A three-bot system that monitors Telegram OSINT/geopolitics channels, filters messages through a keyword pre-filter and LLM evaluation, and automatically posts approved stories to Bluesky with AI-generated summaries, image analysis, and automatic story threading.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β controlbot.py β
β Telegram bot that manages the other two β
β /start, /stop, /restart, /startall, /logs, etc. β
ββββββββββββββββ¬βββββββββββββββββββββββ¬ββββββββββββββββββββ
β manages β manages
βΌ βΌ
ββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββ
β filterbot.py β β posterbot.py β
β β β β
β Monitors TG channels β β Watches filterbot output β
β Keyword pre-filter ββββ AI-formats for Bluesky β
β Semantic deduplication β β Vision: logo detection β
β Auto-translation β β Vision: auto-captioning β
β LLM evaluation (Groq) β β Semantic deduplication β
β Urgency tagging β β Story threading β
β Forwards to destination β β Posts to Bluesky β
ββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββ
| File | Description |
|---|---|
controlbot.py |
|
| Telegram bot supervisor. Runs as the parent process and manages filterbot and posterbot as child processes. Send commands from your phone to start/stop/restart bots, view logs, and check heartbeat stats. | |
filterbot.py |
|
| The core intelligence filter. Connects to your Telegram user account via Telethon, monitors source channels, runs a three-tier keyword pre-filter (override β instant-reject β must-match), detects semantic duplicates, auto-translates non-English messages, and evaluates importance via Groq LLM. Approved messages are forwarded to a destination Telegram channel with urgency tags (π΄ FLASH / π‘ NOTABLE). | |
posterbot.py |
|
| Bluesky publisher. Watches the destination channel that filterbot writes to, batches incoming messages, uses Groq to rewrite them as concise Bluesky posts (β€300 chars), runs vision analysis on images (logo detection + auto-captioning), detects story updates and posts them as threaded replies, and publishes to Bluesky. | |
(TOOL)export_telegram_channels.py |
|
Utility script. Connects to your Telegram account and exports all channels you're subscribed to into a channels.json config file. This is the first thing you run when setting up the project. |
|
channels.json |
|
| Your personal channel configuration (gitignored). Contains channel IDs, names, credibility tiers, and the destination channel. Generated by the export tool. | |
channels.example.json |
|
Template showing the channels.json format. Copy this and fill it in if you prefer manual setup over the export tool. |
|
filters.json |
|
| Your keyword filters and LLM prompt (gitignored). Defines what topics the bot looks for, what it rejects, and how the AI evaluates messages. | |
filters.example.json |
|
Template showing the filters.json format with placeholder keywords. Copy and customize for your use case. |
|
private.env |
|
| Environment variables file containing all API keys and tokens. Never commit this to Git. |
You need credentials from four services:
| Service | What you need | Where to get it |
|---|---|---|
| Telegram API | ||
TELEGRAM_API_ID and TELEGRAM_API_HASH |
||
Telegram BotCONTROL_BOT_TOKEN
@BotFatheron Telegram β/newbot
Telegram User IDADMIN_TELEGRAM_ID
@userinfoboton TelegramGroqGROQ_API_KEY
console.groq.com/keysβ free tier availableBlueskyBLUESKY_HANDLE
and BLUESKY_APP_PASSWORD
bsky.app/settings/app-passwords| Software | Download | |---|---| Python 3.11+ | |
pip install telethon python-telegram-bot python-dotenv deep-translator groq pydantic langdetect sentence-transformers torch atproto
git clone https://github.com/bananaosint/bsky-poster.git
cd bsky-poster
pip install telethon python-telegram-bot python-dotenv deep-translator groq pydantic langdetect sentence-transformers torch atproto
Create a file called private.env
in the project root with the following contents:
TELEGRAM_API_ID=your_api_id
TELEGRAM_API_HASH=your_api_hash
BLUESKY_HANDLE=yourhandle.bsky.social
BLUESKY_APP_PASSWORD=xxxx-xxxx-xxxx-xxxx
GROQ_API_KEY=gsk_xxxxxxxxxxxx
CONTROL_BOT_TOKEN=1234567890:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
ADMIN_TELEGRAM_ID=your_numeric_telegram_id
You have two options:
The export tool automatically finds all Telegram channels you're subscribed to and generates a channels.json
config file:
python "(TOOL)export_telegram_channels.py"
On first run, Telethon will ask for your phone number and a login code sent to Telegram. This creates a local session file so you only need to authenticate once.
The tool will create channels.json
with all your channels. Then open it and:
Setβ Changedestination_channel
-100000000000
to the Telegram channel ID where filterbot should forward approved messages. This must be a channel you own or have admin access to.Remove channels you don't want monitored.Set channel tiersβ Rate each source's credibility:"high"
β Official accounts, verified outlets (IDF, Al Jazeera, etc.)"medium"
or""
β General news aggregators, unverified"low"
β Propaganda outlets, known bias sources
Add rapid-update channelsβ Channels that post many updates per minute (e.g., air raid alert bots). These get a lower dedup threshold so legitimate rapid-fire updates aren't dropped.
Copy the template and fill it in manually:
cp channels.example.json channels.json
Then edit channels.json
β see the format in channels.example.json
. You can find channel IDs by forwarding a message from the channel to @userinfobot.
Copy the example filter config:
cp filters.example.json filters.json
Then edit filters.json
to define what topics your bot monitors. The file has four sections:
Messages containing any of these words bypass ALL other checks and go straight to the LLM. Use for your most critical, unambiguous signals.
Messages containing any of these are dropped immediately. Use for spam, ads, and off-topic noise.
Messages must contain at least one of these to proceed to the LLM. This is your main topic gate β add keywords for whatever you're monitoring.
The system prompt that tells the LLM what "important" means for your use case. Customize this to match your topic.
Examples for different use cases:
Cryptocurrency / DeFi monitoring
{
"override_keywords": {
"keywords": ["hack", "exploit", "rug pull", "flash loan attack", "bridge drained", "sec charges"]
},
"instant_reject_keywords": {
"keywords": ["airdrop", "giveaway", "join our group", "buy now", "100x gem", "not financial advice"]
},
"must_match_keywords": {
"keywords": ["bitcoin", "ethereum", "defi", "hack", "exploit", "sec", "regulation", "whale", "liquidation", "stablecoin", "depeg", "exchange", "binance", "coinbase"]
},
"llm_prompt": {
"system_prompt": "You are a crypto intelligence filter. Approve messages about: security exploits, major price movements (>5%), regulatory actions, exchange issues, whale movements. Reject: shilling, price predictions, memes, influencer opinions. Return JSON with 'important' (bool), 'urgency' (1-3), 'reason' (string)."
}
}
Sports / Football scores
{
"override_keywords": {
"keywords": ["goal!", "red card", "penalty", "injury time", "transfer confirmed"]
},
"instant_reject_keywords": {
"keywords": ["bet now", "odds", "prediction", "fantasy", "subscribe"]
},
"must_match_keywords": {
"keywords": ["goal", "score", "match", "transfer", "signed", "injury", "lineup", "suspended", "champions league", "premier league", "red card", "var"]
},
"llm_prompt": {
"system_prompt": "You are a football news filter. Approve: live match events (goals, cards, substitutions), confirmed transfers, injuries, official announcements. Reject: rumours, opinions, betting, fantasy football. Return JSON with 'important' (bool), 'urgency' (1-3), 'reason' (string)."
}
}
Natural disaster / Weather alerts
{
"override_keywords": {
"keywords": ["tsunami warning", "earthquake", "tornado warning", "hurricane", "evacuation order"]
},
"instant_reject_keywords": {
"keywords": ["donate", "pray for", "climate change debate", "subscribe"]
},
"must_match_keywords": {
"keywords": ["earthquake", "tsunami", "tornado", "hurricane", "wildfire", "flood", "evacuation", "magnitude", "category", "storm surge", "landslide", "volcanic"]
},
"llm_prompt": {
"system_prompt": "You are a disaster alert filter. Approve: active natural disaster reports, official warnings, evacuation orders, casualty reports, damage assessments. Reject: general weather forecasts, climate opinions, historical events. Return JSON with 'important' (bool), 'urgency' (1-3), 'reason' (string)."
}
}
python controlbot.py
Then on Telegram, message your control bot:
/startall
This starts both filterbot and posterbot. On first run, filterbot will prompt in the terminal for your Telegram phone number and a login code.
| Command | Description |
|---|---|
/status |
|
| Show running status of all bots | |
/start <bot> |
|
| Start filterbot or posterbot | |
/stop <bot> |
|
| Stop a specific bot | |
/restart <bot> |
|
| Restart a specific bot | |
/startall |
|
| Start all bots at once | |
/stopall |
|
| Stop all bots | |
/restartall |
|
| Restart all bots | |
/logs <bot> |
|
| View last 50 log lines | |
/stats |
|
| Latest heartbeat from each bot | |
/clearstats |
|
| Reset saved heartbeat stats | |
/help |
|
| List all commands |
Incoming message from source channels
β
βββ 1. Keyword Pre-Filter (0ms, no API cost)
β βββ Override keywords β PASS immediately (e.g. "airstrike", "breaking:")
β βββ Instant-reject keywords β DROP (e.g. "donate", "subscribe")
β βββ Must-match keywords β PASS if signal found, DROP if not
β
βββ 2. Semantic Deduplication
β βββ Compares against last 13 messages using sentence embeddings
β Score β₯ 0.65 β duplicate β DROP
β
βββ 3. Auto-Translation
β βββ Non-English messages translated via Google Translate
β
βββ 4. LLM Evaluation (Groq β Llama 3.1 8B)
β βββ Important β FORWARD with urgency tag
β βββ Not important β DROP
β
βββ 5. Forward to destination channel
βββ π΄ FLASH β active kinetic events
βββ π‘ NOTABLE β significant geopolitical developments
βββ (no tag) β routine newsworthy updates
Message arrives in destination channel
β
βββ 1. Semantic Deduplication (with thread override)
β
βββ 2. Batch Queue (processes every 60 seconds)
β
βββ 3. Image Analysis (if media attached)
β βββ Logo detection β drop image if it's a channel logo
β βββ Auto-captioning β generate caption if no text
β
βββ 4. Story Threading Detection
β βββ Checks if message is an update to a recently posted story
β Score 0.55β0.64 β format as "UPDATE:" and reply to original
β
βββ 5. AI Formatting (Groq β Llama 3.3 70B)
β βββ Standalone β 25-50 word factual summary
β βββ Thread update β short follow-up referencing original
β
βββ 6. Post to Bluesky
βββ Text-only, text + image, or text + video
βββ Thread replies create Bluesky conversation threads
{
"destination_channel": -100XXXXXXXXXX,
"channels": {
"-100XXXXXXXXXX": "Channel Name",
"-100YYYYYYYYYY": "Another Channel"
},
"channel_tiers": {
"-100XXXXXXXXXX": "high",
"-100YYYYYYYYYY": ""
},
"rapid_update_channels": [
-100XXXXXXXXXX
]
}
| Field | Description |
|---|---|
destination_channel |
|
| Where filterbot forwards approved messages. Posterbot also watches this channel. | |
channels |
|
| Map of channel ID β display name. These are the channels filterbot monitors. | |
channel_tiers |
|
| Credibility rating per channel. Affects how strictly the LLM evaluates messages. | |
rapid_update_channels |
|
| Channels that get a lower dedup threshold for rapid-fire updates. |
{
"override_keywords": {
"_comment": "Tier 0: Instant pass β highest confidence signals",
"keywords": ["breaking:", "flash:", "your critical keyword"]
},
"instant_reject_keywords": {
"_comment": "Tier 1: Instant drop β spam and noise",
"keywords": ["donate", "subscribe", "your noise keyword"]
},
"must_match_keywords": {
"_comment": "Tier 2: Topic gate β at least one must match",
"keywords": ["your", "topic", "keywords", "here"]
},
"llm_prompt": {
"_comment": "The AI evaluation prompt β customize for your domain",
"system_prompt": "Your system prompt here..."
}
}
| Field | Description |
|---|---|
override_keywords |
|
| Messages with these words pass instantly. Use for unambiguous, high-priority signals. | |
instant_reject_keywords |
|
| Messages with these words are dropped. Use for spam, ads, engagement bait. | |
must_match_keywords |
|
| At least one must match for the message to reach the LLM. This is your main topic filter. | |
llm_prompt |
|
The system prompt sent to the AI. Defines what "important" means for your use case. Must instruct the LLM to return JSON with important , urgency , and reason fields. |
Note:All keywords are matched as lowercase substrings. Messages are auto-lowercased before matching, so"airstrike"
will match"AIRSTRIKE"
,"Airstrike"
, etc.
| Parameter | File | Default | Description |
|---|---|---|---|
SEMANTIC_THRESHOLD |
|||
| filterbot / posterbot | 0.65 |
||
| Similarity score above which messages are considered duplicates | |||
UPDATE_THRESHOLD |
|||
| posterbot | 0.55 |
||
| Min similarity to consider a message a story update (for threading) | |||
BATCH_INTERVAL |
|||
| posterbot | 60 |
||
| Seconds between batch processing cycles | |||
QUEUE_MAX_SIZE |
|||
| posterbot | 10 |
||
| Max messages queued before oldest is dropped | |||
LOG_BUFFER_SIZE |
|||
| controlbot | 200 |
||
| Rolling log lines kept per bot |
Both bots emit a heartbeat log every 5 minutes with key metrics:
π filterbot | β± 2h15m | π₯ 847 received | β‘ 312 passed pre-filter (37%) | β 28 deduped | β
89 forwarded | β 195 rejected | π΄ 0 Groq fails
π posterbot | β± 2h15m | π₯ 89 received | β 3 deduped | π 82 posted | π§΅ 7 threaded | β 1 failed | π 4 logos dropped | π Queue: 0/10
filterbot also sends a daily digest summary to the destination channel every 24 hours.
This project is private. Do not distribute without permission.