mirror of
https://github.com/caronc/apprise.git
synced 2025-01-03 20:49:19 +01:00
Notification Service Native URL Support; refs #109
This commit is contained in:
parent
d5dfbf74fa
commit
6d9069e106
@ -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
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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…
Reference in New Issue
Block a user