Merge pull request #83 from caronc/23-gotify-support

Added Gotify Support; refs #23
This commit is contained in:
Chris Caron 2019-03-13 16:21:35 -04:00 committed by GitHub
commit c8778e9a00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 303 additions and 6 deletions

View File

@ -38,6 +38,7 @@ The table below identifies the services this tool supports and some example serv
| [Emby](https://github.com/caronc/apprise/wiki/Notify_emby) | emby:// or embys:// | (TCP) 8096 | emby://user@hostname/<br />emby://user:password@hostname | [Emby](https://github.com/caronc/apprise/wiki/Notify_emby) | emby:// or embys:// | (TCP) 8096 | emby://user@hostname/<br />emby://user:password@hostname
| [Faast](https://github.com/caronc/apprise/wiki/Notify_faast) | faast:// | (TCP) 443 | faast://authorizationtoken | [Faast](https://github.com/caronc/apprise/wiki/Notify_faast) | faast:// | (TCP) 443 | faast://authorizationtoken
| [Gnome](https://github.com/caronc/apprise/wiki/Notify_gnome) | gnome:// | n/a | gnome:// | [Gnome](https://github.com/caronc/apprise/wiki/Notify_gnome) | gnome:// | n/a | gnome://
| [Gotify](https://github.com/caronc/apprise/wiki/Notify_gotify) | gotify:// or gotifys:// | (TCP) 80 or 443 | gotify://hostname/token<br />gotifys://hostname/token?priority=high
| [Growl](https://github.com/caronc/apprise/wiki/Notify_growl) | growl:// | (UDP) 23053 | growl://hostname<br />growl://hostname:portno<br />growl://password@hostname<br />growl://password@hostname:port</br>**Note**: you can also use the get parameter _version_ which can allow the growl request to behave using the older v1.x protocol. An example would look like: growl://hostname?version=1 | [Growl](https://github.com/caronc/apprise/wiki/Notify_growl) | growl:// | (UDP) 23053 | growl://hostname<br />growl://hostname:portno<br />growl://password@hostname<br />growl://password@hostname:port</br>**Note**: you can also use the get parameter _version_ which can allow the growl request to behave using the older v1.x protocol. An example would look like: growl://hostname?version=1
| [IFTTT](https://github.com/caronc/apprise/wiki/Notify_ifttt) | ifttt:// | (TCP) 443 | ifttt://webhooksID/Event<br />ifttt://webhooksID/Event1/Event2/EventN<br/>ifttt://webhooksID/Event1/?+Key=Value<br/>ifttt://webhooksID/Event1/?-Key=value1 | [IFTTT](https://github.com/caronc/apprise/wiki/Notify_ifttt) | ifttt:// | (TCP) 443 | ifttt://webhooksID/Event<br />ifttt://webhooksID/Event1/Event2/EventN<br/>ifttt://webhooksID/Event1/?+Key=Value<br/>ifttt://webhooksID/Event1/?-Key=value1
| [Join](https://github.com/caronc/apprise/wiki/Notify_join) | join:// | (TCP) 443 | join://apikey/device<br />join://apikey/device1/device2/deviceN/<br />join://apikey/group<br />join://apikey/groupA/groupB/groupN<br />join://apikey/DeviceA/groupA/groupN/DeviceN/ | [Join](https://github.com/caronc/apprise/wiki/Notify_join) | join:// | (TCP) 443 | join://apikey/device<br />join://apikey/device1/device2/deviceN/<br />join://apikey/group<br />join://apikey/groupA/groupB/groupN<br />join://apikey/DeviceA/groupA/groupN/DeviceN/

View File

@ -0,0 +1,250 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
# All rights reserved.
#
# This code is licensed under the MIT License.
#
# 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.
# For this plugin to work correct, the Gotify server must be set up to allow
# for remote connections.
# Gotify Docker configuration: https://hub.docker.com/r/gotify/server
# Example: https://github.com/gotify/server/blob/\
# f2c2688f0b5e6a816bbcec768ca1c0de5af76b88/ADD_MESSAGE_EXAMPLES.md#python
# API: https://gotify.net/docs/swagger-docs
import six
import requests
from json import dumps
from .NotifyBase import NotifyBase
from ..common import NotifyType
# Priorities
class GotifyPriority(object):
LOW = 0
MODERATE = 3
NORMAL = 5
HIGH = 8
EMERGENCY = 10
GOTIFY_PRIORITIES = (
GotifyPriority.LOW,
GotifyPriority.MODERATE,
GotifyPriority.NORMAL,
GotifyPriority.HIGH,
GotifyPriority.EMERGENCY,
)
class NotifyGotify(NotifyBase):
"""
A wrapper for Gotify Notifications
"""
# The default descriptive name associated with the Notification
service_name = 'Gotify'
# The services URL
service_url = 'https://github.com/gotify/server'
# The default protocol
protocol = 'gotify'
# The default secure protocol
secure_protocol = 'gotifys'
# A URL that takes you to the setup/help of the specific protocol
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_gotify'
def __init__(self, token, priority=None, **kwargs):
"""
Initialize Gotify Object
"""
super(NotifyGotify, self).__init__(**kwargs)
if not isinstance(token, six.string_types):
msg = 'An invalid Gotify token was specified.'
self.logger.warning('msg')
raise TypeError(msg)
if priority not in GOTIFY_PRIORITIES:
self.priority = GotifyPriority.NORMAL
else:
self.priority = priority
if self.secure:
self.schema = 'https'
else:
self.schema = 'http'
# Our access token does not get created until we first
# authenticate with our Gotify server. The same goes for the
# user id below.
self.access_token = token
return
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
"""
Perform Gotify Notification
"""
url = '%s://%s' % (self.schema, self.host)
if self.port:
url += ':%d' % self.port
# Append our remaining path
url += '/message'
# Define our parameteers
params = {
'token': self.access_token,
}
# Prepare Gotify Object
payload = {
'priority': 2,
'title': title,
'message': body,
}
# Our headers
headers = {
'User-Agent': self.app_id,
'Content-Type': 'application/json',
}
self.logger.debug('Gotify POST URL: %s (cert_verify=%r)' % (
url, self.verify_certificate,
))
self.logger.debug('Gotify Payload: %s' % str(payload))
# Always call throttle before the requests are made
self.throttle()
try:
r = requests.post(
url,
params=params,
data=dumps(payload),
headers=headers,
verify=self.verify_certificate,
)
if r.status_code != requests.codes.ok:
# We had a problem
status_str = \
NotifyBase.http_response_code_lookup(r.status_code)
self.logger.warning(
'Failed to send Gotify notification: '
'{}{}error={}.'.format(
status_str,
', ' if status_str else '',
r.status_code))
self.logger.debug(
'Response Details:\r\n{}'.format(r.content))
# Mark our failure
return False
else:
self.logger.info('Sent Gotify notification.')
except requests.RequestException as e:
self.logger.warning(
'A Connection error occured sending Gotify '
'notification to %s.' % self.host)
self.logger.debug('Socket Exception: %s' % str(e))
# Mark our failure
return False
return True
def url(self):
"""
Returns the URL built dynamically based on specified arguments.
"""
# Define any arguments set
args = {
'format': self.notify_format,
'overflow': self.overflow_mode,
'priority': self.priority,
}
default_port = 443 if self.secure else 80
return '{schema}://{hostname}{port}/{token}/?{args}'.format(
schema=self.secure_protocol if self.secure else self.protocol,
hostname=self.host,
port='' if self.port is None or self.port == default_port
else ':{}'.format(self.port),
token=self.access_token,
args=self.urlencode(args),
)
@staticmethod
def parse_url(url):
"""
Parses the URL and returns enough arguments that can allow
us to substantiate this object.
"""
results = NotifyBase.parse_url(url)
if not results:
# We're done early
return results
# optionally find the provider key
try:
token = [x for x in filter(
bool, NotifyBase.split_path(results['fullpath']))][0]
except (AttributeError, IndexError):
token = None
if 'priority' in results['qsd'] and len(results['qsd']['priority']):
_map = {
'l': GotifyPriority.LOW,
'm': GotifyPriority.MODERATE,
'n': GotifyPriority.NORMAL,
'h': GotifyPriority.HIGH,
'e': GotifyPriority.EMERGENCY,
}
try:
results['priority'] = \
_map[results['qsd']['priority'][0].lower()]
except KeyError:
# No priority was set
pass
# Set our token
results['token'] = token
return results

View File

@ -34,6 +34,7 @@ from .NotifyDiscord import NotifyDiscord
from .NotifyEmail import NotifyEmail from .NotifyEmail import NotifyEmail
from .NotifyEmby import NotifyEmby from .NotifyEmby import NotifyEmby
from .NotifyFaast import NotifyFaast from .NotifyFaast import NotifyFaast
from .NotifyGotify import NotifyGotify
from .NotifyGrowl.NotifyGrowl import NotifyGrowl from .NotifyGrowl.NotifyGrowl import NotifyGrowl
from .NotifyGnome import NotifyGnome from .NotifyGnome import NotifyGnome
from .NotifyIFTTT import NotifyIFTTT from .NotifyIFTTT import NotifyIFTTT
@ -72,9 +73,9 @@ SCHEMA_MAP = {}
__all__ = [ __all__ = [
# Notification Services # Notification Services
'NotifyBoxcar', 'NotifyDBus', 'NotifyEmail', 'NotifyEmby', 'NotifyDiscord', 'NotifyBoxcar', 'NotifyDBus', 'NotifyEmail', 'NotifyEmby', 'NotifyDiscord',
'NotifyFaast', 'NotifyGnome', 'NotifyGrowl', 'NotifyIFTTT', 'NotifyJoin', 'NotifyFaast', 'NotifyGnome', 'NotifyGotify', 'NotifyGrowl', 'NotifyIFTTT',
'NotifyJSON', 'NotifyMatrix', 'NotifyMatterMost', 'NotifyProwl', 'NotifyJoin', 'NotifyJSON', 'NotifyMatrix', 'NotifyMatterMost',
'NotifyPushed', 'NotifyPushBullet', 'NotifyPushjet', 'NotifyProwl', 'NotifyPushed', 'NotifyPushBullet', 'NotifyPushjet',
'NotifyPushover', 'NotifyRocketChat', 'NotifyRyver', 'NotifySlack', 'NotifyPushover', 'NotifyRocketChat', 'NotifyRyver', 'NotifySlack',
'NotifySNS', 'NotifyTwitter', 'NotifyTelegram', 'NotifyXBMC', 'NotifySNS', 'NotifyTwitter', 'NotifyTelegram', 'NotifyXBMC',
'NotifyXML', 'NotifyWindows', 'NotifyXML', 'NotifyWindows',

View File

@ -57,9 +57,9 @@ setup(
long_description_content_type='text/markdown', long_description_content_type='text/markdown',
url='https://github.com/caronc/apprise', url='https://github.com/caronc/apprise',
keywords='Push Notifications Email AWS SNS Boxcar Discord Dbus Emby Faast ' keywords='Push Notifications Email AWS SNS Boxcar Discord Dbus Emby Faast '
'Gnome Growl IFTTT Join KODI Matrix Mattermost Prowl PushBullet ' 'Gnome Gotify Growl IFTTT Join KODI Matrix Mattermost Prowl'
'Pushjet Pushed Pushover Rocket.Chat Ryver Slack Telegram Twiiter ' 'PushBullet Pushjet Pushed Pushover Rocket.Chat Ryver Slack Telegram '
'XBMC Microsoft Windows CLI API', 'Twitter XBMC Microsoft Windows CLI API',
author='Chris Caron', author='Chris Caron',
author_email='lead2gold@gmail.com', author_email='lead2gold@gmail.com',
packages=find_packages(), packages=find_packages(),

View File

@ -269,6 +269,51 @@ TEST_URLS = (
'test_requests_exceptions': True, 'test_requests_exceptions': True,
}), }),
##################################
# NotifyGotify
##################################
('gotify://', {
'instance': None,
}),
# No token specified
('gotify://hostname', {
'instance': TypeError,
}),
# Provide a hostname and token
('gotify://hostname/%s' % ('t' * 16), {
'instance': plugins.NotifyGotify,
}),
# Provide a priority
('gotify://hostname/%s?priority=high' % ('i' * 16), {
'instance': plugins.NotifyGotify,
}),
# Provide an invalid priority
('gotify://hostname:8008/%s?priority=invalid' % ('i' * 16), {
'instance': plugins.NotifyGotify,
}),
# An invalid url
('gotify://:@/', {
'instance': None,
}),
('gotify://hostname/%s/' % ('t' * 16), {
'instance': plugins.NotifyGotify,
# force a failure
'response': False,
'requests_response_code': requests.codes.internal_server_error,
}),
('gotifys://localhost/%s/' % ('t' * 16), {
'instance': plugins.NotifyGotify,
# throw a bizzare code forcing us to fail to look it up
'response': False,
'requests_response_code': 999,
}),
('gotify://localhost/%s/' % ('t' * 16), {
'instance': plugins.NotifyGotify,
# Throws a series of connection and transfer exceptions when this flag
# is set and tests that we gracfully handle them
'test_requests_exceptions': True,
}),
################################## ##################################
# NotifyIFTTT - If This Than That # NotifyIFTTT - If This Than That
################################## ##################################