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

186 lines
7.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Telegram Bridge Service
The Telegram Bridge service posts MeshCore channel messages to Telegram channels or groups via the [Telegram Bot API](https://core.telegram.org/bots/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](https://t.me/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](#getting-the-numeric-chat-id-private-channels) 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](https://t.me/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`:
```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
```bash
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
```ini
[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](#getting-the-numeric-chat-id-private-channels).
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](https://t.me/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.