tgwatchspam

A self-hosted Telegram bot that protects community groups from spam — crypto offers, job scams, and recruitment messages. Rule-based, predictable, zero cloud dependency.

Go SQLite Long Polling ~15 MB RAM Self-hosted

Overview

tgwatchspam is a self-hosted spam filter for Telegram community groups. It blocks unwanted messages about crypto, job scams, and other common spam patterns. Runs on any private VPS, requires no web interface, and is managed entirely through Telegram commands.

No web panel, no external services All management happens inside Telegram. The bot stores data locally in a SQLite file and communicates via long polling — no domain, no SSL certificate, no open ports required.
FEAT-001 / 002

Word & Regex Filters

Blocked words and RE2 regex patterns, per chat. Substring match with no word boundaries.

FEAT-003

Lookalike Normalization

Cyrillic/Latin character substitution is detected and normalized before matching.

FEAT-015

Button Verification

New members must tap an inline button to prove they're human before posting.

FEAT-007

Sandbox Mode

Restricts new members to text-only (no media, no link previews) for a configurable period.

FEAT-010

Name Filter

Checks new members' display name and username against spam filters on join.

FEAT-006

Multi-Chat

One bot instance manages multiple groups simultaneously. All data is isolated per chat.

Setup

Requirements

First Run

  1. Clone and build

    git clone https://github.com/sqerison/tgwatchspam
    cd tgwatchspam
    go build -o tgwatchspam ./cmd/bot
  2. Create your .env file

    cp .env.example .env
    # Edit .env and fill in BOT_TOKEN and SUPERADMIN_ID
  3. Run the bot

    ./tgwatchspam

    On startup the bot automatically registers all commands with BotFather so users get autocomplete when typing /tgwatch_.

  4. Add the bot to your group

    Grant it admin rights with: Delete messages, Ban users, Restrict members.

  5. Verify it works

    /tgwatch_show settings

    The bot should reply with the default settings for your chat.

Deployment

Copy the binary and .env to the server, then create a systemd service so it restarts automatically.

scp tgwatchspam .env user@yourserver:/opt/tgwatchspam/

Create /etc/systemd/system/tgwatchspam.service:

[Unit]
Description=tgwatchspam Telegram bot
After=network.target

[Service]
WorkingDirectory=/opt/tgwatchspam
ExecStart=/opt/tgwatchspam/tgwatchspam
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
systemctl enable --now tgwatchspam
journalctl -u tgwatchspam -f   # view live logs
No open ports needed The bot uses long polling — it connects outbound to Telegram. You do not need to configure any firewall rules, reverse proxy, or SSL certificate.

Spam Filtering

Every incoming message passes through a three-stage pipeline:

  1. Normalize — Cyrillic lookalike characters are mapped to their Latin equivalents and the text is lowercased.
  2. Word match — The normalized text is checked against the blocked word list using substring matching (no word boundaries).
  3. Regex match — The normalized text is tested against all RE2 patterns with (?i) prepended automatically.

Word Filtering (FEAT-001)

Words are stored normalized. Matching is case-insensitive and uses substring logic — доход matches доходності, and binance matches binance/bybit (the slash does not break the match, fixing the main flaw of the previous bot).

/tgwatch_add word bitcoin
/tgwatch_add word гарна плата

# Bulk import — one word per line:
/tgwatch_add word
bitcoin
usdt
крипта
заробіток
digital nomad

Regex Filtering (FEAT-002)

Patterns use Go's RE2 syntax. The (?i) flag is added automatically — you do not need to write it. Example patterns:

PatternMatches
u[\.\s]?s[\.\s]?d[\.\s]?tusdt, u.s.d.t, u s d t
\+3[4-9]\d{7,}Non-Italian phone numbers
зп.{0,5}\d+\$Salary spam like "ЗП: 200$"
earn.{0,10}\$\d+"earn $200/day" patterns

