Skip to content

Repository files navigation

🛡️ Telegram Chat Gatekeeper bot

Get rid of the unwanted spam joins out of the box

Demo

Join protection

  1. Triggered by new chat members and join requests.
  2. Applies the chat's manual allowlist before checking known spammer sources. Non-allowlisted known spammers are declined/banned immediately and join artifacts are cleaned up.
  3. Restricts the newcomer while verification is active.
  4. Sends a CAPTCHA-style challenge with configurable option count and timeout.
  5. On success, the newcomer is approved/unrestricted and the challenge message is cleaned up.
  6. On failure or timeout, the newcomer is banned for the configured reject timeout.
  7. Optional greeting text can be shown immediately with the public CAPTCHA for direct joins, or after approval for join-request newcomers.

Spam protection

  1. Every previously untrusted author enters a per-chat message probation. Each new text, caption, or visible rich-message text is checked for at least three hours, and the first safe new message after the deadline is required for release:
    • Manual allowlist ("Indulgence") override
    • Known spammers lookup from local imports and online checks against LoLs bot and CAS/Combot
    • External quote heuristic for obvious cross-chat spam patterns
    • LLM-powered binary classification with built-in and chat-specific spam examples
  2. If the message is considered spam, the user is either immediately banned or sent into community voting, depending on chat settings.
  3. Chat users can report missed spam with /voteban or by mentioning the bot in reply to the message. Reports are rechecked by the LLM first, then either moderated immediately or sent to community voting without pre-deleting the original message.
  4. Clean messages before the deadline remain bound for future edit checks. A distinct clean message after the deadline durably completes probation; commands and media without text start the clock but cannot complete it.

Admin panel

  1. Run /settings in a group where the bot is an admin.
  2. The bot sends a deep-link that opens a private admin panel for that chat.
  3. From there you can configure gatekeeper, new-user message probation, community voting, spam examples, language, and manual not-spammer overrides.
  4. The home screen includes a one-tap Recommended Protection preset and a compact 7-day protection summary.

Installation

Quick Start with Docker Compose

  1. Create a bot via BotFather and enable group messages access.
  2. Clone the repository:
git clone https://github.com/iamwavecut/ngbot.git
cd ngbot
  1. Create a mode-0600 environment file and configure it. compose.yaml is the tracked deployment source; do not copy or maintain a second Compose file:
install -m 0600 /dev/null .env
sed '/^[[:space:]]*#/d; /^[[:space:]]*$/d' .env.example > .env
chmod 0600 .env
# Edit .env and set NG_TOKEN, NGBOT_DATA_PATH, and the selected LLM credential.

Never commit .env, paste it into logs, or pass it on a command line. See Operator deployment guide for secret rotation and release identity checks. 4. Create the writable data directory named by NGBOT_DATA_PATH and give the distroless nonroot user ownership:

sudo install -d -m 0700 -o 65532 -g 65532 /home/username/.ngbot

The image runs as UID/GID 65532:65532; set NGBOT_DATA_PATH=/home/username/.ngbot. Compose always mounts that host path at /data, and the application always uses NG_DOT_PATH=/data in the container. 5. Start the bot:

docker compose up -d
  1. Optional for join-request Mini App CAPTCHA: set NG_GATEKEEPER_WEBAPP_PUBLIC_URL=https://antifraud.rtfm.rsvp, point Caddy at deploy/caddy/ngbot-webapp.Caddyfile, then restart Compose.
  2. Add your bot to chat and give it Ban, Delete, and Invite permissions.
  3. Optional: Change bot language with /lang <code> (e.g., /lang ru).
  4. Optional: Set NG_SPAM_DEBUG_USER_ID to your Telegram user ID for private /testspam and /skipreason; source-chat administrators may use those diagnostics in their chat.
  5. Optional: Open /settings as a group admin and apply Recommended Protection.

Manual Installation

  1. Create the bot and a mode-0600 .env as above.
  2. Export the variables before starting the native binary. The Go binary does not load .env by itself:
set -a
. ./.env
set +a
go mod download
go run ./cmd/ngbot

The native Mini App server defaults to loopback at 127.0.0.1:8080. Only the Compose deployment overrides it to 0.0.0.0:8080 inside the isolated container; Docker publishes it exclusively on host loopback.

Configuration

All configuration is done through environment variables. You can:

  • Set them in your environment
  • Let Docker Compose read a mode-0600 .env file
  • Source a mode-0600 .env before running the native binary

See .env.example for a quick reference. NGBOT_* variables configure only the Compose/build workflow; NG_* variables configure the application.

