What it is
tg-digest has two parts. A read-only scraper collects messages from the Telegram channels you choose and writes them to a JSON file. A control bot lets you drive that scraper entirely from a private Telegram chat with your own bot.
main.pyThe bot never scrapes by itself. When you send /run, it starts main.py as a separate process and reports progress back in the chat.
Quick start
- Install.
pip install -r requirements.txt - Create your bot. Talk to @BotFather in Telegram, send
/newbot, follow the prompts, and copy the token it gives you. - Fill in
.env. Copy.env.exampleto.envand set: API_ID=1234567 API_HASH=... # from my.telegram.org BOT_TOKEN=... # from @BotFather OWNER_ID=... # your numeric Telegram id (or leave blank) - Start the bot. python bot.py If you left
OWNER_IDblank it prints a pairing code in the terminal — send/claim <code>to your bot from your own account. - Log in. Send
/loginand scan the QR with a second device. Then/run today.
Logging in
The bot logs in your user account (not the bot) so the scraper can read your channels. Do it once; the session is saved.
| Command | What it does |
|---|---|
| /login | QR login (recommended). Shows a QR image; scan it from a second device (Telegram → Settings → Devices → Link Desktop Device). Refreshes up to 3 times, ~2–3 min. |
| /login_phone | Fallback: type your phone, then the code Telegram sends. Type the code with separators (e.g. 1-2-3-4-5). Telegram may reject codes sent through a chat — if so, use /login. |
| /status | Shows whether you're logged in, your first name, and only the last 2 digits of your phone. |
| /logout | Confirms, logs out on Telegram's side, and deletes the local session file. |
| /cancel | Aborts a login in progress and clears the state. |
Anything you type in the bot chat is stored on Telegram's servers. The bot deletes your code/password messages immediately, but for the 2FA password the terminal login (python main.py --terminal-login) is safest.
All commands
Every command is owner-only and works in a private chat with your bot. Anyone else gets no reply at all.
Channels
| Command | What it does | Example |
|---|---|---|
| /channels [tag] | Lists your channels, 20 per page, with paused and invalid marks. Optional tag filter. | /channels jobs |
| /add <link|@name|id> [| label] | Validates the channel (read-only) and appends it. Shows the resolved title and whether you're a member. | /add @news | Daily |
| /remove <#|link|name> | Removes a channel after confirmation. If the match is ambiguous it asks which one. | /remove 3 |
| /pause <#> | Marks a line off — the scraper skips it and reports it as paused. | /pause 2 |
| /resume <#> | Removes the off mark. | /resume 2 |
| /tag <#> <tag> | Adds a tag so you can run a subset with group:<tag>. | /tag 1 news |
| /untag <#> <tag> | Removes a tag. | /untag 1 news |
| /groups | Lists your tag groups with counts. | /groups |
Running
| Command | What it does | Example |
|---|---|---|
| /run <window> [options] | Runs the scraper and sends you the digest. See section 6 for options. | /run today |
| /preview <window> [group:<tag>] | Dry run — shows how many messages each channel has. No file is written. | /preview 24h |
| /stop | Kills the running scrape (the whole process tree). | /stop |
| /last | Resends the most recent digest file(s). | /last |
Schedules
| Command | What it does | Example |
|---|---|---|
| /schedule add <HH:MM> <window> [group:<tag>] | Adds a daily run at that IST time (max 5 schedules). | /schedule add 09:00 today |
| /schedule list | Lists schedules with their numbers. | /schedule list |
| /schedule remove <n> | Removes a schedule (after confirmation). | /schedule remove 1 |
| /schedule pause <n> | Temporarily stops a schedule. | /schedule pause 1 |
| /schedule resume <n> | Restarts a paused schedule. | /schedule resume 1 |
Settings & prompts
| Command | What it does | Example |
|---|---|---|
| /settings | Shows every setting and its current value. | /settings |
| /set <key> <value> | Changes one setting (validated). See section 8. | /set dedupe on |
| /prompts | Lists the available summary prompts and marks the selected one. | /prompts |
| /prompt <name> | Sends a prompt's text back to you (as a message, or a .md file if long). | /prompt detailed |
Files, health & safety
| Command | What it does | Example |
|---|---|---|
| /files | Lists recent outputs with buttons to resend any of them. | /files |
| /clean | Deletes outputs older than retention_days (after confirmation). | /clean |
| /find <keyword> [6h|24h|3d] | Searches saved digests locally — no Telegram call. Up to 10 matches with links. | /find result |
| /health | Login state, last run, next schedule, disk space, versions, uptime. | /health |
| /panic | Kill switch (two confirmations). See section 11. | /panic |
channels.txt
Your channel list is a plain text file. The bot edits it for you, but you can also edit it by hand.
# a comment — ignored @news | Daily News | tags=news,jobs | off https://t.me/somechannel | Some Channel -1001234567890| Segment | Meaning |
|---|---|
| entry | @name, https://t.me/name, t.me/name, a post link, or a numeric id. |
| | label | A friendly name shown in /channels and in the digest. |
| | tags=a,b | Tags for grouping. Run just a group with /run today group:a. |
| | off | Pauses the line — the scraper skips it and reports it as paused. |
Old two-column lines (entry | label) keep working unchanged. The file is edited line-by-line, so comments, blank lines and order are preserved, and a channels.txt.bak backup is kept before every change.
Running a digest
The shape of a run command is: /run <window> [options…]
Windows (pick one)
| Window | Means |
|---|---|
| today | From midnight IST until now. |
| yesterday | The whole previous day (IST). |
| 6h · 24h · 3d | The last N hours / days from now. |
| since-last | Everything since the last successful run. |
| <YYYY-MM-DD HH:MM> <YYYY-MM-DD HH:MM> | An explicit IST range: start and end. |
Options (any order, all optional)
| Option | Effect |
|---|---|
| group:<tag> | Only channels carrying that tag. |
| channel:<n> | Only channel number n from /channels. |
| dedupe | Drop near-duplicate messages. |
| min:<N> | Skip messages shorter than N characters (0–500). |
| views | Include view counts. |
| send:no | Run but don't send me the file. |
- One run at a time.
- At least 5 minutes between runs.
- At most 12 runs per day (manual and scheduled share the limit).
- A hard 30-minute timeout, after which the process tree is killed.
While a run is going, the bot keeps one status message updated (at most every 10 seconds) showing the elapsed time and progress like channel 3/12. When it finishes you get a summary — channels ok, messages, skipped channels and reasons, truncated channels, estimated tokens — and the digest arrives as a document. If there are more than 10 parts it asks before sending.
Schedules
A schedule runs a digest for you every day at a fixed IST time. You can keep up to five.
# every day at 9:00 IST, today's messages /schedule add 09:00 today # every evening at 20:30, last 24 hours, only the "news" group /schedule add 20:30 24h group:news /schedule list /schedule pause 2 /schedule resume 2 /schedule remove 1If the bot was off at the scheduled time, it does not run late. When it starts again it tells you what was missed and offers a Run now button. If a run is already going, a scheduled run is skipped with a message.
The bot must be running for schedules to fire. Run it under systemd, tmux or screen — a laptop that sleeps, or Termux with the screen off, will stop it.
Settings
/settings shows all of these. /set <key> <value> changes one — invalid values are rejected without changing anything.
| Key | Values | Default | What it does |
|---|---|---|---|
| format | json · jsonl · txt | json | Output file format. |
| dedupe | on · off | off | Drop near-duplicate messages. |
| min_chars | 0–500 | 0 | Skip messages shorter than this. |
| with_views | on · off | off | Include view counts. |
| max_chars | 50000–1000000 | 300000 | Max size of one output part; larger digests are split. |
| max_per_channel | 100–20000 | 5000 | Cap messages read per channel. |
| retention_days | 1–60 | 7 | Delete output files older than this. |
| autodelete_minutes | 0–1440 | 0 | Auto-delete the bot's sent digest messages (0 = off). |
| summary_prompt | a prompt name | default | Which prompt is embedded in new digests. |
Prompts
A prompt is just text embedded in the digest file as ai_instructions. When you paste the file into an AI, that text tells it what to do. The bot never calls an AI service itself.
| Name | Use it for |
|---|---|
| default | Short instructions embedded in every digest — a good all-round summary. |
| detailed | A full structured brief: coverage, top 10, per-channel, dates table, action items, conflicts, unverified. |
| exam_alerts | Jobs, recruitment and exam updates — notifications, a dates calendar, results, "verify first". |
| news_brief | News and current affairs grouped by topic, with facts vs claims. |
| merge_parts | Combine the summaries of several parts into one final summary. |
| ask | Answer one question from the digest (put your question after QUESTION:). |
Three ways to summarise
Open the digest in any AI chat. The embedded ai_instructions already does the job.
Choose a prompt: /set summary_prompt detailed, then run. Or --summary-prompt detailed on the CLI.
Summarise each part, then give the AI the merge_parts prompt plus the part summaries.
A digest can contain private channel content. Whatever AI service you paste it into will receive that content. The bot itself never contacts an AI service — it only stores, selects and embeds prompt text.
Adding your own prompt
Drop a .md file into prompts/. The first line must be # <name> - <description> and the file must stay under 8000 characters. It then appears in /prompts and can be selected with /set summary_prompt <name>.
Files & search
Lists recent digests (name, size, time, message count) with a button to resend any of them.
Deletes output files older than retention_days, after a confirmation.
Searches saved digests locally — no Telegram call, works with Hindi text. Up to 10 matches with links.
Retention also runs automatically: at startup and once a day, files older than retention_days in the output folder are removed. It only ever touches that folder, and never follows a symlink out of it.
Health & panic
/health is a read-only check-up. /panic is the emergency stop.
Logged in or not · session file present · last run (result, time, duration) · skipped channels from the last run · next scheduled run · free disk space in the output folder · library versions · bot uptime. No secrets, no full phone number.
- Pauses all schedules.
- Stops any running scrape.
- Logs out and deletes the local session file.
- Deletes the output files and
.cache/— but keeps.cache/run_history.jsonand.cache/run.lockso your daily/gap limits survive. - Keeps
channels.txtandsettings.json. - Reports every step honestly, including partial failures.
Troubleshooting
| Symptom | Fix |
|---|---|
| Bot replies to nobody / nothing happens | Check OWNER_ID is your numeric id, or that you claimed ownership with /claim <code>. Non-owners are silently ignored. |
| "Not logged in" | Send /login and scan the QR. Or run python main.py --terminal-login. |
| database is locked | A scrape is using the session file. Wait for it to finish; the bot retries cleanly. |
| QR will not scan | Open the QR from a second device (Settings → Devices → Link Desktop Device) while the bot is still waiting, then /login again. |
| Code rejected right after typing | Telegram may block codes sent through a chat. Use /login (QR) or the terminal. |
| Run refused | You hit a limit — 5-minute gap, 12 runs/day, or a run already in progress. The message tells you which. |
| Schedule did not fire | The bot must be running. A missed run is offered as a Run now button when the bot starts. |
| Help text looks odd | Restart the bot — messages are sent as plain text so underscores and <placeholders> render literally. |
Security
Identified by your numeric Telegram id, never a username. Every handler checks it before doing anything; everyone else is dropped with no reply and no log.
The user client never sends, edits, deletes, forwards, reacts or marks read. An automated scan of the source enforces this.
The scraper runs as a separate process with an argument list — never a shell string — and only validated values reach it.
API keys, tokens, phone number and session files are never logged, never sent to the chat, and never committed to git.
Sensitive inputs (phone, login code, 2FA password) are deleted from the chat right after they are read, but Telegram's servers hold them until then. For the 2FA password, the terminal login is the safest option.
FAQ
| Question | Answer |
|---|---|
| Does it download the files in a channel? | No. It records a label in the media field. For a document it includes the filename (document:notice.pdf); for video, audio, voice, GIF and stickers it records only the type. |
| Does it join channels? | Never from the bot. Joining only happens in the terminal scraper with the explicit --auto-join flag. |
| Can I add or remove channels from the bot? | Yes — /add, /remove, /pause, /resume, /tag, /untag. |
| Does the bot summarise with AI? | No. It embeds a prompt into the JSON; you paste the file into whatever AI you like. |
| How many runs can I do a day? | 12 total, at least 5 minutes apart, one at a time, 30-minute timeout. |
| Where are the digests? | In the output folder on the machine running the bot. /files lists them; /last resends the newest. |