mirror of
https://github.com/caronc/apprise.git
synced 2024-12-12 09:51:05 +01:00
Bark Notification Support Added (#582)
This commit is contained in:
parent
d9b2ed5eee
commit
ca0c8460f1
26
.github/PULL_REQUEST_TEMPLATE.md
vendored
26
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -7,7 +7,7 @@
|
|||||||
<!-- This section is only applicable if you're adding a new service -->
|
<!-- This section is only applicable if you're adding a new service -->
|
||||||
* [ ] apprise/plugins/Notify<!--ServiceName goes here-->.py
|
* [ ] apprise/plugins/Notify<!--ServiceName goes here-->.py
|
||||||
* [ ] setup.py
|
* [ ] setup.py
|
||||||
- add new service into the `keywords` section of the `setup()` declaration
|
- add new service into the `KEYWORDS` file located in the root directory
|
||||||
* [ ] README.md
|
* [ ] README.md
|
||||||
- add entry for new service to table (as a quick reference)
|
- add entry for new service to table (as a quick reference)
|
||||||
* [ ] packaging/redhat/python-apprise.spec
|
* [ ] packaging/redhat/python-apprise.spec
|
||||||
@ -19,3 +19,27 @@
|
|||||||
* [ ] There is no commented out code in this PR.
|
* [ ] There is no commented out code in this PR.
|
||||||
* [ ] No lint errors (use `flake8`)
|
* [ ] No lint errors (use `flake8`)
|
||||||
* [ ] 100% test coverage
|
* [ ] 100% test coverage
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
<!-- If this your code is testable by other users of the program
|
||||||
|
it would be really helpful to define this here -->
|
||||||
|
Anyone can help test this source code as follows:
|
||||||
|
```bash
|
||||||
|
# Create a virtual environment to work in as follows:
|
||||||
|
python3 -m venv apprise
|
||||||
|
|
||||||
|
# Change into our new directory
|
||||||
|
cd apprise
|
||||||
|
|
||||||
|
# Activate our virtual environment
|
||||||
|
source bin/activate
|
||||||
|
|
||||||
|
# Install the branch
|
||||||
|
pip install git+https://github.com/caronc/apprise.git@<this.branch-name>
|
||||||
|
|
||||||
|
# Be sure you're running your Signal API Server and query it like so
|
||||||
|
apprise -t "Test Title" -b "Test Message" \
|
||||||
|
<apprise url related to ticket>
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
81
KEYWORDS
Normal file
81
KEYWORDS
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
Alerts
|
||||||
|
API
|
||||||
|
AWS
|
||||||
|
Boxcar
|
||||||
|
Chat
|
||||||
|
CLI
|
||||||
|
ClickSend
|
||||||
|
DAPNET
|
||||||
|
Dbus
|
||||||
|
Dingtalk
|
||||||
|
Discord
|
||||||
|
Email
|
||||||
|
Emby
|
||||||
|
Faast
|
||||||
|
FCM
|
||||||
|
Flock
|
||||||
|
Gitter
|
||||||
|
Gnome
|
||||||
|
Google
|
||||||
|
Gotify
|
||||||
|
Growl
|
||||||
|
Guilded
|
||||||
|
Home Assistant
|
||||||
|
IFTTT
|
||||||
|
Join
|
||||||
|
Kavenegar
|
||||||
|
KODI
|
||||||
|
Kumulos
|
||||||
|
LaMetric
|
||||||
|
MacOS
|
||||||
|
Mailgun
|
||||||
|
Matrix
|
||||||
|
Mattermost
|
||||||
|
MessageBird
|
||||||
|
Microsoft
|
||||||
|
MQTT
|
||||||
|
MSG91
|
||||||
|
MSTeams
|
||||||
|
Nexmo
|
||||||
|
Nextcloud
|
||||||
|
NextcloudTalk
|
||||||
|
Notica
|
||||||
|
Notifico
|
||||||
|
Ntfy
|
||||||
|
Office365
|
||||||
|
OneSignal
|
||||||
|
Opsgenie
|
||||||
|
ParsePlatform
|
||||||
|
PopcornNotify
|
||||||
|
Prowl
|
||||||
|
PushBullet
|
||||||
|
Pushed
|
||||||
|
Pushjet
|
||||||
|
Push Notifications
|
||||||
|
Pushover
|
||||||
|
PushSafer
|
||||||
|
Reddit
|
||||||
|
Rocket.Chat
|
||||||
|
Ryver
|
||||||
|
SendGrid
|
||||||
|
ServerChan
|
||||||
|
SES
|
||||||
|
Signal
|
||||||
|
SimplePush
|
||||||
|
Sinch
|
||||||
|
Slack
|
||||||
|
SMTP2Go
|
||||||
|
SNS
|
||||||
|
SparkPost
|
||||||
|
Spontit
|
||||||
|
Streamlabs
|
||||||
|
Stride
|
||||||
|
Syslog
|
||||||
|
Techulus
|
||||||
|
Telegram
|
||||||
|
Twilio
|
||||||
|
Twist
|
||||||
|
Twitter
|
||||||
|
Webex
|
||||||
|
Windows
|
||||||
|
XBMC
|
@ -1,4 +1,5 @@
|
|||||||
include LICENSE
|
include LICENSE
|
||||||
|
include KEYWORDS
|
||||||
include README.md
|
include README.md
|
||||||
include requirements.txt
|
include requirements.txt
|
||||||
include win-requirements.txt
|
include win-requirements.txt
|
||||||
|
@ -36,7 +36,8 @@ The table below identifies the services this tool supports and some example serv
|
|||||||
| -------------------- | ---------- | ------------ | -------------- |
|
| -------------------- | ---------- | ------------ | -------------- |
|
||||||
| [Apprise API](https://github.com/caronc/apprise/wiki/Notify_apprise_api) | apprise:// or apprises:// | (TCP) 80 or 443 | apprise://hostname/Token
|
| [Apprise API](https://github.com/caronc/apprise/wiki/Notify_apprise_api) | apprise:// or apprises:// | (TCP) 80 or 443 | apprise://hostname/Token
|
||||||
| [AWS SES](https://github.com/caronc/apprise/wiki/Notify_ses) | ses:// | (TCP) 443 | ses://user@domain/AccessKeyID/AccessSecretKey/RegionName<br/>ses://user@domain/AccessKeyID/AccessSecretKey/RegionName/email1/email2/emailN
|
| [AWS SES](https://github.com/caronc/apprise/wiki/Notify_ses) | ses:// | (TCP) 443 | ses://user@domain/AccessKeyID/AccessSecretKey/RegionName<br/>ses://user@domain/AccessKeyID/AccessSecretKey/RegionName/email1/email2/emailN
|
||||||
| [Boxcar](https://github.com/caronc/apprise/wiki/Notify_boxcar) | boxcar:// | (TCP) 443 | boxcar://hostname<br />boxcar://hostname/@tag<br/>boxcar://hostname/device_token<br />boxcar://hostname/device_token1/device_token2/device_tokenN<br />boxcar://hostname/@tag/@tag2/device_token
|
| [Bark](https://github.com/caronc/apprise/wiki/Notify_bark) | bark:// | (TCP) 80 or 443 | bark://hostname<br />bark://hostname/device_key<br />bark://hostname/device_key1/device_key2/device_keyN
|
||||||
|
| [Boxcar](https://github.com/caronc/apprise/wiki/Notify_bark) | boxcar:// | (TCP) 443 | boxcar://hostname<br />boxcar://hostname/@tag<br/>boxcar://hostname/device_token<br />boxcar://hostname/device_token1/device_token2/device_tokenN<br />boxcar://hostname/@tag/@tag2/device_token
|
||||||
| [Discord](https://github.com/caronc/apprise/wiki/Notify_discord) | discord:// | (TCP) 443 | discord://webhook_id/webhook_token<br />discord://avatar@webhook_id/webhook_token
|
| [Discord](https://github.com/caronc/apprise/wiki/Notify_discord) | discord:// | (TCP) 443 | discord://webhook_id/webhook_token<br />discord://avatar@webhook_id/webhook_token
|
||||||
| [Emby](https://github.com/caronc/apprise/wiki/Notify_emby) | emby:// or embys:// | (TCP) 8096 | emby://user@hostname/<br />emby://user:password@hostname
|
| [Emby](https://github.com/caronc/apprise/wiki/Notify_emby) | emby:// or embys:// | (TCP) 8096 | emby://user@hostname/<br />emby://user:password@hostname
|
||||||
| [Enigma2](https://github.com/caronc/apprise/wiki/Notify_enigma2) | enigma2:// or enigma2s:// | (TCP) 80 or 443 | enigma2://hostname
|
| [Enigma2](https://github.com/caronc/apprise/wiki/Notify_enigma2) | enigma2:// or enigma2s:// | (TCP) 80 or 443 | enigma2://hostname
|
||||||
|
508
apprise/plugins/NotifyBark.py
Normal file
508
apprise/plugins/NotifyBark.py
Normal file
@ -0,0 +1,508 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (C) 2022 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.
|
||||||
|
#
|
||||||
|
# API: https://github.com/Finb/bark-server/blob/master/docs/API_V2.md#python
|
||||||
|
#
|
||||||
|
import six
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
|
||||||
|
from .NotifyBase import NotifyBase
|
||||||
|
from ..URLBase import PrivacyMode
|
||||||
|
from ..common import NotifyImageSize
|
||||||
|
from ..common import NotifyType
|
||||||
|
from ..utils import parse_list
|
||||||
|
from ..utils import parse_bool
|
||||||
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
# Sounds generated off of: https://github.com/Finb/Bark/tree/master/Sounds
|
||||||
|
BARK_SOUNDS = (
|
||||||
|
"alarm.caf",
|
||||||
|
"anticipate.caf",
|
||||||
|
"bell.caf",
|
||||||
|
"birdsong.caf",
|
||||||
|
"bloom.caf",
|
||||||
|
"calypso.caf",
|
||||||
|
"chime.caf",
|
||||||
|
"choo.caf",
|
||||||
|
"descent.caf",
|
||||||
|
"electronic.caf",
|
||||||
|
"fanfare.caf",
|
||||||
|
"glass.caf",
|
||||||
|
"gotosleep.caf",
|
||||||
|
"healthnotification.caf",
|
||||||
|
"horn.caf",
|
||||||
|
"ladder.caf",
|
||||||
|
"mailsent.caf",
|
||||||
|
"minuet.caf",
|
||||||
|
"multiwayinvitation.caf",
|
||||||
|
"newmail.caf",
|
||||||
|
"newsflash.caf",
|
||||||
|
"noir.caf",
|
||||||
|
"paymentsuccess.caf",
|
||||||
|
"shake.caf",
|
||||||
|
"sherwoodforest.caf",
|
||||||
|
"silence.caf",
|
||||||
|
"spell.caf",
|
||||||
|
"suspense.caf",
|
||||||
|
"telegraph.caf",
|
||||||
|
"tiptoes.caf",
|
||||||
|
"typewriters.caf",
|
||||||
|
"update.caf",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Supported Level Entries
|
||||||
|
class NotifyBarkLevel(object):
|
||||||
|
"""
|
||||||
|
Defines the Bark Level options
|
||||||
|
"""
|
||||||
|
ACTIVE = 'active'
|
||||||
|
|
||||||
|
TIME_SENSITIVE = 'timeSensitive'
|
||||||
|
|
||||||
|
PASSIVE = 'passive'
|
||||||
|
|
||||||
|
|
||||||
|
BARK_LEVELS = (
|
||||||
|
NotifyBarkLevel.ACTIVE,
|
||||||
|
NotifyBarkLevel.TIME_SENSITIVE,
|
||||||
|
NotifyBarkLevel.PASSIVE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NotifyBark(NotifyBase):
|
||||||
|
"""
|
||||||
|
A wrapper for Notify Bark Notifications
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The default descriptive name associated with the Notification
|
||||||
|
service_name = 'Bark'
|
||||||
|
|
||||||
|
# The services URL
|
||||||
|
service_url = 'https://github.com/Finb/Bark'
|
||||||
|
|
||||||
|
# The default protocol
|
||||||
|
protocol = 'bark'
|
||||||
|
|
||||||
|
# The default secure protocol
|
||||||
|
secure_protocol = 'barks'
|
||||||
|
|
||||||
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_bark'
|
||||||
|
|
||||||
|
# Allows the user to specify the NotifyImageSize object; this is supported
|
||||||
|
# through the webhook
|
||||||
|
image_size = NotifyImageSize.XY_128
|
||||||
|
|
||||||
|
# Define object templates
|
||||||
|
templates = (
|
||||||
|
'{schema}://{host}:{port}/{targets}',
|
||||||
|
'{schema}://{user}:{password}@{host}/{targets}',
|
||||||
|
'{schema}://{user}:{password}@{host}:{port}/{targets}',
|
||||||
|
'{schema}://{user}:{password}@{host}/{targets}',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Define our template arguments
|
||||||
|
template_tokens = dict(NotifyBase.template_tokens, **{
|
||||||
|
'host': {
|
||||||
|
'name': _('Hostname'),
|
||||||
|
'type': 'string',
|
||||||
|
'required': True,
|
||||||
|
},
|
||||||
|
'port': {
|
||||||
|
'name': _('Port'),
|
||||||
|
'type': 'int',
|
||||||
|
'min': 1,
|
||||||
|
'max': 65535,
|
||||||
|
},
|
||||||
|
'user': {
|
||||||
|
'name': _('Username'),
|
||||||
|
'type': 'string',
|
||||||
|
},
|
||||||
|
'password': {
|
||||||
|
'name': _('Password'),
|
||||||
|
'type': 'string',
|
||||||
|
'private': True,
|
||||||
|
},
|
||||||
|
'target_device': {
|
||||||
|
'name': _('Target Device'),
|
||||||
|
'type': 'string',
|
||||||
|
'map_to': 'targets',
|
||||||
|
},
|
||||||
|
'targets': {
|
||||||
|
'name': _('Targets'),
|
||||||
|
'type': 'list:string',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
# Define our template arguments
|
||||||
|
template_args = dict(NotifyBase.template_args, **{
|
||||||
|
'to': {
|
||||||
|
'alias_of': 'targets',
|
||||||
|
},
|
||||||
|
'sound': {
|
||||||
|
'name': _('Sound'),
|
||||||
|
'type': 'choice:string',
|
||||||
|
'values': BARK_SOUNDS,
|
||||||
|
},
|
||||||
|
'level': {
|
||||||
|
'name': _('Level'),
|
||||||
|
'type': 'choice:string',
|
||||||
|
'values': BARK_LEVELS,
|
||||||
|
},
|
||||||
|
'click': {
|
||||||
|
'name': _('Click'),
|
||||||
|
'type': 'string',
|
||||||
|
},
|
||||||
|
'badge': {
|
||||||
|
'name': _('Badge'),
|
||||||
|
'type': 'int',
|
||||||
|
'min': 0,
|
||||||
|
},
|
||||||
|
'category': {
|
||||||
|
'name': _('Category'),
|
||||||
|
'type': 'string',
|
||||||
|
},
|
||||||
|
'group': {
|
||||||
|
'name': _('Group'),
|
||||||
|
'type': 'string',
|
||||||
|
},
|
||||||
|
'image': {
|
||||||
|
'name': _('Include Image'),
|
||||||
|
'type': 'bool',
|
||||||
|
'default': True,
|
||||||
|
'map_to': 'include_image',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
def __init__(self, targets=None, include_image=True, sound=None,
|
||||||
|
category=None, group=None, level=None, click=None,
|
||||||
|
badge=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Initialize Notify Bark Object
|
||||||
|
"""
|
||||||
|
super(NotifyBark, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
# Prepare our URL
|
||||||
|
self.notify_url = '%s://%s/push' % (
|
||||||
|
'https' if self.secure else 'http',
|
||||||
|
self.host,
|
||||||
|
)
|
||||||
|
|
||||||
|
if isinstance(self.port, int):
|
||||||
|
self.notify_url += ':%d' % self.port
|
||||||
|
|
||||||
|
# Assign our category
|
||||||
|
self.category = \
|
||||||
|
category if isinstance(category, six.string_types) else None
|
||||||
|
|
||||||
|
# Assign our group
|
||||||
|
self.group = group if isinstance(group, six.string_types) else None
|
||||||
|
|
||||||
|
# Initialize device list
|
||||||
|
self.targets = parse_list(targets)
|
||||||
|
|
||||||
|
# Place an image inline with the message body
|
||||||
|
self.include_image = include_image
|
||||||
|
|
||||||
|
# A clickthrough option for notifications
|
||||||
|
self.click = click
|
||||||
|
|
||||||
|
# Badge
|
||||||
|
try:
|
||||||
|
# Acquire our badge count if we can:
|
||||||
|
# - We accept both the integer form as well as a string
|
||||||
|
# representation
|
||||||
|
self.badge = int(badge)
|
||||||
|
if self.badge < 0:
|
||||||
|
raise ValueError()
|
||||||
|
|
||||||
|
except TypeError:
|
||||||
|
# NoneType means use Default; this is an okay exception
|
||||||
|
self.badge = None
|
||||||
|
|
||||||
|
except ValueError:
|
||||||
|
self.badge = None
|
||||||
|
self.logger.warning(
|
||||||
|
'The specified Bark badge ({}) is not valid ', badge)
|
||||||
|
|
||||||
|
# Sound (easy-lookup)
|
||||||
|
self.sound = None if not sound else next(
|
||||||
|
(f for f in BARK_SOUNDS if f.startswith(sound.lower())), None)
|
||||||
|
if sound and not self.sound:
|
||||||
|
self.logger.warning(
|
||||||
|
'The specified Bark sound ({}) was not found ', sound)
|
||||||
|
|
||||||
|
# Level
|
||||||
|
self.level = None if not level else next(
|
||||||
|
(f for f in BARK_LEVELS if f[0] == level[0]), None)
|
||||||
|
if level and not self.level:
|
||||||
|
self.logger.warning(
|
||||||
|
'The specified Bark level ({}) is not valid ', level)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
|
"""
|
||||||
|
Perform Bark Notification
|
||||||
|
"""
|
||||||
|
|
||||||
|
# error tracking (used for function return)
|
||||||
|
has_error = False
|
||||||
|
|
||||||
|
if not len(self.targets):
|
||||||
|
# We have nothing to notify; we're done
|
||||||
|
self.logger.warning('There are no Bark devices to notify')
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Prepare our headers
|
||||||
|
headers = {
|
||||||
|
'User-Agent': self.app_id,
|
||||||
|
'Content-Type': 'application/json; charset=utf-8',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prepare our payload (sample below)
|
||||||
|
# {
|
||||||
|
# "body": "Test Bark Server",
|
||||||
|
# "device_key": "nysrshcqielvoxsa",
|
||||||
|
# "title": "bleem",
|
||||||
|
# "category": "category",
|
||||||
|
# "sound": "minuet.caf",
|
||||||
|
# "badge": 1,
|
||||||
|
# "icon": "https://day.app/assets/images/avatar.jpg",
|
||||||
|
# "group": "test",
|
||||||
|
# "url": "https://mritd.com"
|
||||||
|
# }
|
||||||
|
payload = {
|
||||||
|
'title': title if title else self.app_desc,
|
||||||
|
'body': body,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Acquire our image url if configured to do so
|
||||||
|
image_url = None if not self.include_image else \
|
||||||
|
self.image_url(notify_type)
|
||||||
|
|
||||||
|
if image_url:
|
||||||
|
payload['icon'] = image_url
|
||||||
|
|
||||||
|
if self.sound:
|
||||||
|
payload['sound'] = self.sound
|
||||||
|
|
||||||
|
if self.click:
|
||||||
|
payload['url'] = self.click
|
||||||
|
|
||||||
|
if self.badge:
|
||||||
|
payload['badge'] = self.badge
|
||||||
|
|
||||||
|
if self.level:
|
||||||
|
payload['level'] = self.level
|
||||||
|
|
||||||
|
if self.category:
|
||||||
|
payload['category'] = self.category
|
||||||
|
|
||||||
|
if self.group:
|
||||||
|
payload['group'] = self.group
|
||||||
|
|
||||||
|
auth = None
|
||||||
|
if self.user:
|
||||||
|
auth = (self.user, self.password)
|
||||||
|
|
||||||
|
# Create a copy of the targets
|
||||||
|
targets = list(self.targets)
|
||||||
|
|
||||||
|
while len(targets) > 0:
|
||||||
|
# Retrieve our device key
|
||||||
|
target = targets.pop()
|
||||||
|
|
||||||
|
payload['device_key'] = target
|
||||||
|
self.logger.debug('Bark POST URL: %s (cert_verify=%r)' % (
|
||||||
|
self.notify_url, self.verify_certificate,
|
||||||
|
))
|
||||||
|
self.logger.debug('Bark Payload: %s' % str(payload))
|
||||||
|
|
||||||
|
# Always call throttle before any remote server i/o is made
|
||||||
|
self.throttle()
|
||||||
|
try:
|
||||||
|
r = requests.post(
|
||||||
|
self.notify_url,
|
||||||
|
data=json.dumps(payload),
|
||||||
|
headers=headers,
|
||||||
|
auth=auth,
|
||||||
|
verify=self.verify_certificate,
|
||||||
|
timeout=self.request_timeout,
|
||||||
|
)
|
||||||
|
if r.status_code != requests.codes.ok:
|
||||||
|
# We had a problem
|
||||||
|
status_str = \
|
||||||
|
NotifyBark.http_response_code_lookup(
|
||||||
|
r.status_code)
|
||||||
|
|
||||||
|
self.logger.warning(
|
||||||
|
'Failed to send Bark notification to {}: '
|
||||||
|
'{}{}error={}.'.format(
|
||||||
|
target,
|
||||||
|
status_str,
|
||||||
|
', ' if status_str else '',
|
||||||
|
r.status_code))
|
||||||
|
|
||||||
|
self.logger.debug(
|
||||||
|
'Response Details:\r\n{}'.format(r.content))
|
||||||
|
|
||||||
|
# Mark our failure
|
||||||
|
has_error = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.logger.info(
|
||||||
|
'Sent Bark notification to {}.'.format(target))
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
self.logger.warning(
|
||||||
|
'A Connection error occurred sending Bark '
|
||||||
|
'notification to {}.'.format(target))
|
||||||
|
self.logger.debug('Socket Exception: %s' % str(e))
|
||||||
|
|
||||||
|
# Mark our failure
|
||||||
|
has_error = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
return not has_error
|
||||||
|
|
||||||
|
def url(self, privacy=False, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Returns the URL built dynamically based on specified arguments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Define any URL parameters
|
||||||
|
params = {
|
||||||
|
'image': 'yes' if self.include_image else 'no',
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.sound:
|
||||||
|
params['sound'] = self.sound
|
||||||
|
|
||||||
|
if self.click:
|
||||||
|
params['click'] = self.click
|
||||||
|
|
||||||
|
if self.badge:
|
||||||
|
params['badge'] = str(self.badge)
|
||||||
|
|
||||||
|
if self.level:
|
||||||
|
params['level'] = self.level
|
||||||
|
|
||||||
|
if self.category:
|
||||||
|
params['category'] = self.category
|
||||||
|
|
||||||
|
if self.group:
|
||||||
|
params['group'] = self.group
|
||||||
|
|
||||||
|
# Extend our parameters
|
||||||
|
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
|
||||||
|
|
||||||
|
# Determine Authentication
|
||||||
|
auth = ''
|
||||||
|
if self.user and self.password:
|
||||||
|
auth = '{user}:{password}@'.format(
|
||||||
|
user=NotifyBark.quote(self.user, safe=''),
|
||||||
|
password=self.pprint(
|
||||||
|
self.password, privacy, mode=PrivacyMode.Secret, safe=''),
|
||||||
|
)
|
||||||
|
elif self.user:
|
||||||
|
auth = '{user}@'.format(
|
||||||
|
user=NotifyBark.quote(self.user, safe=''),
|
||||||
|
)
|
||||||
|
|
||||||
|
default_port = 443 if self.secure else 80
|
||||||
|
|
||||||
|
return '{schema}://{auth}{hostname}{port}/{targets}?{params}'.format(
|
||||||
|
schema=self.secure_protocol if self.secure else self.protocol,
|
||||||
|
auth=auth,
|
||||||
|
# never encode hostname since we're expecting it to be a valid one
|
||||||
|
hostname=self.host,
|
||||||
|
port='' if self.port is None or self.port == default_port
|
||||||
|
else ':{}'.format(self.port),
|
||||||
|
targets='/'.join(
|
||||||
|
[NotifyBark.quote('{}'.format(x)) for x in self.targets]),
|
||||||
|
params=NotifyBark.urlencode(params),
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_url(url):
|
||||||
|
"""
|
||||||
|
Parses the URL and returns enough arguments that can allow
|
||||||
|
us to re-instantiate 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 targets
|
||||||
|
results['targets'] = NotifyBark.split_path(results['fullpath'])
|
||||||
|
|
||||||
|
# Category
|
||||||
|
if 'category' in results['qsd'] and results['qsd']['category']:
|
||||||
|
results['category'] = NotifyBark.unquote(
|
||||||
|
results['qsd']['category'].strip())
|
||||||
|
|
||||||
|
# Group
|
||||||
|
if 'group' in results['qsd'] and results['qsd']['group']:
|
||||||
|
results['group'] = NotifyBark.unquote(
|
||||||
|
results['qsd']['group'].strip())
|
||||||
|
|
||||||
|
# Badge
|
||||||
|
if 'badge' in results['qsd'] and results['qsd']['badge']:
|
||||||
|
results['badge'] = NotifyBark.unquote(
|
||||||
|
results['qsd']['badge'].strip())
|
||||||
|
|
||||||
|
# Level
|
||||||
|
if 'level' in results['qsd'] and results['qsd']['level']:
|
||||||
|
results['level'] = NotifyBark.unquote(
|
||||||
|
results['qsd']['level'].strip())
|
||||||
|
|
||||||
|
# Click (URL)
|
||||||
|
if 'click' in results['qsd'] and results['qsd']['click']:
|
||||||
|
results['click'] = NotifyBark.unquote(
|
||||||
|
results['qsd']['click'].strip())
|
||||||
|
|
||||||
|
# Sound
|
||||||
|
if 'sound' in results['qsd'] and results['qsd']['sound']:
|
||||||
|
results['sound'] = NotifyBark.unquote(
|
||||||
|
results['qsd']['sound'].strip())
|
||||||
|
|
||||||
|
# The 'to' makes it easier to use yaml configuration
|
||||||
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
|
results['targets'] += \
|
||||||
|
NotifyBark.parse_list(results['qsd']['to'])
|
||||||
|
|
||||||
|
# use image= for consistency with the other plugins
|
||||||
|
results['include_image'] = \
|
||||||
|
parse_bool(results['qsd'].get('image', True))
|
||||||
|
|
||||||
|
return results
|
@ -274,7 +274,7 @@ class NotifyNtfy(NotifyBase):
|
|||||||
self.logger.warning('There are no ntfy topics to notify')
|
self.logger.warning('There are no ntfy topics to notify')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Create a copy of the subreddits list
|
# Create a copy of the topics
|
||||||
topics = list(self.topics)
|
topics = list(self.topics)
|
||||||
while len(topics) > 0:
|
while len(topics) > 0:
|
||||||
# Retrieve our topic
|
# Retrieve our topic
|
||||||
|
@ -47,16 +47,16 @@ Apprise is a Python package for simplifying access to all of the different
|
|||||||
notification services that are out there. Apprise opens the door and makes
|
notification services that are out there. Apprise opens the door and makes
|
||||||
it easy to access:
|
it easy to access:
|
||||||
|
|
||||||
Apprise API, AWS SES, AWS SNS, Boxcar, ClickSend, DAPNET, DingTalk, Discord,
|
Apprise API, AWS SES, AWS SNS, Bark, Boxcar, ClickSend, DAPNET, DingTalk,
|
||||||
E-Mail, Emby, Faast, FCM, Flock, Gitter, Google Chat, Gotify, Growl, Guilded, Home
|
Discord, E-Mail, Emby, Faast, FCM, Flock, Gitter, Google Chat, Gotify, Growl,
|
||||||
Assistant, IFTTT, Join, Kavenegar, KODI, Kumulos, LaMetric, MacOSX, Mailgun,
|
Guilded, Home Assistant, IFTTT, Join, Kavenegar, KODI, Kumulos, LaMetric,
|
||||||
Mattermost, Matrix, Microsoft Windows, Microsoft Teams, MessageBird, MQTT, MSG91,
|
MacOSX, Mailgun, Mattermost, Matrix, Microsoft Windows, Microsoft Teams,
|
||||||
MyAndroid, Nexmo, Nextcloud, NextcloudTalk, Notica, Notifico, ntfy, Office365,
|
MessageBird, MQTT, MSG91, MyAndroid, Nexmo, Nextcloud, NextcloudTalk, Notica,
|
||||||
OneSignal, Opsgenie, ParsePlatform, PopcornNotify, Prowl, Pushalot,
|
Notifico, ntfy, Office365, OneSignal, Opsgenie, ParsePlatform, PopcornNotify,
|
||||||
PushBullet, Pushjet, Pushover, PushSafer, Reddit, Rocket.Chat, SendGrid,
|
Prowl, Pushalot, PushBullet, Pushjet, Pushover, PushSafer, Reddit,
|
||||||
ServerChan, Signal, SimplePush, Sinch, Slack, SMTP2Go, Spontit, SparkPost,
|
Rocket.Chat, SendGrid, ServerChan, Signal, SimplePush, Sinch, Slack, SMTP2Go,
|
||||||
Super Toasty, Streamlabs, Stride, Syslog, Techulus Push, Telegram, Twilio,
|
Spontit, SparkPost, Super Toasty, Streamlabs, Stride, Syslog, Techulus Push,
|
||||||
Twitter, Twist, XBMC, XMPP, Webex Teams}
|
Telegram, Twilio, Twitter, Twist, XBMC, XMPP, Webex Teams}
|
||||||
|
|
||||||
Name: python-%{pypi_name}
|
Name: python-%{pypi_name}
|
||||||
Version: 0.9.8.3
|
Version: 0.9.8.3
|
||||||
|
12
setup.py
12
setup.py
@ -24,6 +24,7 @@
|
|||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
# THE SOFTWARE.
|
# THE SOFTWARE.
|
||||||
|
|
||||||
|
import re
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
try:
|
try:
|
||||||
@ -69,16 +70,7 @@ setup(
|
|||||||
long_description_content_type='text/markdown',
|
long_description_content_type='text/markdown',
|
||||||
cmdclass=cmdclass,
|
cmdclass=cmdclass,
|
||||||
url='https://github.com/caronc/apprise',
|
url='https://github.com/caronc/apprise',
|
||||||
keywords='Push Notifications Alerts Email AWS SES SNS Boxcar ClickSend '
|
keywords=' '.join(re.split(r'\s+', open('KEYWORDS').read())),
|
||||||
'DAPNET Dingtalk Discord Dbus Emby Faast FCM Flock Gitter Gnome '
|
|
||||||
'Google Chat Gotify Growl Guilded Home Assistant IFTTT Join Kavenegar '
|
|
||||||
'KODI Kumulos LaMetric MacOS Mailgun Matrix Mattermost MessageBird '
|
|
||||||
'MQTT MSG91 Nexmo Nextcloud NextcloudTalk Notica Notifico Ntfy '
|
|
||||||
'Office365 OneSignal Opsgenie ParsePlatform PopcornNotify Prowl '
|
|
||||||
'PushBullet Pushjet Pushed Pushover PushSafer Reddit Rocket.Chat '
|
|
||||||
'Ryver SendGrid ServerChan Signal SimplePush Sinch Slack SMTP2Go '
|
|
||||||
'SparkPost Spontit Streamlabs Stride Syslog Techulus Telegram Twilio '
|
|
||||||
'Twist Twitter XBMC MSTeams Microsoft Windows Webex CLI API',
|
|
||||||
author='Chris Caron',
|
author='Chris Caron',
|
||||||
author_email='lead2gold@gmail.com',
|
author_email='lead2gold@gmail.com',
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
|
148
test/test_plugin_bark.py
Normal file
148
test/test_plugin_bark.py
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (C) 2022 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.
|
||||||
|
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 = (
|
||||||
|
('bark://', {
|
||||||
|
# No no host
|
||||||
|
'instance': None,
|
||||||
|
}),
|
||||||
|
('bark://:@/', {
|
||||||
|
# just invalid all around
|
||||||
|
'instance': None,
|
||||||
|
}),
|
||||||
|
('bark://localhost', {
|
||||||
|
# No Device Key specified
|
||||||
|
'instance': plugins.NotifyBark,
|
||||||
|
# Expected notify() response False (because we won't be able
|
||||||
|
# to actually notify anything if no device_key was specified
|
||||||
|
'notify_response': False,
|
||||||
|
|
||||||
|
}),
|
||||||
|
('bark://192.168.0.6:8081/device_key', {
|
||||||
|
# Everything is okay
|
||||||
|
'instance': plugins.NotifyBark,
|
||||||
|
|
||||||
|
# Our expected url(privacy=True) startswith() response:
|
||||||
|
'privacy_url': 'bark://192.168.0.6:8081/',
|
||||||
|
}),
|
||||||
|
('bark://user@192.168.0.6:8081/device_key', {
|
||||||
|
# Everything is okay (test with user)
|
||||||
|
'instance': plugins.NotifyBark,
|
||||||
|
|
||||||
|
# Our expected url(privacy=True) startswith() response:
|
||||||
|
'privacy_url': 'bark://user@192.168.0.6:8081/',
|
||||||
|
}),
|
||||||
|
('bark://192.168.0.6:8081/device_key/?sound=invalid', {
|
||||||
|
# bad sound, but we go ahead anyway
|
||||||
|
'instance': plugins.NotifyBark,
|
||||||
|
}),
|
||||||
|
('bark://192.168.0.6:8081/device_key/?sound=alarm', {
|
||||||
|
# alarm.caf sound loaded
|
||||||
|
'instance': plugins.NotifyBark,
|
||||||
|
}),
|
||||||
|
('bark://192.168.0.6:8081/device_key/?sound=NOiR.cAf', {
|
||||||
|
# noir.caf sound loaded
|
||||||
|
'instance': plugins.NotifyBark,
|
||||||
|
}),
|
||||||
|
('bark://192.168.0.6:8081/device_key/?badge=100', {
|
||||||
|
# set badge
|
||||||
|
'instance': plugins.NotifyBark,
|
||||||
|
}),
|
||||||
|
('barks://192.168.0.6:8081/device_key/?badge=invalid', {
|
||||||
|
# set invalid badge
|
||||||
|
'instance': plugins.NotifyBark,
|
||||||
|
}),
|
||||||
|
('barks://192.168.0.6:8081/device_key/?badge=-12', {
|
||||||
|
# set invalid badge
|
||||||
|
'instance': plugins.NotifyBark,
|
||||||
|
}),
|
||||||
|
('bark://192.168.0.6:8081/device_key/?category=apprise', {
|
||||||
|
# set category
|
||||||
|
'instance': plugins.NotifyBark,
|
||||||
|
}),
|
||||||
|
('bark://192.168.0.6:8081/device_key/?image=no', {
|
||||||
|
# do not display image
|
||||||
|
'instance': plugins.NotifyBark,
|
||||||
|
}),
|
||||||
|
('bark://192.168.0.6:8081/device_key/?group=apprise', {
|
||||||
|
# set group
|
||||||
|
'instance': plugins.NotifyBark,
|
||||||
|
}),
|
||||||
|
('bark://192.168.0.6:8081/device_key/?level=invalid', {
|
||||||
|
# bad level, but we go ahead anyway
|
||||||
|
'instance': plugins.NotifyBark,
|
||||||
|
}),
|
||||||
|
('bark://192.168.0.6:8081/?to=device_key', {
|
||||||
|
# test use of to= argument
|
||||||
|
'instance': plugins.NotifyBark,
|
||||||
|
}),
|
||||||
|
('bark://192.168.0.6:8081/device_key/?click=http://localhost', {
|
||||||
|
# Our click link
|
||||||
|
'instance': plugins.NotifyBark,
|
||||||
|
}),
|
||||||
|
('bark://192.168.0.6:8081/device_key/?level=active', {
|
||||||
|
# active level
|
||||||
|
'instance': plugins.NotifyBark,
|
||||||
|
}),
|
||||||
|
('bark://user:pass@192.168.0.5:8086/device_key/device_key2/', {
|
||||||
|
# Everything is okay
|
||||||
|
'instance': plugins.NotifyBark,
|
||||||
|
|
||||||
|
# Our expected url(privacy=True) startswith() response:
|
||||||
|
'privacy_url': 'bark://user:****@192.168.0.5:8086/',
|
||||||
|
}),
|
||||||
|
('barks://192.168.0.7/device_key/', {
|
||||||
|
'instance': plugins.NotifyBark,
|
||||||
|
# throw a bizzare code forcing us to fail to look it up
|
||||||
|
'response': False,
|
||||||
|
'requests_response_code': 999,
|
||||||
|
|
||||||
|
# Our expected url(privacy=True) startswith() response:
|
||||||
|
'privacy_url': 'barks://192.168.0.7/device_key',
|
||||||
|
}),
|
||||||
|
('bark://192.168.0.7/device_key', {
|
||||||
|
'instance': plugins.NotifyBark,
|
||||||
|
# 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_bark_urls():
|
||||||
|
"""
|
||||||
|
NotifyBark() Apprise URLs
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Run our general tests
|
||||||
|
AppriseURLTester(tests=apprise_url_tests).run_all()
|
Loading…
Reference in New Issue
Block a user