Added Spug Push Support

pull/1365/head
Chris Caron 2025-07-06 20:38:24 -04:00
parent 91d7b922cb
commit f033184e52
5 changed files with 257 additions and 4 deletions

View File

@ -101,6 +101,7 @@ SNS
SparkPost SparkPost
Splunk Splunk
Spike Spike
SpugPush
Streamlabs Streamlabs
Stride Stride
Synology Chat Synology Chat

View File

@ -127,6 +127,7 @@ The table below identifies the services this tool supports and some example serv
| [SparkPost](https://github.com/caronc/apprise/wiki/Notify_sparkpost) | sparkpost:// | (TCP) 443 | sparkpost://user@hostname/apikey<br />sparkpost://user@hostname/apikey/email<br />sparkpost://user@hostname/apikey/email1/email2/emailN<br />sparkpost://user@hostname/apikey/?name="From%20User" | [SparkPost](https://github.com/caronc/apprise/wiki/Notify_sparkpost) | sparkpost:// | (TCP) 443 | sparkpost://user@hostname/apikey<br />sparkpost://user@hostname/apikey/email<br />sparkpost://user@hostname/apikey/email1/email2/emailN<br />sparkpost://user@hostname/apikey/?name="From%20User"
| [Spike.sh](https://github.com/caronc/apprise/wiki/Notify_spike) | spike:// | (TCP) 443 | spike://Token | [Spike.sh](https://github.com/caronc/apprise/wiki/Notify_spike) | spike:// | (TCP) 443 | spike://Token
| [Splunk](https://github.com/caronc/apprise/wiki/Notify_splunk) | splunk:// or victorops:/ | (TCP) 443 | splunk://route_key@apikey<br />splunk://route_key@apikey/entity_id | [Splunk](https://github.com/caronc/apprise/wiki/Notify_splunk) | splunk:// or victorops:/ | (TCP) 443 | splunk://route_key@apikey<br />splunk://route_key@apikey/entity_id
| [Spug Push](https://github.com/caronc/apprise/wiki/Notify_spugpush) | spugpush:// | (TCP) 443 | spugpush://Token
| [Streamlabs](https://github.com/caronc/apprise/wiki/Notify_streamlabs) | strmlabs:// | (TCP) 443 | strmlabs://AccessToken/<br/>strmlabs://AccessToken/?name=name&identifier=identifier&amount=0&currency=USD | [Streamlabs](https://github.com/caronc/apprise/wiki/Notify_streamlabs) | strmlabs:// | (TCP) 443 | strmlabs://AccessToken/<br/>strmlabs://AccessToken/?name=name&identifier=identifier&amount=0&currency=USD
| [Synology Chat](https://github.com/caronc/apprise/wiki/Notify_synology_chat) | synology:// or synologys:// | (TCP) 80 or 443 | synology://hostname/token<br />synology://hostname:port/token | [Synology Chat](https://github.com/caronc/apprise/wiki/Notify_synology_chat) | synology:// or synologys:// | (TCP) 80 or 443 | synology://hostname/token<br />synology://hostname:port/token
| [Syslog](https://github.com/caronc/apprise/wiki/Notify_syslog) | syslog:// | n/a | syslog://<br />syslog://Facility | [Syslog](https://github.com/caronc/apprise/wiki/Notify_syslog) | syslog:// | n/a | syslog://<br />syslog://Facility

178
apprise/plugins/spugpush.py Normal file
View File

@ -0,0 +1,178 @@
# -*- coding: utf-8 -*-
#
# BSD 2-Clause License
#
# Apprise - Push Notification Library.
# Copyright (c) 2025, Chris Caron <lead2gold@gmail.com>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# Details at:
# https://docs.spug.dev/push/
import re
import requests
import json
from ..utils.parse import validate_regex
from ..url import PrivacyMode
from .base import NotifyBase
from ..locale import gettext_lazy as _
from ..common import NotifyType
class NotifySpugpush(NotifyBase):
"""
A wrapper for SpugPush Notifications
"""
# The default descriptive name associated with the Notification
service_name = _('SpugPush')
# The services URL
service_url = 'https://docs.spug.dev/push/'
# The default secure protocol
secure_protocol = 'spugpush'
# A URL that takes you to the setup/help of the specific protocol
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_spugpush'
# URL used to send notifications with
notify_url = 'https://push.spug.dev/send/'
templates = (
'{schema}://{token}',
)
template_tokens = dict(NotifyBase.template_tokens, **{
'token': {
'name': _('Access Token'),
'type': 'string',
'private': True,
'required': True,
'regex': (r'^[a-zA-Z0-9_-]{32,64}$', 'i'),
},
})
def __init__(self, token, **kwargs):
"""
Initialize SpugPush Object
"""
super().__init__(**kwargs)
self.token = validate_regex(
token, *self.template_tokens['token']['regex']
)
if not self.token:
msg = 'The SpugPush token ({}) is invalid.'.format(token)
self.logger.warning(msg)
raise TypeError(msg)
self.webhook_url = f'{self.notify_url}{self.token}'
def url(self, privacy=False, *args, **kwargs):
"""
Returns the URL built dynamically based on specified arguments.
"""
params = self.url_parameters(privacy=privacy, *args, **kwargs)
return '{schema}://{token}/?{params}'.format(
schema=self.secure_protocol,
token=self.pprint(self.token, privacy, mode=PrivacyMode.Secret),
params=self.urlencode(params),
)
@property
def url_identifier(self):
"""
Returns a unique identifier for this plugin instance
"""
return (self.secure_protocol, self.token)
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
"""
Send a SpugPush Notification
"""
payload = {
'title': title if title else body,
'content': body,
}
headers = {
'User-Agent': self.app_id,
'Content-Type': 'application/json',
}
self.throttle()
try:
response = requests.post(
self.webhook_url,
headers=headers,
data=json.dumps(payload),
verify=self.verify_certificate,
timeout=self.request_timeout,
)
if response.status_code != requests.codes.ok:
self.logger.warning(
'SpugPush notification failed: %d - %s',
response.status_code, response.text)
return False
except requests.RequestException as e:
self.logger.warning(f'SpugPush Exception: {e}')
return False
self.logger.info('SpugPush notification sent successfully.')
return True
@staticmethod
def parse_url(url):
"""
Parses the URL and returns arguments to re-instantiate the object
"""
results = NotifyBase.parse_url(url, verify_host=False)
if not results:
return results
if 'token' in results['qsd'] and results['qsd']['token']:
results['token'] = NotifySpugpush.unquote(results['qsd']['token'])
else:
results['token'] = NotifySpugpush.unquote(results['host'])
return results
@staticmethod
def parse_native_url(url):
"""
Parse native SpugPush webhook URL into Apprise format
"""
match = re.match(
r'^https://push\.spug\.dev/send/([a-z0-9_-]+)$', url, re.I)
if not match:
return None
return NotifySpugpush.parse_url(
'{schema}://{token}'.format(
schema=NotifySpugpush.secure_protocol,
token=match.group(1)))

View File

@ -50,10 +50,10 @@ Opsgenie, PagerDuty, PagerTree, ParsePlatform, Plivo, PopcornNotify, Prowl,
Pushalot, PushBullet, Pushjet, PushMe, Pushover, Pushplus, PushSafer, Pushy, Pushalot, PushBullet, Pushjet, PushMe, Pushover, Pushplus, PushSafer, Pushy,
PushDeer, Revolt, Reddit, Resend, Rocket.Chat, RSyslog, SendGrid, ServerChan, PushDeer, Revolt, Reddit, Resend, Rocket.Chat, RSyslog, SendGrid, ServerChan,
Seven, SFR, Signal, SimplePush, Sinch, Slack, SMPP, SMSEagle, SMS Manager, Seven, SFR, Signal, SimplePush, Sinch, Slack, SMPP, SMSEagle, SMS Manager,
SMTP2Go, SparkPost, Splunk, Spike, Super Toasty, Streamlabs, Stride, Synology SMTP2Go, SparkPost, Splunk, Spike, Spug Push, Super Toasty, Streamlabs, Stride,
Chat, Syslog, Techulus Push, Telegram, Threema Gateway, Twilio, Twitter, Twist, Synology Chat, Syslog, Techulus Push, Telegram, Threema Gateway, Twilio,
Vapid, VictorOps, Voipms, Vonage, WebPush, WeCom Bot, WhatsApp, Webex Teams, Twitter, Twist, Vapid, VictorOps, Voipms, Vonage, WebPush, WeCom Bot, WhatsApp,
Workflows, WxPusher, XBMC} Webex Teams, Workflows, WxPusher, XBMC}
Name: python-%{pypi_name} Name: python-%{pypi_name}
Version: 1.9.3 Version: 1.9.3

View File

@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-
#
# BSD 2-Clause License
#
# Apprise - Push Notification Library.
# Copyright (c) 2025, Chris Caron <lead2gold@gmail.com>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
import requests
from apprise.plugins.spugpush import NotifySpugpush
from helpers import AppriseURLTester
import logging
logging.disable(logging.CRITICAL)
apprise_url_tests = (
('spugpush://', {
'instance': TypeError,
}),
('spugpush://invalid!', {
'instance': TypeError,
}),
('spugpush://abc123def456ghi789jkl012mno345pq', {
'instance': NotifySpugpush,
'privacy_url': 'spugpush://****/',
}),
('spugpush://?token=abc123def456ghi789jkl012mno345pq', {
'instance': NotifySpugpush,
'privacy_url': 'spugpush://****/',
}),
('https://push.spug.dev/send/abc123def456ghi789jkl012mno345pq', {
'instance': NotifySpugpush,
}),
('spugpush://abc123def456ghi789jkl012mno345pq', {
'instance': NotifySpugpush,
'response': False,
'requests_response_code': requests.codes.internal_server_error,
}),
('spugpush://abc123def456ghi789jkl012mno345pq', {
'instance': NotifySpugpush,
'response': False,
'requests_response_code': 999,
}),
('spugpush://ffffffffffffffffffffffffffffffff', {
'instance': NotifySpugpush,
'test_requests_exceptions': True,
}),
)
def test_plugin_spugpush_urls():
AppriseURLTester(tests=apprise_url_tests).run_all()