A daily summarizer bot for Telegram group chats. At 23:59 (Europe/Kyiv) it posts #Підсумки_дня (Daily Summary): a list of topics discussed during the previous day with links to the first message of each topic and a link to the initiator (clickable user link or @username).
- Python 3.11+, uv for dependency management
- python-telegram-bot v21 (uses JobQueue for the daily job)
- OpenAI GPT and Google Generative AI (Gemini) to cluster messages into topics
- Configurable toxicity levels (0-9) for different response styles
- Token-aware message processing using tiktoken for optimal API usage
- SQLite (optional Fly.io volume for persistence across releases)
- Runs in long-polling mode
- Telegram Bot Token from @BotFather
- Create a bot →
/newbot - Disable Group Privacy:
/setprivacy→ pick the bot → Disable (so the bot can read all group messages) - Add the bot to your chat (giving it admin rights is recommended)
- Create a bot →
- API Keys:
- OpenAI API Key (for GPT models)
- Gemini API Key (Google Generative AI)
- uv and Python 3.11+ installed locally
- (Production) flyctl account + CLI
If the repo already has
pyproject.tomlandsrc/main.py, jump to step 3.
- Clone and setup
git clone <your-repo> cd xxl-bot-summarizer uv sync- Install additional dependencies (if needed)
uv add "python-telegram-bot==21._" "google-generativeai==0._"
"openai>=1.102.0" "tiktoken>=0.11.0" "orjson==3.*"- Create .env in the project root (example)
TELEGRAM_BOT_TOKEN=123456789:AA...your_token... OPENAI_API_KEY=sk-...your_openai_key... GEMINI_API_KEY=AIza...your_gemini_key... TZ=Europe/Kyiv
# Configure which chats use which AI provider (comma-separated chat IDs):
OPENAI_CHAT_IDS=-1001234567890,-1009876543210 GEMINI_CHAT_IDS=-1001111111111,-1002222222222
# Optional:
OPENAI_MODEL_NAME=gpt-4o-mini GEMINI_MODEL_NAME=gemini-1.5-flash
# If you mount a volume on Fly (see below):
# DB_PATH=/data/bot.db- Run locally
uv run python src/main.py- Test in a chat
- In a test group, send several messages and replies
- Run /summary_now [0-9] — you should see today's summary with specified toxicity level
- Run /summary_now — uses maximum toxicity (level 9) by default
- /chatid returns the chat id and shows which AI provider is configured
- Use /enable_summaries and /disable_summaries to control automatic daily summaries
Alternatives to get chat id without /chatid:
- Copy the link to any message in a private supergroup:
https://t.me/c/<internal_id>/<msg_id>→ yourchat_idis-100<internal_id>.- Use Bot API:
curl -s "https://api.telegram.org/bot$TOKEN/getUpdates" | jq '.result[].message.chat'(rundeleteWebhookfirst if needed).
- /summary_now [0-9] — Generate immediate summary with optional toxicity level (0=friendly, 9=maximum toxicity)
- /chatid — Show chat ID and configured AI provider
- /enable_summaries — Enable automatic daily summaries
- /disable_summaries — Disable automatic daily summaries
- /status — Show current configuration status
The bot supports both OpenAI and Gemini models. Configure which chats use which provider via environment variables:
- OPENAI_CHAT_IDS: Comma-separated list of chat IDs that will use OpenAI (GPT) models
- GEMINI_CHAT_IDS: Comma-separated list of chat IDs that will use Gemini models
Each chat can only use one provider. The bot will automatically use the appropriate API based on the chat configuration.
- Level 0: Friendly and positive tone
- Level 1-3: Light humor and mild criticism
- Level 4-6: Pronounced sarcasm and sharp criticism
- Level 7-9: Maximum toxicity and provocative style
# Linux/macOS
curl -L [https://fly.io/install.sh](https://fly.io/install.sh) | sh fly auth loginThe project includes a uv-based Dockerfile:
FROM ghcr.io/astral-sh/uv:python3.11-bookworm
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen --no-dev
COPY . .
CMD ["uv", "run", "python", "src/main.py"]fly launch --no-deploy
# pick a region close to Ukraine/Europe (e.g., fra/ams/waw)This creates fly.toml (defaults are fine).
By default, the SQLite file inside the container is ephemeral between releases. To persist history:
fly volumes create data --size 1Add to fly.toml:
[[mounts]]
source = "data"
destination = "/data"And set the DB path as a secret:
fly secrets set DB_PATH=/data/bot.dbImport from .env:
fly secrets import < .env
# or set specific ones
fly secrets set TELEGRAM_BOT_TOKEN=...
OPENAI_API_KEY=...
GEMINI_API_KEY=...
TZ=Europe/Kyiv
OPENAI_CHAT_IDS=-100...
GEMINI_CHAT_IDS=-100...Updating secrets triggers a rolling restart automatically.
fly deployVerify:
fly status
fly logs- Code or Dockerfile changed:
fly deploy - Secrets/env only:
fly secrets import < .env(auto-restart) - Restart current release without rebuilding:
fly apps restart <app-name>
/chatidin the production chat returns a negative id (-100…for supergroups) and shows the correct AI provider- produces a friendly summary
/summary_now 0 /summary_now 9produces a highly toxic summary- produces a summary with clickable links to the first message and to the initiator
/summary_now - A post arrives at 00:00 Europe/Kyiv (if summaries are enabled) #Підсумки_дня
- BotFather Group Privacy = Disabled
- Both OpenAI and Gemini chats work correctly
- Multiple AI providers: Each chat must be configured for exactly one AI provider (OpenAI or Gemini)
- Token limits: The bot uses tiktoken to efficiently manage token usage and stay within API limits
- Private supergroups: message links look like
https://t.me/c/<internal_id>/<msg_id>and work for chat members - HTML escaping: Names/titles/summaries are automatically escaped to avoid broken markup
- Event loop: Uses JobQueue from PTB to avoid event-loop conflicts
- JSON responses: Both providers are configured for JSON output with appropriate fallbacks
- Safety filters: If high toxicity levels are blocked, the bot automatically retries with lower levels
This event loop is already running→ don't callrun_polling()insideasyncio.run(...); use the provided structuresrc/main.pyno running event loopwith schedulers → the project uses PTB JobQueue (see )src/scheduler.pygetUpdates conflict→ rundeleteWebhookbeforegetUpdatesto fetch chat id- No midnight summary → check TZ, JobQueue schedule, summaries enabled via
/enable_summaries, and that privacy is disabled - API errors → check that the chat is configured in the correct
*_CHAT_IDSenvironment variable - Safety blocks → try lower toxicity levels or check API quotas/content policies