96 lines
3.6 KiB
Markdown
96 lines
3.6 KiB
Markdown
# 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/<id>/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).
|