mirror of https://github.com/caronc/apprise
Microsoft Teams Webhook to include team name in URL (#361)
parent
221e304a73
commit
9e9b140642
|
@ -43,22 +43,35 @@
|
||||||
#
|
#
|
||||||
# When you've completed this, it will generate you a (webhook) URL that
|
# When you've completed this, it will generate you a (webhook) URL that
|
||||||
# looks like:
|
# looks like:
|
||||||
# https://outlook.office.com/webhook/ \
|
# https://team-name.office.com/webhook/ \
|
||||||
# abcdefgf8-2f4b-4eca-8f61-225c83db1967@abcdefg2-5a99-4849-8efc-\
|
# abcdefgf8-2f4b-4eca-8f61-225c83db1967@abcdefg2-5a99-4849-8efc-\
|
||||||
# c9e78d28e57d/IncomingWebhook/291289f63a8abd3593e834af4d79f9fe/\
|
# c9e78d28e57d/IncomingWebhook/291289f63a8abd3593e834af4d79f9fe/\
|
||||||
# a2329f43-0ffb-46ab-948b-c9abdad9d643
|
# a2329f43-0ffb-46ab-948b-c9abdad9d643
|
||||||
#
|
#
|
||||||
# Yes... The URL is that big... But it looks like this (greatly simplified):
|
# Yes... The URL is that big... But it looks like this (greatly simplified):
|
||||||
|
# https://TEAM-NAME.office.com/webhook/ABCD/IncomingWebhook/DEFG/HIJK
|
||||||
|
# ^ ^ ^ ^
|
||||||
|
# | | | |
|
||||||
|
# These are important <----------------^--------------------^----^
|
||||||
|
#
|
||||||
|
|
||||||
|
# The Legacy format didn't have the team name identified and reads 'outlook'
|
||||||
|
# While this still works, consider that Microsoft will be dropping support
|
||||||
|
# for this soon, so you may need to update your IncomingWebhook. Here is
|
||||||
|
# what a legacy URL looked like:
|
||||||
# https://outlook.office.com/webhook/ABCD/IncomingWebhook/DEFG/HIJK
|
# https://outlook.office.com/webhook/ABCD/IncomingWebhook/DEFG/HIJK
|
||||||
# ^ ^ ^
|
# ^ ^ ^ ^
|
||||||
|
# | | | |
|
||||||
|
# legacy team reference: 'outlook' | | |
|
||||||
# | | |
|
# | | |
|
||||||
# These are important <--------------^--------------------^----^
|
# These are important <--------------^--------------------^----^
|
||||||
#
|
#
|
||||||
|
|
||||||
# You'll notice that the first token is actually 2 separated by an @ symbol
|
# You'll notice that the first token is actually 2 separated by an @ symbol
|
||||||
# But lets just ignore that and assume it's one great big token instead.
|
# But lets just ignore that and assume it's one great big token instead.
|
||||||
#
|
#
|
||||||
# These 3 tokens is what you'll need to build your URL with:
|
# These 3 tokens need to be placed in the URL after the Team
|
||||||
# msteams://ABCD/DEFG/HIJK
|
# msteams://TEAM/ABCD/DEFG/HIJK
|
||||||
#
|
#
|
||||||
import re
|
import re
|
||||||
import requests
|
import requests
|
||||||
|
@ -101,7 +114,8 @@ class NotifyMSTeams(NotifyBase):
|
||||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_msteams'
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_msteams'
|
||||||
|
|
||||||
# MSTeams uses the http protocol with JSON requests
|
# MSTeams uses the http protocol with JSON requests
|
||||||
notify_url = 'https://outlook.office.com/webhook'
|
notify_url = 'https://{team}.office.com/webhook/' \
|
||||||
|
'{token_a}/IncomingWebhook/{token_b}/{token_c}'
|
||||||
|
|
||||||
# Allows the user to specify the NotifyImageSize object
|
# Allows the user to specify the NotifyImageSize object
|
||||||
image_size = NotifyImageSize.XY_72
|
image_size = NotifyImageSize.XY_72
|
||||||
|
@ -118,11 +132,22 @@ class NotifyMSTeams(NotifyBase):
|
||||||
|
|
||||||
# Define object templates
|
# Define object templates
|
||||||
templates = (
|
templates = (
|
||||||
'{schema}://{token_a}/{token_b}{token_c}',
|
# New required format
|
||||||
|
'{schema}://{team}/{token_a}/{token_b}/{token_c}',
|
||||||
|
|
||||||
|
# Deprecated
|
||||||
|
'{schema}://{token_a}/{token_b}/{token_c}',
|
||||||
)
|
)
|
||||||
|
|
||||||
# Define our template tokens
|
# Define our template tokens
|
||||||
template_tokens = dict(NotifyBase.template_tokens, **{
|
template_tokens = dict(NotifyBase.template_tokens, **{
|
||||||
|
# The Microsoft Team Name
|
||||||
|
'team': {
|
||||||
|
'name': _('Team Name'),
|
||||||
|
'type': 'string',
|
||||||
|
'required': True,
|
||||||
|
'regex': (r'^[A-Z0-9_-]+$', 'i'),
|
||||||
|
},
|
||||||
# Token required as part of the API request
|
# Token required as part of the API request
|
||||||
# /AAAAAAAAA@AAAAAAAAA/........./.........
|
# /AAAAAAAAA@AAAAAAAAA/........./.........
|
||||||
'token_a': {
|
'token_a': {
|
||||||
|
@ -175,8 +200,8 @@ class NotifyMSTeams(NotifyBase):
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, token_a, token_b, token_c, include_image=True,
|
def __init__(self, token_a, token_b, token_c, team=None,
|
||||||
template=None, tokens=None, **kwargs):
|
include_image=True, template=None, tokens=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Microsoft Teams Object
|
Initialize Microsoft Teams Object
|
||||||
|
|
||||||
|
@ -187,6 +212,16 @@ class NotifyMSTeams(NotifyBase):
|
||||||
"""
|
"""
|
||||||
super(NotifyMSTeams, self).__init__(**kwargs)
|
super(NotifyMSTeams, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
self.team = validate_regex(team)
|
||||||
|
if not self.team:
|
||||||
|
NotifyBase.logger.deprecate(
|
||||||
|
"Apprise requires you to identify your Microsoft Team name as "
|
||||||
|
"part of the URL. e.g.: "
|
||||||
|
"msteams://TEAM-NAME/{token_a}/{token_b}/{token_c}")
|
||||||
|
|
||||||
|
# Fallback
|
||||||
|
self.team = 'outlook'
|
||||||
|
|
||||||
self.token_a = validate_regex(
|
self.token_a = validate_regex(
|
||||||
token_a, *self.template_tokens['token_a']['regex'])
|
token_a, *self.template_tokens['token_a']['regex'])
|
||||||
if not self.token_a:
|
if not self.token_a:
|
||||||
|
@ -338,11 +373,11 @@ class NotifyMSTeams(NotifyBase):
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
}
|
}
|
||||||
|
|
||||||
url = '%s/%s/IncomingWebhook/%s/%s' % (
|
notify_url = self.notify_url.format(
|
||||||
self.notify_url,
|
team=self.team,
|
||||||
self.token_a,
|
token_a=self.token_a,
|
||||||
self.token_b,
|
token_b=self.token_b,
|
||||||
self.token_c,
|
token_c=self.token_c,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Generate our payload if it's possible
|
# Generate our payload if it's possible
|
||||||
|
@ -354,7 +389,7 @@ class NotifyMSTeams(NotifyBase):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.logger.debug('MSTeams POST URL: %s (cert_verify=%r)' % (
|
self.logger.debug('MSTeams POST URL: %s (cert_verify=%r)' % (
|
||||||
url, self.verify_certificate,
|
notify_url, self.verify_certificate,
|
||||||
))
|
))
|
||||||
self.logger.debug('MSTeams Payload: %s' % str(payload))
|
self.logger.debug('MSTeams Payload: %s' % str(payload))
|
||||||
|
|
||||||
|
@ -362,7 +397,7 @@ class NotifyMSTeams(NotifyBase):
|
||||||
self.throttle()
|
self.throttle()
|
||||||
try:
|
try:
|
||||||
r = requests.post(
|
r = requests.post(
|
||||||
url,
|
notify_url,
|
||||||
data=json.dumps(payload),
|
data=json.dumps(payload),
|
||||||
headers=headers,
|
headers=headers,
|
||||||
verify=self.verify_certificate,
|
verify=self.verify_certificate,
|
||||||
|
@ -418,9 +453,10 @@ class NotifyMSTeams(NotifyBase):
|
||||||
# Store any template entries if specified
|
# Store any template entries if specified
|
||||||
params.update({':{}'.format(k): v for k, v in self.tokens.items()})
|
params.update({':{}'.format(k): v for k, v in self.tokens.items()})
|
||||||
|
|
||||||
return '{schema}://{token_a}/{token_b}/{token_c}/'\
|
return '{schema}://{team}/{token_a}/{token_b}/{token_c}/'\
|
||||||
'?{params}'.format(
|
'?{params}'.format(
|
||||||
schema=self.secure_protocol,
|
schema=self.secure_protocol,
|
||||||
|
team=NotifyMSTeams.quote(self.team, safe=''),
|
||||||
token_a=self.pprint(self.token_a, privacy, safe=''),
|
token_a=self.pprint(self.token_a, privacy, safe=''),
|
||||||
token_b=self.pprint(self.token_b, privacy, safe=''),
|
token_b=self.pprint(self.token_b, privacy, safe=''),
|
||||||
token_c=self.pprint(self.token_c, privacy, safe=''),
|
token_c=self.pprint(self.token_c, privacy, safe=''),
|
||||||
|
@ -442,6 +478,7 @@ class NotifyMSTeams(NotifyBase):
|
||||||
# Get unquoted entries
|
# Get unquoted entries
|
||||||
entries = NotifyMSTeams.split_path(results['fullpath'])
|
entries = NotifyMSTeams.split_path(results['fullpath'])
|
||||||
|
|
||||||
|
# Deprecated mode (backwards compatibility)
|
||||||
if results.get('user'):
|
if results.get('user'):
|
||||||
# If a user was found, it's because it's still part of the first
|
# If a user was found, it's because it's still part of the first
|
||||||
# token, so we concatinate them
|
# token, so we concatinate them
|
||||||
|
@ -451,28 +488,25 @@ class NotifyMSTeams(NotifyBase):
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# The first token is stored in the hostname
|
# Get the Team from the hostname
|
||||||
results['token_a'] = NotifyMSTeams.unquote(results['host'])
|
results['team'] = NotifyMSTeams.unquote(results['host'])
|
||||||
|
|
||||||
# Now fetch the remaining tokens
|
# Get the token from the path
|
||||||
try:
|
results['token_a'] = None if not entries else entries.pop(0)
|
||||||
results['token_b'] = entries.pop(0)
|
|
||||||
|
|
||||||
except IndexError:
|
results['token_b'] = None if not entries else entries.pop(0)
|
||||||
# We're done
|
results['token_c'] = None if not entries else entries.pop(0)
|
||||||
results['token_b'] = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
results['token_c'] = entries.pop(0)
|
|
||||||
|
|
||||||
except IndexError:
|
|
||||||
# We're done
|
|
||||||
results['token_c'] = None
|
|
||||||
|
|
||||||
# Get Image
|
# Get Image
|
||||||
results['include_image'] = \
|
results['include_image'] = \
|
||||||
parse_bool(results['qsd'].get('image', True))
|
parse_bool(results['qsd'].get('image', True))
|
||||||
|
|
||||||
|
# Get Team name if defined
|
||||||
|
if 'team' in results['qsd'] and results['qsd']['team']:
|
||||||
|
results['team'] = \
|
||||||
|
NotifyMSTeams.unquote(results['qsd']['team'])
|
||||||
|
|
||||||
|
# Template Handling
|
||||||
if 'template' in results['qsd'] and results['qsd']['template']:
|
if 'template' in results['qsd'] and results['qsd']['template']:
|
||||||
results['template'] = \
|
results['template'] = \
|
||||||
NotifyMSTeams.unquote(results['qsd']['template'])
|
NotifyMSTeams.unquote(results['qsd']['template'])
|
||||||
|
@ -485,15 +519,18 @@ class NotifyMSTeams(NotifyBase):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_native_url(url):
|
def parse_native_url(url):
|
||||||
"""
|
"""
|
||||||
Support:
|
Legacy Support:
|
||||||
https://outlook.office.com/webhook/ABCD/IncomingWebhook/DEFG/HIJK
|
https://outlook.office.com/webhook/ABCD/IncomingWebhook/DEFG/HIJK
|
||||||
|
|
||||||
|
New Hook Support:
|
||||||
|
https://team-name.office.com/webhook/ABCD/IncomingWebhook/DEFG/HIJK
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# We don't need to do incredibly details token matching as the purpose
|
# 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
|
# of this is just to detect that were dealing with an msteams url
|
||||||
# token parsing will occur once we initialize the function
|
# token parsing will occur once we initialize the function
|
||||||
result = re.match(
|
result = re.match(
|
||||||
r'^https?://outlook\.office\.com/webhook/'
|
r'^https?://(?P<team>[^.]+)\.office\.com/webhook/'
|
||||||
r'(?P<token_a>[A-Z0-9-]+@[A-Z0-9-]+)/'
|
r'(?P<token_a>[A-Z0-9-]+@[A-Z0-9-]+)/'
|
||||||
r'IncomingWebhook/'
|
r'IncomingWebhook/'
|
||||||
r'(?P<token_b>[A-Z0-9]+)/'
|
r'(?P<token_b>[A-Z0-9]+)/'
|
||||||
|
@ -502,8 +539,10 @@ class NotifyMSTeams(NotifyBase):
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
return NotifyMSTeams.parse_url(
|
return NotifyMSTeams.parse_url(
|
||||||
'{schema}://{token_a}/{token_b}/{token_c}/{params}'.format(
|
'{schema}://{team}/{token_a}/{token_b}/{token_c}'
|
||||||
|
'/{params}'.format(
|
||||||
schema=NotifyMSTeams.secure_protocol,
|
schema=NotifyMSTeams.secure_protocol,
|
||||||
|
team=result.group('team'),
|
||||||
token_a=result.group('token_a'),
|
token_a=result.group('token_a'),
|
||||||
token_b=result.group('token_b'),
|
token_b=result.group('token_b'),
|
||||||
token_c=result.group('token_c'),
|
token_c=result.group('token_c'),
|
||||||
|
|
|
@ -1339,7 +1339,7 @@ def test_apprise_details_plugin_verification():
|
||||||
|
|
||||||
if six.PY2:
|
if six.PY2:
|
||||||
# inspect our object
|
# inspect our object
|
||||||
# getargspec() is depricated in Python v3
|
# getargspec() is deprecated in Python v3
|
||||||
spec = inspect.getargspec(SCHEMA_MAP[protocols[0]].__init__)
|
spec = inspect.getargspec(SCHEMA_MAP[protocols[0]].__init__)
|
||||||
|
|
||||||
function_args = \
|
function_args = \
|
||||||
|
|
|
@ -2025,18 +2025,38 @@ TEST_URLS = (
|
||||||
# All tokens provided - we're good
|
# All tokens provided - we're good
|
||||||
'instance': plugins.NotifyMSTeams}),
|
'instance': plugins.NotifyMSTeams}),
|
||||||
|
|
||||||
|
# Legacy URL Formatting
|
||||||
('msteams://{}@{}/{}/{}?t2'.format(UUID4, UUID4, 'a' * 32, UUID4), {
|
('msteams://{}@{}/{}/{}?t2'.format(UUID4, UUID4, 'a' * 32, UUID4), {
|
||||||
# All tokens provided - we're good
|
# All tokens provided - we're good
|
||||||
'instance': plugins.NotifyMSTeams,
|
'instance': plugins.NotifyMSTeams,
|
||||||
# don't include an image by default
|
# don't include an image by default
|
||||||
'include_image': False,
|
'include_image': False,
|
||||||
}),
|
}),
|
||||||
|
# Legacy URL Formatting
|
||||||
('msteams://{}@{}/{}/{}?image=No'.format(UUID4, UUID4, 'a' * 32, UUID4), {
|
('msteams://{}@{}/{}/{}?image=No'.format(UUID4, UUID4, 'a' * 32, UUID4), {
|
||||||
# All tokens provided - we're good no image
|
# All tokens provided - we're good no image
|
||||||
'instance': plugins.NotifyMSTeams,
|
'instance': plugins.NotifyMSTeams,
|
||||||
|
|
||||||
# Our expected url(privacy=True) startswith() response:
|
# Our expected url(privacy=True) startswith() response:
|
||||||
'privacy_url': 'msteams://8...2/a...a/8...2/',
|
'privacy_url': 'msteams://outlook/8...2/a...a/8...2/',
|
||||||
|
}),
|
||||||
|
# New 2021 URL formatting
|
||||||
|
('msteams://apprise/{}@{}/{}/{}'.format(
|
||||||
|
UUID4, UUID4, 'a' * 32, UUID4), {
|
||||||
|
# All tokens provided - we're good no image
|
||||||
|
'instance': plugins.NotifyMSTeams,
|
||||||
|
|
||||||
|
# Our expected url(privacy=True) startswith() response:
|
||||||
|
'privacy_url': 'msteams://apprise/8...2/a...a/8...2/',
|
||||||
|
}),
|
||||||
|
# New 2021 URL formatting; support team= argument
|
||||||
|
('msteams://{}@{}/{}/{}?team=teamname'.format(
|
||||||
|
UUID4, UUID4, 'a' * 32, UUID4), {
|
||||||
|
# All tokens provided - we're good no image
|
||||||
|
'instance': plugins.NotifyMSTeams,
|
||||||
|
|
||||||
|
# Our expected url(privacy=True) startswith() response:
|
||||||
|
'privacy_url': 'msteams://teamname/8...2/a...a/8...2/',
|
||||||
}),
|
}),
|
||||||
('msteams://{}@{}/{}/{}?tx'.format(UUID4, UUID4, 'a' * 32, UUID4), {
|
('msteams://{}@{}/{}/{}?tx'.format(UUID4, UUID4, 'a' * 32, UUID4), {
|
||||||
'instance': plugins.NotifyMSTeams,
|
'instance': plugins.NotifyMSTeams,
|
||||||
|
|
Loading…
Reference in New Issue