diff --git a/KEYWORDS b/KEYWORDS
index ef340a9a..b9972ce2 100644
--- a/KEYWORDS
+++ b/KEYWORDS
@@ -41,7 +41,6 @@ KODI
Kumulos
LaMetric
Line
-LunaSea
MacOSX
Mailgun
Mastodon
diff --git a/README.md b/README.md
index f1eb8841..835ccd2d 100644
--- a/README.md
+++ b/README.md
@@ -80,7 +80,6 @@ The table below identifies the services this tool supports and some example serv
| [Kumulos](https://github.com/caronc/apprise/wiki/Notify_kumulos) | kumulos:// | (TCP) 443 | kumulos://apikey/serverkey
| [LaMetric Time](https://github.com/caronc/apprise/wiki/Notify_lametric) | lametric:// | (TCP) 443 | lametric://apikey@device_ipaddr
lametric://apikey@hostname:port
lametric://client_id@client_secret
| [Line](https://github.com/caronc/apprise/wiki/Notify_line) | line:// | (TCP) 443 | line://Token@User
line://Token/User1/User2/UserN
-| [LunaSea](https://github.com/caronc/apprise/wiki/Notify_lunasea) | lunasea:// | (TCP) 80 or 443 | lunasea://user:pass@+FireBaseDevice/
lunasea://user:pass@FireBaseUser/
lunasea://user:pass@hostname/+FireBaseDevice/
lunasea://user:pass@hostname/@FireBaseUser/
| [Mailgun](https://github.com/caronc/apprise/wiki/Notify_mailgun) | mailgun:// | (TCP) 443 | mailgun://user@hostname/apikey
mailgun://user@hostname/apikey/email
mailgun://user@hostname/apikey/email1/email2/emailN
mailgun://user@hostname/apikey/?name="From%20User"
| [Mastodon](https://github.com/caronc/apprise/wiki/Notify_mastodon) | mastodon:// or mastodons://| (TCP) 80 or 443 | mastodon://access_key@hostname
mastodon://access_key@hostname/@user
mastodon://access_key@hostname/@user1/@user2/@userN
| [Matrix](https://github.com/caronc/apprise/wiki/Notify_matrix) | matrix:// or matrixs:// | (TCP) 80 or 443 | matrix://hostname
matrix://user@hostname
matrixs://user:pass@hostname:port/#room_alias
matrixs://user:pass@hostname:port/!room_id
matrixs://user:pass@hostname:port/#room_alias/!room_id/#room2
matrixs://token@hostname:port/?webhook=matrix
matrix://user:token@hostname/?webhook=slack&format=markdown
diff --git a/apprise/plugins/lunasea.py b/apprise/plugins/lunasea.py
deleted file mode 100644
index 09a7ab54..00000000
--- a/apprise/plugins/lunasea.py
+++ /dev/null
@@ -1,456 +0,0 @@
-# -*- coding: utf-8 -*-
-# BSD 2-Clause License
-#
-# Apprise - Push Notification Library.
-# Copyright (c) 2025, Chris Caron
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# 1. Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-#
-# 2. Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-#
-# API:
-# https://docs.lunasea.app/lunasea/notifications/custom-notifications
-#
-import re
-import requests
-from json import dumps
-
-from .base import NotifyBase
-from ..common import NotifyType
-from ..common import NotifyImageSize
-from ..utils.parse import (
- parse_list, is_hostname, is_ipaddr, parse_bool)
-from ..locale import gettext_lazy as _
-from ..url import PrivacyMode
-
-
-class LunaSeaMode:
- """
- Define LunaSea Notification Modes
- """
- # App posts upstream to the developer API on LunaSea's website
- CLOUD = "cloud"
-
- # Running a dedicated private ntfy Server
- PRIVATE = "private"
-
-
-LUNASEA_MODES = (
- LunaSeaMode.CLOUD,
- LunaSeaMode.PRIVATE,
-)
-
-
-class NotifyLunaSea(NotifyBase):
- """
- A wrapper for LunaSea Notifications
- """
-
- # The default descriptive name associated with the Notification
- service_name = 'LunaSea'
-
- # The services URL
- service_url = 'https://luasea.app'
-
- # The default insecure protocol
- protocol = ('lunasea', 'lsea')
-
- # The default secure protocol
- secure_protocol = ('lunaseas', 'lseas')
-
- # A URL that takes you to the setup/help of the specific protocol
- setup_url = 'https://github.com/caronc/apprise/wiki/Notify_lunasea'
-
- # Allows the user to specify the NotifyImageSize object
- image_size = NotifyImageSize.XY_256
-
- # LunaSea Notification Details
- cloud_notify_url = 'https://notify.lunasea.app'
- notify_user_path = '/v1/custom/user/{}'
- notify_device_path = '/v1/custom/device/{}'
-
- # if our hostname matches the following we automatically enforce
- # cloud mode
- __auto_cloud_host = re.compile(r'(notify\.)?lunasea\.app', re.IGNORECASE)
-
- # Define object templates
- templates = (
- '{schema}://{targets}',
- '{schema}://{host}/{targets}',
- '{schema}://{host}:{port}/{targets}',
- '{schema}://{user}@{host}/{targets}',
- '{schema}://{user}@{host}:{port}/{targets}',
- '{schema}://{user}:{password}@{host}/{targets}',
- '{schema}://{user}:{password}@{host}:{port}/{targets}',
- )
-
- # Define our template tokens
- template_tokens = dict(NotifyBase.template_tokens, **{
- 'host': {
- 'name': _('Hostname'),
- 'type': 'string',
- },
- 'port': {
- 'name': _('Port'),
- 'type': 'int',
- 'min': 1,
- 'max': 65535,
- },
- 'user': {
- 'name': _('Username'),
- 'type': 'string',
- },
- 'password': {
- 'name': _('Password'),
- 'type': 'string',
- 'private': True,
- },
- 'token': {
- 'name': _('Token'),
- 'type': 'string',
- 'private': True,
- },
- 'target_user': {
- 'name': _('Target User'),
- 'type': 'string',
- 'prefix': '@',
- 'map_to': 'targets',
- },
- 'target_device': {
- 'name': _('Target Device'),
- 'type': 'string',
- 'prefix': '+',
- 'map_to': 'targets',
- },
- 'targets': {
- 'name': _('Targets'),
- 'type': 'list:string',
- 'required': True,
- },
- })
-
- # Define our template arguments
- template_args = dict(NotifyBase.template_args, **{
- 'to': {
- 'alias_of': 'targets',
- },
- 'image': {
- 'name': _('Include Image'),
- 'type': 'bool',
- 'default': False,
- 'map_to': 'include_image',
- },
- 'mode': {
- 'name': _('Mode'),
- 'type': 'choice:string',
- 'values': LUNASEA_MODES,
- 'default': LunaSeaMode.PRIVATE,
- },
- })
-
- def __init__(self, targets=None, mode=None, token=None,
- include_image=False, **kwargs):
- """
- Initialize LunaSea Object
- """
- super().__init__(**kwargs)
-
- # Show image associated with notification
- self.include_image = \
- self.template_args['image']['default'] \
- if include_image is None else include_image
-
- # Prepare our mode
- self.mode = mode.strip().lower() \
- if isinstance(mode, str) \
- else self.template_args['mode']['default']
-
- if self.mode not in LUNASEA_MODES:
- msg = 'An invalid LunaSea mode ({}) was specified.'.format(mode)
- self.logger.warning(msg)
- raise TypeError(msg)
-
- self.targets = []
- for target in parse_list(targets):
- if len(target) < 4:
- self.logger.warning(
- 'A specified target ({}) is invalid and will be '
- 'ignored'.format(target))
- continue
-
- if target[0] == '+':
- # Device
- self.targets.append(('+', target[1:]))
-
- elif target[0] == '@':
- # User
- self.targets.append(('@', target[1:]))
-
- else:
- # User
- self.targets.append(('@', target))
-
- return
-
- def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
- """
- Perform LunaSea Notification
- """
-
- # error tracking (used for function return)
- has_error = False
-
- if not len(self.targets):
- # We have nothing to notify; we're done
- self.logger.warning('There are no LunaSea targets to notify')
- return False
-
- # Prepare our headers
- headers = {
- 'User-Agent': self.app_id,
- 'Content-Type': 'application/json',
- 'Accept': 'application/json',
- }
-
- # prepare payload
- payload = {
- 'title': title if title else self.app_desc,
- 'body': body,
- }
-
- # Acquire image_url
- image_url = None if not self.include_image \
- else self.image_url(notify_type)
-
- if image_url:
- payload['image'] = image_url
-
- # Prepare our Authentication (if defined)
- if self.user and self.password:
- auth = (self.user, self.password)
-
- else:
- # No Auth
- auth = None
-
- if self.mode == LunaSeaMode.CLOUD:
- # Cloud Service
- notify_url = self.cloud_notify_url
-
- else:
- # Local Hosting
- schema = 'https' if self.secure else 'http'
-
- notify_url = '%s://%s' % (schema, self.host)
- if isinstance(self.port, int):
- notify_url += ':%d' % self.port
-
- # Create a copy of the targets list
- targets = list(self.targets)
- while len(targets):
- target = targets.pop(0)
-
- if target[0] == '+':
- url = notify_url + self.notify_device_path.format(target[1])
-
- else:
- url = notify_url + self.notify_user_path.format(target[1])
-
- self.logger.debug('LunaSea POST URL: %s (cert_verify=%r)' % (
- url, self.verify_certificate,
- ))
- self.logger.debug('LunaSea Payload: %s' % str(payload))
-
- # Always call throttle before any remote server i/o is made
- self.throttle()
-
- try:
- r = requests.post(
- url,
- data=dumps(payload),
- headers=headers,
- auth=auth,
- verify=self.verify_certificate,
- timeout=self.request_timeout,
- )
-
- if r.status_code not in (
- requests.codes.ok, requests.codes.no_content):
- # We had a problem
- status_str = \
- NotifyLunaSea.http_response_code_lookup(r.status_code)
-
- self.logger.warning(
- 'Failed to deliver payload to LunaSea:'
- '{}{}error={}.'.format(
- status_str,
- ', ' if status_str else '',
- r.status_code))
-
- self.logger.debug(
- 'Response Details:\r\n{}'.format(r.content))
-
- has_error = True
-
- # otherwise we were successful
- continue
-
- except requests.RequestException as e:
- self.logger.warning(
- 'A Connection error occurred communicating with LunaSea.')
- self.logger.debug('Socket Exception: %s' % str(e))
-
- has_error = True
-
- return not has_error
-
- @property
- def url_identifier(self):
- """
- Returns all of the identifiers that make this URL unique from
- another simliar one. Targets or end points should never be identified
- here.
- """
- secure = self.secure_protocol[0] \
- if self.mode == LunaSeaMode.CLOUD else (
- self.secure_protocol[0] if self.secure else self.protocol[0])
- return (
- secure,
- self.host if self.mode == LunaSeaMode.PRIVATE else None,
- self.port if self.port else (443 if self.secure else 80),
- self.user if self.user else None,
- self.password if self.password else None,
- )
-
- def url(self, privacy=False, *args, **kwargs):
- """
- Returns the URL built dynamically based on specified arguments.
- """
-
- params = {
- 'mode': self.mode,
- 'image': 'yes' if self.include_image else 'no',
- }
-
- # Our URL parameters
- params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
-
- auth = ''
- if self.user and self.password:
- auth = '{user}:{password}@'.format(
- user=NotifyLunaSea.quote(self.user, safe=''),
- password=self.pprint(
- self.password, privacy, mode=PrivacyMode.Secret,
- safe=''),
- )
- elif self.user:
- auth = '{user}@'.format(
- user=NotifyLunaSea.quote(self.user, safe=''),
- )
-
- if self.mode == LunaSeaMode.PRIVATE:
- default_port = 443 if self.secure else 80
- return '{schema}://{auth}{host}{port}/{targets}?{params}'.format(
- schema=self.secure_protocol[0]
- if self.secure else self.protocol[0],
- auth=auth,
- host=self.host,
- port='' if self.port is None or self.port == default_port
- else ':{}'.format(self.port),
- targets='/'.join(
- [NotifyLunaSea.quote(x[0] + x[1], safe='@+')
- for x in self.targets]),
- params=NotifyLunaSea.urlencode(params)
- )
-
- else: # Cloud mode
- return '{schema}://{auth}{targets}?{params}'.format(
- schema=self.protocol[0],
- auth=auth,
- targets='/'.join(
- [NotifyLunaSea.quote(x[0] + x[1], safe='@+')
- for x in self.targets]),
- params=NotifyLunaSea.urlencode(params)
- )
-
- def __len__(self):
- """
- Returns the number of targets associated with this notification
- """
- # always return 1
- return 1 if not self.targets else len(self.targets)
-
- @staticmethod
- def parse_url(url):
- """
- Parses the URL and returns enough arguments that can allow
- us to re-instantiate this object.
-
- """
- results = NotifyBase.parse_url(url, verify_host=False)
- if not results:
- # We're done early as we couldn't load the results
- return results
-
- # Fetch our targets
- results['targets'] = NotifyLunaSea.split_path(results['fullpath'])
-
- # Boolean to include an image or not
- results['include_image'] = parse_bool(results['qsd'].get(
- 'image', NotifyLunaSea.template_args['image']['default']))
-
- # The 'to' makes it easier to use yaml configuration
- if 'to' in results['qsd'] and len(results['qsd']['to']):
- results['targets'] += \
- NotifyLunaSea.parse_list(results['qsd']['to'])
-
- # Mode override
- if 'mode' in results['qsd'] and results['qsd']['mode']:
- results['mode'] = NotifyLunaSea.unquote(
- results['qsd']['mode'].strip().lower())
-
- else:
- # We can try to detect the mode based on the validity of the
- # hostname.
- #
- # This isn't a surfire way to do things though; it's best to
- # specify the mode= flag
- results['mode'] = LunaSeaMode.PRIVATE \
- if ((is_hostname(results['host'])
- or is_ipaddr(results['host'])) and results['targets']) \
- else LunaSeaMode.CLOUD
-
- if results['mode'] == LunaSeaMode.CLOUD:
- # Store first entry as it can be a topic too in this case
- # But only if we also rule it out not being the words
- # lunasea.app itself, something that starts wiht an non-alpha
- # numeric character:
- if not NotifyLunaSea.__auto_cloud_host.search(results['host']):
- # Add it to the front of the list for consistency
- results['targets'].insert(0, results['host'])
-
- elif results['mode'] == LunaSeaMode.PRIVATE and \
- not (is_hostname(results['host'] or
- is_ipaddr(results['host']))):
- # Invalid Host for LunaSeaMode.PRIVATE
- return None
-
- return results
diff --git a/packaging/redhat/python-apprise.spec b/packaging/redhat/python-apprise.spec
index 95a51aec..ab99504e 100644
--- a/packaging/redhat/python-apprise.spec
+++ b/packaging/redhat/python-apprise.spec
@@ -43,7 +43,7 @@ Africas Talking, Apprise API, APRS, AWS SES, AWS SNS, Bark, BlueSky, Burst SMS,
BulkSMS, BulkVS, Chanify, Clickatell, ClickSend, DAPNET, DingTalk, Discord, E-Mail, Emby,
FCM, Feishu, Flock, Free Mobile, Google Chat, Gotify, Growl, Guilded, Home
Assistant, httpSMS, IFTTT, Join, Kavenegar, KODI, Kumulos, LaMetric, Line,
-LunaSea, MacOSX, Mailgun, Mastodon, Mattermost, Matrix, MessageBird, Microsoft
+MacOSX, Mailgun, Mastodon, Mattermost, Matrix, MessageBird, Microsoft
Windows, Microsoft Teams, Misskey, MQTT, MSG91, MyAndroid, Nexmo, Nextcloud,
NextcloudTalk, Notica, Notifiarr, Notifico, ntfy, Office365, OneSignal,
Opsgenie, PagerDuty, PagerTree, ParsePlatform, Plivo, PopcornNotify, Prowl,
diff --git a/test/test_plugin_lunasea.py b/test/test_plugin_lunasea.py
deleted file mode 100644
index 33fe4b7d..00000000
--- a/test/test_plugin_lunasea.py
+++ /dev/null
@@ -1,280 +0,0 @@
-# -*- coding: utf-8 -*-
-# BSD 2-Clause License
-#
-# Apprise - Push Notification Library.
-# Copyright (c) 2025, Chris Caron
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#
-# 1. Redistributions of source code must retain the above copyright notice,
-# this list of conditions and the following disclaimer.
-#
-# 2. Redistributions in binary form must reproduce the above copyright notice,
-# this list of conditions and the following disclaimer in the documentation
-# and/or other materials provided with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-
-import os
-from unittest import mock
-from json import loads
-
-import requests
-from helpers import AppriseURLTester
-
-from apprise.plugins.lunasea import NotifyLunaSea
-
-# Disable logging for a cleaner testing output
-import logging
-logging.disable(logging.CRITICAL)
-
-# Attachment Directory
-TEST_VAR_DIR = os.path.join(os.path.dirname(__file__), 'var')
-
-# Our Testing URLs
-apprise_url_tests = (
- ('lunasea://', {
- # Initializes okay (as cloud mode) but has no targets to notify
- 'instance': NotifyLunaSea,
- # invalid targets specified (nothing to notify)
- # as a result the response type will be false
- 'response': False,
- }),
- ('lunaseas://44$$$$%3012/?mode=private', {
- # Private mode initialization with a horrible hostname
- 'instance': None
- }),
- ('lunasea://:@/', {
- # Initializes okay (as cloud mode) but has no targets to notify
- 'instance': NotifyLunaSea,
- # invalid targets specified (nothing to notify)
- # as a result the response type will be false
- 'response': False,
- }),
- # No targets
- ('lunasea://user:pass@localhost?mode=private', {
- 'instance': NotifyLunaSea,
- # invalid targets specified (nothing to notify)
- # as a result the response type will be false
- 'response': False,
- }),
- # No valid targets
- ('lunasea://user:pass@localhost/#/!/@', {
- 'instance': NotifyLunaSea,
- # invalid targets specified (nothing to notify)
- # as a result the response type will be false
- 'response': False,
- }),
- # user/pass combos
- ('lunasea://user@localhost/@user/', {
- 'instance': NotifyLunaSea,
- # Our expected url(privacy=True) startswith() response:
- 'privacy_url': 'lunasea://user@localhost/@user',
- }),
- # LunaSea cloud mode (enforced)
- ('lunasea://lunasea.app/@user/+device/', {
- 'instance': NotifyLunaSea,
- }),
- # No user/pass combo
- ('lunasea://localhost/@user/@user2/?image=True', {
- 'instance': NotifyLunaSea,
- }),
- # Enforce image but not otherwise find one
- ('lunasea://localhost/+device/?image=True', {
- 'instance': NotifyLunaSea,
- 'include_image': False,
- }),
- # No images
- ('lunasea://localhost/+device/?image=False', {
- 'instance': NotifyLunaSea,
- }),
- ('lunaseas://user:pass@localhost?to=+device', {
- 'instance': NotifyLunaSea,
- # The response text is expected to be the following on a success
- }),
- ('https://just/a/random/host/that/means/nothing', {
- # Nothing transpires from this
- 'instance': None
- }),
- # Several targets
- ('lunasea://user:pass@+device/user/@user2/?mode=cloud', {
- 'instance': NotifyLunaSea,
- # The response text is expected to be the following on a success
- }),
- # Several targets (but do not add lunasea.app)
- ('lunasea://user:pass@lunasea.app/user1/user2/?mode=cloud', {
- 'instance': NotifyLunaSea,
- # The response text is expected to be the following on a success
- }),
- ('lunaseas://user:web/token@localhost/user/?mode=invalid', {
- # Invalid mode
- 'instance': TypeError,
- }),
- ('lunasea://user:pass@localhost:8089/+device/user1', {
- 'instance': NotifyLunaSea,
- # force a failure using basic mode
- 'response': False,
- 'requests_response_code': requests.codes.internal_server_error,
- }),
- ('lunasea://user:pass@localhost:8082/+device', {
- 'instance': NotifyLunaSea,
- # throw a bizzare code forcing us to fail to look it up
- 'response': False,
- 'requests_response_code': 999,
- }),
- ('lunasea://user:pass@localhost:8083/user1/user2/', {
- 'instance': NotifyLunaSea,
- # Throws a series of connection and transfer exceptions when this flag
- # is set and tests that we gracfully handle them
- 'test_requests_exceptions': True,
- }),
-)
-
-
-def test_plugin_lunasea_urls():
- """
- NotifyLunaSea() Apprise URLs
-
- """
-
- # Run our general tests
- AppriseURLTester(tests=apprise_url_tests).run_all()
-
-
-@mock.patch('requests.post')
-def test_plugin_custom_lunasea_edge_cases(mock_post):
- """
- NotifyLunaSea() Edge Cases
-
- """
-
- # Prepare our response
- response = requests.Request()
- response.status_code = requests.codes.ok
- response.content = ''
-
- # Prepare Mock
- mock_post.return_value = response
-
- # Prepare a URL with some garbage in it that gets parsed out anyway
- # key take away is we provided userA and device1
- results = NotifyLunaSea.parse_url('lsea://user:pass@@userA,+device1,~~,,')
-
- assert isinstance(results, dict)
- assert results['user'] == 'user'
- assert results['password'] == 'pass'
- assert results['port'] is None
- assert results['host'] == 'userA,+device1,~~,,'
- assert results['fullpath'] is None
- assert results['path'] is None
- assert results['query'] is None
- assert results['schema'] == 'lsea'
- assert results['url'] == 'lsea://user:pass@userA,+device1,~~,,'
- assert isinstance(results['qsd:'], dict)
-
- instance = NotifyLunaSea(**results)
- assert isinstance(instance, NotifyLunaSea)
- assert len(instance.targets) == 2
- assert ('@', 'userA') in instance.targets
- assert ('+', 'device1') in instance.targets
-
- assert instance.notify("test") is True
-
- # 1 call to user, and second to device
- assert mock_post.call_count == 2
-
- url = mock_post.call_args_list[0][0][0]
- assert url == 'https://notify.lunasea.app/v1/custom/device/device1'
- payload = loads(mock_post.call_args_list[0][1]['data'])
- assert 'title' in payload
- assert 'body' in payload
- assert 'image' not in payload
- assert payload['body'] == 'test'
- assert payload['title'] == 'Apprise Notifications'
-
- url = mock_post.call_args_list[1][0][0]
- assert url == 'https://notify.lunasea.app/v1/custom/user/userA'
- payload = loads(mock_post.call_args_list[1][1]['data'])
- assert 'title' in payload
- assert 'body' in payload
- assert 'image' not in payload
- assert payload['body'] == 'test'
- assert payload['title'] == 'Apprise Notifications'
-
- assert '@userA' in instance.url()
- assert '+device1' in instance.url()
-
- # Test using a locally hosted instance now:
- mock_post.reset_mock()
-
- results = NotifyLunaSea.parse_url(
- 'lseas://user:pass@myhost:3222/@userA,+device1,~~,,')
-
- assert isinstance(results, dict)
- assert results['user'] == 'user'
- assert results['password'] == 'pass'
- assert results['port'] == 3222
- assert results['host'] == 'myhost'
- assert (
- results['fullpath'] == '/%40userA%2C%2Bdevice1%2C~~%2C%2C' or
- # Compatible with RHEL8 (Python v3.6.8)
- results['fullpath'] == '/%40userA%2C%2Bdevice1%2C%7E%7E%2C%2C'
- )
- assert results['path'] == '/'
- assert (
- results['query'] == '%40userA%2C%2Bdevice1%2C~~%2C%2C' or
- # Compatible with RHEL8 (Python v3.6.8)
- results['query'] == '%40userA%2C%2Bdevice1%2C%7E%7E%2C%2C'
- )
- assert results['schema'] == 'lseas'
- assert (
- results['url'] ==
- 'lseas://user:pass@myhost:3222/%40userA%2C%2Bdevice1%2C~~%2C%2C' or
- # Compatible with RHEL8 (Python v3.6.8)
- results['url'] ==
- 'lseas://user:pass@myhost:3222/%40userA%2C%2Bdevice1%2C%7E%7E%2C%2C'
- )
- assert isinstance(results['qsd:'], dict)
-
- instance = NotifyLunaSea(**results)
- assert isinstance(instance, NotifyLunaSea)
- assert len(instance.targets) == 2
- assert ('@', 'userA') in instance.targets
- assert ('+', 'device1') in instance.targets
-
- assert instance.notify("test") is True
-
- # 1 call to user, and second to device
- assert mock_post.call_count == 2
-
- url = mock_post.call_args_list[0][0][0]
- assert url == 'https://myhost:3222/v1/custom/device/device1'
- payload = loads(mock_post.call_args_list[0][1]['data'])
- assert 'title' in payload
- assert 'body' in payload
- assert 'image' not in payload
- assert payload['body'] == 'test'
- assert payload['title'] == 'Apprise Notifications'
-
- url = mock_post.call_args_list[1][0][0]
- assert url == 'https://myhost:3222/v1/custom/user/userA'
- payload = loads(mock_post.call_args_list[1][1]['data'])
- assert 'title' in payload
- assert 'body' in payload
- assert 'image' not in payload
- assert payload['body'] == 'test'
- assert payload['title'] == 'Apprise Notifications'
-
- assert '@userA' in instance.url()
- assert '+device1' in instance.url()