Compose/build variable Purpose Default
NGBOT_DATA_PATH Secured host directory mounted at /data Required
NGBOT_WEBAPP_HOST_PORT WebApp port published on host loopback 18080
NGBOT_VERSION Release version stored in the binary and OCI label dev
NGBOT_REVISION Exact Git revision stored in the binary and OCI label unknown
NGBOT_BUILD_DATE RFC 3339 build time stored in the binary and OCI label unknown

Configuration Options

Required Variable name Description Default Options
✔️ NG_TOKEN Telegram BOT API token
NG_LANG Default language to use in new chats en be, bg, cs, da, de, el, en, es, et, fi, fr, hu, id, it, ja, ko, lt, lv, nb, nl, pl, pt, ro, ru, sk, sl, sv, tr, uk, zh
NG_HANDLERS Enabled bot handlers admin,gatekeeper,reactor Comma-separated list of handlers
NG_LOG_LEVEL Logging verbosity 2 0=Panic, 1=Fatal, 2=Error, 3=Warn, 4=Info, 5=Debug, 6=Trace
NG_DOT_PATH Bot data storage path ~/.ngbot Any valid filesystem path
NG_TELEGRAM_POLL_TIMEOUT Telegram long poll timeout 60s Any valid duration string
NG_TELEGRAM_REQUEST_TIMEOUT Telegram HTTP request timeout 75s Must be greater than poll timeout
NG_TELEGRAM_RECOVERY_WINDOW Maximum degraded polling window before restart 10m Must be greater than request timeout
NG_TELEGRAM_INBOX_MAX_PENDING_ROWS Maximum aggregate pending/retry inbox rows 100000 Positive integer
NG_TELEGRAM_INBOX_MAX_PENDING_BYTES Maximum aggregate pending/retry payload bytes 536870912 Positive integer
NG_TELEGRAM_INBOX_MAX_DISPATCH_PENDING_ROWS Maximum pending/retry rows for one dispatch key 10000 Positive integer
NG_TELEGRAM_INBOX_MAX_DISPATCH_PENDING_BYTES Maximum pending/retry bytes for one dispatch key 33554432 Positive integer
NG_TELEGRAM_INBOX_MIN_FREE_BYTES Minimum database filesystem free space for admission 268435456 Positive integer
NG_GATEKEEPER_WEBAPP_PUBLIC_URL Public HTTPS origin for join-request CAPTCHA Mini App Absolute URL, e.g. https://captcha.example.com
NG_GATEKEEPER_WEBAPP_LISTEN_ADDR Native embedded Mini App server listen address 127.0.0.1:8080 Compose enforces 0.0.0.0:8080 inside the container
NG_GATEKEEPER_WEBAPP_MAX_CONCURRENT Maximum in-flight Mini App requests 32 Integer greater than zero; 0 is invalid
NG_GATEKEEPER_WEBAPP_REQUESTS_PER_MINUTE Per-client Mini App request limit 120 Integer greater than zero; 0 is invalid
NG_LLM_GEMINI_API_KEY Gemini credential; required when reactor uses Gemini Preferred over the legacy key
NG_LLM_OPENAI_API_KEY OpenAI credential; required when reactor uses OpenAI Preferred over the legacy key
NG_LLM_API_KEY Legacy credential fallback for the selected provider Used only when its dedicated key is empty
NG_LLM_API_MODEL Optional LLM model override Provider-specific Any valid OpenAI or Gemini model
NG_LLM_API_URL OpenAI-compatible API base URL https://api.openai.com/v1 Used when NG_LLM_API_TYPE=openai
NG_LLM_API_TYPE LLM provider openai openai, gemini
NG_LLM_REQUEST_TIMEOUT Maximum duration of one classification request 45s Any positive duration string
NG_SPAM_LOG_CHANNEL_USERNAME Channel for spam logging Any valid channel username
NG_SPAM_DEBUG_USER_ID User allowed to run diagnostics in private chat 0 Telegram user ID
NG_SPAM_VERBOSE Verbose in-chat notifications false true, false
NG_SPAM_MESSAGE_PROBATION_DURATION Minimum new-user message probation before a checked safe exit 3h Any positive duration string
NG_SPAM_VOTING_TIMEOUT Voting time limit 5m Any valid duration string
NG_SPAM_MIN_VOTERS Minimum required voters 2 Any positive integer
NG_SPAM_MAX_VOTERS Maximum voters cap 10 Any positive integer
NG_SPAM_MIN_VOTERS_PERCENTAGE Minimum voter percentage 5 Any positive float
NG_SPAM_SUSPECT_NOTIFICATION_TIMEOUT Suspect notification timeout 2m Any valid duration string

The language codes in NG_LANG are the same complete locale catalog used by the admin UI and CAPTCHA resources. CI verifies that translation keys remain complete across that catalog.

