Not a bot. Not a script. It's you β but powered by neural networks.
NexusTG is your digital ghost in the machine. It lives inside your Telegram account, reads your chats, learns your vibe, and speaks with your voice β literally.
While you sleep, work, or exist offline, your AI Twin keeps the conversation flowing. It doesn't just reply; it thinks, hesitates, makes typos, and reacts like a real human would.
"The future isn't about chatbots answering for you. It's about AI becoming indistinguishable from you."
| Module | What It Does | The Vibe |
|---|---|---|
| π§ AI Twin | Auto-replies using Gemini with your personality, custom prompts per chat, realistic typing delays, and "sleep mode" | Your digital doppelgΓ€nger |
| π΅οΈ Ghost Saver | Secretly saves deleted/edited messages and self-destructing media to a private dump chat with user profiles | Digital black box |
| π Voice Hacker | Transcribes voice messages to text automatically, summarizes long audio (>1 min) | Your personal stenographer |
| π Fake Activity | Shows "typing...", "recording video", or "playing game" status for hours | Social engineering toolkit |
| π§ Manual AI (.ai) | Type .ai analyze this in any chat for instant context-aware AI responses |
Jarvis in your pocket |
| π Smart Shopping | Convert "buy milk, bread, eggs" into beautiful checklists automatically | Life organizer |
| π¬ Media Downloader | Auto-downloads TikTok/Instagram Reels without watermarks | Content vault |
| π Fact Checker | Instantly verifies claims using Google Search + academic sources | Bullshit detector |
Dual-core engine:
βββββββββββββββββββ ββββββββββββββββββββ
β Your Telegram β β Settings Bot β
β Account βββββββΊβ (Aiogram) β
ββββββββββ¬βββββββββ ββββββββββ¬ββββββββββ
β β
βΌ βΌ
ββββββββββββββββββββββββββββββββββββββββ
β NexusTG Core Engine β
β βββββββββββββββ βββββββββββββββ β
β β Pyrogram β β Gemini β β
β β Userbot βββββΊβ AI β β
β βββββββββββββββ βββββββββββββββ β
β βββββββββββββββ βββββββββββββββ β
β β SQLite β β yt-dlp β β
β β Database β β Media Proc β β
β βββββββββββββββ βββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββ
- π€ Aiogram β Controls the settings UI through a dedicated bot
- β‘ Pyrogram β The userbot that actually lives in your account and intercepts messages
- π§ Gemini API β Neural network brains with fallback chains
- πΎ SQLite β Local encrypted storage for configs and message cache
- Python 3.11+ or Docker
- Telegram account (obviously)
- 3 brain cells
Run as Administrator:
powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://raw.githubusercontent.com/newfpv/NexusTG/main/install.ps1 | iex"The installer will:
- Install Git, Python, and Docker if missing
- Clone the repo to
%USERPROFILE%\NexusTG - Create a desktop shortcut "Start NexusTG"
- Launch the bot
bash <(curl -sL "https://raw.githubusercontent.com/newfpv/NexusTG/main/install.sh")Installs to ~/NexusTG with systemd service support.
git clone https://github.com/newfpv/NexusTG.git
cd NexusTG
cp .env.example .env
docker compose up -d --buildgit clone https://github.com/newfpv/NexusTG.git
cd NexusTG
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
python -m core.mainOnce running, open your bot in Telegram:
- Send
/startβ Claim admin rights - Enter API credentials:
api_id&api_hashfrom my.telegram.org- Gemini API keys from Google AI Studio
- Authorize your account β Enter phone β Confirm code via inline numpad
- Done. Welcome to the Matrix.
- Set different personalities for different chats (Boss = professional, Friends = chill)
- Configure "Sleep Mode" so AI ignores messages at night (you need to appear offline sometimes)
- Enable "Human Mode" for realistic typing patterns with random pauses and typos
The crown jewel. It doesn't just reply β it performs.
Human Simulation Features:
- β±οΈ Smart Delays: Reads message length Γ 0.05s per character (simulates reading)
- β¨οΈ Jagged Typing: Types in bursts with pauses (1.5-3.5s typing, 0.5-2s breaks)
- π² Imperfection: 5% chance of typo β correction flow
- π Reaction System: Sends [LIKE] β converts to emoji reaction
- π« Ignore Logic: 10% chance to ignore non-questions (like a real busy human)
Context Awareness:
- Parses YouTube links (title, description, subtitles)
- Understands replies and forwarded messages
- Remembers conversation history (last 50 messages)
- Handles images, voice, and video context
Your personal NSA. Creates a secret forum topic for every contact:
- π Deleted Messages: Captured before vanishing
- βοΈ Edited Messages: Shows before/after diff
- β³ Self-Destructing Media: Screenshots TTL photos/videos
- π€ User Dossiers: Auto-generates profiles with photos and metadata
All evidence is sent with random delays (1-5 min) to avoid flood limits.
Welcome to the engine room. This isn't your grandma's "Hello World" tutorial β we're building cybernetic implants for your digital twin.
NexusTG uses a hot-pluggable architecture. Drop a Python file into modules/ and the core automatically:
- Scans for magic variables (
router,register_userbot, etc.) - Injects UI components into the settings bot
- Mounts Pyrogram handlers to the userbot
- Registers startup tasks and database schemas
Core Components:
| Component | Purpose | Access Pattern |
|---|---|---|
CoreAPI |
Database abstraction | Static methods |
plugins |
Global event bus & shared state | Singleton instance |
FSMContext |
User state management | Aiogram native |
PyrogramFSM |
Userbot-side state tracking | plugins.fsm |
User Message β Pyrogram Handler β Your Module Logic β CoreAPI β SQLite
β
Optional: AI Service (Gemini)
β
Response β Typing Simulation β Send Message
Let's build a real-world module: crypto_tracker.py that monitors crypto prices and alerts when BTC drops.
Create modules/crypto_tracker.py:
"""
Crypto Tracker Module for NexusTG
Monitors cryptocurrency prices and sends alerts
"""
import asyncio
import logging
import aiohttp
from aiogram import Router, F, types
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import StatesGroup, State
from pyrogram import Client, filters
from pyrogram.types import InlineKeyboardMarkup as PyroKeyboard, InlineKeyboardButton as PyroButton
from core.utils import safe_edit, safe_delete, get_cancel_kb, CoreAPI, plugins
from core.config import _
# Initialize router for Aiogram (Settings UI)
router = Router()
# Module identifier - used for DB namespace
MODULE_NAME = "crypto_tracker"
# FSM States for configuration
class CryptoFSM(StatesGroup):
set_threshold = State()
set_currency = State()
# ============================================================================
# SECTION 1: DATABASE LAYER
# ============================================================================
async def _get_cfg() -> dict:
"""
Fetch module configuration from global settings.
Returns empty dict with defaults if not initialized.
"""
cfg = await CoreAPI.get_module_cfg(MODULE_NAME)
return {
"enabled": cfg.get("enabled", False),
"threshold": cfg.get("threshold", 5.0), # 5% drop alert
"currency": cfg.get("currency", "BTC"),
"alerts_enabled": cfg.get("alerts_enabled", True),
"chat_id": cfg.get("chat_id", None) # Where to send alerts
}
async def _upd_cfg(**kwargs):
"""Atomic update of module configuration."""
await CoreAPI.update_module_cfg(MODULE_NAME, **kwargs)
# ============================================================================
# SECTION 2: UI INTEGRATION (Aiogram)
# ============================================================================
async def get_settings_buttons() -> list:
"""
Inject button into Global Settings menu.
Returns list of button rows.
"""
return [
[InlineKeyboardButton(
text="π° Crypto Tracker",
callback_data="crypto_main"
)]
]
async def get_main_menu_buttons() -> list:
"""
Optional: Add quick-access button to main dashboard
"""
cfg = await _get_cfg()
if cfg["enabled"]:
return [
[InlineKeyboardButton(
text=f"π {cfg['currency']} Tracker",
callback_data="crypto_quick_view"
)]
]
return []
@router.callback_query(F.data == "crypto_main")
async def crypto_menu(call: types.CallbackQuery, state: FSMContext):
"""Main configuration menu."""
await state.update_data(menu_msg_id=call.message.message_id)
cfg = await _get_cfg()
status = _("status_on") if cfg["enabled"] else _("status_off")
alert_status = _("status_on") if cfg["alerts_enabled"] else _("status_off")
kb = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(
text=f"Status: {status}",
callback_data="crypto_toggle"
)],
[InlineKeyboardButton(
text=f"Currency: {cfg['currency']}",
callback_data="crypto_set_currency"
)],
[InlineKeyboardButton(
text=f"Threshold: {cfg['threshold']}%",
callback_data="crypto_set_threshold"
)],
[InlineKeyboardButton(
text=f"Alerts: {alert_status}",
callback_data="crypto_toggle_alerts"
)],
[InlineKeyboardButton(
text=_("btn_back"),
callback_data="global_settings"
)]
])
text = (
f"π° <b>Crypto Tracker</b>\n\n"
f"Monitor: <code>{cfg['currency']}</code>\n"
f"Alert on drop: <code>{cfg['threshold']}%</code>\n\n"
f"<i>Tracks price changes in real-time.</i>"
)
await safe_edit(call.message, state, text, kb, parse_mode="HTML")
@router.callback_query(F.data == "crypto_toggle")
async def toggle_crypto(call: types.CallbackQuery, state: FSMContext):
"""Toggle module on/off."""
cfg = await _get_cfg()
new_state = not cfg["enabled"]
await _upd_cfg(enabled=new_state)
# If enabling, set current chat as alert target
if new_state:
await _upd_cfg(chat_id=call.message.chat.id)
await crypto_menu(call, state)
@router.callback_query(F.data == "crypto_set_threshold")
async def set_threshold_prompt(call: types.CallbackQuery, state: FSMContext):
"""Ask user for percentage threshold."""
kb = get_cancel_kb("crypto_main")
await safe_edit(
call.message,
state,
"π Enter percentage drop threshold (e.g., 5.5):",
kb
)
await state.set_state(CryptoFSM.set_threshold)
@router.message(CryptoFSM.set_threshold)
async def save_threshold(message: types.Message, state: FSMContext):
"""Save threshold to DB."""
await safe_delete(message)
try:
value = float(message.text.replace(",", "."))
if 0.1 <= value <= 100:
await _upd_cfg(threshold=value)
# Return to menu
await state.set_state(None)
data = await state.get_data()
if data.get("menu_msg_id"):
mock_call = types.CallbackQuery(
id="mock",
from_user=message.from_user,
chat_instance="",
message=types.Message(
message_id=data["menu_msg_id"],
chat=message.chat,
date=message.date
)
)
await crypto_menu(mock_call, state)
else:
raise ValueError("Out of range")
except ValueError:
msg = await message.answer("β Enter a valid number between 0.1 and 100")
await asyncio.sleep(3)
await safe_delete(msg)
# ============================================================================
# SECTION 3: USERBOT LOGIC (Pyrogram)
# ============================================================================
def register_userbot(app: Client):
"""
Register Pyrogram handlers. This runs once at startup.
app: Pyrogram Client instance
"""
# Background task for price monitoring
@app.on_startup
async def start_monitoring():
"""Start background price checker when userbot connects."""
asyncio.create_task(_price_monitor_loop(app))
# Command handler for quick checks
@app.on_message(filters.me & filters.command("price", prefixes="."))
async def check_price_cmd(client, message):
""".price BTC - Get current price."""
args = message.text.split()
symbol = args[1].upper() if len(args) > 1 else "BTC"
price = await _fetch_price(symbol)
if price:
await message.edit(f"π° {symbol}: <code>${price:,.2f}</code>")
else:
await message.edit(f"β Failed to fetch {symbol}")
# Inline button handler for quick actions
@app.on_callback_query(filters.regex("^crypto_"))
async def handle_inline_buttons(client, callback_query):
"""Handle callbacks from sent messages."""
if callback_query.data == "crypto_refresh":
cfg = await _get_cfg()
price = await _fetch_price(cfg["currency"])
if price:
await callback_query.edit_message_text(
f"π° {cfg['currency']}: <code>${price:,.2f}</code>",
reply_markup=PyroKeyboard([[
PyroButton("π Refresh", callback_data="crypto_refresh")
]])
)
# ============================================================================
# SECTION 4: BUSINESS LOGIC
# ============================================================================
async def _fetch_price(symbol: str) -> float | None:
"""Fetch current price from CoinGecko API."""
try:
async with aiohttp.ClientSession() as session:
url = f"https://api.coingecko.com/api/v3/simple/price"
params = {
"ids": symbol.lower(),
"vs_currencies": "usd"
}
async with session.get(url, params=params, timeout=10) as resp:
if resp.status == 200:
data = await resp.json()
return data.get(symbol.lower(), {}).get("usd")
except Exception as e:
logging.error(f"[Crypto] API Error: {e}")
return None
async def _price_monitor_loop(app: Client):
"""
Background task that checks prices every 60 seconds.
Sends alert if drop exceeds threshold.
"""
await asyncio.sleep(10) # Wait for connection
last_price = None
while True:
try:
cfg = await _get_cfg()
if not cfg["enabled"] or not cfg["alerts_enabled"]:
await asyncio.sleep(60)
continue
current_price = await _fetch_price(cfg["currency"])
if not current_price:
await asyncio.sleep(60)
continue
if last_price and cfg["chat_id"]:
change_pct = ((current_price - last_price) / last_price) * 100
if change_pct <= -cfg["threshold"]:
# ALERT! Price dropped significantly
alert_text = (
f"π¨ <b>Crypto Alert</b>\n\n"
f"π° {cfg['currency']} dropped <code>{abs(change_pct):.2f}%</code>\n"
f"π From: <code>${last_price:,.2f}</code>\n"
f"π To: <code>${current_price:,.2f}</code>"
)
try:
await app.send_message(
cfg["chat_id"],
alert_text,
reply_markup=PyroKeyboard([[
PyroButton("π View Chart", url=f"https://coingecko.com/en/coins/{cfg['currency'].lower()}")
]])
)
except Exception as e:
logging.error(f"[Crypto] Failed to send alert: {e}")
last_price = current_price
except Exception as e:
logging.error(f"[Crypto] Monitor error: {e}")
await asyncio.sleep(60)
# ============================================================================
# SECTION 5: LIFECYCLE HOOKS
# ============================================================================
async def on_startup():
"""
Runs once when bot starts. Use for initialization.
"""
logging.info(f"[{MODULE_NAME}] Module initialized")
# Validate API endpoints, warm up cache, etc.
async def on_shutdown():
"""Cleanup on exit."""
logging.info(f"[{MODULE_NAME}] Shutting down...")All database operations are async and transaction-safe:
from core.db import AsyncSessionLocal, CoreRepository
# Global Configuration (Module Settings)
await CoreAPI.get_module_cfg("module_name") # Returns dict
await CoreAPI.update_module_cfg("module_name", key="value", count=42)
# Chat-Specific Configuration
await CoreAPI.get_chat_module_cfg(chat_id, "module_name")
await CoreAPI.update_chat_module_cfg(chat_id, "module_name", enabled=True)
# Raw Global Config (System Level)
config = await CoreAPI.get_global_config()
# Access: config.api_keys, config.global_prompt, etc.
# Session Management
await CoreAPI.save_session(phone, session_string)
await CoreAPI.delete_session()If you need complex relational data, extend the SQLAlchemy models in core/db.py:
# In core/db.py
class CryptoAlert(Base):
__tablename__ = "crypto_alerts"
id: Mapped[int] = mapped_column(primary_key=True)
symbol: Mapped[str] = mapped_column(String)
target_price: Mapped[float] = mapped_column(Float)
chat_id: Mapped[int] = mapped_column(BigInteger)
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)Modules can communicate without direct imports:
# Publisher (in module A)
plugins.events.publish("price_alert", {
"symbol": "BTC",
"price": 42000,
"change": -5.2
})
# Subscriber (in module B)
def setup_events():
plugins.events.subscribe("price_alert", handle_price_alert)
async def handle_price_alert(data):
print(f"Received alert: {data['symbol']} dropped {data['change']}%")# Cache (TTL = 300 seconds)
plugins.cache.set("btc_price", 42000, ttl=300)
price = plugins.cache.get("btc_price") # Returns None if expired
# Persistent State (survives restart)
await CoreAPI.update_module_cfg("my_module", persistent_state={"last_alert": "2024-01-01"})For multi-step user interactions:
class SetupFSM(StatesGroup):
step1 = State()
step2 = State()
@router.callback_query(F.data == "start_setup")
async def step1_handler(call: types.CallbackQuery, state: FSMContext):
await state.set_state(SetupFSM.step1)
await call.message.edit_text("Enter value 1:")
@router.message(SetupFSM.step1)
async def process_step1(message: types.Message, state: FSMContext):
await state.update_data(val1=message.text)
await state.set_state(SetupFSM.step2)
await message.reply("Enter value 2:")
@router.message(SetupFSM.step2)
async def process_step2(message: types.Message, state: FSMContext):
data = await state.get_data()
val1 = data["val1"]
val2 = message.text
await state.clear() # Always clean up!Always use safe_edit to avoid "Message is not modified" errors:
from core.utils import safe_edit
# Instead of:
await message.edit_text("Same text") # Crashes if unchanged
# Use:
await safe_edit(message, state, "Text", keyboard, parse_mode="HTML")Add strings to language_EN.json:
{
"crypto_tracker": {
"alert_template": "π¨ {symbol} dropped by {percent}%!",
"status_active": "Tracking Active",
"error_api": "Failed to connect to exchange API"
}
}Usage in code:
from core.config import _
text = _("alert_template", symbol="BTC", percent="5.2")Always wrap blocking calls in asyncio.to_thread:
# Bad (blocks event loop):
result = heavy_computation()
# Good:
result = await asyncio.to_thread(heavy_computation)Don't block the handler:
# Fire and forget for non-critical operations:
asyncio.create_task(log_to_external_service(data))
# Or use proper background worker:
asyncio.create_task(background_processor())Respect Telegram's limits:
from pyrogram.errors import FloodWait
try:
await message.edit_text("Updated")
except FloodWait as e:
await asyncio.sleep(e.value)
await message.edit_text("Updated")Clean up downloaded files:
import tempfile
import os
try:
path = await message.download()
# Process file...
finally:
if os.path.exists(path):
os.remove(path)import logging
logger = logging.getLogger(MODULE_NAME)
logger.info("Processing message %s", message.id)
logger.debug("API Response: %s", response)import pytest
from unittest.mock import AsyncMock, patch
@pytest.mark.asyncio
async def test_crypto_fetch():
with patch('aiohttp.ClientSession.get') as mock_get:
mock_get.return_value.__aenter__.return_value.json = AsyncMock(
return_value={"bitcoin": {"usd": 42000}}
)
mock_get.return_value.__aenter__.return_value.status = 200
price = await _fetch_price("bitcoin")
assert price == 42000β Global mutable state without cache:
# Bad
prices = {} # Lost on restart, memory leak
# Good
plugins.cache.set("prices", {})β Synchronous database queries:
# Bad
conn = sqlite3.connect("db.sqlite")
cursor = conn.execute("SELECT * FROM table")
# Good
async with AsyncSessionLocal() as session:
result = await session.execute(select(Model))β Ignoring Pyrogram disconnects:
# Bad
await app.send_message(chat_id, "Hi") # Crashes if disconnected
# Good
if app.is_connected:
await app.send_message(chat_id, "Hi")- Create a repo with your module
- Add
module.jsonmetadata:
{
"name": "crypto_tracker",
"version": "1.0.0",
"author": "@yourhandle",
"description": "Real-time crypto price alerts",
"requires": ["aiohttp>=3.8.0"],
"min_nexus_version": "2.0.0"
}- Submit PR to the community modules repo or distribute via GitHub
Now go build something that would make Satoshi Nakamoto jealous. π
β Star this repo β’ π Report Bug β’ π‘ Request Feature
Built with π€ and too much caffeine by the Nexus Team