# BrandMeister LastHeard Telegram Bot A small bot that listens to the [BrandMeister](https://brandmeister.network) DMR "LastHeard" stream and posts a formatted message to a Telegram channel whenever a relevant transmission ends. It is useful for keeping a club or regional channel informed about activity on a specific Talkgroup or repeater cluster. ## What it does The bot connects to the BrandMeister LastHeard Socket.IO stream and reacts to `Session-Stop` events. It sends a Telegram message when **either** of the following matches: - **Mirror Talkgroup** — the transmission's destination is the Talkgroup you set via `TG_TO_WATCH`. - **TG8 cluster** — the transmission is on TG8 *and* came in via a repeater that belongs to the cluster named in `CLUSTER_TO_WATCH` (only when `CLUSTER_WATCH` is enabled). Cluster membership is resolved at startup from the BrandMeister API. Only recent events (stopped within the last ~20 seconds) trigger a message, so old/replayed events are ignored. Each Telegram message includes the source callsign, ID, name, talker alias, the repeater/hotspot used to access the network, slot, Talkgroup, master, RSSI and BER. ## Requirements - A Telegram bot token (create one via [@BotFather](https://t.me/BotFather)) and a channel the bot can post to. - Either Docker (recommended) or Python 3.12+. ## Configuration The bot is configured entirely through environment variables. Copy the example file and fill in your values: ```bash cp .env.example .env ``` | Variable | Required | Description | | ------------------ | -------- | --------------------------------------------------------------------------- | | `TG_TO_WATCH` | yes | Talkgroup ID to mirror to Telegram (e.g. `26235`). | | `CLUSTER_TO_WATCH` | yes | BrandMeister cluster name used for the TG8 check (e.g. `Cluster Name`). | | `TELEGRAM_CHANNEL` | yes | Target channel/chat ID (e.g. `@your_channel`). | | `TELEGRAM_API_KEY` | yes | Telegram bot token from BotFather. | | `CLUSTER_WATCH` | no | `true`/`false` — enable the TG8 cluster check. Defaults to `true`. | | `SLOT_EXCLUDE` | no | Comma-separated `repeaterid:slot` pairs that never count as a slot match (e.g. `262399:2,262383:2`). | | `TG8_EXCLUDE_REPEATERS`| no | Comma-separated repeater IDs whose TG8 traffic is ignored (e.g. `262399`). | ## Running ### With Docker Compose (recommended) ```bash docker compose up -d --build ``` The service restarts automatically unless explicitly stopped. View logs with: ```bash docker compose logs -f ``` ### With plain Docker ```bash docker build -t bm-lh . docker run --env-file .env --restart unless-stopped bm-lh ``` ### Locally with Python ```bash pip install -r requirements.txt set -a && source .env && set +a # load env vars into the shell python -u main.py ``` ## How it works 1. On startup the bot queries the BrandMeister API (`/v2/cluster`) to find the cluster matching `CLUSTER_TO_WATCH`, then fetches its member repeaters (`/v2/cluster//members`). 2. It opens a WebSocket connection to the LastHeard stream (`https://api.brandmeister.network`, path `/lh/socket.io`) and subscribes by emitting `join` with `everything`. 3. For every incoming `mqtt` event it parses the payload and decides whether to forward a Telegram message based on the rules described above. 4. The connection auto-reconnects on errors. ## License Released under the [MIT License](LICENSE).