From 937c68808d352a7b637d6f0ea8fd5370e0ee8e24 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Wed, 3 Dec 2025 13:29:51 -0500 Subject: [PATCH] Added support for slack-gov.com to slack:// (#1463) --- apprise/plugins/slack.py | 76 ++++++++++++++++++++++++++++++-------- tests/test_plugin_slack.py | 34 +++++++++++++++++ 2 files changed, 95 insertions(+), 15 deletions(-) diff --git a/apprise/plugins/slack.py b/apprise/plugins/slack.py index 4aa8b421..68d13b77 100644 --- a/apprise/plugins/slack.py +++ b/apprise/plugins/slack.py @@ -102,7 +102,12 @@ class SlackMode: # We're dealing with a webhook # Our token looks like: T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7 - WEBHOOK = "webhook" + WEBHOOK = "hook" + + # Government Webhook + # Our token still looks like: T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7 + # however we have a different URL we post to + WEBHOOK_GOV = "gov-hook" # We're dealing with a bot (using the OAuth Access Token) # Our token looks like: xoxp-1234-1234-1234-abc124 or @@ -113,6 +118,7 @@ class SlackMode: # Define our Slack Modes SLACK_MODES = ( SlackMode.WEBHOOK, + SlackMode.WEBHOOK_GOV, SlackMode.BOT, ) @@ -140,8 +146,9 @@ class NotifySlack(NotifyBase): attachment_support = True # The maximum targets to include when doing batch transfers - # Slack Webhook URL + # Slack Webhook URLs webhook_url = "https://hooks.slack.com/services" + webhook_gov_url = "https://hooks.slack-gov.com/services" # Slack API URL (used with Bots) api_url = "https://slack.com/api/{}" @@ -277,6 +284,12 @@ class NotifySlack(NotifyBase): "default": True, "map_to": "include_timestamp", }, + "mode": { + "name": _("Message Mode"), + "type": "choice:string", + "values": SLACK_MODES, + # mode is detected if not specified + }, "token": { "name": _("Token"), "alias_of": ("access_token", "token_a", "token_b", "token_c"), @@ -335,15 +348,28 @@ class NotifySlack(NotifyBase): include_footer=None, include_timestamp=None, use_blocks=None, + mode=None, **kwargs, ): """Initialize Slack Object.""" super().__init__(**kwargs) - # Setup our mode - self.mode = SlackMode.BOT if access_token else SlackMode.WEBHOOK + # Store our webhook mode + if mode and isinstance(mode, str): + self.mode = next( + (a for a in SLACK_MODES if a.startswith(mode)), None + ) + if self.mode not in SLACK_MODES: + msg = ( + f"The Slack mode specified ({mode}) is invalid." + ) + self.logger.warning(msg) + raise TypeError(msg) - if self.mode is SlackMode.WEBHOOK: + else: # Detect + self.mode = SlackMode.BOT if access_token else SlackMode.WEBHOOK + + if self.mode in (SlackMode.WEBHOOK, SlackMode.WEBHOOK_GOV): self.access_token = None self.token_a = validate_regex( token_a, *self.template_tokens["token_a"]["regex"] @@ -411,7 +437,7 @@ class NotifySlack(NotifyBase): # a flag lower to not set the channels self.channels.append( None - if self.mode is SlackMode.WEBHOOK + if self.mode in (SlackMode.WEBHOOK, SlackMode.WEBHOOK_GOV) else self.default_notification_channel ) @@ -610,7 +636,7 @@ class NotifySlack(NotifyBase): if ( attach and self.attachment_support - and self.mode is SlackMode.WEBHOOK + and self.mode in (SlackMode.WEBHOOK, SlackMode.WEBHOOK_GOV) ): # Be friendly; let the user know why they can't send their # attachments if using the Webhook mode @@ -623,6 +649,12 @@ class NotifySlack(NotifyBase): f"/{self.token_b}/{self.token_c}" ) + elif self.mode is SlackMode.WEBHOOK_GOV: + url = ( + f"{self.webhook_gov_url}/{self.token_a}" + f"/{self.token_b}/{self.token_c}" + ) + else: # SlackMode.BOT url = self.api_url.format("chat.postMessage") @@ -1132,6 +1164,7 @@ class NotifySlack(NotifyBase): "footer": "yes" if self.include_footer else "no", "timestamp": "yes" if self.include_timestamp else "no", "blocks": "yes" if self.use_blocks else "no", + "mode": self.mode, } # Extend our parameters @@ -1144,7 +1177,7 @@ class NotifySlack(NotifyBase): botname=NotifySlack.quote(self.user, safe=""), ) - if self.mode == SlackMode.WEBHOOK: + if self.mode in (SlackMode.WEBHOOK, SlackMode.WEBHOOK_GOV): return ( "{schema}://{botname}{token_a}/{token_b}/{token_c}/" "{targets}/?{params}".format( @@ -1263,16 +1296,22 @@ class NotifySlack(NotifyBase): parse_bool(results["qsd"].get( "footer", NotifySlack.template_args["footer"]["default"])) + # Get Mode + if "mode" in results["qsd"] and len(results["qsd"]["mode"]): + results["mode"] = NotifySlack.unquote(results["qsd"]["mode"]) + return results @staticmethod def parse_native_url(url): """ - Support https://hooks.slack.com/services/TOKEN_A/TOKEN_B/TOKEN_C + Supports: + - https://hooks.slack.com/services/TOKEN_A/TOKEN_B/TOKEN_C + - https://hooks.slack-gov.com/services/TOKEN_A/TOKEN_B/TOKEN_C """ result = re.match( - r"^https?://hooks\.slack\.com/services/" + r"^https?://(?Phooks\.slack(?P-gov)?\.com)/services/" r"(?P[A-Z0-9]+)/" r"(?P[A-Z0-9]+)/" r"(?P[A-Z0-9]+)/?" @@ -1282,17 +1321,24 @@ class NotifySlack(NotifyBase): ) if result: + params = ( + "" + if not result.group("params") + else result.group("params") + ) + + if result.group("gov"): + # provide gov parameters + params = ("?" if not params else "&") + \ + f"mode={SlackMode.WEBHOOK_GOV}" + return NotifySlack.parse_url( "{schema}://{token_a}/{token_b}/{token_c}/{params}".format( schema=NotifySlack.secure_protocol, token_a=result.group("token_a"), token_b=result.group("token_b"), token_c=result.group("token_c"), - params=( - "" - if not result.group("params") - else result.group("params") - ), + params=params, ) ) diff --git a/tests/test_plugin_slack.py b/tests/test_plugin_slack.py index a201d049..563af9d5 100644 --- a/tests/test_plugin_slack.py +++ b/tests/test_plugin_slack.py @@ -73,6 +73,13 @@ apprise_url_tests = ( "instance": TypeError, }, ), + ( + "slack://T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/?mode=invalid", + { + # invalid Mode provided + "instance": TypeError, + }, + ), ( "slack://T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/#hmm/#-invalid-", { @@ -217,6 +224,22 @@ apprise_url_tests = ( }, }, ), + # Testing modes + ( + ( + "slack://?token=T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/" + "&to=#chan&mode=hook" + ), + {"instance": NotifySlack, "requests_response_text": "ok"}, + ), + # Forced mode on a url that does not have enough details to accomodate + ( + ( + "slack://?token=T1JJ3T3L2/A1BRTD4JD/TIiajkdnlazkcOXrIdevi7FQ/" + "&to=#chan&mode=bot" + ), + {"instance": TypeError}, + ), # Test blocks mode with timestamp variation ( ( @@ -325,6 +348,17 @@ apprise_url_tests = ( { "instance": NotifySlack, "requests_response_text": "ok", + "url_matches": "mode=hook", + }, + ), + ( + "https://hooks.slack-gov.com/services/{}/{}/{}".format( + "A" * 9, "B" * 9, "c" * 24 + ), + { + "instance": NotifySlack, + "requests_response_text": "ok", + "url_matches": "mode=gov-hook", }, ), # Native URL Support with arguments