Skip to content

Repository files navigation

Telegram Spam Sweeper

A small Python tool that keeps a personal Telegram inbox clean. Run from cron every minute, it scans recent unread chats and removes likely spam: unsolicited DMs from strangers, and groups or channels you were added to without consent. Anything that scores as spam is reported to Telegram, the sender is blocked, and the conversation is deleted (for groups: reported and left).

It is deliberately conservative about who can even be considered spam. Contacts, anyone you have ever replied to, verified accounts, official Telegram service accounts, and groups you created are structurally exempt — they are filtered out before any scoring runs, so no keyword or scoring bug can ever touch a real conversation.

Because the removal action is irreversible, every flagged chat — sender, score, matched rules, and the full message text — is written to sweeper.log before anything is deleted, and a --dry-run mode lets you audit decisions without acting on them.

How it works

Telegram bots cannot read a personal inbox, so the sweeper logs in as you using Telegram's client API (MTProto) via Telethon. A one-time interactive login stores a session file; after that, cron runs authenticate silently.

Each run:

  1. Fetches your 100 most recent dialogs and keeps only unread ones. A quiet inbox means the run exits in a couple of seconds.
  2. For each unread private chat: skips trusted senders (see above), then scores the incoming messages against the spam heuristics.
  3. For each unread group/channel: skips groups you created or were added to by a contact, then scores the title plus recent messages.
  4. Chats scoring at or above the threshold are logged in full, then reported as spam, blocked, and deleted.

Scoring heuristics

Defined in heuristics.py (threshold: 3 points):

Signal Points
URL or t.me invite link +2
Crypto/investment keywords (USDT, forex, "guaranteed profit", …) +2
Telegram-Premium / gift / prize bait +2
"Message me on WhatsApp"-style platform redirects +2
Generic spam phrases ("hello dear", job-offer bait, casino, …) +2
Phone number in message +1
Emoji blast (5+ emoji) +1
Stranger baseline — non-contact, unverified, no reply history (DMs) +3
Sender has no username (DMs only) +1
Empty or emoji-decorated display name (DMs only) +1
Media-only opener — photo/sticker, no text (DMs only) +1

The stranger baseline alone meets the threshold: every DM from a non-contact, unverified sender you have never replied to is treated as spam, regardless of content. This is a deliberate policy choice for an account that doesn't expect legitimate first contact from strangers — it catches romance/pig-butchering bait that opens with an innocuous "hi" and no links. The other signals still accumulate so the audit log shows how spammy a flagged chat looked beyond mere strangerhood. To accept legitimate stranger DMs instead, lower the baseline in classify_dm() and rely on the content signals.

Signals are unioned across a chat's messages, so spam split into several short messages scores the same as one combined blast. The keyword lists at the top of heuristics.py are the tuning knobs — edit them freely; the tests pin the behavior around them.

Architecture

┌─────────────┐   every minute   ┌─────────────────────────────────────┐
│    cron      │ ───────────────▶ │ sweeper.py                          │
└─────────────┘                  │  CLI, lock file, logging            │
                                 │  Telethon I/O (fetch, report,       │
                                 │  block, delete)                     │
                                 └──────┬───────────────────┬──────────┘
                                        │ pure calls        │ MTProto
                                        ▼                   ▼
                              ┌──────────────────┐   ┌──────────────┐
                              │ heuristics.py     │   │ Telegram API │
                              │  score_text()     │   └──────────────┘
                              │  classify_dm()    │
                              │  classify_group() │
                              └──────────────────┘

The design keeps a hard boundary between decision and action:

  • heuristics.py — pure functions, no I/O, no Telethon imports. Takes plain values (message texts, is_contact, has_outgoing, …) and returns a Verdict(is_spam, score, reasons). Fully unit-tested in test_heuristics.py (22 tests).
  • sweeper.py — everything with side effects: argument parsing, .env config, the Telethon client, fetching dialogs/messages, and executing verdicts. Thin by design; the logic worth testing lives in heuristics.py.

Operational safeguards in the wiring:

  • Lock file (sweeper.lock) — prevents overlapping runs if a tick runs long; stale locks (>10 min, e.g. from a crash) are reclaimed automatically.
  • Rate limits — Telegram throttles bulk report/block actions on free accounts. On a FloodWaitError the run stops cleanly and the next minute's run resumes, so a large backlog clears over hours without errors.
  • Audit log (sweeper.log) — full message text of every flagged chat is recorded before deletion; nothing disappears without a trace.

Files

File Purpose
sweeper.py Cron entry point: CLI, Telethon I/O, locking, logging
heuristics.py Pure spam-scoring logic and keyword lists
test_heuristics.py Unit tests for the heuristics
.env Your API credentials (git-ignored; see .env.example)
sweeper.session Telethon auth session created by --login (git-ignored)
sweeper.log Audit log of every flagged chat and action taken
cron.log Raw stdout/stderr from cron runs

Setup

Requires Python 3.10+ on macOS or Linux.

1. Install dependencies

python3 -m venv .venv
.venv/bin/pip install -r requirements.txt

2. Get API credentials

Go to https://my.telegram.orgAPI development tools, log in with your phone number, and create an app (any name/platform). Copy the api_id and api_hash, then:

cp .env.example .env   # paste your api_id / api_hash into it

3. Log in once

.venv/bin/python sweeper.py --login

Telegram texts you a login code; entering it saves sweeper.session, which all future runs reuse silently. You never enter the code again.

4. Test the decisions before going live

.venv/bin/python sweeper.py --dry-run

Dry-run logs everything it would remove to sweeper.log without touching anything. Recommended: run dry for a day (see Deployment), skim the log, and tune the keyword lists in heuristics.py if anything looks wrong.

Two extra flags help when auditing decisions:

  • --verbose / -v — log every dialog examined and why it was skipped (already read) or cleared (trusted sender, or score below threshold, with the score and matched rules shown).
  • --include-read — also scan chats you've already opened. The normal cron run only looks at unread chats, so spam you've previously read is invisible to it; run this once (with --dry-run -v first) to sweep the existing backlog.

Deployment

The sweeper deploys as a per-minute cron job on any always-on machine where you completed the login step (the session file must be present).

crontab -e

Start with the dry-run line:

* * * * * cd ~/Sites/telegram && .venv/bin/python sweeper.py --dry-run >> cron.log 2>&1

After a day, review the audit log:

grep FLAGGED sweeper.log

When you're happy with its judgment, remove --dry-run from the crontab line to go live.

macOS note: cron jobs may need Full Disk Access (System Settings → Privacy & Security) depending on where the project lives, and the machine must be awake for ticks to fire. A missed tick is harmless — the next run picks up where things left off.

Monitoring: the sweeper is self-healing (locking, rate-limit backoff, resume-next-run), so monitoring is just reading the logs. sweeper.log shows what was flagged and why; cron.log catches anything unexpected. If the session ever becomes invalid (e.g. you terminate it from Telegram's Settings → Devices), runs log not logged in and exit — rerun --login.

Tests

.venv/bin/python -m unittest test_heuristics

The tests cover the scoring signals, threshold behavior, and — most importantly — the trust short-circuits that keep contacts and real conversations unreachable by the deletion path. If you tune the keyword lists, run the tests to make sure the guarantees still hold.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages