Skip to content

Commit 084b098

Browse files
committed
test coverage
1 parent 14e7982 commit 084b098

File tree

7 files changed

+360
-67
lines changed

7 files changed

+360
-67
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ The table below identifies the services this tool supports and some example serv
9292
| [Nextcloud](https://github.com/caronc/apprise/wiki/Notify_nextcloud) | ncloud:// or nclouds:// | (TCP) 80 or 443 | ncloud://adminuser:pass@host/User<br/>nclouds://adminuser:pass@host/User1/User2/UserN
9393
| [NextcloudTalk](https://github.com/caronc/apprise/wiki/Notify_nextcloudtalk) | nctalk:// or nctalks:// | (TCP) 80 or 443 | nctalk://user:pass@host/RoomId<br/>nctalks://user:pass@host/RoomId1/RoomId2/RoomIdN
9494
| [Notica](https://github.com/caronc/apprise/wiki/Notify_notica) | notica:// | (TCP) 443 | notica://Token/
95+
| [NotificationAPI](https://github.com/caronc/apprise/wiki/Notify_notificationapi) | napi:// | (TCP) 443 | napi://ClientID/ClientSecret/Target<br />napi://ClientID/ClientSecret/Target1/Target2/TargetN<br />napi://MessageType@ClientID/ClientSecret/Target
9596
| [Notifiarr](https://github.com/caronc/apprise/wiki/Notify_notifiarr) | notifiarr:// | (TCP) 443 | notifiarr://apikey/#channel<br />notifiarr://apikey/#channel1/#channel2/#channeln
9697
| [Notifico](https://github.com/caronc/apprise/wiki/Notify_notifico) | notifico:// | (TCP) 443 | notifico://ProjectID/MessageHook/
9798
| [ntfy](https://github.com/caronc/apprise/wiki/Notify_ntfy) | ntfy:// | (TCP) 80 or 443 | ntfy://topic/<br/>ntfys://topic/

apprise/config/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -667,7 +667,7 @@ def config_parse_text(
667667
valid_line_re = re.compile(
668668
r"^\s*(?P<line>([;#]+(?P<comment>.*))|"
669669
r"(\s*(?P<tags>[a-z0-9, \t_-]+)\s*=|=)?\s*"
670-
r"((?P<url>[a-z0-9]{1,12}://.*)|(?P<assign>[a-z0-9, \t_-]+))|"
670+
r"((?P<url>[a-z0-9]{1,32}://.*)|(?P<assign>[a-z0-9, \t_-]+))|"
671671
r"include\s+(?P<config>.+))?\s*$",
672672
re.I,
673673
)

apprise/plugins/notificationapi.py

Lines changed: 36 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
import base64
3434
from email.utils import formataddr
35+
from itertools import chain
3536
from json import dumps, loads
3637
import re
3738

@@ -51,7 +52,7 @@
5152

5253
# Used to detect ID
5354
IS_VALID_ID_RE = re.compile(
54-
r"^\s*(@|%40)?(?P<id>[\w_-]+)\s*", re.I)
55+
r"^\s*(@|%40)?(?P<id>[\w_-]+)\s*$", re.I)
5556

5657

5758
class NotificationAPIRegion:
@@ -85,7 +86,7 @@ class NotificationAPIChannel:
8586
INAPP = "inapp"
8687
WEB_PUSH = "web_push"
8788
MOBILE_PUSH = "mobile_push"
88-
SLACK = "mobile_push"
89+
SLACK = "slack"
8990

9091

9192
# A List of our channels we can use for verification
@@ -209,10 +210,6 @@ class NotifyNotificationAPI(NotifyBase):
209210
"type": "choice:string",
210211
"values": NOTIFICATIONAPI_MODES,
211212
},
212-
"base_url": {
213-
"name": _("Base URL Override"),
214-
"type": "string",
215-
},
216213
"to": {
217214
"alias_of": "targets",
218215
},
@@ -328,7 +325,6 @@ def __init__(self, client_id, client_secret, message_type=None,
328325
self.message_type = self.default_message_type
329326

330327
else:
331-
# Validate information
332328
self.message_type = validate_regex(
333329
message_type, *self.template_tokens["type"]["regex"])
334330
if not self.message_type:
@@ -382,6 +378,9 @@ def __init__(self, client_id, client_secret, message_type=None,
382378
raise TypeError(msg) from None
383379
self.channels.add(channel)
384380

381+
# Used for URL generation afterwards only
382+
self._invalid_targets = []
383+
385384
if targets:
386385
current_target = {}
387386
for entry in parse_list(targets, sort=False):
@@ -417,7 +416,7 @@ def __init__(self, client_id, client_secret, message_type=None,
417416
if result:
418417
if "number" not in current_target:
419418
current_target["number"] = \
420-
('+' if entry[0] == '+' else '') + result["full"]
419+
("+" if entry[0] == "+" else "") + result["full"]
421420
if not self.channels:
422421
self.channels.add(NotificationAPIChannel.SMS)
423422
self.logger.info(
@@ -448,17 +447,17 @@ def __init__(self, client_id, client_secret, message_type=None,
448447
current_target["id"] = result.group("id")
449448
continue
450449

451-
elif "id" in current_target:
452-
# Store and move on
453-
self.targets.append(current_target)
454-
current_target = {
455-
"id": result.group("id")
456-
}
457-
continue
450+
# Store id in next target and move on
451+
self.targets.append(current_target)
452+
current_target = {
453+
"id": result.group("id")
454+
}
455+
continue
458456

459457
self.logger.warning(
460-
"Ignoring invalid NotificationAPI target "
458+
"Dropped invalid NotificationAPI target "
461459
f"({entry}) specified")
460+
self._invalid_targets.append(entry)
462461
continue
463462

464463
if "id" in current_target:
@@ -509,14 +508,6 @@ def __init__(self, client_id, client_secret, message_type=None,
509508
if isinstance(tokens, dict):
510509
self.tokens.update(tokens)
511510

512-
elif tokens:
513-
msg = (
514-
"The specified NotificationAPI Template Tokens "
515-
f"({tokens}) are not identified as a dictionary."
516-
)
517-
self.logger.warning(msg)
518-
raise TypeError(msg)
519-
520511
return
521512

522513
@property
@@ -572,7 +563,7 @@ def url(self, privacy=False, *args, **kwargs):
572563

573564
if self.channels:
574565
# Prepare our default channel
575-
params["channels"] = self.channels
566+
params["channels"] = ",".join(self.channels)
576567

577568
if self.region != self.template_args["region"]["default"]:
578569
# Prepare our default region
@@ -589,8 +580,8 @@ def url(self, privacy=False, *args, **kwargs):
589580

590581
targets = []
591582
for target in self.targets:
592-
if "id" in target:
593-
targets.append(f"@{target['id']}")
583+
# ID is always present
584+
targets.append(f"@{target['id']}")
594585
if "number" in target:
595586
targets.append(f"{target['number']}")
596587
if "email" in target:
@@ -603,7 +594,8 @@ def url(self, privacy=False, *args, **kwargs):
603594
mtype=mtype,
604595
cid=self.pprint(self.client_id, privacy, safe=""),
605596
secret=self.pprint(self.client_secret, privacy, safe=""),
606-
targets=NotifyNotificationAPI.quote("/".join(targets), safe="/"),
597+
targets=NotifyNotificationAPI.quote("/".join(
598+
chain(targets, self._invalid_targets)), safe="/"),
607599
params=NotifyNotificationAPI.urlencode(params),
608600
)
609601

@@ -649,9 +641,9 @@ def gen_payload(self, body, title="", notify_type=NotifyType.INFO,
649641
if self.notify_format == NotifyFormat.HTML else body
650642

651643
for channel in self.channels:
652-
# Python v3.10 supports `match/case` but since Apprise aims to be
653-
# compatible with Python v3.9+, we must use if/else for the time
654-
# being
644+
# Python v3.10 supports `match/case` but since Apprise aims to
645+
# be compatible with Python v3.9+, we must use if/else for the
646+
# time being
655647
if channel == NotificationAPIChannel.SMS:
656648
_payload.update({
657649
NotificationAPIChannel.SMS: {
@@ -703,7 +695,7 @@ def gen_payload(self, body, title="", notify_type=NotifyType.INFO,
703695
},
704696
})
705697

706-
elif channel == NotificationAPIChannel.SLACK:
698+
else: # channel == NotificationAPIChannel.SLACK
707699
_payload.update({
708700
NotificationAPIChannel.SLACK: {
709701
"text": (title + "\n" + text_body)
@@ -883,8 +875,10 @@ def parse_url(url):
883875
results["client_secret"] = None
884876

885877
# Prepare our targets (starting with our host)
886-
results["targets"] = [
887-
NotifyNotificationAPI.unquote(results["host"])]
878+
results["targets"] = []
879+
if results["host"]:
880+
results["targets"].append(
881+
NotifyNotificationAPI.unquote(results["host"]))
888882

889883
# For tracking email sources
890884
results["from_addr"] = None
@@ -919,13 +913,9 @@ def parse_url(url):
919913
results["region"] = \
920914
NotifyNotificationAPI.unquote(results["qsd"]["region"])
921915

922-
if "channel" in results["qsd"] and len(results["qsd"]["channel"]):
923-
results["channel"] = \
924-
NotifyNotificationAPI.unquote(results["qsd"]["channel"])
925-
926-
if "type" in results["qsd"] and len(results["qsd"]["type"]):
927-
results["message_type"] = \
928-
NotifyNotificationAPI.unquote(results["qsd"]["type"])
916+
if "channels" in results["qsd"] and len(results["qsd"]["channels"]):
917+
results["channels"] = \
918+
NotifyNotificationAPI.unquote(results["qsd"]["channels"])
929919

930920
if "mode" in results["qsd"] and len(results["qsd"]["mode"]):
931921
results["mode"] = \
@@ -935,6 +925,11 @@ def parse_url(url):
935925
results["reply_to"] = \
936926
NotifyNotificationAPI.unquote(results["qsd"]["reply"])
937927

928+
# Handling of Message Type
929+
if "type" in results["qsd"] and len(results["qsd"]["type"]):
930+
results["message_type"] = \
931+
NotifyNotificationAPI.unquote(results["qsd"]["type"])
932+
938933
elif results["user"]:
939934
# Pull from user
940935
results["message_type"] = \

apprise/utils/parse.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,14 @@
5858
NOTIFY_CUSTOM_COLON_TOKENS = re.compile(r"^:(?P<key>.*)\s*")
5959

6060
# Used for attempting to acquire the schema if the URL can't be parsed.
61-
GET_SCHEMA_RE = re.compile(r"\s*(?P<schema>[a-z0-9]{1,12})://.*$", re.I)
61+
GET_SCHEMA_RE = re.compile(r"\s*(?P<schema>[a-z0-9]{1,32})://.*$", re.I)
6262

6363
# Used for validating that a provided entry is indeed a schema
6464
# this is slightly different then the GET_SCHEMA_RE above which
6565
# insists the schema is only valid with a :// entry. this one
6666
# extrapolates the individual entries
6767
URL_DETAILS_RE = re.compile(
68-
r"\s*(?P<schema>[a-z0-9]{1,12})(://(?P<base>.*))?$", re.I
68+
r"\s*(?P<schema>[a-z0-9]{1,32})(://(?P<base>.*))?$", re.I
6969
)
7070

7171
# Regular expression based and expanded from:
@@ -124,7 +124,7 @@
124124

125125
# Regular expression used to destinguish between multiple URLs
126126
URL_DETECTION_RE = re.compile(
127-
r"([a-z0-9]+?:\/\/.*?)(?=$|[\s,]+[a-z0-9]{1,12}?:\/\/)", re.I
127+
r"([a-z0-9]+?:\/\/.*?)(?=$|[\s,]+[a-z0-9]{1,32}?:\/\/)", re.I
128128
)
129129

130130
EMAIL_DETECTION_RE = re.compile(
@@ -1100,7 +1100,7 @@ def parse_list(*args, cast=None, allow_whitespace=True, sort=True):
11001100
)
11011101
)
11021102
return (
1103-
[x for x in filter(bool, list(result))]
1103+
list(filter(bool, list(result)))
11041104
if allow_whitespace
11051105
else [x.strip() for x in filter(bool, list(result)) if x.strip()]
11061106
)

packaging/redhat/python-apprise.spec

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,9 @@ notification services. It supports sending alerts to platforms such as: \
6464
`Kumulos`, `LaMetric`, `Lark`, `Line`, `MacOSX`, `Mailgun`, `Mastodon`, \
6565
`Mattermost`, `Matrix`, `MessageBird`, `Microsoft Windows`, \
6666
`Microsoft Teams`, `Misskey`, `MQTT`, `MSG91`, `MyAndroid`, `Nexmo`, \
67-
`Nextcloud`, `NextcloudTalk`, `Notica`, `Notifiarr`, `Notifico`, `ntfy`, \
68-
`Office365`, `OneSignal`, `Opsgenie`, `PagerDuty`, `PagerTree`, \
69-
`ParsePlatform`, `Plivo`, `PopcornNotify`, `Prowl`, `Pushalot`, \
67+
`Nextcloud`, `NextcloudTalk`, `Notica`, `NotificationAPI`, `Notifiarr`,
68+
`Notifico`, `ntfy`, \ `Office365`, `OneSignal`, `Opsgenie`, `PagerDuty`, \
69+
`PagerTree`, `ParsePlatform`, `Plivo`, `PopcornNotify`, `Prowl`, `Pushalot`, \
7070
`PushBullet`, `Pushjet`, `PushMe`, `Pushover`, `Pushplus`, `PushSafer`, \
7171
`Pushy`, `PushDeer`, `QQ Push`, `Revolt`, `Reddit`, `Resend`, `Rocket.Chat`, \
7272
`RSyslog`, `SendGrid`, `SendPulse`, `ServerChan`, `Seven`, `SFR`, `Signal`, \

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ keywords = [
109109
"Nextcloud",
110110
"NextcloudTalk",
111111
"Notica",
112+
"NoticationAPI",
112113
"Notifiarr",
113114
"Notifico",
114115
"Ntfy",

0 commit comments

Comments
 (0)