Caddy reverse proxy

The Docker Compose file binds the Mini App server to 127.0.0.1:${NGBOT_WEBAPP_HOST_PORT:-18080} on the host. A matching Caddy template is available at deploy/caddy/ngbot-webapp.Caddyfile.

Put the application values in the same mode-0600 .env that Compose reads automatically:

NG_GATEKEEPER_WEBAPP_PUBLIC_URL=https://antifraud.rtfm.rsvp
NGBOT_WEBAPP_HOST_PORT=18080

These are file entries, not shell assignments to run before docker compose: Compose reads .env itself. It enforces the container listener, so leave NG_GATEKEEPER_WEBAPP_LISTEN_ADDR at its native default in .env.

Configure the separate Caddy service environment with the public domain and the same canonical host-port variable (for example through its systemd Environment= or EnvironmentFile= settings):

NGBOT_GATEKEEPER_WEBAPP_DOMAIN=antifraud.rtfm.rsvp
NGBOT_WEBAPP_HOST_PORT=18080

Do not give Caddy the bot .env, because it contains application credentials that Caddy does not need.

The Mini App endpoint is intentionally hostile to indexing and unauthorized embedding:

  1. /robots.txt disallows all crawlers, including known search, SEO, and LLM training bots.
  2. /sitemap.xml is an empty sitemap.
  3. X-Robots-Tag denies indexing, following, snippets, archives, image indexing, translation, AI use, and image-AI use.
  4. CSP uses default-src 'none' and per-request nonces for the Telegram script and local inline code.
  5. CSP allows framing only by the official Telegram Web origin; attacker origins remain blocked.
  6. Browser capability APIs are disabled with Permissions-Policy.
  7. Referrers are suppressed with Referrer-Policy: no-referrer.
  8. Responses are marked no-store and private.
  9. Cross-origin resource sharing is not enabled, and cross-site mutation requests are rejected through Fetch Metadata.
  10. POST bodies are size-limited before form parsing.
  11. Known crawler and LLM user agents are rejected before challenge lookup.
  12. MIME sniffing and legacy cross-domain policies are disabled.
  13. Concurrent admission and per-client request rates are bounded; access telemetry records only method, an allowlisted route name, status, and duration, never raw paths, query strings, authorization headers, or bearer tokens.

The embedded server exposes GET /livez for WebApp process liveness and GET /readyz for application readiness. Readiness is published only after every lifecycle component, including the durable update loop, starts successfully and is withdrawn before shutdown. A fatal serving error triggers graceful process shutdown with exit status 1 so Compose can restart the service. Container health checks use /readyz; use ./ngbot --version, the OCI revision label, and the running container image ID to verify a deployed artifact. See deploy/README.md for the executable release and rollback procedure.

Production SQLite maintenance

Do not run PRAGMA quick_check, integrity_check, or other long scans against the live database file. Create a consistent online snapshot first, then run integrity and migration checks against the snapshot:

umask 077
sqlite3 /home/username/.ngbot/bot.db ".backup '/var/backups/ngbot/bot-audit.db'"
chmod 0600 /var/backups/ngbot/bot-audit.db
test "$(sqlite3 /var/backups/ngbot/bot-audit.db 'PRAGMA quick_check;')" = ok
test -z "$(sqlite3 /var/backups/ngbot/bot-audit.db 'PRAGMA foreign_key_check;')"

Delete the audit snapshot after verification. A direct long-running read can hold a SQLite shared lock long enough for application writes to reach the configured busy timeout.

The application has an offline maintenance mode that applies pending migrations, performs a full VACUUM, enables incremental auto-vacuum, runs PRAGMA optimize, validates the database, restores WAL mode, and exits without starting Telegram polling:

docker compose stop ngbot
docker compose run --rm --no-deps ngbot --database-maintenance
docker compose up -d ngbot

Create and verify a database backup before running this command. The service must remain stopped for the complete maintenance run. Normal banlist refreshes use inactive generations, short batched writes, atomic activation, bounded garbage collection, passive WAL checkpoints, and incremental vacuum, so full maintenance is not required after each refresh.

Troubleshooting

Don't hesitate to contact me

telegram linkedin

Notes

  • Gemini requests can reuse server-side explicit caching for the static moderation prefix when the provider supports it.
  • Chat-specific settings, spam examples, and the private settings UI are already implemented.

Acknowledgements

This bot benefits from public anti-spam data shared with the community by:

Thank you to both projects for maintaining and sharing these community safety resources.

Feel free to add feature requests in issues.

About

simple gatekeeper telegram bot

Resources

Stars

59 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages