mirror of
https://github.com/caronc/apprise.git
synced 2024-11-21 15:43:27 +01:00
Pushjet refactored; dropped pushjet library and deps (#147)
This commit is contained in:
parent
f29af0c55b
commit
a420375cc7
@ -1,5 +1,5 @@
|
||||
[run]
|
||||
omit=*/gntp/*,*/pushjet/*
|
||||
omit=*/gntp/*
|
||||
disable_warnings = no-data-collected
|
||||
branch = True
|
||||
source =
|
||||
|
@ -51,7 +51,7 @@ The table below identifies the services this tool supports and some example serv
|
||||
| [Microsoft Teams](https://github.com/caronc/apprise/wiki/Notify_msteams) | msteams:// | (TCP) 443 | msteams://TokenA/TokenB/TokenC/
|
||||
| [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
|
||||
| [Pushjet](https://github.com/caronc/apprise/wiki/Notify_pushjet) | pjet:// or pjets:// | (TCP) 80 or 443 | pjet://secret@hostname<br />pjet://secret@hostname:port<br />pjets://secret@hostname<br />pjets://secret@hostname:port
|
||||
| [Pushjet](https://github.com/caronc/apprise/wiki/Notify_pushjet) | pjet:// or pjets:// | (TCP) 80 or 443 | pjet://hostname/secret<br />pjet://hostname:port/secret<br />pjets://secret@hostname/secret<br />pjets://hostname:port/secret
|
||||
| [Push (Techulus)](https://github.com/caronc/apprise/wiki/Notify_techulus) | push:// | (TCP) 443 | push://apikey/
|
||||
| [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
|
||||
|
292
apprise/plugins/NotifyPushjet.py
Normal file
292
apprise/plugins/NotifyPushjet.py
Normal file
@ -0,0 +1,292 @@
|
||||
# -*- 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 requests
|
||||
from json import dumps
|
||||
|
||||
from .NotifyBase import NotifyBase
|
||||
from ..common import NotifyType
|
||||
from ..AppriseLocale import gettext_lazy as _
|
||||
|
||||
|
||||
class NotifyPushjet(NotifyBase):
|
||||
"""
|
||||
A wrapper for Pushjet Notifications
|
||||
"""
|
||||
|
||||
# The default descriptive name associated with the Notification
|
||||
service_name = 'Pushjet'
|
||||
|
||||
# The default protocol
|
||||
protocol = 'pjet'
|
||||
|
||||
# The default secure protocol
|
||||
secure_protocol = 'pjets'
|
||||
|
||||
# A URL that takes you to the setup/help of the specific protocol
|
||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_pushjet'
|
||||
|
||||
# Disable throttle rate for Pushjet requests since they are normally
|
||||
# local anyway (the remote/online service is no more)
|
||||
request_rate_per_sec = 0
|
||||
|
||||
# Define object templates
|
||||
templates = (
|
||||
'{schema}://{host}:{port}/{secret_key}',
|
||||
'{schema}://{host}/{secret_key}',
|
||||
'{schema}://{user}:{password}@{host}:{port}/{secret_key}',
|
||||
'{schema}://{user}:{password}@{host}/{secret_key}',
|
||||
|
||||
# Kept for backwards compatibility; will be depricated eventually
|
||||
'{schema}://{secret_key}@{host}',
|
||||
'{schema}://{secret_key}@{host}:{port}',
|
||||
)
|
||||
|
||||
# Define our tokens
|
||||
template_tokens = dict(NotifyBase.template_tokens, **{
|
||||
'host': {
|
||||
'name': _('Hostname'),
|
||||
'type': 'string',
|
||||
'required': True,
|
||||
},
|
||||
'port': {
|
||||
'name': _('Port'),
|
||||
'type': 'int',
|
||||
'min': 1,
|
||||
'max': 65535,
|
||||
},
|
||||
'secret_key': {
|
||||
'name': _('Secret Key'),
|
||||
'type': 'string',
|
||||
'required': True,
|
||||
'private': True,
|
||||
},
|
||||
'user': {
|
||||
'name': _('Username'),
|
||||
'type': 'string',
|
||||
},
|
||||
'password': {
|
||||
'name': _('Password'),
|
||||
'type': 'string',
|
||||
'private': True,
|
||||
},
|
||||
})
|
||||
|
||||
template_args = dict(NotifyBase.template_args, **{
|
||||
'secret': {
|
||||
'alias_of': 'secret_key',
|
||||
},
|
||||
})
|
||||
|
||||
def __init__(self, secret_key, **kwargs):
|
||||
"""
|
||||
Initialize Pushjet Object
|
||||
"""
|
||||
super(NotifyPushjet, self).__init__(**kwargs)
|
||||
|
||||
if not secret_key:
|
||||
# You must provide a Pushjet key to work with
|
||||
msg = 'You must specify a Pushjet Secret Key.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# store our key
|
||||
self.secret_key = secret_key
|
||||
|
||||
def url(self):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
"""
|
||||
|
||||
# Define any arguments set
|
||||
args = {
|
||||
'format': self.notify_format,
|
||||
'overflow': self.overflow_mode,
|
||||
'secret': self.secret_key,
|
||||
'verify': 'yes' if self.verify_certificate else 'no',
|
||||
}
|
||||
|
||||
default_port = 443 if self.secure else 80
|
||||
|
||||
# Determine Authentication
|
||||
auth = ''
|
||||
if self.user and self.password:
|
||||
auth = '{user}:{password}@'.format(
|
||||
user=NotifyPushjet.quote(self.user, safe=''),
|
||||
password=NotifyPushjet.quote(self.password, safe=''),
|
||||
)
|
||||
|
||||
return '{schema}://{auth}{hostname}{port}/?{args}'.format(
|
||||
schema=self.secure_protocol if self.secure else self.protocol,
|
||||
auth=auth,
|
||||
hostname=NotifyPushjet.quote(self.host, safe=''),
|
||||
port='' if self.port is None or self.port == default_port
|
||||
else ':{}'.format(self.port),
|
||||
args=NotifyPushjet.urlencode(args),
|
||||
)
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
Perform Pushjet Notification
|
||||
"""
|
||||
|
||||
params = {
|
||||
'secret': self.secret_key,
|
||||
}
|
||||
|
||||
# prepare Pushjet Object
|
||||
payload = {
|
||||
'message': body,
|
||||
'title': title,
|
||||
'link': None,
|
||||
'level': None,
|
||||
}
|
||||
|
||||
headers = {
|
||||
'User-Agent': self.app_id,
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
|
||||
}
|
||||
|
||||
auth = None
|
||||
if self.user:
|
||||
auth = (self.user, self.password)
|
||||
|
||||
notify_url = '{schema}://{host}{port}/message/'.format(
|
||||
schema="https" if self.secure else "http",
|
||||
host=self.host,
|
||||
port=':{}'.format(self.port) if self.port else '')
|
||||
|
||||
self.logger.debug('Pushjet POST URL: %s (cert_verify=%r)' % (
|
||||
notify_url, self.verify_certificate,
|
||||
))
|
||||
self.logger.debug('Pushjet Payload: %s' % str(payload))
|
||||
|
||||
# Always call throttle before any remote server i/o is made
|
||||
self.throttle()
|
||||
|
||||
try:
|
||||
r = requests.post(
|
||||
notify_url,
|
||||
params=params,
|
||||
data=dumps(payload),
|
||||
headers=headers,
|
||||
auth=auth,
|
||||
verify=self.verify_certificate,
|
||||
)
|
||||
if r.status_code != requests.codes.ok:
|
||||
# We had a problem
|
||||
status_str = \
|
||||
NotifyPushjet.http_response_code_lookup(r.status_code)
|
||||
|
||||
self.logger.warning(
|
||||
'Failed to send Pushjet notification: '
|
||||
'{}{}error={}.'.format(
|
||||
status_str,
|
||||
', ' if status_str else '',
|
||||
r.status_code))
|
||||
|
||||
self.logger.debug('Response Details:\r\n{}'.format(r.content))
|
||||
|
||||
# Return; we're done
|
||||
return False
|
||||
|
||||
else:
|
||||
self.logger.info('Sent Pushjet notification.')
|
||||
|
||||
except requests.RequestException as e:
|
||||
self.logger.warning(
|
||||
'A Connection error occured sending Pushjet '
|
||||
'notification to %s.' % self.host)
|
||||
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.
|
||||
|
||||
Syntax:
|
||||
pjet://hostname/secret_key
|
||||
pjet://hostname:port/secret_key
|
||||
pjet://user:pass@hostname/secret_key
|
||||
pjet://user:pass@hostname:port/secret_key
|
||||
pjets://hostname/secret_key
|
||||
pjets://hostname:port/secret_key
|
||||
pjets://user:pass@hostname/secret_key
|
||||
pjets://user:pass@hostname:port/secret_key
|
||||
|
||||
Legacy (Depricated) Syntax:
|
||||
pjet://secret_key@hostname
|
||||
pjet://secret_key@hostname:port
|
||||
pjets://secret_key@hostname
|
||||
pjets://secret_key@hostname:port
|
||||
|
||||
"""
|
||||
results = NotifyBase.parse_url(url)
|
||||
|
||||
if not results:
|
||||
# We're done early as we couldn't load the results
|
||||
return results
|
||||
|
||||
try:
|
||||
# Retrieve our secret_key from the first entry in the url path
|
||||
results['secret_key'] = \
|
||||
NotifyPushjet.split_path(results['fullpath'])[0]
|
||||
|
||||
except IndexError:
|
||||
# no secret key specified
|
||||
results['secret_key'] = None
|
||||
|
||||
# Allow over-riding the secret by specifying it as an argument
|
||||
# this allows people who have http-auth infront to login
|
||||
# through it in addition to supporting the secret key
|
||||
if 'secret' in results['qsd'] and len(results['qsd']['secret']):
|
||||
results['secret_key'] = \
|
||||
NotifyPushjet.parse_list(results['qsd']['secret'])
|
||||
|
||||
if results.get('secret_key') is None:
|
||||
# Deprication Notice issued for v0.7.9
|
||||
NotifyPushjet.logger.deprecate(
|
||||
'The Pushjet URL contains secret_key in the user field'
|
||||
' which will be deprecated in an upcoming '
|
||||
'release. Please place this in the path of the URL instead.'
|
||||
)
|
||||
|
||||
# Store it as it's value based on the user field
|
||||
results['secret_key'] = \
|
||||
NotifyPushjet.unquote(results.get('user'))
|
||||
|
||||
# there is no way http-auth is enabled, be sure to unset the
|
||||
# current defined user (if present). This is done due to some
|
||||
# logic that takes place in the send() since we support http-auth.
|
||||
results['user'] = None
|
||||
results['password'] = None
|
||||
|
||||
return results
|
@ -1,175 +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
|
||||
from . import pushjet
|
||||
|
||||
from ..NotifyBase import NotifyBase
|
||||
from ...common import NotifyType
|
||||
from ...AppriseLocale import gettext_lazy as _
|
||||
|
||||
PUBLIC_KEY_RE = re.compile(
|
||||
r'^[a-z0-9]{4}-[a-z0-9]{6}-[a-z0-9]{12}-[a-z0-9]{5}-[a-z0-9]{9}$', re.I)
|
||||
|
||||
SECRET_KEY_RE = re.compile(r'^[a-z0-9]{32}$', re.I)
|
||||
|
||||
|
||||
class NotifyPushjet(NotifyBase):
|
||||
"""
|
||||
A wrapper for Pushjet Notifications
|
||||
"""
|
||||
|
||||
# The default descriptive name associated with the Notification
|
||||
service_name = 'Pushjet'
|
||||
|
||||
# The default protocol
|
||||
protocol = 'pjet'
|
||||
|
||||
# The default secure protocol
|
||||
secure_protocol = 'pjets'
|
||||
|
||||
# A URL that takes you to the setup/help of the specific protocol
|
||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_pushjet'
|
||||
|
||||
# Disable throttle rate for Pushjet requests since they are normally
|
||||
# local anyway (the remote/online service is no more)
|
||||
request_rate_per_sec = 0
|
||||
|
||||
# Define object templates
|
||||
templates = (
|
||||
'{schema}://{secret_key}@{host}',
|
||||
'{schema}://{secret_key}@{host}:{port}',
|
||||
)
|
||||
|
||||
# Define our tokens
|
||||
template_tokens = dict(NotifyBase.template_tokens, **{
|
||||
'host': {
|
||||
'name': _('Hostname'),
|
||||
'type': 'string',
|
||||
'required': True,
|
||||
},
|
||||
'port': {
|
||||
'name': _('Port'),
|
||||
'type': 'int',
|
||||
'min': 1,
|
||||
'max': 65535,
|
||||
},
|
||||
'secret_key': {
|
||||
'name': _('Secret Key'),
|
||||
'type': 'string',
|
||||
'required': True,
|
||||
'private': True,
|
||||
},
|
||||
})
|
||||
|
||||
def __init__(self, secret_key, **kwargs):
|
||||
"""
|
||||
Initialize Pushjet Object
|
||||
"""
|
||||
super(NotifyPushjet, self).__init__(**kwargs)
|
||||
|
||||
if not secret_key:
|
||||
# You must provide a Pushjet key to work with
|
||||
msg = 'You must specify a Pushjet Secret Key.'
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# store our key
|
||||
self.secret_key = secret_key
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
Perform Pushjet Notification
|
||||
"""
|
||||
# Always call throttle before any remote server i/o is made
|
||||
self.throttle()
|
||||
|
||||
server = "https://" if self.secure else "http://"
|
||||
|
||||
server += self.host
|
||||
if self.port:
|
||||
server += ":" + str(self.port)
|
||||
|
||||
try:
|
||||
api = pushjet.pushjet.Api(server)
|
||||
service = api.Service(secret_key=self.secret_key)
|
||||
|
||||
service.send(body, title)
|
||||
self.logger.info('Sent Pushjet notification.')
|
||||
|
||||
except (pushjet.errors.PushjetError, ValueError) as e:
|
||||
self.logger.warning('Failed to send Pushjet notification.')
|
||||
self.logger.debug('Pushjet Exception: %s' % str(e))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def url(self):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
"""
|
||||
|
||||
# Define any arguments set
|
||||
args = {
|
||||
'format': self.notify_format,
|
||||
'overflow': self.overflow_mode,
|
||||
'verify': 'yes' if self.verify_certificate else 'no',
|
||||
}
|
||||
|
||||
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=NotifyPushjet.quote(self.secret_key, safe=''),
|
||||
hostname=NotifyPushjet.quote(self.host, safe=''),
|
||||
port='' if self.port is None or self.port == default_port
|
||||
else ':{}'.format(self.port),
|
||||
args=NotifyPushjet.urlencode(args),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def parse_url(url):
|
||||
"""
|
||||
Parses the URL and returns enough arguments that can allow
|
||||
us to substantiate this object.
|
||||
|
||||
Syntax:
|
||||
pjet://secret_key@hostname
|
||||
pjet://secret_key@hostname:port
|
||||
pjets://secret_key@hostname
|
||||
pjets://secret_key@hostname:port
|
||||
|
||||
"""
|
||||
results = NotifyBase.parse_url(url)
|
||||
|
||||
if not results:
|
||||
# We're done early as we couldn't load the results
|
||||
return results
|
||||
|
||||
# Store it as it's value
|
||||
results['secret_key'] = \
|
||||
NotifyPushjet.unquote(results.get('user'))
|
||||
|
||||
return results
|
@ -1,6 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""A Python API for Pushjet. Send notifications to your phone from Python scripts!"""
|
||||
|
||||
from .pushjet import Service, Device, Subscription, Message, Api
|
||||
from .errors import PushjetError, AccessError, NonexistentError, SubscriptionError, RequestError, ServerError
|
@ -1,48 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
|
||||
from requests import RequestException
|
||||
|
||||
import sys
|
||||
if sys.version_info[0] < 3:
|
||||
# This is built into Python 3.
|
||||
class ConnectionError(Exception):
|
||||
pass
|
||||
|
||||
class PushjetError(Exception):
|
||||
"""All the errors inherit from this. Therefore, ``except PushjetError`` catches all errors."""
|
||||
|
||||
class AccessError(PushjetError):
|
||||
"""Raised when a secret key is missing for a service method that needs one."""
|
||||
|
||||
class NonexistentError(PushjetError):
|
||||
"""Raised when an attempt to access a nonexistent service is made."""
|
||||
|
||||
class SubscriptionError(PushjetError):
|
||||
"""Raised when an attempt to subscribe to a service that's already subscribed to,
|
||||
or to unsubscribe from a service that isn't subscribed to, is made."""
|
||||
|
||||
class RequestError(PushjetError, ConnectionError):
|
||||
"""Raised if something goes wrong in the connection to the API server.
|
||||
Inherits from ``ConnectionError`` on Python 3, and can therefore be caught
|
||||
with ``except ConnectionError`` there.
|
||||
|
||||
:ivar requests_exception: The underlying `requests <http://docs.python-requests.org>`__
|
||||
exception. Access this if you want to handle different HTTP request errors in different ways.
|
||||
"""
|
||||
|
||||
def __str__(self):
|
||||
return "requests.{error}: {description}".format(
|
||||
error=self.requests_exception.__class__.__name__,
|
||||
description=str(self.requests_exception)
|
||||
)
|
||||
|
||||
def __init__(self, requests_exception):
|
||||
self.requests_exception = requests_exception
|
||||
|
||||
class ServerError(PushjetError):
|
||||
"""Raised if the API server has an error while processing your request.
|
||||
This getting raised means there's a bug in the server! If you manage to
|
||||
track down what caused it, you can `open an issue on Pushjet's GitHub page
|
||||
<https://github.com/Pushjet/Pushjet-Server-Api/issues>`__.
|
||||
"""
|
@ -1,313 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
import requests
|
||||
from functools import partial
|
||||
|
||||
from six import text_type
|
||||
from six.moves.urllib.parse import urljoin
|
||||
|
||||
from .utilities import (
|
||||
NoNoneDict,
|
||||
requires_secret_key, with_api_bound,
|
||||
is_valid_uuid, is_valid_public_key, is_valid_secret_key, repr_format
|
||||
)
|
||||
from .errors import NonexistentError, SubscriptionError, RequestError, ServerError
|
||||
|
||||
DEFAULT_API_URL = 'https://api.pushjet.io/'
|
||||
|
||||
class PushjetModel(object):
|
||||
_api = None # This is filled in later.
|
||||
|
||||
class Service(PushjetModel):
|
||||
"""A Pushjet service to send messages through. To receive messages, devices
|
||||
subscribe to these.
|
||||
|
||||
:param secret_key: The service's API key for write access. If provided,
|
||||
:func:`~pushjet.Service.send`, :func:`~pushjet.Service.edit`, and
|
||||
:func:`~pushjet.Service.delete` become available.
|
||||
Either this or the public key parameter must be present.
|
||||
:param public_key: The service's public API key for read access only.
|
||||
Either this or the secret key parameter must be present.
|
||||
|
||||
:ivar name: The name of the service.
|
||||
:ivar icon_url: The URL to the service's icon. May be ``None``.
|
||||
:ivar created: When the service was created, as seconds from epoch.
|
||||
:ivar secret_key: The service's secret API key, or ``None`` if the service is read-only.
|
||||
:ivar public_key: The service's public API key, to be used when subscribing to the service.
|
||||
"""
|
||||
|
||||
def __repr__(self):
|
||||
return "<Pushjet Service: \"{}\">".format(repr_format(self.name))
|
||||
|
||||
def __init__(self, secret_key=None, public_key=None):
|
||||
if secret_key is None and public_key is None:
|
||||
raise ValueError("Either a secret key or public key "
|
||||
"must be provided.")
|
||||
elif secret_key and not is_valid_secret_key(secret_key):
|
||||
raise ValueError("Invalid secret key provided.")
|
||||
elif public_key and not is_valid_public_key(public_key):
|
||||
raise ValueError("Invalid public key provided.")
|
||||
self.secret_key = text_type(secret_key) if secret_key else None
|
||||
self.public_key = text_type(public_key) if public_key else None
|
||||
self.refresh()
|
||||
|
||||
def _request(self, endpoint, method, is_secret, params=None, data=None):
|
||||
params = params or {}
|
||||
if is_secret:
|
||||
params['secret'] = self.secret_key
|
||||
else:
|
||||
params['service'] = self.public_key
|
||||
return self._api._request(endpoint, method, params, data)
|
||||
|
||||
@requires_secret_key
|
||||
def send(self, message, title=None, link=None, importance=None):
|
||||
"""Send a message to the service's subscribers.
|
||||
|
||||
:param message: The message body to be sent.
|
||||
:param title: (optional) The message's title. Messages can be without title.
|
||||
:param link: (optional) An URL to be sent with the message.
|
||||
:param importance: (optional) The priority level of the message. May be
|
||||
a number between 1 and 5, where 1 is least important and 5 is most.
|
||||
"""
|
||||
data = NoNoneDict({
|
||||
'message': message,
|
||||
'title': title,
|
||||
'link': link,
|
||||
'level': importance
|
||||
})
|
||||
self._request('message', 'POST', is_secret=True, data=data)
|
||||
|
||||
@requires_secret_key
|
||||
def edit(self, name=None, icon_url=None):
|
||||
"""Edit the service's attributes.
|
||||
|
||||
:param name: (optional) A new name to give the service.
|
||||
:param icon_url: (optional) A new URL to use as the service's icon URL.
|
||||
Set to an empty string to remove the service's icon entirely.
|
||||
"""
|
||||
data = NoNoneDict({
|
||||
'name': name,
|
||||
'icon': icon_url
|
||||
})
|
||||
if not data:
|
||||
return
|
||||
self._request('service', 'PATCH', is_secret=True, data=data)
|
||||
self.name = text_type(name)
|
||||
self.icon_url = text_type(icon_url)
|
||||
|
||||
@requires_secret_key
|
||||
def delete(self):
|
||||
"""Delete the service. Irreversible."""
|
||||
self._request('service', 'DELETE', is_secret=True)
|
||||
|
||||
def _update_from_data(self, data):
|
||||
self.name = data['name']
|
||||
self.icon_url = data['icon'] or None
|
||||
self.created = data['created']
|
||||
self.public_key = data['public']
|
||||
self.secret_key = data.get('secret', getattr(self, 'secret_key', None))
|
||||
|
||||
def refresh(self):
|
||||
"""Refresh the server's information, in case it could be edited from elsewhere.
|
||||
|
||||
:raises: :exc:`~pushjet.NonexistentError` if the service was deleted before refreshing.
|
||||
"""
|
||||
key_name = 'public'
|
||||
secret = False
|
||||
if self.secret_key is not None:
|
||||
key_name = 'secret'
|
||||
secret = True
|
||||
|
||||
status, response = self._request('service', 'GET', is_secret=secret)
|
||||
if status == requests.codes.NOT_FOUND:
|
||||
raise NonexistentError("A service with the provided {} key "
|
||||
"does not exist (anymore, at least).".format(key_name))
|
||||
self._update_from_data(response['service'])
|
||||
|
||||
@classmethod
|
||||
def _from_data(cls, data):
|
||||
# This might be a no-no, but I see little alternative if
|
||||
# different constructors with different parameters are needed,
|
||||
# *and* a default __init__ constructor should be present.
|
||||
# This, along with the subclassing for custom API URLs, may
|
||||
# very well be one of those pieces of code you look back at
|
||||
# years down the line - or maybe just a couple of weeks - and say
|
||||
# "what the heck was I thinking"? I assure you, though, future me.
|
||||
# This was the most reasonable thing to get the API + argspecs I wanted.
|
||||
obj = cls.__new__(cls)
|
||||
obj._update_from_data(data)
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def create(cls, name, icon_url=None):
|
||||
"""Create a new service.
|
||||
|
||||
:param name: The name of the new service.
|
||||
:param icon_url: (optional) An URL to an image to be used as the service's icon.
|
||||
:return: The newly-created :class:`~pushjet.Service`.
|
||||
"""
|
||||
data = NoNoneDict({
|
||||
'name': name,
|
||||
'icon': icon_url
|
||||
})
|
||||
_, response = cls._api._request('service', 'POST', data=data)
|
||||
return cls._from_data(response['service'])
|
||||
|
||||
class Device(PushjetModel):
|
||||
"""The "receiver" for messages. Subscribes to services and receives any
|
||||
messages they send.
|
||||
|
||||
:param uuid: The device's unique ID as a UUID. Does not need to be registered
|
||||
before using it. A UUID can be generated with ``uuid.uuid4()``, for example.
|
||||
:ivar uuid: The UUID the device was initialized with.
|
||||
"""
|
||||
|
||||
def __repr__(self):
|
||||
return "<Pushjet Device: {}>".format(self.uuid)
|
||||
|
||||
def __init__(self, uuid):
|
||||
uuid = text_type(uuid)
|
||||
if not is_valid_uuid(uuid):
|
||||
raise ValueError("Invalid UUID provided. Try uuid.uuid4().")
|
||||
self.uuid = text_type(uuid)
|
||||
|
||||
def _request(self, endpoint, method, params=None, data=None):
|
||||
params = (params or {})
|
||||
params['uuid'] = self.uuid
|
||||
return self._api._request(endpoint, method, params, data)
|
||||
|
||||
def subscribe(self, service):
|
||||
"""Subscribe the device to a service.
|
||||
|
||||
:param service: The service to subscribe to. May be a public key or a :class:`~pushjet.Service`.
|
||||
:return: The :class:`~pushjet.Service` subscribed to.
|
||||
|
||||
:raises: :exc:`~pushjet.NonexistentError` if the provided service does not exist.
|
||||
:raises: :exc:`~pushjet.SubscriptionError` if the provided service is already subscribed to.
|
||||
"""
|
||||
data = {}
|
||||
data['service'] = service.public_key if isinstance(service, Service) else service
|
||||
status, response = self._request('subscription', 'POST', data=data)
|
||||
if status == requests.codes.CONFLICT:
|
||||
raise SubscriptionError("The device is already subscribed to that service.")
|
||||
elif status == requests.codes.NOT_FOUND:
|
||||
raise NonexistentError("A service with the provided public key "
|
||||
"does not exist (anymore, at least).")
|
||||
return self._api.Service._from_data(response['service'])
|
||||
|
||||
def unsubscribe(self, service):
|
||||
"""Unsubscribe the device from a service.
|
||||
|
||||
:param service: The service to unsubscribe from. May be a public key or a :class:`~pushjet.Service`.
|
||||
:raises: :exc:`~pushjet.NonexistentError` if the provided service does not exist.
|
||||
:raises: :exc:`~pushjet.SubscriptionError` if the provided service isn't subscribed to.
|
||||
"""
|
||||
data = {}
|
||||
data['service'] = service.public_key if isinstance(service, Service) else service
|
||||
status, _ = self._request('subscription', 'DELETE', data=data)
|
||||
if status == requests.codes.CONFLICT:
|
||||
raise SubscriptionError("The device is not subscribed to that service.")
|
||||
elif status == requests.codes.NOT_FOUND:
|
||||
raise NonexistentError("A service with the provided public key "
|
||||
"does not exist (anymore, at least).")
|
||||
|
||||
def get_subscriptions(self):
|
||||
"""Get all the subscriptions the device has.
|
||||
|
||||
:return: A list of :class:`~pushjet.Subscription`.
|
||||
"""
|
||||
_, response = self._request('subscription', 'GET')
|
||||
subscriptions = []
|
||||
for subscription_dict in response['subscriptions']:
|
||||
subscriptions.append(Subscription(subscription_dict))
|
||||
return subscriptions
|
||||
|
||||
def get_messages(self):
|
||||
"""Get all new (that is, as of yet unretrieved) messages.
|
||||
|
||||
:return: A list of :class:`~pushjet.Message`.
|
||||
"""
|
||||
_, response = self._request('message', 'GET')
|
||||
messages = []
|
||||
for message_dict in response['messages']:
|
||||
messages.append(Message(message_dict))
|
||||
return messages
|
||||
|
||||
class Subscription(object):
|
||||
"""A subscription to a service, with the metadata that entails.
|
||||
|
||||
:ivar service: The service the subscription is to, as a :class:`~pushjet.Service`.
|
||||
:ivar time_subscribed: When the subscription was made, as seconds from epoch.
|
||||
:ivar last_checked: When the device last retrieved messages from the subscription,
|
||||
as seconds from epoch.
|
||||
:ivar device_uuid: The UUID of the device that owns the subscription.
|
||||
"""
|
||||
|
||||
def __repr__(self):
|
||||
return "<Pushjet Subscription to service \"{}\">".format(repr_format(self.service.name))
|
||||
|
||||
def __init__(self, subscription_dict):
|
||||
self.service = Service._from_data(subscription_dict['service'])
|
||||
self.time_subscribed = subscription_dict['timestamp']
|
||||
self.last_checked = subscription_dict['timestamp_checked']
|
||||
self.device_uuid = subscription_dict['uuid'] # Not sure this is needed, but...
|
||||
|
||||
class Message(object):
|
||||
"""A message received from a service.
|
||||
|
||||
:ivar message: The message body.
|
||||
:ivar title: The message title. May be ``None``.
|
||||
:ivar link: The URL the message links to. May be ``None``.
|
||||
:ivar time_sent: When the message was sent, as seconds from epoch.
|
||||
:ivar importance: The message's priority level between 1 and 5, where 1 is
|
||||
least important and 5 is most.
|
||||
:ivar service: The :class:`~pushjet.Service` that sent the message.
|
||||
"""
|
||||
|
||||
def __repr__(self):
|
||||
return "<Pushjet Message: \"{}\">".format(repr_format(self.title or self.message))
|
||||
|
||||
def __init__(self, message_dict):
|
||||
self.message = message_dict['message']
|
||||
self.title = message_dict['title'] or None
|
||||
self.link = message_dict['link'] or None
|
||||
self.time_sent = message_dict['timestamp']
|
||||
self.importance = message_dict['level']
|
||||
self.service = Service._from_data(message_dict['service'])
|
||||
|
||||
class Api(object):
|
||||
"""An API with a custom URL. Use this if you're connecting to a self-hosted
|
||||
Pushjet API instance, or a non-standard one in general.
|
||||
|
||||
:param url: The URL to the API instance.
|
||||
:ivar url: The URL to the API instance, as supplied.
|
||||
"""
|
||||
|
||||
def __repr__(self):
|
||||
return "<Pushjet Api: {}>".format(self.url).encode(sys.stdout.encoding, errors='replace')
|
||||
|
||||
def __init__(self, url):
|
||||
self.url = text_type(url)
|
||||
self.Service = with_api_bound(Service, self)
|
||||
self.Device = with_api_bound(Device, self)
|
||||
|
||||
def _request(self, endpoint, method, params=None, data=None):
|
||||
url = urljoin(self.url, endpoint)
|
||||
try:
|
||||
r = requests.request(method, url, params=params, data=data)
|
||||
except requests.RequestException as e:
|
||||
raise RequestError(e)
|
||||
status = r.status_code
|
||||
if status == requests.codes.INTERNAL_SERVER_ERROR:
|
||||
raise ServerError(
|
||||
"An error occurred in the server while processing your request. "
|
||||
"This should probably be reported to: "
|
||||
"https://github.com/Pushjet/Pushjet-Server-Api/issues"
|
||||
)
|
||||
try:
|
||||
response = r.json()
|
||||
except ValueError:
|
||||
response = {}
|
||||
return status, response
|
||||
|
@ -1,64 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import re
|
||||
import sys
|
||||
from decorator import decorator
|
||||
from .errors import AccessError
|
||||
|
||||
# Help class(...es? Nah. Just singular for now.)
|
||||
|
||||
class NoNoneDict(dict):
|
||||
"""A dict that ignores values that are None. Not completely API-compatible
|
||||
with dict, but contains all that's needed.
|
||||
"""
|
||||
def __repr__(self):
|
||||
return "NoNoneDict({dict})".format(dict=dict.__repr__(self))
|
||||
|
||||
def __init__(self, initial={}):
|
||||
self.update(initial)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if value is not None:
|
||||
dict.__setitem__(self, key, value)
|
||||
|
||||
def update(self, data):
|
||||
for key, value in data.items():
|
||||
self[key] = value
|
||||
|
||||
# Decorators / factories
|
||||
|
||||
@decorator
|
||||
def requires_secret_key(func, self, *args, **kwargs):
|
||||
"""Raise an error if the method is called without a secret key."""
|
||||
if self.secret_key is None:
|
||||
raise AccessError("The Service doesn't have a secret "
|
||||
"key provided, and therefore lacks write permission.")
|
||||
return func(self, *args, **kwargs)
|
||||
|
||||
def with_api_bound(cls, api):
|
||||
new_cls = type(cls.__name__, (cls,), {
|
||||
'_api': api,
|
||||
'__doc__': (
|
||||
"Create a :class:`~pushjet.{name}` bound to the API. "
|
||||
"See :class:`pushjet.{name}` for documentation."
|
||||
).format(name=cls.__name__)
|
||||
})
|
||||
return new_cls
|
||||
|
||||
# Helper functions
|
||||
|
||||
UUID_RE = re.compile(r'^[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}$')
|
||||
PUBLIC_KEY_RE = re.compile(r'^[A-Za-z0-9]{4}-[A-Za-z0-9]{6}-[A-Za-z0-9]{12}-[A-Za-z0-9]{5}-[A-Za-z0-9]{9}$')
|
||||
SECRET_KEY_RE = re.compile(r'^[A-Za-z0-9]{32}$')
|
||||
|
||||
is_valid_uuid = lambda s: UUID_RE.match(s) is not None
|
||||
is_valid_public_key = lambda s: PUBLIC_KEY_RE.match(s) is not None
|
||||
is_valid_secret_key = lambda s: SECRET_KEY_RE.match(s) is not None
|
||||
|
||||
def repr_format(s):
|
||||
s = s.replace('\n', ' ').replace('\r', '')
|
||||
original_length = len(s)
|
||||
s = s[:30]
|
||||
s += '...' if len(s) != original_length else ''
|
||||
s = s.encode(sys.stdout.encoding, errors='replace')
|
||||
return s
|
@ -33,9 +33,6 @@ from os.path import abspath
|
||||
|
||||
# Used for testing
|
||||
from . import NotifyEmail as NotifyEmailBase
|
||||
|
||||
# Required until re-factored into base code
|
||||
from .NotifyPushjet import pushjet
|
||||
from .NotifyGrowl import gntp
|
||||
|
||||
# NotifyBase object is passed in as a module not class
|
||||
@ -62,9 +59,6 @@ __all__ = [
|
||||
|
||||
# gntp (used for NotifyGrowl Testing)
|
||||
'gntp',
|
||||
|
||||
# pushjet (used for NotifyPushjet Testing)
|
||||
'pushjet',
|
||||
]
|
||||
|
||||
# we mirror our base purely for the ability to reset everything; this
|
||||
|
@ -75,7 +75,6 @@ Summary: A simple wrapper to many popular notification services used today
|
||||
%{?python_provide:%python_provide python2-%{pypi_name}}
|
||||
|
||||
BuildRequires: python2-devel
|
||||
BuildRequires: python-decorator
|
||||
BuildRequires: python-requests
|
||||
BuildRequires: python2-requests-oauthlib
|
||||
BuildRequires: python-six
|
||||
@ -89,7 +88,6 @@ BuildRequires: python2-babel
|
||||
BuildRequires: python2-yaml
|
||||
%endif # using rhel7
|
||||
|
||||
Requires: python-decorator
|
||||
Requires: python-requests
|
||||
Requires: python2-requests-oauthlib
|
||||
Requires: python-six
|
||||
@ -134,7 +132,6 @@ Summary: A simple wrapper to many popular notification services used today
|
||||
%{?python_provide:%python_provide python%{python3_pkgversion}-%{pypi_name}}
|
||||
|
||||
BuildRequires: python%{python3_pkgversion}-devel
|
||||
BuildRequires: python%{python3_pkgversion}-decorator
|
||||
BuildRequires: python%{python3_pkgversion}-requests
|
||||
BuildRequires: python%{python3_pkgversion}-requests-oauthlib
|
||||
BuildRequires: python%{python3_pkgversion}-six
|
||||
@ -142,7 +139,6 @@ BuildRequires: python%{python3_pkgversion}-click >= 5.0
|
||||
BuildRequires: python%{python3_pkgversion}-markdown
|
||||
BuildRequires: python%{python3_pkgversion}-yaml
|
||||
BuildRequires: python%{python3_pkgversion}-babel
|
||||
Requires: python%{python3_pkgversion}-decorator
|
||||
Requires: python%{python3_pkgversion}-requests
|
||||
Requires: python%{python3_pkgversion}-requests-oauthlib
|
||||
Requires: python%{python3_pkgversion}-six
|
||||
|
@ -1,4 +1,3 @@
|
||||
decorator
|
||||
requests
|
||||
requests-oauthlib
|
||||
six
|
||||
|
@ -7,7 +7,7 @@ license_file = LICENSE
|
||||
|
||||
[flake8]
|
||||
# We exclude packages we don't maintain
|
||||
exclude = .eggs,.tox,gntp,pushjet
|
||||
exclude = .eggs,.tox,gntp
|
||||
ignore = E722,W503,W504
|
||||
statistics = true
|
||||
builtins = _
|
||||
|
@ -1,204 +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 six
|
||||
from apprise import plugins
|
||||
from apprise import NotifyType
|
||||
from apprise import Apprise
|
||||
|
||||
import mock
|
||||
|
||||
# Disable logging for a cleaner testing output
|
||||
import logging
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
|
||||
TEST_URLS = (
|
||||
##################################
|
||||
# NotifyPushjet
|
||||
##################################
|
||||
('pjet://', {
|
||||
'instance': None,
|
||||
}),
|
||||
('pjets://', {
|
||||
'instance': None,
|
||||
}),
|
||||
('pjet://:@/', {
|
||||
'instance': None,
|
||||
}),
|
||||
# You must specify a username
|
||||
('pjet://%s' % ('a' * 32), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Specify your own server
|
||||
('pjet://%s@localhost' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushjet,
|
||||
}),
|
||||
# Specify your own server with port
|
||||
('pjets://%s@localhost:8080' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushjet,
|
||||
}),
|
||||
('pjet://%s@localhost:8081' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushjet,
|
||||
# Throws a series of connection and transfer exceptions when this flag
|
||||
# is set and tests that we gracfully handle them
|
||||
'test_notify_exceptions': True,
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
@mock.patch('apprise.plugins.pushjet.pushjet.Service.send')
|
||||
@mock.patch('apprise.plugins.pushjet.pushjet.Service.refresh')
|
||||
def test_plugin(mock_refresh, mock_send):
|
||||
"""
|
||||
API: NotifyPushjet Plugin() (pt1)
|
||||
|
||||
"""
|
||||
|
||||
# iterate over our dictionary and test it out
|
||||
for (url, meta) in TEST_URLS:
|
||||
|
||||
# Our expected instance
|
||||
instance = meta.get('instance', None)
|
||||
|
||||
# Our expected server objects
|
||||
self = meta.get('self', None)
|
||||
|
||||
# Our expected Query response (True, False, or exception type)
|
||||
response = meta.get('response', True)
|
||||
|
||||
# Allow us to force the server response code to be something other then
|
||||
# the defaults
|
||||
response = meta.get(
|
||||
'response', True if response else False)
|
||||
|
||||
test_notify_exceptions = meta.get(
|
||||
'test_notify_exceptions', False)
|
||||
|
||||
test_exceptions = (
|
||||
plugins.pushjet.errors.AccessError(
|
||||
0, 'pushjet.AccessError() not handled'),
|
||||
plugins.pushjet.errors.NonexistentError(
|
||||
0, 'pushjet.NonexistentError() not handled'),
|
||||
plugins.pushjet.errors.SubscriptionError(
|
||||
0, 'gntp.SubscriptionError() not handled'),
|
||||
plugins.pushjet.errors.RequestError(
|
||||
'pushjet.RequestError() not handled'),
|
||||
)
|
||||
|
||||
try:
|
||||
obj = Apprise.instantiate(url, suppress_exceptions=False)
|
||||
|
||||
if obj is None:
|
||||
if instance is not None:
|
||||
# We're done (assuming this is what we were expecting)
|
||||
print("{} didn't instantiate itself "
|
||||
"(we expected it to)".format(url))
|
||||
assert False
|
||||
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):
|
||||
# We loaded okay; now lets make sure we can reverse this url
|
||||
assert(isinstance(obj.url(), six.string_types) 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):
|
||||
# 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():
|
||||
# Test that our object has the desired key
|
||||
assert(hasattr(key, obj))
|
||||
assert(getattr(key, obj) == val)
|
||||
|
||||
try:
|
||||
if test_notify_exceptions is False:
|
||||
# Store our response
|
||||
mock_send.return_value = response
|
||||
mock_send.side_effect = None
|
||||
|
||||
# check that we're as expected
|
||||
assert obj.notify(
|
||||
title='test', body='body',
|
||||
notify_type=NotifyType.INFO) == response
|
||||
|
||||
else:
|
||||
for exception in test_exceptions:
|
||||
mock_send.side_effect = exception
|
||||
mock_send.return_value = None
|
||||
try:
|
||||
assert obj.notify(
|
||||
title='test', body='body',
|
||||
notify_type=NotifyType.INFO) is False
|
||||
|
||||
except AssertionError:
|
||||
# Don't mess with these entries
|
||||
raise
|
||||
|
||||
except Exception:
|
||||
# We can't handle this exception type
|
||||
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
|
||||
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
|
||||
if(instance is None):
|
||||
raise
|
||||
|
||||
if not isinstance(e, instance):
|
||||
raise
|
@ -1509,6 +1509,67 @@ TEST_URLS = (
|
||||
'test_requests_exceptions': True,
|
||||
}),
|
||||
|
||||
##################################
|
||||
# NotifyPushjet
|
||||
##################################
|
||||
('pjet://', {
|
||||
'instance': None,
|
||||
}),
|
||||
('pjets://', {
|
||||
'instance': None,
|
||||
}),
|
||||
('pjet://:@/', {
|
||||
'instance': None,
|
||||
}),
|
||||
# You must specify a secret key
|
||||
('pjet://%s' % ('a' * 32), {
|
||||
'instance': TypeError,
|
||||
}),
|
||||
# Legacy method of logging in (soon to be depricated)
|
||||
('pjet://%s@localhost' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushjet,
|
||||
}),
|
||||
# The proper way to log in
|
||||
('pjet://user:pass@localhost/%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushjet,
|
||||
}),
|
||||
# The proper way to log in
|
||||
('pjets://localhost/%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushjet,
|
||||
}),
|
||||
# Specify your own server with login (secret= MUST be provided)
|
||||
('pjet://user:pass@localhost?secret=%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushjet,
|
||||
}),
|
||||
# Specify your own server with login (no secret = fail normally)
|
||||
# however this will work since we're providing depricated support
|
||||
# at this time so the 'user' get's picked up as being the secret_key
|
||||
('pjet://user:pass@localhost', {
|
||||
'instance': plugins.NotifyPushjet,
|
||||
}),
|
||||
# Specify your own server with port
|
||||
('pjets://localhost:8080/%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushjet,
|
||||
}),
|
||||
('pjets://localhost:8080/%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushjet,
|
||||
# force a failure
|
||||
'response': False,
|
||||
'requests_response_code': requests.codes.internal_server_error,
|
||||
}),
|
||||
('pjets://localhost:4343/%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushjet,
|
||||
# throw a bizzare code forcing us to fail to look it up
|
||||
'response': False,
|
||||
'requests_response_code': 999,
|
||||
}),
|
||||
('pjet://localhost:8081/%s' % ('a' * 32), {
|
||||
'instance': plugins.NotifyPushjet,
|
||||
# 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
|
||||
##################################
|
||||
|
Loading…
Reference in New Issue
Block a user