- Updated the profanity filter to check for hate symbols (e.g., swastika Unicode) in addition to word-based profanity, ensuring comprehensive message filtering. - Modified the `censor` function to replace hate symbols with `***`, maintaining functionality even when the `better-profanity` package is unavailable. - Updated documentation in `discord-bridge.md` and `telegram-bridge.md` to reflect the new hate symbol handling features. - Added tests to verify detection and censorship of hate symbols, ensuring robustness of the profanity filter.
7.6 KiB
Telegram Bridge Service
The Telegram Bridge service posts MeshCore channel messages to Telegram channels or groups via the Telegram Bot API. This is a one-way, read-only bridge — messages only flow from MeshCore to Telegram.
Features:
- One-way message flow (MeshCore → Telegram only)
- Multi-channel mapping (map MeshCore channels to Telegram chat IDs)
- Bot API
sendMessagewith optional HTML formatting - DMs are NEVER bridged (hardcoded for privacy)
- Per-chat rate limiting and retries with exponential backoff
- Disabled by default (opt-in)
Quick Start
1. Create a Bot and Get Token
- Open Telegram and message @BotFather
- Send
/newbotand follow the prompts (name and username) - Copy the API token BotFather returns (e.g.
123456789:ABCdefGHI...)
2. Add Bot to Your Channel
- Create a Telegram channel or use an existing one
- Add your bot as an Administrator with at least "Post Messages" permission
- Get the chat ID:
- Public channels: Use the channel username (e.g.
@HowlTest) as the chat ID - Private channels/groups: Use a numeric ID (e.g.
-1001234567890). See Getting the numeric chat ID below.
- Public channels: Use the channel username (e.g.
Getting the numeric chat ID (private channels)
For private channels or groups, the chat ID is a number (often starting with -100). Two ways to get it:
- Forward a message: Forward any message from the private channel to @userinfobot in a private chat. The bot’s reply includes the Chat ID (e.g.
-1003715244454). Use that number in config asbridge.ChannelName = -1003715244454. - Bot API getUpdates: Add your bot to the channel as admin, then send a message in the channel. Open
https://api.telegram.org/bot<YOUR_TOKEN>/getUpdatesin a browser; in the JSON, find the update for that message and usemessage.chat.id(e.g.-1003715244454).
3. Configure Bot
Edit config.ini:
[TelegramBridge]
enabled = true
api_token = YOUR_BOT_TOKEN_FROM_BOTFATHER
# Map MeshCore channel names to Telegram chat IDs
bridge.HowlTest = @HowlTest
# For private: bridge.SomeChannel = -1001234567890
You can also set the token via the TELEGRAM_BOT_TOKEN environment variable (takes precedence over config).
4. Restart Bot
sudo systemctl restart meshcore-bot
# OR if running manually: python3 meshcore_bot.py
5. Test
Send a message on the bridged MeshCore channel — it should appear in the Telegram channel.
Configuration
Config Keys
| Key | Required | Description |
|---|---|---|
enabled |
Yes | true to enable the bridge, false to disable (default: false) |
api_token |
Yes* | Bot token from @BotFather. Can use env var TELEGRAM_BOT_TOKEN instead |
bridge.<channel> |
At least one | MeshCore channel name → Telegram chat ID (@channel or numeric). Use the channel name without # (e.g. bridge.HowlTest). Matching is case-insensitive and ignores a leading #. |
parse_mode |
No | HTML (default), Markdown, or MarkdownV2 |
disable_web_page_preview |
No | true/false — disable link previews (default: false) |
max_message_length |
No | 1–4096; truncate longer messages (default: 4096) |
filter_profanity |
No | Profanity handling: drop (default), censor, or off. Word list via better-profanity; hate symbols (e.g. 卐/卍) are always blocked/censored. |
* Either api_token in config or TELEGRAM_BOT_TOKEN in the environment must be set when the bridge is enabled.
Example
[TelegramBridge]
enabled = true
api_token = 123456789:ABCdefGHIjklMNOpqrSTUvwxYZ
# parse_mode = HTML
# disable_web_page_preview = false
# max_message_length = 4096
bridge.HowlTest = @HowlTest
bridge.Public = @MyPublicChannel
bridge.emergency = -1001234567890
Security & Privacy
Token Security
- Do not log or expose the API token. The service masks it in logs (e.g. first/last 4 chars).
- Prefer storing the token in the
TELEGRAM_BOT_TOKENenvironment variable instead ofconfig.iniin shared environments. - Rotate the token in @BotFather if it was ever exposed (e.g. shared in a channel or committed).
DMs Are Never Bridged
For privacy, DMs are NEVER bridged to Telegram. Only channel messages from explicitly configured MeshCore channels are posted. This is hardcoded and cannot be changed via configuration.
Rate Limits
Telegram enforces roughly 1 message per second per chat and 30 messages per second to different chats. The service:
- Maintains a per-chat queue with minimum 1 second spacing between sends
- Uses exponential backoff on failures and respects
retry_afteron HTTP 429 - Drops messages after max retries or max queue age (configurable internally)
Message Format
Bridged messages use HTML by default:
- Sender is bold:
<b>SenderName</b>: message - Optional channel tag:
<i>[ChannelName]</i> <b>Sender</b>: message - MeshCore mentions
@[username]are rendered as<code>@username</code>(no tg:// link for mesh users) - User text is escaped for HTML (
&,<,>) - Messages longer than
max_message_length(default 4096) are truncated with "…"
Troubleshooting
Service Not Starting
- Ensure
[TelegramBridge]exists andenabled = true - Ensure
api_token(orTELEGRAM_BOT_TOKEN) is set when enabled - Check logs:
grep -i telegram meshcore_bot.log
Messages Not Appearing in Telegram
- Channel mapping: The config key is the MeshCore channel name without
#(e.g.bridge.HowlTestfor channel#howltest). Matching is case-insensitive. Verifybridge.<MeshCoreChannelName>is set to the correct Telegram chat ID. - Bot permissions: Bot must be added to the channel/group as Administrator with "Post Messages"
- Chat ID: For public channels use
@channelusername; for private use numeric ID (e.g.-100...) — see Getting the numeric chat ID. - Logs: Look for send errors or rate-limit messages in
meshcore_bot.log
429 Rate Limit
If you see HTTP 429 responses, the service will re-queue and respect retry_after. Reduce message volume or ensure only needed channels are bridged.
FAQ
Q: Can I bridge messages from Telegram to MeshCore?
A: No. This is a one-way bridge (MeshCore → Telegram only).
Q: Can I bridge DMs?
A: No. DMs are never bridged for privacy. This is hardcoded.
Q: How do I get the numeric chat ID for a private group/channel?
A: Forward a message from the group/channel to @userinfobot or use the Telegram API (e.g. getUpdates after the bot is added).
Q: Can I use topics in a supergroup?
A: The initial implementation does not set message_thread_id. It can be added in a future iteration if you have a mapping from channel/topic to thread ID.
Q: How do I disable the bridge temporarily?
A: Set enabled = false in [TelegramBridge] and restart the bot.
Implementation Details
- Base:
BaseServicePlugin(modules/service_plugins/base_service.py) - Event: Subscribes to
EventType.CHANNEL_MSG_RECVonly - HTTP:
aiohttpwith fallback torequestsin executor; timeout ~10s - Config:
config.inisection[TelegramBridge]; example inconfig.ini.example - Service file:
modules/service_plugins/telegram_bridge_service.py - Loader: Auto-discovered; no changes to
service_plugin_loader.py(loads when section exists andenabled = true)
Dependencies: Uses existing aiohttp and requests — no new pip dependencies.