A locally-running Telegram assistant for Snax. It reads all Telegram messages (DMs, groups, channels), stores them in SQLite, and exposes a Telegram bot (@tgsnaxbot) for Q&A, priority management, and message drafting — all powered by a local Ollama LLM (qwen2.5:14b on a 4090 GPU).
Two Telegram clients running in one asyncio loop:
- User client (Telethon MTProto) — reads Snax's messages, syncs to DB, sends messages as Snax via
/replyand/draft - Bot client (@tgsnaxbot) — only responds to Snax (gated by
SNAX_USER_ID), provides all commands - SQLite + FTS5 — message storage with full-text search
- Ollama — local LLM for Q&A, summaries, intent detection, drafting
src/
├── main.py — Entry point, starts both clients via asyncio.gather()
├── config.py — Loads .env, typed config vars
├── database.py — MessageDB class, all SQL, schema, migrations
├── user_client.py — Telethon user client, sync, real-time event handlers
├── bot_client.py — Bot commands, intent handling, free-form Q&A
├── llm.py — Ollama wrapper, system prompts, intent detection
├── query_engine.py — Multi-signal retrieval (FTS, sender, date range, chat name)
├── prioritizer.py — Priority gathering, LLM summarization, formatted output
└── __init__.py
| Table | Purpose |
|---|---|
chats |
All synced chats with metadata, invite_link cache |
users |
Telegram users seen in messages |
messages |
All messages with FTS5 virtual table for search |
contact_notes |
Per-user notes, nicknames, priority tiers (vip/high/normal/low) |
sync_state |
Key-value for sync progress tracking |
dismissed_chats |
Temporarily hidden from priority (auto-undismiss on new message) |
followup_chats |
Follow-up list (not urgent, needs eventual attention) |
friends |
Friend list with last-contact tracking |
ignored_users |
Permanently hidden from priority and unread |
ignored_folders |
Telegram folders to skip during sync |
ignored_chat_ids |
Resolved chat IDs from ignored folders |
tagged_chats |
Chat tags (bd=Biz Dev, pd=PizzaDAO) with composite PK (chat_id, tag) |
/por/priority— Show priority messages (DMs + group @mentions/replies)/done 3, 6, 7— Mark items as handled (by number or @username)/flup 3, 6, 7— Move to follow-up list/flup— View follow-up list/prio 1, 2— Move from follow-up back to priority/mute 3, 5— Permanently ignore users/unmute @user— Remove from ignore list/muted— List ignored users/undone @user— Bring back to priority/dismissed— List dismissed conversations
/bd 3, @user— Tag as Biz Dev/bd— View Biz Dev list/unbd 3— Remove Biz Dev tag/pd 3, @user— Tag as PizzaDAO/pd— View PizzaDAO list/unpd 3— Remove PizzaDAO tag
/reply @user <message>— Send as Snax/draft 3or/draft @user— AI drafts a reply (optional topic)/send— Send the pending draft/edit <text>— Replace draft text and send
/search <query>— FTS keyword search/unread— Summarize unread messages/status— Sync stats
/fren 3, @user— Add friends/fren— View friends + last contact time/unfren @user— Remove friend/note @user <text>— Add contact notes/notes @user— View contact notes/vip @user— Mark as VIP/tier @user <level>— Set priority tier/contacts— List annotated contacts
/folders— List Telegram folders/ignore <folder>— Stop syncing a folder/unignore <folder>— Resume syncing a folder
All commands also work via natural language through intent detection:
- "1 and 3 are done"
- "follow up on 5"
- "mute 2 and 4"
- "make john a vip"
- Free-form questions: "What did John say about the event?"
/p stores a numbered list. Numbers work with /done, /flup, /mute, /draft, and natural language. /flup stores its own numbered list for /prio.
- Unanswered DMs — DMs where the last message is from them (not Snax)
- Unread messages — Any unread message across all chats
- Group mentions — Messages in groups that @mention Snax or reply to Snax's messages
All filtered against dismissed chats, ignored users, and ignored folders. Deduped to one item per conversation.
- Public groups (have username):
t.me/usernamelink - Private supergroups (chat_id
-100{id}):tg://privatepostdeep link - Basic groups: Invite link fetched via
GetFullChatRequestand cached in DB
- First run: Fetches last 500 messages per chat, marks
initial_sync_complete - Subsequent runs: Catch-up sync, only fetches messages newer than last synced ID
- Real-time: Event handlers for new/edited/deleted/read messages
- Auto-undismiss: When someone sends a new message in a dismissed chat, it returns to priority
Stored in .env (gitignored):
TG_API_ID,TG_API_HASH— Telegram API credentialsTG_BOT_TOKEN— Bot token for @tgsnaxbotSNAX_USER_ID— Snax's Telegram user ID (706681092)OLLAMA_MODEL— Default: qwen2.5:14b
cd C:\Users\samgo\OneDrive\Documents\PizzaDAO\Code\tg
venv\Scripts\activate
python -m src.mainFirst run requires interactive phone/code input for the user client session. Session persists in data/snax_user.session.
- Basic groups without invite links can't be deep-linked (Telegram limitation)
ExportChatInviteRequestrequires admin — falls back toGetFullChatRequestfor existing invite links- FTS5 keyword search is simple OR matching, not semantic
- Priority dedupes to one message per conversation — may miss multiple threads in same group