Files
meshcore-bot/docs/telegram-bridge.md
agessaman e348fd8d53 Enhance profanity filtering to include hate symbol detection and censorship
- 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.
2026-03-01 21:56:13 -08:00

7.6 KiB
Raw Permalink Blame History

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 sendMessage with 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

  1. Open Telegram and message @BotFather
  2. Send /newbot and follow the prompts (name and username)
  3. Copy the API token BotFather returns (e.g. 123456789:ABCdefGHI...)

2. Add Bot to Your Channel

  1. Create a Telegram channel or use an existing one
  2. Add your bot as an Administrator with at least "Post Messages" permission
  3. 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.

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 bots reply includes the Chat ID (e.g. -1003715244454). Use that number in config as bridge.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>/getUpdates in a browser; in the JSON, find the update for that message and use message.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 14096; 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_TOKEN environment variable instead of config.ini in 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_after on 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 and enabled = true
  • Ensure api_token (or TELEGRAM_BOT_TOKEN) is set when enabled
  • Check logs: grep -i telegram meshcore_bot.log

Messages Not Appearing in Telegram

  1. Channel mapping: The config key is the MeshCore channel name without # (e.g. bridge.HowlTest for channel #howltest). Matching is case-insensitive. Verify bridge.<MeshCoreChannelName> is set to the correct Telegram chat ID.
  2. Bot permissions: Bot must be added to the channel/group as Administrator with "Post Messages"
  3. Chat ID: For public channels use @channelusername; for private use numeric ID (e.g. -100...) — see Getting the numeric chat ID.
  4. 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_RECV only
  • HTTP: aiohttp with fallback to requests in executor; timeout ~10s
  • Config: config.ini section [TelegramBridge]; example in config.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 and enabled = true)

Dependencies: Uses existing aiohttp and requests — no new pip dependencies.