A Python application that monitors Persian news Telegram channels, summarizes new posts using AI (OpenRouter LLM), and posts the summaries to your own Telegram channel.
- Monitors multiple Telegram news channels using your user account
- Monitors RSS feeds with keyword-based filtering for Iran-related news
- Generates Persian summaries using OpenRouter LLM API
- Posts summaries to a dedicated Telegram channel via bot
- Configurable check interval (default: 30 minutes)
- Persists last check timestamp to avoid duplicate summaries on restart
- Test mode for local development (writes to file, separate state)
- Runs locally as a macOS LaunchAgent (auto-start, auto-restart)
- Python 3.11+
- uv package manager
- Telegram API credentials
- Telegram Bot token
- OpenRouter API key
git clone <repository-url>
cd news-summarizer
uv sync --all-extras- Go to https://my.telegram.org
- Log in with your phone number
- Go to "API development tools"
- Create a new application
- Copy your
API_IDandAPI_HASH
- Open Telegram and search for @BotFather
- Send
/newbotand follow the prompts - Copy the bot token provided
- Add your bot as an admin to your output channel
- Create an account at https://openrouter.ai
- Go to API Keys section
- Create a new API key
Run the session generator script locally (one-time setup):
uv run python scripts/generate_session.pyYou'll be prompted for:
- Your API ID and API Hash
- Your phone number
- The verification code sent to Telegram
- Your 2FA password (if enabled)
Save the output session string securely.
Copy the example environment file:
cp .env.example .envEdit .env with your credentials:
TELEGRAM_API_ID=your_api_id
TELEGRAM_API_HASH=your_api_hash
TELEGRAM_SESSION_STRING=your_session_string
TELEGRAM_BOT_TOKEN=your_bot_token
OUTPUT_CHANNEL_ID=@your_channel
OPENROUTER_API_KEY=your_openrouter_key
SUMMARY_INTERVAL_MINUTES=30
LLM_MODEL=google/gemini-2.5-flash-liteSee docs/llm-comparison.md for model options and pricing.
Edit config/channels.yaml to add the channels you want to monitor:
channels:
- channel_username_1
- channel_username_2
- channel_username_3Note: Your Telegram account must be able to view these channels (public channels or channels you've joined).
By default the bot summarizes on a flat interval (SUMMARY_INTERVAL_MINUTES). You can make it reactive instead: it measures news intensity each run as a pre-dedup filtered message rate (messages per minute, so the signal does not depend on the current interval) and adapts the cadence:
- Escalation is immediate. When the rate spikes above the baseline (a war breaks out, a major event) or a Cloudflare Radar internet outage fires, the interval shortens at once, down to
min_interval_minutesat a full surge. - Two-gate level test. A level requires BOTH a ratio (rate vs the rolling median baseline) AND an absolute message-rate floor. The floor stops ordinary traffic (a few messages over the window) from clearing the 4x ratio against a near-zero baseline and reading as a false surge.
- Decay is silent and steps back in meaningful jumps. As things calm, the interval grows by
decay_factor(default 2.0, i.e. doubling: 30→60→120→240→360) per decay step, never overshootingSUMMARY_INTERVAL_MINUTES(the steady-state baseline) unless you setmax_interval_minuteshigher. - Notices only on a genuine surge onset. A one-line Persian notice is posted only when the cadence tightens out of a calm NORMAL state. Decay and mid-event re-escalation reschedule silently, so the channel is never spammed with contradictory "calming"/"rising" notices.
- Optional cold-start probe. With
fast_escalation: true, a cheap probe runs everyprobe_interval_minutes, counting messages with no LLM call, and can tighten the cadence between full summary runs. It can only escalate, never relax.
Enable it in config/channels.yaml:
adaptive_cadence:
enabled: true
min_interval_minutes: 5 # Floor cadence during a surge
# max_interval_minutes: 60 # Optional; omit to cap at the baseline
baseline_window: 10 # Recent rate samples for the median baseline
elevated_ratio: 2.0 # >= 2x baseline => half interval (also needs elevated_floor_rate)
surge_ratio: 4.0 # >= 4x baseline => min_interval (also needs surge_floor_rate)
elevated_floor_rate: 0.75 # Absolute min rate (msg/min) to reach ELEVATED
surge_floor_rate: 1.5 # Absolute min rate (msg/min) to reach SURGE
decay_factor: 2.0 # Interval growth per decay step (doubling: 30->60->120)
min_baseline_rate: 0.1 # Baseline floor, in messages per minute
fast_escalation: false # Cheap escalate-only probe between runs
probe_interval_minutes: 5Cadence state (the rate window and current interval) is persisted to .cadence_state so it survives restarts.
uv run python -m src.mainThe bot will:
- Start monitoring the configured channels
- Check for new messages at the configured interval
- Generate and post summaries to your output channel
Press Ctrl+C to stop gracefully.
Test mode allows you to run the application locally without posting to Telegram. Instead, summaries are written to a text file and state is tracked separately from production.
# Run in test mode (writes to output/summaries.txt)
TEST_MODE=true uv run python -m src.main
# Customize test interval (default: 5 minutes)
TEST_MODE=true TEST_SUMMARY_INTERVAL_MINUTES=1 uv run python -m src.mainTest mode features:
- Writes summaries to
output/summaries.txtinstead of Telegram - Uses separate state file (
.last_check.test) to keep test runs isolated - Uses
config/channels.test.yamlif it exists (falls back tochannels.yaml) - Shorter default interval (5 minutes vs 30 minutes)
Test mode environment variables:
| Variable | Default | Description |
|---|---|---|
TEST_MODE |
false |
Enable test mode |
TEST_SUMMARY_INTERVAL_MINUTES |
5 |
Summary interval in test mode |
TEST_OUTPUT_DIR |
output |
Directory for file output |
TEST_STATE_FILE |
.last_check.test |
State file path in test mode |
uv run pytestWith coverage:
uv run pytest --cov=src --cov-report=term-missinguv run ruff check src testsuv run pyright srcnews-summarizer/
├── src/
│ ├── __init__.py
│ ├── main.py # Entry point, scheduler setup
│ ├── config.py # Configuration loading
│ ├── telegram_reader.py # Pyrogram client for reading channels
│ ├── rss_reader.py # RSS/Atom feed reader
│ ├── iran_filter.py # Keyword-based content filtering
│ ├── telegram_bot.py # Bot for posting summaries
│ ├── file_writer.py # File output for test mode
│ ├── output_writer.py # Output writer protocol
│ ├── summarizer.py # OpenRouter LLM integration
│ └── models.py # Data models (Message, Summary)
├── tests/ # Test suite
├── scripts/
│ └── generate_session.py # Session string generator
├── config/
│ ├── channels.yaml # Production channel list
│ └── channels.test.yaml # Test mode channel list
├── .github/workflows/ # CI workflow
├── pyproject.toml # Project configuration
- Never commit your
.envfile or session string to git - The session string grants full access to your Telegram account
- If you suspect your session is compromised, terminate all sessions in Telegram settings
- Store credentials securely in your
.envfile
If you find this bot useful, consider supporting its development:
- Star this repo
- Report bugs and submit PRs
- Sponsor on GitHub or Buy me a coffee
MIT