mirror of
https://github.com/SickGear/SickGear.git
synced 2024-11-25 06:15:04 +00:00
Merge branch 'feature/UpdateApprise' into dev
This commit is contained in:
commit
f69e31b295
129 changed files with 5607 additions and 2321 deletions
|
@ -1,5 +1,6 @@
|
||||||
### 3.31.0 (2023-1x-xx xx:xx:00 UTC)
|
### 3.31.0 (2023-1x-xx xx:xx:00 UTC)
|
||||||
|
|
||||||
|
* Update Apprise 1.3.0 (6458ab0) to 1.6.0 (0c0d5da)
|
||||||
* Update attr 22.2.0 (683d056) to 23.1.0 (67e4ff2)
|
* Update attr 22.2.0 (683d056) to 23.1.0 (67e4ff2)
|
||||||
* Update Beautiful Soup 4.12.2 to 4.12.2 (30c58a1)
|
* Update Beautiful Soup 4.12.2 to 4.12.2 (30c58a1)
|
||||||
* Update diskcache 5.6.1 (4d30686) to 5.6.3 (323787f)
|
* Update diskcache 5.6.1 (4d30686) to 5.6.3 (323787f)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -31,8 +27,8 @@
|
||||||
# POSSIBILITY OF SUCH DAMAGE.
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import concurrent.futures as cf
|
||||||
import os
|
import os
|
||||||
from functools import partial
|
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from . import common
|
from . import common
|
||||||
from .conversion import convert_between
|
from .conversion import convert_between
|
||||||
|
@ -376,7 +372,7 @@ class Apprise:
|
||||||
try:
|
try:
|
||||||
# Process arguments and build synchronous and asynchronous calls
|
# Process arguments and build synchronous and asynchronous calls
|
||||||
# (this step can throw internal errors).
|
# (this step can throw internal errors).
|
||||||
sync_partials, async_cors = self._create_notify_calls(
|
sequential_calls, parallel_calls = self._create_notify_calls(
|
||||||
body, title,
|
body, title,
|
||||||
notify_type=notify_type, body_format=body_format,
|
notify_type=notify_type, body_format=body_format,
|
||||||
tag=tag, match_always=match_always, attach=attach,
|
tag=tag, match_always=match_always, attach=attach,
|
||||||
|
@ -387,49 +383,13 @@ class Apprise:
|
||||||
# No notifications sent, and there was an internal error.
|
# No notifications sent, and there was an internal error.
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not sync_partials and not async_cors:
|
if not sequential_calls and not parallel_calls:
|
||||||
# Nothing to send
|
# Nothing to send
|
||||||
return None
|
return None
|
||||||
|
|
||||||
sync_result = Apprise._notify_all(*sync_partials)
|
sequential_result = Apprise._notify_sequential(*sequential_calls)
|
||||||
|
parallel_result = Apprise._notify_parallel_threadpool(*parallel_calls)
|
||||||
if async_cors:
|
return sequential_result and parallel_result
|
||||||
# A single coroutine sends all asynchronous notifications in
|
|
||||||
# parallel.
|
|
||||||
all_cor = Apprise._async_notify_all(*async_cors)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Python <3.7 automatically starts an event loop if there isn't
|
|
||||||
# already one for the main thread.
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
|
|
||||||
except RuntimeError:
|
|
||||||
# Python >=3.7 raises this exception if there isn't already an
|
|
||||||
# event loop. So, we can spin up our own.
|
|
||||||
loop = asyncio.new_event_loop()
|
|
||||||
asyncio.set_event_loop(loop)
|
|
||||||
loop.set_debug(self.debug)
|
|
||||||
|
|
||||||
# Run the coroutine and wait for the result.
|
|
||||||
async_result = loop.run_until_complete(all_cor)
|
|
||||||
|
|
||||||
# Clean up the loop.
|
|
||||||
loop.close()
|
|
||||||
asyncio.set_event_loop(None)
|
|
||||||
|
|
||||||
else:
|
|
||||||
old_debug = loop.get_debug()
|
|
||||||
loop.set_debug(self.debug)
|
|
||||||
|
|
||||||
# Run the coroutine and wait for the result.
|
|
||||||
async_result = loop.run_until_complete(all_cor)
|
|
||||||
|
|
||||||
loop.set_debug(old_debug)
|
|
||||||
|
|
||||||
else:
|
|
||||||
async_result = True
|
|
||||||
|
|
||||||
return sync_result and async_result
|
|
||||||
|
|
||||||
async def async_notify(self, *args, **kwargs):
|
async def async_notify(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -442,41 +402,42 @@ class Apprise:
|
||||||
try:
|
try:
|
||||||
# Process arguments and build synchronous and asynchronous calls
|
# Process arguments and build synchronous and asynchronous calls
|
||||||
# (this step can throw internal errors).
|
# (this step can throw internal errors).
|
||||||
sync_partials, async_cors = self._create_notify_calls(
|
sequential_calls, parallel_calls = self._create_notify_calls(
|
||||||
*args, **kwargs)
|
*args, **kwargs)
|
||||||
|
|
||||||
except TypeError:
|
except TypeError:
|
||||||
# No notifications sent, and there was an internal error.
|
# No notifications sent, and there was an internal error.
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not sync_partials and not async_cors:
|
if not sequential_calls and not parallel_calls:
|
||||||
# Nothing to send
|
# Nothing to send
|
||||||
return None
|
return None
|
||||||
|
|
||||||
sync_result = Apprise._notify_all(*sync_partials)
|
sequential_result = Apprise._notify_sequential(*sequential_calls)
|
||||||
async_result = await Apprise._async_notify_all(*async_cors)
|
parallel_result = \
|
||||||
return sync_result and async_result
|
await Apprise._notify_parallel_asyncio(*parallel_calls)
|
||||||
|
return sequential_result and parallel_result
|
||||||
|
|
||||||
def _create_notify_calls(self, *args, **kwargs):
|
def _create_notify_calls(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Creates notifications for all the plugins loaded.
|
Creates notifications for all the plugins loaded.
|
||||||
|
|
||||||
Returns a list of synchronous calls (partial functions with no
|
Returns a list of (server, notify() kwargs) tuples for plugins with
|
||||||
arguments required) for plugins with async disabled and a list of
|
parallelism disabled and another list for plugins with parallelism
|
||||||
asynchronous calls (coroutines) for plugins with async enabled.
|
enabled.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
all_calls = list(self._create_notify_gen(*args, **kwargs))
|
all_calls = list(self._create_notify_gen(*args, **kwargs))
|
||||||
|
|
||||||
# Split into synchronous partials and asynchronous coroutines.
|
# Split into sequential and parallel notify() calls.
|
||||||
sync_partials, async_cors = [], []
|
sequential, parallel = [], []
|
||||||
for notify in all_calls:
|
for (server, notify_kwargs) in all_calls:
|
||||||
if asyncio.iscoroutine(notify):
|
if server.asset.async_mode:
|
||||||
async_cors.append(notify)
|
parallel.append((server, notify_kwargs))
|
||||||
else:
|
else:
|
||||||
sync_partials.append(notify)
|
sequential.append((server, notify_kwargs))
|
||||||
|
|
||||||
return sync_partials, async_cors
|
return sequential, parallel
|
||||||
|
|
||||||
def _create_notify_gen(self, body, title='',
|
def _create_notify_gen(self, body, title='',
|
||||||
notify_type=common.NotifyType.INFO,
|
notify_type=common.NotifyType.INFO,
|
||||||
|
@ -493,7 +454,7 @@ class Apprise:
|
||||||
logger.error(msg)
|
logger.error(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
if not (title or body):
|
if not (title or body or attach):
|
||||||
msg = "No message content specified to deliver"
|
msg = "No message content specified to deliver"
|
||||||
logger.error(msg)
|
logger.error(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
@ -533,25 +494,29 @@ class Apprise:
|
||||||
# If our code reaches here, we either did not define a tag (it
|
# If our code reaches here, we either did not define a tag (it
|
||||||
# was set to None), or we did define a tag and the logic above
|
# was set to None), or we did define a tag and the logic above
|
||||||
# determined we need to notify the service it's associated with
|
# determined we need to notify the service it's associated with
|
||||||
if server.notify_format not in conversion_body_map:
|
|
||||||
# Perform Conversion
|
# First we need to generate a key we will use to determine if we
|
||||||
conversion_body_map[server.notify_format] = \
|
# need to build our data out. Entries without are merged with
|
||||||
convert_between(
|
# the body at this stage.
|
||||||
body_format, server.notify_format, content=body)
|
key = server.notify_format if server.title_maxlen > 0\
|
||||||
|
else f'_{server.notify_format}'
|
||||||
|
|
||||||
|
if key not in conversion_title_map:
|
||||||
|
|
||||||
# Prepare our title
|
# Prepare our title
|
||||||
conversion_title_map[server.notify_format] = \
|
conversion_title_map[key] = '' if not title else title
|
||||||
'' if not title else title
|
|
||||||
|
|
||||||
# Tidy Title IF required (hence it will become part of the
|
# Conversion of title only occurs for services where the title
|
||||||
# body)
|
# is blended with the body (title_maxlen <= 0)
|
||||||
if server.title_maxlen <= 0 and \
|
if conversion_title_map[key] and server.title_maxlen <= 0:
|
||||||
conversion_title_map[server.notify_format]:
|
conversion_title_map[key] = convert_between(
|
||||||
|
|
||||||
conversion_title_map[server.notify_format] = \
|
|
||||||
convert_between(
|
|
||||||
body_format, server.notify_format,
|
body_format, server.notify_format,
|
||||||
content=conversion_title_map[server.notify_format])
|
content=conversion_title_map[key])
|
||||||
|
|
||||||
|
# Our body is always converted no matter what
|
||||||
|
conversion_body_map[key] = \
|
||||||
|
convert_between(
|
||||||
|
body_format, server.notify_format, content=body)
|
||||||
|
|
||||||
if interpret_escapes:
|
if interpret_escapes:
|
||||||
#
|
#
|
||||||
|
@ -561,13 +526,13 @@ class Apprise:
|
||||||
try:
|
try:
|
||||||
# Added overhead required due to Python 3 Encoding Bug
|
# Added overhead required due to Python 3 Encoding Bug
|
||||||
# identified here: https://bugs.python.org/issue21331
|
# identified here: https://bugs.python.org/issue21331
|
||||||
conversion_body_map[server.notify_format] = \
|
conversion_body_map[key] = \
|
||||||
conversion_body_map[server.notify_format]\
|
conversion_body_map[key]\
|
||||||
.encode('ascii', 'backslashreplace')\
|
.encode('ascii', 'backslashreplace')\
|
||||||
.decode('unicode-escape')
|
.decode('unicode-escape')
|
||||||
|
|
||||||
conversion_title_map[server.notify_format] = \
|
conversion_title_map[key] = \
|
||||||
conversion_title_map[server.notify_format]\
|
conversion_title_map[key]\
|
||||||
.encode('ascii', 'backslashreplace')\
|
.encode('ascii', 'backslashreplace')\
|
||||||
.decode('unicode-escape')
|
.decode('unicode-escape')
|
||||||
|
|
||||||
|
@ -578,29 +543,26 @@ class Apprise:
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
kwargs = dict(
|
kwargs = dict(
|
||||||
body=conversion_body_map[server.notify_format],
|
body=conversion_body_map[key],
|
||||||
title=conversion_title_map[server.notify_format],
|
title=conversion_title_map[key],
|
||||||
notify_type=notify_type,
|
notify_type=notify_type,
|
||||||
attach=attach,
|
attach=attach,
|
||||||
body_format=body_format
|
body_format=body_format
|
||||||
)
|
)
|
||||||
if server.asset.async_mode:
|
yield (server, kwargs)
|
||||||
yield server.async_notify(**kwargs)
|
|
||||||
else:
|
|
||||||
yield partial(server.notify, **kwargs)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _notify_all(*partials):
|
def _notify_sequential(*servers_kwargs):
|
||||||
"""
|
"""
|
||||||
Process a list of synchronous notify() calls.
|
Process a list of notify() calls sequentially and synchronously.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
success = True
|
success = True
|
||||||
|
|
||||||
for notify in partials:
|
for (server, kwargs) in servers_kwargs:
|
||||||
try:
|
try:
|
||||||
# Send notification
|
# Send notification
|
||||||
result = notify()
|
result = server.notify(**kwargs)
|
||||||
success = success and result
|
success = success and result
|
||||||
|
|
||||||
except TypeError:
|
except TypeError:
|
||||||
|
@ -616,14 +578,71 @@ class Apprise:
|
||||||
return success
|
return success
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _async_notify_all(*cors):
|
def _notify_parallel_threadpool(*servers_kwargs):
|
||||||
"""
|
"""
|
||||||
Process a list of asynchronous async_notify() calls.
|
Process a list of notify() calls in parallel and synchronously.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
n_calls = len(servers_kwargs)
|
||||||
|
|
||||||
|
# 0-length case
|
||||||
|
if n_calls == 0:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# There's no need to use a thread pool for just a single notification
|
||||||
|
if n_calls == 1:
|
||||||
|
return Apprise._notify_sequential(servers_kwargs[0])
|
||||||
|
|
||||||
# Create log entry
|
# Create log entry
|
||||||
logger.info('Notifying %d service(s) asynchronously.', len(cors))
|
logger.info(
|
||||||
|
'Notifying %d service(s) with threads.', len(servers_kwargs))
|
||||||
|
|
||||||
|
with cf.ThreadPoolExecutor() as executor:
|
||||||
|
success = True
|
||||||
|
futures = [executor.submit(server.notify, **kwargs)
|
||||||
|
for (server, kwargs) in servers_kwargs]
|
||||||
|
|
||||||
|
for future in cf.as_completed(futures):
|
||||||
|
try:
|
||||||
|
result = future.result()
|
||||||
|
success = success and result
|
||||||
|
|
||||||
|
except TypeError:
|
||||||
|
# These are our internally thrown notifications.
|
||||||
|
success = False
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
# A catch all so we don't have to abort early
|
||||||
|
# just because one of our plugins has a bug in it.
|
||||||
|
logger.exception("Unhandled Notification Exception")
|
||||||
|
success = False
|
||||||
|
|
||||||
|
return success
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def _notify_parallel_asyncio(*servers_kwargs):
|
||||||
|
"""
|
||||||
|
Process a list of async_notify() calls in parallel and asynchronously.
|
||||||
|
"""
|
||||||
|
|
||||||
|
n_calls = len(servers_kwargs)
|
||||||
|
|
||||||
|
# 0-length case
|
||||||
|
if n_calls == 0:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# (Unlike with the thread pool, we don't optimize for the single-
|
||||||
|
# notification case because asyncio can do useful work while waiting
|
||||||
|
# for that thread to complete)
|
||||||
|
|
||||||
|
# Create log entry
|
||||||
|
logger.info(
|
||||||
|
'Notifying %d service(s) asynchronously.', len(servers_kwargs))
|
||||||
|
|
||||||
|
async def do_call(server, kwargs):
|
||||||
|
return await server.async_notify(**kwargs)
|
||||||
|
|
||||||
|
cors = (do_call(server, kwargs) for (server, kwargs) in servers_kwargs)
|
||||||
results = await asyncio.gather(*cors, return_exceptions=True)
|
results = await asyncio.gather(*cors, return_exceptions=True)
|
||||||
|
|
||||||
if any(isinstance(status, Exception)
|
if any(isinstance(status, Exception)
|
||||||
|
@ -665,6 +684,12 @@ class Apprise:
|
||||||
'setup_url': getattr(plugin, 'setup_url', None),
|
'setup_url': getattr(plugin, 'setup_url', None),
|
||||||
# Placeholder - populated below
|
# Placeholder - populated below
|
||||||
'details': None,
|
'details': None,
|
||||||
|
|
||||||
|
# Let upstream service know of the plugins that support
|
||||||
|
# attachments
|
||||||
|
'attachment_support': getattr(
|
||||||
|
plugin, 'attachment_support', False),
|
||||||
|
|
||||||
# Differentiat between what is a custom loaded plugin and
|
# Differentiat between what is a custom loaded plugin and
|
||||||
# which is native.
|
# which is native.
|
||||||
'category': getattr(plugin, 'category', None)
|
'category': getattr(plugin, 'category', None)
|
||||||
|
@ -790,6 +815,36 @@ class Apprise:
|
||||||
# If we reach here, then we indexed out of range
|
# If we reach here, then we indexed out of range
|
||||||
raise IndexError('list index out of range')
|
raise IndexError('list index out of range')
|
||||||
|
|
||||||
|
def __getstate__(self):
|
||||||
|
"""
|
||||||
|
Pickle Support dumps()
|
||||||
|
"""
|
||||||
|
attributes = {
|
||||||
|
'asset': self.asset,
|
||||||
|
# Prepare our URL list as we need to extract the associated tags
|
||||||
|
# and asset details associated with it
|
||||||
|
'urls': [{
|
||||||
|
'url': server.url(privacy=False),
|
||||||
|
'tag': server.tags if server.tags else None,
|
||||||
|
'asset': server.asset} for server in self.servers],
|
||||||
|
'locale': self.locale,
|
||||||
|
'debug': self.debug,
|
||||||
|
'location': self.location,
|
||||||
|
}
|
||||||
|
|
||||||
|
return attributes
|
||||||
|
|
||||||
|
def __setstate__(self, state):
|
||||||
|
"""
|
||||||
|
Pickle Support loads()
|
||||||
|
"""
|
||||||
|
self.servers = list()
|
||||||
|
self.asset = state['asset']
|
||||||
|
self.locale = state['locale']
|
||||||
|
self.location = state['location']
|
||||||
|
for entry in state['urls']:
|
||||||
|
self.add(entry['url'], asset=entry['asset'], tag=entry['tag'])
|
||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self):
|
||||||
"""
|
"""
|
||||||
Allows the Apprise object to be wrapped in an 'if statement'.
|
Allows the Apprise object to be wrapped in an 'if statement'.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -33,14 +29,13 @@
|
||||||
import ctypes
|
import ctypes
|
||||||
import locale
|
import locale
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import os
|
||||||
|
import re
|
||||||
from os.path import join
|
from os.path import join
|
||||||
from os.path import dirname
|
from os.path import dirname
|
||||||
from os.path import abspath
|
from os.path import abspath
|
||||||
from .logger import logger
|
from .logger import logger
|
||||||
|
|
||||||
# Define our translation domain
|
|
||||||
DOMAIN = 'apprise'
|
|
||||||
LOCALE_DIR = abspath(join(dirname(__file__), 'i18n'))
|
|
||||||
|
|
||||||
# This gets toggled to True if we succeed
|
# This gets toggled to True if we succeed
|
||||||
GETTEXT_LOADED = False
|
GETTEXT_LOADED = False
|
||||||
|
@ -49,17 +44,220 @@ try:
|
||||||
# Initialize gettext
|
# Initialize gettext
|
||||||
import gettext
|
import gettext
|
||||||
|
|
||||||
# install() creates a _() in our builtins
|
|
||||||
gettext.install(DOMAIN, localedir=LOCALE_DIR)
|
|
||||||
|
|
||||||
# Toggle our flag
|
# Toggle our flag
|
||||||
GETTEXT_LOADED = True
|
GETTEXT_LOADED = True
|
||||||
|
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# gettext isn't available; no problem, just fall back to using
|
# gettext isn't available; no problem; Use the library features without
|
||||||
# the library features without multi-language support.
|
# multi-language support.
|
||||||
import builtins
|
pass
|
||||||
builtins.__dict__['_'] = lambda x: x # pragma: no branch
|
|
||||||
|
|
||||||
|
class AppriseLocale:
|
||||||
|
"""
|
||||||
|
A wrapper class to gettext so that we can manipulate multiple lanaguages
|
||||||
|
on the fly if required.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Define our translation domain
|
||||||
|
_domain = 'apprise'
|
||||||
|
|
||||||
|
# The path to our translations
|
||||||
|
_locale_dir = abspath(join(dirname(__file__), 'i18n'))
|
||||||
|
|
||||||
|
# Locale regular expression
|
||||||
|
_local_re = re.compile(
|
||||||
|
r'^((?P<ansii>C)|(?P<lang>([a-z]{2}))([_:](?P<country>[a-z]{2}))?)'
|
||||||
|
r'(\.(?P<enc>[a-z0-9-]+))?$', re.IGNORECASE)
|
||||||
|
|
||||||
|
# Define our default encoding
|
||||||
|
_default_encoding = 'utf-8'
|
||||||
|
|
||||||
|
# The function to assign `_` by default
|
||||||
|
_fn = 'gettext'
|
||||||
|
|
||||||
|
# The language we should fall back to if all else fails
|
||||||
|
_default_language = 'en'
|
||||||
|
|
||||||
|
def __init__(self, language=None):
|
||||||
|
"""
|
||||||
|
Initializes our object, if a language is specified, then we
|
||||||
|
initialize ourselves to that, otherwise we use whatever we detect
|
||||||
|
from the local operating system. If all else fails, we resort to the
|
||||||
|
defined default_language.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Cache previously loaded translations
|
||||||
|
self._gtobjs = {}
|
||||||
|
|
||||||
|
# Get our language
|
||||||
|
self.lang = AppriseLocale.detect_language(language)
|
||||||
|
|
||||||
|
# Our mapping to our _fn
|
||||||
|
self.__fn_map = None
|
||||||
|
|
||||||
|
if GETTEXT_LOADED is False:
|
||||||
|
# We're done
|
||||||
|
return
|
||||||
|
|
||||||
|
# Add language
|
||||||
|
self.add(self.lang)
|
||||||
|
|
||||||
|
def add(self, lang=None, set_default=True):
|
||||||
|
"""
|
||||||
|
Add a language to our list
|
||||||
|
"""
|
||||||
|
lang = lang if lang else self._default_language
|
||||||
|
if lang not in self._gtobjs:
|
||||||
|
# Load our gettext object and install our language
|
||||||
|
try:
|
||||||
|
self._gtobjs[lang] = gettext.translation(
|
||||||
|
self._domain, localedir=self._locale_dir, languages=[lang],
|
||||||
|
fallback=False)
|
||||||
|
|
||||||
|
# The non-intrusive method of applying the gettext change to
|
||||||
|
# the global namespace only
|
||||||
|
self.__fn_map = getattr(self._gtobjs[lang], self._fn)
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
# The translation directory does not exist
|
||||||
|
logger.debug(
|
||||||
|
'Could not load translation path: %s',
|
||||||
|
join(self._locale_dir, lang))
|
||||||
|
|
||||||
|
# Fallback (handle case where self.lang does not exist)
|
||||||
|
if self.lang not in self._gtobjs:
|
||||||
|
self._gtobjs[self.lang] = gettext
|
||||||
|
self.__fn_map = getattr(self._gtobjs[self.lang], self._fn)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
logger.trace('Loaded language %s', lang)
|
||||||
|
|
||||||
|
if set_default:
|
||||||
|
logger.debug('Language set to %s', lang)
|
||||||
|
self.lang = lang
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def lang_at(self, lang, mapto=_fn):
|
||||||
|
"""
|
||||||
|
The syntax works as:
|
||||||
|
with at.lang_at('fr'):
|
||||||
|
# apprise works as though the french language has been
|
||||||
|
# defined. afterwards, the language falls back to whatever
|
||||||
|
# it was.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if GETTEXT_LOADED is False:
|
||||||
|
# Do nothing
|
||||||
|
yield None
|
||||||
|
|
||||||
|
# we're done
|
||||||
|
return
|
||||||
|
|
||||||
|
# Tidy the language
|
||||||
|
lang = AppriseLocale.detect_language(lang, detect_fallback=False)
|
||||||
|
if lang not in self._gtobjs and not self.add(lang, set_default=False):
|
||||||
|
# Do Nothing
|
||||||
|
yield getattr(self._gtobjs[self.lang], mapto)
|
||||||
|
else:
|
||||||
|
# Yield
|
||||||
|
yield getattr(self._gtobjs[lang], mapto)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
@property
|
||||||
|
def gettext(self):
|
||||||
|
"""
|
||||||
|
Return the current language gettext() function
|
||||||
|
|
||||||
|
Useful for assigning to `_`
|
||||||
|
"""
|
||||||
|
return self._gtobjs[self.lang].gettext
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def detect_language(lang=None, detect_fallback=True):
|
||||||
|
"""
|
||||||
|
Returns the language (if it's retrievable)
|
||||||
|
"""
|
||||||
|
# We want to only use the 2 character version of this language
|
||||||
|
# hence en_CA becomes en, en_US becomes en.
|
||||||
|
if not isinstance(lang, str):
|
||||||
|
if detect_fallback is False:
|
||||||
|
# no detection enabled; we're done
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Posix lookup
|
||||||
|
lookup = os.environ.get
|
||||||
|
localename = None
|
||||||
|
for variable in ('LC_ALL', 'LC_CTYPE', 'LANG', 'LANGUAGE'):
|
||||||
|
localename = lookup(variable, None)
|
||||||
|
if localename:
|
||||||
|
result = AppriseLocale._local_re.match(localename)
|
||||||
|
if result and result.group('lang'):
|
||||||
|
return result.group('lang').lower()
|
||||||
|
|
||||||
|
# Windows handling
|
||||||
|
if hasattr(ctypes, 'windll'):
|
||||||
|
windll = ctypes.windll.kernel32
|
||||||
|
try:
|
||||||
|
lang = locale.windows_locale[
|
||||||
|
windll.GetUserDefaultUILanguage()]
|
||||||
|
|
||||||
|
# Our detected windows language
|
||||||
|
return lang[0:2].lower()
|
||||||
|
|
||||||
|
except (TypeError, KeyError):
|
||||||
|
# Fallback to posix detection
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Built in locale library check
|
||||||
|
try:
|
||||||
|
# Acquire our locale
|
||||||
|
lang = locale.getlocale()[0]
|
||||||
|
|
||||||
|
except (ValueError, TypeError) as e:
|
||||||
|
# This occurs when an invalid locale was parsed from the
|
||||||
|
# environment variable. While we still return None in this
|
||||||
|
# case, we want to better notify the end user of this. Users
|
||||||
|
# receiving this error should check their environment
|
||||||
|
# variables.
|
||||||
|
logger.warning(
|
||||||
|
'Language detection failure / {}'.format(str(e)))
|
||||||
|
return None
|
||||||
|
|
||||||
|
return None if not lang else lang[0:2].lower()
|
||||||
|
|
||||||
|
def __getstate__(self):
|
||||||
|
"""
|
||||||
|
Pickle Support dumps()
|
||||||
|
"""
|
||||||
|
state = self.__dict__.copy()
|
||||||
|
|
||||||
|
# Remove the unpicklable entries.
|
||||||
|
del state['_gtobjs']
|
||||||
|
del state['_AppriseLocale__fn_map']
|
||||||
|
return state
|
||||||
|
|
||||||
|
def __setstate__(self, state):
|
||||||
|
"""
|
||||||
|
Pickle Support loads()
|
||||||
|
"""
|
||||||
|
self.__dict__.update(state)
|
||||||
|
# Our mapping to our _fn
|
||||||
|
self.__fn_map = None
|
||||||
|
self._gtobjs = {}
|
||||||
|
self.add(state['lang'], set_default=True)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Prepare our default LOCALE Singleton
|
||||||
|
#
|
||||||
|
LOCALE = AppriseLocale()
|
||||||
|
|
||||||
|
|
||||||
class LazyTranslation:
|
class LazyTranslation:
|
||||||
|
@ -77,7 +275,7 @@ class LazyTranslation:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return gettext.gettext(self.text)
|
return LOCALE.gettext(self.text) if GETTEXT_LOADED else self.text
|
||||||
|
|
||||||
|
|
||||||
# Lazy translation handling
|
# Lazy translation handling
|
||||||
|
@ -86,140 +284,3 @@ def gettext_lazy(text):
|
||||||
A dummy function that can be referenced
|
A dummy function that can be referenced
|
||||||
"""
|
"""
|
||||||
return LazyTranslation(text=text)
|
return LazyTranslation(text=text)
|
||||||
|
|
||||||
|
|
||||||
class AppriseLocale:
|
|
||||||
"""
|
|
||||||
A wrapper class to gettext so that we can manipulate multiple lanaguages
|
|
||||||
on the fly if required.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, language=None):
|
|
||||||
"""
|
|
||||||
Initializes our object, if a language is specified, then we
|
|
||||||
initialize ourselves to that, otherwise we use whatever we detect
|
|
||||||
from the local operating system. If all else fails, we resort to the
|
|
||||||
defined default_language.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Cache previously loaded translations
|
|
||||||
self._gtobjs = {}
|
|
||||||
|
|
||||||
# Get our language
|
|
||||||
self.lang = AppriseLocale.detect_language(language)
|
|
||||||
|
|
||||||
if GETTEXT_LOADED is False:
|
|
||||||
# We're done
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.lang:
|
|
||||||
# Load our gettext object and install our language
|
|
||||||
try:
|
|
||||||
self._gtobjs[self.lang] = gettext.translation(
|
|
||||||
DOMAIN, localedir=LOCALE_DIR, languages=[self.lang])
|
|
||||||
|
|
||||||
# Install our language
|
|
||||||
self._gtobjs[self.lang].install()
|
|
||||||
|
|
||||||
except IOError:
|
|
||||||
# This occurs if we can't access/load our translations
|
|
||||||
pass
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def lang_at(self, lang):
|
|
||||||
"""
|
|
||||||
The syntax works as:
|
|
||||||
with at.lang_at('fr'):
|
|
||||||
# apprise works as though the french language has been
|
|
||||||
# defined. afterwards, the language falls back to whatever
|
|
||||||
# it was.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if GETTEXT_LOADED is False:
|
|
||||||
# yield
|
|
||||||
yield
|
|
||||||
|
|
||||||
# we're done
|
|
||||||
return
|
|
||||||
|
|
||||||
# Tidy the language
|
|
||||||
lang = AppriseLocale.detect_language(lang, detect_fallback=False)
|
|
||||||
|
|
||||||
# Now attempt to load it
|
|
||||||
try:
|
|
||||||
if lang in self._gtobjs:
|
|
||||||
if lang != self.lang:
|
|
||||||
# Install our language only if we aren't using it
|
|
||||||
# already
|
|
||||||
self._gtobjs[lang].install()
|
|
||||||
|
|
||||||
else:
|
|
||||||
self._gtobjs[lang] = gettext.translation(
|
|
||||||
DOMAIN, localedir=LOCALE_DIR, languages=[self.lang])
|
|
||||||
|
|
||||||
# Install our language
|
|
||||||
self._gtobjs[lang].install()
|
|
||||||
|
|
||||||
# Yield
|
|
||||||
yield
|
|
||||||
|
|
||||||
except (IOError, KeyError):
|
|
||||||
# This occurs if we can't access/load our translations
|
|
||||||
# Yield reguardless
|
|
||||||
yield
|
|
||||||
|
|
||||||
finally:
|
|
||||||
# Fall back to our previous language
|
|
||||||
if lang != self.lang and lang in self._gtobjs:
|
|
||||||
# Install our language
|
|
||||||
self._gtobjs[self.lang].install()
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def detect_language(lang=None, detect_fallback=True):
|
|
||||||
"""
|
|
||||||
returns the language (if it's retrievable)
|
|
||||||
"""
|
|
||||||
# We want to only use the 2 character version of this language
|
|
||||||
# hence en_CA becomes en, en_US becomes en.
|
|
||||||
if not isinstance(lang, str):
|
|
||||||
if detect_fallback is False:
|
|
||||||
# no detection enabled; we're done
|
|
||||||
return None
|
|
||||||
|
|
||||||
if hasattr(ctypes, 'windll'):
|
|
||||||
windll = ctypes.windll.kernel32
|
|
||||||
try:
|
|
||||||
lang = locale.windows_locale[
|
|
||||||
windll.GetUserDefaultUILanguage()]
|
|
||||||
|
|
||||||
# Our detected windows language
|
|
||||||
return lang[0:2].lower()
|
|
||||||
|
|
||||||
except (TypeError, KeyError):
|
|
||||||
# Fallback to posix detection
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Detect language
|
|
||||||
lang = locale.getdefaultlocale()[0]
|
|
||||||
|
|
||||||
except ValueError as e:
|
|
||||||
# This occurs when an invalid locale was parsed from the
|
|
||||||
# environment variable. While we still return None in this
|
|
||||||
# case, we want to better notify the end user of this. Users
|
|
||||||
# receiving this error should check their environment
|
|
||||||
# variables.
|
|
||||||
logger.warning(
|
|
||||||
'Language detection failure / {}'.format(str(e)))
|
|
||||||
return None
|
|
||||||
|
|
||||||
except TypeError:
|
|
||||||
# None is returned if the default can't be determined
|
|
||||||
# we're done in this case
|
|
||||||
return None
|
|
||||||
|
|
||||||
return None if not lang else lang[0:2].lower()
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -204,7 +200,14 @@ class URLBase:
|
||||||
self.verify_certificate = parse_bool(kwargs.get('verify', True))
|
self.verify_certificate = parse_bool(kwargs.get('verify', True))
|
||||||
|
|
||||||
# Secure Mode
|
# Secure Mode
|
||||||
self.secure = kwargs.get('secure', False)
|
self.secure = kwargs.get('secure', None)
|
||||||
|
try:
|
||||||
|
if not isinstance(self.secure, bool):
|
||||||
|
# Attempt to detect
|
||||||
|
self.secure = kwargs.get('schema', '')[-1].lower() == 's'
|
||||||
|
|
||||||
|
except (TypeError, IndexError):
|
||||||
|
self.secure = False
|
||||||
|
|
||||||
self.host = URLBase.unquote(kwargs.get('host'))
|
self.host = URLBase.unquote(kwargs.get('host'))
|
||||||
self.port = kwargs.get('port')
|
self.port = kwargs.get('port')
|
||||||
|
@ -228,6 +231,11 @@ class URLBase:
|
||||||
# Always unquote the password if it exists
|
# Always unquote the password if it exists
|
||||||
self.password = URLBase.unquote(self.password)
|
self.password = URLBase.unquote(self.password)
|
||||||
|
|
||||||
|
# Store our full path consistently ensuring it ends with a `/'
|
||||||
|
self.fullpath = URLBase.unquote(kwargs.get('fullpath'))
|
||||||
|
if not isinstance(self.fullpath, str) or not self.fullpath:
|
||||||
|
self.fullpath = '/'
|
||||||
|
|
||||||
# Store our Timeout Variables
|
# Store our Timeout Variables
|
||||||
if 'rto' in kwargs:
|
if 'rto' in kwargs:
|
||||||
try:
|
try:
|
||||||
|
@ -307,7 +315,36 @@ class URLBase:
|
||||||
arguments provied.
|
arguments provied.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError("url() is implimented by the child class.")
|
|
||||||
|
# Our default parameters
|
||||||
|
params = self.url_parameters(privacy=privacy, *args, **kwargs)
|
||||||
|
|
||||||
|
# Determine Authentication
|
||||||
|
auth = ''
|
||||||
|
if self.user and self.password:
|
||||||
|
auth = '{user}:{password}@'.format(
|
||||||
|
user=URLBase.quote(self.user, safe=''),
|
||||||
|
password=self.pprint(
|
||||||
|
self.password, privacy, mode=PrivacyMode.Secret, safe=''),
|
||||||
|
)
|
||||||
|
elif self.user:
|
||||||
|
auth = '{user}@'.format(
|
||||||
|
user=URLBase.quote(self.user, safe=''),
|
||||||
|
)
|
||||||
|
|
||||||
|
default_port = 443 if self.secure else 80
|
||||||
|
|
||||||
|
return '{schema}://{auth}{hostname}{port}{fullpath}?{params}'.format(
|
||||||
|
schema='https' if self.secure else 'http',
|
||||||
|
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),
|
||||||
|
fullpath=URLBase.quote(self.fullpath, safe='/')
|
||||||
|
if self.fullpath else '/',
|
||||||
|
params=URLBase.urlencode(params),
|
||||||
|
)
|
||||||
|
|
||||||
def __contains__(self, tags):
|
def __contains__(self, tags):
|
||||||
"""
|
"""
|
||||||
|
@ -583,6 +620,33 @@ class URLBase:
|
||||||
"""
|
"""
|
||||||
return (self.socket_connect_timeout, self.socket_read_timeout)
|
return (self.socket_connect_timeout, self.socket_read_timeout)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def request_auth(self):
|
||||||
|
"""This is primarily used to fullfill the `auth` keyword argument
|
||||||
|
that is used by requests.get() and requests.put() calls.
|
||||||
|
"""
|
||||||
|
return (self.user, self.password) if self.user else None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def request_url(self):
|
||||||
|
"""
|
||||||
|
Assemble a simple URL that can be used by the requests library
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Acquire our schema
|
||||||
|
schema = 'https' if self.secure else 'http'
|
||||||
|
|
||||||
|
# Prepare our URL
|
||||||
|
url = '%s://%s' % (schema, self.host)
|
||||||
|
|
||||||
|
# Apply Port information if present
|
||||||
|
if isinstance(self.port, int):
|
||||||
|
url += ':%d' % self.port
|
||||||
|
|
||||||
|
# Append our full path
|
||||||
|
return url + self.fullpath
|
||||||
|
|
||||||
def url_parameters(self, *args, **kwargs):
|
def url_parameters(self, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Provides a default set of args to work with. This can greatly
|
Provides a default set of args to work with. This can greatly
|
||||||
|
@ -603,7 +667,8 @@ class URLBase:
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url, verify_host=True, plus_to_space=False):
|
def parse_url(url, verify_host=True, plus_to_space=False,
|
||||||
|
strict_port=False):
|
||||||
"""Parses the URL and returns it broken apart into a dictionary.
|
"""Parses the URL and returns it broken apart into a dictionary.
|
||||||
|
|
||||||
This is very specific and customized for Apprise.
|
This is very specific and customized for Apprise.
|
||||||
|
@ -624,13 +689,13 @@ class URLBase:
|
||||||
|
|
||||||
results = parse_url(
|
results = parse_url(
|
||||||
url, default_schema='unknown', verify_host=verify_host,
|
url, default_schema='unknown', verify_host=verify_host,
|
||||||
plus_to_space=plus_to_space)
|
plus_to_space=plus_to_space, strict_port=strict_port)
|
||||||
|
|
||||||
if not results:
|
if not results:
|
||||||
# We're done; we failed to parse our url
|
# We're done; we failed to parse our url
|
||||||
return results
|
return results
|
||||||
|
|
||||||
# if our URL ends with an 's', then assueme our secure flag is set.
|
# if our URL ends with an 's', then assume our secure flag is set.
|
||||||
results['secure'] = (results['schema'][-1] == 's')
|
results['secure'] = (results['schema'][-1] == 's')
|
||||||
|
|
||||||
# Support SSL Certificate 'verify' keyword. Default to being enabled
|
# Support SSL Certificate 'verify' keyword. Default to being enabled
|
||||||
|
@ -650,6 +715,21 @@ class URLBase:
|
||||||
if 'user' in results['qsd']:
|
if 'user' in results['qsd']:
|
||||||
results['user'] = results['qsd']['user']
|
results['user'] = results['qsd']['user']
|
||||||
|
|
||||||
|
# parse_url() always creates a 'password' and 'user' entry in the
|
||||||
|
# results returned. Entries are set to None if they weren't specified
|
||||||
|
if results['password'] is None and 'user' in results['qsd']:
|
||||||
|
# Handle cases where the user= provided in 2 locations, we want
|
||||||
|
# the original to fall back as a being a password (if one wasn't
|
||||||
|
# otherwise defined)
|
||||||
|
# e.g.
|
||||||
|
# mailtos://PASSWORD@hostname?user=admin@mail-domain.com
|
||||||
|
# - the PASSWORD gets lost in the parse url() since a user=
|
||||||
|
# over-ride is specified.
|
||||||
|
presults = parse_url(results['url'])
|
||||||
|
if presults:
|
||||||
|
# Store our Password
|
||||||
|
results['password'] = presults['user']
|
||||||
|
|
||||||
# Store our socket read timeout if specified
|
# Store our socket read timeout if specified
|
||||||
if 'rto' in results['qsd']:
|
if 'rto' in results['qsd']:
|
||||||
results['rto'] = results['qsd']['rto']
|
results['rto'] = results['qsd']['rto']
|
||||||
|
@ -685,6 +765,15 @@ class URLBase:
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Should be over-ridden and allows the tracking of how many targets
|
||||||
|
are associated with each URLBase object.
|
||||||
|
|
||||||
|
Default is always 1
|
||||||
|
"""
|
||||||
|
return 1
|
||||||
|
|
||||||
def schemas(self):
|
def schemas(self):
|
||||||
"""A simple function that returns a set of all schemas associated
|
"""A simple function that returns a set of all schemas associated
|
||||||
with this object based on the object.protocol and
|
with this object based on the object.protocol and
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -31,7 +27,7 @@
|
||||||
# POSSIBILITY OF SUCH DAMAGE.
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
__title__ = 'Apprise'
|
__title__ = 'Apprise'
|
||||||
__version__ = '1.3.0'
|
__version__ = '1.6.0'
|
||||||
__author__ = 'Chris Caron'
|
__author__ = 'Chris Caron'
|
||||||
__license__ = 'BSD'
|
__license__ = 'BSD'
|
||||||
__copywrite__ = 'Copyright (C) 2023 Chris Caron <lead2gold@gmail.com>'
|
__copywrite__ = 'Copyright (C) 2023 Chris Caron <lead2gold@gmail.com>'
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -68,7 +64,8 @@ class AttachBase(URLBase):
|
||||||
# set to zero (0), then no check is performed
|
# set to zero (0), then no check is performed
|
||||||
# 1 MB = 1048576 bytes
|
# 1 MB = 1048576 bytes
|
||||||
# 5 MB = 5242880 bytes
|
# 5 MB = 5242880 bytes
|
||||||
max_file_size = 5242880
|
# 1 GB = 1048576000 bytes
|
||||||
|
max_file_size = 1048576000
|
||||||
|
|
||||||
# By default all attachments types are inaccessible.
|
# By default all attachments types are inaccessible.
|
||||||
# Developers of items identified in the attachment plugin directory
|
# Developers of items identified in the attachment plugin directory
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -355,6 +351,77 @@ class ConfigBase(URLBase):
|
||||||
# missing and/or expired.
|
# missing and/or expired.
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __normalize_tag_groups(group_tags):
|
||||||
|
"""
|
||||||
|
Used to normalize a tag assign map which looks like:
|
||||||
|
{
|
||||||
|
'group': set('{tag1}', '{group1}', '{tag2}'),
|
||||||
|
'group1': set('{tag2}','{tag3}'),
|
||||||
|
}
|
||||||
|
|
||||||
|
Then normalized it (merging groups); with respect to the above, the
|
||||||
|
output would be:
|
||||||
|
{
|
||||||
|
'group': set('{tag1}', '{tag2}', '{tag3}),
|
||||||
|
'group1': set('{tag2}','{tag3}'),
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Prepare a key set list we can use
|
||||||
|
tag_groups = set([str(x) for x in group_tags.keys()])
|
||||||
|
|
||||||
|
def _expand(tags, ignore=None):
|
||||||
|
"""
|
||||||
|
Expands based on tag provided and returns a set
|
||||||
|
|
||||||
|
this also updates the group_tags while it goes
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Prepare ourselves a return set
|
||||||
|
results = set()
|
||||||
|
ignore = set() if ignore is None else ignore
|
||||||
|
|
||||||
|
# track groups
|
||||||
|
groups = set()
|
||||||
|
|
||||||
|
for tag in tags:
|
||||||
|
if tag in ignore:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Track our groups
|
||||||
|
groups.add(tag)
|
||||||
|
|
||||||
|
# Store what we know is worth keping
|
||||||
|
results |= group_tags[tag] - tag_groups
|
||||||
|
|
||||||
|
# Get simple tag assignments
|
||||||
|
found = group_tags[tag] & tag_groups
|
||||||
|
if not found:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for gtag in found:
|
||||||
|
if gtag in ignore:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Go deeper (recursion)
|
||||||
|
ignore.add(tag)
|
||||||
|
group_tags[gtag] = _expand(set([gtag]), ignore=ignore)
|
||||||
|
results |= group_tags[gtag]
|
||||||
|
|
||||||
|
# Pop ignore
|
||||||
|
ignore.remove(tag)
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
for tag in tag_groups:
|
||||||
|
# Get our tags
|
||||||
|
group_tags[tag] |= _expand(set([tag]))
|
||||||
|
if not group_tags[tag]:
|
||||||
|
ConfigBase.logger.warning(
|
||||||
|
'The group {} has no tags assigned to it'.format(tag))
|
||||||
|
del group_tags[tag]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url, verify_host=True):
|
def parse_url(url, verify_host=True):
|
||||||
"""Parses the URL and returns it broken apart into a dictionary.
|
"""Parses the URL and returns it broken apart into a dictionary.
|
||||||
|
@ -533,6 +600,9 @@ class ConfigBase(URLBase):
|
||||||
# as additional configuration entries when loaded.
|
# as additional configuration entries when loaded.
|
||||||
include <ConfigURL>
|
include <ConfigURL>
|
||||||
|
|
||||||
|
# Assign tag contents to a group identifier
|
||||||
|
<Group(s)>=<Tag(s)>
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# A list of loaded Notification Services
|
# A list of loaded Notification Services
|
||||||
servers = list()
|
servers = list()
|
||||||
|
@ -541,6 +611,12 @@ class ConfigBase(URLBase):
|
||||||
# the include keyword
|
# the include keyword
|
||||||
configs = list()
|
configs = list()
|
||||||
|
|
||||||
|
# Track all of the tags we want to assign later on
|
||||||
|
group_tags = {}
|
||||||
|
|
||||||
|
# Track our entries to preload
|
||||||
|
preloaded = []
|
||||||
|
|
||||||
# Prepare our Asset Object
|
# Prepare our Asset Object
|
||||||
asset = asset if isinstance(asset, AppriseAsset) else AppriseAsset()
|
asset = asset if isinstance(asset, AppriseAsset) else AppriseAsset()
|
||||||
|
|
||||||
|
@ -548,7 +624,7 @@ class ConfigBase(URLBase):
|
||||||
valid_line_re = re.compile(
|
valid_line_re = re.compile(
|
||||||
r'^\s*(?P<line>([;#]+(?P<comment>.*))|'
|
r'^\s*(?P<line>([;#]+(?P<comment>.*))|'
|
||||||
r'(\s*(?P<tags>[a-z0-9, \t_-]+)\s*=|=)?\s*'
|
r'(\s*(?P<tags>[a-z0-9, \t_-]+)\s*=|=)?\s*'
|
||||||
r'(?P<url>[a-z0-9]{2,9}://.*)|'
|
r'((?P<url>[a-z0-9]{1,12}://.*)|(?P<assign>[a-z0-9, \t_-]+))|'
|
||||||
r'include\s+(?P<config>.+))?\s*$', re.I)
|
r'include\s+(?P<config>.+))?\s*$', re.I)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -574,8 +650,13 @@ class ConfigBase(URLBase):
|
||||||
# otherwise.
|
# otherwise.
|
||||||
return (list(), list())
|
return (list(), list())
|
||||||
|
|
||||||
url, config = result.group('url'), result.group('config')
|
# Retrieve our line
|
||||||
if not (url or config):
|
url, assign, config = \
|
||||||
|
result.group('url'), \
|
||||||
|
result.group('assign'), \
|
||||||
|
result.group('config')
|
||||||
|
|
||||||
|
if not (url or config or assign):
|
||||||
# Comment/empty line; do nothing
|
# Comment/empty line; do nothing
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -595,6 +676,33 @@ class ConfigBase(URLBase):
|
||||||
loggable_url = url if not asset.secure_logging \
|
loggable_url = url if not asset.secure_logging \
|
||||||
else cwe312_url(url)
|
else cwe312_url(url)
|
||||||
|
|
||||||
|
if assign:
|
||||||
|
groups = set(parse_list(result.group('tags'), cast=str))
|
||||||
|
if not groups:
|
||||||
|
# no tags were assigned
|
||||||
|
ConfigBase.logger.warning(
|
||||||
|
'Unparseable tag assignment - no group(s) '
|
||||||
|
'on line {}'.format(line))
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get our tags
|
||||||
|
tags = set(parse_list(assign, cast=str))
|
||||||
|
if not tags:
|
||||||
|
# no tags were assigned
|
||||||
|
ConfigBase.logger.warning(
|
||||||
|
'Unparseable tag assignment - no tag(s) to assign '
|
||||||
|
'on line {}'.format(line))
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Update our tag group map
|
||||||
|
for tag_group in groups:
|
||||||
|
if tag_group not in group_tags:
|
||||||
|
group_tags[tag_group] = set()
|
||||||
|
|
||||||
|
# ensure our tag group is never included in the assignment
|
||||||
|
group_tags[tag_group] |= tags - set([tag_group])
|
||||||
|
continue
|
||||||
|
|
||||||
# Acquire our url tokens
|
# Acquire our url tokens
|
||||||
results = plugins.url_to_dict(
|
results = plugins.url_to_dict(
|
||||||
url, secure_logging=asset.secure_logging)
|
url, secure_logging=asset.secure_logging)
|
||||||
|
@ -607,25 +715,57 @@ class ConfigBase(URLBase):
|
||||||
|
|
||||||
# Build a list of tags to associate with the newly added
|
# Build a list of tags to associate with the newly added
|
||||||
# notifications if any were set
|
# notifications if any were set
|
||||||
results['tag'] = set(parse_list(result.group('tags')))
|
results['tag'] = set(parse_list(result.group('tags'), cast=str))
|
||||||
|
|
||||||
# Set our Asset Object
|
# Set our Asset Object
|
||||||
results['asset'] = asset
|
results['asset'] = asset
|
||||||
|
|
||||||
|
# Store our preloaded entries
|
||||||
|
preloaded.append({
|
||||||
|
'results': results,
|
||||||
|
'line': line,
|
||||||
|
'loggable_url': loggable_url,
|
||||||
|
})
|
||||||
|
|
||||||
|
#
|
||||||
|
# Normalize Tag Groups
|
||||||
|
# - Expand Groups of Groups so that they don't exist
|
||||||
|
#
|
||||||
|
ConfigBase.__normalize_tag_groups(group_tags)
|
||||||
|
|
||||||
|
#
|
||||||
|
# URL Processing
|
||||||
|
#
|
||||||
|
for entry in preloaded:
|
||||||
|
# Point to our results entry for easier reference below
|
||||||
|
results = entry['results']
|
||||||
|
|
||||||
|
#
|
||||||
|
# Apply our tag groups if they're defined
|
||||||
|
#
|
||||||
|
for group, tags in group_tags.items():
|
||||||
|
# Detect if anything assigned to this tag also maps back to a
|
||||||
|
# group. If so we want to add the group to our list
|
||||||
|
if next((True for tag in results['tag']
|
||||||
|
if tag in tags), False):
|
||||||
|
results['tag'].add(group)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Attempt to create an instance of our plugin using the
|
# Attempt to create an instance of our plugin using the
|
||||||
# parsed URL information
|
# parsed URL information
|
||||||
plugin = common.NOTIFY_SCHEMA_MAP[results['schema']](**results)
|
plugin = common.NOTIFY_SCHEMA_MAP[
|
||||||
|
results['schema']](**results)
|
||||||
|
|
||||||
# Create log entry of loaded URL
|
# Create log entry of loaded URL
|
||||||
ConfigBase.logger.debug(
|
ConfigBase.logger.debug(
|
||||||
'Loaded URL: %s', plugin.url(privacy=asset.secure_logging))
|
'Loaded URL: %s', plugin.url(
|
||||||
|
privacy=results['asset'].secure_logging))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# the arguments are invalid or can not be used.
|
# the arguments are invalid or can not be used.
|
||||||
ConfigBase.logger.warning(
|
ConfigBase.logger.warning(
|
||||||
'Could not load URL {} on line {}.'.format(
|
'Could not load URL {} on line {}.'.format(
|
||||||
loggable_url, line))
|
entry['loggable_url'], entry['line']))
|
||||||
ConfigBase.logger.debug('Loading Exception: %s' % str(e))
|
ConfigBase.logger.debug('Loading Exception: %s' % str(e))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -28,6 +24,7 @@
|
||||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
# 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
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
# POSSIBILITY OF SUCH DAMAGE.USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
# POSSIBILITY OF SUCH DAMAGE.
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
from ..plugins.NotifyBase import NotifyBase
|
from ..plugins.NotifyBase import NotifyBase
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -6,16 +6,16 @@
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: apprise 1.3.0\n"
|
"Project-Id-Version: apprise 1.6.0\n"
|
||||||
"Report-Msgid-Bugs-To: lead2gold@gmail.com\n"
|
"Report-Msgid-Bugs-To: lead2gold@gmail.com\n"
|
||||||
"POT-Creation-Date: 2023-02-22 17:31-0500\n"
|
"POT-Creation-Date: 2023-10-15 15:56-0400\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=utf-8\n"
|
"Content-Type: text/plain; charset=utf-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Generated-By: Babel 2.9.1\n"
|
"Generated-By: Babel 2.11.0\n"
|
||||||
|
|
||||||
msgid "A local Gnome environment is required."
|
msgid "A local Gnome environment is required."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -164,6 +164,9 @@ msgstr ""
|
||||||
msgid "Consumer Secret"
|
msgid "Consumer Secret"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Content Placement"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Country"
|
msgid "Country"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -209,6 +212,9 @@ msgstr ""
|
||||||
msgid "Device Name"
|
msgid "Device Name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Discord Event ID"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Display Footer"
|
msgid "Display Footer"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -224,12 +230,6 @@ msgstr ""
|
||||||
msgid "Email Header"
|
msgid "Email Header"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Encrypted Password"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Encrypted Salt"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Entity"
|
msgid "Entity"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -272,6 +272,9 @@ msgstr ""
|
||||||
msgid "From Name"
|
msgid "From Name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "From Phone ID"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "From Phone No"
|
msgid "From Phone No"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -317,6 +320,9 @@ msgstr ""
|
||||||
msgid "Integration ID"
|
msgid "Integration ID"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Integration Key"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Is Ad?"
|
msgid "Is Ad?"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -353,6 +359,9 @@ msgstr ""
|
||||||
msgid "Master Key"
|
msgid "Master Key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Matrix API Verion"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Memory"
|
msgid "Memory"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -433,6 +442,12 @@ msgstr ""
|
||||||
msgid "Payload Extras"
|
msgid "Payload Extras"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Ping Discord Role"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Ping Discord User"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -451,9 +466,15 @@ msgstr ""
|
||||||
msgid "Provider Key"
|
msgid "Provider Key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Pushkey"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "QOS"
|
msgid "QOS"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Query Method"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Region"
|
msgid "Region"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -475,24 +496,27 @@ msgstr ""
|
||||||
msgid "Retry"
|
msgid "Retry"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Rooms"
|
msgid "Room ID"
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Route"
|
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Route Group"
|
msgid "Route Group"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Routing Key"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "SMTP Server"
|
msgid "SMTP Server"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Salt"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Schema"
|
msgid "Schema"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Secret"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Secret API Key"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Secret Access Key"
|
msgid "Secret Access Key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -520,6 +544,9 @@ msgstr ""
|
||||||
msgid "Severity"
|
msgid "Severity"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Short URL"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Show Status"
|
msgid "Show Status"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -559,9 +586,6 @@ msgstr ""
|
||||||
msgid "Subtitle"
|
msgid "Subtitle"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Syslog Mode"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Tags"
|
msgid "Tags"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -658,6 +682,15 @@ msgstr ""
|
||||||
msgid "Template Data"
|
msgid "Template Data"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Template ID"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Template Mapping"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Template Name"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Template Path"
|
msgid "Template Path"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -706,12 +739,18 @@ msgstr ""
|
||||||
msgid "Topic"
|
msgid "Topic"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Topic Thread ID"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Transmitter Groups"
|
msgid "Transmitter Groups"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "URL"
|
msgid "URL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "URL Prefix"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "URL Title"
|
msgid "URL Title"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -796,3 +835,6 @@ msgstr ""
|
||||||
msgid "ttl"
|
msgid "ttl"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "validity"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
#
|
#
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: apprise 0.7.6\n"
|
|
||||||
|
"Project-Id-Version: apprise 1.4.5\n"
|
||||||
"Report-Msgid-Bugs-To: lead2gold@gmail.com\n"
|
"Report-Msgid-Bugs-To: lead2gold@gmail.com\n"
|
||||||
"POT-Creation-Date: 2019-05-28 16:56-0400\n"
|
"POT-Creation-Date: 2019-05-28 16:56-0400\n"
|
||||||
"PO-Revision-Date: 2019-05-24 20:00-0400\n"
|
"PO-Revision-Date: 2019-05-24 20:00-0400\n"
|
||||||
|
@ -19,275 +20,271 @@ msgstr ""
|
||||||
"Generated-By: Babel 2.6.0\n"
|
"Generated-By: Babel 2.6.0\n"
|
||||||
|
|
||||||
msgid "API Key"
|
msgid "API Key"
|
||||||
msgstr ""
|
msgstr "API Key"
|
||||||
|
|
||||||
msgid "Access Key"
|
msgid "Access Key"
|
||||||
msgstr ""
|
msgstr "Access Key"
|
||||||
|
|
||||||
msgid "Access Key ID"
|
msgid "Access Key ID"
|
||||||
msgstr ""
|
msgstr "Access Key ID"
|
||||||
|
|
||||||
msgid "Access Secret"
|
msgid "Access Secret"
|
||||||
msgstr ""
|
msgstr "Access Secret"
|
||||||
|
|
||||||
msgid "Access Token"
|
msgid "Access Token"
|
||||||
msgstr ""
|
msgstr "Access Token"
|
||||||
|
|
||||||
msgid "Account SID"
|
msgid "Account SID"
|
||||||
msgstr ""
|
msgstr "Account SID"
|
||||||
|
|
||||||
msgid "Add Tokens"
|
msgid "Add Tokens"
|
||||||
msgstr ""
|
msgstr "Add Tokens"
|
||||||
|
|
||||||
msgid "Application Key"
|
msgid "Application Key"
|
||||||
msgstr ""
|
msgstr "Application Key"
|
||||||
|
|
||||||
msgid "Application Secret"
|
msgid "Application Secret"
|
||||||
msgstr ""
|
msgstr "Application Secret"
|
||||||
|
|
||||||
msgid "Auth Token"
|
msgid "Auth Token"
|
||||||
msgstr ""
|
msgstr "Auth Token"
|
||||||
|
|
||||||
msgid "Authorization Token"
|
msgid "Authorization Token"
|
||||||
msgstr ""
|
msgstr "Authorization Token"
|
||||||
|
|
||||||
msgid "Avatar Image"
|
msgid "Avatar Image"
|
||||||
msgstr ""
|
msgstr "Avatar Image"
|
||||||
|
|
||||||
msgid "Bot Name"
|
msgid "Bot Name"
|
||||||
msgstr ""
|
msgstr "Bot Name"
|
||||||
|
|
||||||
msgid "Bot Token"
|
msgid "Bot Token"
|
||||||
msgstr ""
|
msgstr "Bot Token"
|
||||||
|
|
||||||
msgid "Channels"
|
msgid "Channels"
|
||||||
msgstr ""
|
msgstr "Channels"
|
||||||
|
|
||||||
msgid "Consumer Key"
|
msgid "Consumer Key"
|
||||||
msgstr ""
|
msgstr "Consumer Key"
|
||||||
|
|
||||||
msgid "Consumer Secret"
|
msgid "Consumer Secret"
|
||||||
msgstr ""
|
msgstr "Consumer Secret"
|
||||||
|
|
||||||
msgid "Detect Bot Owner"
|
msgid "Detect Bot Owner"
|
||||||
msgstr ""
|
msgstr "Detect Bot Owner"
|
||||||
|
|
||||||
msgid "Device ID"
|
msgid "Device ID"
|
||||||
msgstr ""
|
msgstr "Device ID"
|
||||||
|
|
||||||
msgid "Display Footer"
|
msgid "Display Footer"
|
||||||
msgstr ""
|
msgstr "Display Footer"
|
||||||
|
|
||||||
msgid "Domain"
|
msgid "Domain"
|
||||||
msgstr ""
|
msgstr "Domain"
|
||||||
|
|
||||||
msgid "Duration"
|
msgid "Duration"
|
||||||
msgstr ""
|
msgstr "Duration"
|
||||||
|
|
||||||
msgid "Events"
|
msgid "Events"
|
||||||
msgstr ""
|
msgstr "Events"
|
||||||
|
|
||||||
msgid "Footer Logo"
|
msgid "Footer Logo"
|
||||||
msgstr ""
|
msgstr "Footer Logo"
|
||||||
|
|
||||||
msgid "From Email"
|
msgid "From Email"
|
||||||
msgstr ""
|
msgstr "From Email"
|
||||||
|
|
||||||
msgid "From Name"
|
msgid "From Name"
|
||||||
msgstr ""
|
msgstr "From Name"
|
||||||
|
|
||||||
msgid "From Phone No"
|
msgid "From Phone No"
|
||||||
msgstr ""
|
msgstr "From Phone No"
|
||||||
|
|
||||||
msgid "Group"
|
msgid "Group"
|
||||||
msgstr ""
|
msgstr "Group"
|
||||||
|
|
||||||
msgid "HTTP Header"
|
msgid "HTTP Header"
|
||||||
msgstr ""
|
msgstr "HTTP Header"
|
||||||
|
|
||||||
msgid "Hostname"
|
msgid "Hostname"
|
||||||
msgstr ""
|
msgstr "Hostname"
|
||||||
|
|
||||||
msgid "Include Image"
|
msgid "Include Image"
|
||||||
msgstr ""
|
msgstr "Include Image"
|
||||||
|
|
||||||
msgid "Modal"
|
msgid "Modal"
|
||||||
msgstr ""
|
msgstr "Modal"
|
||||||
|
|
||||||
msgid "Notify Format"
|
msgid "Notify Format"
|
||||||
msgstr ""
|
msgstr "Notify Format"
|
||||||
|
|
||||||
msgid "Organization"
|
msgid "Organization"
|
||||||
msgstr ""
|
msgstr "Organization"
|
||||||
|
|
||||||
msgid "Overflow Mode"
|
msgid "Overflow Mode"
|
||||||
msgstr ""
|
msgstr "Overflow Mode"
|
||||||
|
|
||||||
msgid "Password"
|
msgid "Password"
|
||||||
msgstr ""
|
msgstr "Password"
|
||||||
|
|
||||||
msgid "Port"
|
msgid "Port"
|
||||||
msgstr ""
|
msgstr "Port"
|
||||||
|
|
||||||
msgid "Priority"
|
msgid "Priority"
|
||||||
msgstr ""
|
msgstr "Priority"
|
||||||
|
|
||||||
msgid "Provider Key"
|
msgid "Provider Key"
|
||||||
msgstr ""
|
msgstr "Provider Key"
|
||||||
|
|
||||||
msgid "Region"
|
msgid "Region"
|
||||||
msgstr ""
|
msgstr "Region"
|
||||||
|
|
||||||
msgid "Region Name"
|
msgid "Region Name"
|
||||||
msgstr ""
|
msgstr "Region Name"
|
||||||
|
|
||||||
msgid "Remove Tokens"
|
msgid "Remove Tokens"
|
||||||
msgstr ""
|
msgstr "Remove Tokens"
|
||||||
|
|
||||||
msgid "Rooms"
|
msgid "Rooms"
|
||||||
msgstr ""
|
msgstr "Rooms"
|
||||||
|
|
||||||
msgid "SMTP Server"
|
msgid "SMTP Server"
|
||||||
msgstr ""
|
msgstr "SMTP Server"
|
||||||
|
|
||||||
msgid "Schema"
|
msgid "Schema"
|
||||||
msgstr ""
|
msgstr "Schema"
|
||||||
|
|
||||||
msgid "Secret Access Key"
|
msgid "Secret Access Key"
|
||||||
msgstr ""
|
msgstr "Secret Access Key"
|
||||||
|
|
||||||
msgid "Secret Key"
|
msgid "Secret Key"
|
||||||
msgstr ""
|
msgstr "Secret Key"
|
||||||
|
|
||||||
msgid "Secure Mode"
|
msgid "Secure Mode"
|
||||||
msgstr ""
|
msgstr "Secure Mode"
|
||||||
|
|
||||||
msgid "Server Timeout"
|
msgid "Server Timeout"
|
||||||
msgstr ""
|
msgstr "Server Timeout"
|
||||||
|
|
||||||
msgid "Sound"
|
msgid "Sound"
|
||||||
msgstr ""
|
msgstr "Sound"
|
||||||
|
|
||||||
msgid "Source JID"
|
msgid "Source JID"
|
||||||
msgstr ""
|
msgstr "Source JID"
|
||||||
|
|
||||||
msgid "Target Channel"
|
msgid "Target Channel"
|
||||||
msgstr ""
|
msgstr "Target Channel"
|
||||||
|
|
||||||
msgid "Target Chat ID"
|
msgid "Target Chat ID"
|
||||||
msgstr ""
|
msgstr "Target Chat ID"
|
||||||
|
|
||||||
msgid "Target Device"
|
msgid "Target Device"
|
||||||
msgstr ""
|
msgstr "Target Device"
|
||||||
|
|
||||||
msgid "Target Device ID"
|
msgid "Target Device ID"
|
||||||
msgstr ""
|
msgstr "Target Device ID"
|
||||||
|
|
||||||
msgid "Target Email"
|
msgid "Target Email"
|
||||||
msgstr ""
|
msgstr "Target Email"
|
||||||
|
|
||||||
msgid "Target Emails"
|
msgid "Target Emails"
|
||||||
msgstr ""
|
msgstr "Target Emails"
|
||||||
|
|
||||||
msgid "Target Encoded ID"
|
msgid "Target Encoded ID"
|
||||||
msgstr ""
|
msgstr "Target Encoded ID"
|
||||||
|
|
||||||
msgid "Target JID"
|
msgid "Target JID"
|
||||||
msgstr ""
|
msgstr "Target JID"
|
||||||
|
|
||||||
msgid "Target Phone No"
|
msgid "Target Phone No"
|
||||||
msgstr ""
|
msgstr "Target Phone No"
|
||||||
|
|
||||||
msgid "Target Room Alias"
|
msgid "Target Room Alias"
|
||||||
msgstr ""
|
msgstr "Target Room Alias"
|
||||||
|
|
||||||
msgid "Target Room ID"
|
msgid "Target Room ID"
|
||||||
msgstr ""
|
msgstr "Target Room ID"
|
||||||
|
|
||||||
msgid "Target Short Code"
|
msgid "Target Short Code"
|
||||||
msgstr ""
|
msgstr "Target Short Code"
|
||||||
|
|
||||||
msgid "Target Tag ID"
|
msgid "Target Tag ID"
|
||||||
msgstr ""
|
msgstr "Target Tag ID"
|
||||||
|
|
||||||
msgid "Target Topic"
|
msgid "Target Topic"
|
||||||
msgstr ""
|
msgstr "Target Topic"
|
||||||
|
|
||||||
msgid "Target User"
|
msgid "Target User"
|
||||||
msgstr ""
|
msgstr "Target User"
|
||||||
|
|
||||||
msgid "Targets"
|
msgid "Targets"
|
||||||
msgstr ""
|
msgstr "Targets"
|
||||||
|
|
||||||
msgid "Text To Speech"
|
msgid "Text To Speech"
|
||||||
msgstr ""
|
msgstr "Text To Speech"
|
||||||
|
|
||||||
msgid "To Channel ID"
|
msgid "To Channel ID"
|
||||||
msgstr ""
|
msgstr "To Channel ID"
|
||||||
|
|
||||||
msgid "To Email"
|
msgid "To Email"
|
||||||
msgstr ""
|
msgstr "To Email"
|
||||||
|
|
||||||
msgid "To User ID"
|
msgid "To User ID"
|
||||||
msgstr ""
|
msgstr "To User ID"
|
||||||
|
|
||||||
msgid "Token"
|
msgid "Token"
|
||||||
msgstr ""
|
msgstr "Token"
|
||||||
|
|
||||||
msgid "Token A"
|
msgid "Token A"
|
||||||
msgstr ""
|
msgstr "Token A"
|
||||||
|
|
||||||
msgid "Token B"
|
msgid "Token B"
|
||||||
msgstr ""
|
msgstr "Token B"
|
||||||
|
|
||||||
msgid "Token C"
|
msgid "Token C"
|
||||||
msgstr ""
|
msgstr "Token C"
|
||||||
|
|
||||||
msgid "Urgency"
|
msgid "Urgency"
|
||||||
msgstr ""
|
msgstr "Urgency"
|
||||||
|
|
||||||
msgid "Use Avatar"
|
msgid "Use Avatar"
|
||||||
msgstr ""
|
msgstr "Use Avatar"
|
||||||
|
|
||||||
msgid "User"
|
msgid "User"
|
||||||
msgstr ""
|
msgstr "User"
|
||||||
|
|
||||||
msgid "User Key"
|
msgid "User Key"
|
||||||
msgstr ""
|
msgstr "User Key"
|
||||||
|
|
||||||
msgid "User Name"
|
msgid "User Name"
|
||||||
msgstr ""
|
msgstr "User Name"
|
||||||
|
|
||||||
msgid "Username"
|
msgid "Username"
|
||||||
msgstr ""
|
msgstr "Username"
|
||||||
|
|
||||||
msgid "Verify SSL"
|
msgid "Verify SSL"
|
||||||
msgstr ""
|
msgstr "Verify SSL"
|
||||||
|
|
||||||
msgid "Version"
|
msgid "Version"
|
||||||
msgstr ""
|
msgstr "Version"
|
||||||
|
|
||||||
msgid "Webhook"
|
msgid "Webhook"
|
||||||
msgstr ""
|
msgstr "Webhook"
|
||||||
|
|
||||||
msgid "Webhook ID"
|
msgid "Webhook ID"
|
||||||
msgstr ""
|
msgstr "Webhook ID"
|
||||||
|
|
||||||
msgid "Webhook Mode"
|
msgid "Webhook Mode"
|
||||||
msgstr ""
|
msgstr "Webhook Mode"
|
||||||
|
|
||||||
msgid "Webhook Token"
|
msgid "Webhook Token"
|
||||||
msgstr ""
|
msgstr "Webhook Token"
|
||||||
|
|
||||||
msgid "X-Axis"
|
msgid "X-Axis"
|
||||||
msgstr ""
|
msgstr "X-Axis"
|
||||||
|
|
||||||
msgid "XEP"
|
msgid "XEP"
|
||||||
msgstr ""
|
msgstr "XEP"
|
||||||
|
|
||||||
msgid "Y-Axis"
|
msgid "Y-Axis"
|
||||||
msgstr ""
|
msgstr "Y-Axis"
|
||||||
|
|
||||||
#~ msgid "Access Key Secret"
|
|
||||||
#~ msgstr ""
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -33,6 +29,7 @@
|
||||||
import re
|
import re
|
||||||
import requests
|
import requests
|
||||||
from json import dumps
|
from json import dumps
|
||||||
|
import base64
|
||||||
|
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from ..URLBase import PrivacyMode
|
from ..URLBase import PrivacyMode
|
||||||
|
@ -42,6 +39,20 @@ from ..utils import validate_regex
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class AppriseAPIMethod:
|
||||||
|
"""
|
||||||
|
Defines the method to post data tot he remote server
|
||||||
|
"""
|
||||||
|
JSON = 'json'
|
||||||
|
FORM = 'form'
|
||||||
|
|
||||||
|
|
||||||
|
APPRISE_API_METHODS = (
|
||||||
|
AppriseAPIMethod.FORM,
|
||||||
|
AppriseAPIMethod.JSON,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class NotifyAppriseAPI(NotifyBase):
|
class NotifyAppriseAPI(NotifyBase):
|
||||||
"""
|
"""
|
||||||
A wrapper for Apprise (Persistent) API Notifications
|
A wrapper for Apprise (Persistent) API Notifications
|
||||||
|
@ -62,9 +73,12 @@ class NotifyAppriseAPI(NotifyBase):
|
||||||
# A URL that takes you to the setup/help of the specific protocol
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_apprise_api'
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_apprise_api'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# Depending on the number of transactions/notifications taking place, this
|
# Depending on the number of transactions/notifications taking place, this
|
||||||
# could take a while. 30 seconds should be enough to perform the task
|
# could take a while. 30 seconds should be enough to perform the task
|
||||||
socket_connect_timeout = 30.0
|
socket_read_timeout = 30.0
|
||||||
|
|
||||||
# Disable throttle rate for Apprise API requests since they are normally
|
# Disable throttle rate for Apprise API requests since they are normally
|
||||||
# local anyway
|
# local anyway
|
||||||
|
@ -119,6 +133,12 @@ class NotifyAppriseAPI(NotifyBase):
|
||||||
'name': _('Tags'),
|
'name': _('Tags'),
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
},
|
},
|
||||||
|
'method': {
|
||||||
|
'name': _('Query Method'),
|
||||||
|
'type': 'choice:string',
|
||||||
|
'values': APPRISE_API_METHODS,
|
||||||
|
'default': APPRISE_API_METHODS[0],
|
||||||
|
},
|
||||||
'to': {
|
'to': {
|
||||||
'alias_of': 'token',
|
'alias_of': 'token',
|
||||||
},
|
},
|
||||||
|
@ -132,7 +152,8 @@ class NotifyAppriseAPI(NotifyBase):
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, token=None, tags=None, headers=None, **kwargs):
|
def __init__(self, token=None, tags=None, method=None, headers=None,
|
||||||
|
**kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Apprise API Object
|
Initialize Apprise API Object
|
||||||
|
|
||||||
|
@ -142,10 +163,6 @@ class NotifyAppriseAPI(NotifyBase):
|
||||||
"""
|
"""
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
self.fullpath = kwargs.get('fullpath')
|
|
||||||
if not isinstance(self.fullpath, str):
|
|
||||||
self.fullpath = '/'
|
|
||||||
|
|
||||||
self.token = validate_regex(
|
self.token = validate_regex(
|
||||||
token, *self.template_tokens['token']['regex'])
|
token, *self.template_tokens['token']['regex'])
|
||||||
if not self.token:
|
if not self.token:
|
||||||
|
@ -154,6 +171,14 @@ class NotifyAppriseAPI(NotifyBase):
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
self.method = self.template_args['method']['default'] \
|
||||||
|
if not isinstance(method, str) else method.lower()
|
||||||
|
|
||||||
|
if self.method not in APPRISE_API_METHODS:
|
||||||
|
msg = 'The method specified ({}) is invalid.'.format(method)
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
# Build list of tags
|
# Build list of tags
|
||||||
self.__tags = parse_list(tags)
|
self.__tags = parse_list(tags)
|
||||||
|
|
||||||
|
@ -169,8 +194,13 @@ class NotifyAppriseAPI(NotifyBase):
|
||||||
Returns the URL built dynamically based on specified arguments.
|
Returns the URL built dynamically based on specified arguments.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Our URL parameters
|
# Define any URL parameters
|
||||||
params = self.url_parameters(privacy=privacy, *args, **kwargs)
|
params = {
|
||||||
|
'method': self.method,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extend our parameters
|
||||||
|
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
|
||||||
|
|
||||||
# Append our headers into our parameters
|
# Append our headers into our parameters
|
||||||
params.update({'+{}'.format(k): v for k, v in self.headers.items()})
|
params.update({'+{}'.format(k): v for k, v in self.headers.items()})
|
||||||
|
@ -209,15 +239,61 @@ class NotifyAppriseAPI(NotifyBase):
|
||||||
token=self.pprint(self.token, privacy, safe=''),
|
token=self.pprint(self.token, privacy, safe=''),
|
||||||
params=NotifyAppriseAPI.urlencode(params))
|
params=NotifyAppriseAPI.urlencode(params))
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,
|
||||||
|
**kwargs):
|
||||||
"""
|
"""
|
||||||
Perform Apprise API Notification
|
Perform Apprise API Notification
|
||||||
"""
|
"""
|
||||||
|
|
||||||
headers = {}
|
# Prepare HTTP Headers
|
||||||
|
headers = {
|
||||||
|
'User-Agent': self.app_id,
|
||||||
|
}
|
||||||
|
|
||||||
# Apply any/all header over-rides defined
|
# Apply any/all header over-rides defined
|
||||||
headers.update(self.headers)
|
headers.update(self.headers)
|
||||||
|
|
||||||
|
attachments = []
|
||||||
|
files = []
|
||||||
|
if attach and self.attachment_support:
|
||||||
|
for no, attachment in enumerate(attach, start=1):
|
||||||
|
# Perform some simple error checking
|
||||||
|
if not attachment:
|
||||||
|
# We could not access the attachment
|
||||||
|
self.logger.error(
|
||||||
|
'Could not access attachment {}.'.format(
|
||||||
|
attachment.url(privacy=True)))
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
if self.method == AppriseAPIMethod.JSON:
|
||||||
|
with open(attachment.path, 'rb') as f:
|
||||||
|
# Output must be in a DataURL format (that's what
|
||||||
|
# PushSafer calls it):
|
||||||
|
attachments.append({
|
||||||
|
'filename': attachment.name,
|
||||||
|
'base64': base64.b64encode(f.read())
|
||||||
|
.decode('utf-8'),
|
||||||
|
'mimetype': attachment.mimetype,
|
||||||
|
})
|
||||||
|
|
||||||
|
else: # AppriseAPIMethod.FORM
|
||||||
|
files.append((
|
||||||
|
'file{:02d}'.format(no),
|
||||||
|
(
|
||||||
|
attachment.name,
|
||||||
|
open(attachment.path, 'rb'),
|
||||||
|
attachment.mimetype,
|
||||||
|
)
|
||||||
|
))
|
||||||
|
|
||||||
|
except (OSError, IOError) as e:
|
||||||
|
self.logger.warning(
|
||||||
|
'An I/O error occurred while reading {}.'.format(
|
||||||
|
attachment.name if attachment else 'attachment'))
|
||||||
|
self.logger.debug('I/O Exception: %s' % str(e))
|
||||||
|
return False
|
||||||
|
|
||||||
# prepare Apprise API Object
|
# prepare Apprise API Object
|
||||||
payload = {
|
payload = {
|
||||||
# Apprise API Payload
|
# Apprise API Payload
|
||||||
|
@ -227,6 +303,14 @@ class NotifyAppriseAPI(NotifyBase):
|
||||||
'format': self.notify_format,
|
'format': self.notify_format,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.method == AppriseAPIMethod.JSON:
|
||||||
|
headers['Content-Type'] = 'application/json'
|
||||||
|
|
||||||
|
if attachments:
|
||||||
|
payload['attachments'] = attachments
|
||||||
|
|
||||||
|
payload = dumps(payload)
|
||||||
|
|
||||||
if self.__tags:
|
if self.__tags:
|
||||||
payload['tag'] = self.__tags
|
payload['tag'] = self.__tags
|
||||||
|
|
||||||
|
@ -242,13 +326,13 @@ class NotifyAppriseAPI(NotifyBase):
|
||||||
url += ':%d' % self.port
|
url += ':%d' % self.port
|
||||||
|
|
||||||
fullpath = self.fullpath.strip('/')
|
fullpath = self.fullpath.strip('/')
|
||||||
url += '/{}/'.format(fullpath) if fullpath else '/'
|
url += '{}'.format('/' + fullpath) if fullpath else ''
|
||||||
url += 'notify/{}'.format(self.token)
|
url += '/notify/{}'.format(self.token)
|
||||||
|
|
||||||
# Some entries can not be over-ridden
|
# Some entries can not be over-ridden
|
||||||
headers.update({
|
headers.update({
|
||||||
'User-Agent': self.app_id,
|
# Our response to be in JSON format always
|
||||||
'Content-Type': 'application/json',
|
'Accept': 'application/json',
|
||||||
# Pass our Source UUID4 Identifier
|
# Pass our Source UUID4 Identifier
|
||||||
'X-Apprise-ID': self.asset._uid,
|
'X-Apprise-ID': self.asset._uid,
|
||||||
# Pass our current recursion count to our upstream server
|
# Pass our current recursion count to our upstream server
|
||||||
|
@ -266,9 +350,10 @@ class NotifyAppriseAPI(NotifyBase):
|
||||||
try:
|
try:
|
||||||
r = requests.post(
|
r = requests.post(
|
||||||
url,
|
url,
|
||||||
data=dumps(payload),
|
data=payload,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
auth=auth,
|
auth=auth,
|
||||||
|
files=files if files else None,
|
||||||
verify=self.verify_certificate,
|
verify=self.verify_certificate,
|
||||||
timeout=self.request_timeout,
|
timeout=self.request_timeout,
|
||||||
)
|
)
|
||||||
|
@ -290,7 +375,8 @@ class NotifyAppriseAPI(NotifyBase):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.logger.info('Sent Apprise API notification.')
|
self.logger.info(
|
||||||
|
'Sent Apprise API notification; method=%s.', self.method)
|
||||||
|
|
||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
|
@ -301,6 +387,18 @@ class NotifyAppriseAPI(NotifyBase):
|
||||||
# Return; we're done
|
# Return; we're done
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
except (OSError, IOError) as e:
|
||||||
|
self.logger.warning(
|
||||||
|
'An I/O error occurred while reading one of the '
|
||||||
|
'attached files.')
|
||||||
|
self.logger.debug('I/O Exception: %s' % str(e))
|
||||||
|
return False
|
||||||
|
|
||||||
|
finally:
|
||||||
|
for file in files:
|
||||||
|
# Ensure all files are closed
|
||||||
|
file[1][1].close()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -377,4 +475,9 @@ class NotifyAppriseAPI(NotifyBase):
|
||||||
# re-assemble our full path
|
# re-assemble our full path
|
||||||
results['fullpath'] = '/'.join(entries)
|
results['fullpath'] = '/'.join(entries)
|
||||||
|
|
||||||
|
# Set method if specified
|
||||||
|
if 'method' in results['qsd'] and len(results['qsd']['method']):
|
||||||
|
results['method'] = \
|
||||||
|
NotifyAppriseAPI.unquote(results['qsd']['method'])
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -127,10 +123,10 @@ class NotifyBark(NotifyBase):
|
||||||
|
|
||||||
# Define object templates
|
# Define object templates
|
||||||
templates = (
|
templates = (
|
||||||
|
'{schema}://{host}/{targets}',
|
||||||
'{schema}://{host}:{port}/{targets}',
|
'{schema}://{host}:{port}/{targets}',
|
||||||
'{schema}://{user}:{password}@{host}/{targets}',
|
'{schema}://{user}:{password}@{host}/{targets}',
|
||||||
'{schema}://{user}:{password}@{host}:{port}/{targets}',
|
'{schema}://{user}:{password}@{host}:{port}/{targets}',
|
||||||
'{schema}://{user}:{password}@{host}/{targets}',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Define our template arguments
|
# Define our template arguments
|
||||||
|
@ -163,6 +159,7 @@ class NotifyBark(NotifyBase):
|
||||||
'targets': {
|
'targets': {
|
||||||
'name': _('Targets'),
|
'name': _('Targets'),
|
||||||
'type': 'list:string',
|
'type': 'list:string',
|
||||||
|
'required': True,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -280,7 +277,7 @@ class NotifyBark(NotifyBase):
|
||||||
# error tracking (used for function return)
|
# error tracking (used for function return)
|
||||||
has_error = False
|
has_error = False
|
||||||
|
|
||||||
if not len(self.targets):
|
if not self.targets:
|
||||||
# We have nothing to notify; we're done
|
# We have nothing to notify; we're done
|
||||||
self.logger.warning('There are no Bark devices to notify')
|
self.logger.warning('There are no Bark devices to notify')
|
||||||
return False
|
return False
|
||||||
|
@ -456,6 +453,12 @@ class NotifyBark(NotifyBase):
|
||||||
params=NotifyBark.urlencode(params),
|
params=NotifyBark.urlencode(params),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
return len(self.targets)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -139,6 +135,18 @@ class NotifyBase(URLBase):
|
||||||
# Default Overflow Mode
|
# Default Overflow Mode
|
||||||
overflow_mode = OverflowMode.UPSTREAM
|
overflow_mode = OverflowMode.UPSTREAM
|
||||||
|
|
||||||
|
# Support Attachments; this defaults to being disabled.
|
||||||
|
# Since apprise allows you to send attachments without a body or title
|
||||||
|
# defined, by letting Apprise know the plugin won't support attachments
|
||||||
|
# up front, it can quickly pass over and ignore calls to these end points.
|
||||||
|
|
||||||
|
# You must set this to true if your application can handle attachments.
|
||||||
|
# You must also consider a flow change to your notification if this is set
|
||||||
|
# to True as well as now there will be cases where both the body and title
|
||||||
|
# may not be set. There will never be a case where a body, or attachment
|
||||||
|
# isn't set in the same call to your notify() function.
|
||||||
|
attachment_support = False
|
||||||
|
|
||||||
# Default Title HTML Tagging
|
# Default Title HTML Tagging
|
||||||
# When a title is specified for a notification service that doesn't accept
|
# When a title is specified for a notification service that doesn't accept
|
||||||
# titles, by default apprise tries to give a plesant view and convert the
|
# titles, by default apprise tries to give a plesant view and convert the
|
||||||
|
@ -316,7 +324,7 @@ class NotifyBase(URLBase):
|
||||||
the_cors = (do_send(**kwargs2) for kwargs2 in send_calls)
|
the_cors = (do_send(**kwargs2) for kwargs2 in send_calls)
|
||||||
return all(await asyncio.gather(*the_cors))
|
return all(await asyncio.gather(*the_cors))
|
||||||
|
|
||||||
def _build_send_calls(self, body, title=None,
|
def _build_send_calls(self, body=None, title=None,
|
||||||
notify_type=NotifyType.INFO, overflow=None,
|
notify_type=NotifyType.INFO, overflow=None,
|
||||||
attach=None, body_format=None, **kwargs):
|
attach=None, body_format=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -339,6 +347,28 @@ class NotifyBase(URLBase):
|
||||||
# bad attachments
|
# bad attachments
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
# Handle situations where the body is None
|
||||||
|
body = '' if not body else body
|
||||||
|
|
||||||
|
elif not (body or attach):
|
||||||
|
# If there is not an attachment at the very least, a body must be
|
||||||
|
# present
|
||||||
|
msg = "No message body or attachment was specified."
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
if not body and not self.attachment_support:
|
||||||
|
# If no body was specified, then we know that an attachment
|
||||||
|
# was. This is logic checked earlier in the code.
|
||||||
|
#
|
||||||
|
# Knowing this, if the plugin itself doesn't support sending
|
||||||
|
# attachments, there is nothing further to do here, just move
|
||||||
|
# along.
|
||||||
|
msg = f"{self.service_name} does not support attachments; " \
|
||||||
|
" service skipped"
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
# Handle situations where the title is None
|
# Handle situations where the title is None
|
||||||
title = '' if not title else title
|
title = '' if not title else title
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -46,6 +42,7 @@ except ImportError:
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from ..URLBase import PrivacyMode
|
from ..URLBase import PrivacyMode
|
||||||
from ..utils import parse_bool
|
from ..utils import parse_bool
|
||||||
|
from ..utils import parse_list
|
||||||
from ..utils import validate_regex
|
from ..utils import validate_regex
|
||||||
from ..common import NotifyType
|
from ..common import NotifyType
|
||||||
from ..common import NotifyImageSize
|
from ..common import NotifyImageSize
|
||||||
|
@ -58,7 +55,7 @@ DEFAULT_TAG = '@all'
|
||||||
# list of tagged devices that the notification need to be send to, and a
|
# list of tagged devices that the notification need to be send to, and a
|
||||||
# boolean operator (‘and’ / ‘or’) that defines the criteria to match devices
|
# boolean operator (‘and’ / ‘or’) that defines the criteria to match devices
|
||||||
# against those tags.
|
# against those tags.
|
||||||
IS_TAG = re.compile(r'^[@](?P<name>[A-Z0-9]{1,63})$', re.I)
|
IS_TAG = re.compile(r'^[@]?(?P<name>[A-Z0-9]{1,63})$', re.I)
|
||||||
|
|
||||||
# Device tokens are only referenced when developing.
|
# Device tokens are only referenced when developing.
|
||||||
# It's not likely you'll send a message directly to a device, but if you do;
|
# It's not likely you'll send a message directly to a device, but if you do;
|
||||||
|
@ -150,6 +147,12 @@ class NotifyBoxcar(NotifyBase):
|
||||||
'to': {
|
'to': {
|
||||||
'alias_of': 'targets',
|
'alias_of': 'targets',
|
||||||
},
|
},
|
||||||
|
'access': {
|
||||||
|
'alias_of': 'access_key',
|
||||||
|
},
|
||||||
|
'secret': {
|
||||||
|
'alias_of': 'secret_key',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
def __init__(self, access, secret, targets=None, include_image=True,
|
def __init__(self, access, secret, targets=None, include_image=True,
|
||||||
|
@ -160,7 +163,7 @@ class NotifyBoxcar(NotifyBase):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
# Initialize tag list
|
# Initialize tag list
|
||||||
self.tags = list()
|
self._tags = list()
|
||||||
|
|
||||||
# Initialize device_token list
|
# Initialize device_token list
|
||||||
self.device_tokens = list()
|
self.device_tokens = list()
|
||||||
|
@ -184,25 +187,23 @@ class NotifyBoxcar(NotifyBase):
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
if not targets:
|
if not targets:
|
||||||
self.tags.append(DEFAULT_TAG)
|
self._tags.append(DEFAULT_TAG)
|
||||||
targets = []
|
targets = []
|
||||||
|
|
||||||
elif isinstance(targets, str):
|
|
||||||
targets = [x for x in filter(bool, TAGS_LIST_DELIM.split(
|
|
||||||
targets,
|
|
||||||
))]
|
|
||||||
|
|
||||||
# Validate targets and drop bad ones:
|
# Validate targets and drop bad ones:
|
||||||
for target in targets:
|
for target in parse_list(targets):
|
||||||
if IS_TAG.match(target):
|
result = IS_TAG.match(target)
|
||||||
|
if result:
|
||||||
# store valid tag/alias
|
# store valid tag/alias
|
||||||
self.tags.append(IS_TAG.match(target).group('name'))
|
self._tags.append(result.group('name'))
|
||||||
|
continue
|
||||||
|
|
||||||
elif IS_DEVICETOKEN.match(target):
|
result = IS_DEVICETOKEN.match(target)
|
||||||
|
if result:
|
||||||
# store valid device
|
# store valid device
|
||||||
self.device_tokens.append(target)
|
self.device_tokens.append(target)
|
||||||
|
continue
|
||||||
|
|
||||||
else:
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Dropped invalid tag/alias/device_token '
|
'Dropped invalid tag/alias/device_token '
|
||||||
'({}) specified.'.format(target),
|
'({}) specified.'.format(target),
|
||||||
|
@ -235,11 +236,10 @@ class NotifyBoxcar(NotifyBase):
|
||||||
if title:
|
if title:
|
||||||
payload['aps']['@title'] = title
|
payload['aps']['@title'] = title
|
||||||
|
|
||||||
if body:
|
|
||||||
payload['aps']['alert'] = body
|
payload['aps']['alert'] = body
|
||||||
|
|
||||||
if self.tags:
|
if self._tags:
|
||||||
payload['tags'] = {'or': self.tags}
|
payload['tags'] = {'or': self._tags}
|
||||||
|
|
||||||
if self.device_tokens:
|
if self.device_tokens:
|
||||||
payload['device_tokens'] = self.device_tokens
|
payload['device_tokens'] = self.device_tokens
|
||||||
|
@ -341,10 +341,18 @@ class NotifyBoxcar(NotifyBase):
|
||||||
self.secret, privacy, mode=PrivacyMode.Secret, safe=''),
|
self.secret, privacy, mode=PrivacyMode.Secret, safe=''),
|
||||||
targets='/'.join([
|
targets='/'.join([
|
||||||
NotifyBoxcar.quote(x, safe='') for x in chain(
|
NotifyBoxcar.quote(x, safe='') for x in chain(
|
||||||
self.tags, self.device_tokens) if x != DEFAULT_TAG]),
|
self._tags, self.device_tokens) if x != DEFAULT_TAG]),
|
||||||
params=NotifyBoxcar.urlencode(params),
|
params=NotifyBoxcar.urlencode(params),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
targets = len(self._tags) + len(self.device_tokens)
|
||||||
|
# DEFAULT_TAG is set if no tokens/tags are otherwise set
|
||||||
|
return targets if targets > 0 else 1
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
@ -374,6 +382,16 @@ class NotifyBoxcar(NotifyBase):
|
||||||
results['targets'] += \
|
results['targets'] += \
|
||||||
NotifyBoxcar.parse_list(results['qsd'].get('to'))
|
NotifyBoxcar.parse_list(results['qsd'].get('to'))
|
||||||
|
|
||||||
|
# Access
|
||||||
|
if 'access' in results['qsd'] and results['qsd']['access']:
|
||||||
|
results['access'] = NotifyBoxcar.unquote(
|
||||||
|
results['qsd']['access'].strip())
|
||||||
|
|
||||||
|
# Secret
|
||||||
|
if 'secret' in results['qsd'] and results['qsd']['secret']:
|
||||||
|
results['secret'] = NotifyBoxcar.unquote(
|
||||||
|
results['qsd']['secret'].strip())
|
||||||
|
|
||||||
# Include images with our message
|
# Include images with our message
|
||||||
results['include_image'] = \
|
results['include_image'] = \
|
||||||
parse_bool(results['qsd'].get('image', True))
|
parse_bool(results['qsd'].get('image', True))
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -121,11 +117,13 @@ class NotifyBulkSMS(NotifyBase):
|
||||||
'user': {
|
'user': {
|
||||||
'name': _('User Name'),
|
'name': _('User Name'),
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
|
'required': True,
|
||||||
},
|
},
|
||||||
'password': {
|
'password': {
|
||||||
'name': _('Password'),
|
'name': _('Password'),
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'private': True,
|
'private': True,
|
||||||
|
'required': True,
|
||||||
},
|
},
|
||||||
'target_phone': {
|
'target_phone': {
|
||||||
'name': _('Target Phone No'),
|
'name': _('Target Phone No'),
|
||||||
|
@ -144,6 +142,7 @@ class NotifyBulkSMS(NotifyBase):
|
||||||
'targets': {
|
'targets': {
|
||||||
'name': _('Targets'),
|
'name': _('Targets'),
|
||||||
'type': 'list:string',
|
'type': 'list:string',
|
||||||
|
'required': True,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -414,6 +413,24 @@ class NotifyBulkSMS(NotifyBase):
|
||||||
for x in self.groups])),
|
for x in self.groups])),
|
||||||
params=NotifyBulkSMS.urlencode(params))
|
params=NotifyBulkSMS.urlencode(params))
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
|
||||||
|
#
|
||||||
|
# Factor batch into calculation
|
||||||
|
#
|
||||||
|
# Note: Groups always require a separate request (and can not be
|
||||||
|
# included in batch calculations)
|
||||||
|
batch_size = 1 if not self.batch else self.default_batch_size
|
||||||
|
targets = len(self.targets)
|
||||||
|
if batch_size > 1:
|
||||||
|
targets = int(targets / batch_size) + \
|
||||||
|
(1 if targets % batch_size else 0)
|
||||||
|
|
||||||
|
return targets + len(self.groups)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
|
460
lib/apprise/plugins/NotifyBurstSMS.py
Normal file
460
lib/apprise/plugins/NotifyBurstSMS.py
Normal file
|
@ -0,0 +1,460 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 2-Clause License
|
||||||
|
#
|
||||||
|
# Apprise - Push Notification Library.
|
||||||
|
# Copyright (c) 2023, 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.
|
||||||
|
|
||||||
|
# Sign-up with https://burstsms.com/
|
||||||
|
#
|
||||||
|
# Define your API Secret here and acquire your API Key
|
||||||
|
# - https://can.transmitsms.com/profile
|
||||||
|
#
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from .NotifyBase import NotifyBase
|
||||||
|
from ..URLBase import PrivacyMode
|
||||||
|
from ..common import NotifyType
|
||||||
|
from ..utils import is_phone_no
|
||||||
|
from ..utils import parse_phone_no
|
||||||
|
from ..utils import parse_bool
|
||||||
|
from ..utils import validate_regex
|
||||||
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class BurstSMSCountryCode:
|
||||||
|
# Australia
|
||||||
|
AU = 'au'
|
||||||
|
# New Zeland
|
||||||
|
NZ = 'nz'
|
||||||
|
# United Kingdom
|
||||||
|
UK = 'gb'
|
||||||
|
# United States
|
||||||
|
US = 'us'
|
||||||
|
|
||||||
|
|
||||||
|
BURST_SMS_COUNTRY_CODES = (
|
||||||
|
BurstSMSCountryCode.AU,
|
||||||
|
BurstSMSCountryCode.NZ,
|
||||||
|
BurstSMSCountryCode.UK,
|
||||||
|
BurstSMSCountryCode.US,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class NotifyBurstSMS(NotifyBase):
|
||||||
|
"""
|
||||||
|
A wrapper for Burst SMS Notifications
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The default descriptive name associated with the Notification
|
||||||
|
service_name = 'Burst SMS'
|
||||||
|
|
||||||
|
# The services URL
|
||||||
|
service_url = 'https://burstsms.com/'
|
||||||
|
|
||||||
|
# The default protocol
|
||||||
|
secure_protocol = 'burstsms'
|
||||||
|
|
||||||
|
# The maximum amount of SMS Messages that can reside within a single
|
||||||
|
# batch transfer based on:
|
||||||
|
# https://developer.transmitsms.com/#74911cf8-dec6-4319-a499-7f535a7fd08c
|
||||||
|
default_batch_size = 500
|
||||||
|
|
||||||
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_burst_sms'
|
||||||
|
|
||||||
|
# Burst SMS uses the http protocol with JSON requests
|
||||||
|
notify_url = 'https://api.transmitsms.com/send-sms.json'
|
||||||
|
|
||||||
|
# The maximum length of the body
|
||||||
|
body_maxlen = 160
|
||||||
|
|
||||||
|
# A title can not be used for SMS Messages. Setting this to zero will
|
||||||
|
# cause any title (if defined) to get placed into the message body.
|
||||||
|
title_maxlen = 0
|
||||||
|
|
||||||
|
# Define object templates
|
||||||
|
templates = (
|
||||||
|
'{schema}://{apikey}:{secret}@{sender_id}/{targets}',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Define our template tokens
|
||||||
|
template_tokens = dict(NotifyBase.template_tokens, **{
|
||||||
|
'apikey': {
|
||||||
|
'name': _('API Key'),
|
||||||
|
'type': 'string',
|
||||||
|
'required': True,
|
||||||
|
'regex': (r'^[a-z0-9]+$', 'i'),
|
||||||
|
'private': True,
|
||||||
|
},
|
||||||
|
'secret': {
|
||||||
|
'name': _('API Secret'),
|
||||||
|
'type': 'string',
|
||||||
|
'private': True,
|
||||||
|
'required': True,
|
||||||
|
'regex': (r'^[a-z0-9]+$', 'i'),
|
||||||
|
},
|
||||||
|
'sender_id': {
|
||||||
|
'name': _('Sender ID'),
|
||||||
|
'type': 'string',
|
||||||
|
'required': True,
|
||||||
|
'map_to': 'source',
|
||||||
|
},
|
||||||
|
'target_phone': {
|
||||||
|
'name': _('Target Phone No'),
|
||||||
|
'type': 'string',
|
||||||
|
'prefix': '+',
|
||||||
|
'regex': (r'^[0-9\s)(+-]+$', 'i'),
|
||||||
|
'map_to': 'targets',
|
||||||
|
},
|
||||||
|
'targets': {
|
||||||
|
'name': _('Targets'),
|
||||||
|
'type': 'list:string',
|
||||||
|
'required': True,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
# Define our template arguments
|
||||||
|
template_args = dict(NotifyBase.template_args, **{
|
||||||
|
'to': {
|
||||||
|
'alias_of': 'targets',
|
||||||
|
},
|
||||||
|
'from': {
|
||||||
|
'alias_of': 'sender_id',
|
||||||
|
},
|
||||||
|
'key': {
|
||||||
|
'alias_of': 'apikey',
|
||||||
|
},
|
||||||
|
'secret': {
|
||||||
|
'alias_of': 'secret',
|
||||||
|
},
|
||||||
|
'country': {
|
||||||
|
'name': _('Country'),
|
||||||
|
'type': 'choice:string',
|
||||||
|
'values': BURST_SMS_COUNTRY_CODES,
|
||||||
|
'default': BurstSMSCountryCode.US,
|
||||||
|
},
|
||||||
|
# Validity
|
||||||
|
# Expire a message send if it is undeliverable (defined in minutes)
|
||||||
|
# If set to Zero (0); this is the default and sets the max validity
|
||||||
|
# period
|
||||||
|
'validity': {
|
||||||
|
'name': _('validity'),
|
||||||
|
'type': 'int',
|
||||||
|
'default': 0
|
||||||
|
},
|
||||||
|
'batch': {
|
||||||
|
'name': _('Batch Mode'),
|
||||||
|
'type': 'bool',
|
||||||
|
'default': False,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
def __init__(self, apikey, secret, source, targets=None, country=None,
|
||||||
|
validity=None, batch=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Initialize Burst SMS Object
|
||||||
|
"""
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
# API Key (associated with project)
|
||||||
|
self.apikey = validate_regex(
|
||||||
|
apikey, *self.template_tokens['apikey']['regex'])
|
||||||
|
if not self.apikey:
|
||||||
|
msg = 'An invalid Burst SMS API Key ' \
|
||||||
|
'({}) was specified.'.format(apikey)
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
# API Secret (associated with project)
|
||||||
|
self.secret = validate_regex(
|
||||||
|
secret, *self.template_tokens['secret']['regex'])
|
||||||
|
if not self.secret:
|
||||||
|
msg = 'An invalid Burst SMS API Secret ' \
|
||||||
|
'({}) was specified.'.format(secret)
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
if not country:
|
||||||
|
self.country = self.template_args['country']['default']
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.country = country.lower().strip()
|
||||||
|
if country not in BURST_SMS_COUNTRY_CODES:
|
||||||
|
msg = 'An invalid Burst SMS country ' \
|
||||||
|
'({}) was specified.'.format(country)
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
# Set our Validity
|
||||||
|
self.validity = self.template_args['validity']['default']
|
||||||
|
if validity:
|
||||||
|
try:
|
||||||
|
self.validity = int(validity)
|
||||||
|
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
msg = 'The Burst SMS Validity specified ({}) is invalid.'\
|
||||||
|
.format(validity)
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
# Prepare Batch Mode Flag
|
||||||
|
self.batch = self.template_args['batch']['default'] \
|
||||||
|
if batch is None else batch
|
||||||
|
|
||||||
|
# The Sender ID
|
||||||
|
self.source = validate_regex(source)
|
||||||
|
if not self.source:
|
||||||
|
msg = 'The Account Sender ID specified ' \
|
||||||
|
'({}) is invalid.'.format(source)
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
# Parse our targets
|
||||||
|
self.targets = list()
|
||||||
|
|
||||||
|
for target in parse_phone_no(targets):
|
||||||
|
# Validate targets and drop bad ones:
|
||||||
|
result = is_phone_no(target)
|
||||||
|
if not result:
|
||||||
|
self.logger.warning(
|
||||||
|
'Dropped invalid phone # '
|
||||||
|
'({}) specified.'.format(target),
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# store valid phone number
|
||||||
|
self.targets.append(result['full'])
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
|
"""
|
||||||
|
Perform Burst SMS Notification
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not self.targets:
|
||||||
|
self.logger.warning(
|
||||||
|
'There are no valid Burst SMS targets to notify.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
# error tracking (used for function return)
|
||||||
|
has_error = False
|
||||||
|
|
||||||
|
# Prepare our headers
|
||||||
|
headers = {
|
||||||
|
'User-Agent': self.app_id,
|
||||||
|
'Accept': 'application/json',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prepare our authentication
|
||||||
|
auth = (self.apikey, self.secret)
|
||||||
|
|
||||||
|
# Prepare our payload
|
||||||
|
payload = {
|
||||||
|
'countrycode': self.country,
|
||||||
|
'message': body,
|
||||||
|
|
||||||
|
# Sender ID
|
||||||
|
'from': self.source,
|
||||||
|
|
||||||
|
# The to gets populated in the loop below
|
||||||
|
'to': None,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Send in batches if identified to do so
|
||||||
|
batch_size = 1 if not self.batch else self.default_batch_size
|
||||||
|
|
||||||
|
# Create a copy of the targets list
|
||||||
|
targets = list(self.targets)
|
||||||
|
|
||||||
|
for index in range(0, len(targets), batch_size):
|
||||||
|
|
||||||
|
# Prepare our user
|
||||||
|
payload['to'] = ','.join(self.targets[index:index + batch_size])
|
||||||
|
|
||||||
|
# Some Debug Logging
|
||||||
|
self.logger.debug('Burst SMS POST URL: {} (cert_verify={})'.format(
|
||||||
|
self.notify_url, self.verify_certificate))
|
||||||
|
self.logger.debug('Burst SMS Payload: {}' .format(payload))
|
||||||
|
|
||||||
|
# Always call throttle before any remote server i/o is made
|
||||||
|
self.throttle()
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.post(
|
||||||
|
self.notify_url,
|
||||||
|
data=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 = \
|
||||||
|
NotifyBurstSMS.http_response_code_lookup(
|
||||||
|
r.status_code)
|
||||||
|
|
||||||
|
self.logger.warning(
|
||||||
|
'Failed to send Burst SMS notification to {} '
|
||||||
|
'target(s): {}{}error={}.'.format(
|
||||||
|
len(self.targets[index:index + batch_size]),
|
||||||
|
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 Burst SMS notification to %d target(s).' %
|
||||||
|
len(self.targets[index:index + batch_size]))
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
self.logger.warning(
|
||||||
|
'A Connection error occurred sending Burst SMS '
|
||||||
|
'notification to %d target(s).' %
|
||||||
|
len(self.targets[index:index + batch_size]))
|
||||||
|
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 = {
|
||||||
|
'country': self.country,
|
||||||
|
'batch': 'yes' if self.batch else 'no',
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.validity:
|
||||||
|
params['validity'] = str(self.validity)
|
||||||
|
|
||||||
|
# Extend our parameters
|
||||||
|
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
|
||||||
|
|
||||||
|
return '{schema}://{key}:{secret}@{source}/{targets}/?{params}'.format(
|
||||||
|
schema=self.secure_protocol,
|
||||||
|
key=self.pprint(self.apikey, privacy, safe=''),
|
||||||
|
secret=self.pprint(
|
||||||
|
self.secret, privacy, mode=PrivacyMode.Secret, safe=''),
|
||||||
|
source=NotifyBurstSMS.quote(self.source, safe=''),
|
||||||
|
targets='/'.join(
|
||||||
|
[NotifyBurstSMS.quote(x, safe='') for x in self.targets]),
|
||||||
|
params=NotifyBurstSMS.urlencode(params))
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
#
|
||||||
|
# Factor batch into calculation
|
||||||
|
#
|
||||||
|
batch_size = 1 if not self.batch else self.default_batch_size
|
||||||
|
targets = len(self.targets)
|
||||||
|
if batch_size > 1:
|
||||||
|
targets = int(targets / batch_size) + \
|
||||||
|
(1 if targets % batch_size else 0)
|
||||||
|
|
||||||
|
return targets if targets > 0 else 1
|
||||||
|
|
||||||
|
@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, verify_host=False)
|
||||||
|
if not results:
|
||||||
|
# We're done early as we couldn't load the results
|
||||||
|
return results
|
||||||
|
|
||||||
|
# The hostname is our source (Sender ID)
|
||||||
|
results['source'] = NotifyBurstSMS.unquote(results['host'])
|
||||||
|
|
||||||
|
# Get any remaining targets
|
||||||
|
results['targets'] = NotifyBurstSMS.split_path(results['fullpath'])
|
||||||
|
|
||||||
|
# Get our account_side and auth_token from the user/pass config
|
||||||
|
results['apikey'] = NotifyBurstSMS.unquote(results['user'])
|
||||||
|
results['secret'] = NotifyBurstSMS.unquote(results['password'])
|
||||||
|
|
||||||
|
# API Key
|
||||||
|
if 'key' in results['qsd'] and len(results['qsd']['key']):
|
||||||
|
# Extract the API Key from an argument
|
||||||
|
results['apikey'] = \
|
||||||
|
NotifyBurstSMS.unquote(results['qsd']['key'])
|
||||||
|
|
||||||
|
# API Secret
|
||||||
|
if 'secret' in results['qsd'] and len(results['qsd']['secret']):
|
||||||
|
# Extract the API Secret from an argument
|
||||||
|
results['secret'] = \
|
||||||
|
NotifyBurstSMS.unquote(results['qsd']['secret'])
|
||||||
|
|
||||||
|
# Support the 'from' and 'source' variable so that we can support
|
||||||
|
# targets this way too.
|
||||||
|
# The 'from' makes it easier to use yaml configuration
|
||||||
|
if 'from' in results['qsd'] and len(results['qsd']['from']):
|
||||||
|
results['source'] = \
|
||||||
|
NotifyBurstSMS.unquote(results['qsd']['from'])
|
||||||
|
if 'source' in results['qsd'] and len(results['qsd']['source']):
|
||||||
|
results['source'] = \
|
||||||
|
NotifyBurstSMS.unquote(results['qsd']['source'])
|
||||||
|
|
||||||
|
# Support country
|
||||||
|
if 'country' in results['qsd'] and len(results['qsd']['country']):
|
||||||
|
results['country'] = \
|
||||||
|
NotifyBurstSMS.unquote(results['qsd']['country'])
|
||||||
|
|
||||||
|
# Support validity value
|
||||||
|
if 'validity' in results['qsd'] and len(results['qsd']['validity']):
|
||||||
|
results['validity'] = \
|
||||||
|
NotifyBurstSMS.unquote(results['qsd']['validity'])
|
||||||
|
|
||||||
|
# Get Batch Mode Flag
|
||||||
|
if 'batch' in results['qsd'] and len(results['qsd']['batch']):
|
||||||
|
results['batch'] = parse_bool(results['qsd']['batch'])
|
||||||
|
|
||||||
|
# Support the 'to' variable so that we can support rooms this way too
|
||||||
|
# The 'to' makes it easier to use yaml configuration
|
||||||
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
|
results['targets'] += \
|
||||||
|
NotifyBurstSMS.parse_phone_no(results['qsd']['to'])
|
||||||
|
|
||||||
|
return results
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -288,6 +284,21 @@ class NotifyClickSend(NotifyBase):
|
||||||
params=NotifyClickSend.urlencode(params),
|
params=NotifyClickSend.urlencode(params),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
#
|
||||||
|
# Factor batch into calculation
|
||||||
|
#
|
||||||
|
batch_size = 1 if not self.batch else self.default_batch_size
|
||||||
|
targets = len(self.targets)
|
||||||
|
if batch_size > 1:
|
||||||
|
targets = int(targets / batch_size) + \
|
||||||
|
(1 if targets % batch_size else 0)
|
||||||
|
|
||||||
|
return targets
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -114,6 +110,7 @@ class NotifyD7Networks(NotifyBase):
|
||||||
'targets': {
|
'targets': {
|
||||||
'name': _('Targets'),
|
'name': _('Targets'),
|
||||||
'type': 'list:string',
|
'type': 'list:string',
|
||||||
|
'required': True,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -357,6 +354,15 @@ class NotifyD7Networks(NotifyBase):
|
||||||
[NotifyD7Networks.quote(x, safe='') for x in self.targets]),
|
[NotifyD7Networks.quote(x, safe='') for x in self.targets]),
|
||||||
params=NotifyD7Networks.urlencode(params))
|
params=NotifyD7Networks.urlencode(params))
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
#
|
||||||
|
# Factor batch into calculation
|
||||||
|
#
|
||||||
|
return len(self.targets) if not self.batch else 1
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -350,6 +346,21 @@ class NotifyDapnet(NotifyBase):
|
||||||
params=NotifyDapnet.urlencode(params),
|
params=NotifyDapnet.urlencode(params),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
#
|
||||||
|
# Factor batch into calculation
|
||||||
|
#
|
||||||
|
batch_size = 1 if not self.batch else self.default_batch_size
|
||||||
|
targets = len(self.targets)
|
||||||
|
if batch_size > 1:
|
||||||
|
targets = int(targets / batch_size) + \
|
||||||
|
(1 if targets % batch_size else 0)
|
||||||
|
|
||||||
|
return targets
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -103,13 +99,18 @@ class NotifyDingTalk(NotifyBase):
|
||||||
'regex': (r'^[a-z0-9]+$', 'i'),
|
'regex': (r'^[a-z0-9]+$', 'i'),
|
||||||
},
|
},
|
||||||
'secret': {
|
'secret': {
|
||||||
'name': _('Token'),
|
'name': _('Secret'),
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'private': True,
|
'private': True,
|
||||||
'regex': (r'^[a-z0-9]+$', 'i'),
|
'regex': (r'^[a-z0-9]+$', 'i'),
|
||||||
},
|
},
|
||||||
'targets': {
|
'target_phone_no': {
|
||||||
'name': _('Target Phone No'),
|
'name': _('Target Phone No'),
|
||||||
|
'type': 'string',
|
||||||
|
'map_to': 'targets',
|
||||||
|
},
|
||||||
|
'targets': {
|
||||||
|
'name': _('Targets'),
|
||||||
'type': 'list:string',
|
'type': 'list:string',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -309,6 +310,13 @@ class NotifyDingTalk(NotifyBase):
|
||||||
[NotifyDingTalk.quote(x, safe='') for x in self.targets]),
|
[NotifyDingTalk.quote(x, safe='') for x in self.targets]),
|
||||||
args=NotifyDingTalk.urlencode(args))
|
args=NotifyDingTalk.urlencode(args))
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
targets = len(self.targets)
|
||||||
|
return targets if targets > 0 else 1
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -50,6 +46,9 @@
|
||||||
import re
|
import re
|
||||||
import requests
|
import requests
|
||||||
from json import dumps
|
from json import dumps
|
||||||
|
from datetime import timedelta
|
||||||
|
from datetime import datetime
|
||||||
|
from datetime import timezone
|
||||||
|
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from ..common import NotifyImageSize
|
from ..common import NotifyImageSize
|
||||||
|
@ -81,9 +80,23 @@ class NotifyDiscord(NotifyBase):
|
||||||
# Discord Webhook
|
# Discord Webhook
|
||||||
notify_url = 'https://discord.com/api/webhooks'
|
notify_url = 'https://discord.com/api/webhooks'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# Allows the user to specify the NotifyImageSize object
|
# Allows the user to specify the NotifyImageSize object
|
||||||
image_size = NotifyImageSize.XY_256
|
image_size = NotifyImageSize.XY_256
|
||||||
|
|
||||||
|
# Discord is kind enough to return how many more requests we're allowed to
|
||||||
|
# continue to make within it's header response as:
|
||||||
|
# X-RateLimit-Reset: The epoc time (in seconds) we can expect our
|
||||||
|
# rate-limit to be reset.
|
||||||
|
# X-RateLimit-Remaining: an integer identifying how many requests we're
|
||||||
|
# still allow to make.
|
||||||
|
request_rate_per_sec = 0
|
||||||
|
|
||||||
|
# Taken right from google.auth.helpers:
|
||||||
|
clock_skew = timedelta(seconds=10)
|
||||||
|
|
||||||
# The maximum allowable characters allowed in the body per message
|
# The maximum allowable characters allowed in the body per message
|
||||||
body_maxlen = 2000
|
body_maxlen = 2000
|
||||||
|
|
||||||
|
@ -135,6 +148,13 @@ class NotifyDiscord(NotifyBase):
|
||||||
'name': _('Avatar URL'),
|
'name': _('Avatar URL'),
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
},
|
},
|
||||||
|
'href': {
|
||||||
|
'name': _('URL'),
|
||||||
|
'type': 'string',
|
||||||
|
},
|
||||||
|
'url': {
|
||||||
|
'alias_of': 'href',
|
||||||
|
},
|
||||||
# Send a message to the specified thread within a webhook's channel.
|
# Send a message to the specified thread within a webhook's channel.
|
||||||
# The thread will automatically be unarchived.
|
# The thread will automatically be unarchived.
|
||||||
'thread': {
|
'thread': {
|
||||||
|
@ -166,7 +186,8 @@ class NotifyDiscord(NotifyBase):
|
||||||
|
|
||||||
def __init__(self, webhook_id, webhook_token, tts=False, avatar=True,
|
def __init__(self, webhook_id, webhook_token, tts=False, avatar=True,
|
||||||
footer=False, footer_logo=True, include_image=False,
|
footer=False, footer_logo=True, include_image=False,
|
||||||
fields=True, avatar_url=None, thread=None, **kwargs):
|
fields=True, avatar_url=None, href=None, thread=None,
|
||||||
|
**kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Discord Object
|
Initialize Discord Object
|
||||||
|
|
||||||
|
@ -215,6 +236,15 @@ class NotifyDiscord(NotifyBase):
|
||||||
# dynamically generated avatar url images
|
# dynamically generated avatar url images
|
||||||
self.avatar_url = avatar_url
|
self.avatar_url = avatar_url
|
||||||
|
|
||||||
|
# A URL to have the title link to
|
||||||
|
self.href = href
|
||||||
|
|
||||||
|
# For Tracking Purposes
|
||||||
|
self.ratelimit_reset = datetime.now(timezone.utc).replace(tzinfo=None)
|
||||||
|
|
||||||
|
# Default to 1.0
|
||||||
|
self.ratelimit_remaining = 1.0
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,
|
def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,
|
||||||
|
@ -235,6 +265,18 @@ class NotifyDiscord(NotifyBase):
|
||||||
# Acquire image_url
|
# Acquire image_url
|
||||||
image_url = self.image_url(notify_type)
|
image_url = self.image_url(notify_type)
|
||||||
|
|
||||||
|
if self.avatar and (image_url or self.avatar_url):
|
||||||
|
payload['avatar_url'] = \
|
||||||
|
self.avatar_url if self.avatar_url else image_url
|
||||||
|
|
||||||
|
if self.user:
|
||||||
|
# Optionally override the default username of the webhook
|
||||||
|
payload['username'] = self.user
|
||||||
|
|
||||||
|
# Associate our thread_id with our message
|
||||||
|
params = {'thread_id': self.thread_id} if self.thread_id else None
|
||||||
|
|
||||||
|
if body:
|
||||||
# our fields variable
|
# our fields variable
|
||||||
fields = []
|
fields = []
|
||||||
|
|
||||||
|
@ -252,6 +294,9 @@ class NotifyDiscord(NotifyBase):
|
||||||
'color': self.color(notify_type, int),
|
'color': self.color(notify_type, int),
|
||||||
}]
|
}]
|
||||||
|
|
||||||
|
if self.href:
|
||||||
|
payload['embeds'][0]['url'] = self.href
|
||||||
|
|
||||||
if self.footer:
|
if self.footer:
|
||||||
# Acquire logo URL
|
# Acquire logo URL
|
||||||
logo_url = self.image_url(notify_type, logo=True)
|
logo_url = self.image_url(notify_type, logo=True)
|
||||||
|
@ -278,7 +323,8 @@ class NotifyDiscord(NotifyBase):
|
||||||
# Swap first entry for description
|
# Swap first entry for description
|
||||||
payload['embeds'][0]['description'] = description
|
payload['embeds'][0]['description'] = description
|
||||||
if fields:
|
if fields:
|
||||||
# Apply our additional parsing for a better presentation
|
# Apply our additional parsing for a better
|
||||||
|
# presentation
|
||||||
payload['embeds'][0]['fields'] = \
|
payload['embeds'][0]['fields'] = \
|
||||||
fields[:self.discord_max_fields]
|
fields[:self.discord_max_fields]
|
||||||
|
|
||||||
|
@ -290,18 +336,7 @@ class NotifyDiscord(NotifyBase):
|
||||||
payload['content'] = \
|
payload['content'] = \
|
||||||
body if not title else "{}\r\n{}".format(title, body)
|
body if not title else "{}\r\n{}".format(title, body)
|
||||||
|
|
||||||
if self.thread_id:
|
if not self._send(payload, params=params):
|
||||||
payload['thread_id'] = self.thread_id
|
|
||||||
|
|
||||||
if self.avatar and (image_url or self.avatar_url):
|
|
||||||
payload['avatar_url'] = \
|
|
||||||
self.avatar_url if self.avatar_url else image_url
|
|
||||||
|
|
||||||
if self.user:
|
|
||||||
# Optionally override the default username of the webhook
|
|
||||||
payload['username'] = self.user
|
|
||||||
|
|
||||||
if not self._send(payload):
|
|
||||||
# We failed to post our message
|
# We failed to post our message
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -315,7 +350,7 @@ class NotifyDiscord(NotifyBase):
|
||||||
# We failed to post our message
|
# We failed to post our message
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
# Update our payload; the idea is to preserve it's other detected
|
# Update our payload; the idea is to preserve it's other detected
|
||||||
# and assigned values for re-use here too
|
# and assigned values for re-use here too
|
||||||
payload.update({
|
payload.update({
|
||||||
|
@ -338,14 +373,15 @@ class NotifyDiscord(NotifyBase):
|
||||||
for attachment in attach:
|
for attachment in attach:
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
'Posting Discord Attachment {}'.format(attachment.name))
|
'Posting Discord Attachment {}'.format(attachment.name))
|
||||||
if not self._send(payload, attach=attachment):
|
if not self._send(payload, params=params, attach=attachment):
|
||||||
# We failed to post our message
|
# We failed to post our message
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Otherwise return
|
# Otherwise return
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _send(self, payload, attach=None, **kwargs):
|
def _send(self, payload, attach=None, params=None, rate_limit=1,
|
||||||
|
**kwargs):
|
||||||
"""
|
"""
|
||||||
Wrapper to the requests (post) object
|
Wrapper to the requests (post) object
|
||||||
"""
|
"""
|
||||||
|
@ -367,8 +403,25 @@ class NotifyDiscord(NotifyBase):
|
||||||
))
|
))
|
||||||
self.logger.debug('Discord Payload: %s' % str(payload))
|
self.logger.debug('Discord Payload: %s' % str(payload))
|
||||||
|
|
||||||
# Always call throttle before any remote server i/o is made
|
# By default set wait to None
|
||||||
self.throttle()
|
wait = None
|
||||||
|
|
||||||
|
if self.ratelimit_remaining <= 0.0:
|
||||||
|
# Determine how long we should wait for or if we should wait at
|
||||||
|
# all. This isn't fool-proof because we can't be sure the client
|
||||||
|
# time (calling this script) is completely synced up with the
|
||||||
|
# Discord server. One would hope we're on NTP and our clocks are
|
||||||
|
# the same allowing this to role smoothly:
|
||||||
|
|
||||||
|
now = datetime.now(timezone.utc).replace(tzinfo=None)
|
||||||
|
if now < self.ratelimit_reset:
|
||||||
|
# We need to throttle for the difference in seconds
|
||||||
|
wait = abs(
|
||||||
|
(self.ratelimit_reset - now + self.clock_skew)
|
||||||
|
.total_seconds())
|
||||||
|
|
||||||
|
# Always call throttle before any remote server i/o is made;
|
||||||
|
self.throttle(wait=wait)
|
||||||
|
|
||||||
# Perform some simple error checking
|
# Perform some simple error checking
|
||||||
if isinstance(attach, AttachBase):
|
if isinstance(attach, AttachBase):
|
||||||
|
@ -396,12 +449,29 @@ class NotifyDiscord(NotifyBase):
|
||||||
|
|
||||||
r = requests.post(
|
r = requests.post(
|
||||||
notify_url,
|
notify_url,
|
||||||
|
params=params,
|
||||||
data=payload if files else dumps(payload),
|
data=payload if files else dumps(payload),
|
||||||
headers=headers,
|
headers=headers,
|
||||||
files=files,
|
files=files,
|
||||||
verify=self.verify_certificate,
|
verify=self.verify_certificate,
|
||||||
timeout=self.request_timeout,
|
timeout=self.request_timeout,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Handle rate limiting (if specified)
|
||||||
|
try:
|
||||||
|
# Store our rate limiting (if provided)
|
||||||
|
self.ratelimit_remaining = \
|
||||||
|
float(r.headers.get(
|
||||||
|
'X-RateLimit-Remaining'))
|
||||||
|
self.ratelimit_reset = datetime.fromtimestamp(
|
||||||
|
int(r.headers.get('X-RateLimit-Reset')),
|
||||||
|
timezone.utc).replace(tzinfo=None)
|
||||||
|
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
# This is returned if we could not retrieve this
|
||||||
|
# information gracefully accept this state and move on
|
||||||
|
pass
|
||||||
|
|
||||||
if r.status_code not in (
|
if r.status_code not in (
|
||||||
requests.codes.ok, requests.codes.no_content):
|
requests.codes.ok, requests.codes.no_content):
|
||||||
|
|
||||||
|
@ -409,6 +479,20 @@ class NotifyDiscord(NotifyBase):
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyBase.http_response_code_lookup(r.status_code)
|
NotifyBase.http_response_code_lookup(r.status_code)
|
||||||
|
|
||||||
|
if r.status_code == requests.codes.too_many_requests \
|
||||||
|
and rate_limit > 0:
|
||||||
|
|
||||||
|
# handle rate limiting
|
||||||
|
self.logger.warning(
|
||||||
|
'Discord rate limiting in effect; '
|
||||||
|
'blocking for %.2f second(s)',
|
||||||
|
self.ratelimit_remaining)
|
||||||
|
|
||||||
|
# Try one more time before failing
|
||||||
|
return self._send(
|
||||||
|
payload=payload, attach=attach, params=params,
|
||||||
|
rate_limit=rate_limit - 1, **kwargs)
|
||||||
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Failed to send {}to Discord notification: '
|
'Failed to send {}to Discord notification: '
|
||||||
'{}{}error={}.'.format(
|
'{}{}error={}.'.format(
|
||||||
|
@ -466,6 +550,9 @@ class NotifyDiscord(NotifyBase):
|
||||||
if self.avatar_url:
|
if self.avatar_url:
|
||||||
params['avatar_url'] = self.avatar_url
|
params['avatar_url'] = self.avatar_url
|
||||||
|
|
||||||
|
if self.href:
|
||||||
|
params['href'] = self.href
|
||||||
|
|
||||||
if self.thread_id:
|
if self.thread_id:
|
||||||
params['thread'] = self.thread_id
|
params['thread'] = self.thread_id
|
||||||
|
|
||||||
|
@ -537,10 +624,23 @@ class NotifyDiscord(NotifyBase):
|
||||||
results['avatar_url'] = \
|
results['avatar_url'] = \
|
||||||
NotifyDiscord.unquote(results['qsd']['avatar_url'])
|
NotifyDiscord.unquote(results['qsd']['avatar_url'])
|
||||||
|
|
||||||
|
# Extract url if it was specified
|
||||||
|
if 'href' in results['qsd']:
|
||||||
|
results['href'] = \
|
||||||
|
NotifyDiscord.unquote(results['qsd']['href'])
|
||||||
|
|
||||||
|
elif 'url' in results['qsd']:
|
||||||
|
results['href'] = \
|
||||||
|
NotifyDiscord.unquote(results['qsd']['url'])
|
||||||
|
# Markdown is implied
|
||||||
|
results['format'] = NotifyFormat.MARKDOWN
|
||||||
|
|
||||||
# Extract thread id if it was specified
|
# Extract thread id if it was specified
|
||||||
if 'thread' in results['qsd']:
|
if 'thread' in results['qsd']:
|
||||||
results['thread'] = \
|
results['thread'] = \
|
||||||
NotifyDiscord.unquote(results['qsd']['thread'])
|
NotifyDiscord.unquote(results['qsd']['thread'])
|
||||||
|
# Markdown is implied
|
||||||
|
results['format'] = NotifyFormat.MARKDOWN
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -43,6 +39,7 @@ from email import charset
|
||||||
|
|
||||||
from socket import error as SocketError
|
from socket import error as SocketError
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from datetime import timezone
|
||||||
|
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from ..URLBase import PrivacyMode
|
from ..URLBase import PrivacyMode
|
||||||
|
@ -340,6 +337,9 @@ class NotifyEmail(NotifyBase):
|
||||||
# A URL that takes you to the setup/help of the specific protocol
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_email'
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_email'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# Default Notify Format
|
# Default Notify Format
|
||||||
notify_format = NotifyFormat.HTML
|
notify_format = NotifyFormat.HTML
|
||||||
|
|
||||||
|
@ -384,8 +384,13 @@ class NotifyEmail(NotifyBase):
|
||||||
'min': 1,
|
'min': 1,
|
||||||
'max': 65535,
|
'max': 65535,
|
||||||
},
|
},
|
||||||
|
'target_email': {
|
||||||
|
'name': _('Target Email'),
|
||||||
|
'type': 'string',
|
||||||
|
'map_to': 'targets',
|
||||||
|
},
|
||||||
'targets': {
|
'targets': {
|
||||||
'name': _('Target Emails'),
|
'name': _('Targets'),
|
||||||
'type': 'list:string',
|
'type': 'list:string',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -764,7 +769,7 @@ class NotifyEmail(NotifyBase):
|
||||||
else:
|
else:
|
||||||
base = MIMEText(body, 'plain', 'utf-8')
|
base = MIMEText(body, 'plain', 'utf-8')
|
||||||
|
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
mixed = MIMEMultipart("mixed")
|
mixed = MIMEMultipart("mixed")
|
||||||
mixed.attach(base)
|
mixed.attach(base)
|
||||||
# Now store our attachments
|
# Now store our attachments
|
||||||
|
@ -805,7 +810,8 @@ class NotifyEmail(NotifyBase):
|
||||||
base['To'] = formataddr((to_name, to_addr), charset='utf-8')
|
base['To'] = formataddr((to_name, to_addr), charset='utf-8')
|
||||||
base['Message-ID'] = make_msgid(domain=self.smtp_host)
|
base['Message-ID'] = make_msgid(domain=self.smtp_host)
|
||||||
base['Date'] = \
|
base['Date'] = \
|
||||||
datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S +0000")
|
datetime.now(timezone.utc)\
|
||||||
|
.strftime("%a, %d %b %Y %H:%M:%S +0000")
|
||||||
base['X-Application'] = self.app_id
|
base['X-Application'] = self.app_id
|
||||||
|
|
||||||
if cc:
|
if cc:
|
||||||
|
@ -999,6 +1005,13 @@ class NotifyEmail(NotifyBase):
|
||||||
params=NotifyEmail.urlencode(params),
|
params=NotifyEmail.urlencode(params),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
targets = len(self.targets)
|
||||||
|
return targets if targets > 0 else 1
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
@ -1023,6 +1036,10 @@ class NotifyEmail(NotifyBase):
|
||||||
# add one to ourselves
|
# add one to ourselves
|
||||||
results['targets'] = NotifyEmail.split_path(results['fullpath'])
|
results['targets'] = NotifyEmail.split_path(results['fullpath'])
|
||||||
|
|
||||||
|
# Attempt to detect 'to' email address
|
||||||
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
|
results['targets'].append(results['qsd']['to'])
|
||||||
|
|
||||||
# Attempt to detect 'from' email address
|
# Attempt to detect 'from' email address
|
||||||
if 'from' in results['qsd'] and len(results['qsd']['from']):
|
if 'from' in results['qsd'] and len(results['qsd']['from']):
|
||||||
from_addr = NotifyEmail.unquote(results['qsd']['from'])
|
from_addr = NotifyEmail.unquote(results['qsd']['from'])
|
||||||
|
@ -1041,10 +1058,6 @@ class NotifyEmail(NotifyBase):
|
||||||
# Extract from name to associate with from address
|
# Extract from name to associate with from address
|
||||||
from_addr = NotifyEmail.unquote(results['qsd']['name'])
|
from_addr = NotifyEmail.unquote(results['qsd']['name'])
|
||||||
|
|
||||||
# Attempt to detect 'to' email address
|
|
||||||
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
|
||||||
results['targets'].append(results['qsd']['to'])
|
|
||||||
|
|
||||||
# Store SMTP Host if specified
|
# Store SMTP Host if specified
|
||||||
if 'smtp' in results['qsd'] and len(results['qsd']['smtp']):
|
if 'smtp' in results['qsd'] and len(results['qsd']['smtp']):
|
||||||
# Extract the smtp server
|
# Extract the smtp server
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -157,7 +153,6 @@ class NotifyFCM(NotifyBase):
|
||||||
'project': {
|
'project': {
|
||||||
'name': _('Project ID'),
|
'name': _('Project ID'),
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'required': True,
|
|
||||||
},
|
},
|
||||||
'target_device': {
|
'target_device': {
|
||||||
'name': _('Target Device'),
|
'name': _('Target Device'),
|
||||||
|
@ -173,6 +168,7 @@ class NotifyFCM(NotifyBase):
|
||||||
'targets': {
|
'targets': {
|
||||||
'name': _('Targets'),
|
'name': _('Targets'),
|
||||||
'type': 'list:string',
|
'type': 'list:string',
|
||||||
|
'required': True,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -555,6 +551,12 @@ class NotifyFCM(NotifyBase):
|
||||||
params=NotifyFCM.urlencode(params),
|
params=NotifyFCM.urlencode(params),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
return len(self.targets)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -47,6 +43,7 @@ from cryptography.hazmat.primitives import asymmetric
|
||||||
from cryptography.exceptions import UnsupportedAlgorithm
|
from cryptography.exceptions import UnsupportedAlgorithm
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from datetime import timezone
|
||||||
from json.decoder import JSONDecodeError
|
from json.decoder import JSONDecodeError
|
||||||
from urllib.parse import urlencode as _urlencode
|
from urllib.parse import urlencode as _urlencode
|
||||||
|
|
||||||
|
@ -106,7 +103,7 @@ class GoogleOAuth:
|
||||||
# Our keys we build using the provided content
|
# Our keys we build using the provided content
|
||||||
self.__refresh_token = None
|
self.__refresh_token = None
|
||||||
self.__access_token = None
|
self.__access_token = None
|
||||||
self.__access_token_expiry = datetime.utcnow()
|
self.__access_token_expiry = datetime.now(timezone.utc)
|
||||||
|
|
||||||
def load(self, path):
|
def load(self, path):
|
||||||
"""
|
"""
|
||||||
|
@ -117,7 +114,7 @@ class GoogleOAuth:
|
||||||
self.content = None
|
self.content = None
|
||||||
self.private_key = None
|
self.private_key = None
|
||||||
self.__access_token = None
|
self.__access_token = None
|
||||||
self.__access_token_expiry = datetime.utcnow()
|
self.__access_token_expiry = datetime.now(timezone.utc)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(path, mode="r", encoding=self.encoding) as fp:
|
with open(path, mode="r", encoding=self.encoding) as fp:
|
||||||
|
@ -199,7 +196,7 @@ class GoogleOAuth:
|
||||||
'token with.')
|
'token with.')
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if self.__access_token_expiry > datetime.utcnow():
|
if self.__access_token_expiry > datetime.now(timezone.utc):
|
||||||
# Return our no-expired key
|
# Return our no-expired key
|
||||||
return self.__access_token
|
return self.__access_token
|
||||||
|
|
||||||
|
@ -209,7 +206,7 @@ class GoogleOAuth:
|
||||||
key_identifier = self.content.get('private_key_id')
|
key_identifier = self.content.get('private_key_id')
|
||||||
|
|
||||||
# Generate our Assertion
|
# Generate our Assertion
|
||||||
now = datetime.utcnow()
|
now = datetime.now(timezone.utc)
|
||||||
expiry = now + self.access_token_lifetime_sec
|
expiry = now + self.access_token_lifetime_sec
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
|
@ -301,7 +298,7 @@ class GoogleOAuth:
|
||||||
if 'expires_in' in response:
|
if 'expires_in' in response:
|
||||||
delta = timedelta(seconds=int(response['expires_in']))
|
delta = timedelta(seconds=int(response['expires_in']))
|
||||||
self.__access_token_expiry = \
|
self.__access_token_expiry = \
|
||||||
delta + datetime.utcnow() - self.clock_skew
|
delta + datetime.now(timezone.utc) - self.clock_skew
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Allow some grace before we expire
|
# Allow some grace before we expire
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -97,8 +93,8 @@ class NotifyFlock(NotifyBase):
|
||||||
# Define object templates
|
# Define object templates
|
||||||
templates = (
|
templates = (
|
||||||
'{schema}://{token}',
|
'{schema}://{token}',
|
||||||
'{schema}://{user}@{token}',
|
'{schema}://{botname}@{token}',
|
||||||
'{schema}://{user}@{token}/{targets}',
|
'{schema}://{botname}@{token}/{targets}',
|
||||||
'{schema}://{token}/{targets}',
|
'{schema}://{token}/{targets}',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -111,9 +107,10 @@ class NotifyFlock(NotifyBase):
|
||||||
'private': True,
|
'private': True,
|
||||||
'required': True,
|
'required': True,
|
||||||
},
|
},
|
||||||
'user': {
|
'botname': {
|
||||||
'name': _('Bot Name'),
|
'name': _('Bot Name'),
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
|
'map_to': 'user',
|
||||||
},
|
},
|
||||||
'to_user': {
|
'to_user': {
|
||||||
'name': _('To User ID'),
|
'name': _('To User ID'),
|
||||||
|
@ -334,6 +331,13 @@ class NotifyFlock(NotifyBase):
|
||||||
params=NotifyFlock.urlencode(params),
|
params=NotifyFlock.urlencode(params),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
targets = len(self.targets)
|
||||||
|
return targets if targets > 0 else 1
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -40,6 +36,16 @@ from ..common import NotifyType
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class FORMPayloadField:
|
||||||
|
"""
|
||||||
|
Identifies the fields available in the FORM Payload
|
||||||
|
"""
|
||||||
|
VERSION = 'version'
|
||||||
|
TITLE = 'title'
|
||||||
|
MESSAGE = 'message'
|
||||||
|
MESSAGETYPE = 'type'
|
||||||
|
|
||||||
|
|
||||||
# Defines the method to send the notification
|
# Defines the method to send the notification
|
||||||
METHODS = (
|
METHODS = (
|
||||||
'POST',
|
'POST',
|
||||||
|
@ -89,6 +95,9 @@ class NotifyForm(NotifyBase):
|
||||||
# A URL that takes you to the setup/help of the specific protocol
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_Custom_Form'
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_Custom_Form'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# Allows the user to specify the NotifyImageSize object
|
# Allows the user to specify the NotifyImageSize object
|
||||||
image_size = NotifyImageSize.XY_128
|
image_size = NotifyImageSize.XY_128
|
||||||
|
|
||||||
|
@ -96,6 +105,12 @@ class NotifyForm(NotifyBase):
|
||||||
# local anyway
|
# local anyway
|
||||||
request_rate_per_sec = 0
|
request_rate_per_sec = 0
|
||||||
|
|
||||||
|
# Define the FORM version to place in all payloads
|
||||||
|
# Version: Major.Minor, Major is only updated if the entire schema is
|
||||||
|
# changed. If just adding new items (or removing old ones, only increment
|
||||||
|
# the Minor!
|
||||||
|
form_version = '1.0'
|
||||||
|
|
||||||
# Define object templates
|
# Define object templates
|
||||||
templates = (
|
templates = (
|
||||||
'{schema}://{host}',
|
'{schema}://{host}',
|
||||||
|
@ -218,6 +233,18 @@ class NotifyForm(NotifyBase):
|
||||||
self.attach_as += self.attach_as_count
|
self.attach_as += self.attach_as_count
|
||||||
self.attach_multi_support = True
|
self.attach_multi_support = True
|
||||||
|
|
||||||
|
# A payload map allows users to over-ride the default mapping if
|
||||||
|
# they're detected with the :overide=value. Normally this would
|
||||||
|
# create a new key and assign it the value specified. However
|
||||||
|
# if the key you specify is actually an internally mapped one,
|
||||||
|
# then a re-mapping takes place using the value
|
||||||
|
self.payload_map = {
|
||||||
|
FORMPayloadField.VERSION: FORMPayloadField.VERSION,
|
||||||
|
FORMPayloadField.TITLE: FORMPayloadField.TITLE,
|
||||||
|
FORMPayloadField.MESSAGE: FORMPayloadField.MESSAGE,
|
||||||
|
FORMPayloadField.MESSAGETYPE: FORMPayloadField.MESSAGETYPE,
|
||||||
|
}
|
||||||
|
|
||||||
self.params = {}
|
self.params = {}
|
||||||
if params:
|
if params:
|
||||||
# Store our extra headers
|
# Store our extra headers
|
||||||
|
@ -228,10 +255,20 @@ class NotifyForm(NotifyBase):
|
||||||
# Store our extra headers
|
# Store our extra headers
|
||||||
self.headers.update(headers)
|
self.headers.update(headers)
|
||||||
|
|
||||||
|
self.payload_overrides = {}
|
||||||
self.payload_extras = {}
|
self.payload_extras = {}
|
||||||
if payload:
|
if payload:
|
||||||
# Store our extra payload entries
|
# Store our extra payload entries
|
||||||
self.payload_extras.update(payload)
|
self.payload_extras.update(payload)
|
||||||
|
for key in list(self.payload_extras.keys()):
|
||||||
|
# Any values set in the payload to alter a system related one
|
||||||
|
# alters the system key. Hence :message=msg maps the 'message'
|
||||||
|
# variable that otherwise already contains the payload to be
|
||||||
|
# 'msg' instead (containing the payload)
|
||||||
|
if key in self.payload_map:
|
||||||
|
self.payload_map[key] = self.payload_extras[key]
|
||||||
|
self.payload_overrides[key] = self.payload_extras[key]
|
||||||
|
del self.payload_extras[key]
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -257,6 +294,8 @@ class NotifyForm(NotifyBase):
|
||||||
# Append our payload extra's into our parameters
|
# Append our payload extra's into our parameters
|
||||||
params.update(
|
params.update(
|
||||||
{':{}'.format(k): v for k, v in self.payload_extras.items()})
|
{':{}'.format(k): v for k, v in self.payload_extras.items()})
|
||||||
|
params.update(
|
||||||
|
{':{}'.format(k): v for k, v in self.payload_overrides.items()})
|
||||||
|
|
||||||
if self.attach_as != self.attach_as_default:
|
if self.attach_as != self.attach_as_default:
|
||||||
# Provide Attach-As extension details
|
# Provide Attach-As extension details
|
||||||
|
@ -305,7 +344,7 @@ class NotifyForm(NotifyBase):
|
||||||
|
|
||||||
# Track our potential attachments
|
# Track our potential attachments
|
||||||
files = []
|
files = []
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
for no, attachment in enumerate(attach, start=1):
|
for no, attachment in enumerate(attach, start=1):
|
||||||
# Perform some simple error checking
|
# Perform some simple error checking
|
||||||
if not attachment:
|
if not attachment:
|
||||||
|
@ -337,15 +376,18 @@ class NotifyForm(NotifyBase):
|
||||||
'form:// Multi-Attachment Support not enabled')
|
'form:// Multi-Attachment Support not enabled')
|
||||||
|
|
||||||
# prepare Form Object
|
# prepare Form Object
|
||||||
payload = {
|
payload = {}
|
||||||
# Version: Major.Minor, Major is only updated if the entire
|
|
||||||
# schema is changed. If just adding new items (or removing
|
for key, value in (
|
||||||
# old ones, only increment the Minor!
|
(FORMPayloadField.VERSION, self.form_version),
|
||||||
'version': '1.0',
|
(FORMPayloadField.TITLE, title),
|
||||||
'title': title,
|
(FORMPayloadField.MESSAGE, body),
|
||||||
'message': body,
|
(FORMPayloadField.MESSAGETYPE, notify_type)):
|
||||||
'type': notify_type,
|
|
||||||
}
|
if not self.payload_map[key]:
|
||||||
|
# Do not store element in payload response
|
||||||
|
continue
|
||||||
|
payload[self.payload_map[key]] = value
|
||||||
|
|
||||||
# Apply any/all payload over-rides defined
|
# Apply any/all payload over-rides defined
|
||||||
payload.update(self.payload_extras)
|
payload.update(self.payload_extras)
|
||||||
|
|
|
@ -1,419 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# BSD 3-Clause License
|
|
||||||
#
|
|
||||||
# Apprise - Push Notification Library.
|
|
||||||
# Copyright (c) 2023, 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.
|
|
||||||
#
|
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
# Once you visit: https://developer.gitter.im/apps you'll get a personal
|
|
||||||
# access token that will look something like this:
|
|
||||||
# b5647881d563fm846dfbb2c27d1fe8f669b8f026
|
|
||||||
|
|
||||||
# Don't worry about generating an app; this token is all you need to form
|
|
||||||
# you're URL with. The syntax is as follows:
|
|
||||||
# gitter://{token}/{channel}
|
|
||||||
|
|
||||||
# Hence a URL might look like the following:
|
|
||||||
# gitter://b5647881d563fm846dfbb2c27d1fe8f669b8f026/apprise
|
|
||||||
|
|
||||||
# Note: You must have joined the channel to send a message to it!
|
|
||||||
|
|
||||||
# Official API reference: https://developer.gitter.im/docs/user-resource
|
|
||||||
|
|
||||||
import re
|
|
||||||
import requests
|
|
||||||
from json import loads
|
|
||||||
from json import dumps
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from .NotifyBase import NotifyBase
|
|
||||||
from ..common import NotifyImageSize
|
|
||||||
from ..common import NotifyFormat
|
|
||||||
from ..common import NotifyType
|
|
||||||
from ..utils import parse_list
|
|
||||||
from ..utils import parse_bool
|
|
||||||
from ..utils import validate_regex
|
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
|
||||||
|
|
||||||
# API Gitter URL
|
|
||||||
GITTER_API_URL = 'https://api.gitter.im/v1'
|
|
||||||
|
|
||||||
# Used to break path apart into list of targets
|
|
||||||
TARGET_LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+')
|
|
||||||
|
|
||||||
|
|
||||||
class NotifyGitter(NotifyBase):
|
|
||||||
"""
|
|
||||||
A wrapper for Gitter Notifications
|
|
||||||
"""
|
|
||||||
|
|
||||||
# The default descriptive name associated with the Notification
|
|
||||||
service_name = 'Gitter'
|
|
||||||
|
|
||||||
# The services URL
|
|
||||||
service_url = 'https://gitter.im/'
|
|
||||||
|
|
||||||
# All notification requests are secure
|
|
||||||
secure_protocol = 'gitter'
|
|
||||||
|
|
||||||
# A URL that takes you to the setup/help of the specific protocol
|
|
||||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_gitter'
|
|
||||||
|
|
||||||
# Allows the user to specify the NotifyImageSize object
|
|
||||||
image_size = NotifyImageSize.XY_32
|
|
||||||
|
|
||||||
# Gitter does not support a title
|
|
||||||
title_maxlen = 0
|
|
||||||
|
|
||||||
# Gitter is kind enough to return how many more requests we're allowed to
|
|
||||||
# continue to make within it's header response as:
|
|
||||||
# X-RateLimit-Reset: The epoc time (in seconds) we can expect our
|
|
||||||
# rate-limit to be reset.
|
|
||||||
# X-RateLimit-Remaining: an integer identifying how many requests we're
|
|
||||||
# still allow to make.
|
|
||||||
request_rate_per_sec = 0
|
|
||||||
|
|
||||||
# For Tracking Purposes
|
|
||||||
ratelimit_reset = datetime.utcnow()
|
|
||||||
|
|
||||||
# Default to 1
|
|
||||||
ratelimit_remaining = 1
|
|
||||||
|
|
||||||
# Default Notification Format
|
|
||||||
notify_format = NotifyFormat.MARKDOWN
|
|
||||||
|
|
||||||
# Define object templates
|
|
||||||
templates = (
|
|
||||||
'{schema}://{token}/{targets}/',
|
|
||||||
)
|
|
||||||
|
|
||||||
# Define our template tokens
|
|
||||||
template_tokens = dict(NotifyBase.template_tokens, **{
|
|
||||||
'token': {
|
|
||||||
'name': _('Token'),
|
|
||||||
'type': 'string',
|
|
||||||
'private': True,
|
|
||||||
'required': True,
|
|
||||||
'regex': (r'^[a-z0-9]{40}$', 'i'),
|
|
||||||
},
|
|
||||||
'targets': {
|
|
||||||
'name': _('Rooms'),
|
|
||||||
'type': 'list:string',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
# Define our template arguments
|
|
||||||
template_args = dict(NotifyBase.template_args, **{
|
|
||||||
'image': {
|
|
||||||
'name': _('Include Image'),
|
|
||||||
'type': 'bool',
|
|
||||||
'default': False,
|
|
||||||
'map_to': 'include_image',
|
|
||||||
},
|
|
||||||
'to': {
|
|
||||||
'alias_of': 'targets',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
def __init__(self, token, targets, include_image=False, **kwargs):
|
|
||||||
"""
|
|
||||||
Initialize Gitter Object
|
|
||||||
"""
|
|
||||||
super().__init__(**kwargs)
|
|
||||||
|
|
||||||
# Secret Key (associated with project)
|
|
||||||
self.token = validate_regex(
|
|
||||||
token, *self.template_tokens['token']['regex'])
|
|
||||||
if not self.token:
|
|
||||||
msg = 'An invalid Gitter API Token ' \
|
|
||||||
'({}) was specified.'.format(token)
|
|
||||||
self.logger.warning(msg)
|
|
||||||
raise TypeError(msg)
|
|
||||||
|
|
||||||
# Parse our targets
|
|
||||||
self.targets = parse_list(targets)
|
|
||||||
if not self.targets:
|
|
||||||
msg = 'There are no valid Gitter targets to notify.'
|
|
||||||
self.logger.warning(msg)
|
|
||||||
raise TypeError(msg)
|
|
||||||
|
|
||||||
# Used to track maping of rooms to their numeric id lookup for
|
|
||||||
# messaging
|
|
||||||
self._room_mapping = None
|
|
||||||
|
|
||||||
# Track whether or not we want to send an image with our notification
|
|
||||||
# or not.
|
|
||||||
self.include_image = include_image
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
|
||||||
"""
|
|
||||||
Perform Gitter Notification
|
|
||||||
"""
|
|
||||||
|
|
||||||
# error tracking (used for function return)
|
|
||||||
has_error = False
|
|
||||||
|
|
||||||
# Set up our image for display if configured to do so
|
|
||||||
image_url = None if not self.include_image \
|
|
||||||
else self.image_url(notify_type)
|
|
||||||
|
|
||||||
if image_url:
|
|
||||||
body = '![alt]({})\n{}'.format(image_url, body)
|
|
||||||
|
|
||||||
if self._room_mapping is None:
|
|
||||||
# Populate our room mapping
|
|
||||||
self._room_mapping = {}
|
|
||||||
postokay, response = self._fetch(url='rooms')
|
|
||||||
if not postokay:
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Response generally looks like this:
|
|
||||||
# [
|
|
||||||
# {
|
|
||||||
# noindex: False,
|
|
||||||
# oneToOne: False,
|
|
||||||
# avatarUrl: 'https://path/to/avatar/url',
|
|
||||||
# url: '/apprise-notifications/community',
|
|
||||||
# public: True,
|
|
||||||
# tags: [],
|
|
||||||
# lurk: False,
|
|
||||||
# uri: 'apprise-notifications/community',
|
|
||||||
# lastAccessTime: '2019-03-25T00:12:28.144Z',
|
|
||||||
# topic: '',
|
|
||||||
# roomMember: True,
|
|
||||||
# groupId: '5c981cecd73408ce4fbbad2f',
|
|
||||||
# githubType: 'REPO_CHANNEL',
|
|
||||||
# unreadItems: 0,
|
|
||||||
# mentions: 0,
|
|
||||||
# security: 'PUBLIC',
|
|
||||||
# userCount: 1,
|
|
||||||
# id: '5c981cecd73408ce4fbbad31',
|
|
||||||
# name: 'apprise/community'
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
for entry in response:
|
|
||||||
self._room_mapping[entry['name'].lower().split('/')[0]] = {
|
|
||||||
# The ID of the room
|
|
||||||
'id': entry['id'],
|
|
||||||
|
|
||||||
# A descriptive name (useful for logging)
|
|
||||||
'uri': entry['uri'],
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create a copy of the targets list
|
|
||||||
targets = list(self.targets)
|
|
||||||
while len(targets):
|
|
||||||
target = targets.pop(0).lower()
|
|
||||||
|
|
||||||
if target not in self._room_mapping:
|
|
||||||
self.logger.warning(
|
|
||||||
'Failed to locate Gitter room {}'.format(target))
|
|
||||||
|
|
||||||
# Flag our error
|
|
||||||
has_error = True
|
|
||||||
continue
|
|
||||||
|
|
||||||
# prepare our payload
|
|
||||||
payload = {
|
|
||||||
'text': body,
|
|
||||||
}
|
|
||||||
|
|
||||||
# Our Notification URL
|
|
||||||
notify_url = 'rooms/{}/chatMessages'.format(
|
|
||||||
self._room_mapping[target]['id'])
|
|
||||||
|
|
||||||
# Perform our query
|
|
||||||
postokay, response = self._fetch(
|
|
||||||
notify_url, payload=dumps(payload), method='POST')
|
|
||||||
|
|
||||||
if not postokay:
|
|
||||||
# Flag our error
|
|
||||||
has_error = True
|
|
||||||
|
|
||||||
return not has_error
|
|
||||||
|
|
||||||
def _fetch(self, url, payload=None, method='GET'):
|
|
||||||
"""
|
|
||||||
Wrapper to request object
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Prepare our headers:
|
|
||||||
headers = {
|
|
||||||
'User-Agent': self.app_id,
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'Authorization': 'Bearer ' + self.token,
|
|
||||||
}
|
|
||||||
if payload:
|
|
||||||
# Only set our header payload if it's defined
|
|
||||||
headers['Content-Type'] = 'application/json'
|
|
||||||
|
|
||||||
# Default content response object
|
|
||||||
content = {}
|
|
||||||
|
|
||||||
# Update our URL
|
|
||||||
url = '{}/{}'.format(GITTER_API_URL, url)
|
|
||||||
|
|
||||||
# Some Debug Logging
|
|
||||||
self.logger.debug('Gitter {} URL: {} (cert_verify={})'.format(
|
|
||||||
method,
|
|
||||||
url, self.verify_certificate))
|
|
||||||
if payload:
|
|
||||||
self.logger.debug('Gitter Payload: {}' .format(payload))
|
|
||||||
|
|
||||||
# By default set wait to None
|
|
||||||
wait = None
|
|
||||||
|
|
||||||
if self.ratelimit_remaining <= 0:
|
|
||||||
# Determine how long we should wait for or if we should wait at
|
|
||||||
# all. This isn't fool-proof because we can't be sure the client
|
|
||||||
# time (calling this script) is completely synced up with the
|
|
||||||
# Gitter server. One would hope we're on NTP and our clocks are
|
|
||||||
# the same allowing this to role smoothly:
|
|
||||||
|
|
||||||
now = datetime.utcnow()
|
|
||||||
if now < self.ratelimit_reset:
|
|
||||||
# We need to throttle for the difference in seconds
|
|
||||||
# We add 0.5 seconds to the end just to allow a grace
|
|
||||||
# period.
|
|
||||||
wait = (self.ratelimit_reset - now).total_seconds() + 0.5
|
|
||||||
|
|
||||||
# Always call throttle before any remote server i/o is made
|
|
||||||
self.throttle(wait=wait)
|
|
||||||
|
|
||||||
# fetch function
|
|
||||||
fn = requests.post if method == 'POST' else requests.get
|
|
||||||
try:
|
|
||||||
r = fn(
|
|
||||||
url,
|
|
||||||
data=payload,
|
|
||||||
headers=headers,
|
|
||||||
verify=self.verify_certificate,
|
|
||||||
timeout=self.request_timeout,
|
|
||||||
)
|
|
||||||
|
|
||||||
if r.status_code != requests.codes.ok:
|
|
||||||
# We had a problem
|
|
||||||
status_str = \
|
|
||||||
NotifyGitter.http_response_code_lookup(r.status_code)
|
|
||||||
|
|
||||||
self.logger.warning(
|
|
||||||
'Failed to send Gitter {} to {}: '
|
|
||||||
'{}error={}.'.format(
|
|
||||||
method,
|
|
||||||
url,
|
|
||||||
', ' if status_str else '',
|
|
||||||
r.status_code))
|
|
||||||
|
|
||||||
self.logger.debug(
|
|
||||||
'Response Details:\r\n{}'.format(r.content))
|
|
||||||
|
|
||||||
# Mark our failure
|
|
||||||
return (False, content)
|
|
||||||
|
|
||||||
try:
|
|
||||||
content = loads(r.content)
|
|
||||||
|
|
||||||
except (AttributeError, TypeError, ValueError):
|
|
||||||
# ValueError = r.content is Unparsable
|
|
||||||
# TypeError = r.content is None
|
|
||||||
# AttributeError = r is None
|
|
||||||
content = {}
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.ratelimit_remaining = \
|
|
||||||
int(r.headers.get('X-RateLimit-Remaining'))
|
|
||||||
self.ratelimit_reset = datetime.utcfromtimestamp(
|
|
||||||
int(r.headers.get('X-RateLimit-Reset')))
|
|
||||||
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
# This is returned if we could not retrieve this information
|
|
||||||
# gracefully accept this state and move on
|
|
||||||
pass
|
|
||||||
|
|
||||||
except requests.RequestException as e:
|
|
||||||
self.logger.warning(
|
|
||||||
'Exception received when sending Gitter {} to {}: '.
|
|
||||||
format(method, url))
|
|
||||||
self.logger.debug('Socket Exception: %s' % str(e))
|
|
||||||
|
|
||||||
# Mark our failure
|
|
||||||
return (False, content)
|
|
||||||
|
|
||||||
return (True, content)
|
|
||||||
|
|
||||||
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',
|
|
||||||
}
|
|
||||||
|
|
||||||
# Extend our parameters
|
|
||||||
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
|
|
||||||
|
|
||||||
return '{schema}://{token}/{targets}/?{params}'.format(
|
|
||||||
schema=self.secure_protocol,
|
|
||||||
token=self.pprint(self.token, privacy, safe=''),
|
|
||||||
targets='/'.join(
|
|
||||||
[NotifyGitter.quote(x, safe='') for x in self.targets]),
|
|
||||||
params=NotifyGitter.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, verify_host=False)
|
|
||||||
if not results:
|
|
||||||
# We're done early as we couldn't load the results
|
|
||||||
return results
|
|
||||||
|
|
||||||
results['token'] = NotifyGitter.unquote(results['host'])
|
|
||||||
|
|
||||||
# Get our entries; split_path() looks after unquoting content for us
|
|
||||||
# by default
|
|
||||||
results['targets'] = NotifyGitter.split_path(results['fullpath'])
|
|
||||||
|
|
||||||
# Support the 'to' variable so that we can support targets this way too
|
|
||||||
# The 'to' makes it easier to use yaml configuration
|
|
||||||
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
|
||||||
results['targets'] += NotifyGitter.parse_list(results['qsd']['to'])
|
|
||||||
|
|
||||||
# Include images with our message
|
|
||||||
results['include_image'] = \
|
|
||||||
parse_bool(results['qsd'].get('image', False))
|
|
||||||
|
|
||||||
return results
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -134,7 +130,6 @@ class NotifyGotify(NotifyBase):
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'map_to': 'fullpath',
|
'map_to': 'fullpath',
|
||||||
'default': '/',
|
'default': '/',
|
||||||
'required': True,
|
|
||||||
},
|
},
|
||||||
'port': {
|
'port': {
|
||||||
'name': _('Port'),
|
'name': _('Port'),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -30,7 +26,6 @@
|
||||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
# POSSIBILITY OF SUCH DAMAGE.
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
#
|
|
||||||
# For this plugin to work, you need to add the Maker applet to your profile
|
# For this plugin to work, you need to add the Maker applet to your profile
|
||||||
# Simply visit https://ifttt.com/search and search for 'Webhooks'
|
# Simply visit https://ifttt.com/search and search for 'Webhooks'
|
||||||
# Or if you're signed in, click here: https://ifttt.com/maker_webhooks
|
# Or if you're signed in, click here: https://ifttt.com/maker_webhooks
|
||||||
|
@ -312,6 +307,12 @@ class NotifyIFTTT(NotifyBase):
|
||||||
params=NotifyIFTTT.urlencode(params),
|
params=NotifyIFTTT.urlencode(params),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
return len(self.events)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -41,6 +37,17 @@ from ..common import NotifyType
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class JSONPayloadField:
|
||||||
|
"""
|
||||||
|
Identifies the fields available in the JSON Payload
|
||||||
|
"""
|
||||||
|
VERSION = 'version'
|
||||||
|
TITLE = 'title'
|
||||||
|
MESSAGE = 'message'
|
||||||
|
ATTACHMENTS = 'attachments'
|
||||||
|
MESSAGETYPE = 'type'
|
||||||
|
|
||||||
|
|
||||||
# Defines the method to send the notification
|
# Defines the method to send the notification
|
||||||
METHODS = (
|
METHODS = (
|
||||||
'POST',
|
'POST',
|
||||||
|
@ -69,6 +76,9 @@ class NotifyJSON(NotifyBase):
|
||||||
# A URL that takes you to the setup/help of the specific protocol
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_Custom_JSON'
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_Custom_JSON'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# Allows the user to specify the NotifyImageSize object
|
# Allows the user to specify the NotifyImageSize object
|
||||||
image_size = NotifyImageSize.XY_128
|
image_size = NotifyImageSize.XY_128
|
||||||
|
|
||||||
|
@ -76,6 +86,12 @@ class NotifyJSON(NotifyBase):
|
||||||
# local anyway
|
# local anyway
|
||||||
request_rate_per_sec = 0
|
request_rate_per_sec = 0
|
||||||
|
|
||||||
|
# Define the JSON version to place in all payloads
|
||||||
|
# Version: Major.Minor, Major is only updated if the entire schema is
|
||||||
|
# changed. If just adding new items (or removing old ones, only increment
|
||||||
|
# the Minor!
|
||||||
|
json_version = '1.0'
|
||||||
|
|
||||||
# Define object templates
|
# Define object templates
|
||||||
templates = (
|
templates = (
|
||||||
'{schema}://{host}',
|
'{schema}://{host}',
|
||||||
|
@ -246,7 +262,7 @@ class NotifyJSON(NotifyBase):
|
||||||
|
|
||||||
# Track our potential attachments
|
# Track our potential attachments
|
||||||
attachments = []
|
attachments = []
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
for attachment in attach:
|
for attachment in attach:
|
||||||
# Perform some simple error checking
|
# Perform some simple error checking
|
||||||
if not attachment:
|
if not attachment:
|
||||||
|
@ -274,20 +290,30 @@ class NotifyJSON(NotifyBase):
|
||||||
self.logger.debug('I/O Exception: %s' % str(e))
|
self.logger.debug('I/O Exception: %s' % str(e))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# prepare JSON Object
|
# Prepare JSON Object
|
||||||
payload = {
|
payload = {
|
||||||
# Version: Major.Minor, Major is only updated if the entire
|
JSONPayloadField.VERSION: self.json_version,
|
||||||
# schema is changed. If just adding new items (or removing
|
JSONPayloadField.TITLE: title,
|
||||||
# old ones, only increment the Minor!
|
JSONPayloadField.MESSAGE: body,
|
||||||
'version': '1.0',
|
JSONPayloadField.ATTACHMENTS: attachments,
|
||||||
'title': title,
|
JSONPayloadField.MESSAGETYPE: notify_type,
|
||||||
'message': body,
|
|
||||||
'attachments': attachments,
|
|
||||||
'type': notify_type,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Apply any/all payload over-rides defined
|
for key, value in self.payload_extras.items():
|
||||||
payload.update(self.payload_extras)
|
|
||||||
|
if key in payload:
|
||||||
|
if not value:
|
||||||
|
# Do not store element in payload response
|
||||||
|
del payload[key]
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Re-map
|
||||||
|
payload[value] = payload[key]
|
||||||
|
del payload[key]
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Append entry
|
||||||
|
payload[key] = value
|
||||||
|
|
||||||
auth = None
|
auth = None
|
||||||
if self.user:
|
if self.user:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -174,7 +170,6 @@ class NotifyJoin(NotifyBase):
|
||||||
'targets': {
|
'targets': {
|
||||||
'name': _('Targets'),
|
'name': _('Targets'),
|
||||||
'type': 'list:string',
|
'type': 'list:string',
|
||||||
'required': True,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -373,6 +368,12 @@ class NotifyJoin(NotifyBase):
|
||||||
for x in self.targets]),
|
for x in self.targets]),
|
||||||
params=NotifyJoin.urlencode(params))
|
params=NotifyJoin.urlencode(params))
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
return len(self.targets)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -324,6 +320,12 @@ class NotifyKavenegar(NotifyBase):
|
||||||
[NotifyKavenegar.quote(x, safe='') for x in self.targets]),
|
[NotifyKavenegar.quote(x, safe='') for x in self.targets]),
|
||||||
params=NotifyKavenegar.urlencode(params))
|
params=NotifyKavenegar.urlencode(params))
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
return len(self.targets)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -370,6 +366,7 @@ class NotifyLametric(NotifyBase):
|
||||||
|
|
||||||
# Device Mode
|
# Device Mode
|
||||||
'{schema}://{apikey}@{host}',
|
'{schema}://{apikey}@{host}',
|
||||||
|
'{schema}://{user}:{apikey}@{host}',
|
||||||
'{schema}://{apikey}@{host}:{port}',
|
'{schema}://{apikey}@{host}:{port}',
|
||||||
'{schema}://{user}:{apikey}@{host}:{port}',
|
'{schema}://{user}:{apikey}@{host}:{port}',
|
||||||
)
|
)
|
||||||
|
@ -404,7 +401,6 @@ class NotifyLametric(NotifyBase):
|
||||||
'host': {
|
'host': {
|
||||||
'name': _('Hostname'),
|
'name': _('Hostname'),
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'required': True,
|
|
||||||
},
|
},
|
||||||
'port': {
|
'port': {
|
||||||
'name': _('Port'),
|
'name': _('Port'),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -102,6 +98,7 @@ class NotifyLine(NotifyBase):
|
||||||
'targets': {
|
'targets': {
|
||||||
'name': _('Targets'),
|
'name': _('Targets'),
|
||||||
'type': 'list:string',
|
'type': 'list:string',
|
||||||
|
'required': True
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -267,6 +264,12 @@ class NotifyLine(NotifyBase):
|
||||||
params=NotifyLine.urlencode(params),
|
params=NotifyLine.urlencode(params),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
return len(self.targets)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -427,6 +423,10 @@ class NotifyMQTT(NotifyBase):
|
||||||
self.logger.debug('Socket Exception: %s' % str(e))
|
self.logger.debug('Socket Exception: %s' % str(e))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if not has_error:
|
||||||
|
# Verbal notice
|
||||||
|
self.logger.info('Sent MQTT notification')
|
||||||
|
|
||||||
return not has_error
|
return not has_error
|
||||||
|
|
||||||
def url(self, privacy=False, *args, **kwargs):
|
def url(self, privacy=False, *args, **kwargs):
|
||||||
|
@ -476,6 +476,12 @@ class NotifyMQTT(NotifyBase):
|
||||||
params=NotifyMQTT.urlencode(params),
|
params=NotifyMQTT.urlencode(params),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
return len(self.topics)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -35,50 +31,31 @@
|
||||||
# Get your (authkey) from the dashboard here:
|
# Get your (authkey) from the dashboard here:
|
||||||
# - https://world.msg91.com/user/index.php#api
|
# - https://world.msg91.com/user/index.php#api
|
||||||
#
|
#
|
||||||
|
# Note: You will need to define a template for this to work
|
||||||
|
#
|
||||||
# Get details on the API used in this plugin here:
|
# Get details on the API used in this plugin here:
|
||||||
# - https://world.msg91.com/apidoc/textsms/send-sms.php
|
# - https://docs.msg91.com/reference/send-sms
|
||||||
|
import re
|
||||||
import requests
|
import requests
|
||||||
|
from json import dumps
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from ..common import NotifyType
|
from ..common import NotifyType
|
||||||
from ..utils import is_phone_no
|
from ..utils import is_phone_no
|
||||||
from ..utils import parse_phone_no
|
from ..utils import parse_phone_no, parse_bool
|
||||||
from ..utils import validate_regex
|
from ..utils import validate_regex
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class MSG91Route:
|
class MSG91PayloadField:
|
||||||
"""
|
"""
|
||||||
Transactional SMS Routes
|
Identifies the fields available in the JSON Payload
|
||||||
route=1 for promotional, route=4 for transactional SMS.
|
|
||||||
"""
|
"""
|
||||||
PROMOTIONAL = 1
|
BODY = 'body'
|
||||||
TRANSACTIONAL = 4
|
MESSAGETYPE = 'type'
|
||||||
|
|
||||||
|
|
||||||
# Used for verification
|
# Add entries here that are reserved
|
||||||
MSG91_ROUTES = (
|
RESERVED_KEYWORDS = ('mobiles', )
|
||||||
MSG91Route.PROMOTIONAL,
|
|
||||||
MSG91Route.TRANSACTIONAL,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MSG91Country:
|
|
||||||
"""
|
|
||||||
Optional value that can be specified on the MSG91 api
|
|
||||||
"""
|
|
||||||
INTERNATIONAL = 0
|
|
||||||
USA = 1
|
|
||||||
INDIA = 91
|
|
||||||
|
|
||||||
|
|
||||||
# Used for verification
|
|
||||||
MSG91_COUNTRIES = (
|
|
||||||
MSG91Country.INTERNATIONAL,
|
|
||||||
MSG91Country.USA,
|
|
||||||
MSG91Country.INDIA,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class NotifyMSG91(NotifyBase):
|
class NotifyMSG91(NotifyBase):
|
||||||
|
@ -99,7 +76,7 @@ class NotifyMSG91(NotifyBase):
|
||||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_msg91'
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_msg91'
|
||||||
|
|
||||||
# MSG91 uses the http protocol with JSON requests
|
# MSG91 uses the http protocol with JSON requests
|
||||||
notify_url = 'https://world.msg91.com/api/sendhttp.php'
|
notify_url = 'https://control.msg91.com/api/v5/flow/'
|
||||||
|
|
||||||
# The maximum length of the body
|
# The maximum length of the body
|
||||||
body_maxlen = 160
|
body_maxlen = 160
|
||||||
|
@ -108,14 +85,24 @@ class NotifyMSG91(NotifyBase):
|
||||||
# cause any title (if defined) to get placed into the message body.
|
# cause any title (if defined) to get placed into the message body.
|
||||||
title_maxlen = 0
|
title_maxlen = 0
|
||||||
|
|
||||||
|
# Our supported mappings and component keys
|
||||||
|
component_key_re = re.compile(
|
||||||
|
r'(?P<key>((?P<id>[a-z0-9_-])?|(?P<map>body|type)))', re.IGNORECASE)
|
||||||
|
|
||||||
# Define object templates
|
# Define object templates
|
||||||
templates = (
|
templates = (
|
||||||
'{schema}://{authkey}/{targets}',
|
'{schema}://{template}@{authkey}/{targets}',
|
||||||
'{schema}://{sender}@{authkey}/{targets}',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Define our template tokens
|
# Define our template tokens
|
||||||
template_tokens = dict(NotifyBase.template_tokens, **{
|
template_tokens = dict(NotifyBase.template_tokens, **{
|
||||||
|
'template': {
|
||||||
|
'name': _('Template ID'),
|
||||||
|
'type': 'string',
|
||||||
|
'required': True,
|
||||||
|
'private': True,
|
||||||
|
'regex': (r'^[a-z0-9 _-]+$', 'i'),
|
||||||
|
},
|
||||||
'authkey': {
|
'authkey': {
|
||||||
'name': _('Authentication Key'),
|
'name': _('Authentication Key'),
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
|
@ -133,10 +120,7 @@ class NotifyMSG91(NotifyBase):
|
||||||
'targets': {
|
'targets': {
|
||||||
'name': _('Targets'),
|
'name': _('Targets'),
|
||||||
'type': 'list:string',
|
'type': 'list:string',
|
||||||
},
|
'required': True,
|
||||||
'sender': {
|
|
||||||
'name': _('Sender ID'),
|
|
||||||
'type': 'string',
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -145,21 +129,23 @@ class NotifyMSG91(NotifyBase):
|
||||||
'to': {
|
'to': {
|
||||||
'alias_of': 'targets',
|
'alias_of': 'targets',
|
||||||
},
|
},
|
||||||
'route': {
|
'short_url': {
|
||||||
'name': _('Route'),
|
'name': _('Short URL'),
|
||||||
'type': 'choice:int',
|
'type': 'bool',
|
||||||
'values': MSG91_ROUTES,
|
'default': False,
|
||||||
'default': MSG91Route.TRANSACTIONAL,
|
|
||||||
},
|
|
||||||
'country': {
|
|
||||||
'name': _('Country'),
|
|
||||||
'type': 'choice:int',
|
|
||||||
'values': MSG91_COUNTRIES,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
def __init__(self, authkey, targets=None, sender=None, route=None,
|
# Define any kwargs we're using
|
||||||
country=None, **kwargs):
|
template_kwargs = {
|
||||||
|
'template_mapping': {
|
||||||
|
'name': _('Template Mapping'),
|
||||||
|
'prefix': ':',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, template, authkey, targets=None, short_url=None,
|
||||||
|
template_mapping=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize MSG91 Object
|
Initialize MSG91 Object
|
||||||
"""
|
"""
|
||||||
|
@ -174,39 +160,20 @@ class NotifyMSG91(NotifyBase):
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
if route is None:
|
# Template ID
|
||||||
self.route = self.template_args['route']['default']
|
self.template = validate_regex(
|
||||||
|
template, *self.template_tokens['template']['regex'])
|
||||||
else:
|
if not self.template:
|
||||||
try:
|
msg = 'An invalid MSG91 Template ID ' \
|
||||||
self.route = int(route)
|
'({}) was specified.'.format(template)
|
||||||
if self.route not in MSG91_ROUTES:
|
|
||||||
# Let outer except catch thi
|
|
||||||
raise ValueError()
|
|
||||||
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
msg = 'The MSG91 route specified ({}) is invalid.'\
|
|
||||||
.format(route)
|
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
if country:
|
if short_url is None:
|
||||||
try:
|
self.short_url = self.template_args['short_url']['default']
|
||||||
self.country = int(country)
|
|
||||||
if self.country not in MSG91_COUNTRIES:
|
|
||||||
# Let outer except catch thi
|
|
||||||
raise ValueError()
|
|
||||||
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
msg = 'The MSG91 country specified ({}) is invalid.'\
|
|
||||||
.format(country)
|
|
||||||
self.logger.warning(msg)
|
|
||||||
raise TypeError(msg)
|
|
||||||
else:
|
else:
|
||||||
self.country = country
|
self.short_url = parse_bool(short_url)
|
||||||
|
|
||||||
# Store our sender
|
|
||||||
self.sender = sender
|
|
||||||
|
|
||||||
# Parse our targets
|
# Parse our targets
|
||||||
self.targets = list()
|
self.targets = list()
|
||||||
|
@ -224,6 +191,11 @@ class NotifyMSG91(NotifyBase):
|
||||||
# store valid phone number
|
# store valid phone number
|
||||||
self.targets.append(result['full'])
|
self.targets.append(result['full'])
|
||||||
|
|
||||||
|
self.template_mapping = {}
|
||||||
|
if template_mapping:
|
||||||
|
# Store our extra payload entries
|
||||||
|
self.template_mapping.update(template_mapping)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
|
@ -239,23 +211,55 @@ class NotifyMSG91(NotifyBase):
|
||||||
# Prepare our headers
|
# Prepare our headers
|
||||||
headers = {
|
headers = {
|
||||||
'User-Agent': self.app_id,
|
'User-Agent': self.app_id,
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/json',
|
||||||
|
'authkey': self.authkey,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Base
|
||||||
|
recipient_payload = {
|
||||||
|
'mobiles': None,
|
||||||
|
# Keyword Tokens
|
||||||
|
MSG91PayloadField.BODY: body,
|
||||||
|
MSG91PayloadField.MESSAGETYPE: notify_type,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prepare Recipient Payload Object
|
||||||
|
for key, value in self.template_mapping.items():
|
||||||
|
|
||||||
|
if key in RESERVED_KEYWORDS:
|
||||||
|
self.logger.warning(
|
||||||
|
'Ignoring MSG91 custom payload entry %s', key)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if key in recipient_payload:
|
||||||
|
if not value:
|
||||||
|
# Do not store element in payload response
|
||||||
|
del recipient_payload[key]
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Re-map
|
||||||
|
recipient_payload[value] = recipient_payload[key]
|
||||||
|
del recipient_payload[key]
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Append entry
|
||||||
|
recipient_payload[key] = value
|
||||||
|
|
||||||
|
# Prepare our recipients
|
||||||
|
recipients = []
|
||||||
|
for target in self.targets:
|
||||||
|
recipient = recipient_payload.copy()
|
||||||
|
recipient['mobiles'] = target
|
||||||
|
recipients.append(recipient)
|
||||||
|
|
||||||
# Prepare our payload
|
# Prepare our payload
|
||||||
payload = {
|
payload = {
|
||||||
'sender': self.sender if self.sender else self.app_id,
|
'template_id': self.template,
|
||||||
'authkey': self.authkey,
|
'short_url': 1 if self.short_url else 0,
|
||||||
'message': body,
|
|
||||||
'response': 'json',
|
|
||||||
# target phone numbers are sent with a comma delimiter
|
# target phone numbers are sent with a comma delimiter
|
||||||
'mobiles': ','.join(self.targets),
|
'recipients': recipients,
|
||||||
'route': str(self.route),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.country:
|
|
||||||
payload['country'] = str(self.country)
|
|
||||||
|
|
||||||
# Some Debug Logging
|
# Some Debug Logging
|
||||||
self.logger.debug('MSG91 POST URL: {} (cert_verify={})'.format(
|
self.logger.debug('MSG91 POST URL: {} (cert_verify={})'.format(
|
||||||
self.notify_url, self.verify_certificate))
|
self.notify_url, self.verify_certificate))
|
||||||
|
@ -267,7 +271,7 @@ class NotifyMSG91(NotifyBase):
|
||||||
try:
|
try:
|
||||||
r = requests.post(
|
r = requests.post(
|
||||||
self.notify_url,
|
self.notify_url,
|
||||||
data=payload,
|
data=dumps(payload),
|
||||||
headers=headers,
|
headers=headers,
|
||||||
verify=self.verify_certificate,
|
verify=self.verify_certificate,
|
||||||
timeout=self.request_timeout,
|
timeout=self.request_timeout,
|
||||||
|
@ -313,22 +317,32 @@ class NotifyMSG91(NotifyBase):
|
||||||
|
|
||||||
# Define any URL parameters
|
# Define any URL parameters
|
||||||
params = {
|
params = {
|
||||||
'route': str(self.route),
|
'short_url': str(self.short_url),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Extend our parameters
|
# Extend our parameters
|
||||||
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
|
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
|
||||||
|
|
||||||
if self.country:
|
# Payload body extras prefixed with a ':' sign
|
||||||
params['country'] = str(self.country)
|
# Append our payload extras into our parameters
|
||||||
|
params.update(
|
||||||
|
{':{}'.format(k): v for k, v in self.template_mapping.items()})
|
||||||
|
|
||||||
return '{schema}://{authkey}/{targets}/?{params}'.format(
|
return '{schema}://{template}@{authkey}/{targets}/?{params}'.format(
|
||||||
schema=self.secure_protocol,
|
schema=self.secure_protocol,
|
||||||
|
template=self.pprint(self.template, privacy, safe=''),
|
||||||
authkey=self.pprint(self.authkey, privacy, safe=''),
|
authkey=self.pprint(self.authkey, privacy, safe=''),
|
||||||
targets='/'.join(
|
targets='/'.join(
|
||||||
[NotifyMSG91.quote(x, safe='') for x in self.targets]),
|
[NotifyMSG91.quote(x, safe='') for x in self.targets]),
|
||||||
params=NotifyMSG91.urlencode(params))
|
params=NotifyMSG91.urlencode(params))
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
targets = len(self.targets)
|
||||||
|
return targets if targets > 0 else 1
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
@ -349,11 +363,11 @@ class NotifyMSG91(NotifyBase):
|
||||||
# The hostname is our authentication key
|
# The hostname is our authentication key
|
||||||
results['authkey'] = NotifyMSG91.unquote(results['host'])
|
results['authkey'] = NotifyMSG91.unquote(results['host'])
|
||||||
|
|
||||||
if 'route' in results['qsd'] and len(results['qsd']['route']):
|
# The template id is kept in the user field
|
||||||
results['route'] = results['qsd']['route']
|
results['template'] = NotifyMSG91.unquote(results['user'])
|
||||||
|
|
||||||
if 'country' in results['qsd'] and len(results['qsd']['country']):
|
if 'short_url' in results['qsd'] and len(results['qsd']['short_url']):
|
||||||
results['country'] = results['qsd']['country']
|
results['short_url'] = parse_bool(results['qsd']['short_url'])
|
||||||
|
|
||||||
# Support the 'to' variable so that we can support targets this way too
|
# Support the 'to' variable so that we can support targets this way too
|
||||||
# The 'to' makes it easier to use yaml configuration
|
# The 'to' makes it easier to use yaml configuration
|
||||||
|
@ -361,4 +375,10 @@ class NotifyMSG91(NotifyBase):
|
||||||
results['targets'] += \
|
results['targets'] += \
|
||||||
NotifyMSG91.parse_phone_no(results['qsd']['to'])
|
NotifyMSG91.parse_phone_no(results['qsd']['to'])
|
||||||
|
|
||||||
|
# store any additional payload extra's defined
|
||||||
|
results['template_mapping'] = {
|
||||||
|
NotifyMSG91.unquote(x): NotifyMSG91.unquote(y)
|
||||||
|
for x, y in results['qsd:'].items()
|
||||||
|
}
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -197,8 +193,7 @@ class NotifyMacOSX(NotifyBase):
|
||||||
self.logger.debug('MacOSX CMD: {}'.format(' '.join(cmd)))
|
self.logger.debug('MacOSX CMD: {}'.format(' '.join(cmd)))
|
||||||
|
|
||||||
# Send our notification
|
# Send our notification
|
||||||
output = subprocess.Popen(
|
output = subprocess.Popen(cmd)
|
||||||
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
||||||
|
|
||||||
# Wait for process to complete
|
# Wait for process to complete
|
||||||
output.wait()
|
output.wait()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -121,6 +117,9 @@ class NotifyMailgun(NotifyBase):
|
||||||
# A URL that takes you to the setup/help of the specific protocol
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_mailgun'
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_mailgun'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# Default Notify Format
|
# Default Notify Format
|
||||||
notify_format = NotifyFormat.HTML
|
notify_format = NotifyFormat.HTML
|
||||||
|
|
||||||
|
@ -152,8 +151,13 @@ class NotifyMailgun(NotifyBase):
|
||||||
'private': True,
|
'private': True,
|
||||||
'required': True,
|
'required': True,
|
||||||
},
|
},
|
||||||
|
'target_email': {
|
||||||
|
'name': _('Target Email'),
|
||||||
|
'type': 'string',
|
||||||
|
'map_to': 'targets',
|
||||||
|
},
|
||||||
'targets': {
|
'targets': {
|
||||||
'name': _('Target Emails'),
|
'name': _('Targets'),
|
||||||
'type': 'list:string',
|
'type': 'list:string',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -366,7 +370,7 @@ class NotifyMailgun(NotifyBase):
|
||||||
# Track our potential files
|
# Track our potential files
|
||||||
files = {}
|
files = {}
|
||||||
|
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
for idx, attachment in enumerate(attach):
|
for idx, attachment in enumerate(attach):
|
||||||
# Perform some simple error checking
|
# Perform some simple error checking
|
||||||
if not attachment:
|
if not attachment:
|
||||||
|
@ -627,6 +631,20 @@ class NotifyMailgun(NotifyBase):
|
||||||
safe='') for e in self.targets]),
|
safe='') for e in self.targets]),
|
||||||
params=NotifyMailgun.urlencode(params))
|
params=NotifyMailgun.urlencode(params))
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
#
|
||||||
|
# Factor batch into calculation
|
||||||
|
#
|
||||||
|
batch_size = 1 if not self.batch else self.default_batch_size
|
||||||
|
targets = len(self.targets)
|
||||||
|
if batch_size > 1:
|
||||||
|
targets = int(targets / batch_size) + \
|
||||||
|
(1 if targets % batch_size else 0)
|
||||||
|
return targets if targets > 0 else 1
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -35,6 +31,7 @@ import requests
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from json import dumps, loads
|
from json import dumps, loads
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from datetime import timezone
|
||||||
|
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from ..URLBase import PrivacyMode
|
from ..URLBase import PrivacyMode
|
||||||
|
@ -110,6 +107,10 @@ class NotifyMastodon(NotifyBase):
|
||||||
# A URL that takes you to the setup/help of the specific protocol
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_mastodon'
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_mastodon'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
|
# Allows the user to specify the NotifyImageSize object
|
||||||
# Allows the user to specify the NotifyImageSize object; this is supported
|
# Allows the user to specify the NotifyImageSize object; this is supported
|
||||||
# through the webhook
|
# through the webhook
|
||||||
image_size = NotifyImageSize.XY_128
|
image_size = NotifyImageSize.XY_128
|
||||||
|
@ -150,7 +151,7 @@ class NotifyMastodon(NotifyBase):
|
||||||
request_rate_per_sec = 0
|
request_rate_per_sec = 0
|
||||||
|
|
||||||
# For Tracking Purposes
|
# For Tracking Purposes
|
||||||
ratelimit_reset = datetime.utcnow()
|
ratelimit_reset = datetime.now(timezone.utc).replace(tzinfo=None)
|
||||||
|
|
||||||
# Default to 1000; users can send up to 1000 DM's and 2400 toot a day
|
# Default to 1000; users can send up to 1000 DM's and 2400 toot a day
|
||||||
# This value only get's adjusted if the server sets it that way
|
# This value only get's adjusted if the server sets it that way
|
||||||
|
@ -378,6 +379,13 @@ class NotifyMastodon(NotifyBase):
|
||||||
params=NotifyMastodon.urlencode(params),
|
params=NotifyMastodon.urlencode(params),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
targets = len(self.targets)
|
||||||
|
return targets if targets > 0 else 1
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,
|
def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -406,11 +414,10 @@ class NotifyMastodon(NotifyBase):
|
||||||
else:
|
else:
|
||||||
targets.add(myself)
|
targets.add(myself)
|
||||||
|
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
# We need to upload our payload first so that we can source it
|
# We need to upload our payload first so that we can source it
|
||||||
# in remaining messages
|
# in remaining messages
|
||||||
for attachment in attach:
|
for attachment in attach:
|
||||||
|
|
||||||
# Perform some simple error checking
|
# Perform some simple error checking
|
||||||
if not attachment:
|
if not attachment:
|
||||||
# We could not access the attachment
|
# We could not access the attachment
|
||||||
|
@ -570,7 +577,7 @@ class NotifyMastodon(NotifyBase):
|
||||||
_payload = deepcopy(payload)
|
_payload = deepcopy(payload)
|
||||||
_payload['media_ids'] = media_ids
|
_payload['media_ids'] = media_ids
|
||||||
|
|
||||||
if no:
|
if no or not body:
|
||||||
# strip text and replace it with the image representation
|
# strip text and replace it with the image representation
|
||||||
_payload['status'] = \
|
_payload['status'] = \
|
||||||
'{:02d}/{:02d}'.format(no + 1, len(batches))
|
'{:02d}/{:02d}'.format(no + 1, len(batches))
|
||||||
|
@ -827,7 +834,7 @@ class NotifyMastodon(NotifyBase):
|
||||||
# Mastodon server. One would hope we're on NTP and our clocks are
|
# Mastodon server. One would hope we're on NTP and our clocks are
|
||||||
# the same allowing this to role smoothly:
|
# the same allowing this to role smoothly:
|
||||||
|
|
||||||
now = datetime.utcnow()
|
now = datetime.now(timezone.utc).replace(tzinfo=None)
|
||||||
if now < self.ratelimit_reset:
|
if now < self.ratelimit_reset:
|
||||||
# We need to throttle for the difference in seconds
|
# We need to throttle for the difference in seconds
|
||||||
# We add 0.5 seconds to the end just to allow a grace
|
# We add 0.5 seconds to the end just to allow a grace
|
||||||
|
@ -885,8 +892,9 @@ class NotifyMastodon(NotifyBase):
|
||||||
# Capture rate limiting if possible
|
# Capture rate limiting if possible
|
||||||
self.ratelimit_remaining = \
|
self.ratelimit_remaining = \
|
||||||
int(r.headers.get('X-RateLimit-Remaining'))
|
int(r.headers.get('X-RateLimit-Remaining'))
|
||||||
self.ratelimit_reset = datetime.utcfromtimestamp(
|
self.ratelimit_reset = datetime.fromtimestamp(
|
||||||
int(r.headers.get('X-RateLimit-Limit')))
|
int(r.headers.get('X-RateLimit-Limit')), timezone.utc
|
||||||
|
).replace(tzinfo=None)
|
||||||
|
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
# This is returned if we could not retrieve this information
|
# This is returned if we could not retrieve this information
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -53,8 +49,11 @@ from ..utils import validate_regex
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
# Define default path
|
# Define default path
|
||||||
MATRIX_V2_API_PATH = '/_matrix/client/r0'
|
|
||||||
MATRIX_V1_WEBHOOK_PATH = '/api/v1/matrix/hook'
|
MATRIX_V1_WEBHOOK_PATH = '/api/v1/matrix/hook'
|
||||||
|
MATRIX_V2_API_PATH = '/_matrix/client/r0'
|
||||||
|
MATRIX_V3_API_PATH = '/_matrix/client/v3'
|
||||||
|
MATRIX_V3_MEDIA_PATH = '/_matrix/media/v3'
|
||||||
|
MATRIX_V2_MEDIA_PATH = '/_matrix/media/r0'
|
||||||
|
|
||||||
# Extend HTTP Error Messages
|
# Extend HTTP Error Messages
|
||||||
MATRIX_HTTP_ERROR_MAP = {
|
MATRIX_HTTP_ERROR_MAP = {
|
||||||
|
@ -88,6 +87,21 @@ MATRIX_MESSAGE_TYPES = (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MatrixVersion:
|
||||||
|
# Version 2
|
||||||
|
V2 = "2"
|
||||||
|
|
||||||
|
# Version 3
|
||||||
|
V3 = "3"
|
||||||
|
|
||||||
|
|
||||||
|
# webhook modes are placed into this list for validation purposes
|
||||||
|
MATRIX_VERSIONS = (
|
||||||
|
MatrixVersion.V2,
|
||||||
|
MatrixVersion.V3,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MatrixWebhookMode:
|
class MatrixWebhookMode:
|
||||||
# Webhook Mode is disabled
|
# Webhook Mode is disabled
|
||||||
DISABLED = "off"
|
DISABLED = "off"
|
||||||
|
@ -128,6 +142,9 @@ class NotifyMatrix(NotifyBase):
|
||||||
# The default secure protocol
|
# The default secure protocol
|
||||||
secure_protocol = 'matrixs'
|
secure_protocol = 'matrixs'
|
||||||
|
|
||||||
|
# Support Attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# A URL that takes you to the setup/help of the specific protocol
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_matrix'
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_matrix'
|
||||||
|
|
||||||
|
@ -147,6 +164,9 @@ class NotifyMatrix(NotifyBase):
|
||||||
# Throttle a wee-bit to avoid thrashing
|
# Throttle a wee-bit to avoid thrashing
|
||||||
request_rate_per_sec = 0.5
|
request_rate_per_sec = 0.5
|
||||||
|
|
||||||
|
# Our Matrix API Version
|
||||||
|
matrix_api_version = '3'
|
||||||
|
|
||||||
# How many retry attempts we'll make in the event the server asks us to
|
# How many retry attempts we'll make in the event the server asks us to
|
||||||
# throttle back.
|
# throttle back.
|
||||||
default_retries = 2
|
default_retries = 2
|
||||||
|
@ -175,7 +195,6 @@ class NotifyMatrix(NotifyBase):
|
||||||
'host': {
|
'host': {
|
||||||
'name': _('Hostname'),
|
'name': _('Hostname'),
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'required': True,
|
|
||||||
},
|
},
|
||||||
'port': {
|
'port': {
|
||||||
'name': _('Port'),
|
'name': _('Port'),
|
||||||
|
@ -194,6 +213,7 @@ class NotifyMatrix(NotifyBase):
|
||||||
},
|
},
|
||||||
'token': {
|
'token': {
|
||||||
'name': _('Access Token'),
|
'name': _('Access Token'),
|
||||||
|
'private': True,
|
||||||
'map_to': 'password',
|
'map_to': 'password',
|
||||||
},
|
},
|
||||||
'target_user': {
|
'target_user': {
|
||||||
|
@ -234,6 +254,12 @@ class NotifyMatrix(NotifyBase):
|
||||||
'values': MATRIX_WEBHOOK_MODES,
|
'values': MATRIX_WEBHOOK_MODES,
|
||||||
'default': MatrixWebhookMode.DISABLED,
|
'default': MatrixWebhookMode.DISABLED,
|
||||||
},
|
},
|
||||||
|
'version': {
|
||||||
|
'name': _('Matrix API Verion'),
|
||||||
|
'type': 'choice:string',
|
||||||
|
'values': MATRIX_VERSIONS,
|
||||||
|
'default': MatrixVersion.V3,
|
||||||
|
},
|
||||||
'msgtype': {
|
'msgtype': {
|
||||||
'name': _('Message Type'),
|
'name': _('Message Type'),
|
||||||
'type': 'choice:string',
|
'type': 'choice:string',
|
||||||
|
@ -248,7 +274,7 @@ class NotifyMatrix(NotifyBase):
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
def __init__(self, targets=None, mode=None, msgtype=None,
|
def __init__(self, targets=None, mode=None, msgtype=None, version=None,
|
||||||
include_image=False, **kwargs):
|
include_image=False, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Matrix Object
|
Initialize Matrix Object
|
||||||
|
@ -282,6 +308,14 @@ class NotifyMatrix(NotifyBase):
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
# Setup our version
|
||||||
|
self.version = self.template_args['version']['default'] \
|
||||||
|
if not isinstance(version, str) else version
|
||||||
|
if self.version not in MATRIX_VERSIONS:
|
||||||
|
msg = 'The version specified ({}) is invalid.'.format(version)
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
# Setup our message type
|
# Setup our message type
|
||||||
self.msgtype = self.template_args['msgtype']['default'] \
|
self.msgtype = self.template_args['msgtype']['default'] \
|
||||||
if not isinstance(msgtype, str) else msgtype.lower()
|
if not isinstance(msgtype, str) else msgtype.lower()
|
||||||
|
@ -521,7 +555,8 @@ class NotifyMatrix(NotifyBase):
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
def _send_server_notification(self, body, title='',
|
def _send_server_notification(self, body, title='',
|
||||||
notify_type=NotifyType.INFO, **kwargs):
|
notify_type=NotifyType.INFO, attach=None,
|
||||||
|
**kwargs):
|
||||||
"""
|
"""
|
||||||
Perform Direct Matrix Server Notification (no webhook)
|
Perform Direct Matrix Server Notification (no webhook)
|
||||||
"""
|
"""
|
||||||
|
@ -548,6 +583,13 @@ class NotifyMatrix(NotifyBase):
|
||||||
# Initiaize our error tracking
|
# Initiaize our error tracking
|
||||||
has_error = False
|
has_error = False
|
||||||
|
|
||||||
|
attachments = None
|
||||||
|
if attach and self.attachment_support:
|
||||||
|
attachments = self._send_attachments(attach)
|
||||||
|
if attachments is False:
|
||||||
|
# take an early exit
|
||||||
|
return False
|
||||||
|
|
||||||
while len(rooms) > 0:
|
while len(rooms) > 0:
|
||||||
|
|
||||||
# Get our room
|
# Get our room
|
||||||
|
@ -568,19 +610,43 @@ class NotifyMatrix(NotifyBase):
|
||||||
image_url = None if not self.include_image else \
|
image_url = None if not self.include_image else \
|
||||||
self.image_url(notify_type)
|
self.image_url(notify_type)
|
||||||
|
|
||||||
|
# Build our path
|
||||||
|
if self.version == MatrixVersion.V3:
|
||||||
|
path = '/rooms/{}/send/m.room.message/0'.format(
|
||||||
|
NotifyMatrix.quote(room_id))
|
||||||
|
|
||||||
|
else:
|
||||||
|
path = '/rooms/{}/send/m.room.message'.format(
|
||||||
|
NotifyMatrix.quote(room_id))
|
||||||
|
|
||||||
|
if self.version == MatrixVersion.V2:
|
||||||
|
#
|
||||||
|
# Attachments don't work beyond V2 at this time
|
||||||
|
#
|
||||||
if image_url:
|
if image_url:
|
||||||
# Define our payload
|
# Define our payload
|
||||||
image_payload = {
|
image_payload = {
|
||||||
'msgtype': 'm.image',
|
'msgtype': 'm.image',
|
||||||
'url': image_url,
|
'url': image_url,
|
||||||
'body': '{}'.format(notify_type if not title else title),
|
'body': '{}'.format(
|
||||||
|
notify_type if not title else title),
|
||||||
}
|
}
|
||||||
# Build our path
|
|
||||||
path = '/rooms/{}/send/m.room.message'.format(
|
|
||||||
NotifyMatrix.quote(room_id))
|
|
||||||
|
|
||||||
# Post our content
|
# Post our content
|
||||||
postokay, response = self._fetch(path, payload=image_payload)
|
postokay, response = self._fetch(
|
||||||
|
path, payload=image_payload)
|
||||||
|
if not postokay:
|
||||||
|
# Mark our failure
|
||||||
|
has_error = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
if attachments:
|
||||||
|
for attachment in attachments:
|
||||||
|
attachment['room_id'] = room_id
|
||||||
|
attachment['type'] = 'm.room.message'
|
||||||
|
|
||||||
|
postokay, response = self._fetch(
|
||||||
|
path, payload=attachment)
|
||||||
if not postokay:
|
if not postokay:
|
||||||
# Mark our failure
|
# Mark our failure
|
||||||
has_error = True
|
has_error = True
|
||||||
|
@ -615,12 +681,10 @@ class NotifyMatrix(NotifyBase):
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
# Build our path
|
|
||||||
path = '/rooms/{}/send/m.room.message'.format(
|
|
||||||
NotifyMatrix.quote(room_id))
|
|
||||||
|
|
||||||
# Post our content
|
# Post our content
|
||||||
postokay, response = self._fetch(path, payload=payload)
|
method = 'PUT' if self.version == MatrixVersion.V3 else 'POST'
|
||||||
|
postokay, response = self._fetch(
|
||||||
|
path, payload=payload, method=method)
|
||||||
if not postokay:
|
if not postokay:
|
||||||
# Notify our user
|
# Notify our user
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
|
@ -632,6 +696,62 @@ class NotifyMatrix(NotifyBase):
|
||||||
|
|
||||||
return not has_error
|
return not has_error
|
||||||
|
|
||||||
|
def _send_attachments(self, attach):
|
||||||
|
"""
|
||||||
|
Posts all of the provided attachments
|
||||||
|
"""
|
||||||
|
|
||||||
|
payloads = []
|
||||||
|
if self.version != MatrixVersion.V2:
|
||||||
|
self.logger.warning(
|
||||||
|
'Add ?v=2 to Apprise URL to support Attachments')
|
||||||
|
return next((False for a in attach if not a), [])
|
||||||
|
|
||||||
|
for attachment in attach:
|
||||||
|
if not attachment:
|
||||||
|
# invalid attachment (bad file)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not re.match(r'^image/', attachment.mimetype, re.I):
|
||||||
|
# unsuppored at this time
|
||||||
|
continue
|
||||||
|
|
||||||
|
postokay, response = \
|
||||||
|
self._fetch('/upload', attachment=attachment)
|
||||||
|
if not (postokay and isinstance(response, dict)):
|
||||||
|
# Failed to perform upload
|
||||||
|
return False
|
||||||
|
|
||||||
|
# If we get here, we'll have a response that looks like:
|
||||||
|
# {
|
||||||
|
# "content_uri": "mxc://example.com/a-unique-key"
|
||||||
|
# }
|
||||||
|
|
||||||
|
if self.version == MatrixVersion.V3:
|
||||||
|
# Prepare our payload
|
||||||
|
payloads.append({
|
||||||
|
"body": attachment.name,
|
||||||
|
"info": {
|
||||||
|
"mimetype": attachment.mimetype,
|
||||||
|
"size": len(attachment),
|
||||||
|
},
|
||||||
|
"msgtype": "m.image",
|
||||||
|
"url": response.get('content_uri'),
|
||||||
|
})
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Prepare our payload
|
||||||
|
payloads.append({
|
||||||
|
"info": {
|
||||||
|
"mimetype": attachment.mimetype,
|
||||||
|
},
|
||||||
|
"msgtype": "m.image",
|
||||||
|
"body": "tta.webp",
|
||||||
|
"url": response.get('content_uri'),
|
||||||
|
})
|
||||||
|
|
||||||
|
return payloads
|
||||||
|
|
||||||
def _register(self):
|
def _register(self):
|
||||||
"""
|
"""
|
||||||
Register with the service if possible.
|
Register with the service if possible.
|
||||||
|
@ -695,7 +815,18 @@ class NotifyMatrix(NotifyBase):
|
||||||
'user/pass combo is missing.')
|
'user/pass combo is missing.')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Prepare our Registration Payload
|
# Prepare our Authentication Payload
|
||||||
|
if self.version == MatrixVersion.V3:
|
||||||
|
payload = {
|
||||||
|
'type': 'm.login.password',
|
||||||
|
'identifier': {
|
||||||
|
'type': 'm.id.user',
|
||||||
|
'user': self.user,
|
||||||
|
},
|
||||||
|
'password': self.password,
|
||||||
|
}
|
||||||
|
|
||||||
|
else:
|
||||||
payload = {
|
payload = {
|
||||||
'type': 'm.login.password',
|
'type': 'm.login.password',
|
||||||
'user': self.user,
|
'user': self.user,
|
||||||
|
@ -970,7 +1101,8 @@ class NotifyMatrix(NotifyBase):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _fetch(self, path, payload=None, params=None, method='POST'):
|
def _fetch(self, path, payload=None, params=None, attachment=None,
|
||||||
|
method='POST'):
|
||||||
"""
|
"""
|
||||||
Wrapper to request.post() to manage it's response better and make
|
Wrapper to request.post() to manage it's response better and make
|
||||||
the send() function cleaner and easier to maintain.
|
the send() function cleaner and easier to maintain.
|
||||||
|
@ -983,6 +1115,7 @@ class NotifyMatrix(NotifyBase):
|
||||||
headers = {
|
headers = {
|
||||||
'User-Agent': self.app_id,
|
'User-Agent': self.app_id,
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.access_token is not None:
|
if self.access_token is not None:
|
||||||
|
@ -991,19 +1124,39 @@ class NotifyMatrix(NotifyBase):
|
||||||
default_port = 443 if self.secure else 80
|
default_port = 443 if self.secure else 80
|
||||||
|
|
||||||
url = \
|
url = \
|
||||||
'{schema}://{hostname}:{port}{matrix_api}{path}'.format(
|
'{schema}://{hostname}{port}'.format(
|
||||||
schema='https' if self.secure else 'http',
|
schema='https' if self.secure else 'http',
|
||||||
hostname=self.host,
|
hostname=self.host,
|
||||||
port='' if self.port is None
|
port='' if self.port is None
|
||||||
or self.port == default_port else self.port,
|
or self.port == default_port else f':{self.port}')
|
||||||
matrix_api=MATRIX_V2_API_PATH,
|
|
||||||
path=path)
|
if path == '/upload':
|
||||||
|
if self.version == MatrixVersion.V3:
|
||||||
|
url += MATRIX_V3_MEDIA_PATH + path
|
||||||
|
|
||||||
|
else:
|
||||||
|
url += MATRIX_V2_MEDIA_PATH + path
|
||||||
|
|
||||||
|
params = {'filename': attachment.name}
|
||||||
|
with open(attachment.path, 'rb') as fp:
|
||||||
|
payload = fp.read()
|
||||||
|
|
||||||
|
# Update our content type
|
||||||
|
headers['Content-Type'] = attachment.mimetype
|
||||||
|
|
||||||
|
else:
|
||||||
|
if self.version == MatrixVersion.V3:
|
||||||
|
url += MATRIX_V3_API_PATH + path
|
||||||
|
|
||||||
|
else:
|
||||||
|
url += MATRIX_V2_API_PATH + path
|
||||||
|
|
||||||
# Our response object
|
# Our response object
|
||||||
response = {}
|
response = {}
|
||||||
|
|
||||||
# fetch function
|
# fetch function
|
||||||
fn = requests.post if method == 'POST' else requests.get
|
fn = requests.post if method == 'POST' else (
|
||||||
|
requests.put if method == 'PUT' else requests.get)
|
||||||
|
|
||||||
# Define how many attempts we'll make if we get caught in a throttle
|
# Define how many attempts we'll make if we get caught in a throttle
|
||||||
# event
|
# event
|
||||||
|
@ -1024,13 +1177,16 @@ class NotifyMatrix(NotifyBase):
|
||||||
try:
|
try:
|
||||||
r = fn(
|
r = fn(
|
||||||
url,
|
url,
|
||||||
data=dumps(payload),
|
data=dumps(payload) if not attachment else payload,
|
||||||
params=params,
|
params=params,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
verify=self.verify_certificate,
|
verify=self.verify_certificate,
|
||||||
timeout=self.request_timeout,
|
timeout=self.request_timeout,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.logger.debug(
|
||||||
|
'Matrix Response: code=%d, %s' % (
|
||||||
|
r.status_code, str(r.content)))
|
||||||
response = loads(r.content)
|
response = loads(r.content)
|
||||||
|
|
||||||
if r.status_code == 429:
|
if r.status_code == 429:
|
||||||
|
@ -1094,6 +1250,13 @@ class NotifyMatrix(NotifyBase):
|
||||||
# Return; we're done
|
# Return; we're done
|
||||||
return (False, response)
|
return (False, response)
|
||||||
|
|
||||||
|
except (OSError, IOError) as e:
|
||||||
|
self.logger.warning(
|
||||||
|
'An I/O error occurred while reading {}.'.format(
|
||||||
|
attachment.name if attachment else 'unknown file'))
|
||||||
|
self.logger.debug('I/O Exception: %s' % str(e))
|
||||||
|
return (False, {})
|
||||||
|
|
||||||
return (True, response)
|
return (True, response)
|
||||||
|
|
||||||
# If we get here, we ran out of retries
|
# If we get here, we ran out of retries
|
||||||
|
@ -1160,6 +1323,7 @@ class NotifyMatrix(NotifyBase):
|
||||||
params = {
|
params = {
|
||||||
'image': 'yes' if self.include_image else 'no',
|
'image': 'yes' if self.include_image else 'no',
|
||||||
'mode': self.mode,
|
'mode': self.mode,
|
||||||
|
'version': self.version,
|
||||||
'msgtype': self.msgtype,
|
'msgtype': self.msgtype,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1196,6 +1360,13 @@ class NotifyMatrix(NotifyBase):
|
||||||
params=NotifyMatrix.urlencode(params),
|
params=NotifyMatrix.urlencode(params),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
targets = len(self.rooms)
|
||||||
|
return targets if targets > 0 else 1
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
@ -1250,6 +1421,14 @@ class NotifyMatrix(NotifyBase):
|
||||||
if 'token' in results['qsd'] and len(results['qsd']['token']):
|
if 'token' in results['qsd'] and len(results['qsd']['token']):
|
||||||
results['password'] = NotifyMatrix.unquote(results['qsd']['token'])
|
results['password'] = NotifyMatrix.unquote(results['qsd']['token'])
|
||||||
|
|
||||||
|
# Support the use of the version= or v= keyword
|
||||||
|
if 'version' in results['qsd'] and len(results['qsd']['version']):
|
||||||
|
results['version'] = \
|
||||||
|
NotifyMatrix.unquote(results['qsd']['version'])
|
||||||
|
|
||||||
|
elif 'v' in results['qsd'] and len(results['qsd']['v']):
|
||||||
|
results['version'] = NotifyMatrix.unquote(results['qsd']['v'])
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -1259,7 +1438,7 @@ class NotifyMatrix(NotifyBase):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
result = re.match(
|
result = re.match(
|
||||||
r'^https?://webhooks\.t2bot\.io/api/v1/matrix/hook/'
|
r'^https?://webhooks\.t2bot\.io/api/v[0-9]+/matrix/hook/'
|
||||||
r'(?P<webhook_token>[A-Z0-9_-]+)/?'
|
r'(?P<webhook_token>[A-Z0-9_-]+)/?'
|
||||||
r'(?P<params>\?.+)?$', url, re.I)
|
r'(?P<params>\?.+)?$', url, re.I)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -91,11 +87,11 @@ class NotifyMattermost(NotifyBase):
|
||||||
# Define object templates
|
# Define object templates
|
||||||
templates = (
|
templates = (
|
||||||
'{schema}://{host}/{token}',
|
'{schema}://{host}/{token}',
|
||||||
'{schema}://{host}/{token}:{port}',
|
'{schema}://{host}:{port}/{token}',
|
||||||
|
'{schema}://{host}/{fullpath}/{token}',
|
||||||
|
'{schema}://{host}:{port}/{fullpath}/{token}',
|
||||||
'{schema}://{botname}@{host}/{token}',
|
'{schema}://{botname}@{host}/{token}',
|
||||||
'{schema}://{botname}@{host}:{port}/{token}',
|
'{schema}://{botname}@{host}:{port}/{token}',
|
||||||
'{schema}://{host}/{fullpath}/{token}',
|
|
||||||
'{schema}://{host}/{fullpath}{token}:{port}',
|
|
||||||
'{schema}://{botname}@{host}/{fullpath}/{token}',
|
'{schema}://{botname}@{host}/{fullpath}/{token}',
|
||||||
'{schema}://{botname}@{host}:{port}/{fullpath}/{token}',
|
'{schema}://{botname}@{host}:{port}/{fullpath}/{token}',
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -311,6 +307,13 @@ class NotifyMessageBird(NotifyBase):
|
||||||
[NotifyMessageBird.quote(x, safe='') for x in self.targets]),
|
[NotifyMessageBird.quote(x, safe='') for x in self.targets]),
|
||||||
params=NotifyMessageBird.urlencode(params))
|
params=NotifyMessageBird.urlencode(params))
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
targets = len(self.targets)
|
||||||
|
return targets if targets > 0 else 1
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -29,6 +25,7 @@
|
||||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
# 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
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
# POSSIBILITY OF SUCH DAMAGE.
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
# 1. visit https://misskey-hub.net/ and see what it's all about if you want.
|
# 1. visit https://misskey-hub.net/ and see what it's all about if you want.
|
||||||
# Choose a service you want to create an account on from here:
|
# Choose a service you want to create an account on from here:
|
||||||
# https://misskey-hub.net/en/instances.html
|
# https://misskey-hub.net/en/instances.html
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -67,6 +63,8 @@ class NotifyNextcloud(NotifyBase):
|
||||||
|
|
||||||
# Define object templates
|
# Define object templates
|
||||||
templates = (
|
templates = (
|
||||||
|
'{schema}://{host}/{targets}',
|
||||||
|
'{schema}://{host}:{port}/{targets}',
|
||||||
'{schema}://{user}:{password}@{host}/{targets}',
|
'{schema}://{user}:{password}@{host}/{targets}',
|
||||||
'{schema}://{user}:{password}@{host}:{port}/{targets}',
|
'{schema}://{user}:{password}@{host}:{port}/{targets}',
|
||||||
)
|
)
|
||||||
|
@ -116,6 +114,10 @@ class NotifyNextcloud(NotifyBase):
|
||||||
'min': 1,
|
'min': 1,
|
||||||
'default': 21,
|
'default': 21,
|
||||||
},
|
},
|
||||||
|
'url_prefix': {
|
||||||
|
'name': _('URL Prefix'),
|
||||||
|
'type': 'string',
|
||||||
|
},
|
||||||
'to': {
|
'to': {
|
||||||
'alias_of': 'targets',
|
'alias_of': 'targets',
|
||||||
},
|
},
|
||||||
|
@ -129,17 +131,15 @@ class NotifyNextcloud(NotifyBase):
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, targets=None, version=None, headers=None, **kwargs):
|
def __init__(self, targets=None, version=None, headers=None,
|
||||||
|
url_prefix=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Nextcloud Object
|
Initialize Nextcloud Object
|
||||||
"""
|
"""
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
# Store our targets
|
||||||
self.targets = parse_list(targets)
|
self.targets = parse_list(targets)
|
||||||
if len(self.targets) == 0:
|
|
||||||
msg = 'At least one Nextcloud target user must be specified.'
|
|
||||||
self.logger.warning(msg)
|
|
||||||
raise TypeError(msg)
|
|
||||||
|
|
||||||
self.version = self.template_args['version']['default']
|
self.version = self.template_args['version']['default']
|
||||||
if version is not None:
|
if version is not None:
|
||||||
|
@ -155,6 +155,10 @@ class NotifyNextcloud(NotifyBase):
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
# Support URL Prefix
|
||||||
|
self.url_prefix = '' if not url_prefix \
|
||||||
|
else url_prefix.strip('/')
|
||||||
|
|
||||||
self.headers = {}
|
self.headers = {}
|
||||||
if headers:
|
if headers:
|
||||||
# Store our extra headers
|
# Store our extra headers
|
||||||
|
@ -167,6 +171,11 @@ class NotifyNextcloud(NotifyBase):
|
||||||
Perform Nextcloud Notification
|
Perform Nextcloud Notification
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if len(self.targets) == 0:
|
||||||
|
# There were no services to notify
|
||||||
|
self.logger.warning('There were no Nextcloud targets to notify.')
|
||||||
|
return False
|
||||||
|
|
||||||
# Prepare our Header
|
# Prepare our Header
|
||||||
headers = {
|
headers = {
|
||||||
'User-Agent': self.app_id,
|
'User-Agent': self.app_id,
|
||||||
|
@ -198,11 +207,11 @@ class NotifyNextcloud(NotifyBase):
|
||||||
auth = (self.user, self.password)
|
auth = (self.user, self.password)
|
||||||
|
|
||||||
# Nextcloud URL based on version used
|
# Nextcloud URL based on version used
|
||||||
notify_url = '{schema}://{host}/ocs/v2.php/'\
|
notify_url = '{schema}://{host}/{url_prefix}/ocs/v2.php/'\
|
||||||
'apps/admin_notifications/' \
|
'apps/admin_notifications/' \
|
||||||
'api/v1/notifications/{target}' \
|
'api/v1/notifications/{target}' \
|
||||||
if self.version < 21 else \
|
if self.version < 21 else \
|
||||||
'{schema}://{host}/ocs/v2.php/'\
|
'{schema}://{host}/{url_prefix}/ocs/v2.php/'\
|
||||||
'apps/notifications/'\
|
'apps/notifications/'\
|
||||||
'api/v2/admin_notifications/{target}'
|
'api/v2/admin_notifications/{target}'
|
||||||
|
|
||||||
|
@ -210,6 +219,7 @@ class NotifyNextcloud(NotifyBase):
|
||||||
schema='https' if self.secure else 'http',
|
schema='https' if self.secure else 'http',
|
||||||
host=self.host if not isinstance(self.port, int)
|
host=self.host if not isinstance(self.port, int)
|
||||||
else '{}:{}'.format(self.host, self.port),
|
else '{}:{}'.format(self.host, self.port),
|
||||||
|
url_prefix=self.url_prefix,
|
||||||
target=target,
|
target=target,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -279,6 +289,9 @@ class NotifyNextcloud(NotifyBase):
|
||||||
# Set our version
|
# Set our version
|
||||||
params['version'] = str(self.version)
|
params['version'] = str(self.version)
|
||||||
|
|
||||||
|
if self.url_prefix:
|
||||||
|
params['url_prefix'] = self.url_prefix
|
||||||
|
|
||||||
# Extend our parameters
|
# Extend our parameters
|
||||||
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
|
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
|
||||||
|
|
||||||
|
@ -312,6 +325,13 @@ class NotifyNextcloud(NotifyBase):
|
||||||
params=NotifyNextcloud.urlencode(params),
|
params=NotifyNextcloud.urlencode(params),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
targets = len(self.targets)
|
||||||
|
return targets if targets else 1
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
@ -339,6 +359,12 @@ class NotifyNextcloud(NotifyBase):
|
||||||
results['version'] = \
|
results['version'] = \
|
||||||
NotifyNextcloud.unquote(results['qsd']['version'])
|
NotifyNextcloud.unquote(results['qsd']['version'])
|
||||||
|
|
||||||
|
# Support URL Prefixes
|
||||||
|
if 'url_prefix' in results['qsd'] \
|
||||||
|
and len(results['qsd']['url_prefix']):
|
||||||
|
results['url_prefix'] = \
|
||||||
|
NotifyNextcloud.unquote(results['qsd']['url_prefix'])
|
||||||
|
|
||||||
# Add our headers that the user can potentially over-ride if they wish
|
# Add our headers that the user can potentially over-ride if they wish
|
||||||
# to to our returned result set and tidy entries by unquoting them
|
# to to our returned result set and tidy entries by unquoting them
|
||||||
results['headers'] = {
|
results['headers'] = {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -96,6 +92,11 @@ class NotifyNextcloudTalk(NotifyBase):
|
||||||
'private': True,
|
'private': True,
|
||||||
'required': True,
|
'required': True,
|
||||||
},
|
},
|
||||||
|
'target_room_id': {
|
||||||
|
'name': _('Room ID'),
|
||||||
|
'type': 'string',
|
||||||
|
'map_to': 'targets',
|
||||||
|
},
|
||||||
'targets': {
|
'targets': {
|
||||||
'name': _('Targets'),
|
'name': _('Targets'),
|
||||||
'type': 'list:string',
|
'type': 'list:string',
|
||||||
|
@ -103,6 +104,14 @@ class NotifyNextcloudTalk(NotifyBase):
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Define our template arguments
|
||||||
|
template_args = dict(NotifyBase.template_args, **{
|
||||||
|
'url_prefix': {
|
||||||
|
'name': _('URL Prefix'),
|
||||||
|
'type': 'string',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
# Define any kwargs we're using
|
# Define any kwargs we're using
|
||||||
template_kwargs = {
|
template_kwargs = {
|
||||||
'headers': {
|
'headers': {
|
||||||
|
@ -111,7 +120,7 @@ class NotifyNextcloudTalk(NotifyBase):
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, targets=None, headers=None, **kwargs):
|
def __init__(self, targets=None, headers=None, url_prefix=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Nextcloud Talk Object
|
Initialize Nextcloud Talk Object
|
||||||
"""
|
"""
|
||||||
|
@ -122,11 +131,12 @@ class NotifyNextcloudTalk(NotifyBase):
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
# Store our targets
|
||||||
self.targets = parse_list(targets)
|
self.targets = parse_list(targets)
|
||||||
if len(self.targets) == 0:
|
|
||||||
msg = 'At least one Nextcloud Talk Room ID must be specified.'
|
# Support URL Prefix
|
||||||
self.logger.warning(msg)
|
self.url_prefix = '' if not url_prefix \
|
||||||
raise TypeError(msg)
|
else url_prefix.strip('/')
|
||||||
|
|
||||||
self.headers = {}
|
self.headers = {}
|
||||||
if headers:
|
if headers:
|
||||||
|
@ -140,6 +150,12 @@ class NotifyNextcloudTalk(NotifyBase):
|
||||||
Perform Nextcloud Talk Notification
|
Perform Nextcloud Talk Notification
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if len(self.targets) == 0:
|
||||||
|
# There were no services to notify
|
||||||
|
self.logger.warning(
|
||||||
|
'There were no Nextcloud Talk targets to notify.')
|
||||||
|
return False
|
||||||
|
|
||||||
# Prepare our Header
|
# Prepare our Header
|
||||||
headers = {
|
headers = {
|
||||||
'User-Agent': self.app_id,
|
'User-Agent': self.app_id,
|
||||||
|
@ -171,13 +187,14 @@ class NotifyNextcloudTalk(NotifyBase):
|
||||||
}
|
}
|
||||||
|
|
||||||
# Nextcloud Talk URL
|
# Nextcloud Talk URL
|
||||||
notify_url = '{schema}://{host}'\
|
notify_url = '{schema}://{host}/{url_prefix}'\
|
||||||
'/ocs/v2.php/apps/spreed/api/v1/chat/{target}'
|
'/ocs/v2.php/apps/spreed/api/v1/chat/{target}'
|
||||||
|
|
||||||
notify_url = notify_url.format(
|
notify_url = notify_url.format(
|
||||||
schema='https' if self.secure else 'http',
|
schema='https' if self.secure else 'http',
|
||||||
host=self.host if not isinstance(self.port, int)
|
host=self.host if not isinstance(self.port, int)
|
||||||
else '{}:{}'.format(self.host, self.port),
|
else '{}:{}'.format(self.host, self.port),
|
||||||
|
url_prefix=self.url_prefix,
|
||||||
target=target,
|
target=target,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -200,7 +217,8 @@ class NotifyNextcloudTalk(NotifyBase):
|
||||||
verify=self.verify_certificate,
|
verify=self.verify_certificate,
|
||||||
timeout=self.request_timeout,
|
timeout=self.request_timeout,
|
||||||
)
|
)
|
||||||
if r.status_code != requests.codes.created:
|
if r.status_code not in (
|
||||||
|
requests.codes.created, requests.codes.ok):
|
||||||
# We had a problem
|
# We had a problem
|
||||||
status_str = \
|
status_str = \
|
||||||
NotifyNextcloudTalk.http_response_code_lookup(
|
NotifyNextcloudTalk.http_response_code_lookup(
|
||||||
|
@ -240,6 +258,14 @@ class NotifyNextcloudTalk(NotifyBase):
|
||||||
Returns the URL built dynamically based on specified arguments.
|
Returns the URL built dynamically based on specified arguments.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Our default set of parameters
|
||||||
|
params = self.url_parameters(privacy=privacy, *args, **kwargs)
|
||||||
|
|
||||||
|
# Append our headers into our parameters
|
||||||
|
params.update({'+{}'.format(k): v for k, v in self.headers.items()})
|
||||||
|
if self.url_prefix:
|
||||||
|
params['url_prefix'] = self.url_prefix
|
||||||
|
|
||||||
# Determine Authentication
|
# Determine Authentication
|
||||||
auth = '{user}:{password}@'.format(
|
auth = '{user}:{password}@'.format(
|
||||||
user=NotifyNextcloudTalk.quote(self.user, safe=''),
|
user=NotifyNextcloudTalk.quote(self.user, safe=''),
|
||||||
|
@ -249,7 +275,7 @@ class NotifyNextcloudTalk(NotifyBase):
|
||||||
|
|
||||||
default_port = 443 if self.secure else 80
|
default_port = 443 if self.secure else 80
|
||||||
|
|
||||||
return '{schema}://{auth}{hostname}{port}/{targets}' \
|
return '{schema}://{auth}{hostname}{port}/{targets}?{params}' \
|
||||||
.format(
|
.format(
|
||||||
schema=self.secure_protocol
|
schema=self.secure_protocol
|
||||||
if self.secure else self.protocol,
|
if self.secure else self.protocol,
|
||||||
|
@ -261,8 +287,16 @@ class NotifyNextcloudTalk(NotifyBase):
|
||||||
else ':{}'.format(self.port),
|
else ':{}'.format(self.port),
|
||||||
targets='/'.join([NotifyNextcloudTalk.quote(x)
|
targets='/'.join([NotifyNextcloudTalk.quote(x)
|
||||||
for x in self.targets]),
|
for x in self.targets]),
|
||||||
|
params=NotifyNextcloudTalk.urlencode(params),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
targets = len(self.targets)
|
||||||
|
return targets if targets else 1
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
@ -280,6 +314,12 @@ class NotifyNextcloudTalk(NotifyBase):
|
||||||
results['targets'] = \
|
results['targets'] = \
|
||||||
NotifyNextcloudTalk.split_path(results['fullpath'])
|
NotifyNextcloudTalk.split_path(results['fullpath'])
|
||||||
|
|
||||||
|
# Support URL Prefixes
|
||||||
|
if 'url_prefix' in results['qsd'] \
|
||||||
|
and len(results['qsd']['url_prefix']):
|
||||||
|
results['url_prefix'] = \
|
||||||
|
NotifyNextcloudTalk.unquote(results['qsd']['url_prefix'])
|
||||||
|
|
||||||
# Add our headers that the user can potentially over-ride if they wish
|
# Add our headers that the user can potentially over-ride if they wish
|
||||||
# to to our returned result set and tidy entries by unquoting them
|
# to to our returned result set and tidy entries by unquoting them
|
||||||
results['headers'] = {
|
results['headers'] = {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -112,12 +108,12 @@ class NotifyNotica(NotifyBase):
|
||||||
'{schema}://{user}:{password}@{host}:{port}/{token}',
|
'{schema}://{user}:{password}@{host}:{port}/{token}',
|
||||||
|
|
||||||
# Self-hosted notica servers (with custom path)
|
# Self-hosted notica servers (with custom path)
|
||||||
'{schema}://{host}{path}{token}',
|
'{schema}://{host}{path}/{token}',
|
||||||
'{schema}://{host}:{port}{path}{token}',
|
'{schema}://{host}:{port}/{path}/{token}',
|
||||||
'{schema}://{user}@{host}{path}{token}',
|
'{schema}://{user}@{host}/{path}/{token}',
|
||||||
'{schema}://{user}@{host}:{port}{path}{token}',
|
'{schema}://{user}@{host}:{port}{path}/{token}',
|
||||||
'{schema}://{user}:{password}@{host}{path}{token}',
|
'{schema}://{user}:{password}@{host}{path}/{token}',
|
||||||
'{schema}://{user}:{password}@{host}:{port}{path}{token}',
|
'{schema}://{user}:{password}@{host}:{port}/{path}/{token}',
|
||||||
)
|
)
|
||||||
|
|
||||||
# Define our template tokens
|
# Define our template tokens
|
||||||
|
|
472
lib/apprise/plugins/NotifyNotifiarr.py
Normal file
472
lib/apprise/plugins/NotifyNotifiarr.py
Normal file
|
@ -0,0 +1,472 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 2-Clause License
|
||||||
|
#
|
||||||
|
# Apprise - Push Notification Library.
|
||||||
|
# Copyright (c) 2023, 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 re
|
||||||
|
import requests
|
||||||
|
from json import dumps
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
|
from .NotifyBase import NotifyBase
|
||||||
|
from ..common import NotifyType
|
||||||
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
from ..common import NotifyImageSize
|
||||||
|
from ..utils import parse_list, parse_bool
|
||||||
|
from ..utils import validate_regex
|
||||||
|
|
||||||
|
# Used to break path apart into list of channels
|
||||||
|
CHANNEL_LIST_DELIM = re.compile(r'[ \t\r\n,#\\/]+')
|
||||||
|
|
||||||
|
CHANNEL_REGEX = re.compile(
|
||||||
|
r'^\s*(\#|\%35)?(?P<channel>[0-9]+)', re.I)
|
||||||
|
|
||||||
|
# For API Details see:
|
||||||
|
# https://notifiarr.wiki/Client/Installation
|
||||||
|
|
||||||
|
# Another good example:
|
||||||
|
# https://notifiarr.wiki/en/Website/ \
|
||||||
|
# Integrations/Passthrough#payload-example-1
|
||||||
|
|
||||||
|
|
||||||
|
class NotifyNotifiarr(NotifyBase):
|
||||||
|
"""
|
||||||
|
A wrapper for Notifiarr Notifications
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The default descriptive name associated with the Notification
|
||||||
|
service_name = 'Notifiarr'
|
||||||
|
|
||||||
|
# The services URL
|
||||||
|
service_url = 'https://notifiarr.com/'
|
||||||
|
|
||||||
|
# The default secure protocol
|
||||||
|
secure_protocol = 'notifiarr'
|
||||||
|
|
||||||
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_notifiarr'
|
||||||
|
|
||||||
|
# The Notification URL
|
||||||
|
notify_url = 'https://notifiarr.com/api/v1/notification/apprise'
|
||||||
|
|
||||||
|
# Notifiarr Throttling (knowing in advance reduces 429 responses)
|
||||||
|
# define('NOTIFICATION_LIMIT_SECOND_USER', 5);
|
||||||
|
# define('NOTIFICATION_LIMIT_SECOND_PATRON', 15);
|
||||||
|
|
||||||
|
# Throttle requests ever so slightly
|
||||||
|
request_rate_per_sec = 0.04
|
||||||
|
|
||||||
|
# Allows the user to specify the NotifyImageSize object
|
||||||
|
image_size = NotifyImageSize.XY_256
|
||||||
|
|
||||||
|
# Define object templates
|
||||||
|
templates = (
|
||||||
|
'{schema}://{apikey}/{targets}',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Define our apikeys; these are the minimum apikeys required required to
|
||||||
|
# be passed into this function (as arguments). The syntax appends any
|
||||||
|
# previously defined in the base package and builds onto them
|
||||||
|
template_tokens = dict(NotifyBase.template_tokens, **{
|
||||||
|
'apikey': {
|
||||||
|
'name': _('Token'),
|
||||||
|
'type': 'string',
|
||||||
|
'required': True,
|
||||||
|
'private': True,
|
||||||
|
},
|
||||||
|
'target_channel': {
|
||||||
|
'name': _('Target Channel'),
|
||||||
|
'type': 'string',
|
||||||
|
'prefix': '#',
|
||||||
|
'map_to': 'targets',
|
||||||
|
},
|
||||||
|
'targets': {
|
||||||
|
'name': _('Targets'),
|
||||||
|
'type': 'list:string',
|
||||||
|
'required': True,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
# Define our template arguments
|
||||||
|
template_args = dict(NotifyBase.template_args, **{
|
||||||
|
'key': {
|
||||||
|
'alias_of': 'apikey',
|
||||||
|
},
|
||||||
|
'apikey': {
|
||||||
|
'alias_of': 'apikey',
|
||||||
|
},
|
||||||
|
'discord_user': {
|
||||||
|
'name': _('Ping Discord User'),
|
||||||
|
'type': 'int',
|
||||||
|
},
|
||||||
|
'discord_role': {
|
||||||
|
'name': _('Ping Discord Role'),
|
||||||
|
'type': 'int',
|
||||||
|
},
|
||||||
|
'event': {
|
||||||
|
'name': _('Discord Event ID'),
|
||||||
|
'type': 'int',
|
||||||
|
},
|
||||||
|
'image': {
|
||||||
|
'name': _('Include Image'),
|
||||||
|
'type': 'bool',
|
||||||
|
'default': False,
|
||||||
|
'map_to': 'include_image',
|
||||||
|
},
|
||||||
|
'source': {
|
||||||
|
'name': _('Source'),
|
||||||
|
'type': 'string',
|
||||||
|
},
|
||||||
|
'from': {
|
||||||
|
'alias_of': 'source'
|
||||||
|
},
|
||||||
|
'to': {
|
||||||
|
'alias_of': 'targets',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
def __init__(self, apikey=None, include_image=None,
|
||||||
|
discord_user=None, discord_role=None,
|
||||||
|
event=None, targets=None, source=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Initialize Notifiarr Object
|
||||||
|
|
||||||
|
headers can be a dictionary of key/value pairs that you want to
|
||||||
|
additionally include as part of the server headers to post with
|
||||||
|
|
||||||
|
"""
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
self.apikey = apikey
|
||||||
|
if not self.apikey:
|
||||||
|
msg = 'An invalid Notifiarr APIKey ' \
|
||||||
|
'({}) was specified.'.format(apikey)
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
# Place a thumbnail image inline with the message body
|
||||||
|
self.include_image = include_image \
|
||||||
|
if isinstance(include_image, bool) \
|
||||||
|
else self.template_args['image']['default']
|
||||||
|
|
||||||
|
# Set up our user if specified
|
||||||
|
self.discord_user = 0
|
||||||
|
if discord_user:
|
||||||
|
try:
|
||||||
|
self.discord_user = int(discord_user)
|
||||||
|
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
msg = 'An invalid Notifiarr User ID ' \
|
||||||
|
'({}) was specified.'.format(discord_user)
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
# Set up our role if specified
|
||||||
|
self.discord_role = 0
|
||||||
|
if discord_role:
|
||||||
|
try:
|
||||||
|
self.discord_role = int(discord_role)
|
||||||
|
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
msg = 'An invalid Notifiarr Role ID ' \
|
||||||
|
'({}) was specified.'.format(discord_role)
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
# Prepare our source (if set)
|
||||||
|
self.source = validate_regex(source)
|
||||||
|
|
||||||
|
self.event = 0
|
||||||
|
if event:
|
||||||
|
try:
|
||||||
|
self.event = int(event)
|
||||||
|
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
msg = 'An invalid Notifiarr Discord Event ID ' \
|
||||||
|
'({}) was specified.'.format(event)
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
# Prepare our targets
|
||||||
|
self.targets = {
|
||||||
|
'channels': [],
|
||||||
|
'invalid': [],
|
||||||
|
}
|
||||||
|
|
||||||
|
for target in parse_list(targets):
|
||||||
|
result = CHANNEL_REGEX.match(target)
|
||||||
|
if result:
|
||||||
|
# Store role information
|
||||||
|
self.targets['channels'].append(int(result.group('channel')))
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.logger.warning(
|
||||||
|
'Dropped invalid channel '
|
||||||
|
'({}) specified.'.format(target),
|
||||||
|
)
|
||||||
|
self.targets['invalid'].append(target)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
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.source:
|
||||||
|
params['source'] = self.source
|
||||||
|
|
||||||
|
if self.discord_user:
|
||||||
|
params['discord_user'] = self.discord_user
|
||||||
|
|
||||||
|
if self.discord_role:
|
||||||
|
params['discord_role'] = self.discord_role
|
||||||
|
|
||||||
|
if self.event:
|
||||||
|
params['event'] = self.event
|
||||||
|
|
||||||
|
# Extend our parameters
|
||||||
|
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
|
||||||
|
|
||||||
|
return '{schema}://{apikey}' \
|
||||||
|
'/{targets}?{params}'.format(
|
||||||
|
schema=self.secure_protocol,
|
||||||
|
apikey=self.pprint(self.apikey, privacy, safe=''),
|
||||||
|
targets='/'.join(
|
||||||
|
[NotifyNotifiarr.quote(x, safe='+#@') for x in chain(
|
||||||
|
# Channels
|
||||||
|
['#{}'.format(x) for x in self.targets['channels']],
|
||||||
|
# Pass along the same invalid entries as were provided
|
||||||
|
self.targets['invalid'],
|
||||||
|
)]),
|
||||||
|
params=NotifyNotifiarr.urlencode(params),
|
||||||
|
)
|
||||||
|
|
||||||
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
|
"""
|
||||||
|
Perform Notifiarr Notification
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not self.targets['channels']:
|
||||||
|
# There were no services to notify
|
||||||
|
self.logger.warning(
|
||||||
|
'There were no Notifiarr channels to notify.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
# No error to start with
|
||||||
|
has_error = False
|
||||||
|
|
||||||
|
# Acquire image_url
|
||||||
|
image_url = self.image_url(notify_type)
|
||||||
|
|
||||||
|
for idx, channel in enumerate(self.targets['channels']):
|
||||||
|
# prepare Notifiarr Object
|
||||||
|
payload = {
|
||||||
|
'source': self.source if self.source else self.app_id,
|
||||||
|
'type': notify_type,
|
||||||
|
'notification': {
|
||||||
|
'update': True if self.event else False,
|
||||||
|
'name': self.app_id,
|
||||||
|
'event': str(self.event)
|
||||||
|
if self.event else "",
|
||||||
|
},
|
||||||
|
'discord': {
|
||||||
|
'color': self.color(notify_type),
|
||||||
|
'ping': {
|
||||||
|
'pingUser': self.discord_user
|
||||||
|
if not idx and self.discord_user else 0,
|
||||||
|
'pingRole': self.discord_role
|
||||||
|
if not idx and self.discord_role else 0,
|
||||||
|
},
|
||||||
|
'text': {
|
||||||
|
'title': title,
|
||||||
|
'content': '',
|
||||||
|
'description': body,
|
||||||
|
'footer': self.app_desc,
|
||||||
|
},
|
||||||
|
'ids': {
|
||||||
|
'channel': channel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.include_image and image_url:
|
||||||
|
payload['discord']['text']['icon'] = image_url
|
||||||
|
payload['discord']['images'] = {
|
||||||
|
'thumbnail': image_url,
|
||||||
|
}
|
||||||
|
|
||||||
|
if not self._send(payload):
|
||||||
|
has_error = True
|
||||||
|
|
||||||
|
return not has_error
|
||||||
|
|
||||||
|
def _send(self, payload):
|
||||||
|
"""
|
||||||
|
Send notification
|
||||||
|
"""
|
||||||
|
self.logger.debug('Notifiarr POST URL: %s (cert_verify=%r)' % (
|
||||||
|
self.notify_url, self.verify_certificate,
|
||||||
|
))
|
||||||
|
self.logger.debug('Notifiarr Payload: %s' % str(payload))
|
||||||
|
|
||||||
|
# Prepare HTTP Headers
|
||||||
|
headers = {
|
||||||
|
'User-Agent': self.app_id,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'text/plain',
|
||||||
|
'X-api-Key': self.apikey,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Always call throttle before any remote server i/o is made
|
||||||
|
self.throttle()
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.post(
|
||||||
|
self.notify_url,
|
||||||
|
data=dumps(payload),
|
||||||
|
headers=headers,
|
||||||
|
verify=self.verify_certificate,
|
||||||
|
timeout=self.request_timeout,
|
||||||
|
)
|
||||||
|
if r.status_code < 200 or r.status_code >= 300:
|
||||||
|
# We had a problem
|
||||||
|
status_str = \
|
||||||
|
NotifyNotifiarr.http_response_code_lookup(r.status_code)
|
||||||
|
|
||||||
|
self.logger.warning(
|
||||||
|
'Failed to send Notifiarr %s notification: '
|
||||||
|
'%serror=%s.',
|
||||||
|
status_str,
|
||||||
|
', ' if status_str else '',
|
||||||
|
str(r.status_code))
|
||||||
|
|
||||||
|
self.logger.debug('Response Details:\r\n{}'.format(r.content))
|
||||||
|
|
||||||
|
# Return; we're done
|
||||||
|
return False
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.logger.info('Sent Notifiarr notification.')
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
self.logger.warning(
|
||||||
|
'A Connection error occurred sending Notifiarr '
|
||||||
|
'Chat notification to %s.' % self.host)
|
||||||
|
self.logger.debug('Socket Exception: %s' % str(e))
|
||||||
|
|
||||||
|
# Return; we're done
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
targets = len(self.targets['channels']) + len(self.targets['invalid'])
|
||||||
|
return targets if targets > 0 else 1
|
||||||
|
|
||||||
|
@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, verify_host=False)
|
||||||
|
if not results:
|
||||||
|
# We're done early as we couldn't load the results
|
||||||
|
return results
|
||||||
|
|
||||||
|
# Get channels
|
||||||
|
results['targets'] = NotifyNotifiarr.split_path(results['fullpath'])
|
||||||
|
|
||||||
|
if 'discord_user' in results['qsd'] and \
|
||||||
|
len(results['qsd']['discord_user']):
|
||||||
|
results['discord_user'] = \
|
||||||
|
NotifyNotifiarr.unquote(
|
||||||
|
results['qsd']['discord_user'])
|
||||||
|
|
||||||
|
if 'discord_role' in results['qsd'] and \
|
||||||
|
len(results['qsd']['discord_role']):
|
||||||
|
results['discord_role'] = \
|
||||||
|
NotifyNotifiarr.unquote(results['qsd']['discord_role'])
|
||||||
|
|
||||||
|
if 'event' in results['qsd'] and \
|
||||||
|
len(results['qsd']['event']):
|
||||||
|
results['event'] = \
|
||||||
|
NotifyNotifiarr.unquote(results['qsd']['event'])
|
||||||
|
|
||||||
|
# Include images with our message
|
||||||
|
results['include_image'] = \
|
||||||
|
parse_bool(results['qsd'].get('image', False))
|
||||||
|
|
||||||
|
# Track if we need to extract the hostname as a target
|
||||||
|
host_is_potential_target = False
|
||||||
|
|
||||||
|
if 'source' in results['qsd'] and len(results['qsd']['source']):
|
||||||
|
results['source'] = \
|
||||||
|
NotifyNotifiarr.unquote(results['qsd']['source'])
|
||||||
|
|
||||||
|
elif 'from' in results['qsd'] and len(results['qsd']['from']):
|
||||||
|
results['source'] = \
|
||||||
|
NotifyNotifiarr.unquote(results['qsd']['from'])
|
||||||
|
|
||||||
|
# Set our apikey if found as an argument
|
||||||
|
if 'apikey' in results['qsd'] and len(results['qsd']['apikey']):
|
||||||
|
results['apikey'] = \
|
||||||
|
NotifyNotifiarr.unquote(results['qsd']['apikey'])
|
||||||
|
|
||||||
|
host_is_potential_target = True
|
||||||
|
|
||||||
|
elif 'key' in results['qsd'] and len(results['qsd']['key']):
|
||||||
|
results['apikey'] = \
|
||||||
|
NotifyNotifiarr.unquote(results['qsd']['key'])
|
||||||
|
|
||||||
|
host_is_potential_target = True
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Pop the first element (this is the api key)
|
||||||
|
results['apikey'] = \
|
||||||
|
NotifyNotifiarr.unquote(results['host'])
|
||||||
|
|
||||||
|
if host_is_potential_target is True and results['host']:
|
||||||
|
results['targets'].append(NotifyNotifiarr.unquote(results['host']))
|
||||||
|
|
||||||
|
# Support the 'to' variable so that we can support rooms this way too
|
||||||
|
# The 'to' makes it easier to use yaml configuration
|
||||||
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
|
results['targets'] += [x for x in filter(
|
||||||
|
bool, CHANNEL_LIST_DELIM.split(
|
||||||
|
NotifyNotifiarr.unquote(results['qsd']['to'])))]
|
||||||
|
|
||||||
|
return results
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -172,6 +168,9 @@ class NotifyNtfy(NotifyBase):
|
||||||
# Default upstream/cloud host if none is defined
|
# Default upstream/cloud host if none is defined
|
||||||
cloud_notify_url = 'https://ntfy.sh'
|
cloud_notify_url = 'https://ntfy.sh'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# Allows the user to specify the NotifyImageSize object
|
# Allows the user to specify the NotifyImageSize object
|
||||||
image_size = NotifyImageSize.XY_256
|
image_size = NotifyImageSize.XY_256
|
||||||
|
|
||||||
|
@ -405,14 +404,14 @@ class NotifyNtfy(NotifyBase):
|
||||||
# Retrieve our topic
|
# Retrieve our topic
|
||||||
topic = topics.pop()
|
topic = topics.pop()
|
||||||
|
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
# We need to upload our payload first so that we can source it
|
# We need to upload our payload first so that we can source it
|
||||||
# in remaining messages
|
# in remaining messages
|
||||||
for no, attachment in enumerate(attach):
|
for no, attachment in enumerate(attach):
|
||||||
|
|
||||||
# First message only includes the text
|
# First message only includes the text (if defined)
|
||||||
_body = body if not no else None
|
_body = body if not no and body else None
|
||||||
_title = title if not no else None
|
_title = title if not no and title else None
|
||||||
|
|
||||||
# Perform some simple error checking
|
# Perform some simple error checking
|
||||||
if not attachment:
|
if not attachment:
|
||||||
|
@ -453,10 +452,6 @@ class NotifyNtfy(NotifyBase):
|
||||||
'User-Agent': self.app_id,
|
'User-Agent': self.app_id,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Some default values for our request object to which we'll update
|
|
||||||
# depending on what our payload is
|
|
||||||
files = None
|
|
||||||
|
|
||||||
# See https://ntfy.sh/docs/publish/#publish-as-json
|
# See https://ntfy.sh/docs/publish/#publish-as-json
|
||||||
data = {}
|
data = {}
|
||||||
|
|
||||||
|
@ -494,11 +489,23 @@ class NotifyNtfy(NotifyBase):
|
||||||
data['topic'] = topic
|
data['topic'] = topic
|
||||||
virt_payload = data
|
virt_payload = data
|
||||||
|
|
||||||
|
if self.attach:
|
||||||
|
virt_payload['attach'] = self.attach
|
||||||
|
|
||||||
|
if self.filename:
|
||||||
|
virt_payload['filename'] = self.filename
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Point our payload to our parameters
|
# Point our payload to our parameters
|
||||||
virt_payload = params
|
virt_payload = params
|
||||||
notify_url += '/{topic}'.format(topic=topic)
|
notify_url += '/{topic}'.format(topic=topic)
|
||||||
|
|
||||||
|
# Prepare our Header
|
||||||
|
virt_payload['filename'] = attach.name
|
||||||
|
|
||||||
|
with open(attach.path, 'rb') as fp:
|
||||||
|
data = fp.read()
|
||||||
|
|
||||||
if image_url:
|
if image_url:
|
||||||
headers['X-Icon'] = image_url
|
headers['X-Icon'] = image_url
|
||||||
|
|
||||||
|
@ -523,18 +530,6 @@ class NotifyNtfy(NotifyBase):
|
||||||
if self.__tags:
|
if self.__tags:
|
||||||
headers['X-Tags'] = ",".join(self.__tags)
|
headers['X-Tags'] = ",".join(self.__tags)
|
||||||
|
|
||||||
if isinstance(attach, AttachBase):
|
|
||||||
# Prepare our Header
|
|
||||||
params['filename'] = attach.name
|
|
||||||
|
|
||||||
# prepare our files object
|
|
||||||
files = {'file': (attach.name, open(attach.path, 'rb'))}
|
|
||||||
|
|
||||||
elif self.attach is not None:
|
|
||||||
data['attach'] = self.attach
|
|
||||||
if self.filename is not None:
|
|
||||||
data['filename'] = self.filename
|
|
||||||
|
|
||||||
self.logger.debug('ntfy POST URL: %s (cert_verify=%r)' % (
|
self.logger.debug('ntfy POST URL: %s (cert_verify=%r)' % (
|
||||||
notify_url, self.verify_certificate,
|
notify_url, self.verify_certificate,
|
||||||
))
|
))
|
||||||
|
@ -547,13 +542,15 @@ class NotifyNtfy(NotifyBase):
|
||||||
# Default response type
|
# Default response type
|
||||||
response = None
|
response = None
|
||||||
|
|
||||||
|
if not attach:
|
||||||
|
data = dumps(data)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = requests.post(
|
r = requests.post(
|
||||||
notify_url,
|
notify_url,
|
||||||
params=params if params else None,
|
params=params if params else None,
|
||||||
data=dumps(data) if data else None,
|
data=data,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
files=files,
|
|
||||||
auth=auth,
|
auth=auth,
|
||||||
verify=self.verify_certificate,
|
verify=self.verify_certificate,
|
||||||
timeout=self.request_timeout,
|
timeout=self.request_timeout,
|
||||||
|
@ -608,7 +605,6 @@ class NotifyNtfy(NotifyBase):
|
||||||
notify_url) + 'notification.'
|
notify_url) + 'notification.'
|
||||||
)
|
)
|
||||||
self.logger.debug('Socket Exception: %s' % str(e))
|
self.logger.debug('Socket Exception: %s' % str(e))
|
||||||
return False, response
|
|
||||||
|
|
||||||
except (OSError, IOError) as e:
|
except (OSError, IOError) as e:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
|
@ -616,13 +612,8 @@ class NotifyNtfy(NotifyBase):
|
||||||
attach.name if isinstance(attach, AttachBase)
|
attach.name if isinstance(attach, AttachBase)
|
||||||
else virt_payload))
|
else virt_payload))
|
||||||
self.logger.debug('I/O Exception: %s' % str(e))
|
self.logger.debug('I/O Exception: %s' % str(e))
|
||||||
return False, response
|
|
||||||
|
|
||||||
finally:
|
return False, response
|
||||||
# Close our file (if it's open) stored in the second element
|
|
||||||
# of our files tuple (index 1)
|
|
||||||
if files:
|
|
||||||
files['file'][1].close()
|
|
||||||
|
|
||||||
def url(self, privacy=False, *args, **kwargs):
|
def url(self, privacy=False, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -698,6 +689,12 @@ class NotifyNtfy(NotifyBase):
|
||||||
params=NotifyNtfy.urlencode(params)
|
params=NotifyNtfy.urlencode(params)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
return len(self.topics)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -148,8 +144,13 @@ class NotifyOffice365(NotifyBase):
|
||||||
'private': True,
|
'private': True,
|
||||||
'required': True,
|
'required': True,
|
||||||
},
|
},
|
||||||
|
'target_email': {
|
||||||
|
'name': _('Target Email'),
|
||||||
|
'type': 'string',
|
||||||
|
'map_to': 'targets',
|
||||||
|
},
|
||||||
'targets': {
|
'targets': {
|
||||||
'name': _('Target Emails'),
|
'name': _('Targets'),
|
||||||
'type': 'list:string',
|
'type': 'list:string',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -596,6 +597,12 @@ class NotifyOffice365(NotifyBase):
|
||||||
safe='') for e in self.targets]),
|
safe='') for e in self.targets]),
|
||||||
params=NotifyOffice365.urlencode(params))
|
params=NotifyOffice365.urlencode(params))
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
return len(self.targets)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -51,7 +47,7 @@ from ..utils import is_email
|
||||||
from ..AppriseLocale import gettext_lazy as _
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class OneSignalCategory(NotifyBase):
|
class OneSignalCategory:
|
||||||
"""
|
"""
|
||||||
We define the different category types that we can notify via OneSignal
|
We define the different category types that we can notify via OneSignal
|
||||||
"""
|
"""
|
||||||
|
@ -92,7 +88,7 @@ class NotifyOneSignal(NotifyBase):
|
||||||
image_size = NotifyImageSize.XY_72
|
image_size = NotifyImageSize.XY_72
|
||||||
|
|
||||||
# The maximum allowable batch sizes per message
|
# The maximum allowable batch sizes per message
|
||||||
maximum_batch_size = 2000
|
default_batch_size = 2000
|
||||||
|
|
||||||
# Define object templates
|
# Define object templates
|
||||||
templates = (
|
templates = (
|
||||||
|
@ -121,7 +117,7 @@ class NotifyOneSignal(NotifyBase):
|
||||||
'private': True,
|
'private': True,
|
||||||
'required': True,
|
'required': True,
|
||||||
},
|
},
|
||||||
'target_device': {
|
'target_player': {
|
||||||
'name': _('Target Player ID'),
|
'name': _('Target Player ID'),
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'map_to': 'targets',
|
'map_to': 'targets',
|
||||||
|
@ -146,6 +142,7 @@ class NotifyOneSignal(NotifyBase):
|
||||||
'targets': {
|
'targets': {
|
||||||
'name': _('Targets'),
|
'name': _('Targets'),
|
||||||
'type': 'list:string',
|
'type': 'list:string',
|
||||||
|
'required': True,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -204,7 +201,7 @@ class NotifyOneSignal(NotifyBase):
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
# Prepare Batch Mode Flag
|
# Prepare Batch Mode Flag
|
||||||
self.batch_size = self.maximum_batch_size if batch else 1
|
self.batch_size = self.default_batch_size if batch else 1
|
||||||
|
|
||||||
# Place a thumbnail image inline with the message body
|
# Place a thumbnail image inline with the message body
|
||||||
self.include_image = include_image
|
self.include_image = include_image
|
||||||
|
@ -432,6 +429,26 @@ class NotifyOneSignal(NotifyBase):
|
||||||
params=NotifyOneSignal.urlencode(params),
|
params=NotifyOneSignal.urlencode(params),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
#
|
||||||
|
# Factor batch into calculation
|
||||||
|
#
|
||||||
|
if self.batch_size > 1:
|
||||||
|
# Batches can only be sent by group (you can't combine groups into
|
||||||
|
# a single batch)
|
||||||
|
total_targets = 0
|
||||||
|
for k, m in self.targets.items():
|
||||||
|
targets = len(m)
|
||||||
|
total_targets += int(targets / self.batch_size) + \
|
||||||
|
(1 if targets % self.batch_size else 0)
|
||||||
|
return total_targets
|
||||||
|
|
||||||
|
# Normal batch count; just count the targets
|
||||||
|
return sum([len(m) for _, m in self.targets.items()])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -172,7 +168,7 @@ class NotifyOpsgenie(NotifyBase):
|
||||||
opsgenie_default_region = OpsgenieRegion.US
|
opsgenie_default_region = OpsgenieRegion.US
|
||||||
|
|
||||||
# The maximum allowable targets within a notification
|
# The maximum allowable targets within a notification
|
||||||
maximum_batch_size = 50
|
default_batch_size = 50
|
||||||
|
|
||||||
# Define object templates
|
# Define object templates
|
||||||
templates = (
|
templates = (
|
||||||
|
@ -308,7 +304,7 @@ class NotifyOpsgenie(NotifyBase):
|
||||||
self.details.update(details)
|
self.details.update(details)
|
||||||
|
|
||||||
# Prepare Batch Mode Flag
|
# Prepare Batch Mode Flag
|
||||||
self.batch_size = self.maximum_batch_size if batch else 1
|
self.batch_size = self.default_batch_size if batch else 1
|
||||||
|
|
||||||
# Assign our tags (if defined)
|
# Assign our tags (if defined)
|
||||||
self.__tags = parse_list(tags)
|
self.__tags = parse_list(tags)
|
||||||
|
@ -536,6 +532,20 @@ class NotifyOpsgenie(NotifyBase):
|
||||||
for x in self.targets]),
|
for x in self.targets]),
|
||||||
params=NotifyOpsgenie.urlencode(params))
|
params=NotifyOpsgenie.urlencode(params))
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
#
|
||||||
|
# Factor batch into calculation
|
||||||
|
#
|
||||||
|
targets = len(self.targets)
|
||||||
|
if self.batch_size > 1:
|
||||||
|
targets = int(targets / self.batch_size) + \
|
||||||
|
(1 if targets % self.batch_size else 0)
|
||||||
|
|
||||||
|
return targets if targets > 0 else 1
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -142,7 +138,7 @@ class NotifyPagerDuty(NotifyBase):
|
||||||
},
|
},
|
||||||
# Optional but triggers V2 API
|
# Optional but triggers V2 API
|
||||||
'integrationkey': {
|
'integrationkey': {
|
||||||
'name': _('Routing Key'),
|
'name': _('Integration Key'),
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'private': True,
|
'private': True,
|
||||||
'required': True
|
'required': True
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -30,8 +26,6 @@
|
||||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
# POSSIBILITY OF SUCH DAMAGE.
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
# Official API reference: https://developer.gitter.im/docs/user-resource
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import requests
|
import requests
|
||||||
from json import dumps
|
from json import dumps
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -93,6 +89,7 @@ class NotifyPopcornNotify(NotifyBase):
|
||||||
'targets': {
|
'targets': {
|
||||||
'name': _('Targets'),
|
'name': _('Targets'),
|
||||||
'type': 'list:string',
|
'type': 'list:string',
|
||||||
|
'required': True,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -265,6 +262,21 @@ class NotifyPopcornNotify(NotifyBase):
|
||||||
[NotifyPopcornNotify.quote(x, safe='') for x in self.targets]),
|
[NotifyPopcornNotify.quote(x, safe='') for x in self.targets]),
|
||||||
params=NotifyPopcornNotify.urlencode(params))
|
params=NotifyPopcornNotify.urlencode(params))
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
#
|
||||||
|
# Factor batch into calculation
|
||||||
|
#
|
||||||
|
batch_size = 1 if not self.batch else self.default_batch_size
|
||||||
|
targets = len(self.targets)
|
||||||
|
if batch_size > 1:
|
||||||
|
targets = int(targets / batch_size) + \
|
||||||
|
(1 if targets % batch_size else 0)
|
||||||
|
|
||||||
|
return targets
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -75,6 +71,9 @@ class NotifyPushBullet(NotifyBase):
|
||||||
# PushBullet uses the http protocol with JSON requests
|
# PushBullet uses the http protocol with JSON requests
|
||||||
notify_url = 'https://api.pushbullet.com/v2/{}'
|
notify_url = 'https://api.pushbullet.com/v2/{}'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# Define object templates
|
# Define object templates
|
||||||
templates = (
|
templates = (
|
||||||
'{schema}://{accesstoken}',
|
'{schema}://{accesstoken}',
|
||||||
|
@ -150,7 +149,7 @@ class NotifyPushBullet(NotifyBase):
|
||||||
# Build a list of our attachments
|
# Build a list of our attachments
|
||||||
attachments = []
|
attachments = []
|
||||||
|
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
# We need to upload our payload first so that we can source it
|
# We need to upload our payload first so that we can source it
|
||||||
# in remaining messages
|
# in remaining messages
|
||||||
for attachment in attach:
|
for attachment in attach:
|
||||||
|
@ -261,6 +260,7 @@ class NotifyPushBullet(NotifyBase):
|
||||||
"PushBullet recipient {} parsed as a device"
|
"PushBullet recipient {} parsed as a device"
|
||||||
.format(recipient))
|
.format(recipient))
|
||||||
|
|
||||||
|
if body:
|
||||||
okay, response = self._send(
|
okay, response = self._send(
|
||||||
self.notify_url.format('pushes'), payload)
|
self.notify_url.format('pushes'), payload)
|
||||||
if not okay:
|
if not okay:
|
||||||
|
@ -406,6 +406,12 @@ class NotifyPushBullet(NotifyBase):
|
||||||
targets=targets,
|
targets=targets,
|
||||||
params=NotifyPushBullet.urlencode(params))
|
params=NotifyPushBullet.urlencode(params))
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
return len(self.targets)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
|
218
lib/apprise/plugins/NotifyPushDeer.py
Normal file
218
lib/apprise/plugins/NotifyPushDeer.py
Normal file
|
@ -0,0 +1,218 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 2-Clause License
|
||||||
|
#
|
||||||
|
# Apprise - Push Notification Library.
|
||||||
|
# Copyright (c) 2023, 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 ..common import NotifyType
|
||||||
|
from .NotifyBase import NotifyBase
|
||||||
|
from ..utils import validate_regex
|
||||||
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
# Syntax:
|
||||||
|
# schan://{key}/
|
||||||
|
|
||||||
|
|
||||||
|
class NotifyPushDeer(NotifyBase):
|
||||||
|
"""
|
||||||
|
A wrapper for PushDeer Notifications
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The default descriptive name associated with the Notification
|
||||||
|
service_name = 'PushDeer'
|
||||||
|
|
||||||
|
# The services URL
|
||||||
|
service_url = 'https://www.pushdeer.com/'
|
||||||
|
|
||||||
|
# Insecure Protocol Access
|
||||||
|
protocol = 'pushdeer'
|
||||||
|
|
||||||
|
# Secure Protocol
|
||||||
|
secure_protocol = 'pushdeers'
|
||||||
|
|
||||||
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_PushDeer'
|
||||||
|
|
||||||
|
# Default hostname
|
||||||
|
default_hostname = 'api2.pushdeer.com'
|
||||||
|
|
||||||
|
# PushDeer API
|
||||||
|
notify_url = '{schema}://{host}:{port}/message/push?pushkey={pushKey}'
|
||||||
|
|
||||||
|
# Define object templates
|
||||||
|
templates = (
|
||||||
|
'{schema}://{pushkey}',
|
||||||
|
'{schema}://{host}/{pushkey}',
|
||||||
|
'{schema}://{host}:{port}/{pushkey}',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Define our template tokens
|
||||||
|
template_tokens = dict(NotifyBase.template_tokens, **{
|
||||||
|
'host': {
|
||||||
|
'name': _('Hostname'),
|
||||||
|
'type': 'string',
|
||||||
|
},
|
||||||
|
'port': {
|
||||||
|
'name': _('Port'),
|
||||||
|
'type': 'int',
|
||||||
|
'min': 1,
|
||||||
|
'max': 65535,
|
||||||
|
},
|
||||||
|
'pushkey': {
|
||||||
|
'name': _('Pushkey'),
|
||||||
|
'type': 'string',
|
||||||
|
'private': True,
|
||||||
|
'required': True,
|
||||||
|
'regex': (r'^[a-z0-9]+$', 'i'),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
def __init__(self, pushkey, **kwargs):
|
||||||
|
"""
|
||||||
|
Initialize PushDeer Object
|
||||||
|
"""
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
# PushKey (associated with project)
|
||||||
|
self.push_key = validate_regex(
|
||||||
|
pushkey, *self.template_tokens['pushkey']['regex'])
|
||||||
|
if not self.push_key:
|
||||||
|
msg = 'An invalid PushDeer API Pushkey ' \
|
||||||
|
'({}) was specified.'.format(pushkey)
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
|
"""
|
||||||
|
Perform PushDeer Notification
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Prepare our persistent_notification.create payload
|
||||||
|
payload = {
|
||||||
|
'text': title if title else body,
|
||||||
|
'type': 'text',
|
||||||
|
'desp': body if title else '',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set our schema
|
||||||
|
schema = 'https' if self.secure else 'http'
|
||||||
|
|
||||||
|
# Set host
|
||||||
|
host = self.default_hostname
|
||||||
|
if self.host:
|
||||||
|
host = self.host
|
||||||
|
|
||||||
|
# Set port
|
||||||
|
port = 443 if self.secure else 80
|
||||||
|
if self.port:
|
||||||
|
port = self.port
|
||||||
|
|
||||||
|
# Our Notification URL
|
||||||
|
notify_url = self.notify_url.format(
|
||||||
|
schema=schema, host=host, port=port, pushKey=self.push_key)
|
||||||
|
|
||||||
|
# Some Debug Logging
|
||||||
|
self.logger.debug('PushDeer URL: {} (cert_verify={})'.format(
|
||||||
|
notify_url, self.verify_certificate))
|
||||||
|
self.logger.debug('PushDeer Payload: {}'.format(payload))
|
||||||
|
|
||||||
|
# Always call throttle before any remote server i/o is made
|
||||||
|
self.throttle()
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.post(
|
||||||
|
notify_url,
|
||||||
|
data=payload,
|
||||||
|
timeout=self.request_timeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
if r.status_code != requests.codes.ok:
|
||||||
|
# We had a problem
|
||||||
|
status_str = \
|
||||||
|
NotifyPushDeer.http_response_code_lookup(
|
||||||
|
r.status_code)
|
||||||
|
|
||||||
|
self.logger.warning(
|
||||||
|
'Failed to send PushDeer notification: '
|
||||||
|
'{}{}error={}.'.format(
|
||||||
|
status_str,
|
||||||
|
', ' if status_str else '',
|
||||||
|
r.status_code))
|
||||||
|
|
||||||
|
self.logger.debug(
|
||||||
|
'Response Details:\r\n{}'.format(r.content))
|
||||||
|
return False
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.logger.info('Sent PushDeer notification.')
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
self.logger.warning(
|
||||||
|
'A Connection error occured sending PushDeer '
|
||||||
|
'notification.'
|
||||||
|
)
|
||||||
|
self.logger.debug('Socket Exception: %s' % str(e))
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def url(self, privacy=False):
|
||||||
|
"""
|
||||||
|
Returns the URL built dynamically based on specified arguments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.host:
|
||||||
|
url = '{schema}://{host}{port}/{pushkey}'
|
||||||
|
else:
|
||||||
|
url = '{schema}://{pushkey}'
|
||||||
|
|
||||||
|
return url.format(
|
||||||
|
schema=self.secure_protocol if self.secure else self.protocol,
|
||||||
|
host=self.host,
|
||||||
|
port='' if not self.port else ':{}'.format(self.port),
|
||||||
|
pushkey=self.pprint(self.push_key, privacy, safe=''))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_url(url):
|
||||||
|
"""
|
||||||
|
Parses the URL and returns enough arguments that can allow
|
||||||
|
us to substantiate this object.
|
||||||
|
"""
|
||||||
|
results = NotifyBase.parse_url(url, verify_host=False)
|
||||||
|
if not results:
|
||||||
|
# We're done early as we couldn't parse the URL
|
||||||
|
return results
|
||||||
|
|
||||||
|
fullpaths = NotifyPushDeer.split_path(results['fullpath'])
|
||||||
|
|
||||||
|
if len(fullpaths) == 0:
|
||||||
|
results['pushkey'] = results['host']
|
||||||
|
results['host'] = None
|
||||||
|
else:
|
||||||
|
results['pushkey'] = fullpaths.pop()
|
||||||
|
|
||||||
|
return results
|
221
lib/apprise/plugins/NotifyPushMe.py
Normal file
221
lib/apprise/plugins/NotifyPushMe.py
Normal file
|
@ -0,0 +1,221 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 2-Clause License
|
||||||
|
#
|
||||||
|
# Apprise - Push Notification Library.
|
||||||
|
# Copyright (c) 2023, 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 .NotifyBase import NotifyBase
|
||||||
|
from ..common import NotifyType
|
||||||
|
from ..common import NotifyFormat
|
||||||
|
from ..utils import validate_regex
|
||||||
|
from ..utils import parse_bool
|
||||||
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class NotifyPushMe(NotifyBase):
|
||||||
|
"""
|
||||||
|
A wrapper for PushMe Notifications
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The default descriptive name associated with the Notification
|
||||||
|
service_name = 'PushMe'
|
||||||
|
|
||||||
|
# The services URL
|
||||||
|
service_url = 'https://push.i-i.me/'
|
||||||
|
|
||||||
|
# Insecure protocol (for those self hosted requests)
|
||||||
|
protocol = 'pushme'
|
||||||
|
|
||||||
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_pushme'
|
||||||
|
|
||||||
|
# PushMe URL
|
||||||
|
notify_url = 'https://push.i-i.me/'
|
||||||
|
|
||||||
|
# Define object templates
|
||||||
|
templates = (
|
||||||
|
'{schema}://{token}',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Define our template tokens
|
||||||
|
template_tokens = dict(NotifyBase.template_tokens, **{
|
||||||
|
'token': {
|
||||||
|
'name': _('Token'),
|
||||||
|
'type': 'string',
|
||||||
|
'private': True,
|
||||||
|
'required': True,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
# Define our template arguments
|
||||||
|
template_args = dict(NotifyBase.template_args, **{
|
||||||
|
'token': {
|
||||||
|
'alias_of': 'token',
|
||||||
|
},
|
||||||
|
'push_key': {
|
||||||
|
'alias_of': 'token',
|
||||||
|
},
|
||||||
|
'status': {
|
||||||
|
'name': _('Show Status'),
|
||||||
|
'type': 'bool',
|
||||||
|
'default': True,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
def __init__(self, token, status=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Initialize PushMe Object
|
||||||
|
"""
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
# Token (associated with project)
|
||||||
|
self.token = validate_regex(token)
|
||||||
|
if not self.token:
|
||||||
|
msg = 'An invalid PushMe Token ' \
|
||||||
|
'({}) was specified.'.format(token)
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
# Set Status type
|
||||||
|
self.status = status
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
|
"""
|
||||||
|
Perform PushMe Notification
|
||||||
|
"""
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'User-Agent': self.app_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prepare our payload
|
||||||
|
params = {
|
||||||
|
'push_key': self.token,
|
||||||
|
'title': title if not self.status
|
||||||
|
else '{} {}'.format(self.asset.ascii(notify_type), title),
|
||||||
|
'content': body,
|
||||||
|
'type': 'markdown'
|
||||||
|
if self.notify_format == NotifyFormat.MARKDOWN else 'text'
|
||||||
|
}
|
||||||
|
|
||||||
|
self.logger.debug('PushMe POST URL: %s (cert_verify=%r)' % (
|
||||||
|
self.notify_url, self.verify_certificate,
|
||||||
|
))
|
||||||
|
self.logger.debug('PushMe Payload: %s' % str(params))
|
||||||
|
|
||||||
|
# Always call throttle before any remote server i/o is made
|
||||||
|
self.throttle()
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.post(
|
||||||
|
self.notify_url,
|
||||||
|
params=params,
|
||||||
|
headers=headers,
|
||||||
|
verify=self.verify_certificate,
|
||||||
|
timeout=self.request_timeout,
|
||||||
|
)
|
||||||
|
if r.status_code != requests.codes.ok:
|
||||||
|
# We had a problem
|
||||||
|
status_str = \
|
||||||
|
NotifyPushMe.http_response_code_lookup(r.status_code)
|
||||||
|
|
||||||
|
self.logger.warning(
|
||||||
|
'Failed to send PushMe 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 PushMe notification.')
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
self.logger.warning(
|
||||||
|
'A Connection error occurred sending PushMe notification.',
|
||||||
|
)
|
||||||
|
self.logger.debug('Socket Exception: %s' % str(e))
|
||||||
|
|
||||||
|
# Return; we're done
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def url(self, privacy=False, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Returns the URL built dynamically based on specified arguments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Define any URL parameters
|
||||||
|
params = {
|
||||||
|
'status': 'yes' if self.status else 'no',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extend our parameters
|
||||||
|
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
|
||||||
|
|
||||||
|
# Official URLs are easy to assemble
|
||||||
|
return '{schema}://{token}/?{params}'.format(
|
||||||
|
schema=self.protocol,
|
||||||
|
token=self.pprint(self.token, privacy, safe=''),
|
||||||
|
params=NotifyPushMe.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, verify_host=False)
|
||||||
|
if not results:
|
||||||
|
# We're done early as we couldn't load the results
|
||||||
|
return results
|
||||||
|
|
||||||
|
# Store our token using the host
|
||||||
|
results['token'] = NotifyPushMe.unquote(results['host'])
|
||||||
|
|
||||||
|
# The 'token' makes it easier to use yaml configuration
|
||||||
|
if 'token' in results['qsd'] and len(results['qsd']['token']):
|
||||||
|
results['token'] = NotifyPushMe.unquote(results['qsd']['token'])
|
||||||
|
|
||||||
|
elif 'push_key' in results['qsd'] and len(results['qsd']['push_key']):
|
||||||
|
# Support 'push_key' if specified
|
||||||
|
results['token'] = NotifyPushMe.unquote(results['qsd']['push_key'])
|
||||||
|
|
||||||
|
# Get status switch
|
||||||
|
results['status'] = \
|
||||||
|
parse_bool(results['qsd'].get('status', True))
|
||||||
|
|
||||||
|
return results
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -336,6 +332,9 @@ class NotifyPushSafer(NotifyBase):
|
||||||
# The default secure protocol
|
# The default secure protocol
|
||||||
secure_protocol = 'psafers'
|
secure_protocol = 'psafers'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# Number of requests to a allow per second
|
# Number of requests to a allow per second
|
||||||
request_rate_per_sec = 1.2
|
request_rate_per_sec = 1.2
|
||||||
|
|
||||||
|
@ -546,7 +545,7 @@ class NotifyPushSafer(NotifyBase):
|
||||||
# Initialize our list of attachments
|
# Initialize our list of attachments
|
||||||
attachments = []
|
attachments = []
|
||||||
|
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
# We need to upload our payload first so that we can source it
|
# We need to upload our payload first so that we can source it
|
||||||
# in remaining messages
|
# in remaining messages
|
||||||
for attachment in attach:
|
for attachment in attach:
|
||||||
|
@ -794,6 +793,12 @@ class NotifyPushSafer(NotifyBase):
|
||||||
targets=targets,
|
targets=targets,
|
||||||
params=NotifyPushSafer.urlencode(params))
|
params=NotifyPushSafer.urlencode(params))
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
return len(self.targets)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -329,6 +325,13 @@ class NotifyPushed(NotifyBase):
|
||||||
)]),
|
)]),
|
||||||
params=NotifyPushed.urlencode(params))
|
params=NotifyPushed.urlencode(params))
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
targets = len(self.channels) + len(self.users)
|
||||||
|
return targets if targets > 0 else 1
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -32,6 +28,7 @@
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import requests
|
import requests
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from ..common import NotifyType
|
from ..common import NotifyType
|
||||||
|
@ -46,7 +43,7 @@ from ..attachment.AttachBase import AttachBase
|
||||||
PUSHOVER_SEND_TO_ALL = 'ALL_DEVICES'
|
PUSHOVER_SEND_TO_ALL = 'ALL_DEVICES'
|
||||||
|
|
||||||
# Used to detect a Device
|
# Used to detect a Device
|
||||||
VALIDATE_DEVICE = re.compile(r'^[a-z0-9_]{1,25}$', re.I)
|
VALIDATE_DEVICE = re.compile(r'^\s*(?P<device>[a-z0-9_-]{1,25})\s*$', re.I)
|
||||||
|
|
||||||
|
|
||||||
# Priorities
|
# Priorities
|
||||||
|
@ -164,6 +161,9 @@ class NotifyPushover(NotifyBase):
|
||||||
# Pushover uses the http protocol with JSON requests
|
# Pushover uses the http protocol with JSON requests
|
||||||
notify_url = 'https://api.pushover.net/1/messages.json'
|
notify_url = 'https://api.pushover.net/1/messages.json'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# The maximum allowable characters allowed in the body per message
|
# The maximum allowable characters allowed in the body per message
|
||||||
body_maxlen = 1024
|
body_maxlen = 1024
|
||||||
|
|
||||||
|
@ -201,7 +201,7 @@ class NotifyPushover(NotifyBase):
|
||||||
'target_device': {
|
'target_device': {
|
||||||
'name': _('Target Device'),
|
'name': _('Target Device'),
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'regex': (r'^[a-z0-9_]{1,25}$', 'i'),
|
'regex': (r'^[a-z0-9_-]{1,25}$', 'i'),
|
||||||
'map_to': 'targets',
|
'map_to': 'targets',
|
||||||
},
|
},
|
||||||
'targets': {
|
'targets': {
|
||||||
|
@ -276,10 +276,30 @@ class NotifyPushover(NotifyBase):
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
self.targets = parse_list(targets)
|
# Track our valid devices
|
||||||
if len(self.targets) == 0:
|
targets = parse_list(targets)
|
||||||
|
|
||||||
|
# Track any invalid entries
|
||||||
|
self.invalid_targets = list()
|
||||||
|
|
||||||
|
if len(targets) == 0:
|
||||||
self.targets = (PUSHOVER_SEND_TO_ALL, )
|
self.targets = (PUSHOVER_SEND_TO_ALL, )
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.targets = []
|
||||||
|
for target in targets:
|
||||||
|
result = VALIDATE_DEVICE.match(target)
|
||||||
|
if result:
|
||||||
|
# Store device information
|
||||||
|
self.targets.append(result.group('device'))
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.logger.warning(
|
||||||
|
'Dropped invalid Pushover device '
|
||||||
|
'({}) specified.'.format(target),
|
||||||
|
)
|
||||||
|
self.invalid_targets.append(target)
|
||||||
|
|
||||||
# Setup supplemental url
|
# Setup supplemental url
|
||||||
self.supplemental_url = supplemental_url
|
self.supplemental_url = supplemental_url
|
||||||
self.supplemental_url_title = supplemental_url_title
|
self.supplemental_url_title = supplemental_url_title
|
||||||
|
@ -288,9 +308,8 @@ class NotifyPushover(NotifyBase):
|
||||||
self.sound = NotifyPushover.default_pushover_sound \
|
self.sound = NotifyPushover.default_pushover_sound \
|
||||||
if not isinstance(sound, str) else sound.lower()
|
if not isinstance(sound, str) else sound.lower()
|
||||||
if self.sound and self.sound not in PUSHOVER_SOUNDS:
|
if self.sound and self.sound not in PUSHOVER_SOUNDS:
|
||||||
msg = 'The sound specified ({}) is invalid.'.format(sound)
|
msg = 'Using custom sound specified ({}). '.format(sound)
|
||||||
self.logger.warning(msg)
|
self.logger.debug(msg)
|
||||||
raise TypeError(msg)
|
|
||||||
|
|
||||||
# The Priority of the message
|
# The Priority of the message
|
||||||
self.priority = int(
|
self.priority = int(
|
||||||
|
@ -338,22 +357,11 @@ class NotifyPushover(NotifyBase):
|
||||||
Perform Pushover Notification
|
Perform Pushover Notification
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# error tracking (used for function return)
|
if not self.targets:
|
||||||
has_error = False
|
# There were no services to notify
|
||||||
|
|
||||||
# Create a copy of the devices list
|
|
||||||
devices = list(self.targets)
|
|
||||||
while len(devices):
|
|
||||||
device = devices.pop(0)
|
|
||||||
|
|
||||||
if VALIDATE_DEVICE.match(device) is None:
|
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'The device specified (%s) is invalid.' % device,
|
'There were no Pushover targets to notify.')
|
||||||
)
|
return False
|
||||||
|
|
||||||
# Mark our failure
|
|
||||||
has_error = True
|
|
||||||
continue
|
|
||||||
|
|
||||||
# prepare JSON Object
|
# prepare JSON Object
|
||||||
payload = {
|
payload = {
|
||||||
|
@ -362,18 +370,20 @@ class NotifyPushover(NotifyBase):
|
||||||
'priority': str(self.priority),
|
'priority': str(self.priority),
|
||||||
'title': title if title else self.app_desc,
|
'title': title if title else self.app_desc,
|
||||||
'message': body,
|
'message': body,
|
||||||
'device': device,
|
'device': ','.join(self.targets),
|
||||||
'sound': self.sound,
|
'sound': self.sound,
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.supplemental_url:
|
if self.supplemental_url:
|
||||||
payload['url'] = self.supplemental_url
|
payload['url'] = self.supplemental_url
|
||||||
|
|
||||||
if self.supplemental_url_title:
|
if self.supplemental_url_title:
|
||||||
payload['url_title'] = self.supplemental_url_title
|
payload['url_title'] = self.supplemental_url_title
|
||||||
|
|
||||||
if self.notify_format == NotifyFormat.HTML:
|
if self.notify_format == NotifyFormat.HTML:
|
||||||
# https://pushover.net/api#html
|
# https://pushover.net/api#html
|
||||||
payload['html'] = 1
|
payload['html'] = 1
|
||||||
|
|
||||||
elif self.notify_format == NotifyFormat.MARKDOWN:
|
elif self.notify_format == NotifyFormat.MARKDOWN:
|
||||||
payload['message'] = convert_between(
|
payload['message'] = convert_between(
|
||||||
NotifyFormat.MARKDOWN, NotifyFormat.HTML, body)
|
NotifyFormat.MARKDOWN, NotifyFormat.HTML, body)
|
||||||
|
@ -382,33 +392,32 @@ class NotifyPushover(NotifyBase):
|
||||||
if self.priority == PushoverPriority.EMERGENCY:
|
if self.priority == PushoverPriority.EMERGENCY:
|
||||||
payload.update({'retry': self.retry, 'expire': self.expire})
|
payload.update({'retry': self.retry, 'expire': self.expire})
|
||||||
|
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
# Create a copy of our payload
|
# Create a copy of our payload
|
||||||
_payload = payload.copy()
|
_payload = payload.copy()
|
||||||
|
|
||||||
# Send with attachments
|
# Send with attachments
|
||||||
for attachment in attach:
|
for no, attachment in enumerate(attach):
|
||||||
# Simple send
|
if no or not body:
|
||||||
|
# To handle multiple attachments, clean up our message
|
||||||
|
_payload['message'] = attachment.name
|
||||||
|
|
||||||
if not self._send(_payload, attachment):
|
if not self._send(_payload, attachment):
|
||||||
# Mark our failure
|
# Mark our failure
|
||||||
has_error = True
|
return False
|
||||||
# clean exit from our attachment loop
|
|
||||||
break
|
# Clear our title if previously set
|
||||||
|
_payload['title'] = ''
|
||||||
|
|
||||||
# To handle multiple attachments, clean up our message
|
|
||||||
_payload['title'] = '...'
|
|
||||||
_payload['message'] = attachment.name
|
|
||||||
# No need to alarm for each consecutive attachment uploaded
|
# No need to alarm for each consecutive attachment uploaded
|
||||||
# afterwards
|
# afterwards
|
||||||
_payload['sound'] = PushoverSound.NONE
|
_payload['sound'] = PushoverSound.NONE
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Simple send
|
# Simple send
|
||||||
if not self._send(payload):
|
return self._send(payload)
|
||||||
# Mark our failure
|
|
||||||
has_error = True
|
|
||||||
|
|
||||||
return not has_error
|
return True
|
||||||
|
|
||||||
def _send(self, payload, attach=None):
|
def _send(self, payload, attach=None):
|
||||||
"""
|
"""
|
||||||
|
@ -562,8 +571,9 @@ class NotifyPushover(NotifyBase):
|
||||||
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
|
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
|
||||||
|
|
||||||
# Escape our devices
|
# Escape our devices
|
||||||
devices = '/'.join([NotifyPushover.quote(x, safe='')
|
devices = '/'.join(
|
||||||
for x in self.targets])
|
[NotifyPushover.quote(x, safe='')
|
||||||
|
for x in chain(self.targets, self.invalid_targets)])
|
||||||
|
|
||||||
if devices == PUSHOVER_SEND_TO_ALL:
|
if devices == PUSHOVER_SEND_TO_ALL:
|
||||||
# keyword is reserved for internal usage only; it's safe to remove
|
# keyword is reserved for internal usage only; it's safe to remove
|
||||||
|
|
384
lib/apprise/plugins/NotifyPushy.py
Normal file
384
lib/apprise/plugins/NotifyPushy.py
Normal file
|
@ -0,0 +1,384 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 2-Clause License
|
||||||
|
#
|
||||||
|
# Apprise - Push Notification Library.
|
||||||
|
# Copyright (c) 2023, 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.
|
||||||
|
|
||||||
|
# API reference: https://pushy.me/docs/api/send-notifications
|
||||||
|
import re
|
||||||
|
import requests
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
|
from json import dumps, loads
|
||||||
|
from .NotifyBase import NotifyBase
|
||||||
|
from ..common import NotifyType
|
||||||
|
from ..utils import parse_list
|
||||||
|
from ..utils import validate_regex
|
||||||
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
# Used to detect a Device and Topic
|
||||||
|
VALIDATE_DEVICE = re.compile(r'^@(?P<device>[a-z0-9]+)$', re.I)
|
||||||
|
VALIDATE_TOPIC = re.compile(r'^[#]?(?P<topic>[a-z0-9]+)$', re.I)
|
||||||
|
|
||||||
|
# Extend HTTP Error Messages
|
||||||
|
PUSHY_HTTP_ERROR_MAP = {
|
||||||
|
401: 'Unauthorized - Invalid Token.',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class NotifyPushy(NotifyBase):
|
||||||
|
"""
|
||||||
|
A wrapper for Pushy Notifications
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The default descriptive name associated with the Notification
|
||||||
|
service_name = 'Pushy'
|
||||||
|
|
||||||
|
# The services URL
|
||||||
|
service_url = 'https://pushy.me/'
|
||||||
|
|
||||||
|
# All Pushy requests are secure
|
||||||
|
secure_protocol = 'pushy'
|
||||||
|
|
||||||
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_pushy'
|
||||||
|
|
||||||
|
# Pushy uses the http protocol with JSON requests
|
||||||
|
notify_url = 'https://api.pushy.me/push?api_key={apikey}'
|
||||||
|
|
||||||
|
# The maximum allowable characters allowed in the body per message
|
||||||
|
body_maxlen = 4096
|
||||||
|
|
||||||
|
# Define object templates
|
||||||
|
templates = (
|
||||||
|
'{schema}://{apikey}/{targets}',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Define our template tokens
|
||||||
|
template_tokens = dict(NotifyBase.template_tokens, **{
|
||||||
|
'apikey': {
|
||||||
|
'name': _('Secret API Key'),
|
||||||
|
'type': 'string',
|
||||||
|
'private': True,
|
||||||
|
'required': True,
|
||||||
|
},
|
||||||
|
'target_device': {
|
||||||
|
'name': _('Target Device'),
|
||||||
|
'type': 'string',
|
||||||
|
'prefix': '@',
|
||||||
|
'map_to': 'targets',
|
||||||
|
},
|
||||||
|
'target_topic': {
|
||||||
|
'name': _('Target Topic'),
|
||||||
|
'type': 'string',
|
||||||
|
'prefix': '#',
|
||||||
|
'map_to': 'targets',
|
||||||
|
},
|
||||||
|
'targets': {
|
||||||
|
'name': _('Targets'),
|
||||||
|
'type': 'list:string',
|
||||||
|
'required': True,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
# Define our template arguments
|
||||||
|
template_args = dict(NotifyBase.template_args, **{
|
||||||
|
'sound': {
|
||||||
|
# Specify something like ping.aiff
|
||||||
|
'name': _('Sound'),
|
||||||
|
'type': 'string',
|
||||||
|
},
|
||||||
|
'badge': {
|
||||||
|
'name': _('Badge'),
|
||||||
|
'type': 'int',
|
||||||
|
'min': 0,
|
||||||
|
},
|
||||||
|
'to': {
|
||||||
|
'alias_of': 'targets',
|
||||||
|
},
|
||||||
|
'key': {
|
||||||
|
'alias_of': 'apikey',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
def __init__(self, apikey, targets=None, sound=None, badge=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Initialize Pushy Object
|
||||||
|
"""
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
# Access Token (associated with project)
|
||||||
|
self.apikey = validate_regex(apikey)
|
||||||
|
if not self.apikey:
|
||||||
|
msg = 'An invalid Pushy Secret API Key ' \
|
||||||
|
'({}) was specified.'.format(apikey)
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
# Get our targets
|
||||||
|
self.devices = []
|
||||||
|
self.topics = []
|
||||||
|
|
||||||
|
for target in parse_list(targets):
|
||||||
|
result = VALIDATE_TOPIC.match(target)
|
||||||
|
if result:
|
||||||
|
self.topics.append(result.group('topic'))
|
||||||
|
continue
|
||||||
|
|
||||||
|
result = VALIDATE_DEVICE.match(target)
|
||||||
|
if result:
|
||||||
|
self.devices.append(result.group('device'))
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.logger.warning(
|
||||||
|
'Dropped invalid topic/device '
|
||||||
|
'({}) specified.'.format(target),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Setup our sound
|
||||||
|
self.sound = sound
|
||||||
|
|
||||||
|
# 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 Pushy badge ({}) is not valid ', badge)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
|
"""
|
||||||
|
Perform Pushy Notification
|
||||||
|
"""
|
||||||
|
|
||||||
|
if len(self.topics) + len(self.devices) == 0:
|
||||||
|
# There were no services to notify
|
||||||
|
self.logger.warning('There were no Pushy targets to notify.')
|
||||||
|
return False
|
||||||
|
|
||||||
|
# error tracking (used for function return)
|
||||||
|
has_error = False
|
||||||
|
|
||||||
|
# Default Header
|
||||||
|
headers = {
|
||||||
|
'User-Agent': self.app_id,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accepts': 'application/json',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Our URL
|
||||||
|
notify_url = self.notify_url.format(apikey=self.apikey)
|
||||||
|
|
||||||
|
# Default content response object
|
||||||
|
content = {}
|
||||||
|
|
||||||
|
# Create a copy of targets (topics and devices)
|
||||||
|
targets = list(self.topics) + list(self.devices)
|
||||||
|
while len(targets):
|
||||||
|
target = targets.pop(0)
|
||||||
|
|
||||||
|
# prepare JSON Object
|
||||||
|
payload = {
|
||||||
|
# Mandatory fields
|
||||||
|
'to': target,
|
||||||
|
"data": {
|
||||||
|
"message": body,
|
||||||
|
},
|
||||||
|
"notification": {
|
||||||
|
'body': body,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Optional payload items
|
||||||
|
if title:
|
||||||
|
payload['notification']['title'] = title
|
||||||
|
|
||||||
|
if self.sound:
|
||||||
|
payload['notification']['sound'] = self.sound
|
||||||
|
|
||||||
|
if self.badge is not None:
|
||||||
|
payload['notification']['badge'] = self.badge
|
||||||
|
|
||||||
|
self.logger.debug('Pushy POST URL: %s (cert_verify=%r)' % (
|
||||||
|
notify_url, self.verify_certificate,
|
||||||
|
))
|
||||||
|
self.logger.debug('Pushy Payload: %s' % str(payload))
|
||||||
|
|
||||||
|
# Always call throttle before any remote server i/o is made
|
||||||
|
self.throttle()
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.post(
|
||||||
|
notify_url,
|
||||||
|
data=dumps(payload),
|
||||||
|
headers=headers,
|
||||||
|
verify=self.verify_certificate,
|
||||||
|
timeout=self.request_timeout,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sample response
|
||||||
|
# See: https://pushy.me/docs/api/send-notifications
|
||||||
|
# {
|
||||||
|
# "success": true,
|
||||||
|
# "id": "5ea9b214b47cad768a35f13a",
|
||||||
|
# "info": {
|
||||||
|
# "devices": 1
|
||||||
|
# "failed": ['abc']
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
try:
|
||||||
|
content = loads(r.content)
|
||||||
|
|
||||||
|
except (AttributeError, TypeError, ValueError):
|
||||||
|
# ValueError = r.content is Unparsable
|
||||||
|
# TypeError = r.content is None
|
||||||
|
# AttributeError = r is None
|
||||||
|
content = {
|
||||||
|
"success": False,
|
||||||
|
"id": '',
|
||||||
|
"info": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.status_code != requests.codes.ok \
|
||||||
|
or not content.get('success'):
|
||||||
|
|
||||||
|
# We had a problem
|
||||||
|
status_str = \
|
||||||
|
NotifyPushy.http_response_code_lookup(
|
||||||
|
r.status_code, PUSHY_HTTP_ERROR_MAP)
|
||||||
|
|
||||||
|
self.logger.warning(
|
||||||
|
'Failed to send Pushy notification to {}: '
|
||||||
|
'{}{}error={}.'.format(
|
||||||
|
target,
|
||||||
|
status_str,
|
||||||
|
', ' if status_str else '',
|
||||||
|
r.status_code))
|
||||||
|
|
||||||
|
self.logger.debug(
|
||||||
|
'Response Details:\r\n{}'.format(r.content))
|
||||||
|
|
||||||
|
has_error = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.logger.info(
|
||||||
|
'Sent Pushy notification to %s.' % target)
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
self.logger.warning(
|
||||||
|
'A Connection error occurred sending Pushy:%s '
|
||||||
|
'notification', target)
|
||||||
|
self.logger.debug('Socket Exception: %s' % str(e))
|
||||||
|
|
||||||
|
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 = {}
|
||||||
|
if self.sound:
|
||||||
|
params['sound'] = self.sound
|
||||||
|
|
||||||
|
if self.badge is not None:
|
||||||
|
params['badge'] = str(self.badge)
|
||||||
|
|
||||||
|
# Extend our parameters
|
||||||
|
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
|
||||||
|
|
||||||
|
return '{schema}://{apikey}/{targets}/?{params}'.format(
|
||||||
|
schema=self.secure_protocol,
|
||||||
|
apikey=self.pprint(self.apikey, privacy, safe=''),
|
||||||
|
targets='/'.join(
|
||||||
|
[NotifyPushy.quote(x, safe='@#') for x in chain(
|
||||||
|
# Topics are prefixed with a pound/hashtag symbol
|
||||||
|
['#{}'.format(x) for x in self.topics],
|
||||||
|
# Devices
|
||||||
|
['@{}'.format(x) for x in self.devices],
|
||||||
|
)]),
|
||||||
|
params=NotifyPushy.urlencode(params))
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
return len(self.topics) + len(self.devices)
|
||||||
|
|
||||||
|
@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, verify_host=False)
|
||||||
|
if not results:
|
||||||
|
# We're done early as we couldn't load the results
|
||||||
|
return results
|
||||||
|
|
||||||
|
# Token
|
||||||
|
results['apikey'] = NotifyPushy.unquote(results['host'])
|
||||||
|
|
||||||
|
# Retrieve all of our targets
|
||||||
|
results['targets'] = NotifyPushy.split_path(results['fullpath'])
|
||||||
|
|
||||||
|
# Get the sound
|
||||||
|
if 'sound' in results['qsd'] and len(results['qsd']['sound']):
|
||||||
|
results['sound'] = \
|
||||||
|
NotifyPushy.unquote(results['qsd']['sound'])
|
||||||
|
|
||||||
|
# Badge
|
||||||
|
if 'badge' in results['qsd'] and results['qsd']['badge']:
|
||||||
|
results['badge'] = NotifyPushy.unquote(
|
||||||
|
results['qsd']['badge'].strip())
|
||||||
|
|
||||||
|
# Support key variable to store Secret API Key
|
||||||
|
if 'key' in results['qsd'] and len(results['qsd']['key']):
|
||||||
|
results['apikey'] = results['qsd']['key']
|
||||||
|
|
||||||
|
# The 'to' makes it easier to use yaml configuration
|
||||||
|
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||||
|
results['targets'] += \
|
||||||
|
NotifyPushy.parse_list(results['qsd']['to'])
|
||||||
|
|
||||||
|
return results
|
376
lib/apprise/plugins/NotifyRSyslog.py
Normal file
376
lib/apprise/plugins/NotifyRSyslog.py
Normal file
|
@ -0,0 +1,376 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# BSD 2-Clause License
|
||||||
|
#
|
||||||
|
# Apprise - Push Notification Library.
|
||||||
|
# Copyright (c) 2023, 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 os
|
||||||
|
import socket
|
||||||
|
|
||||||
|
from .NotifyBase import NotifyBase
|
||||||
|
from ..common import NotifyType
|
||||||
|
from ..utils import parse_bool
|
||||||
|
from ..AppriseLocale import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class syslog:
|
||||||
|
"""
|
||||||
|
Extrapoloated information from the syslog library so that this plugin
|
||||||
|
would not be dependent on it.
|
||||||
|
"""
|
||||||
|
# Notification Categories
|
||||||
|
LOG_KERN = 0
|
||||||
|
LOG_USER = 8
|
||||||
|
LOG_MAIL = 16
|
||||||
|
LOG_DAEMON = 24
|
||||||
|
LOG_AUTH = 32
|
||||||
|
LOG_SYSLOG = 40
|
||||||
|
LOG_LPR = 48
|
||||||
|
LOG_NEWS = 56
|
||||||
|
LOG_UUCP = 64
|
||||||
|
LOG_CRON = 72
|
||||||
|
LOG_LOCAL0 = 128
|
||||||
|
LOG_LOCAL1 = 136
|
||||||
|
LOG_LOCAL2 = 144
|
||||||
|
LOG_LOCAL3 = 152
|
||||||
|
LOG_LOCAL4 = 160
|
||||||
|
LOG_LOCAL5 = 168
|
||||||
|
LOG_LOCAL6 = 176
|
||||||
|
LOG_LOCAL7 = 184
|
||||||
|
|
||||||
|
# Notification Types
|
||||||
|
LOG_INFO = 6
|
||||||
|
LOG_NOTICE = 5
|
||||||
|
LOG_WARNING = 4
|
||||||
|
LOG_CRIT = 2
|
||||||
|
|
||||||
|
|
||||||
|
class SyslogFacility:
|
||||||
|
"""
|
||||||
|
All of the supported facilities
|
||||||
|
"""
|
||||||
|
KERN = 'kern'
|
||||||
|
USER = 'user'
|
||||||
|
MAIL = 'mail'
|
||||||
|
DAEMON = 'daemon'
|
||||||
|
AUTH = 'auth'
|
||||||
|
SYSLOG = 'syslog'
|
||||||
|
LPR = 'lpr'
|
||||||
|
NEWS = 'news'
|
||||||
|
UUCP = 'uucp'
|
||||||
|
CRON = 'cron'
|
||||||
|
LOCAL0 = 'local0'
|
||||||
|
LOCAL1 = 'local1'
|
||||||
|
LOCAL2 = 'local2'
|
||||||
|
LOCAL3 = 'local3'
|
||||||
|
LOCAL4 = 'local4'
|
||||||
|
LOCAL5 = 'local5'
|
||||||
|
LOCAL6 = 'local6'
|
||||||
|
LOCAL7 = 'local7'
|
||||||
|
|
||||||
|
|
||||||
|
SYSLOG_FACILITY_MAP = {
|
||||||
|
SyslogFacility.KERN: syslog.LOG_KERN,
|
||||||
|
SyslogFacility.USER: syslog.LOG_USER,
|
||||||
|
SyslogFacility.MAIL: syslog.LOG_MAIL,
|
||||||
|
SyslogFacility.DAEMON: syslog.LOG_DAEMON,
|
||||||
|
SyslogFacility.AUTH: syslog.LOG_AUTH,
|
||||||
|
SyslogFacility.SYSLOG: syslog.LOG_SYSLOG,
|
||||||
|
SyslogFacility.LPR: syslog.LOG_LPR,
|
||||||
|
SyslogFacility.NEWS: syslog.LOG_NEWS,
|
||||||
|
SyslogFacility.UUCP: syslog.LOG_UUCP,
|
||||||
|
SyslogFacility.CRON: syslog.LOG_CRON,
|
||||||
|
SyslogFacility.LOCAL0: syslog.LOG_LOCAL0,
|
||||||
|
SyslogFacility.LOCAL1: syslog.LOG_LOCAL1,
|
||||||
|
SyslogFacility.LOCAL2: syslog.LOG_LOCAL2,
|
||||||
|
SyslogFacility.LOCAL3: syslog.LOG_LOCAL3,
|
||||||
|
SyslogFacility.LOCAL4: syslog.LOG_LOCAL4,
|
||||||
|
SyslogFacility.LOCAL5: syslog.LOG_LOCAL5,
|
||||||
|
SyslogFacility.LOCAL6: syslog.LOG_LOCAL6,
|
||||||
|
SyslogFacility.LOCAL7: syslog.LOG_LOCAL7,
|
||||||
|
}
|
||||||
|
|
||||||
|
SYSLOG_FACILITY_RMAP = {
|
||||||
|
syslog.LOG_KERN: SyslogFacility.KERN,
|
||||||
|
syslog.LOG_USER: SyslogFacility.USER,
|
||||||
|
syslog.LOG_MAIL: SyslogFacility.MAIL,
|
||||||
|
syslog.LOG_DAEMON: SyslogFacility.DAEMON,
|
||||||
|
syslog.LOG_AUTH: SyslogFacility.AUTH,
|
||||||
|
syslog.LOG_SYSLOG: SyslogFacility.SYSLOG,
|
||||||
|
syslog.LOG_LPR: SyslogFacility.LPR,
|
||||||
|
syslog.LOG_NEWS: SyslogFacility.NEWS,
|
||||||
|
syslog.LOG_UUCP: SyslogFacility.UUCP,
|
||||||
|
syslog.LOG_CRON: SyslogFacility.CRON,
|
||||||
|
syslog.LOG_LOCAL0: SyslogFacility.LOCAL0,
|
||||||
|
syslog.LOG_LOCAL1: SyslogFacility.LOCAL1,
|
||||||
|
syslog.LOG_LOCAL2: SyslogFacility.LOCAL2,
|
||||||
|
syslog.LOG_LOCAL3: SyslogFacility.LOCAL3,
|
||||||
|
syslog.LOG_LOCAL4: SyslogFacility.LOCAL4,
|
||||||
|
syslog.LOG_LOCAL5: SyslogFacility.LOCAL5,
|
||||||
|
syslog.LOG_LOCAL6: SyslogFacility.LOCAL6,
|
||||||
|
syslog.LOG_LOCAL7: SyslogFacility.LOCAL7,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Used as a lookup when handling the Apprise -> Syslog Mapping
|
||||||
|
SYSLOG_PUBLISH_MAP = {
|
||||||
|
NotifyType.INFO: syslog.LOG_INFO,
|
||||||
|
NotifyType.SUCCESS: syslog.LOG_NOTICE,
|
||||||
|
NotifyType.FAILURE: syslog.LOG_CRIT,
|
||||||
|
NotifyType.WARNING: syslog.LOG_WARNING,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class NotifyRSyslog(NotifyBase):
|
||||||
|
"""
|
||||||
|
A wrapper for Remote Syslog Notifications
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The default descriptive name associated with the Notification
|
||||||
|
service_name = 'Remote Syslog'
|
||||||
|
|
||||||
|
# The services URL
|
||||||
|
service_url = 'https://tools.ietf.org/html/rfc5424'
|
||||||
|
|
||||||
|
# The default protocol
|
||||||
|
protocol = 'rsyslog'
|
||||||
|
|
||||||
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_rsyslog'
|
||||||
|
|
||||||
|
# Disable throttle rate for RSyslog requests
|
||||||
|
request_rate_per_sec = 0
|
||||||
|
|
||||||
|
# Define object templates
|
||||||
|
templates = (
|
||||||
|
'{schema}://{host}',
|
||||||
|
'{schema}://{host}:{port}',
|
||||||
|
'{schema}://{host}/{facility}',
|
||||||
|
'{schema}://{host}:{port}/{facility}',
|
||||||
|
)
|
||||||
|
|
||||||
|
# Define our template tokens
|
||||||
|
template_tokens = dict(NotifyBase.template_tokens, **{
|
||||||
|
'facility': {
|
||||||
|
'name': _('Facility'),
|
||||||
|
'type': 'choice:string',
|
||||||
|
'values': [k for k in SYSLOG_FACILITY_MAP.keys()],
|
||||||
|
'default': SyslogFacility.USER,
|
||||||
|
'required': True,
|
||||||
|
},
|
||||||
|
'host': {
|
||||||
|
'name': _('Hostname'),
|
||||||
|
'type': 'string',
|
||||||
|
'required': True,
|
||||||
|
},
|
||||||
|
'port': {
|
||||||
|
'name': _('Port'),
|
||||||
|
'type': 'int',
|
||||||
|
'min': 1,
|
||||||
|
'max': 65535,
|
||||||
|
'default': 514,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
# Define our template arguments
|
||||||
|
template_args = dict(NotifyBase.template_args, **{
|
||||||
|
'facility': {
|
||||||
|
# We map back to the same element defined in template_tokens
|
||||||
|
'alias_of': 'facility',
|
||||||
|
},
|
||||||
|
'logpid': {
|
||||||
|
'name': _('Log PID'),
|
||||||
|
'type': 'bool',
|
||||||
|
'default': True,
|
||||||
|
'map_to': 'log_pid',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
def __init__(self, facility=None, log_pid=True, **kwargs):
|
||||||
|
"""
|
||||||
|
Initialize RSyslog Object
|
||||||
|
"""
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
if facility:
|
||||||
|
try:
|
||||||
|
self.facility = SYSLOG_FACILITY_MAP[facility]
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
msg = 'An invalid syslog facility ' \
|
||||||
|
'({}) was specified.'.format(facility)
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.facility = \
|
||||||
|
SYSLOG_FACILITY_MAP[
|
||||||
|
self.template_tokens['facility']['default']]
|
||||||
|
|
||||||
|
# Include PID with each message.
|
||||||
|
self.log_pid = log_pid
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
|
"""
|
||||||
|
Perform RSyslog Notification
|
||||||
|
"""
|
||||||
|
|
||||||
|
if title:
|
||||||
|
# Format title
|
||||||
|
body = '{}: {}'.format(title, body)
|
||||||
|
|
||||||
|
# Always call throttle before any remote server i/o is made
|
||||||
|
self.throttle()
|
||||||
|
host = self.host
|
||||||
|
port = self.port if self.port \
|
||||||
|
else self.template_tokens['port']['default']
|
||||||
|
|
||||||
|
if self.log_pid:
|
||||||
|
payload = '<%d>- %d - %s' % (
|
||||||
|
SYSLOG_PUBLISH_MAP[notify_type] + self.facility * 8,
|
||||||
|
os.getpid(), body)
|
||||||
|
|
||||||
|
else:
|
||||||
|
payload = '<%d>- %s' % (
|
||||||
|
SYSLOG_PUBLISH_MAP[notify_type] + self.facility * 8, body)
|
||||||
|
|
||||||
|
# send UDP packet to upstream server
|
||||||
|
self.logger.debug(
|
||||||
|
'RSyslog Host: %s:%d/%s',
|
||||||
|
host, port, SYSLOG_FACILITY_RMAP[self.facility])
|
||||||
|
self.logger.debug('RSyslog Payload: %s' % str(payload))
|
||||||
|
|
||||||
|
# our sent bytes
|
||||||
|
sent = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
sock.settimeout(self.socket_connect_timeout)
|
||||||
|
sent = sock.sendto(payload.encode('utf-8'), (host, port))
|
||||||
|
sock.close()
|
||||||
|
|
||||||
|
except socket.gaierror as e:
|
||||||
|
self.logger.warning(
|
||||||
|
'A connection error occurred sending RSyslog '
|
||||||
|
'notification to %s:%d/%s', host, port,
|
||||||
|
SYSLOG_FACILITY_RMAP[self.facility]
|
||||||
|
)
|
||||||
|
self.logger.debug('Socket Exception: %s' % str(e))
|
||||||
|
return False
|
||||||
|
|
||||||
|
except socket.timeout as e:
|
||||||
|
self.logger.warning(
|
||||||
|
'A connection timeout occurred sending RSyslog '
|
||||||
|
'notification to %s:%d/%s', host, port,
|
||||||
|
SYSLOG_FACILITY_RMAP[self.facility]
|
||||||
|
)
|
||||||
|
self.logger.debug('Socket Exception: %s' % str(e))
|
||||||
|
return False
|
||||||
|
|
||||||
|
if sent < len(payload):
|
||||||
|
self.logger.warning(
|
||||||
|
'RSyslog sent %d byte(s) but intended to send %d byte(s)',
|
||||||
|
sent, len(payload))
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.logger.info('Sent RSyslog notification.')
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def url(self, privacy=False, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Returns the URL built dynamically based on specified arguments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Define any URL parameters
|
||||||
|
params = {
|
||||||
|
'logpid': 'yes' if self.log_pid else 'no',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extend our parameters
|
||||||
|
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
|
||||||
|
|
||||||
|
return '{schema}://{hostname}{port}/{facility}/?{params}'.format(
|
||||||
|
schema=self.protocol,
|
||||||
|
hostname=NotifyRSyslog.quote(self.host, safe=''),
|
||||||
|
port='' if self.port is None
|
||||||
|
or self.port == self.template_tokens['port']['default']
|
||||||
|
else ':{}'.format(self.port),
|
||||||
|
facility=self.template_tokens['facility']['default']
|
||||||
|
if self.facility not in SYSLOG_FACILITY_RMAP
|
||||||
|
else SYSLOG_FACILITY_RMAP[self.facility],
|
||||||
|
params=NotifyRSyslog.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, verify_host=False)
|
||||||
|
if not results:
|
||||||
|
# We're done early as we couldn't load the results
|
||||||
|
return results
|
||||||
|
|
||||||
|
tokens = []
|
||||||
|
|
||||||
|
# Get our path values
|
||||||
|
tokens.extend(NotifyRSyslog.split_path(results['fullpath']))
|
||||||
|
|
||||||
|
# Initialization
|
||||||
|
facility = None
|
||||||
|
|
||||||
|
if tokens:
|
||||||
|
# Store the last entry as the facility
|
||||||
|
facility = tokens[-1].lower()
|
||||||
|
|
||||||
|
# However if specified on the URL, that will over-ride what was
|
||||||
|
# identified
|
||||||
|
if 'facility' in results['qsd'] and len(results['qsd']['facility']):
|
||||||
|
facility = results['qsd']['facility'].lower()
|
||||||
|
|
||||||
|
if facility and facility not in SYSLOG_FACILITY_MAP:
|
||||||
|
# Find first match; if no match is found we set the result
|
||||||
|
# to the matching key. This allows us to throw a TypeError
|
||||||
|
# during the __init__() call. The benifit of doing this
|
||||||
|
# check here is if we do have a valid match, we can support
|
||||||
|
# short form matches like 'u' which will match against user
|
||||||
|
facility = next((f for f in SYSLOG_FACILITY_MAP.keys()
|
||||||
|
if f.startswith(facility)), facility)
|
||||||
|
|
||||||
|
# Save facility if set
|
||||||
|
if facility:
|
||||||
|
results['facility'] = facility
|
||||||
|
|
||||||
|
# Include PID as part of the message logged
|
||||||
|
results['log_pid'] = parse_bool(
|
||||||
|
results['qsd'].get(
|
||||||
|
'logpid',
|
||||||
|
NotifyRSyslog.template_args['logpid']['default']))
|
||||||
|
|
||||||
|
return results
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -30,7 +26,6 @@
|
||||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
# POSSIBILITY OF SUCH DAMAGE.
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
#
|
|
||||||
# 1. Visit https://www.reddit.com/prefs/apps and scroll to the bottom
|
# 1. Visit https://www.reddit.com/prefs/apps and scroll to the bottom
|
||||||
# 2. Click on the button that reads 'are you a developer? create an app...'
|
# 2. Click on the button that reads 'are you a developer? create an app...'
|
||||||
# 3. Set the mode to `script`,
|
# 3. Set the mode to `script`,
|
||||||
|
@ -56,6 +51,7 @@ import requests
|
||||||
from json import loads
|
from json import loads
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from datetime import timezone
|
||||||
|
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
from ..URLBase import PrivacyMode
|
from ..URLBase import PrivacyMode
|
||||||
|
@ -133,12 +129,6 @@ class NotifyReddit(NotifyBase):
|
||||||
# still allow to make.
|
# still allow to make.
|
||||||
request_rate_per_sec = 0
|
request_rate_per_sec = 0
|
||||||
|
|
||||||
# For Tracking Purposes
|
|
||||||
ratelimit_reset = datetime.utcnow()
|
|
||||||
|
|
||||||
# Default to 1.0
|
|
||||||
ratelimit_remaining = 1.0
|
|
||||||
|
|
||||||
# Taken right from google.auth.helpers:
|
# Taken right from google.auth.helpers:
|
||||||
clock_skew = timedelta(seconds=10)
|
clock_skew = timedelta(seconds=10)
|
||||||
|
|
||||||
|
@ -185,6 +175,7 @@ class NotifyReddit(NotifyBase):
|
||||||
'targets': {
|
'targets': {
|
||||||
'name': _('Targets'),
|
'name': _('Targets'),
|
||||||
'type': 'list:string',
|
'type': 'list:string',
|
||||||
|
'required': True,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -275,7 +266,7 @@ class NotifyReddit(NotifyBase):
|
||||||
# Our keys we build using the provided content
|
# Our keys we build using the provided content
|
||||||
self.__refresh_token = None
|
self.__refresh_token = None
|
||||||
self.__access_token = None
|
self.__access_token = None
|
||||||
self.__access_token_expiry = datetime.utcnow()
|
self.__access_token_expiry = datetime.now(timezone.utc)
|
||||||
|
|
||||||
self.kind = kind.strip().lower() \
|
self.kind = kind.strip().lower() \
|
||||||
if isinstance(kind, str) \
|
if isinstance(kind, str) \
|
||||||
|
@ -324,6 +315,13 @@ class NotifyReddit(NotifyBase):
|
||||||
if not self.subreddits:
|
if not self.subreddits:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'No subreddits were identified to be notified')
|
'No subreddits were identified to be notified')
|
||||||
|
|
||||||
|
# For Rate Limit Tracking Purposes
|
||||||
|
self.ratelimit_reset = datetime.now(timezone.utc).replace(tzinfo=None)
|
||||||
|
|
||||||
|
# Default to 1.0
|
||||||
|
self.ratelimit_remaining = 1.0
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def url(self, privacy=False, *args, **kwargs):
|
def url(self, privacy=False, *args, **kwargs):
|
||||||
|
@ -367,6 +365,12 @@ class NotifyReddit(NotifyBase):
|
||||||
params=NotifyReddit.urlencode(params),
|
params=NotifyReddit.urlencode(params),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
return len(self.subreddits)
|
||||||
|
|
||||||
def login(self):
|
def login(self):
|
||||||
"""
|
"""
|
||||||
A simple wrapper to authenticate with the Reddit Server
|
A simple wrapper to authenticate with the Reddit Server
|
||||||
|
@ -411,10 +415,10 @@ class NotifyReddit(NotifyBase):
|
||||||
if 'expires_in' in response:
|
if 'expires_in' in response:
|
||||||
delta = timedelta(seconds=int(response['expires_in']))
|
delta = timedelta(seconds=int(response['expires_in']))
|
||||||
self.__access_token_expiry = \
|
self.__access_token_expiry = \
|
||||||
delta + datetime.utcnow() - self.clock_skew
|
delta + datetime.now(timezone.utc) - self.clock_skew
|
||||||
else:
|
else:
|
||||||
self.__access_token_expiry = self.access_token_lifetime_sec + \
|
self.__access_token_expiry = self.access_token_lifetime_sec + \
|
||||||
datetime.utcnow() - self.clock_skew
|
datetime.now(timezone.utc) - self.clock_skew
|
||||||
|
|
||||||
# The Refresh Token
|
# The Refresh Token
|
||||||
self.__refresh_token = response.get(
|
self.__refresh_token = response.get(
|
||||||
|
@ -538,10 +542,10 @@ class NotifyReddit(NotifyBase):
|
||||||
# Determine how long we should wait for or if we should wait at
|
# Determine how long we should wait for or if we should wait at
|
||||||
# all. This isn't fool-proof because we can't be sure the client
|
# all. This isn't fool-proof because we can't be sure the client
|
||||||
# time (calling this script) is completely synced up with the
|
# time (calling this script) is completely synced up with the
|
||||||
# Gitter server. One would hope we're on NTP and our clocks are
|
# Reddit server. One would hope we're on NTP and our clocks are
|
||||||
# the same allowing this to role smoothly:
|
# the same allowing this to role smoothly:
|
||||||
|
|
||||||
now = datetime.utcnow()
|
now = datetime.now(timezone.utc).replace(tzinfo=None)
|
||||||
if now < self.ratelimit_reset:
|
if now < self.ratelimit_reset:
|
||||||
# We need to throttle for the difference in seconds
|
# We need to throttle for the difference in seconds
|
||||||
wait = abs(
|
wait = abs(
|
||||||
|
@ -665,8 +669,9 @@ class NotifyReddit(NotifyBase):
|
||||||
self.ratelimit_remaining = \
|
self.ratelimit_remaining = \
|
||||||
float(r.headers.get(
|
float(r.headers.get(
|
||||||
'X-RateLimit-Remaining'))
|
'X-RateLimit-Remaining'))
|
||||||
self.ratelimit_reset = datetime.utcfromtimestamp(
|
self.ratelimit_reset = datetime.fromtimestamp(
|
||||||
int(r.headers.get('X-RateLimit-Reset')))
|
int(r.headers.get('X-RateLimit-Reset')), timezone.utc
|
||||||
|
).replace(tzinfo=None)
|
||||||
|
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
# This is returned if we could not retrieve this information
|
# This is returned if we could not retrieve this information
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -348,6 +344,13 @@ class NotifyRocketChat(NotifyBase):
|
||||||
params=NotifyRocketChat.urlencode(params),
|
params=NotifyRocketChat.urlencode(params),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
targets = len(self.channels) + len(self.rooms) + len(self.users)
|
||||||
|
return targets if targets > 0 else 1
|
||||||
|
|
||||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||||
"""
|
"""
|
||||||
wrapper to _send since we can alert more then one channel
|
wrapper to _send since we can alert more then one channel
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -91,7 +87,7 @@ class NotifyRyver(NotifyBase):
|
||||||
# Define object templates
|
# Define object templates
|
||||||
templates = (
|
templates = (
|
||||||
'{schema}://{organization}/{token}',
|
'{schema}://{organization}/{token}',
|
||||||
'{schema}://{user}@{organization}/{token}',
|
'{schema}://{botname}@{organization}/{token}',
|
||||||
)
|
)
|
||||||
|
|
||||||
# Define our template tokens
|
# Define our template tokens
|
||||||
|
@ -109,9 +105,10 @@ class NotifyRyver(NotifyBase):
|
||||||
'private': True,
|
'private': True,
|
||||||
'regex': (r'^[A-Z0-9]{15}$', 'i'),
|
'regex': (r'^[A-Z0-9]{15}$', 'i'),
|
||||||
},
|
},
|
||||||
'user': {
|
'botname': {
|
||||||
'name': _('Bot Name'),
|
'name': _('Bot Name'),
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
|
'map_to': 'user',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -89,6 +85,7 @@ import base64
|
||||||
import requests
|
import requests
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from datetime import timezone
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
|
@ -135,6 +132,9 @@ class NotifySES(NotifyBase):
|
||||||
# A URL that takes you to the setup/help of the specific protocol
|
# A URL that takes you to the setup/help of the specific protocol
|
||||||
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_ses'
|
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_ses'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# AWS is pretty good for handling data load so request limits
|
# AWS is pretty good for handling data load so request limits
|
||||||
# can occur in much shorter bursts
|
# can occur in much shorter bursts
|
||||||
request_rate_per_sec = 2.5
|
request_rate_per_sec = 2.5
|
||||||
|
@ -156,6 +156,7 @@ class NotifySES(NotifyBase):
|
||||||
'name': _('From Email'),
|
'name': _('From Email'),
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'map_to': 'from_addr',
|
'map_to': 'from_addr',
|
||||||
|
'required': True,
|
||||||
},
|
},
|
||||||
'access_key_id': {
|
'access_key_id': {
|
||||||
'name': _('Access Key ID'),
|
'name': _('Access Key ID'),
|
||||||
|
@ -173,6 +174,7 @@ class NotifySES(NotifyBase):
|
||||||
'name': _('Region'),
|
'name': _('Region'),
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'regex': (r'^[a-z]{2}-[a-z-]+?-[0-9]+$', 'i'),
|
'regex': (r'^[a-z]{2}-[a-z-]+?-[0-9]+$', 'i'),
|
||||||
|
'required': True,
|
||||||
'map_to': 'region_name',
|
'map_to': 'region_name',
|
||||||
},
|
},
|
||||||
'targets': {
|
'targets': {
|
||||||
|
@ -424,7 +426,8 @@ class NotifySES(NotifyBase):
|
||||||
content = MIMEText(body, 'plain', 'utf-8')
|
content = MIMEText(body, 'plain', 'utf-8')
|
||||||
|
|
||||||
# Create a Multipart container if there is an attachment
|
# Create a Multipart container if there is an attachment
|
||||||
base = MIMEMultipart() if attach else content
|
base = MIMEMultipart() \
|
||||||
|
if attach and self.attachment_support else content
|
||||||
|
|
||||||
# TODO: Deduplicate with `NotifyEmail`?
|
# TODO: Deduplicate with `NotifyEmail`?
|
||||||
base['Subject'] = Header(title, 'utf-8')
|
base['Subject'] = Header(title, 'utf-8')
|
||||||
|
@ -436,10 +439,11 @@ class NotifySES(NotifyBase):
|
||||||
base['Reply-To'] = formataddr(reply_to, charset='utf-8')
|
base['Reply-To'] = formataddr(reply_to, charset='utf-8')
|
||||||
base['Cc'] = ','.join(cc)
|
base['Cc'] = ','.join(cc)
|
||||||
base['Date'] = \
|
base['Date'] = \
|
||||||
datetime.utcnow().strftime("%a, %d %b %Y %H:%M:%S +0000")
|
datetime.now(
|
||||||
|
timezone.utc).strftime("%a, %d %b %Y %H:%M:%S +0000")
|
||||||
base['X-Application'] = self.app_id
|
base['X-Application'] = self.app_id
|
||||||
|
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
# First attach our body to our content as the first element
|
# First attach our body to our content as the first element
|
||||||
base.attach(content)
|
base.attach(content)
|
||||||
|
|
||||||
|
@ -585,7 +589,7 @@ class NotifySES(NotifyBase):
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get a reference time (used for header construction)
|
# Get a reference time (used for header construction)
|
||||||
reference = datetime.utcnow()
|
reference = datetime.now(timezone.utc)
|
||||||
|
|
||||||
# Provide Content-Length
|
# Provide Content-Length
|
||||||
headers['Content-Length'] = str(len(payload))
|
headers['Content-Length'] = str(len(payload))
|
||||||
|
@ -816,6 +820,13 @@ class NotifySES(NotifyBase):
|
||||||
params=NotifySES.urlencode(params),
|
params=NotifySES.urlencode(params),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
targets = len(self.targets)
|
||||||
|
return targets if targets > 0 else 1
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# BSD 3-Clause License
|
# BSD 2-Clause License
|
||||||
#
|
#
|
||||||
# Apprise - Push Notification Library.
|
# Apprise - Push Notification Library.
|
||||||
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
# Copyright (c) 2023, Chris Caron <lead2gold@gmail.com>
|
||||||
|
@ -14,10 +14,6 @@
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# 3. Neither the name of the copyright holder nor the names of its
|
|
||||||
# contributors may be used to endorse or promote products derived from
|
|
||||||
# this software without specific prior written permission.
|
|
||||||
#
|
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
@ -73,6 +69,22 @@ SMSEAGLE_PRIORITY_MAP = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SMSEagleCategory:
|
||||||
|
"""
|
||||||
|
We define the different category types that we can notify via SMS Eagle
|
||||||
|
"""
|
||||||
|
PHONE = 'phone'
|
||||||
|
GROUP = 'group'
|
||||||
|
CONTACT = 'contact'
|
||||||
|
|
||||||
|
|
||||||
|
SMSEAGLE_CATEGORIES = (
|
||||||
|
SMSEagleCategory.PHONE,
|
||||||
|
SMSEagleCategory.GROUP,
|
||||||
|
SMSEagleCategory.CONTACT,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class NotifySMSEagle(NotifyBase):
|
class NotifySMSEagle(NotifyBase):
|
||||||
"""
|
"""
|
||||||
A wrapper for SMSEagle Notifications
|
A wrapper for SMSEagle Notifications
|
||||||
|
@ -96,6 +108,9 @@ class NotifySMSEagle(NotifyBase):
|
||||||
# The path we send our notification to
|
# The path we send our notification to
|
||||||
notify_path = '/jsonrpc/sms'
|
notify_path = '/jsonrpc/sms'
|
||||||
|
|
||||||
|
# Support attachments
|
||||||
|
attachment_support = True
|
||||||
|
|
||||||
# The maxumum length of the text message
|
# The maxumum length of the text message
|
||||||
# The actual limit is 160 but SMSEagle looks after the handling
|
# The actual limit is 160 but SMSEagle looks after the handling
|
||||||
# of large messages in it's upstream service
|
# of large messages in it's upstream service
|
||||||
|
@ -129,6 +144,7 @@ class NotifySMSEagle(NotifyBase):
|
||||||
'token': {
|
'token': {
|
||||||
'name': _('Access Token'),
|
'name': _('Access Token'),
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
|
'required': True,
|
||||||
},
|
},
|
||||||
'target_phone': {
|
'target_phone': {
|
||||||
'name': _('Target Phone No'),
|
'name': _('Target Phone No'),
|
||||||
|
@ -154,6 +170,7 @@ class NotifySMSEagle(NotifyBase):
|
||||||
'targets': {
|
'targets': {
|
||||||
'name': _('Targets'),
|
'name': _('Targets'),
|
||||||
'type': 'list:string',
|
'type': 'list:string',
|
||||||
|
'required': True,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -322,7 +339,7 @@ class NotifySMSEagle(NotifyBase):
|
||||||
has_error = False
|
has_error = False
|
||||||
|
|
||||||
attachments = []
|
attachments = []
|
||||||
if attach:
|
if attach and self.attachment_support:
|
||||||
for attachment in attach:
|
for attachment in attach:
|
||||||
# Perform some simple error checking
|
# Perform some simple error checking
|
||||||
if not attachment:
|
if not attachment:
|
||||||
|
@ -403,15 +420,15 @@ class NotifySMSEagle(NotifyBase):
|
||||||
batch_size = 1 if not self.batch else self.default_batch_size
|
batch_size = 1 if not self.batch else self.default_batch_size
|
||||||
|
|
||||||
notify_by = {
|
notify_by = {
|
||||||
'phone': {
|
SMSEagleCategory.PHONE: {
|
||||||
"method": "sms.send_sms",
|
"method": "sms.send_sms",
|
||||||
'target': 'to',
|
'target': 'to',
|
||||||
},
|
},
|
||||||
'group': {
|
SMSEagleCategory.GROUP: {
|
||||||
"method": "sms.send_togroup",
|
"method": "sms.send_togroup",
|
||||||
'target': 'groupname',
|
'target': 'groupname',
|
||||||
},
|
},
|
||||||
'contact': {
|
SMSEagleCategory.CONTACT: {
|
||||||
"method": "sms.send_tocontact",
|
"method": "sms.send_tocontact",
|
||||||
'target': 'contactname',
|
'target': 'contactname',
|
||||||
},
|
},
|
||||||
|
@ -420,7 +437,7 @@ class NotifySMSEagle(NotifyBase):
|
||||||
# categories separated into a tuple since notify_by.keys()
|
# categories separated into a tuple since notify_by.keys()
|
||||||
# returns an unpredicable list in Python 2.7 which causes
|
# returns an unpredicable list in Python 2.7 which causes
|
||||||
# tests to fail every so often
|
# tests to fail every so often
|
||||||
for category in ('phone', 'group', 'contact'):
|
for category in SMSEAGLE_CATEGORIES:
|
||||||
# Create a copy of our template
|
# Create a copy of our template
|
||||||
payload = {
|
payload = {
|
||||||
'method': notify_by[category]['method'],
|
'method': notify_by[category]['method'],
|
||||||
|
@ -596,6 +613,28 @@ class NotifySMSEagle(NotifyBase):
|
||||||
params=NotifySMSEagle.urlencode(params),
|
params=NotifySMSEagle.urlencode(params),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
"""
|
||||||
|
Returns the number of targets associated with this notification
|
||||||
|
"""
|
||||||
|
#
|
||||||
|
# Factor batch into calculation
|
||||||
|
#
|
||||||
|
batch_size = 1 if not self.batch else self.default_batch_size
|
||||||
|
if batch_size > 1:
|
||||||
|
# Batches can only be sent by group (you can't combine groups into
|
||||||
|
# a single batch)
|
||||||
|
total_targets = 0
|
||||||
|
for c in SMSEAGLE_CATEGORIES:
|
||||||
|
targets = len(getattr(self, f'target_{c}s'))
|
||||||
|
total_targets += int(targets / batch_size) + \
|
||||||
|
(1 if targets % batch_size else 0)
|
||||||
|
return total_targets
|
||||||
|
|
||||||
|
# Normal batch count; just count the targets
|
||||||
|
return len(self.target_phones) + len(self.target_contacts) + \
|
||||||
|
len(self.target_groups)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
"""
|
"""
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue