Microsoft Teams Webhook to include team name in URL (#361)

pull/240/head
Chris Caron 2021-02-22 14:35:28 -05:00 committed by GitHub
parent 221e304a73
commit 9e9b140642
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 95 additions and 36 deletions

View File

@ -43,22 +43,35 @@
#
# When you've completed this, it will generate you a (webhook) URL that
# looks like:
# https://outlook.office.com/webhook/ \
# https://team-name.office.com/webhook/ \
# abcdefgf8-2f4b-4eca-8f61-225c83db1967@abcdefg2-5a99-4849-8efc-\
# c9e78d28e57d/IncomingWebhook/291289f63a8abd3593e834af4d79f9fe/\
# a2329f43-0ffb-46ab-948b-c9abdad9d643
#
# 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
# ^ ^ ^
# ^ ^ ^ ^
# | | | |
# legacy team reference: 'outlook' | | |
# | | |
# These are important <--------------^--------------------^----^
#
# 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.
#
# These 3 tokens is what you'll need to build your URL with:
# msteams://ABCD/DEFG/HIJK
# These 3 tokens need to be placed in the URL after the Team
# msteams://TEAM/ABCD/DEFG/HIJK
#
import re
import requests
@ -101,7 +114,8 @@ class NotifyMSTeams(NotifyBase):
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_msteams'
# 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
image_size = NotifyImageSize.XY_72
@ -118,11 +132,22 @@ class NotifyMSTeams(NotifyBase):
# Define object 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
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
# /AAAAAAAAA@AAAAAAAAA/........./.........
'token_a': {
@ -175,8 +200,8 @@ class NotifyMSTeams(NotifyBase):
},
}
def __init__(self, token_a, token_b, token_c, include_image=True,
template=None, tokens=None, **kwargs):
def __init__(self, token_a, token_b, token_c, team=None,
include_image=True, template=None, tokens=None, **kwargs):
"""
Initialize Microsoft Teams Object
@ -187,6 +212,16 @@ class NotifyMSTeams(NotifyBase):
"""
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(
token_a, *self.template_tokens['token_a']['regex'])
if not self.token_a:
@ -338,11 +373,11 @@ class NotifyMSTeams(NotifyBase):
'Content-Type': 'application/json',
}
url = '%s/%s/IncomingWebhook/%s/%s' % (
self.notify_url,
self.token_a,
self.token_b,
self.token_c,
notify_url = self.notify_url.format(
team=self.team,
token_a=self.token_a,
token_b=self.token_b,
token_c=self.token_c,
)
# Generate our payload if it's possible
@ -354,7 +389,7 @@ class NotifyMSTeams(NotifyBase):
return False
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))
@ -362,7 +397,7 @@ class NotifyMSTeams(NotifyBase):
self.throttle()
try:
r = requests.post(
url,
notify_url,
data=json.dumps(payload),
headers=headers,
verify=self.verify_certificate,
@ -418,9 +453,10 @@ class NotifyMSTeams(NotifyBase):
# Store any template entries if specified
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(
schema=self.secure_protocol,
team=NotifyMSTeams.quote(self.team, safe=''),
token_a=self.pprint(self.token_a, privacy, safe=''),
token_b=self.pprint(self.token_b, privacy, safe=''),
token_c=self.pprint(self.token_c, privacy, safe=''),
@ -442,6 +478,7 @@ class NotifyMSTeams(NotifyBase):
# Get unquoted entries
entries = NotifyMSTeams.split_path(results['fullpath'])
# Deprecated mode (backwards compatibility)
if results.get('user'):
# If a user was found, it's because it's still part of the first
# token, so we concatinate them
@ -451,28 +488,25 @@ class NotifyMSTeams(NotifyBase):
)
else:
# The first token is stored in the hostname
results['token_a'] = NotifyMSTeams.unquote(results['host'])
# Get the Team from the hostname
results['team'] = NotifyMSTeams.unquote(results['host'])
# Now fetch the remaining tokens
try:
results['token_b'] = entries.pop(0)
# Get the token from the path
results['token_a'] = None if not entries else entries.pop(0)
except IndexError:
# We're done
results['token_b'] = None
try:
results['token_c'] = entries.pop(0)
except IndexError:
# We're done
results['token_c'] = None
results['token_b'] = None if not entries else entries.pop(0)
results['token_c'] = None if not entries else entries.pop(0)
# Get Image
results['include_image'] = \
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']:
results['template'] = \
NotifyMSTeams.unquote(results['qsd']['template'])
@ -485,15 +519,18 @@ class NotifyMSTeams(NotifyBase):
@staticmethod
def parse_native_url(url):
"""
Support:
Legacy Support:
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
# 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'^https?://(?P<team>[^.]+)\.office\.com/webhook/'
r'(?P<token_a>[A-Z0-9-]+@[A-Z0-9-]+)/'
r'IncomingWebhook/'
r'(?P<token_b>[A-Z0-9]+)/'
@ -502,8 +539,10 @@ class NotifyMSTeams(NotifyBase):
if result:
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,
team=result.group('team'),
token_a=result.group('token_a'),
token_b=result.group('token_b'),
token_c=result.group('token_c'),

View File

@ -1339,7 +1339,7 @@ def test_apprise_details_plugin_verification():
if six.PY2:
# inspect our object
# getargspec() is depricated in Python v3
# getargspec() is deprecated in Python v3
spec = inspect.getargspec(SCHEMA_MAP[protocols[0]].__init__)
function_args = \

View File

@ -2025,18 +2025,38 @@ TEST_URLS = (
# All tokens provided - we're good
'instance': plugins.NotifyMSTeams}),
# Legacy URL Formatting
('msteams://{}@{}/{}/{}?t2'.format(UUID4, UUID4, 'a' * 32, UUID4), {
# All tokens provided - we're good
'instance': plugins.NotifyMSTeams,
# don't include an image by default
'include_image': False,
}),
# Legacy URL Formatting
('msteams://{}@{}/{}/{}?image=No'.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://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), {
'instance': plugins.NotifyMSTeams,