Browse Source

Notification Service Native URL Support; refs #109

pull/119/head
Chris Caron 6 years ago committed by GitHub
parent
commit
6d9069e106
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 18
      apprise/Apprise.py
  2. 18
      apprise/plugins/NotifyBase.py
  3. 23
      apprise/plugins/NotifyDiscord.py
  4. 21
      apprise/plugins/NotifyFlock.py
  5. 25
      apprise/plugins/NotifyIFTTT.py
  6. 31
      apprise/plugins/NotifyMSTeams.py
  7. 26
      apprise/plugins/NotifyRyver.py
  8. 25
      apprise/plugins/NotifySlack.py
  9. 21
      apprise/plugins/NotifyWebexTeams.py
  10. 10
      apprise/plugins/__init__.py
  11. 39
      test/test_rest_plugins.py

18
apprise/Apprise.py

@ -124,12 +124,20 @@ class Apprise(object):
# Some basic validation
if schema not in plugins.SCHEMA_MAP:
logger.error('Unsupported schema {}.'.format(schema))
return None
# Give the user the benefit of the doubt that the user may be
# using one of the URLs provided to them by their notification
# service. Before we fail for good, just scan all the plugins
# that support he native_url() parse function
results = \
next((r['plugin'].parse_native_url(_url)
for r in plugins.MODULE_MAP.values()
if r['plugin'].parse_native_url(_url) is not None),
None)
# Parse our url details of the server object as dictionary
# containing all of the information parsed from our URL
results = plugins.SCHEMA_MAP[schema].parse_url(_url)
else:
# Parse our url details of the server object as dictionary
# containing all of the information parsed from our URL
results = plugins.SCHEMA_MAP[schema].parse_url(_url)
if results is None:
# Failed to parse the server URL

18
apprise/plugins/NotifyBase.py

@ -402,3 +402,21 @@ class NotifyBase(URLBase):
del results['overflow']
return results
@staticmethod
def parse_native_url(url):
"""
This is a base class that can be optionally over-ridden by child
classes who can build their Apprise URL based on the one provided
by the notification service they choose to use.
The intent of this is to make Apprise a little more userfriendly
to people who aren't familiar with constructing URLs and wish to
use the ones that were just provied by their notification serivice
that they're using.
This function will return None if the passed in URL can't be matched
as belonging to the notification service. Otherwise this function
should return the same set of results that parse_url() does.
"""
return None

23
apprise/plugins/NotifyDiscord.py

