Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7475903
Better logging when a URL can not be parsed (#257)
caronc Jul 24, 2020
84feb16
Gotify supports extended URL paths beyond just the hostname (#259)
caronc Jul 27, 2020
e54e4e7
Eased up on Flock credential QA (left for upstream)
caronc Jul 28, 2020
138c8eb
Bugfix + Eased up on Nexmo credential QA (left for upstream)
caronc Jul 28, 2020
f79f5ac
Cleaned up logging; removed trailing period
caronc Jul 28, 2020
57cb5f3
--input-format (-i) switch added to CLI (#261)
caronc Jul 29, 2020
a91064a
Refactored templating; introduced rto= and cto= URL parameters (#264)
caronc Aug 1, 2020
3b8b790
Added Spontit support (#253)
caronc Aug 1, 2020
ffcde04
Python 3.9 Beta CI added & Fedora 33 Rawhide support (#265)
caronc Aug 1, 2020
f3993b1
verbosity switches added to README
caronc Aug 3, 2020
479218d
Growl notification library rewrite; gntp now external dependency (#252)
caronc Aug 4, 2020
bc44bdc
Lametric Time/Clock support added (#267)
caronc Aug 7, 2020
ad6316b
Improved URL parsing; introducing IPV6 support (#269)
caronc Aug 8, 2020
0ef4e45
Better Email Internationalization Support (#270)
caronc Aug 9, 2020
5388089
Python 3.5 intermittent exeception removed from test coverage
caronc Aug 11, 2020
e65b1dc
bumped version to v0.8.7
caronc Aug 13, 2020
4f4b15d
Asset passed into AppriseConfig() is now recognized (#272)
caronc Aug 15, 2020
aa039e7
Python asyncio Integration - Notifications sent Asynchronously (#273)
caronc Aug 16, 2020
ca695ed
Telegram bot owner detection moved from init() to send() (#274)
caronc Aug 17, 2020
fbaf1e4
Bugfix :beetle: - Global parameters placed in url() again (#275)
caronc Aug 17, 2020
6e1b8a0
Advanced email parsing added; eg: Full Name email@domain.com (#276)
caronc Aug 18, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ matrix:
env: TOXENV=py37
- python: "3.8"
env: TOXENV=py38
- python: "3.9-dev"
env: TOXENV=py39
- python: "pypy2.7-6.0"
env: TOXENV=pypy
- python: "pypy3.5-6.0"
Expand Down
29 changes: 17 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ To inform or tell (someone). To make one aware of something.

* One notification library to rule them all.
* A common and intuitive notification syntax.
* Supports the handling of images and attachments (to the notification services that will accept them).
* Supports the handling of images and attachments (_to the notification services that will accept them_).
* It's incredibly lightweight.
* Amazing response times because all messages sent asyncronously.

System owners who wish to provide a notification service no longer need to research each and every new one as they appear. They just need to include this one library and then they can immediately gain access to almost all of the notifications services available to us today.
Developers who wish to provide a notification service no longer need to research each and every one out there. They no longer need to try to adapt to the new ones that comeout thereafter. They just need to include this one library and then they can immediately gain access to almost all of the notifications services available to us today.

System Administrators who wish to send a notification from a scheduled task or from the command line also no longer need to find the right tool for the job. Everything is already wrapped and supported within the *apprise* script that ships with this product.
System Administrators and DevOps who wish to send a notification now no longer need to find the right tool for the job. Everything is already wrapped and supported within the `apprise` command line tool (CLI) that ships with this product.

[![Paypal](https://img.shields.io/badge/paypal-donate-green.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=MHANV39UZNQ5E)
[![Follow](https://img.shields.io/twitter/follow/l2gnux)](https://twitter.com/l2gnux/)<br/>
Expand Down Expand Up @@ -45,6 +47,7 @@ The table below identifies the services this tool supports and some example serv
| [Join](https://github.com/caronc/apprise/wiki/Notify_join) | join:// | (TCP) 443 | join://apikey/device<br />join://apikey/device1/device2/deviceN/<br />join://apikey/group<br />join://apikey/groupA/groupB/groupN<br />join://apikey/DeviceA/groupA/groupN/DeviceN/
| [KODI](https://github.com/caronc/apprise/wiki/Notify_kodi) | kodi:// or kodis:// | (TCP) 8080 or 443 | kodi://hostname<br />kodi://user@hostname<br />kodi://user:password@hostname:port
| [Kumulos](https://github.com/caronc/apprise/wiki/Notify_kumulos) | kumulos:// | (TCP) 443 | kumulos://apikey/serverkey
| [LaMetric](https://github.com/caronc/apprise/wiki/Notify_lametric) | lametric:// | (TCP) 443 | lametric://apikey@device_ipaddr<br/>lametric://apikey@hostname:port<br/>lametric://client_id@client_secret
| [Mailgun](https://github.com/caronc/apprise/wiki/Notify_mailgun) | mailgun:// | (TCP) 443 | mailgun://user@hostname/apikey<br />mailgun://user@hostname/apikey/email<br />mailgun://user@hostname/apikey/email1/email2/emailN<br />mailgun://user@hostname/apikey/?name="From%20User"
| [Matrix](https://github.com/caronc/apprise/wiki/Notify_matrix) | matrix:// or matrixs:// | (TCP) 80 or 443 | matrix://hostname<br />matrix://user@hostname<br />matrixs://user:pass@hostname:port/#room_alias<br />matrixs://user:pass@hostname:port/!room_id<br />matrixs://user:pass@hostname:port/#room_alias/!room_id/#room2<br />matrixs://token@hostname:port/?webhook=matrix<br />matrix://user:token@hostname/?webhook=slack&format=markdown
| [Mattermost](https://github.com/caronc/apprise/wiki/Notify_mattermost) | mmost:// | (TCP) 8065 | mmost://hostname/authkey<br />mmost://hostname:80/authkey<br />mmost://user@hostname:80/authkey<br />mmost://hostname/authkey?channel=channel<br />mmosts://hostname/authkey<br />mmosts://user@hostname/authkey<br />
Expand All @@ -66,6 +69,7 @@ The table below identifies the services this tool supports and some example serv
| [SendGrid](https://github.com/caronc/apprise/wiki/Notify_sendgrid) | sendgrid:// | (TCP) 443 | sendgrid://APIToken:FromEmail/<br />sendgrid://APIToken:FromEmail/ToEmail<br />sendgrid://APIToken:FromEmail/ToEmail1/ToEmail2/ToEmailN/
| [SimplePush](https://github.com/caronc/apprise/wiki/Notify_simplepush) | spush:// | (TCP) 443 | spush://apikey<br />spush://salt:password@apikey<br />spush://apikey?event=Apprise
| [Slack](https://github.com/caronc/apprise/wiki/Notify_slack) | slack:// | (TCP) 443 | slack://TokenA/TokenB/TokenC/<br />slack://TokenA/TokenB/TokenC/Channel<br />slack://botname@TokenA/TokenB/TokenC/Channel<br />slack://user@TokenA/TokenB/TokenC/Channel1/Channel2/ChannelN
| [Spontit](https://github.com/caronc/apprise/wiki/Notify_spontit) | spontit:// | (TCP) 443 | spontit://UserID@APIKey/<br />spontit://UserID@APIKey/Channel<br />spontit://UserID@APIKey/Channel1/Channel2/ChannelN
| [Syslog](https://github.com/caronc/apprise/wiki/Notify_syslog) | syslog:// | n/a | syslog://<br />syslog://Facility
| [Telegram](https://github.com/caronc/apprise/wiki/Notify_telegram) | tgram:// | (TCP) 443 | tgram://bottoken/ChatID<br />tgram://bottoken/ChatID1/ChatID2/ChatIDN
| [Twitter](https://github.com/caronc/apprise/wiki/Notify_twitter) | twitter:// | (TCP) 443 | twitter://CKey/CSecret/AKey/ASecret<br/>twitter://user@CKey/CSecret/AKey/ASecret<br/>twitter://CKey/CSecret/AKey/ASecret/User1/User2/User2<br/>twitter://CKey/CSecret/AKey/ASecret?mode=tweet
Expand Down Expand Up @@ -120,18 +124,19 @@ pip install apprise
A small command line tool is also provided with this package called *apprise*. If you know the server url's you wish to notify, you can simply provide them all on the command line and send your notifications that way:
```bash
# Send a notification to as many servers as you want
# as you can easily chain one after another:
apprise -t 'my title' -b 'my notification body' \
# as you can easily chain one after another (the -vv provides some
# additional verbosity to help let you know what is going on):
apprise -vv -t 'my title' -b 'my notification body' \
'mailto://myemail:mypass@gmail.com' \
'pbul://o.gn5kj6nfhv736I7jC3cj3QLRiyhgl98b'

# If you don't specify a --body (-b) then stdin is used allowing
# you to use the tool as part of your every day administration:
cat /proc/cpuinfo | apprise -t 'cpu info' \
cat /proc/cpuinfo | apprise -vv -t 'cpu info' \
'mailto://myemail:mypass@gmail.com'

# The title field is totally optional
uptime | apprise \
uptime | apprise -vv \
'discord:///4174216298/JHMHI8qBe7bk2ZwO5U711o3dV_js'
```

Expand All @@ -152,16 +157,16 @@ No one wants to put there credentials out for everyone to see on the command lin
# %LOCALAPPDATA%/Apprise/apprise.yml

# If you loaded one of those files, your command line gets really easy:
apprise -t 'my title' -b 'my notification body'
apprise -vv -t 'my title' -b 'my notification body'

# If you want to deviate from the default paths or specify more than one,
# just specify them using the --config switch:
apprise -t 'my title' -b 'my notification body' \
apprise -vv -t 'my title' -b 'my notification body' \
--config=/path/to/my/config.yml

# Got lots of configuration locations? No problem, you can specify them all:
# Apprise can even fetch the configuration from over a network!
apprise -t 'my title' -b 'my notification body' \
apprise -vv -t 'my title' -b 'my notification body' \
--config=/path/to/my/config.yml \
--config=https://localhost/my/apprise/config
```
Expand All @@ -170,13 +175,13 @@ apprise -t 'my title' -b 'my notification body' \
Apprise also supports file attachments too! Specify as many attachments to a notification as you want.
```bash
# Send a funny image you found on the internet to a colleague:
apprise --title 'Agile Joke' \
apprise -vv --title 'Agile Joke' \
--body 'Did you see this one yet?' \
--attach https://i.redd.it/my2t4d2fx0u31.jpg \
'mailto://myemail:mypass@gmail.com'

# Easily send an update from a critical server to your dev team
apprise --title 'system crash' \
apprise -vv --title 'system crash' \
--body 'I do not think Jim fixed the bug; see attached...' \
--attach /var/log/myprogram.log \
--attach /var/debug/core.2345 \
Expand Down
54 changes: 42 additions & 12 deletions apprise/Apprise.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from .common import MATCH_ALL_TAG
from .utils import is_exclusive_match
from .utils import parse_list
from .utils import split_urls
from .utils import parse_urls
from .logger import logger

from .AppriseAsset import AppriseAsset
Expand All @@ -46,13 +46,19 @@
from . import plugins
from . import __version__

# Python v3+ support code made importable so it can remain backwards
# compatible with Python v2
from . import py3compat
ASYNCIO_SUPPORT = not six.PY2


class Apprise(object):
"""
Our Notification Manager

"""
def __init__(self, servers=None, asset=None):

def __init__(self, servers=None, asset=None, debug=False):
"""
Loads a set of server urls while applying the Asset() module to each
if specified.
Expand All @@ -78,6 +84,9 @@ def __init__(self, servers=None, asset=None):
# Initialize our locale object
self.locale = AppriseLocale()

# Set our debug flag
self.debug = debug

@staticmethod
def instantiate(url, asset=None, tag=None, suppress_exceptions=True):
"""
Expand Down Expand Up @@ -111,14 +120,10 @@ def instantiate(url, asset=None, tag=None, suppress_exceptions=True):
# Acquire our url tokens
results = plugins.url_to_dict(url)
if results is None:
# Failed to parse the server URL
logger.error('Unparseable URL {}.'.format(url))
# Failed to parse the server URL; detailed logging handled
# inside url_to_dict - nothing to report here.
return None

logger.trace('URL {} unpacked as:{}{}'.format(
url, os.linesep, os.linesep.join(
['{}="{}"'.format(k, v) for k, v in results.items()])))

elif isinstance(url, dict):
# We already have our result set
results = url
Expand Down Expand Up @@ -154,11 +159,14 @@ def instantiate(url, asset=None, tag=None, suppress_exceptions=True):
plugin = plugins.SCHEMA_MAP[results['schema']](**results)

# Create log entry of loaded URL
logger.debug('Loaded URL: {}'.format(plugin.url()))
logger.debug('Loaded {} URL: {}'.format(
plugins.SCHEMA_MAP[results['schema']].service_name,
plugin.url()))

except Exception:
# the arguments are invalid or can not be used.
logger.error('Could not load URL: %s' % url)
logger.error('Could not load {} URL: {}'.format(
plugins.SCHEMA_MAP[results['schema']].service_name, url))
return None

else:
Expand Down Expand Up @@ -189,7 +197,7 @@ def add(self, servers, asset=None, tag=None):

if isinstance(servers, six.string_types):
# build our server list
servers = split_urls(servers)
servers = parse_urls(servers)
if len(servers) == 0:
return False

Expand Down Expand Up @@ -226,7 +234,7 @@ def add(self, servers, asset=None, tag=None):
# returns None if it fails
instance = Apprise.instantiate(_server, asset=asset, tag=tag)
if not isinstance(instance, NotifyBase):
# No logging is requird as instantiate() handles failure
# No logging is required as instantiate() handles failure
# and/or success reasons for us
return_status = False
continue
Expand Down Expand Up @@ -327,6 +335,10 @@ def notify(self, body, title='', notify_type=NotifyType.INFO,
body_format = self.asset.body_format \
if body_format is None else body_format

# for asyncio support; we track a list of our servers to notify
# sequentially
coroutines = []

# Iterate over our loaded plugins
for server in self.find(tag):
if status is None:
Expand Down Expand Up @@ -384,6 +396,18 @@ def notify(self, body, title='', notify_type=NotifyType.INFO,
# Store entry directly
conversion_map[server.notify_format] = body

if ASYNCIO_SUPPORT and server.asset.async_mode:
# Build a list of servers requiring notification
# that will be triggered asynchronously afterwards
coroutines.append(server.async_notify(
body=conversion_map[server.notify_format],
title=title,
notify_type=notify_type,
attach=attach))

# We gather at this point and notify at the end
continue

try:
# Send notification
if not server.notify(
Expand All @@ -405,6 +429,12 @@ def notify(self, body, title='', notify_type=NotifyType.INFO,
logger.exception("Notification Exception")
status = False

if coroutines:
# perform our async notification(s)
if not py3compat.asyncio.notify(coroutines, debug=self.debug):
# Toggle our status only if we had a failure
status = False

return status

def details(self, lang=None):
Expand Down
6 changes: 6 additions & 0 deletions apprise/AppriseAsset.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ class AppriseAsset(object):
# will be the default.
body_format = None

# Always attempt to send notifications asynchronous (as the same time
# if possible)
# This is a Python 3 supported option only. If set to False, then
# notifications are sent sequentially (one after another)
async_mode = True

def __init__(self, **kwargs):
"""
Asset Initialization
Expand Down
Loading