mirror of https://github.com/caronc/apprise
				
				
				
			
							parent
							
								
									7862110fac
								
							
						
					
					
						commit
						5bf89aeefc
					
				| 
						 | 
				
			
			@ -77,6 +77,7 @@ The table below identifies the services this tool supports and some example serv
 | 
			
		|||
| [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<br />rocket://webhook@hostname<br />rockets://webhook@hostname/@User/#Channel
 | 
			
		||||
| [Ryver](https://github.com/caronc/apprise/wiki/Notify_ryver) | ryver://  | (TCP) 443   | ryver://Organization/Token<br />ryver://botname@Organization/Token
 | 
			
		||||
| [SendGrid](https://github.com/caronc/apprise/wiki/Notify_sendgrid) | sendgrid://  | (TCP) 443   | sendgrid://APIToken:FromEmail/<br />sendgrid://APIToken:FromEmail/ToEmail<br />sendgrid://APIToken:FromEmail/ToEmail1/ToEmail2/ToEmailN/
 | 
			
		||||
| [ServerChan](https://github.com/caronc/apprise/wiki/Notify_serverchan) | serverchan://   | (TCP) 443    | serverchan://token/
 | 
			
		||||
| [SimplePush](https://github.com/caronc/apprise/wiki/Notify_simplepush) | spush://   | (TCP) 443    | spush://apikey<br />spush://salt:password@apikey<br />spush://apikey?event=Apprise
 | 
			
		||||
| [Slack](https://github.com/caronc/apprise/wiki/Notify_slack) | slack://  | (TCP) 443   | slack://TokenA/TokenB/TokenC/<br />slack://TokenA/TokenB/TokenC/Channel<br />slack://botname@TokenA/TokenB/TokenC/Channel<br />slack://user@TokenA/TokenB/TokenC/Channel1/Channel2/ChannelN
 | 
			
		||||
| [SMTP2Go](https://github.com/caronc/apprise/wiki/Notify_smtp2go) | smtp2go:// | (TCP) 443 | smtp2go://user@hostname/apikey<br />smtp2go://user@hostname/apikey/email<br />smtp2go://user@hostname/apikey/email1/email2/emailN<br />smtp2go://user@hostname/apikey/?name="From%20User"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,173 @@
 | 
			
		|||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2020 Chris Caron <xuzheliang135@qq.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 ..common import NotifyType
 | 
			
		||||
from .NotifyBase import NotifyBase
 | 
			
		||||
from ..utils import validate_regex
 | 
			
		||||
from ..AppriseLocale import gettext_lazy as _
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Register at https://sct.ftqq.com/
 | 
			
		||||
#   - do as the page describe and you will get the token
 | 
			
		||||
 | 
			
		||||
# Syntax:
 | 
			
		||||
#  schan://{access_token}/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NotifyServerChan(NotifyBase):
 | 
			
		||||
    """
 | 
			
		||||
    A wrapper for ServerChan Notifications
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    # The default descriptive name associated with the Notification
 | 
			
		||||
    service_name = 'ServerChan'
 | 
			
		||||
 | 
			
		||||
    # The services URL
 | 
			
		||||
    service_url = 'https://sct.ftqq.com/'
 | 
			
		||||
 | 
			
		||||
    # All notification requests are secure
 | 
			
		||||
    secure_protocol = 'schan'
 | 
			
		||||
 | 
			
		||||
    # A URL that takes you to the setup/help of the specific protocol
 | 
			
		||||
    setup_url = 'https://github.com/caronc/apprise/wiki/Notify_serverchan'
 | 
			
		||||
 | 
			
		||||
    # ServerChan API
 | 
			
		||||
    notify_url = 'https://sctapi.ftqq.com/{token}.send'
 | 
			
		||||
 | 
			
		||||
    # Define object templates
 | 
			
		||||
    templates = (
 | 
			
		||||
        '{schema}://{token}/',
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    # Define our template tokens
 | 
			
		||||
    template_tokens = dict(NotifyBase.template_tokens, **{
 | 
			
		||||
        'token': {
 | 
			
		||||
            'name': _('Token'),
 | 
			
		||||
            'type': 'string',
 | 
			
		||||
            'private': True,
 | 
			
		||||
            'required': True,
 | 
			
		||||
            'regex': (r'^[a-z0-9]+$', 'i'),
 | 
			
		||||
        },
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    def __init__(self, token, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Initialize ServerChan Object
 | 
			
		||||
        """
 | 
			
		||||
        super(NotifyServerChan, self).__init__(**kwargs)
 | 
			
		||||
 | 
			
		||||
        # Token (associated with project)
 | 
			
		||||
        self.token = validate_regex(
 | 
			
		||||
            token, *self.template_tokens['token']['regex'])
 | 
			
		||||
        if not self.token:
 | 
			
		||||
            msg = 'An invalid ServerChan API Token ' \
 | 
			
		||||
                  '({}) was specified.'.format(token)
 | 
			
		||||
            self.logger.warning(msg)
 | 
			
		||||
            raise TypeError(msg)
 | 
			
		||||
 | 
			
		||||
    def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        Perform ServerChan Notification
 | 
			
		||||
        """
 | 
			
		||||
        payload = {
 | 
			
		||||
            'title': title,
 | 
			
		||||
            'desp': body,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        # Our Notification URL
 | 
			
		||||
        notify_url = self.notify_url.format(token=self.token)
 | 
			
		||||
 | 
			
		||||
        # Some Debug Logging
 | 
			
		||||
        self.logger.debug('ServerChan URL: {} (cert_verify={})'.format(
 | 
			
		||||
            notify_url, self.verify_certificate))
 | 
			
		||||
        self.logger.debug('ServerChan Payload: {}'.format(payload))
 | 
			
		||||
 | 
			
		||||
        # Always call throttle before any remote server i/o is made
 | 
			
		||||
        self.throttle()
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            r = requests.post(
 | 
			
		||||
                notify_url,
 | 
			
		||||
                data=payload,
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            if r.status_code != requests.codes.ok:
 | 
			
		||||
                # We had a problem
 | 
			
		||||
                status_str = \
 | 
			
		||||
                    NotifyServerChan.http_response_code_lookup(
 | 
			
		||||
                        r.status_code)
 | 
			
		||||
 | 
			
		||||
                self.logger.warning(
 | 
			
		||||
                    'Failed to send ServerChan notification: '
 | 
			
		||||
                    '{}{}error={}.'.format(
 | 
			
		||||
                        status_str,
 | 
			
		||||
                        ', ' if status_str else '',
 | 
			
		||||
                        r.status_code))
 | 
			
		||||
 | 
			
		||||
                self.logger.debug(
 | 
			
		||||
                    'Response Details:\r\n{}'.format(r.content))
 | 
			
		||||
                return False
 | 
			
		||||
 | 
			
		||||
            else:
 | 
			
		||||
                self.logger.info('Sent ServerChan notification.')
 | 
			
		||||
 | 
			
		||||
        except requests.RequestException as e:
 | 
			
		||||
            self.logger.warning(
 | 
			
		||||
                'A Connection error occured sending ServerChan '
 | 
			
		||||
                'notification.'
 | 
			
		||||
            )
 | 
			
		||||
            self.logger.debug('Socket Exception: %s' % str(e))
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        return True
 | 
			
		||||
 | 
			
		||||
    def url(self, privacy=False):
 | 
			
		||||
        """
 | 
			
		||||
        Returns the URL built dynamically based on specified arguments.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        return '{schema}://{token}'.format(
 | 
			
		||||
            schema=self.secure_protocol,
 | 
			
		||||
            token=self.pprint(self.token, privacy, safe=''))
 | 
			
		||||
 | 
			
		||||
    @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, verify_host=False)
 | 
			
		||||
        if not results:
 | 
			
		||||
            # We're done early as we couldn't parse the URL
 | 
			
		||||
            return results
 | 
			
		||||
 | 
			
		||||
        pattern = 'schan://([a-zA-Z0-9]+)/' + \
 | 
			
		||||
                  ('?' if not url.endswith('/') else '')
 | 
			
		||||
        result = re.match(pattern, url)
 | 
			
		||||
        results['token'] = result.group(1) if result else ''
 | 
			
		||||
        return results
 | 
			
		||||
| 
						 | 
				
			
			@ -53,8 +53,8 @@ IFTTT, Join, Kavenegar, KODI, Kumulos, LaMetric, MacOSX, Mailgun, Mattermost,
 | 
			
		|||
Matrix, Microsoft Windows, Microsoft Teams, MessageBird, MQTT, MSG91, MyAndroid,
 | 
			
		||||
Nexmo, Nextcloud, Notica, Notifico, Office365, OneSignal, Opsgenie,
 | 
			
		||||
ParsePlatform, PopcornNotify, Prowl, Pushalot, PushBullet, Pushjet, Pushover,
 | 
			
		||||
PushSafer, Reddit, Rocket.Chat, SendGrid, SimplePush, Sinch, Slack, SMTP2Go,
 | 
			
		||||
Spontit, SparkPost, Super Toasty, Streamlabs, Stride, Syslog, Techulus Push,
 | 
			
		||||
PushSafer, Reddit, Rocket.Chat, SendGrid, ServerChan, SimplePush, Sinch, Slack,
 | 
			
		||||
SMTP2Go, Spontit, SparkPost, Super Toasty, Streamlabs, Stride, Syslog, Techulus Push,
 | 
			
		||||
Telegram, Twilio, Twitter, Twist, XBMC, XMPP, Webex Teams}
 | 
			
		||||
 | 
			
		||||
Name:           python-%{pypi_name}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										4
									
								
								setup.py
								
								
								
								
							
							
						
						
									
										4
									
								
								setup.py
								
								
								
								
							| 
						 | 
				
			
			@ -75,8 +75,8 @@ setup(
 | 
			
		|||
        'LaMetric MacOS Mailgun Matrix Mattermost MessageBird MQTT MSG91 '
 | 
			
		||||
        'Nexmo Nextcloud Notica Notifico Office365 OneSignal Opsgenie '
 | 
			
		||||
        'ParsePlatform PopcornNotify Prowl PushBullet Pushjet Pushed '
 | 
			
		||||
        'Pushover PushSafer Reddit Rocket.Chat Ryver SendGrid SimplePush '
 | 
			
		||||
        'Sinch Slack SMTP2Go SparkPost Spontit Streamlabs '
 | 
			
		||||
        'Pushover PushSafer Reddit Rocket.Chat Ryver SendGrid ServerChan '
 | 
			
		||||
        'SimplePush Sinch Slack SMTP2Go SparkPost Spontit Streamlabs '
 | 
			
		||||
        'Stride Syslog Techulus '
 | 
			
		||||
        'Telegram Twilio Twist Twitter XBMC MSTeams Microsoft Windows Webex '
 | 
			
		||||
        'CLI API',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,71 @@
 | 
			
		|||
# -*- coding: utf-8 -*-
 | 
			
		||||
#
 | 
			
		||||
# Copyright (C) 2021 Chris Caron <xuzheliang135@qq.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.
 | 
			
		||||
from apprise import plugins
 | 
			
		||||
from helpers import AppriseURLTester
 | 
			
		||||
 | 
			
		||||
# Disable logging for a cleaner testing output
 | 
			
		||||
import logging
 | 
			
		||||
logging.disable(logging.CRITICAL)
 | 
			
		||||
 | 
			
		||||
# Our Testing URLs
 | 
			
		||||
apprise_url_tests = (
 | 
			
		||||
    ('schan://', {
 | 
			
		||||
        # No Access Token specified
 | 
			
		||||
        'instance': TypeError,
 | 
			
		||||
    }),
 | 
			
		||||
    ('schan://a_bd_/', {
 | 
			
		||||
        # invalid Access Token
 | 
			
		||||
        'instance': TypeError,
 | 
			
		||||
    }),
 | 
			
		||||
    ('schan://12345678', {
 | 
			
		||||
        # access token
 | 
			
		||||
        'instance': plugins.NotifyServerChan,
 | 
			
		||||
 | 
			
		||||
        # Our expected url(privacy=True) startswith() response:
 | 
			
		||||
        'privacy_url': 'schan://1...8',
 | 
			
		||||
    }),
 | 
			
		||||
    ('schan://{}'.format('a' * 8), {
 | 
			
		||||
        'instance': plugins.NotifyServerChan,
 | 
			
		||||
        # throw a bizzare code forcing us to fail to look it up
 | 
			
		||||
        'response': False,
 | 
			
		||||
        'requests_response_code': 999,
 | 
			
		||||
    }),
 | 
			
		||||
    ('schan://{}'.format('a' * 8), {
 | 
			
		||||
        'instance': plugins.NotifyServerChan,
 | 
			
		||||
        # Throws a series of connection and transfer exceptions when this flag
 | 
			
		||||
        # is set and tests that we gracfully handle them
 | 
			
		||||
        'test_requests_exceptions': True,
 | 
			
		||||
    }),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_plugin_serverchan_urls():
 | 
			
		||||
    """
 | 
			
		||||
    NotifyServerChan() Apprise URLs
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    # Run our general tests
 | 
			
		||||
    AppriseURLTester(tests=apprise_url_tests).run_all()
 | 
			
		||||
		Loading…
	
		Reference in New Issue