---
name: telegram
description: Use when reading, searching, sending, or managing Telegram messages and folders for the user's personal account. Triggers on "Telegram'a mesaj gönder", "şu kanaldan son mesajları getir", "Telegram'da ara", "okunmamış mesajlar", "Telegram klasörlerini güncelle", "yeni kanalları kategorize et", "fetch telegram dialogs", "telegram inbox", "@username'e şunu yaz", or any direct mention of fetch_all/tg_read/tg_send/tg_search/tg_inbox/apply_folders. Also covers the Telethon-based pipeline at /home/salva/Documents/telegram (auth, session, channels.json, assignments.json).
---

# Telegram Operator (Telethon)

## Overview

Read, search, send, manage, and organize the user's Telegram personal account from the command line via Telethon. All scripts share one venv and one `.session` file at `/home/salva/Documents/telegram/`.

```
                       ┌── tg_read.py    (fetch from a chat)
                       ├── tg_send.py    (send text/file, reply, silent)
   Telethon client ────┼── tg_search.py  (global / scoped search)
   (one .session)      ├── tg_inbox.py   (unread overview, mark-read)
                       └── folder pipeline:
                              fetch_all.py → build_assignments.py → apply_folders.py
```

## Project location

`/home/salva/Documents/telegram/`

| File | Role |
|---|---|
| `api.txt` | api_id / api_hash from my.telegram.org. **Do not commit.** |
| `config.py` | Loads creds → `API_ID`, `API_HASH`, `SESSION_NAME` |
| `telegram_session.session` | Telethon SQLite session. **Do not delete unless re-login needed.** |
| `venv/` | Project venv, activate with `source venv/bin/activate` |
| `requirements.txt` | `telethon>=1.43.1` |
| `tg_utils.py` | Shared helpers: `resolve_chat`, `fmt_msg`, `confirm`, `parse_date` |
| `tg_read.py` | Read messages from a chat |
| `tg_send.py` | Send text and/or file (interactive confirm by default) |
| `tg_search.py` | Search messages globally or in one chat |
| `tg_inbox.py` | Unread overview + mark-as-read (single or bulk) |
| `fetch_all.py` | Snapshot all dialogs + 40 messages each → `data/channels.json` |
| `build_assignments.py` | Static id→folder map → `data/assignments.json` |
| `apply_folders.py` | Push folder layout to Telegram (interactive y/N) |
| `categorize.py` | Library helper used if pipeline grows beyond static dict |
| `data/` | All JSON outputs (channels, assignments, compact, names.tsv, …) |

## Setup

```bash
cd /home/salva/Documents/telegram
source venv/bin/activate    # or:  python3 -m venv venv && pip install -r requirements.txt
```

First run on a new machine: any script will prompt for phone number → SMS code → 2FA password (if set), then writes `telegram_session.session`. Subsequent runs are silent.

If you see `AuthKeyUnregisteredError` or `SessionPasswordNeededError` after a long absence: delete the `.session` file and re-login.

## Common chat references

Every script accepts the same `chat` argument forms:

- `"@username"` — public username (channels, bots, users)
- `12345` or `-1001234567890` — numeric id (positive = user, negative = group/channel)
- `"some name"` — case-insensitive substring of the dialog name; errors out if 0 or >1 matches, listing the candidates
- `"me"` / `"self"` — Saved Messages (your own DM-to-self chat)

## Reading

```bash
python tg_read.py "@durov"                       # last 20 messages
python tg_read.py "Born2beroot" --limit 100
python tg_read.py "@channel" --since 2026-04-01  # since a date
python tg_read.py "@x" --search "CVE"            # filter inside the chat
python tg_read.py "@x" --json                    # machine-readable output
python tg_read.py "@x" --mark-read               # also clear the unread badge
```

Output format: `id │ YYYY-MM-DD HH:MM │ sender(20) │ text(200)` — one row per message, `--json` flips to a JSON array with `{id,date,sender_id,text,has_media,reply_to}` per item.

## Sending

