SpamProtectionBot is a Telegram bot that protects group chats and personal inboxes from spam, phishing, and abuse, backed by the Open Federated Database specification. The bot itself holds no spam data of its own — every scan, entity lookup, and report is delegated to a Federation server (see FederationLib for a reference implementation you can self-host), and the bot is designed to keep working, in a reduced form, even when no Federation server is configured at all.
- Scanning — every member message in a protected group is checked against Federation for spam, phishing, malware, and other threats, with a configurable response ranging from notify-only to delete-and-ban.
- Join Protection — new members are checked against Federation the moment they join, so known bad actors can be restricted, banned, or have their join request declined before they cause damage.
- Reporting — members can flag a message with
/report, filing evidence with the Federation network and notifying moderators in one step. - Secretary Mode — connects to a personal Telegram Business account to screen first-time contacts before they reach your inbox, independently of group protection.
- Privacy Mode — a per-chat switch that minimizes what is published to Federation (no automatic entity publishing, scan submissions carry only message content) while the features that need data to work still send the minimum required.
- Operator tooling —
/auth,/blacklist, and/linklet Federation operators manage entities and close assigned reports without leaving Telegram, using their own credentials rather than the bot's. - Anonymous or authenticated — the bot runs against a Federation server with or without its own access token; see Federation Authentication for what changes between the two.
- Multi-language — every user-facing string is served from a translation file discovered on the classpath; see Adding a Language.
- SpamProtectionBot
- License
There are a few approaches to deploying SpamProtectionBot, depending on whether you run the jar directly or use Docker, and whether you connect to an existing Federation server or host your own. This section covers building the bot, running it, and the options it accepts; the file it reads at startup is described in Configuration.
- Java 24 or later, both to build and to run the jar. The bot's own code targets Java 17 bytecode, but the JFederation client it depends on ships class files compiled for Java 24 — a JVM older than that cannot even load them, regardless of what the bot's own code targets. Not needed when running the Docker image.
- A Telegram bot token issued by @BotFather.
- Optionally, a running Federation server (such as FederationLib) to enable spam-protection features. The bot starts and answers commands without one, but scanning, join protection, and reporting have nothing to check against.
The project builds with Maven and packages every dependency into a single runnable jar via the shade plugin:
mvn packageThe jar is written to target/spb-1.0.0.jar; mvn test runs the test suite on its own. Start the bot by pointing it
at its configuration file and database:
java -jar spb.jar --config /etc/spb/configuration.yml --database /var/lib/spb/database.dbBoth paths have defaults, so the bot can also be started with no arguments at all from a directory that already
has a configuration.yml in it. Neither the configuration file nor the database should be committed to version
control, since the former holds your bot token and the latter holds live chat data.
The Dockerfile builds the shaded jar with Maven and ships it on a minimal JRE image. It reads its
configuration from /app/configuration.yml and keeps its database under /data:
docker build -t spam-protection-bot .
docker run -d --name spam-protection-bot \
-v "$(pwd)/configuration.yml:/app/configuration.yml:ro" \
-v spb-data:/data \
spam-protection-botPre-built images are also published to ghcr.io/nosial/spamprotectionbot.
The image sets SPB_CONFIG=/app/configuration.yml and SPB_DATABASE=/data/database.db as its defaults, so either
path can be moved with -e — mount the file or volume at the new location to match. Options given after the image
name, such as --database /data/other.db, still take precedence.
Two compose files are provided, depending on whether you connect to an existing Federation server or host your own.
Using an existing Federation server. docker-compose.instance.yml runs only the
bot. Set federation.endpoint in configuration.yml to the server's base URL (prefer https for a server reached
over the internet, since the access token is sent with every request) and, optionally, federation.access_token to
an operator token issued by that server's administrator:
docker compose -f docker-compose.instance.yml up -d --buildHosting your own Federation server. docker-compose.yml spins up the whole stack: the bot, a
self-hosted FederationLib server, and FederationLib's required MariaDB
database and recommended Redis cache, wired together on an internal network so the bot reaches Federation at
http://federation:7000/ without exposing it:
# in configuration.yml, set federation.endpoint to http://federation:7000/
docker compose up -dBefore running this anywhere but a local machine, change FEDERATION_ACCESS_TOKEN in docker-compose.yml (or
override it via a .env file) from its placeholder value
The bot starts fine with no federation.access_token of its own — it runs anonymously against whatever the
Federation server allows without authentication, per Federation Authentication. To run
it authenticated instead, create a dedicated operator with client permissions
(docker compose exec federation federationlib create-operator --help for the exact syntax), put that operator's
token in configuration.yml's federation.access_token, and restart just the bot: docker compose restart bot.
Never reuse the root access of a Federation Server with this bot.
| Option | Environment variable | Description |
|---|---|---|
-c, --config <path> |
SPB_CONFIG |
Path to the YAML configuration file (default: configuration.yml) |
-d, --database <path> |
SPB_DATABASE |
Path to the SQLite database file, created when missing (default: ./database.db) |
-h, --help |
— | Show the usage text and exit |
The environment variables suit service managers and containers:
SPB_CONFIG=/etc/spb/configuration.yml SPB_DATABASE=/var/lib/spb/database.db java -jar spb.jarAn option given on the command line takes precedence over its environment variable, and the variable takes precedence over the default. A variable that is set but empty is treated as unset; one set to an invalid path stops the bot from starting, with a message naming the variable.
SpamProtectionBot reads a single YAML file at startup and validates it: a property left out falls back to its default value below, but a property that is present and invalid (wrong type, out of range, blank where text is required) stops the bot from starting, with a message naming the offending field. Start from configuration.sample.yml, a fully annotated example:
cp configuration.sample.yml configuration.ymlAt minimum, set bot.name and bot.api_key, and — to enable spam protection — federation.endpoint.
The bot section is required.
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
bot.name |
string | — | Yes | Display name shown in the /start greeting |
bot.api_key |
string | — | Yes | Telegram Bot API token issued by BotFather |
bot.api_host |
string | api.telegram.org |
No | Telegram Bot API host to connect to |
bot.api_scheme |
string | https |
No | URL scheme used to reach api_host; must be https or http |
bot.api_port |
int | 443 |
No | Port api_host listens on, in the range 1–65535 |
bot.test_server |
bool | false |
No | Talk to Telegram's test server instead of the production one |
bot.worker_threads |
int | number of CPU cores, at least 2 |
No | Concurrent threads processing incoming updates; must be >= 1 |
bot.queue_capacity |
int | 1024 |
No | Maximum pending updates before surplus updates are dropped; must be >= 1 |
bot.notification_interval_seconds |
int | 60 |
No | Seconds between checks for newly assigned, open Federation reports; must be >= 1 |
bot.default_language |
string | en |
No | Default language code for user-facing text |
bot.privacy_mode |
bool | false |
No | Default Privacy Mode setting for newly registered chats |
bot.connect_timeout_seconds |
int | 30 |
No | How long the Telegram API client waits to establish a connection; must be >= 1 |
bot.read_timeout_seconds |
int | 300 |
No | How long the Telegram API client waits for a response; must be >= 1. Kept generous because long polling holds the connection open until an update arrives |
bot.write_timeout_seconds |
int | 60 |
No | How long the Telegram API client waits while sending a request; must be >= 1 |
bot.shutdown_timeout_seconds |
int | 30 |
No | How long shutdown waits for in-flight updates to finish before the queue is abandoned; must be >= 1 |
The SQLite database location is deliberately not part of this file — it is a runtime location, not a bot setting,
and is chosen with --database or SPB_DATABASE instead (see
Command-Line Interface).
The federation section is entirely optional. When it is absent, the bot starts and answers commands normally, but
every feature that depends on Federation — scanning, join protection, reporting, entity lookups — reports itself
unavailable rather than failing.
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
federation.endpoint |
string | — | Yes, when the section is present | Base URL of the Federation server |
federation.access_token |
string | — | No | Access token the bot authenticates with; omit it to connect anonymously |
federation.web_application_endpoint |
string | — | No | Base URL of a FederationWeb instance for the same server; see Federation Web Application |
When federation.web_application_endpoint is set, every message that displays a Federation record — /info,
/evidence, /report <uuid>, /blacklist, report notifications and summaries, the operator #REPORT_ASSIGNED
notification and its evidence, and the secretary contact page — carries a button opening that record's page
(/reports/…, /evidence/…, /entities/… or /blacklist/…) in the web application. The button stays on a
notification after its action buttons are used up. Telegram rejects a message whose button URL it cannot open, so the
value must be a public http or https URL — a localhost or 127.0.0.1 address will cause those messages to fail.
A Federation server treats an anonymous client and an authenticated one differently, and — importantly — treats a misconfigured authenticated client worse than an anonymous one: several requests a server may allow anonymously are refused outright to a token that identifies an operator without client permissions, since the server holds authenticated requests to a stricter standard than anonymous ones. A token that does not clear that bar is therefore worse than no token at all.
To avoid that trap, the bot checks its own token's permissions once at startup. If federation.access_token is set
but the operator it belongs to lacks client permissions (or the token is rejected outright), the bot logs a warning
and reconnects anonymously for the rest of the run, exactly as if no token had been configured. You can see which
mode the bot ended up in from its startup log, or at any time with the /ping command, which reports Federation's
status alongside the bot's own.
A handful of features — most notably submitting a report with /report — have no anonymous path on the server at
all: they always need the bot's own token to carry client permissions, regardless of what the server otherwise
allows anonymously. When the bot is not authenticated, these features are omitted from /settings entirely rather
than shown and left to fail.
Once the bot is running, it can protect group chats, a personal inbox, or both. The two are set up independently.
Add the bot to a group and promote it to administrator with, at minimum, the Change Group Information
permission — this is what lets it open its own configuration menu. Delete Messages and Ban Users are
optional but needed for scanning and join protection to take automated action rather than only notify. Once
promoted, send /start or /settings in the group to open the configuration menu and enable protection.
Secretary Mode protects a personal inbox rather than a group. In Telegram, go to Settings → Business → Chatbots,
connect the bot, and grant it the Read Messages and Delete Received Messages permissions. Messages from
contacts you have not interacted with before are then checked against Federation and handled automatically
according to the behavior you choose in the Secretary Settings menu (/settings in a private chat).
The commands below are grouped by who uses them. Full syntax and explanations for every command are also available
from the bot itself via /help.
| Command | Description |
|---|---|
/help |
Show the in-bot help menu, private chats only |
/start |
Add the bot to a group, or open your private menu |
/settings |
Open the configuration menu |
/language, /lang |
Change your language |
/ping |
Check the bot's and Federation server's status |
| Command | Description |
|---|---|
/report |
Reply to a message and send this to open the report dialog, or send /report <type> [comment] to submit directly. Forwarding a message to the bot privately reports it the same way. |
/report <uuid> |
Look up an existing report by its UUID |
/connect <id> |
Link a notification chat or channel, using the id shown on the Chat Linking settings page |
Available in any chat; no authentication required.
| Command | Description |
|---|---|
/info [target] |
Look up a Federation entity's record and recommendation. With no target, replying to a message looks up its author; sent plain, it looks up yourself. A target can also be a username, user id, entity address, UUID, or hash. |
/evidence <uuid> |
Look up a Federation evidence record by its UUID |
These commands require signing in as a Federation operator first, and run with that operator's own permissions rather than the bot's.
| Command | Description |
|---|---|
/auth <access_token> |
Sign in as a Federation operator with an access token |
/authinfo |
Show your current operator identity and permissions |
/deauth |
Sign out |
/blacklist <type> <expires> (reply) |
Create a supporting report for the replied-to message's author, then blacklist them. Give an existing <report_uuid> before the type to blacklist against it instead, or the full <identifier> <report_uuid> <type> <expires> to skip the reply entirely |
/link <entity> <target> <type> |
Record a relationship (alternative, proxy, or child) between two Federation entities |
Every user-facing string is served from a YAML file under src/main/resources/languages/, discovered on the
classpath at startup — there is no code change involved in adding one. A translation file is a nested mapping
flattened into dotted keys at lookup time, and must declare a localization_properties section naming the language:
localization_properties:
name: "Español"
emoji: "🇪🇸"
general:
"yes": "Sí"
"no": "No"
# ...Copy en.yml as a starting point, translate its values, and name the new file after the language's code (e.g.
es.yml). A key missing from a language falls back to the default language, so a partial translation degrades
gracefully rather than breaking. bot.default_language in the configuration file selects which language is used
when a user or chat has not chosen one.
The official repository for SpamProtectionBot is hosted on n64; mirrors are also kept on:
This project is licensed under the MIT License - see the LICENSE file for details.