mirror of https://github.com/caronc/apprise
				
				
				
			Merge branch 'master' into 46-decommission-palot
						commit
						d943a7c0a0
					
				| 
						 | 
					@ -44,12 +44,12 @@ The table below identifies the services this tool supports and some example serv
 | 
				
			||||||
| [Mattermost](https://github.com/caronc/apprise/wiki/Notify_mattermost) | mmost://  | (TCP) 8065 | mmost://hostname/authkey<br />mmost://hostname:80/authkey<br />mmost://user@hostname:80/authkey<br />mmost://hostname/authkey?channel=channel<br />mmosts://hostname/authkey<br />mmosts://user@hostname/authkey<br />
 | 
					| [Mattermost](https://github.com/caronc/apprise/wiki/Notify_mattermost) | mmost://  | (TCP) 8065 | mmost://hostname/authkey<br />mmost://hostname:80/authkey<br />mmost://user@hostname:80/authkey<br />mmost://hostname/authkey?channel=channel<br />mmosts://hostname/authkey<br />mmosts://user@hostname/authkey<br />
 | 
				
			||||||
| [Prowl](https://github.com/caronc/apprise/wiki/Notify_prowl) | prowl://   | (TCP) 443    | prowl://apikey<br />prowl://apikey/providerkey
 | 
					| [Prowl](https://github.com/caronc/apprise/wiki/Notify_prowl) | prowl://   | (TCP) 443    | prowl://apikey<br />prowl://apikey/providerkey
 | 
				
			||||||
| [PushBullet](https://github.com/caronc/apprise/wiki/Notify_pushbullet) | pbul://    | (TCP) 443    | pbul://accesstoken<br />pbul://accesstoken/#channel<br/>pbul://accesstoken/A_DEVICE_ID<br />pbul://accesstoken/email@address.com<br />pbul://accesstoken/#channel/#channel2/email@address.net/DEVICE
 | 
					| [PushBullet](https://github.com/caronc/apprise/wiki/Notify_pushbullet) | pbul://    | (TCP) 443    | pbul://accesstoken<br />pbul://accesstoken/#channel<br/>pbul://accesstoken/A_DEVICE_ID<br />pbul://accesstoken/email@address.com<br />pbul://accesstoken/#channel/#channel2/email@address.net/DEVICE
 | 
				
			||||||
| [Pushjet](https://github.com/caronc/apprise/wiki/Notify_pushjet) | pjet://  | (TCP) 80   | pjet://secret<br />pjet://secret@hostname<br />pjet://secret@hostname:port<br />pjets://secret@hostname<br />pjets://secret@hostname:port<br /><i>Note: if no hostname defined https://api.pushjet.io will be used
 | 
					| [Pushjet](https://github.com/caronc/apprise/wiki/Notify_pushjet) | pjet://  | (TCP) 80   | pjet://secret@hostname<br />pjet://secret@hostname:port<br />pjets://secret@hostname<br />pjets://secret@hostname:port
 | 
				
			||||||
 | 
					| [Pushed](https://github.com/caronc/apprise/wiki/Notify_pushed) | pushed://    | (TCP) 443    | pushed://appkey/appsecret/<br/>pushed://appkey/appsecret/#ChannelAlias<br/>pushed://appkey/appsecret/#ChannelAlias1/#ChannelAlias2/#ChannelAliasN<br/>pushed://appkey/appsecret/@UserPushedID<br/>pushed://appkey/appsecret/@UserPushedID1/@UserPushedID2/@UserPushedIDN
 | 
				
			||||||
| [Pushover](https://github.com/caronc/apprise/wiki/Notify_pushover)  | pover://   | (TCP) 443   | pover://user@token<br />pover://user@token/DEVICE<br />pover://user@token/DEVICE1/DEVICE2/DEVICEN<br />_Note: you must specify both your user_id and token_
 | 
					| [Pushover](https://github.com/caronc/apprise/wiki/Notify_pushover)  | pover://   | (TCP) 443   | pover://user@token<br />pover://user@token/DEVICE<br />pover://user@token/DEVICE1/DEVICE2/DEVICEN<br />_Note: you must specify both your user_id and token_
 | 
				
			||||||
| [Rocket.Chat](https://github.com/caronc/apprise/wiki/Notify_rocketchat) | rocket:// or rockets://  | (TCP) 80 or 443   | rocket://user:password@hostname/RoomID/Channel<br />rockets://user:password@hostname:443/Channel1/Channel1/RoomID<br />rocket://user:password@hostname/Channel
 | 
					| [Rocket.Chat](https://github.com/caronc/apprise/wiki/Notify_rocketchat) | rocket:// or rockets://  | (TCP) 80 or 443   | rocket://user:password@hostname/RoomID/Channel<br />rockets://user:password@hostname:443/Channel1/Channel1/RoomID<br />rocket://user:password@hostname/Channel
 | 
				
			||||||
| [Slack](https://github.com/caronc/apprise/wiki/Notify_slack) | slack://  | (TCP) 443   | slack://TokenA/TokenB/TokenC/Channel<br />slack://botname@TokenA/TokenB/TokenC/Channel<br />slack://user@TokenA/TokenB/TokenC/Channel1/Channel2/ChannelN
 | 
					| [Slack](https://github.com/caronc/apprise/wiki/Notify_slack) | slack://  | (TCP) 443   | slack://TokenA/TokenB/TokenC/Channel<br />slack://botname@TokenA/TokenB/TokenC/Channel<br />slack://user@TokenA/TokenB/TokenC/Channel1/Channel2/ChannelN
 | 
				
			||||||
| [Stride](https://github.com/caronc/apprise/wiki/Notify_stride)  | stride://   | (TCP) 443   | stride://auth_token/cloud_id/convo_id
 | 
					| [Stride](https://github.com/caronc/apprise/wiki/Notify_stride)  | stride://   | (TCP) 443   | stride://auth_token/cloud_id/convo_id
 | 
				
			||||||
| [Super Toasty](https://github.com/caronc/apprise/wiki/Notify_toasty)  | toasty://   | (TCP) 80   | toasty://user@DEVICE<br />toasty://user@DEVICE1/DEVICE2/DEVICEN<br />_Note: you must specify both your user_id and at least 1 device!_
 | 
					 | 
				
			||||||
| [Telegram](https://github.com/caronc/apprise/wiki/Notify_telegram) | tgram://  | (TCP) 443   | tgram://bottoken/ChatID<br />tgram://bottoken/ChatID1/ChatID2/ChatIDN
 | 
					| [Telegram](https://github.com/caronc/apprise/wiki/Notify_telegram) | tgram://  | (TCP) 443   | tgram://bottoken/ChatID<br />tgram://bottoken/ChatID1/ChatID2/ChatIDN
 | 
				
			||||||
| [Twitter](https://github.com/caronc/apprise/wiki/Notify_twitter) | tweet://  | (TCP) 443   | tweet://user@CKey/CSecret/AKey/ASecret
 | 
					| [Twitter](https://github.com/caronc/apprise/wiki/Notify_twitter) | tweet://  | (TCP) 443   | tweet://user@CKey/CSecret/AKey/ASecret
 | 
				
			||||||
| [XBMC](https://github.com/caronc/apprise/wiki/Notify_xbmc) | xbmc:// or xbmcs://    | (TCP) 8080 or 443   | xbmc://hostname<br />xbmc://user@hostname<br />xbmc://user:password@hostname:port
 | 
					| [XBMC](https://github.com/caronc/apprise/wiki/Notify_xbmc) | xbmc:// or xbmcs://    | (TCP) 8080 or 443   | xbmc://hostname<br />xbmc://user@hostname<br />xbmc://user:password@hostname:port
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,301 @@
 | 
				
			||||||
 | 
					# -*- coding: utf-8 -*-
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
 | 
				
			||||||
 | 
					# All rights reserved.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# This code is licensed under the MIT License.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					# of this software and associated documentation files(the "Software"), to deal
 | 
				
			||||||
 | 
					# in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
 | 
				
			||||||
 | 
					# copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					# furnished to do so, subject to the following conditions :
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# The above copyright notice and this permission notice shall be included in
 | 
				
			||||||
 | 
					# all copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
				
			||||||
 | 
					# THE SOFTWARE.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import re
 | 
				
			||||||
 | 
					import requests
 | 
				
			||||||
 | 
					from json import dumps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .NotifyBase import NotifyBase
 | 
				
			||||||
 | 
					from .NotifyBase import HTTP_ERROR_MAP
 | 
				
			||||||
 | 
					from ..utils import compat_is_basestring
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Used to detect and parse channels
 | 
				
			||||||
 | 
					IS_CHANNEL = re.compile(r'^#(?P<name>[A-Za-z0-9]+)$')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Used to detect and parse a users push id
 | 
				
			||||||
 | 
					IS_USER_PUSHED_ID = re.compile(r'^@(?P<name>[A-Za-z0-9]+)$')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Used to break apart list of potential tags by their delimiter
 | 
				
			||||||
 | 
					# into a usable list.
 | 
				
			||||||
 | 
					LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class NotifyPushed(NotifyBase):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    A wrapper to Pushed Notifications
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # The default descriptive name associated with the Notification
 | 
				
			||||||
 | 
					    service_name = 'Pushed'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # The services URL
 | 
				
			||||||
 | 
					    service_url = 'https://pushed.co/'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # The default secure protocol
 | 
				
			||||||
 | 
					    secure_protocol = 'pushed'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # A URL that takes you to the setup/help of the specific protocol
 | 
				
			||||||
 | 
					    setup_url = 'https://github.com/caronc/apprise/wiki/Notify_pushed'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Pushed uses the http protocol with JSON requests
 | 
				
			||||||
 | 
					    notify_url = 'https://api.pushed.co/1/push'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # The maximum allowable characters allowed in the body per message
 | 
				
			||||||
 | 
					    body_maxlen = 140
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, app_key, app_secret, recipients=None, **kwargs):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Initialize Pushed Object
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        super(NotifyPushed, self).__init__(**kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not app_key:
 | 
				
			||||||
 | 
					            raise TypeError(
 | 
				
			||||||
 | 
					                'An invalid Application Key was specified.'
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not app_secret:
 | 
				
			||||||
 | 
					            raise TypeError(
 | 
				
			||||||
 | 
					                'An invalid Application Secret was specified.'
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Initialize channel list
 | 
				
			||||||
 | 
					        self.channels = list()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Initialize user list
 | 
				
			||||||
 | 
					        self.users = list()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if recipients is None:
 | 
				
			||||||
 | 
					            recipients = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        elif compat_is_basestring(recipients):
 | 
				
			||||||
 | 
					            recipients = [x for x in filter(bool, LIST_DELIM.split(
 | 
				
			||||||
 | 
					                recipients,
 | 
				
			||||||
 | 
					            ))]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        elif not isinstance(recipients, (set, tuple, list)):
 | 
				
			||||||
 | 
					            raise TypeError(
 | 
				
			||||||
 | 
					                'An invalid receipient list was specified.'
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Validate recipients and drop bad ones:
 | 
				
			||||||
 | 
					        for recipient in recipients:
 | 
				
			||||||
 | 
					            result = IS_CHANNEL.match(recipient)
 | 
				
			||||||
 | 
					            if result:
 | 
				
			||||||
 | 
					                # store valid device
 | 
				
			||||||
 | 
					                self.channels.append(result.group('name'))
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            result = IS_USER_PUSHED_ID.match(recipient)
 | 
				
			||||||
 | 
					            if result:
 | 
				
			||||||
 | 
					                # store valid room
 | 
				
			||||||
 | 
					                self.users.append(result.group('name'))
 | 
				
			||||||
 | 
					                continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.logger.warning(
 | 
				
			||||||
 | 
					                'Dropped invalid channel/userid '
 | 
				
			||||||
 | 
					                '(%s) specified.' % recipient,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Store our data
 | 
				
			||||||
 | 
					        self.app_key = app_key
 | 
				
			||||||
 | 
					        self.app_secret = app_secret
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def notify(self, title, body, notify_type, **kwargs):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Perform Pushed Notification
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Initiaize our error tracking
 | 
				
			||||||
 | 
					        has_error = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # prepare JSON Object
 | 
				
			||||||
 | 
					        payload = {
 | 
				
			||||||
 | 
					            'app_key': self.app_key,
 | 
				
			||||||
 | 
					            'app_secret': self.app_secret,
 | 
				
			||||||
 | 
					            'target_type': 'app',
 | 
				
			||||||
 | 
					            'content': body,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # So the logic is as follows:
 | 
				
			||||||
 | 
					        #  - if no user/channel was specified, then we just simply notify the
 | 
				
			||||||
 | 
					        #    app.
 | 
				
			||||||
 | 
					        #  - if there are user/channels specified, then we only alert them
 | 
				
			||||||
 | 
					        #    while respecting throttle limits (in the event there are a lot of
 | 
				
			||||||
 | 
					        #    entries.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if len(self.channels) + len(self.users) == 0:
 | 
				
			||||||
 | 
					            # Just notify the app
 | 
				
			||||||
 | 
					            return self.send_notification(
 | 
				
			||||||
 | 
					                payload=payload, notify_type=notify_type, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # If our code reaches here, we want to target channels and users (by
 | 
				
			||||||
 | 
					        # their Pushed_ID instead...
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Generate a copy of our original list
 | 
				
			||||||
 | 
					        channels = list(self.channels)
 | 
				
			||||||
 | 
					        users = list(self.users)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Copy our payload
 | 
				
			||||||
 | 
					        _payload = dict(payload)
 | 
				
			||||||
 | 
					        _payload['target_type'] = 'channel'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        while len(channels) > 0:
 | 
				
			||||||
 | 
					            # Get Channel
 | 
				
			||||||
 | 
					            _payload['target_alias'] = channels.pop(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if not self.send_notification(
 | 
				
			||||||
 | 
					                    payload=_payload, notify_type=notify_type, **kwargs):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # toggle flag
 | 
				
			||||||
 | 
					                has_error = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if len(channels) + len(users) > 0:
 | 
				
			||||||
 | 
					                # Prevent thrashing requests
 | 
				
			||||||
 | 
					                self.throttle()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Copy our payload
 | 
				
			||||||
 | 
					        _payload = dict(payload)
 | 
				
			||||||
 | 
					        _payload['target_type'] = 'pushed_id'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Send all our defined User Pushed ID's
 | 
				
			||||||
 | 
					        while len(users):
 | 
				
			||||||
 | 
					            # Get User's Pushed ID
 | 
				
			||||||
 | 
					            _payload['pushed_id'] = users.pop(0)
 | 
				
			||||||
 | 
					            if not self.send_notification(
 | 
				
			||||||
 | 
					                    payload=_payload, notify_type=notify_type, **kwargs):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # toggle flag
 | 
				
			||||||
 | 
					                has_error = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if len(users) > 0:
 | 
				
			||||||
 | 
					                # Prevent thrashing requests
 | 
				
			||||||
 | 
					                self.throttle()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return not has_error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def send_notification(self, payload, notify_type, **kwargs):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        A lower level call that directly pushes a payload to the Pushed
 | 
				
			||||||
 | 
					        Notification servers.  This should never be called directly; it is
 | 
				
			||||||
 | 
					        referenced automatically through the notify() function.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        headers = {
 | 
				
			||||||
 | 
					            'User-Agent': self.app_id,
 | 
				
			||||||
 | 
					            'Content-Type': 'application/json'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.logger.debug('Pushed POST URL: %s (cert_verify=%r)' % (
 | 
				
			||||||
 | 
					            self.notify_url, self.verify_certificate,
 | 
				
			||||||
 | 
					        ))
 | 
				
			||||||
 | 
					        self.logger.debug('Pushed Payload: %s' % str(payload))
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            r = requests.post(
 | 
				
			||||||
 | 
					                self.notify_url,
 | 
				
			||||||
 | 
					                data=dumps(payload),
 | 
				
			||||||
 | 
					                headers=headers,
 | 
				
			||||||
 | 
					                verify=self.verify_certificate,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if r.status_code != requests.codes.ok:
 | 
				
			||||||
 | 
					                # We had a problem
 | 
				
			||||||
 | 
					                try:
 | 
				
			||||||
 | 
					                    self.logger.warning(
 | 
				
			||||||
 | 
					                        'Failed to send Pushed notification: '
 | 
				
			||||||
 | 
					                        '%s (error=%s).' % (
 | 
				
			||||||
 | 
					                            HTTP_ERROR_MAP[r.status_code],
 | 
				
			||||||
 | 
					                            r.status_code))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                except KeyError:
 | 
				
			||||||
 | 
					                    self.logger.warning(
 | 
				
			||||||
 | 
					                        'Failed to send Pushed notification '
 | 
				
			||||||
 | 
					                        '(error=%s).' % r.status_code)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                self.logger.debug('Response Details: %s' % r.raw.read())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # Return; we're done
 | 
				
			||||||
 | 
					                return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                self.logger.info('Sent Pushed notification.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        except requests.RequestException as e:
 | 
				
			||||||
 | 
					            self.logger.warning(
 | 
				
			||||||
 | 
					                'A Connection error occured sending Pushed notification.')
 | 
				
			||||||
 | 
					            self.logger.debug('Socket Exception: %s' % str(e))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Return; we're done
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def parse_url(url):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Parses the URL and returns enough arguments that can allow
 | 
				
			||||||
 | 
					        us to substantiate this object.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        results = NotifyBase.parse_url(url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not results:
 | 
				
			||||||
 | 
					            # We're done early as we couldn't load the results
 | 
				
			||||||
 | 
					            return results
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Apply our settings now
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # The first token is stored in the hostname
 | 
				
			||||||
 | 
					        app_key = results['host']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Initialize our recipients
 | 
				
			||||||
 | 
					        recipients = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Now fetch the remaining tokens
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            app_secret = \
 | 
				
			||||||
 | 
					                [x for x in filter(bool, NotifyBase.split_path(
 | 
				
			||||||
 | 
					                    results['fullpath']))][0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        except (ValueError, AttributeError, IndexError):
 | 
				
			||||||
 | 
					            # Force some bad values that will get caught
 | 
				
			||||||
 | 
					            # in parsing later
 | 
				
			||||||
 | 
					            app_secret = None
 | 
				
			||||||
 | 
					            app_key = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Get our recipients
 | 
				
			||||||
 | 
					        recipients = \
 | 
				
			||||||
 | 
					            [x for x in filter(bool, NotifyBase.split_path(
 | 
				
			||||||
 | 
					                results['fullpath']))][1:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        results['app_key'] = app_key
 | 
				
			||||||
 | 
					        results['app_secret'] = app_secret
 | 
				
			||||||
 | 
					        results['recipients'] = recipients
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return results
 | 
				
			||||||
| 
						 | 
					@ -43,9 +43,6 @@ class NotifyPushjet(NotifyBase):
 | 
				
			||||||
    # The default descriptive name associated with the Notification
 | 
					    # The default descriptive name associated with the Notification
 | 
				
			||||||
    service_name = 'Pushjet'
 | 
					    service_name = 'Pushjet'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # The services URL
 | 
					 | 
				
			||||||
    service_url = 'https://pushjet.io/'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # The default protocol
 | 
					    # The default protocol
 | 
				
			||||||
    protocol = 'pjet'
 | 
					    protocol = 'pjet'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -66,21 +63,16 @@ class NotifyPushjet(NotifyBase):
 | 
				
			||||||
        Perform Pushjet Notification
 | 
					        Perform Pushjet Notification
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            if self.user and self.host:
 | 
					            server = "http://"
 | 
				
			||||||
                server = "http://"
 | 
					            if self.secure:
 | 
				
			||||||
                if self.secure:
 | 
					                server = "https://"
 | 
				
			||||||
                    server = "https://"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                server += self.host
 | 
					            server += self.host
 | 
				
			||||||
                if self.port:
 | 
					            if self.port:
 | 
				
			||||||
                    server += ":" + str(self.port)
 | 
					                server += ":" + str(self.port)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                api = pushjet.Api(server)
 | 
					            api = pushjet.Api(server)
 | 
				
			||||||
                service = api.Service(secret_key=self.user)
 | 
					            service = api.Service(secret_key=self.user)
 | 
				
			||||||
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                api = pushjet.Api(pushjet.DEFAULT_API_URL)
 | 
					 | 
				
			||||||
                service = api.Service(secret_key=self.host)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            service.send(body, title)
 | 
					            service.send(body, title)
 | 
				
			||||||
            self.logger.info('Sent Pushjet notification.')
 | 
					            self.logger.info('Sent Pushjet notification.')
 | 
				
			||||||
| 
						 | 
					@ -91,3 +83,28 @@ class NotifyPushjet(NotifyBase):
 | 
				
			||||||
            return False
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return True
 | 
					        return True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    def parse_url(url):
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        Parses the URL and returns enough arguments that can allow
 | 
				
			||||||
 | 
					        us to substantiate this object.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Syntax:
 | 
				
			||||||
 | 
					           pjet://secret@hostname
 | 
				
			||||||
 | 
					           pjet://secret@hostname:port
 | 
				
			||||||
 | 
					           pjets://secret@hostname
 | 
				
			||||||
 | 
					           pjets://secret@hostname:port
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        results = NotifyBase.parse_url(url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not results:
 | 
				
			||||||
 | 
					            # We're done early as we couldn't load the results
 | 
				
			||||||
 | 
					            return results
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not results.get('user'):
 | 
				
			||||||
 | 
					            # a username is required
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return results
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,187 +0,0 @@
 | 
				
			||||||
# -*- coding: utf-8 -*-
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Copyright (C) 2019 Chris Caron <lead2gold@gmail.com>
 | 
					 | 
				
			||||||
# All rights reserved.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# This code is licensed under the MIT License.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
					 | 
				
			||||||
# of this software and associated documentation files(the "Software"), to deal
 | 
					 | 
				
			||||||
# in the Software without restriction, including without limitation the rights
 | 
					 | 
				
			||||||
# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
 | 
					 | 
				
			||||||
# copies of the Software, and to permit persons to whom the Software is
 | 
					 | 
				
			||||||
# furnished to do so, subject to the following conditions :
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# The above copyright notice and this permission notice shall be included in
 | 
					 | 
				
			||||||
# all copies or substantial portions of the Software.
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
					 | 
				
			||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
					 | 
				
			||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
 | 
					 | 
				
			||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
					 | 
				
			||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
					 | 
				
			||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 | 
					 | 
				
			||||||
# THE SOFTWARE.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import re
 | 
					 | 
				
			||||||
import requests
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from .NotifyBase import NotifyBase
 | 
					 | 
				
			||||||
from .NotifyBase import HTTP_ERROR_MAP
 | 
					 | 
				
			||||||
from ..common import NotifyImageSize
 | 
					 | 
				
			||||||
from ..utils import compat_is_basestring
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Used to break apart list of potential devices by their delimiter
 | 
					 | 
				
			||||||
# into a usable list.
 | 
					 | 
				
			||||||
DEVICES_LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class NotifyToasty(NotifyBase):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    A wrapper for Toasty Notifications
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # The default descriptive name associated with the Notification
 | 
					 | 
				
			||||||
    service_name = 'Toasty'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # The services URL
 | 
					 | 
				
			||||||
    service_url = 'http://supertoasty.com/'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # The default protocol
 | 
					 | 
				
			||||||
    protocol = 'toasty'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # A URL that takes you to the setup/help of the specific protocol
 | 
					 | 
				
			||||||
    setup_url = 'https://github.com/caronc/apprise/wiki/Notify_toasty'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Toasty uses the http protocol with JSON requests
 | 
					 | 
				
			||||||
    notify_url = 'http://api.supertoasty.com/notify/'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Allows the user to specify the NotifyImageSize object
 | 
					 | 
				
			||||||
    image_size = NotifyImageSize.XY_128
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def __init__(self, devices, **kwargs):
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Initialize Toasty Object
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        super(NotifyToasty, self).__init__(**kwargs)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if compat_is_basestring(devices):
 | 
					 | 
				
			||||||
            self.devices = [x for x in filter(bool, DEVICES_LIST_DELIM.split(
 | 
					 | 
				
			||||||
                devices,
 | 
					 | 
				
			||||||
            ))]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        elif isinstance(devices, (set, tuple, list)):
 | 
					 | 
				
			||||||
            self.devices = devices
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            self.devices = list()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if len(devices) == 0:
 | 
					 | 
				
			||||||
            raise TypeError('You must specify at least 1 device.')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not self.user:
 | 
					 | 
				
			||||||
            raise TypeError('You must specify a username.')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def notify(self, title, body, notify_type, **kwargs):
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Perform Toasty Notification
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        headers = {
 | 
					 | 
				
			||||||
            'User-Agent': self.app_id,
 | 
					 | 
				
			||||||
            'Content-Type': 'multipart/form-data',
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # error tracking (used for function return)
 | 
					 | 
				
			||||||
        has_error = False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Create a copy of the devices list
 | 
					 | 
				
			||||||
        devices = list(self.devices)
 | 
					 | 
				
			||||||
        while len(devices):
 | 
					 | 
				
			||||||
            device = devices.pop(0)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # prepare JSON Object
 | 
					 | 
				
			||||||
            payload = {
 | 
					 | 
				
			||||||
                'sender': NotifyBase.quote(self.user),
 | 
					 | 
				
			||||||
                'title': NotifyBase.quote(title),
 | 
					 | 
				
			||||||
                'text': NotifyBase.quote(body),
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            image_url = self.image_url(notify_type)
 | 
					 | 
				
			||||||
            if image_url:
 | 
					 | 
				
			||||||
                payload['image'] = image_url
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # URL to transmit content via
 | 
					 | 
				
			||||||
            url = '%s%s' % (self.notify_url, device)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            self.logger.debug('Toasty POST URL: %s (cert_verify=%r)' % (
 | 
					 | 
				
			||||||
                url, self.verify_certificate,
 | 
					 | 
				
			||||||
            ))
 | 
					 | 
				
			||||||
            self.logger.debug('Toasty Payload: %s' % str(payload))
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                r = requests.get(
 | 
					 | 
				
			||||||
                    url,
 | 
					 | 
				
			||||||
                    data=payload,
 | 
					 | 
				
			||||||
                    headers=headers,
 | 
					 | 
				
			||||||
                    verify=self.verify_certificate,
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                if r.status_code != requests.codes.ok:
 | 
					 | 
				
			||||||
                    # We had a problem
 | 
					 | 
				
			||||||
                    try:
 | 
					 | 
				
			||||||
                        self.logger.warning(
 | 
					 | 
				
			||||||
                            'Failed to send Toasty:%s '
 | 
					 | 
				
			||||||
                            'notification: %s (error=%s).' % (
 | 
					 | 
				
			||||||
                                device,
 | 
					 | 
				
			||||||
                                HTTP_ERROR_MAP[r.status_code],
 | 
					 | 
				
			||||||
                                r.status_code))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    except KeyError:
 | 
					 | 
				
			||||||
                        self.logger.warning(
 | 
					 | 
				
			||||||
                            'Failed to send Toasty:%s '
 | 
					 | 
				
			||||||
                            'notification (error=%s).' % (
 | 
					 | 
				
			||||||
                                device,
 | 
					 | 
				
			||||||
                                r.status_code))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    # self.logger.debug('Response Details: %s' % r.raw.read())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    # Return; we're done
 | 
					 | 
				
			||||||
                    has_error = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    self.logger.info(
 | 
					 | 
				
			||||||
                        'Sent Toasty notification to %s.' % device)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            except requests.RequestException as e:
 | 
					 | 
				
			||||||
                self.logger.warning(
 | 
					 | 
				
			||||||
                    'A Connection error occured sending Toasty:%s ' % (
 | 
					 | 
				
			||||||
                        device) + 'notification.'
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                self.logger.debug('Socket Exception: %s' % str(e))
 | 
					 | 
				
			||||||
                has_error = True
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if len(devices):
 | 
					 | 
				
			||||||
                # Prevent thrashing requests
 | 
					 | 
				
			||||||
                self.throttle()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return not has_error
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @staticmethod
 | 
					 | 
				
			||||||
    def parse_url(url):
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        Parses the URL and returns enough arguments that can allow
 | 
					 | 
				
			||||||
        us to substantiate this object.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        """
 | 
					 | 
				
			||||||
        results = NotifyBase.parse_url(url)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not results:
 | 
					 | 
				
			||||||
            # We're done early as we couldn't load the results
 | 
					 | 
				
			||||||
            return results
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Apply our settings now
 | 
					 | 
				
			||||||
        devices = NotifyBase.unquote(results['fullpath'])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Store our devices
 | 
					 | 
				
			||||||
        results['devices'] = '%s/%s' % (results['host'], devices)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return results
 | 
					 | 
				
			||||||
| 
						 | 
					@ -40,6 +40,7 @@ from .NotifyJSON import NotifyJSON
 | 
				
			||||||
from .NotifyMatrix import NotifyMatrix
 | 
					from .NotifyMatrix import NotifyMatrix
 | 
				
			||||||
from .NotifyMatterMost import NotifyMatterMost
 | 
					from .NotifyMatterMost import NotifyMatterMost
 | 
				
			||||||
from .NotifyProwl import NotifyProwl
 | 
					from .NotifyProwl import NotifyProwl
 | 
				
			||||||
 | 
					from .NotifyPushed import NotifyPushed
 | 
				
			||||||
from .NotifyPushBullet import NotifyPushBullet
 | 
					from .NotifyPushBullet import NotifyPushBullet
 | 
				
			||||||
from .NotifyPushjet.NotifyPushjet import NotifyPushjet
 | 
					from .NotifyPushjet.NotifyPushjet import NotifyPushjet
 | 
				
			||||||
from .NotifyPushover import NotifyPushover
 | 
					from .NotifyPushover import NotifyPushover
 | 
				
			||||||
| 
						 | 
					@ -47,7 +48,6 @@ from .NotifyRocketChat import NotifyRocketChat
 | 
				
			||||||
from .NotifySlack import NotifySlack
 | 
					from .NotifySlack import NotifySlack
 | 
				
			||||||
from .NotifyStride import NotifyStride
 | 
					from .NotifyStride import NotifyStride
 | 
				
			||||||
from .NotifyTelegram import NotifyTelegram
 | 
					from .NotifyTelegram import NotifyTelegram
 | 
				
			||||||
from .NotifyToasty import NotifyToasty
 | 
					 | 
				
			||||||
from .NotifyTwitter.NotifyTwitter import NotifyTwitter
 | 
					from .NotifyTwitter.NotifyTwitter import NotifyTwitter
 | 
				
			||||||
from .NotifyXBMC import NotifyXBMC
 | 
					from .NotifyXBMC import NotifyXBMC
 | 
				
			||||||
from .NotifyXML import NotifyXML
 | 
					from .NotifyXML import NotifyXML
 | 
				
			||||||
| 
						 | 
					@ -67,10 +67,10 @@ __all__ = [
 | 
				
			||||||
    'NotifyBoxcar', 'NotifyEmail', 'NotifyEmby', 'NotifyDiscord',
 | 
					    'NotifyBoxcar', 'NotifyEmail', 'NotifyEmby', 'NotifyDiscord',
 | 
				
			||||||
    'NotifyFaast', 'NotifyGnome', 'NotifyGrowl', 'NotifyIFTTT', 'NotifyJoin',
 | 
					    'NotifyFaast', 'NotifyGnome', 'NotifyGrowl', 'NotifyIFTTT', 'NotifyJoin',
 | 
				
			||||||
    'NotifyJSON', 'NotifyMatrix', 'NotifyMatterMost', 'NotifyProwl',
 | 
					    'NotifyJSON', 'NotifyMatrix', 'NotifyMatterMost', 'NotifyProwl',
 | 
				
			||||||
    'NotifyPushBullet', 'NotifyPushjet', 'NotifyPushover',
 | 
					    'NotifyPushed', 'NotifyPushBullet', 'NotifyPushjet',
 | 
				
			||||||
    'NotifyRocketChat', 'NotifySlack', 'NotifyStride', 'NotifyToasty',
 | 
					    'NotifyPushover', 'NotifyRocketChat', 'NotifySlack', 'NotifyStride',
 | 
				
			||||||
    'NotifyTwitter', 'NotifyTelegram', 'NotifyXBMC', 'NotifyXML',
 | 
					    'NotifyTwitter', 'NotifyTelegram', 'NotifyXBMC',
 | 
				
			||||||
    'NotifyWindows',
 | 
					    'NotifyXML', 'NotifyWindows',
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Reference
 | 
					    # Reference
 | 
				
			||||||
    'NotifyImageSize', 'NOTIFY_IMAGE_SIZES', 'NotifyType', 'NOTIFY_TYPES',
 | 
					    'NotifyImageSize', 'NOTIFY_IMAGE_SIZES', 'NotifyType', 'NOTIFY_TYPES',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,9 +38,9 @@ TEST_URLS = (
 | 
				
			||||||
    ('pjets://', {
 | 
					    ('pjets://', {
 | 
				
			||||||
        'instance': None,
 | 
					        'instance': None,
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
    # Default query (uses pushjet server)
 | 
					    #  You must specify a username
 | 
				
			||||||
    ('pjet://%s' % ('a' * 32), {
 | 
					    ('pjet://%s' % ('a' * 32), {
 | 
				
			||||||
        'instance': plugins.NotifyPushjet,
 | 
					        'instance': None,
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
    # Specify your own server
 | 
					    # Specify your own server
 | 
				
			||||||
    ('pjet://%s@localhost' % ('a' * 32), {
 | 
					    ('pjet://%s@localhost' % ('a' * 32), {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -782,7 +782,25 @@ TEST_URLS = (
 | 
				
			||||||
    ('pbul://%s/device/#channel/user@example.com/' % ('a' * 32), {
 | 
					    ('pbul://%s/device/#channel/user@example.com/' % ('a' * 32), {
 | 
				
			||||||
        'instance': plugins.NotifyPushBullet,
 | 
					        'instance': plugins.NotifyPushBullet,
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
    # APIKey + bad url
 | 
					    # ,
 | 
				
			||||||
 | 
					    ('pbul://%s' % ('a' * 32), {
 | 
				
			||||||
 | 
					        'instance': plugins.NotifyPushBullet,
 | 
				
			||||||
 | 
					        # force a failure
 | 
				
			||||||
 | 
					        'response': False,
 | 
				
			||||||
 | 
					        'requests_response_code': requests.codes.internal_server_error,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    ('pbul://%s' % ('a' * 32), {
 | 
				
			||||||
 | 
					        'instance': plugins.NotifyPushBullet,
 | 
				
			||||||
 | 
					        # throw a bizzare code forcing us to fail to look it up
 | 
				
			||||||
 | 
					        'response': False,
 | 
				
			||||||
 | 
					        'requests_response_code': 999,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    ('pbul://%s' % ('a' * 32), {
 | 
				
			||||||
 | 
					        'instance': plugins.NotifyPushBullet,
 | 
				
			||||||
 | 
					        # Throws a series of connection and transfer exceptions when this flag
 | 
				
			||||||
 | 
					        # is set and tests that we gracfully handle them
 | 
				
			||||||
 | 
					        'test_requests_exceptions': True,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
    ('pbul://:@/', {
 | 
					    ('pbul://:@/', {
 | 
				
			||||||
        'instance': None,
 | 
					        'instance': None,
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
| 
						 | 
					@ -805,6 +823,98 @@ TEST_URLS = (
 | 
				
			||||||
        'test_requests_exceptions': True,
 | 
					        'test_requests_exceptions': True,
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ##################################
 | 
				
			||||||
 | 
					    # NotifyPushed
 | 
				
			||||||
 | 
					    ##################################
 | 
				
			||||||
 | 
					    ('pushed://', {
 | 
				
			||||||
 | 
					        'instance': None,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    # Application Key Only
 | 
				
			||||||
 | 
					    ('pushed://%s' % ('a' * 32), {
 | 
				
			||||||
 | 
					        'instance': TypeError,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    # Application Key+Secret
 | 
				
			||||||
 | 
					    ('pushed://%s/%s' % ('a' * 32, 'a' * 64), {
 | 
				
			||||||
 | 
					        'instance': plugins.NotifyPushed,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    # Application Key+Secret + channel
 | 
				
			||||||
 | 
					    ('pushed://%s/%s/#channel/' % ('a' * 32, 'a' * 64), {
 | 
				
			||||||
 | 
					        'instance': plugins.NotifyPushed,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    # Application Key+Secret + dropped entry
 | 
				
			||||||
 | 
					    ('pushed://%s/%s/dropped/' % ('a' * 32, 'a' * 64), {
 | 
				
			||||||
 | 
					        'instance': plugins.NotifyPushed,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    # Application Key+Secret + 2 channels
 | 
				
			||||||
 | 
					    ('pushed://%s/%s/#channel1/#channel2' % ('a' * 32, 'a' * 64), {
 | 
				
			||||||
 | 
					        'instance': plugins.NotifyPushed,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    # Application Key+Secret + User Pushed ID
 | 
				
			||||||
 | 
					    ('pushed://%s/%s/@ABCD/' % ('a' * 32, 'a' * 64), {
 | 
				
			||||||
 | 
					        'instance': plugins.NotifyPushed,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    # Application Key+Secret + 2 devices
 | 
				
			||||||
 | 
					    ('pushed://%s/%s/@ABCD/@DEFG/' % ('a' * 32, 'a' * 64), {
 | 
				
			||||||
 | 
					        'instance': plugins.NotifyPushed,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    # Application Key+Secret + Combo
 | 
				
			||||||
 | 
					    ('pushed://%s/%s/@ABCD/#channel' % ('a' * 32, 'a' * 64), {
 | 
				
			||||||
 | 
					        'instance': plugins.NotifyPushed,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    # ,
 | 
				
			||||||
 | 
					    ('pushed://%s/%s' % ('a' * 32, 'a' * 64), {
 | 
				
			||||||
 | 
					        'instance': plugins.NotifyPushed,
 | 
				
			||||||
 | 
					        # force a failure
 | 
				
			||||||
 | 
					        'response': False,
 | 
				
			||||||
 | 
					        'requests_response_code': requests.codes.internal_server_error,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    ('pushed://%s/%s' % ('a' * 32, 'a' * 64), {
 | 
				
			||||||
 | 
					        'instance': plugins.NotifyPushed,
 | 
				
			||||||
 | 
					        # throw a bizzare code forcing us to fail to look it up
 | 
				
			||||||
 | 
					        'response': False,
 | 
				
			||||||
 | 
					        'requests_response_code': 999,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    ('pushed://%s/%s' % ('a' * 32, 'a' * 64), {
 | 
				
			||||||
 | 
					        'instance': plugins.NotifyPushed,
 | 
				
			||||||
 | 
					        # Throws a series of connection and transfer exceptions when this flag
 | 
				
			||||||
 | 
					        # is set and tests that we gracfully handle them
 | 
				
			||||||
 | 
					        'test_requests_exceptions': True,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    ('pushed://:@/', {
 | 
				
			||||||
 | 
					        'instance': None,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    ('pushed://%s/%s' % ('a' * 32, 'a' * 64), {
 | 
				
			||||||
 | 
					        'instance': plugins.NotifyPushed,
 | 
				
			||||||
 | 
					        # force a failure
 | 
				
			||||||
 | 
					        'response': False,
 | 
				
			||||||
 | 
					        'requests_response_code': requests.codes.internal_server_error,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    ('pushed://%s/%s' % ('a' * 32, 'a' * 64), {
 | 
				
			||||||
 | 
					        'instance': plugins.NotifyPushed,
 | 
				
			||||||
 | 
					        # throw a bizzare code forcing us to fail to look it up
 | 
				
			||||||
 | 
					        'response': False,
 | 
				
			||||||
 | 
					        'requests_response_code': 999,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    ('pushed://%s/%s/#channel' % ('a' * 32, 'a' * 64), {
 | 
				
			||||||
 | 
					        'instance': plugins.NotifyPushed,
 | 
				
			||||||
 | 
					        # throw a bizzare code forcing us to fail to look it up
 | 
				
			||||||
 | 
					        'response': False,
 | 
				
			||||||
 | 
					        'requests_response_code': 999,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    ('pushed://%s/%s/@user' % ('a' * 32, 'a' * 64), {
 | 
				
			||||||
 | 
					        'instance': plugins.NotifyPushed,
 | 
				
			||||||
 | 
					        # throw a bizzare code forcing us to fail to look it up
 | 
				
			||||||
 | 
					        'response': False,
 | 
				
			||||||
 | 
					        'requests_response_code': 999,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    ('pushed://%s/%s' % ('a' * 32, 'a' * 64), {
 | 
				
			||||||
 | 
					        'instance': plugins.NotifyPushed,
 | 
				
			||||||
 | 
					        # Throws a series of connection and transfer exceptions when this flag
 | 
				
			||||||
 | 
					        # is set and tests that we gracfully handle them
 | 
				
			||||||
 | 
					        'test_requests_exceptions': True,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ##################################
 | 
					    ##################################
 | 
				
			||||||
    # NotifyPushover
 | 
					    # NotifyPushover
 | 
				
			||||||
    ##################################
 | 
					    ##################################
 | 
				
			||||||
| 
						 | 
					@ -1207,49 +1317,6 @@ TEST_URLS = (
 | 
				
			||||||
        'test_requests_exceptions': True,
 | 
					        'test_requests_exceptions': True,
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ##################################
 | 
					 | 
				
			||||||
    # NotifyToasty (SuperToasty)
 | 
					 | 
				
			||||||
    ##################################
 | 
					 | 
				
			||||||
    ('toasty://', {
 | 
					 | 
				
			||||||
        'instance': None,
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
    # No username specified but contains a device
 | 
					 | 
				
			||||||
    ('toasty://%s' % ('d' * 32), {
 | 
					 | 
				
			||||||
        'instance': TypeError,
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
    # User + 1 device
 | 
					 | 
				
			||||||
    ('toasty://user@device', {
 | 
					 | 
				
			||||||
        'instance': plugins.NotifyToasty,
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
    # User + 3 devices
 | 
					 | 
				
			||||||
    ('toasty://user@device0/device1/device2/', {
 | 
					 | 
				
			||||||
        'instance': plugins.NotifyToasty,
 | 
					 | 
				
			||||||
        # don't include an image by default
 | 
					 | 
				
			||||||
        'include_image': False,
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
    # bad url
 | 
					 | 
				
			||||||
    ('toasty://:@/', {
 | 
					 | 
				
			||||||
        'instance': None,
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
    ('toasty://user@device', {
 | 
					 | 
				
			||||||
        'instance': plugins.NotifyToasty,
 | 
					 | 
				
			||||||
        # force a failure
 | 
					 | 
				
			||||||
        'response': False,
 | 
					 | 
				
			||||||
        'requests_response_code': requests.codes.internal_server_error,
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
    ('toasty://user@device', {
 | 
					 | 
				
			||||||
        'instance': plugins.NotifyToasty,
 | 
					 | 
				
			||||||
        # throw a bizzare code forcing us to fail to look it up
 | 
					 | 
				
			||||||
        'response': False,
 | 
					 | 
				
			||||||
        'requests_response_code': 999,
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
    ('toasty://user@device', {
 | 
					 | 
				
			||||||
        'instance': plugins.NotifyToasty,
 | 
					 | 
				
			||||||
        # Throws a series of connection and transfer exceptions when this flag
 | 
					 | 
				
			||||||
        # is set and tests that we gracfully handle them
 | 
					 | 
				
			||||||
        'test_requests_exceptions': True,
 | 
					 | 
				
			||||||
    }),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ##################################
 | 
					    ##################################
 | 
				
			||||||
    # NotifyKODI
 | 
					    # NotifyKODI
 | 
				
			||||||
    ##################################
 | 
					    ##################################
 | 
				
			||||||
| 
						 | 
					@ -2261,6 +2328,106 @@ def test_notify_pushbullet_plugin(mock_post, mock_get):
 | 
				
			||||||
    assert(plugins.NotifyPushBullet.parse_url(42) is None)
 | 
					    assert(plugins.NotifyPushBullet.parse_url(42) is None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@mock.patch('requests.get')
 | 
				
			||||||
 | 
					@mock.patch('requests.post')
 | 
				
			||||||
 | 
					def test_notify_pushed_plugin(mock_post, mock_get):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    API: NotifyPushed() Extra Checks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    # Chat ID
 | 
				
			||||||
 | 
					    recipients = '@ABCDEFG, @DEFGHIJ, #channel, #channel2'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Some required input
 | 
				
			||||||
 | 
					    app_key = 'ABCDEFG'
 | 
				
			||||||
 | 
					    app_secret = 'ABCDEFG'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Prepare Mock
 | 
				
			||||||
 | 
					    mock_get.return_value = requests.Request()
 | 
				
			||||||
 | 
					    mock_post.return_value = requests.Request()
 | 
				
			||||||
 | 
					    mock_post.return_value.status_code = requests.codes.ok
 | 
				
			||||||
 | 
					    mock_get.return_value.status_code = requests.codes.ok
 | 
				
			||||||
 | 
					    mock_post.return_value.text = ''
 | 
				
			||||||
 | 
					    mock_get.return_value.text = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        obj = plugins.NotifyPushed(
 | 
				
			||||||
 | 
					            app_key=app_key,
 | 
				
			||||||
 | 
					            app_secret=None,
 | 
				
			||||||
 | 
					            recipients=None,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        assert(False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    except TypeError:
 | 
				
			||||||
 | 
					        # No application Secret was specified; it's a good thing if
 | 
				
			||||||
 | 
					        # this exception was thrown
 | 
				
			||||||
 | 
					        assert(True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        obj = plugins.NotifyPushed(
 | 
				
			||||||
 | 
					            app_key=app_key,
 | 
				
			||||||
 | 
					            app_secret=app_secret,
 | 
				
			||||||
 | 
					            recipients=None,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        # recipients list set to (None) is perfectly fine; in this
 | 
				
			||||||
 | 
					        # case it will notify the App
 | 
				
			||||||
 | 
					        assert(True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    except TypeError:
 | 
				
			||||||
 | 
					        # Exception should never be thrown!
 | 
				
			||||||
 | 
					        assert(False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        obj = plugins.NotifyPushed(
 | 
				
			||||||
 | 
					            app_key=app_key,
 | 
				
			||||||
 | 
					            app_secret=app_secret,
 | 
				
			||||||
 | 
					            recipients=object(),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        # invalid recipients list (object)
 | 
				
			||||||
 | 
					        assert(False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    except TypeError:
 | 
				
			||||||
 | 
					        # Exception should be thrown about the fact no recipients were
 | 
				
			||||||
 | 
					        # specified
 | 
				
			||||||
 | 
					        assert(True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        obj = plugins.NotifyPushed(
 | 
				
			||||||
 | 
					            app_key=app_key,
 | 
				
			||||||
 | 
					            app_secret=app_secret,
 | 
				
			||||||
 | 
					            recipients=set(),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        # Any empty set is acceptable
 | 
				
			||||||
 | 
					        assert(True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    except TypeError:
 | 
				
			||||||
 | 
					        # Exception should never be thrown
 | 
				
			||||||
 | 
					        assert(False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    obj = plugins.NotifyPushed(
 | 
				
			||||||
 | 
					        app_key=app_key,
 | 
				
			||||||
 | 
					        app_secret=app_secret,
 | 
				
			||||||
 | 
					        recipients=recipients,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    assert(isinstance(obj, plugins.NotifyPushed))
 | 
				
			||||||
 | 
					    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
 | 
				
			||||||
 | 
					    assert plugins.NotifyPushed.parse_url(42) is None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Prepare Mock to fail
 | 
				
			||||||
 | 
					    mock_post.return_value.status_code = requests.codes.internal_server_error
 | 
				
			||||||
 | 
					    mock_get.return_value.status_code = requests.codes.internal_server_error
 | 
				
			||||||
 | 
					    mock_post.return_value.text = ''
 | 
				
			||||||
 | 
					    mock_get.return_value.text = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@mock.patch('requests.get')
 | 
					@mock.patch('requests.get')
 | 
				
			||||||
@mock.patch('requests.post')
 | 
					@mock.patch('requests.post')
 | 
				
			||||||
def test_notify_pushover_plugin(mock_post, mock_get):
 | 
					def test_notify_pushover_plugin(mock_post, mock_get):
 | 
				
			||||||
| 
						 | 
					@ -2456,54 +2623,6 @@ def test_notify_rocketchat_plugin(mock_post, mock_get):
 | 
				
			||||||
    assert obj.logout() is False
 | 
					    assert obj.logout() is False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@mock.patch('requests.get')
 | 
					 | 
				
			||||||
@mock.patch('requests.post')
 | 
					 | 
				
			||||||
def test_notify_toasty_plugin(mock_post, mock_get):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    API: NotifyToasty() Extra Checks
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Support strings
 | 
					 | 
				
			||||||
    devices = 'device1,device2,,,,'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # User
 | 
					 | 
				
			||||||
    user = 'l2g'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Prepare Mock
 | 
					 | 
				
			||||||
    mock_get.return_value = requests.Request()
 | 
					 | 
				
			||||||
    mock_post.return_value = requests.Request()
 | 
					 | 
				
			||||||
    mock_post.return_value.status_code = requests.codes.ok
 | 
					 | 
				
			||||||
    mock_get.return_value.status_code = requests.codes.ok
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        obj = plugins.NotifyToasty(user=user, devices=None)
 | 
					 | 
				
			||||||
        # No devices specified
 | 
					 | 
				
			||||||
        assert(False)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    except TypeError:
 | 
					 | 
				
			||||||
        # Exception should be thrown about the fact no token was specified
 | 
					 | 
				
			||||||
        assert(True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        obj = plugins.NotifyToasty(user=user, devices=set())
 | 
					 | 
				
			||||||
        # No devices specified
 | 
					 | 
				
			||||||
        assert(False)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    except TypeError:
 | 
					 | 
				
			||||||
        # Exception should be thrown about the fact no token was specified
 | 
					 | 
				
			||||||
        assert(True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    obj = plugins.NotifyToasty(user=user, devices=devices)
 | 
					 | 
				
			||||||
    assert(isinstance(obj, plugins.NotifyToasty))
 | 
					 | 
				
			||||||
    assert(len(obj.devices) == 2)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Support the handling of an empty and invalid URL strings
 | 
					 | 
				
			||||||
    assert(plugins.NotifyToasty.parse_url(None) is None)
 | 
					 | 
				
			||||||
    assert(plugins.NotifyToasty.parse_url('') is None)
 | 
					 | 
				
			||||||
    assert(plugins.NotifyToasty.parse_url(42) is None)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@mock.patch('requests.get')
 | 
					@mock.patch('requests.get')
 | 
				
			||||||
@mock.patch('requests.post')
 | 
					@mock.patch('requests.post')
 | 
				
			||||||
def test_notify_telegram_plugin(mock_post, mock_get):
 | 
					def test_notify_telegram_plugin(mock_post, mock_get):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue