From 997b5eeec3d7e92c122b83eed8a2477662a296f4 Mon Sep 17 00:00:00 2001 From: JHCD Date: Sun, 19 Jul 2015 20:29:22 +0200 Subject: [PATCH] add NMA plugin #33 first version, only with one receiver --- config/config.template.ini | 11 +- includes/pynma/README.md | 137 +++++++++++++++++++ includes/pynma/__init__.py | 4 + includes/pynma/pynma.py | 148 +++++++++++++++++++++ plugins/notifyMyAndroid/notifyMyAndroid.py | 119 +++++++++++++++++ 5 files changed, 418 insertions(+), 1 deletion(-) create mode 100644 includes/pynma/README.md create mode 100644 includes/pynma/__init__.py create mode 100755 includes/pynma/pynma.py create mode 100644 plugins/notifyMyAndroid/notifyMyAndroid.py diff --git a/config/config.template.ini b/config/config.template.ini index 0bfe019..f82235e 100644 --- a/config/config.template.ini +++ b/config/config.template.ini @@ -97,6 +97,7 @@ eMail = 0 BosMon = 0 firEmergency = 0 jsonSocket = 0 +notifyMyAndroid = 0 # for developing template-module template = 0 @@ -220,6 +221,7 @@ bosmon_password = firserver = localhost firport = 9001 + [jsonSocket] # Protocol for socket (TCP|UDP) protocol = UDP @@ -228,10 +230,17 @@ server = 192.168.0.1 port = 8888 +[notifyMyAndroid] +# this APIKey is used for Plugin AND BOSWatch-Monitor +APIKey = +# Priority goes from -2 (lowest) to 2 (highest). the default priority is 0 (normal) +priority = 0 + + ##################### ##### Not ready yet # ##################### [template] test1 = testString -test2 = 123456 +test2 = 123456 \ No newline at end of file diff --git a/includes/pynma/README.md b/includes/pynma/README.md new file mode 100644 index 0000000..e72ff1e --- /dev/null +++ b/includes/pynma/README.md @@ -0,0 +1,137 @@ +Pynma +====== + +Pynma is a simple python module for the [NotifyMyAndroid][nma] [API][NMA API]. + +[nma]: http://nma.usk.bz/ +[NMA API]: http://nma.usk.bz/api.php + +Credits to: Damien Degois (github.com/babs) +Refactoring: Adriano Maia (adriano@usk.bz) + +[NotifyMyAndroid][nma] +--------------- +NotifyMyAndroid is a Prowl-like application for the Android. Notifications can be sent from your application Android device using push. NMA has an extensive API, which allows your scripts to integrate beautifully. (source: http://nma.usk.bz/) + +### How it works: +First, import the module: + + import pynma + +#### Keys management + +Create a PyNMA simple instance: + + p = pynma.PyNMA( "apikey(s)", "developerkey") + +A developerkey is optional. If you'd like to add just one API key, set it as string, if you want more, just provide a list of API key strings. + + p = pynma.PyNMA(['apikey1','apikey2']) # multiple API keys + p = pynma.PyNMA("apikey1","providerkey") # 1 API key with a providerkey + +For more flexible usage, you can add and remove keys: + + p.addkey("apikey1") + p.addkey(["apikey2","apikey3"]) + +Or set or change the providerkey + + p.developerkey("developerkey") + +#### Notification or Push or Add + + p.push(application, event, description, (opt) url, (opt) priority, (opt) batch mode) + +##### Application + +Application is your message generating application name (limited to 256) + +ex: my music player + +##### Event + +Event is the event name (limited to 1000) + +ex: switched to next track + +##### Description + +The description is the payload of your message (limited to 10000 (10k)) +ex: + + Playing next song, Blah Blah Blah + Artist: blah blah + Album: blah blah + Track: 18/24 + +##### Url + +The URL which should be attached to the notification. +This will trigger a redirect when on the user's device launched, and is viewable in the notification list. + +##### Priority + +Priority goes from -2 (lowest) to 2 (highest). the default priority is 0 (normal) + +##### Batch mode + +Batch mode is a boolean value to set if you'd like to push the same message to multiple API keys 5 by 5 (as the actual verion of prowl API allows you). This can reduce the number of call you make to the API which are limited. + +#### Return + +The push method returns a dict containing different values depending of the success of you call: + +##### The call succeed + +you'll have in the dict those keys: + + type: success + code: the HTTP like code (200 if success) + remaining: the number of API call you can to until the reset + resetdate: number of remaining minutes till the hourly reset of your API call limit + +##### The call failed + +For wathever reason, you call failed, the dict key "message" will contains the erro message returned by Prowl API. You'll find those keys: + + code: 400, 401, 402 or 500 (depends of the error kind) + message: API error message + +For the code description, please refer to [NMA API documentation][NMA API] for more informations + +##### The python module encountered an unhandled problem (mostly during parsing) + +The return keys will be: + + code: 600 + type: pynmaerror + message: the exception message + +Thanks +------ + +* **Cev** for URL integration and some fixes in docstring +* **ChaoticXSinZ** for UTF-8 integration and other typos + +License (MIT) +------------- + + Copyright (c) 2010-2011, Damien Degois. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. diff --git a/includes/pynma/__init__.py b/includes/pynma/__init__.py new file mode 100644 index 0000000..a75b428 --- /dev/null +++ b/includes/pynma/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/python + +from .pynma import PyNMA + diff --git a/includes/pynma/pynma.py b/includes/pynma/pynma.py new file mode 100755 index 0000000..2fc5556 --- /dev/null +++ b/includes/pynma/pynma.py @@ -0,0 +1,148 @@ +#!/usr/bin/python + +from xml.dom.minidom import parseString + +try: + from http.client import HTTPSConnection +except ImportError: + from httplib import HTTPSConnection + +try: + from urllib.parse import urlencode +except ImportError: + from urllib import urlencode + +__version__ = "1.0" + +API_SERVER = 'www.notifymyandroid.com' +ADD_PATH = '/publicapi/notify' + +USER_AGENT="PyNMA/v%s"%__version__ + +def uniq_preserve(seq): # Dave Kirby + # Order preserving + seen = set() + return [x for x in seq if x not in seen and not seen.add(x)] + +def uniq(seq): + # Not order preserving + return list({}.fromkeys(seq).keys()) + +class PyNMA(object): + """PyNMA(apikey=[], developerkey=None) +takes 2 optional arguments: + - (opt) apykey: might me a string containing 1 key or an array of keys + - (opt) developerkey: where you can store your developer key +""" + + def __init__(self, apikey=[], developerkey=None): + self._developerkey = None + self.developerkey(developerkey) + if apikey: + if type(apikey) == str: + apikey = [apikey] + self._apikey = uniq(apikey) + + def addkey(self, key): + "Add a key (register ?)" + if type(key) == str: + if not key in self._apikey: + self._apikey.append(key) + elif type(key) == list: + for k in key: + if not k in self._apikey: + self._apikey.append(k) + + def delkey(self, key): + "Removes a key (unregister ?)" + if type(key) == str: + if key in self._apikey: + self._apikey.remove(key) + elif type(key) == list: + for k in key: + if key in self._apikey: + self._apikey.remove(k) + + def developerkey(self, developerkey): + "Sets the developer key (and check it has the good length)" + if type(developerkey) == str and len(developerkey) == 48: + self._developerkey = developerkey + + def push(self, application="", event="", description="", url="", contenttype=None, priority=0, batch_mode=False, html=False): + """Pushes a message on the registered API keys. +takes 5 arguments: + - (req) application: application name [256] + - (req) event: event name [1000] + - (req) description: description [10000] + - (opt) url: url [512] + - (opt) contenttype: Content Type (act: None (plain text) or text/html) + - (opt) priority: from -2 (lowest) to 2 (highest) (def:0) + - (opt) batch_mode: push to all keys at once (def:False) + - (opt) html: shortcut for contenttype=text/html +Warning: using batch_mode will return error only if all API keys are bad + cf: http://nma.usk.bz/api.php +""" + datas = { + 'application': application[:256].encode('utf8'), + 'event': event[:1024].encode('utf8'), + 'description': description[:10000].encode('utf8'), + 'priority': priority + } + + if url: + datas['url'] = url[:512] + + if contenttype == "text/html" or html == True: # Currently only accepted content type + datas['content-type'] = "text/html" + + if self._developerkey: + datas['developerkey'] = self._developerkey + + results = {} + + if not batch_mode: + for key in self._apikey: + datas['apikey'] = key + res = self.callapi('POST', ADD_PATH, datas) + results[key] = res + else: + datas['apikey'] = ",".join(self._apikey) + res = self.callapi('POST', ADD_PATH, datas) + results[datas['apikey']] = res + return results + + def callapi(self, method, path, args): + headers = { 'User-Agent': USER_AGENT } + if method == "POST": + headers['Content-type'] = "application/x-www-form-urlencoded" + http_handler = HTTPSConnection(API_SERVER) + http_handler.request(method, path, urlencode(args), headers) + resp = http_handler.getresponse() + + try: + res = self._parse_reponse(resp.read()) + except Exception as e: + res = {'type': "pynmaerror", + 'code': 600, + 'message': str(e) + } + pass + + return res + + def _parse_reponse(self, response): + root = parseString(response).firstChild + for elem in root.childNodes: + if elem.nodeType == elem.TEXT_NODE: continue + if elem.tagName == 'success': + res = dict(list(elem.attributes.items())) + res['message'] = "" + res['type'] = elem.tagName + return res + if elem.tagName == 'error': + res = dict(list(elem.attributes.items())) + res['message'] = elem.firstChild.nodeValue + res['type'] = elem.tagName + return res + + diff --git a/plugins/notifyMyAndroid/notifyMyAndroid.py b/plugins/notifyMyAndroid/notifyMyAndroid.py new file mode 100644 index 0000000..675e8bc --- /dev/null +++ b/plugins/notifyMyAndroid/notifyMyAndroid.py @@ -0,0 +1,119 @@ +#!/usr/bin/python +# -*- coding: UTF-8 -*- + +""" +notifyMyAndroid-Plugin to dispatch FMS-, ZVEI- and POCSAG-messages via UDP/TCP + +@author: Jens Herrmann + +@requires: notifyMyAndroid-Configuration has to be set in the config.ini +""" + +import logging # Global logger + +import socket # for connection +import json # for data-transfer + +from includes import globals # Global variables + +from includes.helper import configHandler +from includes.helper import timeHandler +from includes.helper import uft8Converter # UTF-8 converter +from includes.pynma import pynma + +## +# +# onLoad (init) function of plugin +# will be called one time by the pluginLoader on start +# +def onLoad(): + """ + While loading the plugins by pluginLoader.loadPlugins() + this onLoad() routine is called one time for initialize the plugin + + @requires: nothing + + @return: nothing + """ + # nothing to do for this plugin + return + + +## +# +# Main function of jsonSocket-plugin +# will be called by the alarmHandler +# +def run(typ,freq,data): + """ + This function is the implementation of the notifyMyAndroid-Plugin. + + The configuration is set in the config.ini. + + @type typ: string (FMS|ZVEI|POC) + @param typ: Typ of the dataset for sending via UDP/TCP + @type data: map of data (structure see interface.txt) + @param data: Contains the parameter for dispatch to UDP. + @type freq: string + @keyword freq: frequency of the SDR Stick + + @requires: notifyMyAndroid-Configuration has to be set in the config.ini + + @return: nothing + """ + try: + if configHandler.checkConfig("notifyMyAndroid"): #read and debug the config + + try: + # + # initialize to pyNMA + # + APIKey = globals.config.get("notifyMyAndroid","APIKey") + nma = pynma.PyNMA(APIKey) + + except: + logging.error("cannot initialize pyNMA") + logging.debug("cannot initialize %s-socket", exc_info=True) + # Without class, plugin couldn't work + return + + else: + # toDo is equals for all types, so only check if typ is supported + supportedTypes = ["FMS", "ZVEI", "POC"] + if typ in supportedTypes: + logging.debug("Start %s to NMA", typ) + try: + # send data + event = data['description'] + msg = timeHandler.curtime() + if len(data['msg']) > 0: + msg += "\n" + data['msg'] + response = nma.push("BOSWatch", uft8Converter.convertToUTF8(event), uft8Converter.convertToUTF8(msg), priority=globals.config.getint("notifyMyAndroid","priority")) + except: + logging.error("%s to NMA failed", typ) + logging.debug("%s to NMA failed", typ, exc_info=True) + return + else: + try: + # + # check HTTP-Response + # + if str(response[APIKey]['code']) == "200": #Check HTTP Response an print a Log or Error + logging.debug("NMA response: %s" , str(response[APIKey]['code'])) + if int(response[APIKey]['remaining']) < 11: + logging.warning("NMA remaining msgs: %s" , str(response[APIKey]['remaining'])) + else: + logging.debug("NMA remaining msgs: %s" , str(response[APIKey]['remaining'])) + else: + logging.warning("NMA response: %s - %s" , str(response[APIKey]['code']), str(response[APIKey]['message'])) + except: #otherwise + logging.error("cannot read pynma response") + logging.debug("cannot read pynma response", exc_info=True) + return + else: + logging.warning("Invalid Typ: %s", typ) + + except: + # something very mysterious + logging.error("unknown error") + logging.debug("unknown error", exc_info=True) \ No newline at end of file