A production-ready Telegram bot that monitors subreddits and pushes new post notifications to subscribed users, with per-user keyword filtering.
- 🔍 Subreddit monitoring — Admins add/remove subreddits; the bot polls them every N minutes via APScheduler.
- 📬 Per-user keyword filters — Each user independently chooses:
- Inclusive mode — receive posts only if the title contains one of their keywords.
- Exclusive mode — receive all posts except those matching blocked keywords.
- No filter — receive everything (default).
- 🔄 Duplicate guard — A
seen_poststable prevents the same post being sent twice. - ⚡ Fully async —
aiosqlitefor DB,asyncio.to_thread()for PRAW,AsyncIOSchedulerfor polling. - 🛡️ Graceful shutdown — Catches
SIGINT/SIGTERM, stops the scheduler, closes the DB cleanly.
| Component | Library |
|---|---|
| Telegram | python-telegram-bot v21 (async) |
| PRAW 7 | |
| Database | SQLite via aiosqlite |
| Scheduler | APScheduler 3 (AsyncIOScheduler) |
| Config | python-dotenv |
slavewatch/
├── bot/
│ ├── main.py ← entry point, bot setup, scheduler
│ ├── config.py ← env vars and constants
│ ├── db.py ← SQLite schema + async DB helpers
│ ├── reddit.py ← PRAW wrapper + polling logic
│ ├── handlers.py ← Telegram command/callback handlers
│ ├── filters.py ← keyword filter matching (pure functions)
│ └── notifications.py ← format and send post alerts
├── requirements.txt
├── .env.example
└── README.md
git clone <repo-url> slavewatch
cd slavewatch
python3.11 -m venv .venv
source .venv/bin/activatepip install -r requirements.txt- Open Telegram and start a chat with @BotFather.
- Send
/newbotand follow the prompts. - Copy the bot token.
- Go to https://www.reddit.com/prefs/apps.
- Click "Create App" → choose "script".
- Fill in any name and redirect URI (
http://localhost). - Copy the client ID (under the app name) and client secret.
cp .env.example .envEdit .env with your credentials:
TELEGRAM_BOT_TOKEN=your_telegram_bot_token
REDDIT_CLIENT_ID=your_client_id
REDDIT_CLIENT_SECRET=your_client_secret
REDDIT_USER_AGENT=SlaveWatch:v1.0 (by /u/YourUsername)
ADMIN_IDS=123456789 # your Telegram user ID
POLL_INTERVAL=5 # minutes between polls
REDDIT_FETCH_LIMIT=25
DATABASE_URL=bot.db💡 Find your Telegram user ID by messaging @userinfobot.
cd bot
python main.pyThe bot will initialise the SQLite database, start polling Reddit, and begin accepting Telegram commands.
| Command | Description |
|---|---|
/start |
Register and see the welcome message |
/filter |
Open the filter settings menu |
/mystatus |
Show your current filter mode and keywords |
/done |
Finish adding keywords during a filter session |
| Command | Description |
|---|---|
/addsubreddit <name> |
Start monitoring a subreddit |
/removesubreddit <name> |
Stop monitoring a subreddit |
/listsubreddits |
List all monitored subreddits |
Admin privileges are granted by adding Telegram user IDs to
ADMIN_IDSin.env.
A user can be in one of three states:
| State | Behaviour |
|---|---|
| No filter | Receive every new post from all monitored subreddits |
| Inclusive mode | Receive posts only if the title contains ≥1 of their keywords |
| Exclusive mode | Receive posts unless the title contains any of their blocked words |
Important: Switching modes clears all existing keywords — a user can only be in one mode at a time.
- User sends
/filter. - Inline keyboard appears: Inclusive mode / Exclusive mode / Clear all filters.
- User clicks Inclusive mode.
- Bot confirms and prompts for keywords.
- User sends
python, machine learning, AI(CSV or one per message). - User sends
/doneor clicks the Done button.
PRAW uses the requests library which is synchronous and blocks the thread it runs on. In an async application this would stall the entire event loop. By wrapping PRAW calls with asyncio.to_thread(), they execute in Python's thread pool executor, keeping the event loop free to handle Telegram updates simultaneously.
seen_posts is checked before dispatching and updated after. If the process crashes between dispatch and mark, the post will be re-sent on the next poll. This is an intentional trade-off (at-least-once delivery > silent loss).
PRAW is configured with ratelimit_seconds=600, meaning it will wait up to 10 minutes rather than crash when Reddit enforces its rate limit. The scheduler adds natural spacing because polls happen at fixed intervals.
Create /etc/systemd/system/slavewatch.service:
[Unit]
Description=SlaveWatch Reddit-to-Telegram Bot
After=network.target
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/dev_projects/slavewatch/bot
ExecStart=/home/ubuntu/dev_projects/slavewatch/.venv/bin/python main.py
Restart=on-failure
RestartSec=10
EnvironmentFile=/home/ubuntu/dev_projects/slavewatch/.env
[Install]
WantedBy=multi-user.targetThen enable and start:
sudo systemctl daemon-reload
sudo systemctl enable --now slavewatch
sudo journalctl -u slavewatch -f # follow logs| Symptom | Fix |
|---|---|
Missing required environment variable |
Ensure .env is in the project root and all required keys are set |
r/XYZ not found or banned |
The subreddit may be private, quarantined, or misspelled |
| Bot doesn't respond | Check TELEGRAM_BOT_TOKEN and that the bot is not blocked by Telegram |
| No notifications arriving | Run /mystatus — you may have an inclusive filter with no matching keywords |
| Duplicate notifications | Should not happen; check that DATABASE_URL is consistent across restarts |
MIT — do whatever you want, but don't blame us if Reddit changes their API again. 🙂