aboutsummaryrefslogtreecommitdiffhomepage
path: root/libs/apprise/plugins/NotifySlack.py
diff options
context:
space:
mode:
Diffstat (limited to 'libs/apprise/plugins/NotifySlack.py')
-rw-r--r--libs/apprise/plugins/NotifySlack.py615
1 files changed, 440 insertions, 175 deletions
diff --git a/libs/apprise/plugins/NotifySlack.py b/libs/apprise/plugins/NotifySlack.py
index 17a7ebbc6..e16885e60 100644
--- a/libs/apprise/plugins/NotifySlack.py
+++ b/libs/apprise/plugins/NotifySlack.py
@@ -23,21 +23,41 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
-# To use this plugin, you need to first access https://api.slack.com
-# Specifically https://my.slack.com/services/new/incoming-webhook/
-# to create a new incoming webhook for your account. You'll need to
-# follow the wizard to pre-determine the channel(s) you want your
-# message to broadcast to, and when you're complete, you will
-# recieve a URL that looks something like this:
-# https://hooks.slack.com/services/T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ
-# ^ ^ ^
-# | | |
-# These are important <--------------^---------^---------------^
+# There are 2 ways to use this plugin...
+# Method 1: Via Webhook:
+# Visit https://my.slack.com/services/new/incoming-webhook/
+# to create a new incoming webhook for your account. You'll need to
+# follow the wizard to pre-determine the channel(s) you want your
+# message to broadcast to, and when you're complete, you will
+# recieve a URL that looks something like this:
+# https://hooks.slack.com/services/T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7
+# ^ ^ ^
+# | | |
+# These are important <--------------^---------^---------------^
#
+# Method 2: Via a Bot:
+# 1. visit: https://api.slack.com/apps?new_app=1
+# 2. Pick an App Name (such as Apprise) and select your workspace. Then
+# press 'Create App'
+# 3. You'll be able to click on 'Bots' from here where you can then choose
+# to add a 'Bot User'. Give it a name and choose 'Add Bot User'.
+# 4. Now you can choose 'Install App' to which you can choose 'Install App
+# to Workspace'.
+# 5. You will need to authorize the app which you get promopted to do.
+# 6. Finally you'll get some important information providing you your
+# 'OAuth Access Token' and 'Bot User OAuth Access Token' such as:
+# slack://{Oauth Access Token}
#
+# ... which might look something like:
+# slack://xoxp-1234-1234-1234-4ddbc191d40ee098cbaae6f3523ada2d
+# ... or:
+# slack://xoxb-1234-1234-4ddbc191d40ee098cbaae6f3523ada2d
+#
+
import re
import requests
from json import dumps
+from json import loads
from time import time
from .NotifyBase import NotifyBase
@@ -46,20 +66,9 @@ from ..common import NotifyType
from ..common import NotifyFormat
from ..utils import parse_bool
from ..utils import parse_list
+from ..utils import validate_regex
from ..AppriseLocale import gettext_lazy as _
-# Token required as part of the API request
-# /AAAAAAAAA/........./........................
-VALIDATE_TOKEN_A = re.compile(r'[A-Z0-9]{9}')
-
-# Token required as part of the API request
-# /........./BBBBBBBBB/........................
-VALIDATE_TOKEN_B = re.compile(r'[A-Z0-9]{9}')
-
-# Token required as part of the API request
-# /........./........./CCCCCCCCCCCCCCCCCCCCCCCC
-VALIDATE_TOKEN_C = re.compile(r'[A-Za-z0-9]{24}')
-
# Extend HTTP Error Messages
SLACK_HTTP_ERROR_MAP = {
401: 'Unauthorized - Invalid Token.',
@@ -68,8 +77,26 @@ SLACK_HTTP_ERROR_MAP = {
# Used to break path apart into list of channels
CHANNEL_LIST_DELIM = re.compile(r'[ \t\r\n,#\\/]+')
-# Used to detect a channel
-IS_VALID_TARGET_RE = re.compile(r'[+#@]?([A-Z0-9_]{1,32})', re.I)
+
+class SlackMode(object):
+ """
+ Tracks the mode of which we're using Slack
+ """
+ # We're dealing with a webhook
+ # Our token looks like: T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7
+ WEBHOOK = 'webhook'
+
+ # We're dealing with a bot (using the OAuth Access Token)
+ # Our token looks like: xoxp-1234-1234-1234-abc124 or
+ # Our token looks like: xoxb-1234-1234-abc124 or
+ BOT = 'bot'
+
+
+# Define our Slack Modes
+SLACK_MODES = (
+ SlackMode.WEBHOOK,
+ SlackMode.BOT,
+)
class NotifySlack(NotifyBase):
@@ -86,27 +113,43 @@ class NotifySlack(NotifyBase):
# The default secure protocol
secure_protocol = 'slack'
+ # Allow 50 requests per minute (Tier 2).
+ # 60/50 = 0.2
+ request_rate_per_sec = 1.2
+
# A URL that takes you to the setup/help of the specific protocol
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_slack'
- # Slack uses the http protocol with JSON requests
- notify_url = 'https://hooks.slack.com/services'
+ # Slack Webhook URL
+ webhook_url = 'https://hooks.slack.com/services'
+
+ # Slack API URL (used with Bots)
+ api_url = 'https://slack.com/api/{}'
# Allows the user to specify the NotifyImageSize object
image_size = NotifyImageSize.XY_72
# The maximum allowable characters allowed in the body per message
- body_maxlen = 1000
+ body_maxlen = 35000
# Default Notification Format
notify_format = NotifyFormat.MARKDOWN
+ # Bot's do not have default channels to notify; so #general
+ # becomes the default channel in BOT mode
+ default_notification_channel = '#general'
+
# Define object templates
templates = (
+ # Webhook
'{schema}://{token_a}/{token_b}{token_c}',
'{schema}://{botname}@{token_a}/{token_b}{token_c}',
'{schema}://{token_a}/{token_b}{token_c}/{targets}',
'{schema}://{botname}@{token_a}/{token_b}{token_c}/{targets}',
+
+ # Bot
+ '{schema}://{access_token}/',
+ '{schema}://{access_token}/{targets}',
)
# Define our template tokens
@@ -116,26 +159,42 @@ class NotifySlack(NotifyBase):
'type': 'string',
'map_to': 'user',
},
+ # Bot User OAuth Access Token
+ # which always starts with xoxp- e.g.:
+ # xoxb-1234-1234-4ddbc191d40ee098cbaae6f3523ada2d
+ 'access_token': {
+ 'name': _('OAuth Access Token'),
+ 'type': 'string',
+ 'private': True,
+ 'required': True,
+ 'regex': (r'^xox[abp]-[A-Z0-9-]+$', 'i'),
+ },
+ # Token required as part of the Webhook request
+ # /AAAAAAAAA/........./........................
'token_a': {
'name': _('Token A'),
'type': 'string',
'private': True,
'required': True,
- 'regex': (r'[A-Z0-9]{9}', 'i'),
+ 'regex': (r'^[A-Z0-9]{9}$', 'i'),
},
+ # Token required as part of the Webhook request
+ # /........./BBBBBBBBB/........................
'token_b': {
'name': _('Token B'),
'type': 'string',
'private': True,
'required': True,
- 'regex': (r'[A-Z0-9]{9}', 'i'),
+ 'regex': (r'^[A-Z0-9]{9}$', 'i'),
},
+ # Token required as part of the Webhook request
+ # /........./........./CCCCCCCCCCCCCCCCCCCCCCCC
'token_c': {
'name': _('Token C'),
'type': 'string',
'private': True,
'required': True,
- 'regex': (r'[A-Za-z0-9]{24}', 'i'),
+ 'regex': (r'^[A-Za-z0-9]{24}$', 'i'),
},
'target_encoded_id': {
'name': _('Target Encoded ID'),
@@ -169,59 +228,60 @@ class NotifySlack(NotifyBase):
'default': True,
'map_to': 'include_image',
},
+ 'footer': {
+ 'name': _('Include Footer'),
+ 'type': 'bool',
+ 'default': True,
+ 'map_to': 'include_footer',
+ },
'to': {
'alias_of': 'targets',
},
})
- def __init__(self, token_a, token_b, token_c, targets,
- include_image=True, **kwargs):
+ def __init__(self, access_token=None, token_a=None, token_b=None,
+ token_c=None, targets=None, include_image=True,
+ include_footer=True, **kwargs):
"""
Initialize Slack Object
"""
super(NotifySlack, self).__init__(**kwargs)
- if not token_a:
- msg = 'The first API token is not specified.'
- self.logger.warning(msg)
- raise TypeError(msg)
-
- if not token_b:
- msg = 'The second API token is not specified.'
- self.logger.warning(msg)
- raise TypeError(msg)
-
- if not token_c:
- msg = 'The third API token is not specified.'
- self.logger.warning(msg)
- raise TypeError(msg)
-
- if not VALIDATE_TOKEN_A.match(token_a.strip()):
- msg = 'The first API token specified ({}) is invalid.'\
- .format(token_a)
- self.logger.warning(msg)
- raise TypeError(msg)
-
- # The token associated with the account
- self.token_a = token_a.strip()
-
- if not VALIDATE_TOKEN_B.match(token_b.strip()):
- msg = 'The second API token specified ({}) is invalid.'\
- .format(token_b)
- self.logger.warning(msg)
- raise TypeError(msg)
-
- # The token associated with the account
- self.token_b = token_b.strip()
-
- if not VALIDATE_TOKEN_C.match(token_c.strip()):
- msg = 'The third API token specified ({}) is invalid.'\
- .format(token_c)
- self.logger.warning(msg)
- raise TypeError(msg)
-
- # The token associated with the account
- self.token_c = token_c.strip()
+ # Setup our mode
+ self.mode = SlackMode.BOT if access_token else SlackMode.WEBHOOK
+
+ if self.mode is SlackMode.WEBHOOK:
+ self.token_a = validate_regex(
+ token_a, *self.template_tokens['token_a']['regex'])
+ if not self.token_a:
+ msg = 'An invalid Slack (first) Token ' \
+ '({}) was specified.'.format(token_a)
+ self.logger.warning(msg)
+ raise TypeError(msg)
+
+ self.token_b = validate_regex(
+ token_b, *self.template_tokens['token_b']['regex'])
+ if not self.token_b:
+ msg = 'An invalid Slack (second) Token ' \
+ '({}) was specified.'.format(token_b)
+ self.logger.warning(msg)
+ raise TypeError(msg)
+
+ self.token_c = validate_regex(
+ token_c, *self.template_tokens['token_c']['regex'])
+ if not self.token_c:
+ msg = 'An invalid Slack (third) Token ' \
+ '({}) was specified.'.format(token_c)
+ self.logger.warning(msg)
+ raise TypeError(msg)
+ else:
+ self.access_token = validate_regex(
+ access_token, *self.template_tokens['access_token']['regex'])
+ if not self.access_token:
+ msg = 'An invalid Slack OAuth Access Token ' \
+ '({}) was specified.'.format(access_token)
+ self.logger.warning(msg)
+ raise TypeError(msg)
if not self.user:
self.logger.warning(
@@ -233,7 +293,9 @@ class NotifySlack(NotifyBase):
# No problem; the webhook is smart enough to just notify the
# channel it was created for; adding 'None' is just used as
# a flag lower to not set the channels
- self.channels.append(None)
+ self.channels.append(
+ None if self.mode is SlackMode.WEBHOOK
+ else self.default_notification_channel)
# Formatting requirements are defined here:
# https://api.slack.com/docs/message-formatting
@@ -255,16 +317,16 @@ class NotifySlack(NotifyBase):
# Place a thumbnail image inline with the message body
self.include_image = include_image
- def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
+ # Place a footer with each post
+ self.include_footer = include_footer
+ return
+
+ def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,
+ **kwargs):
"""
Perform Slack Notification
"""
- headers = {
- 'User-Agent': self.app_id,
- 'Content-Type': 'application/json',
- }
-
# error tracking (used for function return)
has_error = False
@@ -275,14 +337,8 @@ class NotifySlack(NotifyBase):
body = self._re_formatting_rules.sub( # pragma: no branch
lambda x: self._re_formatting_map[x.group()], body,
)
- url = '%s/%s/%s/%s' % (
- self.notify_url,
- self.token_a,
- self.token_b,
- self.token_c,
- )
- # prepare JSON Object
+ # Prepare JSON Object (applicable to both WEBHOOK and BOT mode)
payload = {
'username': self.user if self.user else self.app_id,
# Use Markdown language
@@ -293,102 +349,287 @@ class NotifySlack(NotifyBase):
'color': self.color(notify_type),
# Time
'ts': time(),
- 'footer': self.app_id,
}],
}
+ # Prepare our URL (depends on mode)
+ if self.mode is SlackMode.WEBHOOK:
+ url = '{}/{}/{}/{}'.format(
+ self.webhook_url,
+ self.token_a,
+ self.token_b,
+ self.token_c,
+ )
+
+ else: # SlackMode.BOT
+ url = self.api_url.format('chat.postMessage')
+
+ if self.include_footer:
+ # Include the footer only if specified to do so
+ payload['attachments'][0]['footer'] = self.app_id
+
+ if attach and self.mode is SlackMode.WEBHOOK:
+ # Be friendly; let the user know why they can't send their
+ # attachments if using the Webhook mode
+ self.logger.warning(
+ 'Slack Webhooks do not support attachments.')
+
# Create a copy of the channel list
channels = list(self.channels)
+
+ attach_channel_list = []
while len(channels):
channel = channels.pop(0)
if channel is not None:
- # Channel over-ride was specified
- if not IS_VALID_TARGET_RE.match(channel):
+ _channel = validate_regex(
+ channel, r'[+#@]?(?P<value>[A-Z0-9_]{1,32})')
+
+ if not _channel:
+ # Channel over-ride was specified
self.logger.warning(
"The specified target {} is invalid;"
- "skipping.".format(channel))
+ "skipping.".format(_channel))
# Mark our failure
has_error = True
continue
- if len(channel) > 1 and channel[0] == '+':
+ if len(_channel) > 1 and _channel[0] == '+':
# Treat as encoded id if prefixed with a +
- payload['channel'] = channel[1:]
+ payload['channel'] = _channel[1:]
- elif len(channel) > 1 and channel[0] == '@':
+ elif len(_channel) > 1 and _channel[0] == '@':
# Treat @ value 'as is'
- payload['channel'] = channel
+ payload['channel'] = _channel
else:
# Prefix with channel hash tag
- payload['channel'] = '#%s' % channel
+ payload['channel'] = '#{}'.format(_channel)
+
+ # Store the valid and massaged payload that is recognizable by
+ # slack. This list is used for sending attachments later.
+ attach_channel_list.append(payload['channel'])
# Acquire our to-be footer icon if configured to do so
image_url = None if not self.include_image \
else self.image_url(notify_type)
if image_url:
- payload['attachments'][0]['footer_icon'] = image_url
+ payload['icon_url'] = image_url
- self.logger.debug('Slack POST URL: %s (cert_verify=%r)' % (
- url, self.verify_certificate,
- ))
- self.logger.debug('Slack Payload: %s' % str(payload))
+ if self.include_footer:
+ payload['attachments'][0]['footer_icon'] = image_url
- # Always call throttle before any remote server i/o is made
- self.throttle()
- try:
- r = requests.post(
- url,
- data=dumps(payload),
- headers=headers,
- verify=self.verify_certificate,
- )
- if r.status_code != requests.codes.ok:
- # We had a problem
- status_str = \
- NotifySlack.http_response_code_lookup(
- r.status_code, SLACK_HTTP_ERROR_MAP)
+ response = self._send(url, payload)
+ if not response:
+ # Handle any error
+ has_error = True
+ continue
- self.logger.warning(
- 'Failed to send Slack notification{}: '
- '{}{}error={}.'.format(
- ' to {}'.format(channel)
- if channel is not None else '',
- status_str,
- ', ' if status_str else '',
- r.status_code))
+ self.logger.info(
+ 'Sent Slack notification{}.'.format(
+ ' to {}'.format(channel)
+ if channel is not None else ''))
- self.logger.debug(
- 'Response Details:\r\n{}'.format(r.content))
+ if attach and self.mode is SlackMode.BOT and attach_channel_list:
+ # Send our attachments (can only be done in bot mode)
+ for attachment in attach:
+ self.logger.info(
+ 'Posting Slack Attachment {}'.format(attachment.name))
- # Mark our failure
- has_error = True
- continue
+ # Prepare API Upload Payload
+ _payload = {
+ 'filename': attachment.name,
+ 'channels': ','.join(attach_channel_list)
+ }
- else:
- self.logger.info(
- 'Sent Slack notification{}.'.format(
- ' to {}'.format(channel)
- if channel is not None else ''))
+ # Our URL
+ _url = self.api_url.format('files.upload')
+
+ response = self._send(_url, _payload, attach=attachment)
+ if not (response and response.get('file') and
+ response['file'].get('url_private')):
+ # We failed to post our attachments, take an early exit
+ return False
+
+ return not has_error
+
+ def _send(self, url, payload, attach=None, **kwargs):
+ """
+ Wrapper to the requests (post) object
+ """
+
+ self.logger.debug('Slack POST URL: %s (cert_verify=%r)' % (
+ url, self.verify_certificate,
+ ))
+ self.logger.debug('Slack Payload: %s' % str(payload))
+
+ headers = {
+ 'User-Agent': self.app_id,
+ }
+
+ if not attach:
+ headers['Content-Type'] = 'application/json; charset=utf-8'
+
+ if self.mode is SlackMode.BOT:
+ headers['Authorization'] = 'Bearer {}'.format(self.access_token)
+
+ # Our response object
+ response = None
+
+ # Always call throttle before any remote server i/o is made
+ self.throttle()
+
+ # Our attachment path (if specified)
+ files = None
+
+ try:
+ # Open our attachment path if required:
+ if attach:
+ files = {'file': (attach.name, open(attach.path, 'rb'))}
+
+ r = requests.post(
+ url,
+ data=payload if attach else dumps(payload),
+ headers=headers,
+ files=files,
+ verify=self.verify_certificate,
+ )
+
+ if r.status_code != requests.codes.ok:
+ # We had a problem
+ status_str = \
+ NotifySlack.http_response_code_lookup(
+ r.status_code, SLACK_HTTP_ERROR_MAP)
- except requests.RequestException as e:
self.logger.warning(
- 'A Connection error occured sending Slack '
- 'notification{}.'.format(
- ' to {}'.format(channel)
- if channel is not None else ''))
- self.logger.debug('Socket Exception: %s' % str(e))
+ 'Failed to send {}to Slack: '
+ '{}{}error={}.'.format(
+ attach.name if attach else '',
+ status_str,
+ ', ' if status_str else '',
+ r.status_code))
- # Mark our failure
- has_error = True
- continue
+ self.logger.debug(
+ 'Response Details:\r\n{}'.format(r.content))
+ return False
- return not has_error
+ try:
+ response = loads(r.content)
+
+ except (AttributeError, TypeError, ValueError):
+ # ValueError = r.content is Unparsable
+ # TypeError = r.content is None
+ # AttributeError = r is None
+ pass
+
+ if not (response and response.get('ok', True)):
+ # Bare minimum requirements not met
+ self.logger.warning(
+ 'Failed to send {}to Slack: error={}.'.format(
+ attach.name if attach else '',
+ r.status_code))
+
+ self.logger.debug(
+ 'Response Details:\r\n{}'.format(r.content))
+ return False
+
+ # Message Post Response looks like this:
+ # {
+ # "attachments": [
+ # {
+ # "color": "3AA3E3",
+ # "fallback": "test",
+ # "id": 1,
+ # "text": "my body",
+ # "title": "my title",
+ # "ts": 1573694687
+ # }
+ # ],
+ # "bot_id": "BAK4K23G5",
+ # "icons": {
+ # "image_48": "https://s3-us-west-2.amazonaws.com/...
+ # },
+ # "subtype": "bot_message",
+ # "text": "",
+ # "ts": "1573694689.003700",
+ # "type": "message",
+ # "username": "Apprise"
+ # }
+
+ # File Attachment Responses look like this
+ # {
+ # "file": {
+ # "channels": [],
+ # "comments_count": 0,
+ # "created": 1573617523,
+ # "display_as_bot": false,
+ # "editable": false,
+ # "external_type": "",
+ # "filetype": "png",
+ # "groups": [],
+ # "has_rich_preview": false,
+ # "id": "FQJJLDAHM",
+ # "image_exif_rotation": 1,
+ # "ims": [],
+ # "is_external": false,
+ # "is_public": false,
+ # "is_starred": false,
+ # "mimetype": "image/png",
+ # "mode": "hosted",
+ # "name": "apprise-test.png",
+ # "original_h": 640,
+ # "original_w": 640,
+ # "permalink": "https://{name}.slack.com/files/...
+ # "permalink_public": "https://slack-files.com/...
+ # "pretty_type": "PNG",
+ # "public_url_shared": false,
+ # "shares": {},
+ # "size": 238810,
+ # "thumb_160": "https://files.slack.com/files-tmb/...
+ # "thumb_360": "https://files.slack.com/files-tmb/...
+ # "thumb_360_h": 360,
+ # "thumb_360_w": 360,
+ # "thumb_480": "https://files.slack.com/files-tmb/...
+ # "thumb_480_h": 480,
+ # "thumb_480_w": 480,
+ # "thumb_64": "https://files.slack.com/files-tmb/...
+ # "thumb_80": "https://files.slack.com/files-tmb/...
+ # "thumb_tiny": abcd...
+ # "timestamp": 1573617523,
+ # "title": "apprise-test",
+ # "url_private": "https://files.slack.com/files-pri/...
+ # "url_private_download": "https://files.slack.com/files-...
+ # "user": "UADKLLMJT",
+ # "username": ""
+ # },
+ # "ok": true
+ # }
+ except requests.RequestException as e:
+ self.logger.warning(
+ 'A Connection error occured posting {}to Slack.'.format(
+ attach.name if attach else ''))
+ self.logger.debug('Socket Exception: %s' % str(e))
+ return False
+
+ except (OSError, IOError) as e:
+ self.logger.warning(
+ 'An I/O error occured while reading {}.'.format(
+ attach.name if attach else 'attachment'))
+ self.logger.debug('I/O Exception: %s' % str(e))
+ return False
- def url(self):
+ finally:
+ # Close our file (if it's open) stored in the second element
+ # of our files tuple (index 1)
+ if files:
+ files['file'][1].close()
+
+ # Return the response for processing
+ return response
+
+ def url(self, privacy=False, *args, **kwargs):
"""
Returns the URL built dynamically based on specified arguments.
"""
@@ -398,23 +639,35 @@ class NotifySlack(NotifyBase):
'format': self.notify_format,
'overflow': self.overflow_mode,
'image': 'yes' if self.include_image else 'no',
+ 'footer': 'yes' if self.include_footer else 'no',
'verify': 'yes' if self.verify_certificate else 'no',
}
- # Determine if there is a botname present
- botname = ''
- if self.user:
- botname = '{botname}@'.format(
- botname=NotifySlack.quote(self.user, safe=''),
- )
+ if self.mode == SlackMode.WEBHOOK:
+ # Determine if there is a botname present
+ botname = ''
+ if self.user:
+ botname = '{botname}@'.format(
+ botname=NotifySlack.quote(self.user, safe=''),
+ )
- return '{schema}://{botname}{token_a}/{token_b}/{token_c}/{targets}/'\
+ return '{schema}://{botname}{token_a}/{token_b}/{token_c}/'\
+ '{targets}/?{args}'.format(
+ schema=self.secure_protocol,
+ botname=botname,
+ 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=''),
+ targets='/'.join(
+ [NotifySlack.quote(x, safe='')
+ for x in self.channels]),
+ args=NotifySlack.urlencode(args),
+ )
+ # else -> self.mode == SlackMode.BOT:
+ return '{schema}://{access_token}/{targets}/'\
'?{args}'.format(
schema=self.secure_protocol,
- botname=botname,
- token_a=NotifySlack.quote(self.token_a, safe=''),
- token_b=NotifySlack.quote(self.token_b, safe=''),
- token_c=NotifySlack.quote(self.token_c, safe=''),
+ access_token=self.pprint(self.access_token, privacy, safe=''),
targets='/'.join(
[NotifySlack.quote(x, safe='') for x in self.channels]),
args=NotifySlack.urlencode(args),
@@ -427,32 +680,40 @@ class NotifySlack(NotifyBase):
us to substantiate this object.
"""
- results = NotifyBase.parse_url(url)
-
+ results = NotifyBase.parse_url(url, verify_host=False)
if not results:
# We're done early as we couldn't load the results
return results
+ # The first token is stored in the hostname
+ token = NotifySlack.unquote(results['host'])
+
# Get unquoted entries
entries = NotifySlack.split_path(results['fullpath'])
- # The first token is stored in the hostname
- results['token_a'] = NotifySlack.unquote(results['host'])
+ # Verify if our token_a us a bot token or part of a webhook:
+ if token.startswith('xo'):
+ # We're dealing with a bot
+ results['access_token'] = token
- # Now fetch the remaining tokens
- try:
- results['token_b'] = entries.pop(0)
+ else:
+ # We're dealing with a webhook
+ results['token_a'] = token
- except IndexError:
- # We're done
- results['token_b'] = None
+ # Now fetch the remaining tokens
+ try:
+ results['token_b'] = entries.pop(0)
- try:
- results['token_c'] = entries.pop(0)
+ except IndexError:
+ # We're done
+ results['token_b'] = None
- except IndexError:
- # We're done
- results['token_c'] = None
+ try:
+ results['token_c'] = entries.pop(0)
+
+ except IndexError:
+ # We're done
+ results['token_c'] = None
# assign remaining entries to the channels we wish to notify
results['targets'] = entries
@@ -464,10 +725,14 @@ class NotifySlack(NotifyBase):
bool, CHANNEL_LIST_DELIM.split(
NotifySlack.unquote(results['qsd']['to'])))]
- # Get Image
+ # Get Image Flag
results['include_image'] = \
parse_bool(results['qsd'].get('image', True))
+ # Get Footer Flag
+ results['include_footer'] = \
+ parse_bool(results['qsd'].get('footer', True))
+
return results
@staticmethod
@@ -478,10 +743,10 @@ class NotifySlack(NotifyBase):
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)
+ r'(?P<token_a>[A-Z0-9]+)/'
+ r'(?P<token_b>[A-Z0-9]+)/'
+ r'(?P<token_c>[A-Z0-9]+)/?'
+ r'(?P<args>\?.+)?$', url, re.I)
if result:
return NotifySlack.parse_url(