enh(telegram): parse_mode Unterstützung hinzugefügt

- Internationalisierung der Kommentare
- parse_mode hinzugefügt (für Formatierungsmöglichkeiten) mit Auswahlmöglichkeit "HTML" und "MarkdownV2"
- Ergänzung in Dokumentation
- kleinere Korrekturen in Dokumentation
- Dokumentation um die Möglichkeit von Block-Strings (|) ergänzt (Danke sm7tix für den Input!)
This commit is contained in:
KoenigMjr 2025-11-17 18:31:34 +01:00
parent 524efbb0aa
commit c5015f2160
2 changed files with 52 additions and 34 deletions

View file

@ -2,16 +2,16 @@
--- ---
## Beschreibung ## Beschreibung
Mit diesem Plugin ist es moeglich, Telegram-Nachrichten für POCSAG-Alarmierungen zu senden. Dieses Plugin ermöglicht das Versenden von Telegram-Nachrichten für verschiedene Alarmierungsarten.
Außerdem werden Locations versendet, wenn die Felder `lat` und `lon` im Paket definiert sind. (beispielsweise durch das [Geocoding](../modul/geocoding.md) Modul) Wenn im eingehenden Paket die Felder `lat` und `lon` vorhanden sind (z. B. durch das [Geocoding](../modul/geocoding.md) Modul), wird zusätzlich automatisch der Standort als Telegram-Location gesendet.
Die abarbeitung der Alarmierungen erfolgt per Queue nach den Limits der Telegram API, damit keine Nachrichten verloren gehen, diese Funktion kann mit dem ```queue``` Parameter deaktiviert werden. Das Senden der Nachrichten erfolgt über eine interne Queue mit Retry-Logik und exponentiellem Backoff, um die Vorgaben der Telegram API einzuhalten und Nachrichtenverluste zu verhindern. Die Retry-Parameter (max_retries, initial_delay, max_delay) können in der Konfiguration angepasst werden.
## Unterstütze Alarmtypen ## Unterstütze Alarmtypen
- Fms - FMS
- Pocsag - POCSAG
- Zvei - ZVEI
- Msg - MSG
## Resource ## Resource
`telegram` `telegram`
@ -20,16 +20,17 @@ Die abarbeitung der Alarmierungen erfolgt per Queue nach den Limits der Telegram
|Feld|Beschreibung|Default| |Feld|Beschreibung|Default|
|----|------------|-------| |----|------------|-------|
|botToken|Der Api-Key des Telegram-Bots|| |botToken|Der Api-Key des Telegram-Bots|-|
|chatIds|Liste mit Chat-Ids der Empfängers / der Emfänger-Gruppen|| |chatIds|Liste mit Chat-Ids der Empfängers / der Empfänger-Gruppen|-|
|startup_message|Nachricht, dass das Telegram-Plugin erfolgreich geladen wurde|leer| |startup_message|Nachricht beim erfolgreichen Initialisieren des Plugins|leer|
|message_fms|Format der Nachricht für FMS|`{FMS}`| |message_fms|Formatvorlage für FMS-Alarm|`{FMS}`|
|message_pocsag|Format der Nachricht für Pocsag|`{RIC}({SRIC})\n{MSG}`| |message_pocsag|Formatvorlage für POCSAG|`{RIC}({SRIC})\n{MSG}`|
|message_zvei|Format der Nachricht für ZVEI|`{TONE}`| |message_zvei|Formatvorlage für ZVEI|`{TONE}`|
|message_msg|Format der Nachricht für MSG|| |message_msg|Formatvorlage für MSG-Nachricht|-|
|max_retries|Anzahl der Versuche, bis das Senden abgebrochen wird|5| |max_retries|Anzahl Wiederholungsversuche bei Fehlern|5|
|initial_delay|Verzögerung des zweiten Sendeversuchs|2 [Sek.]| |initial_delay|Initiale Wartezeit bei Wiederholungsversuchen|2 [Sek.]|
|max_delay|Maximale Verzögerung|60 [Sek.]| |max_delay|Maximale Retry-Verzögerung|300 [Sek.]|
|parse_mode|Formatierung ("HTML" oder "MarkdownV2"), Case-sensitive!|leer|
**Beispiel:** **Beispiel:**
```yaml ```yaml
@ -37,20 +38,32 @@ Die abarbeitung der Alarmierungen erfolgt per Queue nach den Limits der Telegram
name: Telegram Plugin name: Telegram Plugin
res: telegram res: telegram
config: config:
message_pocsag: "{RIC}({SRIC})\n{MSG}" message_pocsag: |
<b>POCSAG Alarm:</b>
RIC: <b>{RIC}</b> ({SRIC})
{MSG}
parse_mode: "HTML"
startup_message: "Server up and running!" startup_message: "Server up and running!"
botToken: "BOT_TOKEN" botToken: "BOT_TOKEN"
chatIds: chatIds:
- "CHAT_ID" - "CHAT_ID"
``` ```
Hinweis:
Über parse_mode kannst du Telegram-Formatierungen verwenden:
- HTML: `<b>fett</b>`, `<i>kursiv</i>`, `<u>unterstrichen</u>`, `<s>durchgestrichen</s>`, ...
- MarkdownV2: `**fett**`, `__unterstrichen__`, `_italic \*text_` usw. (Escape-Regeln beachten)
Block-Strings (|) eignen sich perfekt für mehrzeilige Nachrichten und vermeiden Escape-Zeichen wie \n
--- ---
## Modul Abhängigkeiten ## Modul Abhängigkeiten
Aus dem Modul [Geocoding](../modul/geocoding.md) (optional/nur POCSAG): OPTIONAL, nur für POCSAG-Locationversand: Aus dem Modul [Geocoding](../modul/geocoding.md):
- `lat` - `lat`
- `lon` - `lon`
--- ---
## Externe Abhängigkeiten ## Externe Abhängigkeiten
- python-telegram-bot keine