```bash
python tg_send.py "@user" "Hello"                       # interactive [y/N]
python tg_send.py "me" "note to self" --yes             # auto-confirm
python tg_send.py "@chan" "Caption" --file report.pdf
python tg_send.py "@x" "" --file img.png --caption "ss" # file-only
python tg_send.py "@x" "Reply" --reply-to 12345
python tg_send.py "@x" "ping" --silent                  # no notification
python tg_send.py "@x" "<b>bold</b>" --parse html
```

**Send safety policy** — by default, `tg_send.py` prints a preview of the destination + payload and asks `Gönder? [y/N]` before transmitting. Pass `--yes` (or `-y`) to skip the prompt for scripted/automated runs. This matches the convention used by `apply_folders.py`.

Default text parse mode is **markdown**. Use `--parse html` for HTML-style entities (`<b>`, `<i>`, `<a href=…>`), or `--parse none` for plain.

## Searching

```bash
python tg_search.py "ransomware"                       # global, last 50 hits
python tg_search.py "Putin" --since 2026-04-01 -n 200
python tg_search.py "kitap" --chat "E Kitap PDF"       # scoped to one chat
python tg_search.py "report" --chat me
```

Global search uses Telegram's server-side message index. Each hit is prefixed with the chat's title in `[brackets]`. Scoped search (`--chat`) is faster and avoids the per-chat title resolution lookup.

## Inbox / unread management

```bash
python tg_inbox.py                            # ranked by unread count
python tg_inbox.py --top 20
python tg_inbox.py --include-archived         # include archived folder
python tg_inbox.py --mark-read "Born2bero"    # clear ONE chat
python tg_inbox.py --mark-all-read            # clear EVERY unread (asks y/N)
python tg_inbox.py --mark-all-read --yes      # … or skip prompt
```

The bulk `--mark-all-read` is destructive on the unread badge state and irreversible — there is no "mark-as-unread" RPC. The script always confirms unless `--yes`.

## Folder pipeline (≈600 dialogs → 9 folders)

3-stage workflow for organizing dialogs into Telegram client-side folders:

```bash
python fetch_all.py             # ~1-3 min, refreshes data/channels.json
python build_assignments.py     # warns about ⚠ unassigned ids
# → if warnings: edit build_assignments.py:A, add the new ids, rerun
python apply_folders.py         # interactive y/N to push to Telegram
```

### Folder schema (current — titles capped at 12 chars by Telegram)

| Emoji | Title | Scope |
|---|---|---|
| 🛡 | `Güvenlik` | Cybersec, hacking, intel feeds, OSINT, ham radio |
| ☁ | `Logs & Cloud` | Cloud account dumps, ULP/redline logs, cracked services |
| ⚔ | `Rus-Ukrayna` | Russia/Ukraine war channels, both sides + Western trackers |
| 🕌 | `Ortadoğu` | Middle East news (Arabic/Persian/Turkish/English) |
| 🎖 | `Askeri Jeo` | Turkish military, geopolitics, MGK, defense industry |
| 📚 | `E-Kitap` | E-books, audiobooks, manga, KPSS/YKS material |
| 🌐 | `Dil & Kurs` | Russian/Swahili/English language groups, Udemy/PacktPub |
| 📈 | `Finans` | Borsa İstanbul, trading, stock tips, central bank |
| 💬 | `Sosyal` | Twitch, social, hobby groups, anything else |

Full id→folder map: `build_assignments.py:A` (~260 entries). Edit the dict, **never** edit `data/assignments.json` directly — `build_assignments.py` regenerates it.

### New-channel triage (unassigned id heuristic)

When `build_assignments.py` reports `⚠ assignment eksik`, read the channel's name and first messages from `data/channels.json`, then assign by these rules (first match wins):