Cyrillic/Latin Normalization (FEAT-003)

Spammers bypass filters by mixing scripts — for example writing Rоblox with a Cyrillic о. The bot normalizes these before matching:

CyrillicMaps toCyrillicMaps to
а е о р с х у іa e o p c x y iА В Е К М Н О Р С Т Х УA B E K M H O P C T X Y
Testing filters Use /tgwatch_check <text> to test any message against the current filters without taking any action. The bot will show exactly which rule matched and what would have happened.

Spam Actions

When a message matches a filter, the bot takes a configurable action. The message is always deleted first.

ActionWhat happensDefault
banMessage deleted + user permanently bannedYes
kickMessage deleted + user removed (can rejoin via invite)
muteMessage deleted + user muted for N hours
deleteMessage deleted only, no further action
/tgwatch_set action ban
/tgwatch_set action mute
/tgwatch_set mute_duration 24    # hours, used when action=mute

After every spam detection the bot posts a notification in the group:

Spam removed User: @spammer
Matched word: bitcoin
Action: permanently banned

New Member Controls

Button Verification (FEAT-015)

When enabled, new members must tap an inline button before they can post anything. This stops the vast majority of bot accounts that join and immediately post spam.

Flow:

  1. User joins → bot fully restricts them and posts a public message with a button: "I'm not a bot — let me in"
  2. User taps the button → bot verifies it's the same person → removes the verification message
  3. If they don't tap within the timeout → bot kicks them automatically
Button security Only the user who joined can click their own button. If another user taps it, they receive a private popup: "This button is not for you."
/tgwatch_set verification on
/tgwatch_set verification_timeout 5    # minutes before auto-kick (default: 2)

Sandbox Mode (FEAT-007)

After passing verification (or immediately if verification is off), new members can be placed in sandbox mode. During sandbox they can post regular text messages but cannot send media, stickers, GIFs, or link previews.

Sandbox and verification work together:

/tgwatch_set sandbox on
/tgwatch_set sandbox_duration 24    # hours (default: 24)
/tgwatch_unrestrict                 # reply to a user's message to lift sandbox early

Name Filter (FEAT-010)

When enabled, the bot runs a new member's display name and username through the same word/regex filters on join. Accounts like crypto_earn_fast or usdt_exchanger are kicked before they post anything.

/tgwatch_set name_filter on

Admin Authorization

All management commands require the user to be a group admin. Non-admins who send commands are silently ignored — no error reply, no indication that the command was received. This avoids tipping off spammers.

LevelWhoWhat they can do
Admin Any Telegram group admin All filter and settings commands within their chat
Superadmin User ID set in SUPERADMIN_ID Everything + /tgwatch_copy via DM with the bot

Admin status is verified live via Telegram's getChatMember API on every command call — no caching, no stale permissions.

Spam Log

Every filtered message is recorded in SQLite with full context: timestamp, user, matched rule, original message text, and action taken.

/tgwatch_log            # last 10 entries
/tgwatch_log 25         # last 25 entries (max 50)
/tgwatch_log clear      # clear the log for this chat (inline button)

Multi-Chat Support

One bot instance manages multiple groups simultaneously. All data — word lists, regex patterns, settings, spam log — is completely isolated per chat_id. No data is shared between chats unless explicitly copied.

# Superadmin only, send this in a DM with the bot:
/tgwatch_copy -100123456789 -100987654321

The bot will ask for confirmation before overwriting. Existing data in the target chat is replaced, not merged.

Command Reference

All commands use the /tgwatch_ prefix to avoid conflicts with other bots in the same group.

Word Filters

CommandWhoDescription
/tgwatch_add word <text>AdminAdd a blocked word or phrase
/tgwatch_add word + linesAdminBulk add — one word per line after the command
/tgwatch_remove word <text>AdminRemove a word
/tgwatch_list wordsAdminList all blocked words
/tgwatch_clear wordsAdminRemove all words (inline button confirmation)

Regex Filters

CommandWhoDescription
/tgwatch_add regex <pattern>AdminAdd a RE2 regex pattern ((?i) applied automatically)
/tgwatch_remove regex <pattern>AdminRemove a pattern
/tgwatch_list regexAdminList all patterns
/tgwatch_clear regexAdminRemove all patterns (inline button confirmation)

Spam Action Settings

CommandWhoDescription
/tgwatch_set action delete|mute|ban|kickAdminAction when spam is detected (default: ban)
/tgwatch_set mute_duration <hours>AdminMute duration (used when action=mute)

New Member Controls

CommandWhoDescription
/tgwatch_set verification on|offAdminRequire new members to tap a button before posting
/tgwatch_set verification_timeout <min>AdminMinutes to verify before auto-kick (default: 2)
/tgwatch_set sandbox on|offAdminRestrict new members to text-only for sandbox_duration
/tgwatch_set sandbox_duration <hours>AdminHow long sandbox lasts (default: 24h)
/tgwatch_set name_filter on|offAdminKick new members whose name/username matches spam filters
/tgwatch_unrestrictAdminReply to a message to manually lift sandbox on that user

Management

CommandWhoDescription
/tgwatch_show settingsAdminShow all current settings for this chat
/tgwatch_check <text>AdminTest text against filters without taking action
/tgwatch_log [n]AdminShow last N spam entries (default 10, max 50)
/tgwatch_log clearAdminClear the spam log for this chat (inline button confirmation)
/tgwatch_cleanAdminDelete all bot replies and admin commands from chat
/tgwatch_helpAdminShow all available commands in chat
/tgwatch_copy <src_id> <dst_id>SuperadminCopy all settings from one chat to another (use in DM)

Configuration

Configuration is loaded from a .env file in the working directory.

VariableRequiredDefaultDescription
BOT_TOKENYesTelegram bot token from @BotFather
SUPERADMIN_IDYesNumeric Telegram user ID of the owner (from @userinfobot)
DATABASE_PATHNo./data/bot.dbPath to the SQLite database file
LOG_LEVELNoinfodebug / info / warn / error

Per-Chat Settings

These are stored in SQLite and changed via bot commands. Each chat has independent settings.

SettingDefaultCommand
Spam actionban/tgwatch_set action
Mute duration24h/tgwatch_set mute_duration
Verificationoff/tgwatch_set verification
Verification timeout2 min/tgwatch_set verification_timeout
Sandboxoff/tgwatch_set sandbox
Sandbox duration24h/tgwatch_set sandbox_duration
Name filteron/tgwatch_set name_filter

Feature Index

Each feature has a tag in source code comments (e.g. [FEAT-001]). Search with grep -r "FEAT-001" .

IDFeaturePrimary File
FEAT-001Word/phrase filteringinternal/filter/words.go
FEAT-002Regex filteringinternal/filter/regex.go
FEAT-003Cyrillic/Latin normalizationinternal/filter/normalize.go
FEAT-004Spam actionsinternal/bot/handlers.go
FEAT-005Admin authorizationinternal/admin/admin.go
FEAT-006Multi-chat supportinternal/storage/storage.go
FEAT-007Sandbox modeinternal/bot/handlers.go
FEAT-008/tgwatch_check commandinternal/bot/handlers.go
FEAT-009Spam loginternal/storage/storage.go
FEAT-010Name/username filter on joininternal/bot/handlers.go
FEAT-011Bulk word importinternal/bot/handlers.go
FEAT-012Copy settings between chatsinternal/bot/handlers.go
FEAT-013/tgwatch_clean commandinternal/bot/handlers.go
FEAT-014Spam notification in chatinternal/bot/handlers.go
FEAT-015Button verification for new membersinternal/bot/handlers.go


tgwatchspam — self-hosted, open source, written in Go. github.com/sqerison/tgwatchspam