@ -395,6 +395,29 @@ class NotifyDiscord(NotifyBase):
return results
@staticmethod
def parse_native_url(url):
"""
Support https://discordapp.com/api/webhooks/WEBHOOK_ID/WEBHOOK_TOKEN
"""
result = re.match(
r'^https?://discordapp\.com/api/webhooks/'
r'(?P<webhook_id>[0-9]+)/'
r'(?P<webhook_token>[A-Z0-9_-]+)/?'
r'(?P<args>\?[.+])?$', url, re.I)
if result:
return NotifyDiscord.parse_url(
'{schema}://{webhook_id}/{webhook_token}/{args}'.format(
schema=NotifyDiscord.secure_protocol,
webhook_id=result.group('webhook_id'),
webhook_token=result.group('webhook_token'),
args='' if not result.group('args')
else result.group('args')))
return None
@staticmethod
def extract_markdown_sections(markdown):
"""

21
apprise/plugins/NotifyFlock.py

@ -358,3 +358,24 @@ class NotifyFlock(NotifyBase):
parse_bool(results['qsd'].get('image', True))
return results
@staticmethod
def parse_native_url(url):
"""
Support https://api.flock.com/hooks/sendMessage/TOKEN
"""
result = re.match(
r'^https?://api\.flock\.com/hooks/sendMessage/'
r'(?P<token>[a-z0-9-]{24})/?'
r'(?P<args>\?[.+])?$', url, re.I)
if result:
return NotifyFlock.parse_url(
'{schema}://{token}/{args}'.format(
schema=NotifyFlock.secure_protocol,
token=result.group('token'),
args='' if not result.group('args')
else result.group('args')))
return None

25
apprise/plugins/NotifyIFTTT.py

@ -39,6 +39,7 @@
#
# For each event you create you will assign it a name (this will be known as
# the {event} when building your URL.
import re
import requests
from json import dumps
@ -344,3 +345,27 @@ class NotifyIFTTT(NotifyBase):
NotifyIFTTT.parse_list(results['qsd']['to'])
return results
@staticmethod
def parse_native_url(url):
"""
Support https://maker.ifttt.com/use/WEBHOOK_ID/EVENT_ID
"""
result = re.match(
r'^https?://maker\.ifttt\.com/use/'
r'(?P<webhook_id>[A-Z0-9_-]+)'
r'/?(?P<events>([A-Z0-9_-]+/?)+)?'
r'/?(?P<args>\?[.+])?$', url, re.I)
if result:
return NotifyIFTTT.parse_url(
'{schema}://{webhook_id}{events}{args}'.format(
schema=NotifyIFTTT.secure_protocol,
webhook_id=result.group('webhook_id'),
events='' if not result.group('events')
else '@{}'.format(result.group('events')),
args='' if not result.group('args')
else result.group('args')))
return None

31
apprise/plugins/NotifyMSTeams.py

@ -74,7 +74,6 @@ from ..AppriseLocale import gettext_lazy as _
# Used to prepare our UUID regex matching
UUID4_RE = \
r'[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}'
# r'[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
# Token required as part of the API request
# /AAAAAAAAA@AAAAAAAAA/........./.........
@ -364,3 +363,33 @@ class NotifyMSTeams(NotifyBase):
parse_bool(results['qsd'].get('image', True))
return results
@staticmethod
def parse_native_url(url):
"""
Support:
https://outlook.office.com/webhook/ABCD/IncomingWebhook/DEFG/HIJK
"""
# We don't need to do incredibly details token matching as the purpose
# of this is just to detect that were dealing with an msteams url
# token parsing will occur once we initialize the function
result = re.match(
r'^https?://outlook\.office\.com/webhook/'
r'(?P<token_a>[A-Z0-9-]+@[A-Z0-9-]+)/'
r'IncomingWebhook/'
r'(?P<token_b>[A-Z0-9]+)/'
r'(?P<token_c>[A-Z0-9-]+)/?'
r'(?P<args>\?[.+])?$', url, re.I)
if result:
return NotifyMSTeams.parse_url(
'{schema}://{token_a}/{token_b}/{token_c}/{args}'.format(
schema=NotifyMSTeams.secure_protocol,
token_a=result.group('token_a'),
token_b=result.group('token_b'),
token_c=result.group('token_c'),
args='' if not result.group('args')
else result.group('args')))
return None

26
apprise/plugins/NotifyRyver.py

@ -43,10 +43,10 @@ from ..utils import parse_bool
from ..AppriseLocale import gettext_lazy as _
# Token required as part of the API request
VALIDATE_TOKEN = re.compile(r'[A-Za-z0-9]{15}')
VALIDATE_TOKEN = re.compile(r'[A-Z0-9]{15}', re.I)
# Organization required as part of the API request
VALIDATE_ORG = re.compile(r'[A-Za-z0-9-]{3,32}')
VALIDATE_ORG = re.compile(r'[A-Z0-9_-]{3,32}', re.I)
class RyverWebhookMode(object):
@ -353,3 +353,25 @@ class NotifyRyver(NotifyBase):
parse_bool(results['qsd'].get('image', True))
return results
@staticmethod
def parse_native_url(url):
"""
Support https://RYVER_ORG.ryver.com/application/webhook/TOKEN
"""
result = re.match(
r'^https?://(?P<org>[A-Z0-9_-]+)\.ryver\.com/application/webhook/'
r'(?P<webhook_token>[A-Z0-9]+)/?'
r'(?P<args>\?[.+])?$', url, re.I)
if result:
return NotifyRyver.parse_url(
'{schema}://{org}/{webhook_token}/{args}'.format(
schema=NotifyRyver.secure_protocol,
org=result.group('org'),
webhook_token=result.group('webhook_token'),
args='' if not result.group('args')
else result.group('args')))
return None

25
apprise/plugins/NotifySlack.py

@ -472,3 +472,28 @@ class NotifySlack(NotifyBase):
parse_bool(results['qsd'].get('image', True))
return results
@staticmethod
def parse_native_url(url):
"""
Support https://hooks.slack.com/services/TOKEN_A/TOKEN_B/TOKEN_C
"""
result = re.match(
r'^https?://hooks\.slack\.com/services/'
r'(?P<token_a>[A-Z0-9]{9})/'
r'(?P<token_b>[A-Z0-9]{9})/'
r'(?P<token_c>[A-Z0-9]{24})/?'
r'(?P<args>\?[.+])?$', url, re.I)
if result:
return NotifySlack.parse_url(
'{schema}://{token_a}/{token_b}/{token_c}/{args}'.format(
schema=NotifySlack.secure_protocol,
token_a=result.group('token_a'),
token_b=result.group('token_b'),
token_c=result.group('token_c'),
args='' if not result.group('args')
else result.group('args')))
return None

21
apprise/plugins/NotifyWebexTeams.py

@ -245,3 +245,24 @@ class NotifyWebexTeams(NotifyBase):
results['token'] = NotifyWebexTeams.unquote(results['host'])
return results
@staticmethod
def parse_native_url(url):
"""
Support https://api.ciscospark.com/v1/webhooks/incoming/WEBHOOK_TOKEN
"""
result = re.match(
r'^https?://api\.ciscospark\.com/v[1-9][0-9]*/webhooks/incoming/'
r'(?P<webhook_token>[A-Z0-9_-]+)/?'
r'(?P<args>\?[.+])?$', url, re.I)
if result:
return NotifyWebexTeams.parse_url(
'{schema}://{webhook_token}/{args}'.format(
schema=NotifyWebexTeams.secure_protocol,
webhook_token=result.group('webhook_token'),
args='' if not result.group('args')
else result.group('args')))
return None

10
apprise/plugins/__init__.py

@ -73,7 +73,7 @@ __all__ = [
# we mirror our base purely for the ability to reset everything; this
# is generally only used in testing and should not be used by developers
__MODULE_MAP = {}
MODULE_MAP = {}
# Load our Lookup Matrix
@ -117,12 +117,12 @@ def __load_matrix(path=abspath(dirname(__file__)), name='apprise.plugins'):
# Filter out non-notification modules
continue
elif plugin_name in __MODULE_MAP:
elif plugin_name in MODULE_MAP:
# we're already handling this object
continue
# Add our plugin name to our module map
__MODULE_MAP[plugin_name] = {
MODULE_MAP[plugin_name] = {
'plugin': plugin,
'module': module,
}
@ -171,7 +171,7 @@ def __reset_matrix():
SCHEMA_MAP.clear()
# Iterate over our module map so we can clear out our __all__ and globals
for plugin_name in __MODULE_MAP.keys():
for plugin_name in MODULE_MAP.keys():
# Clear out globals
del globals()[plugin_name]
@ -179,7 +179,7 @@ def __reset_matrix():
__all__.remove(plugin_name)
# Clear out our module map
__MODULE_MAP.clear()
MODULE_MAP.clear()
# Dynamically build our schema base

39
test/test_rest_plugins.py

@ -177,7 +177,12 @@ TEST_URLS = (
# don't include an image by default
'include_image': True,
}),
('https://discordapp.com/api/webhooks/{}/{}'.format(
'0' * 10, 'B' * 40), {
# Native URL Support, take the discord URL and still build from it
'instance': plugins.NotifyDiscord,
'requests_response_code': requests.codes.no_content,
}),
('discord://%s/%s?format=markdown&avatar=No&footer=No' % (
'i' * 24, 't' * 64), {
'instance': plugins.NotifyDiscord,
@ -331,6 +336,10 @@ TEST_URLS = (
('flock://%s?format=text' % ('i' * 24), {
'instance': plugins.NotifyFlock,
}),
# Native URL Support, take the slack URL and still build from it
('https://api.flock.com/hooks/sendMessage/{}/'.format('i' * 24), {
'instance': plugins.NotifyFlock,
}),
# Bot API presumed if one or more targets are specified
# Provide markdown format
('flock://%s/u:%s?format=markdown' % ('i' * 24, 'u' * 12), {
@ -534,6 +543,14 @@ TEST_URLS = (
('ifttt://WebHookID@EventID/EventID2/', {
'instance': plugins.NotifyIFTTT,
}),
# Support native URL references
('https://maker.ifttt.com/use/WebHookID/', {
# No EventID specified
'instance': TypeError,
}),
('https://maker.ifttt.com/use/WebHookID/EventID/', {
'instance': plugins.NotifyIFTTT,
}),
# Test website connection failures
('ifttt://WebHookID@EventID', {
'instance': plugins.NotifyIFTTT,
@ -1052,6 +1069,12 @@ TEST_URLS = (
# All tokens provided - we're good
'instance': plugins.NotifyMSTeams,
}),
# Support native URLs
('https://outlook.office.com/webhook/{}@{}/IncomingWebhook/{}/{}'
.format(UUID4, UUID4, 'a' * 32, UUID4), {
# All tokens provided - we're good
'instance': plugins.NotifyMSTeams}),
('msteams://{}@{}/{}/{}?t2'.format(UUID4, UUID4, 'a' * 32, UUID4), {
# All tokens provided - we're good
'instance': plugins.NotifyMSTeams,
@ -1647,6 +1670,10 @@ TEST_URLS = (
# the user told the webhook to use; set our ryver mode
'instance': plugins.NotifyRyver,
}),
# Support Native URLs
('https://apprise.ryver.com/application/webhook/ckhrjW8w672m6HG', {
'instance': plugins.NotifyRyver,
}),
('ryver://caronc@apprise/ckhrjW8w672m6HG', {
'instance': plugins.NotifyRyver,
# don't include an image by default
@ -1719,6 +1746,11 @@ TEST_URLS = (
# Missing a channel, falls back to webhook channel bindings
'instance': plugins.NotifySlack,
}),
# Native URL Support, take the slack URL and still build from it
('https://hooks.slack.com/services/{}/{}/{}'.format(
'A' * 9, 'B' * 9, 'c' * 24), {
'instance': plugins.NotifySlack,
}),
('slack://username@INVALID/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/#cool', {
# invalid 1st Token
'instance': TypeError,
@ -2041,6 +2073,11 @@ TEST_URLS = (
# token provided - we're good
'instance': plugins.NotifyWebexTeams,
}),
# Support Native URLs
('https://api.ciscospark.com/v1/webhooks/incoming/{}'.format('a' * 80), {
# token provided - we're good
'instance': plugins.NotifyWebexTeams,
}),
('wxteams://{}'.format('a' * 80), {
'instance': plugins.NotifyWebexTeams,
# force a failure

Loading…
Cancel
Save