diff --git a/boswatch/wildcard.py b/boswatch/wildcard.py index 71d1c42..3519502 100644 --- a/boswatch/wildcard.py +++ b/boswatch/wildcard.py @@ -52,51 +52,53 @@ def replaceWildcards(message, bwPacket): # info wildcards # server - "{SNAME}": bwPacket.getField("serverName"), - "{SVERS}": bwPacket.getField("serverVersion"), - "{SDATE}": bwPacket.getField("serverBuildDate"), - "{SBRCH}": bwPacket.getField("serverBranch"), + "{SNAME}": bwPacket.get("serverName"), + "{SVERS}": bwPacket.get("serverVersion"), + "{SDATE}": bwPacket.get("serverBuildDate"), + "{SBRCH}": bwPacket.get("serverBranch"), # client - "{CNAME}": bwPacket.getField("clientName"), - "{CIP}": bwPacket.getField("clientIP"), - "{CVERS}": bwPacket.getField("clientVersion"), - "{CDATE}": bwPacket.getField("clientBuildDate"), - "{CBRCH}": bwPacket.getField("clientBranch"), + "{CNAME}": bwPacket.get("clientName"), + "{CIP}": bwPacket.get("clientIP"), + "{CVERS}": bwPacket.get("clientVersion"), + "{CDATE}": bwPacket.get("clientBuildDate"), + "{CBRCH}": bwPacket.get("clientBranch"), # boswatch wildcards - "{INSRC}": bwPacket.getField("mode"), - "{TIMES}": bwPacket.getField("mode"), - "{FREQ}": bwPacket.getField("frequency"), - "{MODE}": bwPacket.getField("mode"), + "{INSRC}": bwPacket.get("inputSource"), + "{TIMES}": bwPacket.get("timestamp"), + "{FREQ}": bwPacket.get("frequency"), + "{MODE}": bwPacket.get("mode"), # fms wildcards - "{FMS}": bwPacket.getField("fms"), - "{SERV}": bwPacket.getField("service"), - "{COUNT}": bwPacket.getField("country"), - "{LOC}": bwPacket.getField("location"), - "{VEHC}": bwPacket.getField("vehicle"), - "{STAT}": bwPacket.getField("status"), - "{DIR}": bwPacket.getField("direction"), - "{DIRT}": bwPacket.getField("dirextionText"), - "{TACI}": bwPacket.getField("tacticalInfo"), + "{FMS}": bwPacket.get("fms"), + "{SERV}": bwPacket.get("service"), + "{COUNT}": bwPacket.get("country"), + "{LOC}": bwPacket.get("location"), + "{VEHC}": bwPacket.get("vehicle"), + "{STAT}": bwPacket.get("status"), + "{DIR}": bwPacket.get("direction"), + "{DIRT}": bwPacket.get("directionText"), + "{TACI}": bwPacket.get("tacticalInfo"), # pocsag wildcards - "{BIT}": bwPacket.getField("bitrate"), - "{RIC}": bwPacket.getField("ric"), - "{SRIC}": bwPacket.getField("subric"), - "{SRICT}": bwPacket.getField("subricText"), - "{MSG}": bwPacket.getField("message"), + "{BIT}": bwPacket.get("bitrate"), + "{RIC}": bwPacket.get("ric"), + "{SRIC}": bwPacket.get("subric"), + "{SRICT}": bwPacket.get("subricText"), + "{MSG}": bwPacket.get("message"), # zvei wildcards - "{TONE}": bwPacket.getField("tone"), + "{TONE}": bwPacket.get("tone"), # message for MSG packet is done in poc } for wildcard, field in _wildcards.items(): - message = message.replace(wildcard, field) + if field is not None: + message = message.replace(wildcard, field) for wildcard, field in _additionalWildcards.items(): - message = message.replace(wildcard, bwPacket.getField(field)) + if field is not None: + message = message.replace(wildcard, bwPacket.get(field)) return message diff --git a/docu/docs/develop/packet.md b/docu/docs/develop/packet.md index 14d2e88..e67de83 100644 --- a/docu/docs/develop/packet.md +++ b/docu/docs/develop/packet.md @@ -47,5 +47,5 @@ Ein BOSWatch Datenpaket wird in einem Python Dict abgebildet. In der nachfolgend |vehicle|X||||`{VEC}`|| |status|X||||`{STAT}`|| |direction|X||||`{DIR}`|| -|dirextionText|X||||`{DIRT}`|(Fhz->Lst, Lst->Fhz)| +|directionText|X||||`{DIRT}`|(Fhz->Lst, Lst->Fhz)| |tacticalInfo|X||||`{TACI}`|(I, II, III, IV)| diff --git a/docu/docs/modul/descriptor.md b/docu/docs/modul/descriptor.md index dd53b5d..a472b0f 100644 --- a/docu/docs/modul/descriptor.md +++ b/docu/docs/modul/descriptor.md @@ -4,11 +4,16 @@ ## Beschreibung Mit diesem Modul können einem Alarmpaket beliebige Beschreibungen in Abhänigkeit der enthaltenen Informationen hinzugefügt werden. +## Unterstütze Alarmtypen +- Fms +- Pocsag +- Zvei +- Msg + ## Resource `descriptor` ## Konfiguration - Informationen zum Aufbau eines [BOSWatch Pakets](../develop/packet.md) |Feld|Beschreibung|Default| @@ -19,7 +24,6 @@ Informationen zum Aufbau eines [BOSWatch Pakets](../develop/packet.md) |descriptions|Liste der Beschreibungen|| #### `descriptions:` - |Feld|Beschreibung|Default| |----|------------|-------| |for|Inhalt im `scanField` auf welchem geprüft werden soll|| @@ -50,17 +54,18 @@ Informationen zum Aufbau eines [BOSWatch Pakets](../develop/packet.md) ``` --- -## Abhängigkeiten +## Modul Abhängigkeiten +- keine +--- +## Externe Abhängigkeiten - keine --- ## Paket Modifikationen - - Wenn im Paket das Feld `scanField` vorhanden ist, wird das Feld `descrField` dem Paket hinzugefügt - Wenn keine Beschreibung vorhanden ist, wird im Feld `descrField` der Inhalt des Feldes `scanField` hinterlegt --- ## Zusätzliche Wildcards - -- Von der Konfiguration abhängig \ No newline at end of file +- Von der Konfiguration abhängig diff --git a/docu/docs/modul/geocoding.md b/docu/docs/modul/geocoding.md new file mode 100644 index 0000000..f2e69d7 --- /dev/null +++ b/docu/docs/modul/geocoding.md @@ -0,0 +1,55 @@ +#
Geocoding
+--- + +## Beschreibung +Mit diesem Modul können einem Paket die Koordinaten eines Ortes oder einer Adresse angefügt werden. + +## Unterstützte Alarmtypen +- Pocsag + +## Resource +`geocoding` + +## Konfiguration +|Feld|Beschreibung|Default| +|----|------------|-------| +apiProvider|Der Provider für das Geocoding| +apiToken|Der Api-Token fuer die Geocoding-Api| +geoRegex|Regex Capture-Group zum Herausfiltern der Adresse| + +#### Verfügbare Geocoding Provider +|Name|Einstellungswert| +|----|------------| +|Mapbox|mapbox| +|Google Maps|google| + +**Beispiel:** +```yaml + - type: module + name: Geocoding Module + res: geocoding + config: + apiProvider: "{{ Provider für Geocoding }}" + apiToken: "{{ API-Key für Provider }}" + regex: "((?:[^ ]*,)*?)" +``` + +--- +## Modul Abhängigkeiten +- keine + +--- +## Externe Abhängigkeiten +- geocoder + +--- +## Paket Modifikationen +- `address`: gefundene Adresse +- `lat`: Latitude der Adresse +- `lon`: Longitude der Adresse + +--- +## Zusätzliche Wildcards +- `{ADDRESS}`: gefundene Adresse +- `{LAT}`: Latitude der Adresse +- `{LON}`: Longitude der Adresse \ No newline at end of file diff --git a/docu/docs/modul/mode_filter.md b/docu/docs/modul/mode_filter.md index f805835..e016003 100644 --- a/docu/docs/modul/mode_filter.md +++ b/docu/docs/modul/mode_filter.md @@ -4,11 +4,16 @@ ## Beschreibung Mit diesem Modul ist es möglich, die Pakete auf bestimmte Modes (FMS, POCSAG, ZVEI) zu Filtern. Je nach Konfiguration werden Pakete eines bestimmten Modes im aktuellen Router weitergeleitet oder verworfen. +## Unterstütze Alarmtypen +- Fms +- Pocsag +- Zvei +- Msg + ## Resource `filter.modeFilter` ## Konfiguration - |Feld|Beschreibung|Default| |----|------------|-------| |allowed|Liste der erlaubten Paket Typen `fms` `zvei` `pocsag` `msg`|| @@ -24,16 +29,18 @@ Mit diesem Modul ist es möglich, die Pakete auf bestimmte Modes (FMS, POCSAG, Z ``` --- -## Abhängigkeiten +## Modul Abhängigkeiten +- keine +--- +## Externe Abhängigkeiten - keine --- ## Paket Modifikationen - - keine --- ## Zusätzliche Wildcards - - keine + diff --git a/docu/docs/modul/regex_filter.md b/docu/docs/modul/regex_filter.md index f554452..77e0a1d 100644 --- a/docu/docs/modul/regex_filter.md +++ b/docu/docs/modul/regex_filter.md @@ -15,18 +15,22 @@ Folgendes gilt: Vereinfacht kann man sagen, dass einzelnen Router ODER-verknüpft und die jeweiligen Checks UND-verknüpft sind. +## Unterstütze Alarmtypen +- Fms +- Pocsag +- Zvei +- Msg + ## Resource `filter.regexFilter` ## Konfiguration - |Feld|Beschreibung|Default| |----|------------|-------| |name|Beliebiger Name des Filters|| |checks|Liste der einzelnen Checks innerhalb des Filters|| #### `checks:` - |Feld|Beschreibung|Default| |----|------------|-------| |field|Name des Feldes innerhalb des BOSWatch Pakets welches untersucht werden soll|| @@ -50,16 +54,17 @@ Vereinfacht kann man sagen, dass einzelnen Router ODER-verknüpft und die jeweil ``` --- -## Abhängigkeiten +## Modul Abhängigkeiten +- keine +--- +## Externe Abhängigkeiten - keine --- ## Paket Modifikationen - - keine --- ## Zusätzliche Wildcards - -- keine +- keine \ No newline at end of file diff --git a/docu/docs/plugin/http.md b/docu/docs/plugin/http.md new file mode 100644 index 0000000..82f5a9f --- /dev/null +++ b/docu/docs/plugin/http.md @@ -0,0 +1,45 @@ +#
Http
+--- + +## Beschreibung +Mit diesem Plugin ist es moeglich, Http-Anfragen für Alarmierungen zu senden. +Wildcards in den Urls werden automatisch ersetzt. + +## Unterstütze Alarmtypen +- Fms +- Pocsag +- Zvei +- Msg + +## Resource +`http` + +## Konfiguration +|Feld|Beschreibung|Default| +|----|------------|-------| +|fms|Liste mit Urls für Fms-Alarmierung|| +|pocsag|Liste mit Urls für Pocsag-Alarmierung|| +|zvei|Liste mit Urls für Zvei-Alarmierung|| +|msg|Liste mit Urls für Msg-Alarmierung|| + +**Beispiel:** +```yaml + - type: plugin + name: HTTP Plugin + res: http + config: + pocsag: + - "http://google.com?q={MSG}" + - "http://duckduckgo.com?q={MSG}" + fms: + - "http://duckduckgo.com?q={LOC}" +``` + +--- +## Modul Abhängigkeiten +- keine + +--- +## Externe Abhängigkeiten +- asyncio +- aiohttp diff --git a/docu/docs/plugin/telegram.md b/docu/docs/plugin/telegram.md new file mode 100644 index 0000000..1eb5a01 --- /dev/null +++ b/docu/docs/plugin/telegram.md @@ -0,0 +1,43 @@ +#
Telegram
+--- + +## Beschreibung +Mit diesem Plugin ist es moeglich, Telegram-Nachrichten für POCSAG-Alarmierungen zu senden. +Außerdem werden Locations versenden, wenn die Felder `lat` und `lon` im Paket definiert sind. (beispielsweise durch das [Geocoding](../modul/geocoding.md) Modul) + +## Unterstütze Alarmtypen +- Pocsag + +## Resource +`telegram` + +## Konfiguration + +|Feld|Beschreibung|Default| +|----|------------|-------| +|message|Format der Nachricht|| +|botToken|Der Api-Key des Telegram-Bots|| +|chatIds|Liste mit Chat-Ids der Empfängers / der Emfänger-Gruppen|| + +**Beispiel:** +```yaml + - type: plugin + name: Telegram Plugin + res: telegram + config: + message: "{RIC}({SRIC})\n{MSG}" + botToken: "{{ Telegram Bot Token }}" + chatIds: + - "{{ Telegram Chat Id }}" +``` + +--- +## Modul Abhängigkeiten +Aus dem Modul [Geocoding](../modul/geocoding.md) (optional): + +- `lat` +- `lon` + +--- +## Externe Abhängigkeiten +- python-telegram-bot diff --git a/docu/mkdocs.yml b/docu/mkdocs.yml index 0948dd6..df708b9 100644 --- a/docu/mkdocs.yml +++ b/docu/mkdocs.yml @@ -18,10 +18,13 @@ nav: - Routing Mechanismus: information/router.md - Changelog: changelog.md - Module: + - Descriptor: modul/descriptor.md + - Geocoding: modul/geocoding.md - Mode Filter: modul/mode_filter.md - Regex Filter: modul/regex_filter.md - - Descriptor: modul/descriptor.md - - Plugins: tbd.md + - Plugins: + - Http: plugin/http.md + - Telegram: plugin/telegram.md - Entwickler: - Eigenes Modul/Plugin schreiben: develop/ModulPlugin.md - BOSWatch Alarmpaket Format: develop/packet.md diff --git a/module/geocoding.py b/module/geocoding.py new file mode 100644 index 0000000..572bc0d --- /dev/null +++ b/module/geocoding.py @@ -0,0 +1,80 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""! + ____ ____ ______ __ __ __ _____ + / __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ / + / __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ < + / /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ / +/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/ + German BOS Information Script + by Bastian Schroll + +@file: geocoding.py +@date: 22.02.2020 +@author: Jan Speller +@description: Geocoding Module +""" +import logging +from module.moduleBase import ModuleBase + +# ###################### # +# Custom plugin includes # +import geocoder +import re +# ###################### # + +logging.debug("- %s loaded", __name__) + + +class BoswatchModule(ModuleBase): + """!Description of the Module""" + def __init__(self, config): + """!Do not change anything here!""" + super().__init__(__name__, config) # you can access the config class on 'self.config' + + def doWork(self, bwPacket): + """!start an run of the module. + + @param bwPacket: A BOSWatch packet instance""" + if bwPacket.get("mode") == "pocsag": + self.geocode(bwPacket) + + return bwPacket + + def geocode(self, bwPacket): + """!find address in message and get latitude and longitude + + @param bwPacket: A BOSWatch packet instance""" + try: + addressArray = re.search(self.config.get("regex"), bwPacket.get("message")) + provider = self.config.get("apiProvider") + + if addressArray[1] is None: + logging.info("No address found, skipping geocoding") + return bwPacket + + address = addressArray[1] + bwPacket.set("address", address) + self.registerWildcard("{ADDRESS}", "address") + logging.info("Found address: '" + address + "' in packet") + + if "mapbox" == provider: + logging.info("Using Mapbox as provider") + g = geocoder.mapbox(address, key=self.config.get("apiToken")) + elif "google" == provider: + logging.info("Using Google as provider") + g = geocoder.google(address, key=self.config.get("apiToken")) + else: + return bwPacket + + (lat, lon) = g.latlng + logging.info("Found following coordinates for address: [lat=" + str(lat) + ", lon=" + str(lon) + "]") + bwPacket.set("lat", lat) + bwPacket.set("lon", lon) + self.registerWildcard("{LAT}", "lat") + self.registerWildcard("{LON}", "lon") + + return bwPacket + except Exception as e: + logging.exception("Unknown Error while executing geocoding module: " + str(type(e).__name__) + ": " + str(e)) + return bwPacket diff --git a/plugin/http.py b/plugin/http.py new file mode 100644 index 0000000..5d206bc --- /dev/null +++ b/plugin/http.py @@ -0,0 +1,100 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""! + ____ ____ ______ __ __ __ _____ + / __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ / + / __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ < + / /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ / +/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/ + German BOS Information Script + by Bastian Schroll + +@file: http.py +@date: 23.02.2020 +@author: Jan Speller +@description: Http Plugin +""" +import logging +from plugin.pluginBase import PluginBase + +# ###################### # +# Custom plugin includes # +import asyncio +from aiohttp import ClientSession +# ###################### # + +logging.debug("- %s loaded", __name__) + + +class BoswatchPlugin(PluginBase): + """!Description of the Plugin""" + def __init__(self, config): + """!Do not change anything here!""" + super().__init__(__name__, config) # you can access the config class on 'self.config' + + def fms(self, bwPacket): + """!Called on FMS alarm + + @param bwPacket: bwPacket instance + Remove if not implemented""" + urls = self.config.get("fms") + self.makeRequests(urls) + + def pocsag(self, bwPacket): + """!Called on POCSAG alarm + + @param bwPacket: bwPacket instance + Remove if not implemented""" + urls = self.config.get("pocsag") + self.makeRequests(urls) + + def zvei(self, bwPacket): + """!Called on ZVEI alarm + + @param bwPacket: bwPacket instance + Remove if not implemented""" + urls = self.config.get("zvei") + self.makeRequests(urls) + + def msg(self, bwPacket): + """!Called on MSG packet + + @param bwPacket: bwPacket instance + Remove if not implemented""" + urls = self.config.get("msg") + self.makeRequests(urls) + + def makeRequests(self, urls): + """Parses wildcard urls and handles asynchronus requests + + @param urls: array of urls""" + urls = [self.parseWildcards(url) for url in urls] + + loop = asyncio.get_event_loop() + + future = asyncio.ensure_future(self.asyncRequests(urls)) + loop.run_until_complete(future) + + async def asyncRequests(self, urls): + """Handles asynchronus requests + + @param urls: array of urls to send requests to""" + tasks = [] + + async with ClientSession() as session: + for url in urls: + task = asyncio.ensure_future(self.fetch(url, session)) + tasks.append(task) + + responses = asyncio.gather(*tasks) + await responses + + async def fetch(self, url, session): + """Fetches requests + + @param url: url + + @param session: Clientsession instance""" + async with session.get(url) as response: + logging.info("{} returned [{}]".format(response.url, response.status)) + return await response.read() diff --git a/plugin/pluginBase.py b/plugin/pluginBase.py index 5785d69..e26a54b 100644 --- a/plugin/pluginBase.py +++ b/plugin/pluginBase.py @@ -189,4 +189,4 @@ class PluginBase(ABC): if self._bwPacket is None: logging.warning("wildcard replacing not allowed - no bwPacket set") return msg - return wildcard.replaceWildcards(self._bwPacket, msg) + return wildcard.replaceWildcards(msg, self._bwPacket) diff --git a/plugin/telegram.py b/plugin/telegram.py new file mode 100644 index 0000000..5dea41f --- /dev/null +++ b/plugin/telegram.py @@ -0,0 +1,66 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""! + ____ ____ ______ __ __ __ _____ + / __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ / + / __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ < + / /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ / +/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/ + German BOS Information Script + by Bastian Schroll + +@file: telegram.py +@date: 20.02.2020 +@author: Jan Speller +@description: Telegram Plugin +""" +import logging +from plugin.pluginBase import PluginBase + +# ###################### # +# Custom plugin includes # +from telegram.error import (TelegramError, Unauthorized, BadRequest, TimedOut, NetworkError) +import telegram +# ###################### # + +logging.debug("- %s loaded", __name__) + + +class BoswatchPlugin(PluginBase): + """!Description of the Plugin""" + + def __init__(self, config): + """!Do not change anything here!""" + super().__init__(__name__, config) # you can access the config class on 'self.config' + + def onLoad(self): + """!Called by import of the plugin""" + self.bot = telegram.Bot(token=self.config.get("botToken", default="")) + + def pocsag(self, bwPacket): + """!Called on POCSAG alarm + + @param bwPacket: bwPacket instance""" + msg = self.parseWildcards(self.config.get("message")) + if bwPacket.get("lat") is not None and bwPacket.get("lon") is not None: + logging.debug("Found coordinates in packet") + (lat, lon) = (bwPacket.get("lat"), bwPacket.get("lon")) + + for chatId in self.config.get("chatIds", default=[]): + try: + # Send Message via Telegram + logging.info("Sending message to " + chatId) + self.bot.send_message(chat_id=chatId, text=msg) + + # Send Location via Telegram if lat and lon are defined + if lat is not None and lon is not None: + logging.info("Sending location to " + chatId) + self.bot.sendLocation(chat_id=chatId, latitude=lat, longitude=lon) + except Unauthorized: + logging.exception("Error while sending Telegram Message, please Check your api-key") + except (TimedOut, NetworkError): + logging.exception("Error while sending Telegram Message, please Check your connectivity") + except (BadRequest, TelegramError): + logging.exception("Error while sending Telegram Message") + except Exception as e: + logging.exception("Unknown Error while sending Telegram Message: " + str(type(e).__name__) + ": " + str(e)) diff --git a/pytest.sh b/pytest.sh new file mode 100644 index 0000000..2a06d75 --- /dev/null +++ b/pytest.sh @@ -0,0 +1,3 @@ +source ./venv/bin/activate +pytest -c test/pytest.ini +deactivate \ No newline at end of file