View file

@ -10,7 +10,7 @@ r"""!
by Bastian Schroll by Bastian Schroll
@file: telegram.py @file: telegram.py
@date: 12.07.2025 @date: 17.11.2025
@author: Claus Schichl nach der Idee von Jan Speller @author: Claus Schichl nach der Idee von Jan Speller
@description: Telegram-Plugin mit Retry-Logik ohne externe Telegram-Abhängigkeiten @description: Telegram-Plugin mit Retry-Logik ohne externe Telegram-Abhängigkeiten
""" """
@ -31,16 +31,17 @@ logger = logging.getLogger(__name__)
# =========================== # ===========================
# TelegramSender-Klasse # TelegramSender-Class
# =========================== # ===========================
class TelegramSender: class TelegramSender:
def __init__(self, bot_token, chat_ids, max_retries=None, initial_delay=None, max_delay=None): def __init__(self, bot_token, chat_ids, max_retries=None, initial_delay=None, max_delay=None, parse_mode=None):
self.bot_token = bot_token self.bot_token = bot_token
self.chat_ids = chat_ids self.chat_ids = chat_ids
self.max_retries = max_retries if max_retries is not None else 5 self.max_retries = max_retries if max_retries is not None else 5
self.initial_delay = initial_delay if initial_delay is not None else 2 self.initial_delay = initial_delay if initial_delay is not None else 2
self.max_delay = max_delay if max_delay is not None else 300 self.max_delay = max_delay if max_delay is not None else 300
self.parse_mode = parse_mode
self.msg_queue = queue.Queue() self.msg_queue = queue.Queue()
self._worker = threading.Thread(target=self._worker_loop, daemon=True) self._worker = threading.Thread(target=self._worker_loop, daemon=True)
self._worker.start() self._worker.start()
@ -75,11 +76,11 @@ class TelegramSender:
logger.warning(f"Erneutes Einreihen der Nachricht (Versuch {retry_count + 1}).") logger.warning(f"Erneutes Einreihen der Nachricht (Versuch {retry_count + 1}).")
self.msg_queue.put((msg_type, chat_id, content, retry_count + 1)) self.msg_queue.put((msg_type, chat_id, content, retry_count + 1))
# Nutze den von Telegram gelieferten Wert (retry_after), falls vorhanden # use the Telegram-provided value (retry_after) if available
wait_time = custom_delay if custom_delay is not None else delay wait_time = custom_delay if custom_delay is not None else delay
time.sleep(wait_time) time.sleep(wait_time)
# Erhöhe Delay für den nächsten Versuch (exponentielles Backoff) # increase delay for the next attempt (exponential backoff)
delay = min(delay * 2, self.max_delay) delay = min(delay * 2, self.max_delay)
except Exception as e: except Exception as e:
@ -91,8 +92,10 @@ class TelegramSender:
url = f"https://api.telegram.org/bot{self.bot_token}/sendMessage" url = f"https://api.telegram.org/bot{self.bot_token}/sendMessage"
payload = { payload = {
'chat_id': chat_id, 'chat_id': chat_id,
'text': content 'text': content,
} }
if self.parse_mode:
payload['parse_mode'] = self.parse_mode
elif msg_type == "location": elif msg_type == "location":
url = f"https://api.telegram.org/bot{self.bot_token}/sendLocation" url = f"https://api.telegram.org/bot{self.bot_token}/sendLocation"
payload = { payload = {
@ -101,25 +104,25 @@ class TelegramSender:
} }
else: else:
logger.error("Unbekannter Nachrichtentyp.") logger.error("Unbekannter Nachrichtentyp.")
return False, True, None # Unbekannter Typ = permanent falsch return False, True, None # unknown message type = permanent failure
try: try:
custom_delay = None # Standardwert für Rückgabe, außer bei 429 custom_delay = None # standardvalue for return, except in case of 429
response = requests.post(url, data=payload, timeout=10) response = requests.post(url, data=payload, timeout=10)
if response.status_code == 429: if response.status_code == 429:
custom_delay = response.json().get("parameters", {}).get("retry_after", 5) custom_delay = response.json().get("parameters", {}).get("retry_after", 5)
logger.warning(f"Rate Limit erreicht warte {custom_delay} Sekunden.") logger.warning(f"Rate Limit erreicht warte {custom_delay} Sekunden.")
return False, False, custom_delay # Telegram gibt genaue Wartezeit vor return False, False, custom_delay # Telegram gives exact wait time
if response.status_code == 400: if response.status_code == 400:
logger.error("Ungültige Parameter Nachricht wird nicht erneut gesendet.") logger.error("Ungültige Parameter Nachricht wird nicht erneut gesendet.")
return False, True, custom_delay # Permanent fehlerhaft return False, True, custom_delay # permanent failure
if response.status_code == 401: if response.status_code == 401:
logger.critical("Ungültiger Bot-Token bitte prüfen!") logger.critical("Ungültiger Bot-Token bitte prüfen!")
return False, True, custom_delay # Permanent fehlerhaft return False, True, custom_delay # permanent failure
response.raise_for_status() response.raise_for_status()
logger.info(f"Erfolgreich gesendet an Chat-ID {chat_id}") logger.info(f"Erfolgreich gesendet an Chat-ID {chat_id}")
@ -131,7 +134,7 @@ class TelegramSender:
# =========================== # ===========================
# BoswatchPlugin-Klasse # BoswatchPlugin-Class
# =========================== # ===========================
@ -149,17 +152,19 @@ class BoswatchPlugin(PluginBase):
logger.error("botToken oder chatIds fehlen in der Konfiguration!") logger.error("botToken oder chatIds fehlen in der Konfiguration!")
return return
# Konfigurierbare Parameter mit Fallback-Defaults # configurable parameters with fallback defaults
max_retries = self.config.get("max_retries") max_retries = self.config.get("max_retries")
initial_delay = self.config.get("initial_delay") initial_delay = self.config.get("initial_delay")
max_delay = self.config.get("max_delay") max_delay = self.config.get("max_delay")
parse_mode = self.config.get("parse_mode")
self.sender = TelegramSender( self.sender = TelegramSender(
bot_token=bot_token, bot_token=bot_token,
chat_ids=chat_ids, chat_ids=chat_ids,
max_retries=max_retries, max_retries=max_retries,
initial_delay=initial_delay, initial_delay=initial_delay,
max_delay=max_delay max_delay=max_delay,
parse_mode=parse_mode
) )
startup_message = self.config.get("startup_message") startup_message = self.config.get("startup_message")