diff --git a/README.md b/README.md
index 3127f114..57766fac 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,7 @@ The table below identifies the services this tool supports and some example serv
| [Faast](https://github.com/caronc/apprise/wiki/Notify_faast) | faast:// | (TCP) 443 | faast://authorizationtoken
| [Gnome](https://github.com/caronc/apprise/wiki/Notify_gnome) | gnome:// | n/a | gnome://
| [Growl](https://github.com/caronc/apprise/wiki/Notify_growl) | growl:// | (UDP) 23053 | growl://hostname
growl://hostname:portno
growl://password@hostname
growl://password@hostname:port**Note**: you can also use the get parameter _version_ which can allow the growl request to behave using the older v1.x protocol. An example would look like: growl://hostname?version=1
-| [IFTTT](https://github.com/caronc/apprise/wiki/Notify_ifttt) | ifttt:// | (TCP) 443 | ifttt://webhooksID/EventToTrigger
ifttt://webhooksID/EventToTrigger/Value1/Value2/Value3
ifttt://webhooksID/EventToTrigger/?Value3=NewEntry&Value2=AnotherValue
+| [IFTTT](https://github.com/caronc/apprise/wiki/Notify_ifttt) | ifttt:// | (TCP) 443 | ifttt://webhooksID/EventToTrigger
ifttt://webhooksID/EventToTrigger/Event2/EventN
| [Join](https://github.com/caronc/apprise/wiki/Notify_join) | join:// | (TCP) 443 | join://apikey/device
join://apikey/device1/device2/deviceN/
join://apikey/group
join://apikey/groupA/groupB/groupN
join://apikey/DeviceA/groupA/groupN/DeviceN/
| [KODI](https://github.com/caronc/apprise/wiki/Notify_kodi) | kodi:// or kodis:// | (TCP) 8080 or 443 | kodi://hostname
kodi://user@hostname
kodi://user:password@hostname:port
| [Matrix](https://github.com/caronc/apprise/wiki/Notify_matrix) | matrix:// or matrixs:// | (TCP) 80 or 443 | matrix://token
matrix://user@token
matrixs://token?mode=slack
matrixs://user@token
diff --git a/apprise/Apprise.py b/apprise/Apprise.py
index 576f1d54..9c9b81de 100644
--- a/apprise/Apprise.py
+++ b/apprise/Apprise.py
@@ -171,7 +171,7 @@ class Apprise(object):
# URL information
plugin = SCHEMA_MAP[results['schema']](**results)
- except:
+ except Exception:
# the arguments are invalid or can not be used.
logger.error('Could not load URL: %s' % url)
return None
@@ -432,6 +432,33 @@ class Apprise(object):
return response
+ def urls(self):
+ """
+ Returns all of the loaded URLs defined in this apprise object.
+ """
+ return [x.url() for x in self.servers]
+
+ def pop(self, index):
+ """
+ Removes an indexed Notification Service from the stack and
+ returns it.
+ """
+
+ # Remove our entry
+ return self.servers.pop(index)
+
+ def __getitem__(self, index):
+ """
+ Returns the indexed server entry of a loaded notification server
+ """
+ return self.servers[index]
+
+ def __iter__(self):
+ """
+ Returns an iterator to our server list
+ """
+ return iter(self.servers)
+
def __len__(self):
"""
Returns the number of servers loaded
diff --git a/apprise/plugins/NotifyBase.py b/apprise/plugins/NotifyBase.py
index a8471f83..9e05a908 100644
--- a/apprise/plugins/NotifyBase.py
+++ b/apprise/plugins/NotifyBase.py
@@ -260,6 +260,14 @@ class NotifyBase(object):
color_type=color_type,
)
+ def url(self):
+ """
+ Assembles the URL associated with the notification based on the
+ arguments provied.
+
+ """
+ raise NotImplementedError("This is implimented by the child class.")
+
def __contains__(self, tags):
"""
Returns true if the tag specified is associated with this notification.
@@ -347,16 +355,20 @@ class NotifyBase(object):
"""
common urlencode function
+ The query should always be a dictionary.
+
"""
+ # Tidy query by eliminating any records set to None
+ _query = {k: v for (k, v) in query.items() if v is not None}
try:
# Python v3.x
return _urlencode(
- query, doseq=doseq, safe=safe, encoding=encoding,
+ _query, doseq=doseq, safe=safe, encoding=encoding,
errors=errors)
except TypeError:
# Python v2.7
- return _urlencode(query)
+ return _urlencode(_query)
@staticmethod
def split_path(path, unquote=True):
diff --git a/apprise/plugins/NotifyBoxcar.py b/apprise/plugins/NotifyBoxcar.py
index 58891b76..dd55a3a3 100644
--- a/apprise/plugins/NotifyBoxcar.py
+++ b/apprise/plugins/NotifyBoxcar.py
@@ -29,6 +29,7 @@ import re
from time import time
import hmac
from hashlib import sha1
+from itertools import chain
try:
from urlparse import urlparse
@@ -272,6 +273,26 @@ class NotifyBoxcar(NotifyBase):
return True
+ def url(self):
+ """
+ Returns the URL built dynamically based on specified arguments.
+ """
+
+ # Define any arguments set
+ args = {
+ 'format': self.notify_format
+ }
+
+ return '{schema}://{access}/{secret}/{recipients}/?{args}'.format(
+ schema=self.secure_protocol,
+ access=self.quote(self.access),
+ secret=self.quote(self.secret),
+ recipients='/'.join([
+ self.quote(x) for x in chain(
+ self.tags, self.device_tokens) if x != DEFAULT_TAG]),
+ args=self.urlencode(args),
+ )
+
@staticmethod
def parse_url(url):
"""
diff --git a/apprise/plugins/NotifyDBus.py b/apprise/plugins/NotifyDBus.py
index 0517f1b6..8083a4a6 100644
--- a/apprise/plugins/NotifyDBus.py
+++ b/apprise/plugins/NotifyDBus.py
@@ -287,6 +287,13 @@ class NotifyDBus(NotifyBase):
return True
+ def url(self):
+ """
+ Returns the URL built dynamically based on specified arguments.
+ """
+
+ return '{schema}://'.format(schema=self.schema)
+
@staticmethod
def parse_url(url):
"""
diff --git a/apprise/plugins/NotifyDiscord.py b/apprise/plugins/NotifyDiscord.py
index 7cdf283c..2ce78bd0 100644
--- a/apprise/plugins/NotifyDiscord.py
+++ b/apprise/plugins/NotifyDiscord.py
@@ -180,8 +180,8 @@ class NotifyDiscord(NotifyBase):
else:
# not markdown
- payload['content'] = body if not title \
- else "{}\r\n{}".format(title, body)
+ payload['content'] = \
+ body if not title else "{}\r\n{}".format(title, body)
if self.avatar and image_url:
payload['avatar_url'] = image_url
@@ -241,6 +241,27 @@ class NotifyDiscord(NotifyBase):
return True
+ def url(self):
+ """
+ Returns the URL built dynamically based on specified arguments.
+ """
+
+ # Define any arguments set
+ args = {
+ 'format': self.notify_format,
+ 'tts': 'yes' if self.tts else 'no',
+ 'avatar': 'yes' if self.avatar else 'no',
+ 'footer': 'yes' if self.footer else 'no',
+ 'thumbnail': 'yes' if self.thumbnail else 'no',
+ }
+
+ return '{schema}://{webhook_id}/{webhook_token}/?{args}'.format(
+ schema=self.secure_protocol,
+ webhook_id=self.quote(self.webhook_id),
+ webhook_token=self.quote(self.webhook_token),
+ args=self.urlencode(args),
+ )
+
@staticmethod
def parse_url(url):
"""
diff --git a/apprise/plugins/NotifyEmail.py b/apprise/plugins/NotifyEmail.py
index 69d836c8..bdc29a2d 100644
--- a/apprise/plugins/NotifyEmail.py
+++ b/apprise/plugins/NotifyEmail.py
@@ -421,6 +421,52 @@ class NotifyEmail(NotifyBase):
return True
+ def url(self):
+ """
+ Returns the URL built dynamically based on specified arguments.
+ """
+
+ # Define any arguments set
+ args = {
+ 'format': self.notify_format,
+ 'to': self.to_addr,
+ 'from': self.from_addr,
+ 'name': self.from_name,
+ 'mode': self.secure_mode,
+ 'smtp': self.smtp_host,
+ 'timeout': self.timeout,
+ 'user': self.user,
+ }
+
+ # pull email suffix from username (if present)
+ user = self.user.split('@')[0]
+
+ # Determine Authentication
+ auth = ''
+ if self.user and self.password:
+ auth = '{user}:{password}@'.format(
+ user=self.quote(user, safe=''),
+ password=self.quote(self.password, safe=''),
+ )
+ else:
+ # user url
+ auth = '{user}@'.format(
+ user=self.quote(user, safe=''),
+ )
+
+ # Default Port setup
+ default_port = \
+ self.default_secure_port if self.secure else self.default_port
+
+ return '{schema}://{auth}{hostname}{port}/?{args}'.format(
+ schema=self.secure_protocol if self.secure else self.protocol,
+ auth=auth,
+ hostname=self.host,
+ port='' if self.port is None or self.port == default_port
+ else ':{}'.format(self.port),
+ args=self.urlencode(args),
+ )
+
@staticmethod
def parse_url(url):
"""
diff --git a/apprise/plugins/NotifyEmby.py b/apprise/plugins/NotifyEmby.py
index 02093078..4a514ee5 100644
--- a/apprise/plugins/NotifyEmby.py
+++ b/apprise/plugins/NotifyEmby.py
@@ -535,6 +535,38 @@ class NotifyEmby(NotifyBase):
return not has_error
+ def url(self):
+ """
+ Returns the URL built dynamically based on specified arguments.
+ """
+
+ # Define any arguments set
+ args = {
+ 'format': self.notify_format,
+ 'modal': 'yes' if self.modal else 'no',
+ }
+
+ # Determine Authentication
+ auth = ''
+ if self.user and self.password:
+ auth = '{user}:{password}@'.format(
+ user=self.quote(self.user, safe=''),
+ password=self.quote(self.password, safe=''),
+ )
+ else: # self.user is set
+ auth = '{user}@'.format(
+ user=self.quote(self.user, safe=''),
+ )
+
+ return '{schema}://{auth}{hostname}{port}/?{args}'.format(
+ schema=self.secure_protocol if self.secure else self.protocol,
+ auth=auth,
+ hostname=self.host,
+ port='' if self.port is None or self.port == self.emby_default_port
+ else ':{}'.format(self.port),
+ args=self.urlencode(args),
+ )
+
@property
def is_authenticated(self):
"""
diff --git a/apprise/plugins/NotifyFaast.py b/apprise/plugins/NotifyFaast.py
index cabee82b..5fffd724 100644
--- a/apprise/plugins/NotifyFaast.py
+++ b/apprise/plugins/NotifyFaast.py
@@ -124,6 +124,22 @@ class NotifyFaast(NotifyBase):
return True
+ def url(self):
+ """
+ Returns the URL built dynamically based on specified arguments.
+ """
+
+ # Define any arguments set
+ args = {
+ 'format': self.notify_format,
+ }
+
+ return '{schema}://{authtoken}/?{args}'.format(
+ schema=self.protocol,
+ authtoken=self.quote(self.authtoken, safe=''),
+ args=self.urlencode(args),
+ )
+
@staticmethod
def parse_url(url):
"""
diff --git a/apprise/plugins/NotifyGnome.py b/apprise/plugins/NotifyGnome.py
index 7f9747e5..a0f20fcc 100644
--- a/apprise/plugins/NotifyGnome.py
+++ b/apprise/plugins/NotifyGnome.py
@@ -164,6 +164,13 @@ class NotifyGnome(NotifyBase):
return True
+ def url(self):
+ """
+ Returns the URL built dynamically based on specified arguments.
+ """
+
+ return '{schema}://'.format(schema=self.protocol)
+
@staticmethod
def parse_url(url):
"""
diff --git a/apprise/plugins/NotifyGrowl/NotifyGrowl.py b/apprise/plugins/NotifyGrowl/NotifyGrowl.py
index e478998b..b88cbb4d 100644
--- a/apprise/plugins/NotifyGrowl/NotifyGrowl.py
+++ b/apprise/plugins/NotifyGrowl/NotifyGrowl.py
@@ -207,6 +207,43 @@ class NotifyGrowl(NotifyBase):
return True
+ def url(self):
+ """
+ Returns the URL built dynamically based on specified arguments.
+ """
+
+ _map = {
+ GrowlPriority.LOW: 'low',
+ GrowlPriority.MODERATE: 'moderate',
+ GrowlPriority.NORMAL: 'normal',
+ GrowlPriority.HIGH: 'high',
+ GrowlPriority.EMERGENCY: 'emergency',
+ }
+
+ # Define any arguments set
+ args = {
+ 'format': self.notify_format,
+ 'priority':
+ _map[GrowlPriority.NORMAL] if self.priority not in _map
+ else _map[self.priority],
+ 'version': self.version,
+ }
+
+ auth = ''
+ if self.password:
+ auth = '{password}@'.format(
+ password=self.quote(self.user, safe=''),
+ )
+
+ return '{schema}://{auth}{hostname}{port}/?{args}'.format(
+ schema=self.secure_protocol if self.secure else self.protocol,
+ auth=auth,
+ hostname=self.host,
+ port='' if self.port is None or self.port == self.default_port
+ else ':{}'.format(self.port),
+ args=self.urlencode(args),
+ )
+
@staticmethod
def parse_url(url):
"""
@@ -239,15 +276,10 @@ class NotifyGrowl(NotifyBase):
if 'priority' in results['qsd'] and len(results['qsd']['priority']):
_map = {
'l': GrowlPriority.LOW,
- '-2': GrowlPriority.LOW,
'm': GrowlPriority.MODERATE,
- '-1': GrowlPriority.MODERATE,
'n': GrowlPriority.NORMAL,
- '0': GrowlPriority.NORMAL,
'h': GrowlPriority.HIGH,
- '1': GrowlPriority.HIGH,
'e': GrowlPriority.EMERGENCY,
- '2': GrowlPriority.EMERGENCY,
}
try:
results['priority'] = \
diff --git a/apprise/plugins/NotifyIFTTT.py b/apprise/plugins/NotifyIFTTT.py
index 45a7782b..2f0bad50 100644
--- a/apprise/plugins/NotifyIFTTT.py
+++ b/apprise/plugins/NotifyIFTTT.py
@@ -34,7 +34,7 @@
# URL. For example, it might look like this:
# https://maker.ifttt.com/use/a3nHB7gA9TfBQSqJAHklod
#
-# In the above example a3nHB7gA9TfBQSqJAHklod becomes your {apikey}
+# In the above example a3nHB7gA9TfBQSqJAHklod becomes your {webhook_id}
# You will need this to make this notification work correctly
#
# For each event you create you will assign it a name (this will be known as
@@ -44,6 +44,7 @@ import requests
from json import dumps
from .NotifyBase import NotifyBase
from .NotifyBase import HTTP_ERROR_MAP
+from ..utils import parse_list
class NotifyIFTTT(NotifyBase):
@@ -59,7 +60,7 @@ class NotifyIFTTT(NotifyBase):
service_url = 'https://ifttt.com/'
# The default protocol
- protocol = 'ifttt'
+ secure_protocol = 'ifttt'
# A URL that takes you to the setup/help of the specific protocol
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_ifttt'
@@ -87,35 +88,28 @@ class NotifyIFTTT(NotifyBase):
ifttt_default_type_key = 'value3'
# IFTTT uses the http protocol with JSON requests
- notify_url = 'https://maker.ifttt.com/trigger/{event}/with/key/{apikey}'
+ notify_url = 'https://maker.ifttt.com/' \
+ 'trigger/{event}/with/key/{webhook_id}'
- def __init__(self, apikey, event, event_args=None, **kwargs):
+ def __init__(self, webhook_id, events, **kwargs):
"""
Initialize IFTTT Object
"""
super(NotifyIFTTT, self).__init__(**kwargs)
- if not apikey:
- raise TypeError('You must specify the Webhooks apikey.')
+ if not webhook_id:
+ raise TypeError('You must specify the Webhooks webhook_id.')
- if not event:
- raise TypeError('You must specify the Event you wish to trigger.')
+ # Store our Events we wish to trigger
+ self.events = parse_list(events)
+
+ if not self.events:
+ raise TypeError(
+ 'You must specify at least one event you wish to trigger on.')
# Store our APIKey
- self.apikey = apikey
-
- # Store our Event we wish to trigger
- self.event = event
-
- if isinstance(event_args, dict):
- # Make a copy of the arguments so that they can't change
- # outside of this plugin
- self.event_args = event_args.copy()
-
- else:
- # Force a dictionary
- self.event_args = dict()
+ self.webhook_id = webhook_id
def notify(self, title, body, notify_type, **kwargs):
"""
@@ -134,72 +128,101 @@ class NotifyIFTTT(NotifyBase):
self.ifttt_default_type_key: notify_type,
}
- # Update our payload using any other event_args specified
- payload.update(self.event_args)
-
# Eliminate empty fields; users wishing to cancel the use of the
# self.ifttt_default_ entries can preset these keys to being
# empty so that they get caught here and removed.
payload = {x: y for x, y in payload.items() if y}
- # URL to transmit content via
- url = self.notify_url.format(
- apikey=self.apikey,
- event=self.event,
+ # Track our failures
+ error_count = 0
+
+ # Create a copy of our event lit
+ events = list(self.events)
+
+ while len(events):
+
+ # Retrive an entry off of our event list
+ event = events.pop(0)
+
+ # URL to transmit content via
+ url = self.notify_url.format(
+ webhook_id=self.webhook_id,
+ event=event,
+ )
+
+ self.logger.debug('IFTTT POST URL: %s (cert_verify=%r)' % (
+ url, self.verify_certificate,
+ ))
+ self.logger.debug('IFTTT Payload: %s' % str(payload))
+ try:
+ r = requests.post(
+ url,
+ data=dumps(payload),
+ headers=headers,
+ verify=self.verify_certificate,
+ )
+ self.logger.debug(
+ u"IFTTT HTTP response status: %r" % r.status_code)
+ self.logger.debug(
+ u"IFTTT HTTP response headers: %r" % r.headers)
+ self.logger.debug(
+ u"IFTTT HTTP response body: %r" % r.content)
+
+ if r.status_code != requests.codes.ok:
+ # We had a problem
+ try:
+ self.logger.warning(
+ 'Failed to send IFTTT:%s '
+ 'notification: %s (error=%s).' % (
+ event,
+ HTTP_ERROR_MAP[r.status_code],
+ r.status_code))
+
+ except KeyError:
+ self.logger.warning(
+ 'Failed to send IFTTT:%s '
+ 'notification (error=%s).' % (
+ event, r.status_code))
+
+ # self.logger.debug('Response Details: %s' % r.content)
+ error_count += 1
+
+ else:
+ self.logger.info(
+ 'Sent IFTTT notification to Event %s.' % event)
+
+ except requests.RequestException as e:
+ self.logger.warning(
+ 'A Connection error occured sending IFTTT:%s ' % (
+ event) + 'notification.'
+ )
+ self.logger.debug('Socket Exception: %s' % str(e))
+ error_count += 1
+
+ finally:
+ if len(events):
+ # Prevent thrashing requests
+ self.throttle()
+
+ return (error_count == 0)
+
+ def url(self):
+ """
+ Returns the URL built dynamically based on specified arguments.
+ """
+
+ # Define any arguments set
+ args = {
+ 'format': self.notify_format,
+ }
+
+ return '{schema}://{webhook_id}@{events}/?{args}'.format(
+ schema=self.secure_protocol,
+ webhook_id=self.webhook_id,
+ events='/'.join([self.quote(x, safe='') for x in self.events]),
+ args=self.urlencode(args),
)
- self.logger.debug('IFTTT POST URL: %s (cert_verify=%r)' % (
- url, self.verify_certificate,
- ))
- self.logger.debug('IFTTT Payload: %s' % str(payload))
- try:
- r = requests.post(
- url,
- data=dumps(payload),
- headers=headers,
- verify=self.verify_certificate,
- )
- self.logger.debug(
- u"IFTTT HTTP response status: %r" % r.status_code)
- self.logger.debug(
- u"IFTTT HTTP response headers: %r" % r.headers)
- self.logger.debug(
- u"IFTTT HTTP response body: %r" % r.content)
-
- if r.status_code != requests.codes.ok:
- # We had a problem
- try:
- self.logger.warning(
- 'Failed to send IFTTT:%s '
- 'notification: %s (error=%s).' % (
- self.event,
- HTTP_ERROR_MAP[r.status_code],
- r.status_code))
-
- except KeyError:
- self.logger.warning(
- 'Failed to send IFTTT:%s '
- 'notification (error=%s).' % (
- self.event,
- r.status_code))
-
- # self.logger.debug('Response Details: %s' % r.content)
- return False
-
- else:
- self.logger.info(
- 'Sent IFTTT notification to Event %s.' % self.event)
-
- except requests.RequestException as e:
- self.logger.warning(
- 'A Connection error occured sending IFTTT:%s ' % (
- self.event) + 'notification.'
- )
- self.logger.debug('Socket Exception: %s' % str(e))
- return False
-
- return True
-
@staticmethod
def parse_url(url):
"""
@@ -214,22 +237,14 @@ class NotifyIFTTT(NotifyBase):
return results
# Our Event
- results['event'] = results['host']
+ results['events'] = list()
+ results['events'].append(results['host'])
# Our API Key
- results['apikey'] = results['user']
+ results['webhook_id'] = results['user']
- # Store ValueX entries based on each entry past the host
- results['event_args'] = {
- '{0}{1}'.format(NotifyIFTTT.ifttt_default_key_prefix, n + 1):
- NotifyBase.unquote(x)
- for n, x in enumerate(
- NotifyBase.split_path(results['fullpath'])) if x}
-
- # Allow users to set key=val parameters to specify more types
- # of payload options
- results['event_args'].update(
- {k: NotifyBase.unquote(v)
- for k, v in results['qsd'].items()})
+ # Now fetch the remaining tokens
+ results['events'].extend([x for x in filter(
+ bool, NotifyBase.split_path(results['fullpath']))][0:])
return results
diff --git a/apprise/plugins/NotifyJSON.py b/apprise/plugins/NotifyJSON.py
index ba160e92..c2903c43 100644
--- a/apprise/plugins/NotifyJSON.py
+++ b/apprise/plugins/NotifyJSON.py
@@ -70,6 +70,39 @@ class NotifyJSON(NotifyBase):
return
+ def url(self):
+ """
+ Returns the URL built dynamically based on specified arguments.
+ """
+
+ # Define any arguments set
+ args = {
+ 'format': self.notify_format,
+ }
+
+ # Determine Authentication
+ auth = ''
+ if self.user and self.password:
+ auth = '{user}:{password}@'.format(
+ user=self.quote(self.user, safe=''),
+ password=self.quote(self.password, safe=''),
+ )
+ elif self.user:
+ auth = '{user}@'.format(
+ user=self.quote(self.user, safe=''),
+ )
+
+ default_port = 443 if self.secure else 80
+
+ return '{schema}://{auth}{hostname}{port}/?{args}'.format(
+ schema=self.secure_protocol if self.secure else self.protocol,
+ auth=auth,
+ hostname=self.host,
+ port='' if self.port is None or self.port == default_port
+ else ':{}'.format(self.port),
+ args=self.urlencode(args),
+ )
+
def notify(self, title, body, notify_type, **kwargs):
"""
Perform JSON Notification
diff --git a/apprise/plugins/NotifyJoin.py b/apprise/plugins/NotifyJoin.py
index 67b75ab9..ed74c430 100644
--- a/apprise/plugins/NotifyJoin.py
+++ b/apprise/plugins/NotifyJoin.py
@@ -78,7 +78,7 @@ class NotifyJoin(NotifyBase):
service_url = 'https://joaoapps.com/join/'
# The default protocol
- protocol = 'join'
+ secure_protocol = 'join'
# A URL that takes you to the setup/help of the specific protocol
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_join'
@@ -233,6 +233,22 @@ class NotifyJoin(NotifyBase):
return return_status
+ def url(self):
+ """
+ Returns the URL built dynamically based on specified arguments.
+ """
+
+ # Define any arguments set
+ args = {
+ 'format': self.notify_format,
+ }
+
+ return '{schema}://{apikey}/{devices}/?{args}'.format(
+ schema=self.secure_protocol,
+ apikey=self.quote(self.apikey, safe=''),
+ devices='/'.join([self.quote(x) for x in self.devices]),
+ args=self.urlencode(args))
+
@staticmethod
def parse_url(url):
"""
diff --git a/apprise/plugins/NotifyMatrix.py b/apprise/plugins/NotifyMatrix.py
index 53c38942..27422d1b 100644
--- a/apprise/plugins/NotifyMatrix.py
+++ b/apprise/plugins/NotifyMatrix.py
@@ -112,7 +112,6 @@ class NotifyMatrix(NotifyBase):
if not self.user:
self.logger.warning(
'No user was specified; using %s.' % MATRIX_DEFAULT_USER)
- self.user = MATRIX_DEFAULT_USER
if mode not in MATRIX_NOTIFICATION_MODES:
self.logger.warning('The mode specified (%s) is invalid.' % mode)
@@ -209,7 +208,7 @@ class NotifyMatrix(NotifyBase):
def __slack_mode_payload(self, title, body, notify_type):
# prepare JSON Object
payload = {
- 'username': self.user,
+ 'username': self.user if self.user else MATRIX_DEFAULT_USER,
# Use Markdown language
'mrkdwn': True,
'attachments': [{
@@ -230,13 +229,43 @@ class NotifyMatrix(NotifyBase):
msg = '
%s
%s
' % (title, body)
payload = {
- 'displayName': self.user,
+ 'displayName': self.user if self.user else MATRIX_DEFAULT_USER,
'format': 'html',
'text': msg,
}
return payload
+ def url(self):
+ """
+ Returns the URL built dynamically based on specified arguments.
+ """
+
+ # Define any arguments set
+ args = {
+ 'format': self.notify_format,
+ 'mode': self.mode,
+ }
+
+ # Determine Authentication
+ auth = ''
+ if self.user:
+ auth = '{user}@'.format(
+ user=self.quote(self.user, safe=''),
+ )
+
+ default_port = 443 if self.secure else 80
+
+ return '{schema}://{auth}{host}/{token}{port}/?{args}'.format(
+ schema=self.secure_protocol if self.secure else self.protocol,
+ host=self.host,
+ auth=auth,
+ token=self.token,
+ port='' if self.port is None or self.port == default_port
+ else ':{}'.format(self.port),
+ args=self.urlencode(args),
+ )
+
@staticmethod
def parse_url(url):
"""
diff --git a/apprise/plugins/NotifyMatterMost.py b/apprise/plugins/NotifyMatterMost.py
index e92b8987..02ed020c 100644
--- a/apprise/plugins/NotifyMatterMost.py
+++ b/apprise/plugins/NotifyMatterMost.py
@@ -179,6 +179,28 @@ class NotifyMatterMost(NotifyBase):
return True
+ def url(self):
+ """
+ Returns the URL built dynamically based on specified arguments.
+ """
+
+ # Define any arguments set
+ args = {
+ 'format': self.notify_format,
+ }
+
+ default_port = 443 if self.secure else self.default_port
+ default_schema = self.secure_protocol if self.secure else self.protocol
+
+ return '{schema}://{hostname}{port}/{authtoken}/?{args}'.format(
+ schema=default_schema,
+ hostname=self.host,
+ port='' if not self.port or self.port == default_port
+ else ':{}'.format(self.port),
+ authtoken=self.quote(self.authtoken, safe=''),
+ args=self.urlencode(args),
+ )
+
@staticmethod
def parse_url(url):
"""
diff --git a/apprise/plugins/NotifyProwl.py b/apprise/plugins/NotifyProwl.py
index 7ddb6f11..c501d370 100644
--- a/apprise/plugins/NotifyProwl.py
+++ b/apprise/plugins/NotifyProwl.py
@@ -190,6 +190,34 @@ class NotifyProwl(NotifyBase):
return True
+ def url(self):
+ """
+ Returns the URL built dynamically based on specified arguments.
+ """
+
+ _map = {
+ ProwlPriority.LOW: 'low',
+ ProwlPriority.MODERATE: 'moderate',
+ ProwlPriority.NORMAL: 'normal',
+ ProwlPriority.HIGH: 'high',
+ ProwlPriority.EMERGENCY: 'emergency',
+ }
+
+ # Define any arguments set
+ args = {
+ 'format': self.notify_format,
+ 'priority': 'normal' if self.priority not in _map
+ else _map[self.priority]
+ }
+
+ return '{schema}://{apikey}/{providerkey}/?{args}'.format(
+ schema=self.secure_protocol,
+ apikey=self.quote(self.apikey, safe=''),
+ providerkey='' if not self.providerkey
+ else self.quote(self.providerkey, safe=''),
+ args=self.urlencode(args),
+ )
+
@staticmethod
def parse_url(url):
"""
@@ -216,15 +244,10 @@ class NotifyProwl(NotifyBase):
if 'priority' in results['qsd'] and len(results['qsd']['priority']):
_map = {
'l': ProwlPriority.LOW,
- '-2': ProwlPriority.LOW,
'm': ProwlPriority.MODERATE,
- '-1': ProwlPriority.MODERATE,
'n': ProwlPriority.NORMAL,
- '0': ProwlPriority.NORMAL,
'h': ProwlPriority.HIGH,
- '1': ProwlPriority.HIGH,
'e': ProwlPriority.EMERGENCY,
- '2': ProwlPriority.EMERGENCY,
}
try:
results['priority'] = \
diff --git a/apprise/plugins/NotifyPushBullet.py b/apprise/plugins/NotifyPushBullet.py
index 5eac9a46..e772d2eb 100644
--- a/apprise/plugins/NotifyPushBullet.py
+++ b/apprise/plugins/NotifyPushBullet.py
@@ -182,6 +182,28 @@ class NotifyPushBullet(NotifyBase):
return not has_error
+ def url(self):
+ """
+ Returns the URL built dynamically based on specified arguments.
+ """
+
+ # Define any arguments set
+ args = {
+ 'format': self.notify_format,
+ }
+
+ recipients = '/'.join([self.quote(x) for x in self.recipients])
+ if recipients == PUSHBULLET_SEND_TO_ALL:
+ # keyword is reserved for internal usage only; it's safe to remove
+ # it from the recipients list
+ recipients = ''
+
+ return '{schema}://{accesstoken}/{recipients}/?{args}'.format(
+ schema=self.secure_protocol,
+ accesstoken=self.quote(self.accesstoken, safe=''),
+ recipients=recipients,
+ args=self.urlencode(args))
+
@staticmethod
def parse_url(url):
"""
diff --git a/apprise/plugins/NotifyPushed.py b/apprise/plugins/NotifyPushed.py
index b925ba63..df5a72fe 100644
--- a/apprise/plugins/NotifyPushed.py
+++ b/apprise/plugins/NotifyPushed.py
@@ -26,6 +26,7 @@
import re
import requests
from json import dumps
+from itertools import chain
from .NotifyBase import NotifyBase
from .NotifyBase import HTTP_ERROR_MAP
@@ -256,6 +257,29 @@ class NotifyPushed(NotifyBase):
return True
+ def url(self):
+ """
+ Returns the URL built dynamically based on specified arguments.
+ """
+
+ # Define any arguments set
+ args = {
+ 'format': self.notify_format,
+ }
+
+ return '{schema}://{app_key}/{app_secret}/{targets}/?{args}'.format(
+ schema=self.secure_protocol,
+ app_key=self.quote(self.app_key, safe=''),
+ app_secret=self.quote(self.app_secret, safe=''),
+ targets='/'.join(
+ [self.quote(x) for x in chain(
+ # Channels are prefixed with a pound/hashtag symbol
+ ['#{}'.format(x) for x in self.channels],
+ # Users are prefixed with an @ symbol
+ ['@{}'.format(x) for x in self.users],
+ )]),
+ args=self.urlencode(args))
+
@staticmethod
def parse_url(url):
"""
diff --git a/apprise/plugins/NotifyPushjet/NotifyPushjet.py b/apprise/plugins/NotifyPushjet/NotifyPushjet.py
index cb9ddc94..f4bf6062 100644
--- a/apprise/plugins/NotifyPushjet/NotifyPushjet.py
+++ b/apprise/plugins/NotifyPushjet/NotifyPushjet.py
@@ -52,12 +52,15 @@ class NotifyPushjet(NotifyBase):
# A URL that takes you to the setup/help of the specific protocol
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_pushjet'
- def __init__(self, **kwargs):
+ def __init__(self, secret_key, **kwargs):
"""
Initialize Pushjet Object
"""
super(NotifyPushjet, self).__init__(**kwargs)
+ # store our key
+ self.secret_key = secret_key
+
def notify(self, title, body, notify_type):
"""
Perform Pushjet Notification
@@ -72,7 +75,7 @@ class NotifyPushjet(NotifyBase):
server += ":" + str(self.port)
api = pushjet.Api(server)
- service = api.Service(secret_key=self.user)
+ service = api.Service(secret_key=self.secret_key)
service.send(body, title)
self.logger.info('Sent Pushjet notification.')
@@ -84,6 +87,27 @@ class NotifyPushjet(NotifyBase):
return True
+ def url(self):
+ """
+ Returns the URL built dynamically based on specified arguments.
+ """
+
+ # Define any arguments set
+ args = {
+ 'format': self.notify_format,
+ }
+
+ default_port = 443 if self.secure else 80
+
+ return '{schema}://{secret_key}@{hostname}{port}/?{args}'.format(
+ schema=self.secure_protocol if self.secure else self.protocol,
+ secret_key=self.quote(self.secret_key, safe=''),
+ hostname=self.host,
+ port='' if self.port is None or self.port == default_port
+ else ':{}'.format(self.port),
+ args=self.urlencode(args),
+ )
+
@staticmethod
def parse_url(url):
"""
@@ -91,10 +115,10 @@ class NotifyPushjet(NotifyBase):
us to substantiate this object.
Syntax:
- pjet://secret@hostname
- pjet://secret@hostname:port
- pjets://secret@hostname
- pjets://secret@hostname:port
+ pjet://secret_key@hostname
+ pjet://secret_key@hostname:port
+ pjets://secret_key@hostname
+ pjets://secret_key@hostname:port
"""
results = NotifyBase.parse_url(url)
@@ -107,4 +131,7 @@ class NotifyPushjet(NotifyBase):
# a username is required
return None
+ # Store it as it's value
+ results['secret_key'] = results.get('user')
+
return results
diff --git a/apprise/plugins/NotifyPushover.py b/apprise/plugins/NotifyPushover.py
index fee093d7..72232f7b 100644
--- a/apprise/plugins/NotifyPushover.py
+++ b/apprise/plugins/NotifyPushover.py
@@ -237,6 +237,41 @@ class NotifyPushover(NotifyBase):
return not has_error
+ def url(self):
+ """
+ Returns the URL built dynamically based on specified arguments.
+ """
+
+ _map = {
+ PushoverPriority.LOW: 'low',
+ PushoverPriority.MODERATE: 'moderate',
+ PushoverPriority.NORMAL: 'normal',
+ PushoverPriority.HIGH: 'high',
+ PushoverPriority.EMERGENCY: 'emergency',
+ }
+
+ # Define any arguments set
+ args = {
+ 'format': self.notify_format,
+ 'priority':
+ _map[PushoverPriority.NORMAL] if self.priority not in _map
+ else _map[self.priority],
+ }
+
+ devices = '/'.join([self.quote(x) for x in self.devices])
+ if devices == PUSHOVER_SEND_TO_ALL:
+ # keyword is reserved for internal usage only; it's safe to remove
+ # it from the devices list
+ devices = ''
+
+ return '{schema}://{auth}{token}/{devices}/?{args}'.format(
+ schema=self.secure_protocol,
+ auth='' if not self.user
+ else '{user}@'.format(user=self.quote(self.user, safe='')),
+ token=self.quote(self.token, safe=''),
+ devices=devices,
+ args=self.urlencode(args))
+
@staticmethod
def parse_url(url):
"""
diff --git a/apprise/plugins/NotifyRocketChat.py b/apprise/plugins/NotifyRocketChat.py
index cb86b84d..899c8196 100644
--- a/apprise/plugins/NotifyRocketChat.py
+++ b/apprise/plugins/NotifyRocketChat.py
@@ -26,6 +26,7 @@
import re
import requests
from json import loads
+from itertools import chain
from .NotifyBase import NotifyBase
from .NotifyBase import HTTP_ERROR_MAP
@@ -106,6 +107,12 @@ class NotifyRocketChat(NotifyBase):
elif not isinstance(recipients, (set, tuple, list)):
recipients = []
+ if not (self.user and self.password):
+ # Username & Password is required for Rocket Chat to work
+ raise TypeError(
+ 'No Rocket.Chat user/pass combo specified.'
+ )
+
# Validate recipients and drop bad ones:
for recipient in recipients:
result = IS_CHANNEL.match(recipient)
@@ -133,6 +140,40 @@ class NotifyRocketChat(NotifyBase):
# Used to track token headers upon authentication (if successful)
self.headers = {}
+ def url(self):
+ """
+ Returns the URL built dynamically based on specified arguments.
+ """
+
+ # Define any arguments set
+ args = {
+ 'format': self.notify_format,
+ }
+
+ # Determine Authentication
+ auth = '{user}:{password}@'.format(
+ user=self.quote(self.user, safe=''),
+ password=self.quote(self.password, safe=''),
+ )
+
+ default_port = 443 if self.secure else 80
+
+ return '{schema}://{auth}{hostname}{port}/{targets}/?{args}'.format(
+ schema=self.secure_protocol if self.secure else self.protocol,
+ auth=auth,
+ hostname=self.host,
+ port='' if self.port is None or self.port == default_port
+ else ':{}'.format(self.port),
+ targets='/'.join(
+ [self.quote(x) for x in chain(
+ # Channels are prefixed with a pound/hashtag symbol
+ ['#{}'.format(x) for x in self.channels],
+ # Rooms are as is
+ self.rooms,
+ )]),
+ args=self.urlencode(args),
+ )
+
def notify(self, title, body, notify_type, **kwargs):
"""
wrapper to send_notification since we can alert more then one channel
diff --git a/apprise/plugins/NotifyRyver.py b/apprise/plugins/NotifyRyver.py
index c0f6b81c..49b17412 100644
--- a/apprise/plugins/NotifyRyver.py
+++ b/apprise/plugins/NotifyRyver.py
@@ -221,6 +221,32 @@ class NotifyRyver(NotifyBase):
return True
+ def url(self):
+ """
+ Returns the URL built dynamically based on specified arguments.
+ """
+
+ # Define any arguments set
+ args = {
+ 'format': self.notify_format,
+ 'webhook': self.webhook,
+ }
+
+ # Determine if there is a botname present
+ botname = ''
+ if self.user:
+ botname = '{botname}@'.format(
+ botname=self.quote(self.user, safe=''),
+ )
+
+ return '{schema}://{botname}{organization}/{token}/?{args}'.format(
+ schema=self.secure_protocol,
+ botname=botname,
+ organization=self.quote(self.organization, safe=''),
+ token=self.quote(self.token, safe=''),
+ args=self.urlencode(args),
+ )
+
@staticmethod
def parse_url(url):
"""
diff --git a/apprise/plugins/NotifySNS.py b/apprise/plugins/NotifySNS.py
index abc46f42..b29327f0 100644
--- a/apprise/plugins/NotifySNS.py
+++ b/apprise/plugins/NotifySNS.py
@@ -31,6 +31,7 @@ from hashlib import sha256
from datetime import datetime
from collections import OrderedDict
from xml.etree import ElementTree
+from itertools import chain
from .NotifyBase import NotifyBase
from .NotifyBase import HTTP_ERROR_MAP
@@ -58,8 +59,8 @@ LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+')
# region as a delimiter. This is a bit hacky; but it's much easier than having
# users of this product search though this Access Key Secret and escape all
# of the forward slashes!
-IS_REGION = re.compile(r'^\s*(?P[a-z]{2})-'
- r'(?P[a-z]+)-(?P[0-9]+)\s*$', re.I)
+IS_REGION = re.compile(
+ r'^\s*(?P[a-z]{2})-(?P[a-z]+)-(?P[0-9]+)\s*$', re.I)
# Extend HTTP Error Messages
AWS_HTTP_ERROR_MAP = HTTP_ERROR_MAP.copy()
@@ -521,6 +522,32 @@ class NotifySNS(NotifyBase):
return response
+ def url(self):
+ """
+ Returns the URL built dynamically based on specified arguments.
+ """
+
+ # Define any arguments set
+ args = {
+ 'format': self.notify_format,
+ }
+
+ return '{schema}://{key_id}/{key_secret}/{region}/{targets}/'\
+ '?{args}'.format(
+ schema=self.secure_protocol,
+ key_id=self.quote(self.aws_access_key_id, safe=''),
+ key_secret=self.quote(self.aws_secret_access_key, safe=''),
+ region=self.quote(self.aws_region_name, safe=''),
+ targets='/'.join(
+ [self.quote(x) for x in chain(
+ # Phone # are prefixed with a plus symbol
+ ['+{}'.format(x) for x in self.phone],
+ # Topics are prefixed with a pound/hashtag symbol
+ ['#{}'.format(x) for x in self.topics],
+ )]),
+ args=self.urlencode(args),
+ )
+
@staticmethod
def parse_url(url):
"""
diff --git a/apprise/plugins/NotifySlack.py b/apprise/plugins/NotifySlack.py
index ffa8a71a..430a33ff 100644
--- a/apprise/plugins/NotifySlack.py
+++ b/apprise/plugins/NotifySlack.py
@@ -141,7 +141,6 @@ class NotifySlack(NotifyBase):
if not self.user:
self.logger.warning(
'No user was specified; using %s.' % SLACK_DEFAULT_USER)
- self.user = SLACK_DEFAULT_USER
if compat_is_basestring(channels):
self.channels = [x for x in filter(bool, CHANNEL_LIST_DELIM.split(
@@ -231,7 +230,7 @@ class NotifySlack(NotifyBase):
# prepare JSON Object
payload = {
'channel': _channel,
- 'username': self.user,
+ 'username': self.user if self.user else SLACK_DEFAULT_USER,
# Use Markdown language
'mrkdwn': True,
'attachments': [{
@@ -297,6 +296,35 @@ class NotifySlack(NotifyBase):
return notify_okay
+ def url(self):
+ """
+ Returns the URL built dynamically based on specified arguments.
+ """
+
+ # Define any arguments set
+ args = {
+ 'format': self.notify_format,
+ }
+
+ # Determine if there is a botname present
+ botname = ''
+ if self.user:
+ botname = '{botname}@'.format(
+ botname=self.quote(self.user, safe=''),
+ )
+
+ return '{schema}://{botname}{token_a}/{token_b}/{token_c}/{targets}/'\
+ '?{args}'.format(
+ schema=self.secure_protocol,
+ botname=botname,
+ token_a=self.quote(self.token_a, safe=''),
+ token_b=self.quote(self.token_b, safe=''),
+ token_c=self.quote(self.token_c, safe=''),
+ targets='/'.join(
+ [self.quote(x, safe='') for x in self.channels]),
+ args=self.urlencode(args),
+ )
+
@staticmethod
def parse_url(url):
"""
diff --git a/apprise/plugins/NotifyTelegram.py b/apprise/plugins/NotifyTelegram.py
index 4ad54cc8..094665fe 100644
--- a/apprise/plugins/NotifyTelegram.py
+++ b/apprise/plugins/NotifyTelegram.py
@@ -60,8 +60,8 @@ from json import dumps
from .NotifyBase import NotifyBase
from .NotifyBase import HTTP_ERROR_MAP
from ..common import NotifyImageSize
-from ..utils import compat_is_basestring
from ..utils import parse_bool
+from ..utils import parse_list
from ..common import NotifyFormat
TELEGRAM_IMAGE_XY = NotifyImageSize.XY_256
@@ -81,9 +81,6 @@ IS_CHAT_ID_RE = re.compile(
re.IGNORECASE,
)
-# Used to break path apart into list of chat identifiers
-CHAT_ID_LIST_DELIM = re.compile(r'[ \t\r\n,#\\/]+')
-
class NotifyTelegram(NotifyBase):
"""
@@ -134,16 +131,8 @@ class NotifyTelegram(NotifyBase):
# Store our Bot Token
self.bot_token = result.group('key')
- if compat_is_basestring(chat_ids):
- self.chat_ids = [x for x in filter(bool, CHAT_ID_LIST_DELIM.split(
- chat_ids,
- ))]
-
- elif isinstance(chat_ids, (set, tuple, list)):
- self.chat_ids = list(chat_ids)
-
- else:
- self.chat_ids = list()
+ # Parse our list
+ self.chat_ids = parse_list(chat_ids)
if self.user:
# Treat this as a channel too
@@ -153,7 +142,7 @@ class NotifyTelegram(NotifyBase):
_id = self.detect_bot_owner()
if _id:
# Store our id
- self.chat_ids = [str(_id)]
+ self.chat_ids.append(str(_id))
if len(self.chat_ids) == 0:
self.logger.warning('No chat_id(s) were specified.')
@@ -501,6 +490,25 @@ class NotifyTelegram(NotifyBase):
return not has_error
+ def url(self):
+ """
+ Returns the URL built dynamically based on specified arguments.
+ """
+
+ # Define any arguments set
+ args = {
+ 'format': self.notify_format,
+ }
+
+ # No need to check the user token because the user automatically gets
+ # appended into the list of chat ids
+ return '{schema}://{bot_token}/{targets}/?{args}'.format(
+ schema=self.secure_protocol,
+ bot_token=self.quote(self.bot_token, safe=''),
+ targets='/'.join(
+ [self.quote('@{}'.format(x)) for x in self.chat_ids]),
+ args=self.urlencode(args))
+
@staticmethod
def parse_url(url):
"""
@@ -512,17 +520,17 @@ class NotifyTelegram(NotifyBase):
# tgram:// messages since the bot_token has a colon in it.
# It invalidates an normal URL.
- # This hack searches for this bogus URL and corrects it
- # so we can properly load it further down. The other
- # alternative is to ask users to actually change the colon
- # into a slash (which will work too), but it's more likely
- # to cause confusion... So this is the next best thing
+ # This hack searches for this bogus URL and corrects it so we can
+ # properly load it further down. The other alternative is to ask users
+ # to actually change the colon into a slash (which will work too), but
+ # it's more likely to cause confusion... So this is the next best thing
+ # we also check for %3A (incase the URL is encoded) as %3A == :
try:
tgram = re.match(
- r'(?P%s://)(bot)?(?P([a-z0-9_-]+)'
- r'(:[a-z0-9_-]+)?@)?(?P[0-9]+):+'
- r'(?P.*)$' % NotifyTelegram.secure_protocol,
- url, re.I)
+ r'(?P{schema}://)(bot)?(?P([a-z0-9_-]+)'
+ r'(:[a-z0-9_-]+)?@)?(?P[0-9]+)(:|%3A)+'
+ r'(?P.*)$'.format(
+ schema=NotifyTelegram.secure_protocol), url, re.I)
except (TypeError, AttributeError):
# url is bad; force tgram to be None
@@ -534,14 +542,11 @@ class NotifyTelegram(NotifyBase):
if tgram.group('prefix'):
# Try again
- results = NotifyBase.parse_url(
- '%s%s%s/%s' % (
- tgram.group('protocol'),
- tgram.group('prefix'),
- tgram.group('btoken_a'),
- tgram.group('remaining'),
- ),
- )
+ results = NotifyBase.parse_url('%s%s%s/%s' % (
+ tgram.group('protocol'),
+ tgram.group('prefix'),
+ tgram.group('btoken_a'),
+ tgram.group('remaining')))
else:
# Try again
@@ -562,9 +567,8 @@ class NotifyTelegram(NotifyBase):
bot_token = '%s:%s' % (bot_token_a, bot_token_b)
- chat_ids = ','.join(
- [x for x in filter(
- bool, NotifyBase.split_path(results['fullpath']))][1:])
+ chat_ids = [x for x in filter(
+ bool, NotifyBase.split_path(results['fullpath']))][1:]
# Store our bot token
results['bot_token'] = bot_token
diff --git a/apprise/plugins/NotifyTwitter/NotifyTwitter.py b/apprise/plugins/NotifyTwitter/NotifyTwitter.py
index df7dbdd8..394e5473 100644
--- a/apprise/plugins/NotifyTwitter/NotifyTwitter.py
+++ b/apprise/plugins/NotifyTwitter/NotifyTwitter.py
@@ -109,7 +109,9 @@ class NotifyTwitter(NotifyBase):
)
return False
- text = '%s\r\n%s' % (title, body)
+ # Only set title if it was specified
+ text = body if not title else '%s\r\n%s' % (title, body)
+
try:
# Get our API
api = tweepy.API(self.auth)
diff --git a/apprise/plugins/NotifyWindows.py b/apprise/plugins/NotifyWindows.py
index ea0f0e16..86fd6dc8 100644
--- a/apprise/plugins/NotifyWindows.py
+++ b/apprise/plugins/NotifyWindows.py
@@ -175,6 +175,13 @@ class NotifyWindows(NotifyBase):
return True
+ def url(self):
+ """
+ Returns the URL built dynamically based on specified arguments.
+ """
+
+ return '{schema}://'.format(schema=self.protocol)
+
@staticmethod
def parse_url(url):
"""
diff --git a/apprise/plugins/NotifyXBMC.py b/apprise/plugins/NotifyXBMC.py
index 264e8fad..fc46645f 100644
--- a/apprise/plugins/NotifyXBMC.py
+++ b/apprise/plugins/NotifyXBMC.py
@@ -44,11 +44,16 @@ class NotifyXBMC(NotifyBase):
# The services URL
service_url = 'http://kodi.tv/'
+ xbmc_protocol = 'xbmc'
+ xbmc_secure_protocol = 'xbmcs'
+ kodi_protocol = 'kodi'
+ kodi_secure_protocol = 'kodis'
+
# The default protocols
- protocol = ('xbmc', 'kodi')
+ protocol = (xbmc_protocol, kodi_protocol)
# The default secure protocols
- secure_protocol = ('xbmc', 'kodis')
+ secure_protocol = (xbmc_secure_protocol, kodi_secure_protocol)
# A URL that takes you to the setup/help of the specific protocol
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_kodi'
@@ -224,6 +229,44 @@ class NotifyXBMC(NotifyBase):
return True
+ def url(self):
+ """
+ Returns the URL built dynamically based on specified arguments.
+ """
+
+ # Define any arguments set
+ args = {
+ 'format': self.notify_format,
+ }
+
+ # Determine Authentication
+ auth = ''
+ if self.user and self.password:
+ auth = '{user}:{password}@'.format(
+ user=self.quote(self.user, safe=''),
+ password=self.quote(self.password, safe=''),
+ )
+ elif self.user:
+ auth = '{user}@'.format(
+ user=self.quote(self.user, safe=''),
+ )
+
+ default_schema = self.xbmc_protocol if (
+ self.protocol <= self.xbmc_remote_protocol) else self.kodi_protocol
+ default_port = 443 if self.secure else self.xbmc_default_port
+ if self.secure:
+ # Append 's' to schema
+ default_schema + 's'
+
+ return '{schema}://{auth}{hostname}{port}/?{args}'.format(
+ schema=default_schema,
+ auth=auth,
+ hostname=self.host,
+ port='' if not self.port or self.port == default_port
+ else ':{}'.format(self.port),
+ args=self.urlencode(args),
+ )
+
@staticmethod
def parse_url(url):
"""
diff --git a/apprise/plugins/NotifyXML.py b/apprise/plugins/NotifyXML.py
index 9b2f36b2..cad538db 100644
--- a/apprise/plugins/NotifyXML.py
+++ b/apprise/plugins/NotifyXML.py
@@ -85,6 +85,39 @@ class NotifyXML(NotifyBase):
return
+ def url(self):
+ """
+ Returns the URL built dynamically based on specified arguments.
+ """
+
+ # Define any arguments set
+ args = {
+ 'format': self.notify_format,
+ }
+
+ # Determine Authentication
+ auth = ''
+ if self.user and self.password:
+ auth = '{user}:{password}@'.format(
+ user=self.quote(self.user, safe=''),
+ password=self.quote(self.password, safe=''),
+ )
+ elif self.user:
+ auth = '{user}@'.format(
+ user=self.quote(self.user, safe=''),
+ )
+
+ default_port = 443 if self.secure else 80
+
+ return '{schema}://{auth}{hostname}{port}/?{args}'.format(
+ schema=self.secure_protocol if self.secure else self.protocol,
+ auth=auth,
+ hostname=self.host,
+ port='' if self.port is None or self.port == default_port
+ else ':{}'.format(self.port),
+ args=self.urlencode(args),
+ )
+
def notify(self, title, body, notify_type, **kwargs):
"""
Perform XML Notification
diff --git a/apprise/utils.py b/apprise/utils.py
index 091c7e88..420cf2b5 100644
--- a/apprise/utils.py
+++ b/apprise/utils.py
@@ -397,6 +397,10 @@ def parse_list(*args):
elif isinstance(arg, (set, list, tuple)):
result += parse_list(*arg)
+ elif arg is None:
+ # Ignore
+ continue
+
else:
# Convert whatever it is to a string and work with it
result += parse_list(str(arg))
diff --git a/test/test_api.py b/test/test_api.py
index fa6ffada..bf499a65 100644
--- a/test/test_api.py
+++ b/test/test_api.py
@@ -71,15 +71,34 @@ def test_apprise():
a = Apprise(servers=servers)
- # 3 servers loaded
+ # 2 servers loaded
assert(len(a) == 2)
+ # We can retrieve our URLs this way:
+ assert(len(a.urls()) == 2)
+
# We can add another server
assert(
a.add('mmosts://mattermost.server.local/'
'3ccdd113474722377935511fc85d3dd4') is True)
assert(len(a) == 3)
+ # We can pop an object off of our stack by it's indexed value:
+ obj = a.pop(0)
+ assert(isinstance(obj, NotifyBase) is True)
+ assert(len(a) == 2)
+
+ # We can retrieve elements from our list too by reference:
+ assert(compat_is_basestring(a[0].url()) is True)
+
+ # We can iterate over our list too:
+ count = 0
+ for o in a:
+ assert(compat_is_basestring(o.url()) is True)
+ count += 1
+ # verify that we did indeed iterate over each element
+ assert(len(a) == count)
+
# We can empty our set
a.clear()
assert(len(a) == 0)
diff --git a/test/test_email_plugin.py b/test/test_email_plugin.py
index bbd89c8d..80d108f4 100644
--- a/test/test_email_plugin.py
+++ b/test/test_email_plugin.py
@@ -26,6 +26,7 @@
from apprise import plugins
from apprise import NotifyType
from apprise import Apprise
+from apprise.utils import compat_is_basestring
from apprise.plugins import NotifyEmailBase
import smtplib
@@ -49,7 +50,7 @@ TEST_URLS = (
# No Username
('mailtos://:pass@nuxref.com:567', {
# Can't prepare a To address using this expression
- 'exception': TypeError,
+ 'instance': TypeError,
}),
# Pre-Configured Email Services
@@ -115,27 +116,27 @@ TEST_URLS = (
}),
# Invalid From Address
('mailtos://user:pass@nuxref.com?from=@', {
- 'exception': TypeError,
+ 'instance': TypeError,
}),
# Invalid From Address
('mailtos://nuxref.com?user=&pass=.', {
- 'exception': TypeError,
+ 'instance': TypeError,
}),
# Invalid To Address
('mailtos://user:pass@nuxref.com?to=@', {
- 'exception': TypeError,
+ 'instance': TypeError,
}),
# Valid URL, but can't structure a proper email
('mailtos://nuxref.com?user=%20!&pass=.', {
- 'exception': TypeError,
+ 'instance': TypeError,
}),
# Invalid From (and To) Address
('mailtos://nuxref.com?to=test', {
- 'exception': TypeError,
+ 'instance': TypeError,
}),
# Invalid Secure Mode
('mailtos://user:pass@example.com?mode=notamode', {
- 'exception': TypeError,
+ 'instance': TypeError,
}),
# STARTTLS flag checking
('mailtos://user:pass@gmail.com?mode=starttls', {
@@ -172,9 +173,6 @@ def test_email_plugin(mock_smtp, mock_smtpssl):
# Our expected instance
instance = meta.get('instance', None)
- # Our expected exception
- exception = meta.get('exception', None)
-
# Our expected server objects
self = meta.get('self', None)
@@ -217,19 +215,37 @@ def test_email_plugin(mock_smtp, mock_smtpssl):
try:
obj = Apprise.instantiate(url, suppress_exceptions=False)
- assert(exception is None)
-
if obj is None:
- # We're done
+ # We're done (assuming this is what we were expecting)
+ assert instance is None
continue
if instance is None:
# Expected None but didn't get it
- print('%s instantiated %s' % (url, str(obj)))
+ print('%s instantiated %s (but expected None)' % (
+ url, str(obj)))
assert(False)
assert(isinstance(obj, instance))
+ if isinstance(obj, plugins.NotifyBase.NotifyBase):
+ # We loaded okay; now lets make sure we can reverse this url
+ assert(compat_is_basestring(obj.url()) is True)
+
+ # Instantiate the exact same object again using the URL from
+ # the one that was already created properly
+ obj_cmp = Apprise.instantiate(obj.url())
+
+ # Our object should be the same instance as what we had
+ # originally expected above.
+ if not isinstance(obj_cmp, plugins.NotifyBase.NotifyBase):
+ # Assert messages are hard to trace back with the way
+ # these tests work. Just printing before throwing our
+ # assertion failure makes things easier to debug later on
+ print('TEST FAIL: {} regenerated as {}'.format(
+ url, obj.url()))
+ assert(False)
+
if self:
# Iterate over our expected entries inside of our object
for key, val in self.items():
@@ -258,16 +274,17 @@ def test_email_plugin(mock_smtp, mock_smtpssl):
except Exception as e:
# We can't handle this exception type
- print('%s / %s' % (url, str(e)))
- assert False
+ raise
except AssertionError:
# Don't mess with these entries
+ print('%s AssertionError' % url)
raise
except Exception as e:
# Check that we were expecting this exception to happen
- assert isinstance(e, response)
+ if not isinstance(e, response):
+ raise
except AssertionError:
# Don't mess with these entries
@@ -276,9 +293,11 @@ def test_email_plugin(mock_smtp, mock_smtpssl):
except Exception as e:
# Handle our exception
- print('%s / %s' % (url, str(e)))
- assert(exception is not None)
- assert(isinstance(e, exception))
+ if(instance is None):
+ raise
+
+ if not isinstance(e, instance):
+ raise
@mock.patch('smtplib.SMTP')
diff --git a/test/test_glib_plugin.py b/test/test_glib_plugin.py
index 98d5e6b4..c2650f1a 100644
--- a/test/test_glib_plugin.py
+++ b/test/test_glib_plugin.py
@@ -28,6 +28,7 @@ import mock
import sys
import types
import apprise
+from apprise.utils import compat_is_basestring
try:
# Python v3.4+
@@ -223,6 +224,9 @@ def test_dbus_plugin(mock_mainloop, mock_byte, mock_bytearray,
assert(isinstance(obj, apprise.plugins.NotifyDBus) is True)
obj.duration = 0
+ # Test url() call
+ assert(compat_is_basestring(obj.url()) is True)
+
# Our notification succeeds even though the gi library was not loaded
assert(obj.notify(title='title', body='body',
notify_type=apprise.NotifyType.INFO) is True)
diff --git a/test/test_gnome_plugin.py b/test/test_gnome_plugin.py
index 150293c9..ef66e8d2 100644
--- a/test/test_gnome_plugin.py
+++ b/test/test_gnome_plugin.py
@@ -28,6 +28,7 @@ import sys
import types
import apprise
+from apprise.utils import compat_is_basestring
try:
# Python v3.4+
@@ -113,6 +114,9 @@ def test_gnome_plugin():
# Check that it found our mocked environments
assert(obj._enabled is True)
+ # Test url() call
+ assert(compat_is_basestring(obj.url()) is True)
+
# test notifications
assert(obj.notify(title='title', body='body',
notify_type=apprise.NotifyType.INFO) is True)
diff --git a/test/test_growl_plugin.py b/test/test_growl_plugin.py
index 289f726f..295ec5fb 100644
--- a/test/test_growl_plugin.py
+++ b/test/test_growl_plugin.py
@@ -26,6 +26,8 @@
from apprise import plugins
from apprise import NotifyType
from apprise import Apprise
+from apprise.utils import compat_is_basestring
+
import mock
import re
@@ -195,7 +197,6 @@ def test_growl_plugin(mock_gntp):
except Exception as e:
# We can't handle this exception type
- print('%s / %s' % (url, str(e)))
assert False
# We're done this part of the test
@@ -216,10 +217,27 @@ def test_growl_plugin(mock_gntp):
if instance is None:
# Expected None but didn't get it
- print('%s instantiated %s' % (url, str(obj)))
assert(False)
- assert(isinstance(obj, instance))
+ assert(isinstance(obj, instance) is True)
+
+ if isinstance(obj, plugins.NotifyBase.NotifyBase):
+ # We loaded okay; now lets make sure we can reverse this url
+ assert(compat_is_basestring(obj.url()) is True)
+
+ # Instantiate the exact same object again using the URL from
+ # the one that was already created properly
+ obj_cmp = Apprise.instantiate(obj.url())
+
+ # Our object should be the same instance as what we had
+ # originally expected above.
+ if not isinstance(obj_cmp, plugins.NotifyBase.NotifyBase):
+ # Assert messages are hard to trace back with the way
+ # these tests work. Just printing before throwing our
+ # assertion failure makes things easier to debug later on
+ print('TEST FAIL: {} regenerated as {}'.format(
+ url, obj.url()))
+ assert(False)
if self:
# Iterate over our expected entries inside of our object
diff --git a/test/test_notify_base.py b/test/test_notify_base.py
index dc3ee937..1afd328c 100644
--- a/test/test_notify_base.py
+++ b/test/test_notify_base.py
@@ -52,6 +52,16 @@ def test_notify_base():
nb = NotifyBase(port=10)
assert nb.port == 10
+ try:
+ nb.url()
+ assert False
+
+ except NotImplementedError:
+ # Each sub-module is that inherits this as a parent is required to
+ # over-ride this function. So direct calls to this throws a not
+ # implemented error intentionally
+ assert True
+
# Throttle overrides..
nb = NotifyBase()
nb.throttle_attempt = 0.0
diff --git a/test/test_pushjet_plugin.py b/test/test_pushjet_plugin.py
index 789d4844..f2d19028 100644
--- a/test/test_pushjet_plugin.py
+++ b/test/test_pushjet_plugin.py
@@ -26,6 +26,8 @@
from apprise import plugins
from apprise import NotifyType
from apprise import Apprise
+from apprise.utils import compat_is_basestring
+
import mock
TEST_URLS = (
@@ -104,13 +106,37 @@ def test_plugin(mock_refresh, mock_send):
try:
obj = Apprise.instantiate(url, suppress_exceptions=False)
- if instance is None:
- # Check that we got what we came for
- assert obj is instance
+ if obj is None:
+ # We're done (assuming this is what we were expecting)
+ assert instance is None
continue
+ if instance is None:
+ # Expected None but didn't get it
+ print('%s instantiated %s (but expected None)' % (
+ url, str(obj)))
+ assert(False)
+
assert(isinstance(obj, instance))
+ if isinstance(obj, plugins.NotifyBase.NotifyBase):
+ # We loaded okay; now lets make sure we can reverse this url
+ assert(compat_is_basestring(obj.url()) is True)
+
+ # Instantiate the exact same object again using the URL from
+ # the one that was already created properly
+ obj_cmp = Apprise.instantiate(obj.url())
+
+ # Our object should be the same instance as what we had
+ # originally expected above.
+ if not isinstance(obj_cmp, plugins.NotifyBase.NotifyBase):
+ # Assert messages are hard to trace back with the way
+ # these tests work. Just printing before throwing our
+ # assertion failure makes things easier to debug later on
+ print('TEST FAIL: {} regenerated as {}'.format(
+ url, obj.url()))
+ assert(False)
+
if self:
# Iterate over our expected entries inside of our object
for key, val in self.items():
@@ -144,21 +170,27 @@ def test_plugin(mock_refresh, mock_send):
except Exception as e:
# We can't handle this exception type
- assert False
+ raise
except AssertionError:
# Don't mess with these entries
+ print('%s AssertionError' % url)
raise
except Exception as e:
# Check that we were expecting this exception to happen
- assert isinstance(e, response)
+ if not isinstance(e, response):
+ raise
except AssertionError:
# Don't mess with these entries
+ print('%s AssertionError' % url)
raise
except Exception as e:
# Handle our exception
- assert(instance is not None)
- assert(isinstance(e, instance))
+ if(instance is None):
+ raise
+
+ if not isinstance(e, instance):
+ raise
diff --git a/test/test_rest_plugins.py b/test/test_rest_plugins.py
index bee5fbea..f0f6d29a 100644
--- a/test/test_rest_plugins.py
+++ b/test/test_rest_plugins.py
@@ -263,42 +263,13 @@ TEST_URLS = (
('ifttt://EventID/', {
'instance': TypeError,
}),
- # Value1 gets assigned Entry1
- # Title =
- # Body =
- ('ifttt://WebHookID@EventID/Entry1/', {
- 'instance': plugins.NotifyIFTTT,
- }),
- # Value1, Value2, and Value2, the below assigns:
- # Value1 = Entry1
- # Value2 = AnotherEntry
- # Value3 = ThirdValue
- # Title =
- # Body =
- ('ifttt://WebHookID@EventID/Entry1/AnotherEntry/ThirdValue', {
- 'instance': plugins.NotifyIFTTT,
- }),
- # Mix and match content, the below assigns:
- # Value1 = FirstValue
- # AnotherKey = Hello
- # Value5 = test
- # Title =
- # Body =
- ('ifttt://WebHookID@EventID/FirstValue/?AnotherKey=Hello&Value5=test', {
- 'instance': plugins.NotifyIFTTT,
- }),
- # This would assign:
- # Value1 = FirstValue
- # Title = - disable the one passed by the notify call
- # Body = - disable the one passed by the notify call
- # The idea here is maybe you just want to use the apprise IFTTTT hook
- # to trigger something and not nessisarily pass text along to it
- ('ifttt://WebHookID@EventID/FirstValue/?Title=&Body=', {
- 'instance': plugins.NotifyIFTTT,
- }),
('ifttt://:@/', {
'instance': None,
}),
+ # A nicely formed ifttt url with 2 events defined:
+ ('ifttt://WebHookID@EventID/EventID2/', {
+ 'instance': plugins.NotifyIFTTT,
+ }),
# Test website connection failures
('ifttt://WebHookID@EventID', {
'instance': plugins.NotifyIFTTT,
@@ -392,6 +363,9 @@ TEST_URLS = (
('json://user:pass@localhost', {
'instance': plugins.NotifyJSON,
}),
+ ('json://user@localhost', {
+ 'instance': plugins.NotifyJSON,
+ }),
('json://localhost:8080', {
'instance': plugins.NotifyJSON,
}),
@@ -529,8 +503,8 @@ TEST_URLS = (
('matrix://user@localhost/%s' % ('a' * 64), {
'instance': plugins.NotifyMatrix,
}),
- # Name, port and token (secure)
- ('matrixs://user@localhost:9000/%s' % ('a' * 64), {
+ # port and token (secure)
+ ('matrixs://localhost:9000/%s' % ('a' * 64), {
'instance': plugins.NotifyMatrix,
}),
# Name, port, token and slack mode
@@ -963,6 +937,14 @@ TEST_URLS = (
('rocket://user:pass@localhost/#/!/@', {
'instance': TypeError,
}),
+ # No user/pass combo
+ ('rocket://user@localhost/room/', {
+ 'instance': TypeError,
+ }),
+ # No user/pass combo
+ ('rocket://localhost/room/', {
+ 'instance': TypeError,
+ }),
# A room and port identifier
('rocket://user:pass@localhost:8080/room/', {
'instance': plugins.NotifyRocketChat,
@@ -1383,7 +1365,7 @@ TEST_URLS = (
('xbmc://user:pass@localhost:8080', {
'instance': plugins.NotifyXBMC,
}),
- ('xbmc://localhost', {
+ ('xbmc://user@localhost', {
'instance': plugins.NotifyXBMC,
# don't include an image by default
'include_image': False,
@@ -1432,6 +1414,9 @@ TEST_URLS = (
('xml://localhost', {
'instance': plugins.NotifyXML,
}),
+ ('xml://user@localhost', {
+ 'instance': plugins.NotifyXML,
+ }),
('xml://user:pass@localhost', {
'instance': plugins.NotifyXML,
}),
@@ -1487,6 +1472,8 @@ def test_rest_plugins(mock_post, mock_get):
API: REST Based Plugins()
"""
+ # Disable Throttling to speed testing
+ plugins.NotifyBase.NotifyBase.throttle_attempt = 0
# iterate over our dictionary and test it out
for (url, meta) in TEST_URLS:
@@ -1567,10 +1554,31 @@ def test_rest_plugins(mock_post, mock_get):
assert instance is None
continue
+ if instance is None:
+ # Expected None but didn't get it
+ print('%s instantiated %s (but expected None)' % (
+ url, str(obj)))
+ assert(False)
+
assert(isinstance(obj, instance))
- # Disable throttling to speed up unit tests
- obj.throttle_attempt = 0
+ if isinstance(obj, plugins.NotifyBase.NotifyBase):
+ # We loaded okay; now lets make sure we can reverse this url
+ assert(compat_is_basestring(obj.url()) is True)
+
+ # Instantiate the exact same object again using the URL from
+ # the one that was already created properly
+ obj_cmp = Apprise.instantiate(obj.url())
+
+ # Our object should be the same instance as what we had
+ # originally expected above.
+ if not isinstance(obj_cmp, plugins.NotifyBase.NotifyBase):
+ # Assert messages are hard to trace back with the way
+ # these tests work. Just printing before throwing our
+ # assertion failure makes things easier to debug later on
+ print('TEST FAIL: {} regenerated as {}'.format(
+ url, obj.url()))
+ assert(False)
if self:
# Iterate over our expected entries inside of our object
@@ -1605,8 +1613,7 @@ def test_rest_plugins(mock_post, mock_get):
except Exception as e:
# We can't handle this exception type
- print('%s / %s' % (url, str(e)))
- assert False
+ raise
except AssertionError:
# Don't mess with these entries
@@ -1615,7 +1622,8 @@ def test_rest_plugins(mock_post, mock_get):
except Exception as e:
# Check that we were expecting this exception to happen
- assert isinstance(e, response)
+ if not isinstance(e, response):
+ raise
#
# Stage 2: without title defined
@@ -1643,8 +1651,7 @@ def test_rest_plugins(mock_post, mock_get):
except Exception as e:
# We can't handle this exception type
- print('%s / %s' % (url, str(e)))
- assert False
+ raise
except AssertionError:
# Don't mess with these entries
@@ -1653,7 +1660,8 @@ def test_rest_plugins(mock_post, mock_get):
except Exception as e:
# Check that we were expecting this exception to happen
- assert isinstance(e, response)
+ if not isinstance(e, response):
+ raise
except AssertionError:
# Don't mess with these entries
@@ -1662,9 +1670,11 @@ def test_rest_plugins(mock_post, mock_get):
except Exception as e:
# Handle our exception
- print('%s / %s' % (url, str(e)))
- assert(instance is not None)
- assert(isinstance(e, instance))
+ if(instance is None):
+ raise
+
+ if not isinstance(e, instance):
+ raise
@mock.patch('requests.get')
@@ -1674,6 +1684,9 @@ def test_notify_boxcar_plugin(mock_post, mock_get):
API: NotifyBoxcar() Extra Checks
"""
+ # Disable Throttling to speed testing
+ plugins.NotifyBase.NotifyBase.throttle_attempt = 0
+
# Generate some generic message types
device = 'A' * 64
tag = '@B' * 63
@@ -1724,9 +1737,6 @@ def test_notify_boxcar_plugin(mock_post, mock_get):
# Test notifications without a body or a title
p = plugins.NotifyBoxcar(access=access, secret=secret, recipients=None)
- # Disable throttling to speed up unit tests
- p.throttle_attempt = 0
-
p.notify(body=None, title=None, notify_type=NotifyType.INFO) is True
@@ -1737,6 +1747,8 @@ def test_notify_discord_plugin(mock_post, mock_get):
API: NotifyDiscord() Extra Checks
"""
+ # Disable Throttling to speed testing
+ plugins.NotifyBase.NotifyBase.throttle_attempt = 0
# Initialize some generic (but valid) tokens
webhook_id = 'A' * 24
@@ -1762,9 +1774,6 @@ def test_notify_discord_plugin(mock_post, mock_get):
webhook_token=webhook_token,
footer=True, thumbnail=False)
- # Disable throttling to speed up unit tests
- obj.throttle_attempt = 0
-
# This call includes an image with it's payload:
assert obj.notify(title='title', body='body',
notify_type=NotifyType.INFO) is True
@@ -2190,10 +2199,12 @@ def test_notify_ifttt_plugin(mock_post, mock_get):
API: NotifyIFTTT() Extra Checks
"""
+ # Disable Throttling to speed testing
+ plugins.NotifyBase.NotifyBase.throttle_attempt = 0
# Initialize some generic (but valid) tokens
- apikey = 'webhookid'
- event = 'event'
+ webhook_id = 'webhookid'
+ events = ['event1', 'event2']
# Prepare Mock
mock_get.return_value = requests.Request()
@@ -2204,7 +2215,7 @@ def test_notify_ifttt_plugin(mock_post, mock_get):
mock_post.return_value.content = '{}'
try:
- obj = plugins.NotifyIFTTT(apikey=apikey, event=None, event_args=None)
+ obj = plugins.NotifyIFTTT(webhook_id=webhook_id, events=None)
# No token specified
assert(False)
@@ -2212,12 +2223,8 @@ def test_notify_ifttt_plugin(mock_post, mock_get):
# Exception should be thrown about the fact no token was specified
assert(True)
- obj = plugins.NotifyIFTTT(apikey=apikey, event=event, event_args=None)
+ obj = plugins.NotifyIFTTT(webhook_id=webhook_id, events=events)
assert(isinstance(obj, plugins.NotifyIFTTT))
- assert(len(obj.event_args) == 0)
-
- # Disable throttling to speed up unit tests
- obj.throttle_attempt = 0
assert obj.notify(title='title', body='body',
notify_type=NotifyType.INFO) is True
@@ -2230,6 +2237,9 @@ def test_notify_join_plugin(mock_post, mock_get):
API: NotifyJoin() Extra Checks
"""
+ # Disable Throttling to speed testing
+ plugins.NotifyBase.NotifyBase.throttle_attempt = 0
+
# Generate some generic message types
device = 'A' * 32
group = 'group.chrome'
@@ -2250,9 +2260,6 @@ def test_notify_join_plugin(mock_post, mock_get):
mock_post.return_value.status_code = requests.codes.created
mock_get.return_value.status_code = requests.codes.created
- # Disable throttling to speed up unit tests
- p.throttle_attempt = 0
-
# Test notifications without a body or a title; nothing to send
# so we return False
p.notify(body=None, title=None, notify_type=NotifyType.INFO) is False
@@ -2265,6 +2272,8 @@ def test_notify_slack_plugin(mock_post, mock_get):
API: NotifySlack() Extra Checks
"""
+ # Disable Throttling to speed testing
+ plugins.NotifyBase.NotifyBase.throttle_attempt = 0
# Initialize some generic (but valid) tokens
token_a = 'A' * 9
@@ -2300,9 +2309,6 @@ def test_notify_slack_plugin(mock_post, mock_get):
token_a=token_a, token_b=token_b, token_c=token_c, channels=channels,
include_image=True)
- # Disable throttling to speed up unit tests
- obj.throttle_attempt = 0
-
# This call includes an image with it's payload:
assert obj.notify(title='title', body='body',
notify_type=NotifyType.INFO) is True
@@ -2358,6 +2364,9 @@ def test_notify_pushed_plugin(mock_post, mock_get):
API: NotifyPushed() Extra Checks
"""
+ # Disable Throttling to speed testing
+ plugins.NotifyBase.NotifyBase.throttle_attempt = 0
+
# Chat ID
recipients = '@ABCDEFG, @DEFGHIJ, #channel, #channel2'
@@ -2436,9 +2445,6 @@ def test_notify_pushed_plugin(mock_post, mock_get):
assert(len(obj.channels) == 2)
assert(len(obj.users) == 2)
- # Disable throttling to speed up unit tests
- obj.throttle_attempt = 0
-
# Support the handling of an empty and invalid URL strings
assert plugins.NotifyPushed.parse_url(None) is None
assert plugins.NotifyPushed.parse_url('') is None
@@ -2458,6 +2464,8 @@ def test_notify_pushover_plugin(mock_post, mock_get):
API: NotifyPushover() Extra Checks
"""
+ # Disable Throttling to speed testing
+ plugins.NotifyBase.NotifyBase.throttle_attempt = 0
# Initialize some generic (but valid) tokens
token = 'a' * 30
@@ -2487,9 +2495,6 @@ def test_notify_pushover_plugin(mock_post, mock_get):
assert(isinstance(obj, plugins.NotifyPushover))
assert(len(obj.devices) == 3)
- # Disable throttling to speed up unit tests
- obj.throttle_attempt = 0
-
# This call fails because there is 1 invalid device
assert obj.notify(title='title', body='body',
notify_type=NotifyType.INFO) is False
@@ -2500,9 +2505,6 @@ def test_notify_pushover_plugin(mock_post, mock_get):
# device defined here
assert(len(obj.devices) == 1)
- # Disable throttling to speed up unit tests
- obj.throttle_attempt = 0
-
# This call succeeds because all of the devices are valid
assert obj.notify(title='title', body='body',
notify_type=NotifyType.INFO) is True
@@ -2526,9 +2528,16 @@ def test_notify_rocketchat_plugin(mock_post, mock_get):
API: NotifyRocketChat() Extra Checks
"""
+ # Disable Throttling to speed testing
+ plugins.NotifyBase.NotifyBase.throttle_attempt = 0
+
# Chat ID
recipients = 'l2g, lead2gold, #channel, #channel2'
+ # Authentication
+ user = 'myuser'
+ password = 'mypass'
+
# Prepare Mock
mock_get.return_value = requests.Request()
mock_post.return_value = requests.Request()
@@ -2538,7 +2547,8 @@ def test_notify_rocketchat_plugin(mock_post, mock_get):
mock_get.return_value.text = ''
try:
- obj = plugins.NotifyRocketChat(recipients=None)
+ obj = plugins.NotifyRocketChat(
+ user=user, password=password, recipients=None)
# invalid recipients list (None)
assert(False)
@@ -2548,7 +2558,8 @@ def test_notify_rocketchat_plugin(mock_post, mock_get):
assert(True)
try:
- obj = plugins.NotifyRocketChat(recipients=object())
+ obj = plugins.NotifyRocketChat(
+ user=user, password=password, recipients=object())
# invalid recipients list (object)
assert(False)
@@ -2558,7 +2569,8 @@ def test_notify_rocketchat_plugin(mock_post, mock_get):
assert(True)
try:
- obj = plugins.NotifyRocketChat(recipients=set())
+ obj = plugins.NotifyRocketChat(
+ user=user, password=password, recipients=set())
# invalid recipient list/set (no entries)
assert(False)
@@ -2567,14 +2579,12 @@ def test_notify_rocketchat_plugin(mock_post, mock_get):
# specified
assert(True)
- obj = plugins.NotifyRocketChat(recipients=recipients)
+ obj = plugins.NotifyRocketChat(
+ user=user, password=password, recipients=recipients)
assert(isinstance(obj, plugins.NotifyRocketChat))
assert(len(obj.channels) == 2)
assert(len(obj.rooms) == 2)
- # Disable throttling to speed up unit tests
- obj.throttle_attempt = 0
-
#
# Logout
#
@@ -2653,6 +2663,9 @@ def test_notify_telegram_plugin(mock_post, mock_get):
API: NotifyTelegram() Extra Checks
"""
+ # Disable Throttling to speed testing
+ plugins.NotifyBase.NotifyBase.throttle_attempt = 0
+
# Bot Token
bot_token = '123456789:abcdefg_hijklmnop'
invalid_bot_token = 'abcd:123'
@@ -2710,6 +2723,12 @@ def test_notify_telegram_plugin(mock_post, mock_get):
assert(isinstance(obj, plugins.NotifyTelegram))
assert(len(obj.chat_ids) == 2)
+ # test url call
+ assert(compat_is_basestring(obj.url()))
+ # Test that we can load the string we generate back:
+ obj = plugins.NotifyTelegram(**plugins.NotifyTelegram.parse_url(obj.url()))
+ assert(isinstance(obj, plugins.NotifyTelegram))
+
# Support the handling of an empty and invalid URL strings
assert(plugins.NotifyTelegram.parse_url(None) is None)
assert(plugins.NotifyTelegram.parse_url('') is None)
@@ -2730,10 +2749,6 @@ def test_notify_telegram_plugin(mock_post, mock_get):
nimg_obj = plugins.NotifyTelegram(bot_token=bot_token, chat_ids=chat_ids)
nimg_obj.asset = AppriseAsset(image_path_mask=False, image_url_mask=False)
- # Disable throttling to speed up unit tests
- nimg_obj.throttle_attempt = 0
- obj.throttle_attempt = 0
-
# Test that our default settings over-ride base settings since they are
# not the same as the one specified in the base; this check merely
# ensures our plugin inheritance is working properly
diff --git a/test/test_windows_plugin.py b/test/test_windows_plugin.py
index 1c577ede..bfe52632 100644
--- a/test/test_windows_plugin.py
+++ b/test/test_windows_plugin.py
@@ -29,6 +29,7 @@ import types
# Rebuild our Apprise environment
import apprise
+from apprise.utils import compat_is_basestring
try:
# Python v3.4+
@@ -107,6 +108,9 @@ def test_windows_plugin():
obj = apprise.Apprise.instantiate('windows://', suppress_exceptions=False)
obj.duration = 0
+ # Test URL functionality
+ assert(compat_is_basestring(obj.url()) is True)
+
# Check that it found our mocked environments
assert(obj._enabled is True)