```
HACK / CVE / exploit / SOC / OSINT / red team / siber       → Güvenlik
cloud-free / ulp / redline / cracked / leaked logs / vbv     → Logs & Cloud
Ukraine / Russia / Donbas / Kyiv / Москва (war context)      → Rus-Ukrayna
Arabic-script (ar/fa) news, Israel/Gaza/Syria/Iran            → Ortadoğu
TSK / SİHA / NATO / geopolitics / military doctrine          → Askeri Jeo
PDF / kitap / e-book / sesli kitap / manga / KPSS / YKS      → E-Kitap
Udemy / Coursera / Russian/Swahili/Arabic/French/IELTS       → Dil & Kurs
borsa / hisse / trading / forex / kripto                     → Finans
twitch / hobby / chat / barahol / banter                     → Sosyal
```

Edge cases:
- Russia/Ukraine **doctrine** (not war news) → `Askeri Jeo`, not `Rus-Ukrayna`.
- Stock-tip Udemy channels → `Finans`, not `Dil & Kurs`.
- Sesli Kitap / Manga / KPSS folded into `E-Kitap`.

## Telethon API constraints

- `DialogFilter.id` 0 and 1 are reserved (All Chats, etc.); `apply_folders.py` skips them.
- Folder titles capped at **12 characters** by Telegram. Telegram allows up to 30 folders (100 with Premium); current schema uses 9.
- `iter_dialogs(archived=None)` returns both normal and archived; `archived=False` (default in `tg_inbox.py`) returns only normal.
- `iter_messages(entity, search=...)` is server-side full-text; `iter_messages(None, search=...)` is the global search.
- Rate limits: don't run `fetch_all.py` more than ~once per hour for accounts with many dialogs (`FloodWaitError`). For sending in tight loops, sleep ≥1s between messages or be ready to handle `FloodWait`.
- `client.send_read_acknowledge(entity)` clears unread; there is no inverse RPC to mark unread.

## Auth & secrets

- `api.txt` and `telegram_session.session` are **as good as a password**: anyone with both can read all your messages and send as you. Keep them out of git, dotfiles sync, and shared backups.
- The MTProto session is bound to the device fingerprint Telethon presents. Telegram → Settings → Devices lists active sessions; revoke "TelegramTUI / linux" entries you don't recognize.
- 2FA password (cloud password) is **not** stored in `.session`; you'll be prompted on first login if it's set.

## When NOT to run

- `apply_folders.py` overwrites each folder's `include_peers` — manual folder rearrangements in the Telegram client are lost. Always confirm before pushing.
- `tg_send.py` and `tg_inbox.py --mark-all-read` are destructive in the "user-visible-side-effect" sense; default behavior is interactive confirm. Don't `--yes` blindly in a script unless the destination/payload is hard-coded and reviewed.
- `fetch_all.py` more than ~hourly: triggers `FloodWaitError` for large accounts.

## Snippets cookbook

```python
# One-off custom run inside the same venv:
import asyncio
from telethon import TelegramClient
from config import API_ID, API_HASH, SESSION_NAME
from tg_utils import resolve_chat, fmt_msg

async def main():
    async with TelegramClient(SESSION_NAME, API_ID, API_HASH) as c:
        e = await resolve_chat(c, "@durov")
        async for m in c.iter_messages(e, limit=5):
            print(fmt_msg(m))

asyncio.run(main())
```

```python
# Live monitoring (event handler):
from telethon import events
@client.on(events.NewMessage(chats=["@channel1", "@channel2"]))
async def handler(event):
    print(event.chat.title, event.message.text)
client.run_until_disconnected()
```

```python
# Forward N messages from A to B:
msgs = await client.get_messages(src_entity, ids=[101, 102, 103])
await client.forward_messages(dst_entity, msgs)
```

```python
# Download all media from a chat into ./media/:
async for msg in client.iter_messages(entity, limit=100):
    if msg.media:
        await msg.download_media(file="media/")
```

## Related skills

- `obsidian-tasks` — track Telegram-organization items as tasks.
- `news-crawler`, `freshrss`, `freshrss-reader` — alternative news ingestion paths; `Askeri Jeo`/`Ortadoğu` Telegram channels overlap with FreshRSS feeds.
- `obsidian-linux` — once messages are extracted, can convert into vault notes via `notesmd-cli`.
