diff options
author | morpheus65535 <[email protected]> | 2023-06-07 14:05:42 -0400 |
---|---|---|
committer | morpheus65535 <[email protected]> | 2023-06-07 14:05:42 -0400 |
commit | 07f601f407ef5b9e6fe0b0db842f3bec8c9916b0 (patch) | |
tree | 9d1fa6b5fe1ec006ff063dcb0cbb296e64efebce | |
parent | 0956d401bc11854a2abaeef100ce6e5cf75aeb20 (diff) | |
download | bazarr-07f601f407ef5b9e6fe0b0db842f3bec8c9916b0.tar.gz bazarr-07f601f407ef5b9e6fe0b0db842f3bec8c9916b0.zip |
Updated apprise module to improve notification system. #2163
122 files changed, 7159 insertions, 2991 deletions
diff --git a/libs/apprise/Apprise.py b/libs/apprise/Apprise.py index 39a1ff0aa..8c2cf5330 100644 --- a/libs/apprise/Apprise.py +++ b/libs/apprise/Apprise.py @@ -1,28 +1,37 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - +# 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. + +import asyncio +import concurrent.futures as cf import os from itertools import chain from . import common @@ -43,11 +52,6 @@ from .plugins.NotifyBase import NotifyBase from . import plugins from . import __version__ -# Python v3+ support code made importable, so it can remain backwards -# compatible with Python v2 -# TODO: Review after dropping support for Python 2. -from . import py3compat - class Apprise: """ @@ -369,91 +373,83 @@ class Apprise: such as turning a \n into an actual new line, etc. """ - return py3compat.asyncio.tosync( - self.async_notify( + try: + # Process arguments and build synchronous and asynchronous calls + # (this step can throw internal errors). + sequential_calls, parallel_calls = self._create_notify_calls( body, title, notify_type=notify_type, body_format=body_format, tag=tag, match_always=match_always, attach=attach, - interpret_escapes=interpret_escapes, - ), - debug=self.debug - ) + interpret_escapes=interpret_escapes + ) + + except TypeError: + # No notifications sent, and there was an internal error. + return False + + if not sequential_calls and not parallel_calls: + # Nothing to send + return None + + sequential_result = Apprise._notify_sequential(*sequential_calls) + parallel_result = Apprise._notify_parallel_threadpool(*parallel_calls) + return sequential_result and parallel_result - def async_notify(self, *args, **kwargs): + async def async_notify(self, *args, **kwargs): """ Send a notification to all the plugins previously loaded, for - asynchronous callers. This method is an async method that should be - awaited on, even if it is missing the async keyword in its signature. - (This is omitted to preserve syntax compatibility with Python 2.) + asynchronous callers. The arguments are identical to those of Apprise.notify(). """ try: - coroutines = list( - self._notifyall( - Apprise._notifyhandlerasync, *args, **kwargs)) + # Process arguments and build synchronous and asynchronous calls + # (this step can throw internal errors). + sequential_calls, parallel_calls = self._create_notify_calls( + *args, **kwargs) except TypeError: # No notifications sent, and there was an internal error. - return py3compat.asyncio.toasyncwrapvalue(False) + return False - else: - if len(coroutines) > 0: - # All notifications sent, return False if any failed. - return py3compat.asyncio.notify(coroutines) + if not sequential_calls and not parallel_calls: + # Nothing to send + return None - else: - # No notifications sent. - return py3compat.asyncio.toasyncwrapvalue(None) + sequential_result = Apprise._notify_sequential(*sequential_calls) + parallel_result = \ + await Apprise._notify_parallel_asyncio(*parallel_calls) + return sequential_result and parallel_result - @staticmethod - def _notifyhandler(server, **kwargs): - """ - The synchronous notification sender. Returns True if the notification - sent successfully. + def _create_notify_calls(self, *args, **kwargs): """ + Creates notifications for all the plugins loaded. - try: - # Send notification - return server.notify(**kwargs) - - except TypeError: - # These our our internally thrown notifications - return 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") - return False - - @staticmethod - def _notifyhandlerasync(server, **kwargs): - """ - The asynchronous notification sender. Returns a coroutine that yields - True if the notification sent successfully. + Returns a list of (server, notify() kwargs) tuples for plugins with + parallelism disabled and another list for plugins with parallelism + enabled. """ - if server.asset.async_mode: - return server.async_notify(**kwargs) + all_calls = list(self._create_notify_gen(*args, **kwargs)) - else: - # Send the notification immediately, and wrap the result in a - # coroutine. - status = Apprise._notifyhandler(server, **kwargs) - return py3compat.asyncio.toasyncwrapvalue(status) + # Split into sequential and parallel notify() calls. + sequential, parallel = [], [] + for (server, notify_kwargs) in all_calls: + if server.asset.async_mode: + parallel.append((server, notify_kwargs)) + else: + sequential.append((server, notify_kwargs)) - def _notifyall(self, handler, body, title='', - notify_type=common.NotifyType.INFO, body_format=None, - tag=common.MATCH_ALL_TAG, match_always=True, attach=None, - interpret_escapes=None): - """ - Creates notifications for all the plugins loaded. + return sequential, parallel - Returns a generator that calls handler for each notification. The first - and only argument supplied to handler is the server, and the keyword - arguments are exactly as they would be passed to server.notify(). + def _create_notify_gen(self, body, title='', + notify_type=common.NotifyType.INFO, + body_format=None, tag=common.MATCH_ALL_TAG, + match_always=True, attach=None, + interpret_escapes=None): + """ + Internal generator function for _create_notify_calls(). """ if len(self) == 0: @@ -546,14 +542,121 @@ class Apprise: logger.error(msg) raise TypeError(msg) - yield handler( - server, + kwargs = dict( body=conversion_body_map[server.notify_format], title=conversion_title_map[server.notify_format], notify_type=notify_type, attach=attach, - body_format=body_format, + body_format=body_format ) + yield (server, kwargs) + + @staticmethod + def _notify_sequential(*servers_kwargs): + """ + Process a list of notify() calls sequentially and synchronously. + """ + + success = True + + for (server, kwargs) in servers_kwargs: + try: + # Send notification + result = server.notify(**kwargs) + 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 + def _notify_parallel_threadpool(*servers_kwargs): + """ + 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 + 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) + + if any(isinstance(status, Exception) + and not isinstance(status, TypeError) for status in results): + # 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") + return False + + if any(isinstance(status, TypeError) for status in results): + # These are our internally thrown notifications. + return False + + return all(results) def details(self, lang=None, show_requirements=False, show_disabled=False): """ @@ -581,6 +684,7 @@ class Apprise: 'setup_url': getattr(plugin, 'setup_url', None), # Placeholder - populated below 'details': None, + # Differentiat between what is a custom loaded plugin and # which is native. 'category': getattr(plugin, 'category', None) diff --git a/libs/apprise/AppriseAsset.py b/libs/apprise/AppriseAsset.py index 80bd0656c..34821e278 100644 --- a/libs/apprise/AppriseAsset.py +++ b/libs/apprise/AppriseAsset.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import re from uuid import uuid4 diff --git a/libs/apprise/AppriseAttachment.py b/libs/apprise/AppriseAttachment.py index b808cfaee..0a3913ed0 100644 --- a/libs/apprise/AppriseAttachment.py +++ b/libs/apprise/AppriseAttachment.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. from . import attachment from . import URLBase @@ -170,6 +177,11 @@ class AppriseAttachment: return_status = False continue + elif isinstance(_attachment, AppriseAttachment): + # We were provided a list of Apprise Attachments + # append our content together + instance = _attachment.attachments + elif not isinstance(_attachment, attachment.AttachBase): logger.warning( "An invalid attachment (type={}) was specified.".format( @@ -196,7 +208,11 @@ class AppriseAttachment: continue # Add our initialized plugin to our server listings - self.attachments.append(instance) + if isinstance(instance, list): + self.attachments.extend(instance) + + else: + self.attachments.append(instance) # Return our status return return_status diff --git a/libs/apprise/AppriseConfig.py b/libs/apprise/AppriseConfig.py index f92d31d18..8f2857776 100644 --- a/libs/apprise/AppriseConfig.py +++ b/libs/apprise/AppriseConfig.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. from . import config from . import ConfigBase diff --git a/libs/apprise/AppriseLocale.py b/libs/apprise/AppriseLocale.py index 9da8467b7..ce61d0c9b 100644 --- a/libs/apprise/AppriseLocale.py +++ b/libs/apprise/AppriseLocale.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import ctypes import locale @@ -67,7 +74,7 @@ class LazyTranslation: """ self.text = text - super(LazyTranslation, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def __str__(self): return gettext.gettext(self.text) diff --git a/libs/apprise/URLBase.py b/libs/apprise/URLBase.py index eb4a379e4..4b33920ea 100644 --- a/libs/apprise/URLBase.py +++ b/libs/apprise/URLBase.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import re from .logger import logger @@ -194,7 +201,7 @@ class URLBase: asset if isinstance(asset, AppriseAsset) else AppriseAsset() # Certificate Verification (for SSL calls); default to being enabled - self.verify_certificate = kwargs.get('verify', True) + self.verify_certificate = parse_bool(kwargs.get('verify', True)) # Secure Mode self.secure = kwargs.get('secure', False) @@ -222,24 +229,22 @@ class URLBase: self.password = URLBase.unquote(self.password) # Store our Timeout Variables - if 'socket_read_timeout' in kwargs: + if 'rto' in kwargs: try: - self.socket_read_timeout = \ - float(kwargs.get('socket_read_timeout')) + self.socket_read_timeout = float(kwargs.get('rto')) except (TypeError, ValueError): self.logger.warning( 'Invalid socket read timeout (rto) was specified {}' - .format(kwargs.get('socket_read_timeout'))) + .format(kwargs.get('rto'))) - if 'socket_connect_timeout' in kwargs: + if 'cto' in kwargs: try: - self.socket_connect_timeout = \ - float(kwargs.get('socket_connect_timeout')) + self.socket_connect_timeout = float(kwargs.get('cto')) except (TypeError, ValueError): self.logger.warning( 'Invalid socket connect timeout (cto) was specified {}' - .format(kwargs.get('socket_connect_timeout'))) + .format(kwargs.get('cto'))) if 'tag' in kwargs: # We want to associate some tags with our notification service. @@ -598,7 +603,7 @@ class URLBase: } @staticmethod - def parse_url(url, verify_host=True): + def parse_url(url, verify_host=True, plus_to_space=False): """Parses the URL and returns it broken apart into a dictionary. This is very specific and customized for Apprise. @@ -618,7 +623,8 @@ class URLBase: """ 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) if not results: # We're done; we failed to parse our url @@ -646,11 +652,11 @@ class URLBase: # Store our socket read timeout if specified if 'rto' in results['qsd']: - results['socket_read_timeout'] = results['qsd']['rto'] + results['rto'] = results['qsd']['rto'] # Store our socket connect timeout if specified if 'cto' in results['qsd']: - results['socket_connect_timeout'] = results['qsd']['cto'] + results['cto'] = results['qsd']['cto'] if 'port' in results['qsd']: results['port'] = results['qsd']['port'] @@ -679,6 +685,15 @@ class URLBase: 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): """A simple function that returns a set of all schemas associated with this object based on the object.protocol and diff --git a/libs/apprise/__init__.py b/libs/apprise/__init__.py index 04ae0982d..3a9136e96 100644 --- a/libs/apprise/__init__.py +++ b/libs/apprise/__init__.py @@ -1,33 +1,40 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2022 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. __title__ = 'Apprise' -__version__ = '1.1.0' +__version__ = '1.4.0' __author__ = 'Chris Caron' -__license__ = 'MIT' -__copywrite__ = 'Copyright (C) 2022 Chris Caron <[email protected]>' +__license__ = 'BSD' +__copywrite__ = 'Copyright (C) 2023 Chris Caron <[email protected]>' __email__ = '[email protected]' __status__ = 'Production' diff --git a/libs/apprise/attachment/AttachBase.py b/libs/apprise/attachment/AttachBase.py index 16f6c6429..2b05c8497 100644 --- a/libs/apprise/attachment/AttachBase.py +++ b/libs/apprise/attachment/AttachBase.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import os import time @@ -120,7 +127,7 @@ class AttachBase(URLBase): should be considered expired. """ - super(AttachBase, self).__init__(**kwargs) + super().__init__(**kwargs) if not mimetypes.inited: # Ensure mimetypes has been initialized diff --git a/libs/apprise/attachment/AttachFile.py b/libs/apprise/attachment/AttachFile.py index 20ee15199..f89b915eb 100644 --- a/libs/apprise/attachment/AttachFile.py +++ b/libs/apprise/attachment/AttachFile.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import re import os @@ -50,7 +57,7 @@ class AttachFile(AttachBase): Initialize Local File Attachment Object """ - super(AttachFile, self).__init__(**kwargs) + super().__init__(**kwargs) # Store path but mark it dirty since we have not performed any # verification at this point. diff --git a/libs/apprise/attachment/AttachHTTP.py b/libs/apprise/attachment/AttachHTTP.py index da1d698e8..d8b46ff28 100644 --- a/libs/apprise/attachment/AttachHTTP.py +++ b/libs/apprise/attachment/AttachHTTP.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import re import os @@ -61,7 +68,7 @@ class AttachHTTP(AttachBase): additionally include as part of the server headers to post with """ - super(AttachHTTP, self).__init__(**kwargs) + super().__init__(**kwargs) self.schema = 'https' if self.secure else 'http' @@ -254,7 +261,7 @@ class AttachHTTP(AttachBase): self._temp_file.close() self._temp_file = None - super(AttachHTTP, self).invalidate() + super().invalidate() def url(self, privacy=False, *args, **kwargs): """ diff --git a/libs/apprise/attachment/__init__.py b/libs/apprise/attachment/__init__.py index 7f83769a8..1b0e1bfe6 100644 --- a/libs/apprise/attachment/__init__.py +++ b/libs/apprise/attachment/__init__.py @@ -1,30 +1,36 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import re - from os import listdir from os.path import dirname from os.path import abspath diff --git a/libs/apprise/cli.py b/libs/apprise/cli.py index 0e60e5cd2..a3335bbb5 100644 --- a/libs/apprise/cli.py +++ b/libs/apprise/cli.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- - -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# BSD 3-Clause License +# +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# This code is licensed under the MIT License. +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 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. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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 click import logging @@ -73,28 +80,64 @@ DEFAULT_CONFIG_PATHS = ( '~/.apprise/apprise.yml', '~/.config/apprise/apprise', '~/.config/apprise/apprise.yml', + + # Global Configuration Support + '/etc/apprise', + '/etc/apprise.yml', + '/etc/apprise/apprise', + '/etc/apprise/apprise.yml', ) # Define our paths to search for plugins DEFAULT_PLUGIN_PATHS = ( '~/.apprise/plugins', '~/.config/apprise/plugins', + + # Global Plugin Support + '/var/lib/apprise/plugins', ) # Detect Windows if platform.system() == 'Windows': # Default Config Search Path for Windows Users DEFAULT_CONFIG_PATHS = ( - expandvars('%APPDATA%/Apprise/apprise'), - expandvars('%APPDATA%/Apprise/apprise.yml'), - expandvars('%LOCALAPPDATA%/Apprise/apprise'), - expandvars('%LOCALAPPDATA%/Apprise/apprise.yml'), + expandvars('%APPDATA%\\Apprise\\apprise'), + expandvars('%APPDATA%\\Apprise\\apprise.yml'), + expandvars('%LOCALAPPDATA%\\Apprise\\apprise'), + expandvars('%LOCALAPPDATA%\\Apprise\\apprise.yml'), + + # + # Global Support + # + + # C:\ProgramData\Apprise\ + expandvars('%ALLUSERSPROFILE%\\Apprise\\apprise'), + expandvars('%ALLUSERSPROFILE%\\Apprise\\apprise.yml'), + + # C:\Program Files\Apprise + expandvars('%PROGRAMFILES%\\Apprise\\apprise'), + expandvars('%PROGRAMFILES%\\Apprise\\apprise.yml'), + + # C:\Program Files\Common Files + expandvars('%COMMONPROGRAMFILES%\\Apprise\\apprise'), + expandvars('%COMMONPROGRAMFILES%\\Apprise\\apprise.yml'), ) # Default Plugin Search Path for Windows Users DEFAULT_PLUGIN_PATHS = ( - expandvars('%APPDATA%/Apprise/plugins'), - expandvars('%LOCALAPPDATA%/Apprise/plugins'), + expandvars('%APPDATA%\\Apprise\\plugins'), + expandvars('%LOCALAPPDATA%\\Apprise\\plugins'), + + # + # Global Support + # + + # C:\ProgramData\Apprise\plugins + expandvars('%ALLUSERSPROFILE%\\Apprise\\plugins'), + # C:\Program Files\Apprise\plugins + expandvars('%PROGRAMFILES%\\Apprise\\plugins'), + # C:\Program Files\Common Files + expandvars('%COMMONPROGRAMFILES%\\Apprise\\plugins'), ) diff --git a/libs/apprise/common.py b/libs/apprise/common.py index 958bd22ee..8380c405e 100644 --- a/libs/apprise/common.py +++ b/libs/apprise/common.py @@ -1,28 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - +# 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. # we mirror our base purely for the ability to reset everything; this # is generally only used in testing and should not be used by developers diff --git a/libs/apprise/common.pyi b/libs/apprise/common.pyi index 769573185..862fc4f27 100644 --- a/libs/apprise/common.pyi +++ b/libs/apprise/common.pyi @@ -1,3 +1,7 @@ +import types +import typing as t + + class NotifyType: INFO: NotifyType SUCCESS: NotifyType @@ -12,4 +16,7 @@ class NotifyFormat: class ContentLocation: LOCAL: ContentLocation HOSTED: ContentLocation - INACCESSIBLE: ContentLocation
\ No newline at end of file + INACCESSIBLE: ContentLocation + + +NOTIFY_MODULE_MAP: t.Dict[str, t.Dict[str, t.Union[t.Type["NotifyBase"], types.ModuleType]]] diff --git a/libs/apprise/config/ConfigBase.py b/libs/apprise/config/ConfigBase.py index d504a98dd..5eb73ebcb 100644 --- a/libs/apprise/config/ConfigBase.py +++ b/libs/apprise/config/ConfigBase.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2020 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import os import re @@ -113,7 +120,7 @@ class ConfigBase(URLBase): these 'include' entries to be honored, this value must be set to True. """ - super(ConfigBase, self).__init__(**kwargs) + super().__init__(**kwargs) # Tracks the time the content was last retrieved on. This place a role # for cases where we are not caching our response and are required to @@ -548,7 +555,7 @@ class ConfigBase(URLBase): # Define what a valid line should look like valid_line_re = re.compile( r'^\s*(?P<line>([;#]+(?P<comment>.*))|' - r'(\s*(?P<tags>[^=]+)=|=)?\s*' + r'(\s*(?P<tags>[a-z0-9, \t_-]+)\s*=|=)?\s*' r'(?P<url>[a-z0-9]{2,9}://.*)|' r'include\s+(?P<config>.+))?\s*$', re.I) diff --git a/libs/apprise/config/ConfigFile.py b/libs/apprise/config/ConfigFile.py index 10f0a463c..b2c211766 100644 --- a/libs/apprise/config/ConfigFile.py +++ b/libs/apprise/config/ConfigFile.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import re import os @@ -53,7 +60,7 @@ class ConfigFile(ConfigBase): additionally include as part of the server headers to post with """ - super(ConfigFile, self).__init__(**kwargs) + super().__init__(**kwargs) # Store our file path as it was set self.path = os.path.abspath(os.path.expanduser(path)) diff --git a/libs/apprise/config/ConfigHTTP.py b/libs/apprise/config/ConfigHTTP.py index 795c6fac8..5813b90a9 100644 --- a/libs/apprise/config/ConfigHTTP.py +++ b/libs/apprise/config/ConfigHTTP.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import re import requests @@ -75,7 +82,7 @@ class ConfigHTTP(ConfigBase): additionally include as part of the server headers to post with """ - super(ConfigHTTP, self).__init__(**kwargs) + super().__init__(**kwargs) self.schema = 'https' if self.secure else 'http' diff --git a/libs/apprise/config/ConfigMemory.py b/libs/apprise/config/ConfigMemory.py index c8d49a141..ec44e9b4f 100644 --- a/libs/apprise/config/ConfigMemory.py +++ b/libs/apprise/config/ConfigMemory.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2020 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. from .ConfigBase import ConfigBase from ..AppriseLocale import gettext_lazy as _ @@ -46,7 +53,7 @@ class ConfigMemory(ConfigBase): Memory objects just store the raw configuration in memory. There is no external reference point. It's always considered cached. """ - super(ConfigMemory, self).__init__(**kwargs) + super().__init__(**kwargs) # Store our raw config into memory self.content = content diff --git a/libs/apprise/config/__init__.py b/libs/apprise/config/__init__.py index 783118903..7d03a34a8 100644 --- a/libs/apprise/config/__init__.py +++ b/libs/apprise/config/__init__.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import re from os import listdir diff --git a/libs/apprise/conversion.py b/libs/apprise/conversion.py index 3b692aa60..77c9aa5e5 100644 --- a/libs/apprise/conversion.py +++ b/libs/apprise/conversion.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2022 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import re from markdown import markdown @@ -99,7 +106,7 @@ class HTMLConverter(HTMLParser, object): BLOCK_END = {} def __init__(self, **kwargs): - super(HTMLConverter, self).__init__(**kwargs) + super().__init__(**kwargs) # Shoudl we store the text content or not? self._do_store = True diff --git a/libs/apprise/decorators/CustomNotifyPlugin.py b/libs/apprise/decorators/CustomNotifyPlugin.py index 39fb51a9e..9c8e7cb1d 100644 --- a/libs/apprise/decorators/CustomNotifyPlugin.py +++ b/libs/apprise/decorators/CustomNotifyPlugin.py @@ -1,27 +1,35 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2022 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. + from ..plugins.NotifyBase import NotifyBase from ..utils import URL_DETAILS_RE from ..utils import parse_url @@ -134,7 +142,7 @@ class CustomNotifyPlugin(NotifyBase): """ # init parent - super(CustomNotifyPluginWrapper, self).__init__(**kwargs) + super().__init__(**kwargs) self._default_args = {} diff --git a/libs/apprise/decorators/__init__.py b/libs/apprise/decorators/__init__.py index a6ef9662a..699fd0da4 100644 --- a/libs/apprise/decorators/__init__.py +++ b/libs/apprise/decorators/__init__.py @@ -1,27 +1,35 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2022 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. + from .notify import notify diff --git a/libs/apprise/decorators/notify.py b/libs/apprise/decorators/notify.py index 3705e8708..36842b419 100644 --- a/libs/apprise/decorators/notify.py +++ b/libs/apprise/decorators/notify.py @@ -1,27 +1,35 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2022 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. + from .CustomNotifyPlugin import CustomNotifyPlugin diff --git a/libs/apprise/logger.py b/libs/apprise/logger.py index 4510e1b60..005a3e0d7 100644 --- a/libs/apprise/logger.py +++ b/libs/apprise/logger.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import os import logging @@ -181,6 +188,7 @@ class LogCapture: if self.__path: # Close our file pointer self.__buffer_ptr.close() + self.__handler.close() if self.__delete: try: # Always remove file afterwards diff --git a/libs/apprise/plugins/NotifyAppriseAPI.py b/libs/apprise/plugins/NotifyAppriseAPI.py index 10d52d5ba..b8765496f 100644 --- a/libs/apprise/plugins/NotifyAppriseAPI.py +++ b/libs/apprise/plugins/NotifyAppriseAPI.py @@ -1,31 +1,39 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2021 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import re import requests from json import dumps +import base64 from .NotifyBase import NotifyBase from ..URLBase import PrivacyMode @@ -35,6 +43,20 @@ from ..utils import validate_regex 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): """ A wrapper for Apprise (Persistent) API Notifications @@ -57,7 +79,7 @@ class NotifyAppriseAPI(NotifyBase): # Depending on the number of transactions/notifications taking place, this # 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 # local anyway @@ -112,6 +134,12 @@ class NotifyAppriseAPI(NotifyBase): 'name': _('Tags'), 'type': 'string', }, + 'method': { + 'name': _('Query Method'), + 'type': 'choice:string', + 'values': APPRISE_API_METHODS, + 'default': APPRISE_API_METHODS[0], + }, 'to': { 'alias_of': 'token', }, @@ -125,7 +153,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 @@ -133,7 +162,7 @@ class NotifyAppriseAPI(NotifyBase): additionally include as part of the server headers to post with """ - super(NotifyAppriseAPI, self).__init__(**kwargs) + super().__init__(**kwargs) self.fullpath = kwargs.get('fullpath') if not isinstance(self.fullpath, str): @@ -147,6 +176,14 @@ class NotifyAppriseAPI(NotifyBase): self.logger.warning(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 self.__tags = parse_list(tags) @@ -162,8 +199,13 @@ class NotifyAppriseAPI(NotifyBase): Returns the URL built dynamically based on specified arguments. """ - # Our URL parameters - params = self.url_parameters(privacy=privacy, *args, **kwargs) + # Define any URL parameters + params = { + 'method': self.method, + } + + # Extend our parameters + params.update(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()}) @@ -202,15 +244,61 @@ class NotifyAppriseAPI(NotifyBase): token=self.pprint(self.token, privacy, safe=''), 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 """ - headers = {} + # Prepare HTTP Headers + headers = { + 'User-Agent': self.app_id, + } + # Apply any/all header over-rides defined headers.update(self.headers) + attachments = [] + files = [] + if attach: + 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 payload = { # Apprise API Payload @@ -220,6 +308,11 @@ class NotifyAppriseAPI(NotifyBase): 'format': self.notify_format, } + if self.method == AppriseAPIMethod.JSON: + headers['Content-Type'] = 'application/json' + payload['attachments'] = attachments + payload = dumps(payload) + if self.__tags: payload['tag'] = self.__tags @@ -240,8 +333,8 @@ class NotifyAppriseAPI(NotifyBase): # Some entries can not be over-ridden headers.update({ - 'User-Agent': self.app_id, - 'Content-Type': 'application/json', + # Our response to be in JSON format always + 'Accept': 'application/json', # Pass our Source UUID4 Identifier 'X-Apprise-ID': self.asset._uid, # Pass our current recursion count to our upstream server @@ -259,9 +352,10 @@ class NotifyAppriseAPI(NotifyBase): try: r = requests.post( url, - data=dumps(payload), + data=payload, headers=headers, auth=auth, + files=files if files else None, verify=self.verify_certificate, timeout=self.request_timeout, ) @@ -283,7 +377,8 @@ class NotifyAppriseAPI(NotifyBase): return False else: - self.logger.info('Sent Apprise API notification.') + self.logger.info( + 'Sent Apprise API notification; method=%s.', self.method) except requests.RequestException as e: self.logger.warning( @@ -294,6 +389,18 @@ class NotifyAppriseAPI(NotifyBase): # Return; we're done 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 @staticmethod @@ -370,4 +477,9 @@ class NotifyAppriseAPI(NotifyBase): # re-assemble our full path 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 diff --git a/libs/apprise/plugins/NotifyBark.py b/libs/apprise/plugins/NotifyBark.py index d6283c022..f1c6d7bf9 100644 --- a/libs/apprise/plugins/NotifyBark.py +++ b/libs/apprise/plugins/NotifyBark.py @@ -1,27 +1,35 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2022 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. + # # API: https://github.com/Finb/bark-server/blob/master/docs/API_V2.md#python # @@ -204,7 +212,7 @@ class NotifyBark(NotifyBase): """ Initialize Notify Bark Object """ - super(NotifyBark, self).__init__(**kwargs) + super().__init__(**kwargs) # Prepare our URL self.notify_url = '%s://%s%s/push' % ( @@ -272,7 +280,7 @@ class NotifyBark(NotifyBase): # error tracking (used for function return) has_error = False - if not len(self.targets): + if not self.targets: # We have nothing to notify; we're done self.logger.warning('There are no Bark devices to notify') return False @@ -448,6 +456,12 @@ class NotifyBark(NotifyBase): params=NotifyBark.urlencode(params), ) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + return len(self.targets) + @staticmethod def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyBase.py b/libs/apprise/plugins/NotifyBase.py index 4bb937795..1b07baa71 100644 --- a/libs/apprise/plugins/NotifyBase.py +++ b/libs/apprise/plugins/NotifyBase.py @@ -1,29 +1,38 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - +# 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. + +import asyncio import re +from functools import partial from ..URLBase import URLBase from ..common import NotifyType @@ -36,12 +45,7 @@ from ..AppriseLocale import gettext_lazy as _ from ..AppriseAttachment import AppriseAttachment -# Wrap our base with the asyncio wrapper -from ..py3compat.asyncio import AsyncNotifyBase -BASE_OBJECT = AsyncNotifyBase - - -class NotifyBase(BASE_OBJECT): +class NotifyBase(URLBase): """ This is the base class for all notification services """ @@ -180,7 +184,7 @@ class NotifyBase(BASE_OBJECT): """ - super(NotifyBase, self).__init__(**kwargs) + super().__init__(**kwargs) if 'format' in kwargs: # Store the specified format if specified @@ -267,19 +271,64 @@ class NotifyBase(BASE_OBJECT): color_type=color_type, ) - def notify(self, body, title=None, notify_type=NotifyType.INFO, - overflow=None, attach=None, body_format=None, **kwargs): + def notify(self, *args, **kwargs): """ Performs notification + """ + try: + # Build a list of dictionaries that can be used to call send(). + send_calls = list(self._build_send_calls(*args, **kwargs)) + + except TypeError: + # Internal error + return False + + else: + # Loop through each call, one at a time. (Use a list rather than a + # generator to call all the partials, even in case of a failure.) + the_calls = [self.send(**kwargs2) for kwargs2 in send_calls] + return all(the_calls) + + async def async_notify(self, *args, **kwargs): + """ + Performs notification for asynchronous callers + """ + try: + # Build a list of dictionaries that can be used to call send(). + send_calls = list(self._build_send_calls(*args, **kwargs)) + + except TypeError: + # Internal error + return False + + else: + loop = asyncio.get_event_loop() + + # Wrap each call in a coroutine that uses the default executor. + # TODO: In the future, allow plugins to supply a native + # async_send() method. + async def do_send(**kwargs2): + send = partial(self.send, **kwargs2) + result = await loop.run_in_executor(None, send) + return result + # gather() all calls in parallel. + the_cors = (do_send(**kwargs2) for kwargs2 in send_calls) + return all(await asyncio.gather(*the_cors)) + + def _build_send_calls(self, body, title=None, + notify_type=NotifyType.INFO, overflow=None, + attach=None, body_format=None, **kwargs): + """ + Get a list of dictionaries that can be used to call send() or + (in the future) async_send(). """ if not self.enabled: # Deny notifications issued to services that are disabled - self.logger.warning( - "{} is currently disabled on this system.".format( - self.service_name)) - return False + msg = f"{self.service_name} is currently disabled on this system." + self.logger.warning(msg) + raise TypeError(msg) # Prepare attachments if required if attach is not None and not isinstance(attach, AppriseAttachment): @@ -288,7 +337,7 @@ class NotifyBase(BASE_OBJECT): except TypeError: # bad attachments - return False + raise # Handle situations where the title is None title = '' if not title else title @@ -299,14 +348,11 @@ class NotifyBase(BASE_OBJECT): body_format=body_format): # Send notification - if not self.send(body=chunk['body'], title=chunk['title'], - notify_type=notify_type, attach=attach, - body_format=body_format): - - # Toggle our return status flag - return False - - return True + yield dict( + body=chunk['body'], title=chunk['title'], + notify_type=notify_type, attach=attach, + body_format=body_format + ) def _apply_overflow(self, body, title=None, overflow=None, body_format=None): @@ -423,13 +469,13 @@ class NotifyBase(BASE_OBJECT): 'overflow': self.overflow_mode, } - params.update(super(NotifyBase, self).url_parameters(*args, **kwargs)) + params.update(super().url_parameters(*args, **kwargs)) # return default parameters return params @staticmethod - def parse_url(url, verify_host=True): + def parse_url(url, verify_host=True, plus_to_space=False): """Parses the URL and returns it broken apart into a dictionary. This is very specific and customized for Apprise. @@ -447,7 +493,8 @@ class NotifyBase(BASE_OBJECT): A dictionary is returned containing the URL fully parsed if successful, otherwise None is returned. """ - results = URLBase.parse_url(url, verify_host=verify_host) + results = URLBase.parse_url( + url, verify_host=verify_host, plus_to_space=plus_to_space) if not results: # We're done; we failed to parse our url diff --git a/libs/apprise/plugins/NotifyBoxcar.py b/libs/apprise/plugins/NotifyBoxcar.py index b40b71cd9..8e7045c7b 100644 --- a/libs/apprise/plugins/NotifyBoxcar.py +++ b/libs/apprise/plugins/NotifyBoxcar.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import re import requests @@ -39,6 +46,7 @@ except ImportError: from .NotifyBase import NotifyBase from ..URLBase import PrivacyMode from ..utils import parse_bool +from ..utils import parse_list from ..utils import validate_regex from ..common import NotifyType from ..common import NotifyImageSize @@ -51,7 +59,7 @@ DEFAULT_TAG = '@all' # 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 # 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. # It's not likely you'll send a message directly to a device, but if you do; @@ -150,10 +158,10 @@ class NotifyBoxcar(NotifyBase): """ Initialize Boxcar Object """ - super(NotifyBoxcar, self).__init__(**kwargs) + super().__init__(**kwargs) # Initialize tag list - self.tags = list() + self._tags = list() # Initialize device_token list self.device_tokens = list() @@ -177,29 +185,27 @@ class NotifyBoxcar(NotifyBase): raise TypeError(msg) if not targets: - self.tags.append(DEFAULT_TAG) + self._tags.append(DEFAULT_TAG) targets = [] - elif isinstance(targets, str): - targets = [x for x in filter(bool, TAGS_LIST_DELIM.split( - targets, - ))] - # Validate targets and drop bad ones: - for target in targets: - if IS_TAG.match(target): + for target in parse_list(targets): + result = IS_TAG.match(target) + if result: # 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 self.device_tokens.append(target) + continue - else: - self.logger.warning( - 'Dropped invalid tag/alias/device_token ' - '({}) specified.'.format(target), - ) + self.logger.warning( + 'Dropped invalid tag/alias/device_token ' + '({}) specified.'.format(target), + ) # Track whether or not we want to send an image with our notification # or not. @@ -231,8 +237,8 @@ class NotifyBoxcar(NotifyBase): if body: payload['aps']['alert'] = body - if self.tags: - payload['tags'] = {'or': self.tags} + if self._tags: + payload['tags'] = {'or': self._tags} if self.device_tokens: payload['device_tokens'] = self.device_tokens @@ -334,10 +340,18 @@ class NotifyBoxcar(NotifyBase): self.secret, privacy, mode=PrivacyMode.Secret, safe=''), targets='/'.join([ 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), ) + 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 def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyBulkSMS.py b/libs/apprise/plugins/NotifyBulkSMS.py index 8fa546421..814badaef 100644 --- a/libs/apprise/plugins/NotifyBulkSMS.py +++ b/libs/apprise/plugins/NotifyBulkSMS.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2022 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # To use this service you will need a BulkSMS account # You will need credits (new accounts start with a few) @@ -30,7 +37,6 @@ # API is documented here: # - https://www.bulksms.com/developer/json/v1/#tag/Message import re -import six import requests import json from itertools import chain @@ -192,7 +198,7 @@ class NotifyBulkSMS(NotifyBase): # Setup our route self.route = self.template_args['route']['default'] \ - if not isinstance(route, six.string_types) else route.upper() + if not isinstance(route, str) else route.upper() if self.route not in BULKSMS_ROUTING_GROUPS: msg = 'The route specified ({}) is invalid.'.format(route) self.logger.warning(msg) @@ -408,6 +414,24 @@ class NotifyBulkSMS(NotifyBase): for x in self.groups])), 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 def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyClickSend.py b/libs/apprise/plugins/NotifyClickSend.py index 9054c6f01..ed6e462fc 100644 --- a/libs/apprise/plugins/NotifyClickSend.py +++ b/libs/apprise/plugins/NotifyClickSend.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # To use this plugin, simply signup with clicksend: # https://www.clicksend.com/ @@ -132,7 +139,7 @@ class NotifyClickSend(NotifyBase): """ Initialize ClickSend Object """ - super(NotifyClickSend, self).__init__(**kwargs) + super().__init__(**kwargs) # Prepare Batch Mode Flag self.batch = batch @@ -281,6 +288,21 @@ class NotifyClickSend(NotifyBase): 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 def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyD7Networks.py b/libs/apprise/plugins/NotifyD7Networks.py index b7575d92e..3d0ee8aa4 100644 --- a/libs/apprise/plugins/NotifyD7Networks.py +++ b/libs/apprise/plugins/NotifyD7Networks.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # To use this service you will need a D7 Networks account from their website # at https://d7networks.com/ @@ -29,17 +36,18 @@ # After you've established your account you can get your api login credentials # (both user and password) from the API Details section from within your # account profile area: https://d7networks.com/accounts/profile/ +# +# API Reference: https://d7networks.com/docs/Messages/Send_Message/ import requests -import base64 from json import dumps from json import loads 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 validate_regex from ..utils import parse_bool from ..AppriseLocale import gettext_lazy as _ @@ -52,25 +60,6 @@ D7NETWORKS_HTTP_ERROR_MAP = { } -# Priorities -class D7SMSPriority: - """ - D7 Networks SMS Message Priority - """ - LOW = 0 - MODERATE = 1 - NORMAL = 2 - HIGH = 3 - - -D7NETWORK_SMS_PRIORITIES = ( - D7SMSPriority.LOW, - D7SMSPriority.MODERATE, - D7SMSPriority.NORMAL, - D7SMSPriority.HIGH, -) - - class NotifyD7Networks(NotifyBase): """ A wrapper for D7 Networks Notifications @@ -92,11 +81,8 @@ class NotifyD7Networks(NotifyBase): # A URL that takes you to the setup/help of the specific protocol setup_url = 'https://github.com/caronc/apprise/wiki/Notify_d7networks' - # D7 Networks batch notification URL - notify_batch_url = 'http://rest-api.d7networks.com/secure/sendbatch' - # D7 Networks single notification URL - notify_url = 'http://rest-api.d7networks.com/secure/send' + notify_url = 'https://api.d7networks.com/messages/v1/send' # The maximum length of the body body_maxlen = 160 @@ -107,21 +93,16 @@ class NotifyD7Networks(NotifyBase): # Define object templates templates = ( - '{schema}://{user}:{password}@{targets}', + '{schema}://{token}@{targets}', ) # Define our template tokens template_tokens = dict(NotifyBase.template_tokens, **{ - 'user': { - 'name': _('Username'), + 'token': { + 'name': _('API Access Token'), 'type': 'string', 'required': True, - }, - 'password': { - 'name': _('Password'), - 'type': 'string', 'private': True, - 'required': True, }, 'target_phone': { 'name': _('Target Phone No'), @@ -138,16 +119,11 @@ class NotifyD7Networks(NotifyBase): # Define our template arguments template_args = dict(NotifyBase.template_args, **{ - 'priority': { - 'name': _('Priority'), - 'type': 'choice:int', - 'min': D7SMSPriority.LOW, - 'max': D7SMSPriority.HIGH, - 'values': D7NETWORK_SMS_PRIORITIES, - - # The website identifies that the default priority is low; so - # this plugin will honor that same default - 'default': D7SMSPriority.LOW, + 'unicode': { + # Unicode characters (default is 'auto') + 'name': _('Unicode Characters'), + 'type': 'bool', + 'default': False, }, 'batch': { 'name': _('Batch Mode'), @@ -172,19 +148,12 @@ class NotifyD7Networks(NotifyBase): }, }) - def __init__(self, targets=None, priority=None, source=None, batch=False, - **kwargs): + def __init__(self, token=None, targets=None, source=None, + batch=False, unicode=None, **kwargs): """ Initialize D7 Networks Object """ - super(NotifyD7Networks, self).__init__(**kwargs) - - # The Priority of the message - if priority not in D7NETWORK_SMS_PRIORITIES: - self.priority = self.template_args['priority']['default'] - - else: - self.priority = priority + super().__init__(**kwargs) # Prepare Batch Mode Flag self.batch = batch @@ -193,8 +162,15 @@ class NotifyD7Networks(NotifyBase): self.source = None \ if not isinstance(source, str) else source.strip() - if not (self.user and self.password): - msg = 'A D7 Networks user/pass was not provided.' + # Define whether or not we should set the unicode flag + self.unicode = self.template_args['unicode']['default'] \ + if unicode is None else bool(unicode) + + # The token associated with the account + self.token = validate_regex(token) + if not self.token: + msg = 'The D7 Networks token specified ({}) is invalid.'\ + .format(token) self.logger.warning(msg) raise TypeError(msg) @@ -229,40 +205,41 @@ class NotifyD7Networks(NotifyBase): # error tracking (used for function return) has_error = False - auth = '{user}:{password}'.format( - user=self.user, password=self.password) - - # Python 3's versio of b64encode() expects a byte array and not - # a string. To accommodate this, we encode the content here - auth = auth.encode('utf-8') - # Prepare our headers headers = { 'User-Agent': self.app_id, + 'Content-Type': 'application/json', 'Accept': 'application/json', - 'Authorization': 'Basic {}'.format(base64.b64encode(auth)) + 'Authorization': f'Bearer {self.token}', } - # Our URL varies depending if we're doing a batch mode or not - url = self.notify_batch_url if self.batch else self.notify_url + payload = { + 'message_globals': { + 'channel': 'sms', + }, + 'messages': [{ + # Populated later on + 'recipients': None, + 'content': body, + 'data_coding': + # auto is a better substitute over 'text' as text is easier to + # detect from a post than `unicode` is. + 'auto' if not self.unicode else 'unicode', + }], + } # use the list directly targets = list(self.targets) + if self.source: + payload['message_globals']['originator'] = self.source + + target = None while len(targets): if self.batch: # Prepare our payload - payload = { - 'globals': { - 'priority': self.priority, - 'from': self.source if self.source else self.app_id, - }, - 'messages': [{ - 'to': self.targets, - 'content': body, - }], - } + payload['messages'][0]['recipients'] = self.targets # Reset our targets so we don't keep going. This is required # because we're in batch mode; we only need to loop once. @@ -274,24 +251,19 @@ class NotifyD7Networks(NotifyBase): target = targets.pop(0) # Prepare our payload - payload = { - 'priority': self.priority, - 'content': body, - 'to': target, - 'from': self.source if self.source else self.app_id, - } + payload['messages'][0]['recipients'] = [target] # Some Debug Logging self.logger.debug( 'D7 Networks POST URL: {} (cert_verify={})'.format( - url, self.verify_certificate)) + self.notify_url, self.verify_certificate)) self.logger.debug('D7 Networks Payload: {}' .format(payload)) # Always call throttle before any remote server i/o is made self.throttle() try: r = requests.post( - url, + self.notify_url, data=dumps(payload), headers=headers, verify=self.verify_certificate, @@ -337,29 +309,9 @@ class NotifyD7Networks(NotifyBase): else: if self.batch: - count = len(self.targets) - try: - # Get our message delivery count if we can - json_response = loads(r.content) - count = int(json_response.get( - 'data', {}).get('messageCount', -1)) - - except (AttributeError, TypeError, ValueError): - # ValueError = r.content is Unparsable - # TypeError = r.content is None - # AttributeError = r is None - - # We could not parse JSON response. Assume that - # our delivery is okay for now. - pass - - if count != len(self.targets): - has_error = True - self.logger.info( 'Sent D7 Networks batch SMS notification to ' - '{} of {} target(s).'.format( - count, len(self.targets))) + '{} target(s).'.format(len(self.targets))) else: self.logger.info( @@ -389,26 +341,31 @@ class NotifyD7Networks(NotifyBase): # Define any URL parameters params = { 'batch': 'yes' if self.batch else 'no', + 'unicode': 'yes' if self.unicode else 'no', } - # Extend our parameters - params.update(self.url_parameters(privacy=privacy, *args, **kwargs)) - - if self.priority != self.template_args['priority']['default']: - params['priority'] = str(self.priority) - if self.source: params['from'] = self.source - return '{schema}://{user}:{password}@{targets}/?{params}'.format( + # Extend our parameters + params.update(self.url_parameters(privacy=privacy, *args, **kwargs)) + + return '{schema}://{token}@{targets}/?{params}'.format( schema=self.secure_protocol, - user=NotifyD7Networks.quote(self.user, safe=''), - password=self.pprint( - self.password, privacy, mode=PrivacyMode.Secret, safe=''), + token=self.pprint(self.token, privacy, safe=''), targets='/'.join( [NotifyD7Networks.quote(x, safe='') for x in self.targets]), 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 def parse_url(url): """ @@ -421,6 +378,23 @@ class NotifyD7Networks(NotifyBase): # We're done early as we couldn't load the results return results + if 'token' in results['qsd'] and len(results['qsd']['token']): + results['token'] = \ + NotifyD7Networks.unquote(results['qsd']['token']) + + elif results['user']: + results['token'] = NotifyD7Networks.unquote(results['user']) + + if results['password']: + # Support token containing a colon (:) + results['token'] += \ + ':' + NotifyD7Networks.unquote(results['password']) + + elif results['password']: + # Support token starting with a colon (:) + results['token'] = \ + ':' + NotifyD7Networks.unquote(results['password']) + # Initialize our targets results['targets'] = list() @@ -432,44 +406,27 @@ class NotifyD7Networks(NotifyBase): results['targets'].extend( NotifyD7Networks.split_path(results['fullpath'])) - # Set our priority - if 'priority' in results['qsd'] and len(results['qsd']['priority']): - _map = { - 'l': D7SMSPriority.LOW, - '0': D7SMSPriority.LOW, - 'm': D7SMSPriority.MODERATE, - '1': D7SMSPriority.MODERATE, - 'n': D7SMSPriority.NORMAL, - '2': D7SMSPriority.NORMAL, - 'h': D7SMSPriority.HIGH, - '3': D7SMSPriority.HIGH, - } - try: - results['priority'] = \ - _map[results['qsd']['priority'][0].lower()] - - except KeyError: - # No priority was set - pass - - # 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'] = \ - NotifyD7Networks.unquote(results['qsd']['from']) - if 'source' in results['qsd'] and len(results['qsd']['source']): - results['source'] = \ - NotifyD7Networks.unquote(results['qsd']['source']) - # Get Batch Mode Flag results['batch'] = \ parse_bool(results['qsd'].get('batch', False)) + # Get Unicode Flag + results['unicode'] = \ + parse_bool(results['qsd'].get('unicode', False)) + # 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'] += \ NotifyD7Networks.parse_phone_no(results['qsd']['to']) + # Support the 'from' and source variable + if 'from' in results['qsd'] and len(results['qsd']['from']): + results['source'] = \ + NotifyD7Networks.unquote(results['qsd']['from']) + + elif 'source' in results['qsd'] and len(results['qsd']['source']): + results['source'] = \ + NotifyD7Networks.unquote(results['qsd']['source']) + return results diff --git a/libs/apprise/plugins/NotifyDBus.py b/libs/apprise/plugins/NotifyDBus.py index b568dfe73..62a1093c8 100644 --- a/libs/apprise/plugins/NotifyDBus.py +++ b/libs/apprise/plugins/NotifyDBus.py @@ -1,31 +1,39 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. from __future__ import absolute_import from __future__ import print_function +import sys from .NotifyBase import NotifyBase from ..common import NotifyImageSize from ..common import NotifyType @@ -77,6 +85,13 @@ try: NOTIFY_DBUS_SUPPORT_ENABLED = ( LOOP_GLIB is not None or LOOP_QT is not None) + # ImportError: When using gi.repository you must not import static modules + # like "gobject". Please change all occurrences of "import gobject" to + # "from gi.repository import GObject". + # See: https://bugzilla.gnome.org/show_bug.cgi?id=709183 + if "gobject" in sys.modules: # pragma: no cover + del sys.modules["gobject"] + try: # The following is required for Image/Icon loading only import gi @@ -233,7 +248,7 @@ class NotifyDBus(NotifyBase): Initialize DBus Object """ - super(NotifyDBus, self).__init__(**kwargs) + super().__init__(**kwargs) # Track our notifications self.registry = {} @@ -272,12 +287,9 @@ class NotifyDBus(NotifyBase): self.x_axis = None self.y_axis = None - # Track whether or not we want to send an image with our notification - # or not. + # Track whether we want to add an image to the notification. self.include_image = include_image - return - def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs): """ Perform DBus Notification @@ -286,10 +298,10 @@ class NotifyDBus(NotifyBase): try: session = SessionBus(mainloop=MAINLOOP_MAP[self.schema]) - except DBusException: + except DBusException as e: # Handle exception self.logger.warning('Failed to send DBus notification.') - self.logger.exception('DBus Exception') + self.logger.debug(f'DBus Exception: {e}') return False # If there is no title, but there is a body, swap the two to get rid @@ -342,8 +354,8 @@ class NotifyDBus(NotifyBase): except Exception as e: self.logger.warning( - "Could not load Gnome notification icon ({}): {}" - .format(icon_path, e)) + "Could not load notification icon (%s).", icon_path) + self.logger.debug(f'DBus Exception: {e}') try: # Always call throttle() before any remote execution is made @@ -370,9 +382,9 @@ class NotifyDBus(NotifyBase): self.logger.info('Sent DBus notification.') - except Exception: + except Exception as e: self.logger.warning('Failed to send DBus notification.') - self.logger.exception('DBus Exception') + self.logger.debug(f'DBus Exception: {e}') return False return True diff --git a/libs/apprise/plugins/NotifyDapnet.py b/libs/apprise/plugins/NotifyDapnet.py index 27ea65cd3..1b718286a 100644 --- a/libs/apprise/plugins/NotifyDapnet.py +++ b/libs/apprise/plugins/NotifyDapnet.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # To use this plugin, sign up with Hampager (you need to be a licensed # ham radio operator @@ -179,7 +186,7 @@ class NotifyDapnet(NotifyBase): """ Initialize Dapnet Object """ - super(NotifyDapnet, self).__init__(**kwargs) + super().__init__(**kwargs) # Parse our targets self.targets = list() @@ -343,6 +350,21 @@ class NotifyDapnet(NotifyBase): 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 def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyDingTalk.py b/libs/apprise/plugins/NotifyDingTalk.py index 68c069479..ae2a9b499 100644 --- a/libs/apprise/plugins/NotifyDingTalk.py +++ b/libs/apprise/plugins/NotifyDingTalk.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2020 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import re import time @@ -124,7 +131,7 @@ class NotifyDingTalk(NotifyBase): """ Initialize DingTalk Object """ - super(NotifyDingTalk, self).__init__(**kwargs) + super().__init__(**kwargs) # Secret Key (associated with project) self.token = validate_regex( @@ -302,6 +309,13 @@ class NotifyDingTalk(NotifyBase): [NotifyDingTalk.quote(x, safe='') for x in self.targets]), 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 def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyDiscord.py b/libs/apprise/plugins/NotifyDiscord.py index 48e2c2836..fff76eef2 100644 --- a/libs/apprise/plugins/NotifyDiscord.py +++ b/libs/apprise/plugins/NotifyDiscord.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # For this to work correctly you need to create a webhook. To do this just # click on the little gear icon next to the channel you're part of. From @@ -164,7 +171,7 @@ class NotifyDiscord(NotifyBase): Initialize Discord Object """ - super(NotifyDiscord, self).__init__(**kwargs) + super().__init__(**kwargs) # Webhook ID (associated with project) self.webhook_id = validate_regex(webhook_id) @@ -283,9 +290,6 @@ class NotifyDiscord(NotifyBase): payload['content'] = \ body if not title else "{}\r\n{}".format(title, body) - if self.thread_id: - 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 @@ -294,7 +298,8 @@ class NotifyDiscord(NotifyBase): # Optionally override the default username of the webhook payload['username'] = self.user - if not self._send(payload): + params = {'thread_id': self.thread_id} if self.thread_id else None + if not self._send(payload, params=params): # We failed to post our message return False @@ -338,7 +343,7 @@ class NotifyDiscord(NotifyBase): # Otherwise return return True - def _send(self, payload, attach=None, **kwargs): + def _send(self, payload, attach=None, params=None, **kwargs): """ Wrapper to the requests (post) object """ @@ -389,6 +394,7 @@ class NotifyDiscord(NotifyBase): r = requests.post( notify_url, + params=params, data=payload if files else dumps(payload), headers=headers, files=files, @@ -580,7 +586,7 @@ class NotifyDiscord(NotifyBase): if description: # Strip description from our string since it has been handled # now. - markdown = re.sub(description, '', markdown, count=1) + markdown = re.sub(re.escape(description), '', markdown, count=1) regex = re.compile( r'\s*#[# \t\v]*(?P<name>[^\n]+)(\n|\s*$)' diff --git a/libs/apprise/plugins/NotifyEmail.py b/libs/apprise/plugins/NotifyEmail.py index 5858f0906..e55de7314 100644 --- a/libs/apprise/plugins/NotifyEmail.py +++ b/libs/apprise/plugins/NotifyEmail.py @@ -1,30 +1,39 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - +# 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. + +import dataclasses import re import smtplib +import typing as t from email.mime.text import MIMEText from email.mime.application import MIMEApplication from email.mime.multipart import MIMEMultipart @@ -41,6 +50,7 @@ from ..common import NotifyFormat, NotifyType from ..conversion import convert_between from ..utils import is_email, parse_emails from ..AppriseLocale import gettext_lazy as _ +from ..logger import logger # Globally Default encoding mode set to Quoted Printable. charset.add_charset('utf-8', charset.QP, charset.QP, 'utf-8') @@ -60,15 +70,23 @@ class WebBaseLogin: # Secure Email Modes class SecureMailMode: + INSECURE = "insecure" SSL = "ssl" STARTTLS = "starttls" # Define all of the secure modes (used during validation) -SECURE_MODES = ( - SecureMailMode.SSL, - SecureMailMode.STARTTLS, -) +SECURE_MODES = { + SecureMailMode.STARTTLS: { + 'default_port': 587, + }, + SecureMailMode.SSL: { + 'default_port': 465, + }, + SecureMailMode.INSECURE: { + 'default_port': 25, + }, +} # To attempt to make this script stupid proof, if we detect an email address # that is part of the this table, we can pre-use a lot more defaults if they @@ -109,7 +127,7 @@ EMAIL_TEMPLATES = ( 'Microsoft Hotmail', re.compile( r'^((?P<label>[^+]+)\+)?(?P<id>[^@]+)@' - r'(?P<domain>(outlook|hotmail|live)\.com(\.au)?)$', re.I), + r'(?P<domain>(hotmail|live)\.com(\.au)?)$', re.I), { 'port': 587, 'smtp_host': 'smtp-mail.outlook.com', @@ -119,6 +137,21 @@ EMAIL_TEMPLATES = ( }, ), + # Microsoft Outlook + ( + 'Microsoft Outlook', + re.compile( + r'^((?P<label>[^+]+)\+)?(?P<id>[^@]+)@' + r'(?P<domain>(smtp\.)?outlook\.com(\.au)?)$', re.I), + { + 'port': 587, + 'smtp_host': 'smtp.outlook.com', + 'secure': True, + 'secure_mode': SecureMailMode.STARTTLS, + 'login_type': (WebBaseLogin.EMAIL, ) + }, + ), + # Microsoft Office 365 (Email Server) # You must specify an authenticated sender address in the from= settings # and a valid email in the to= to deliver your emails to @@ -282,6 +315,13 @@ EMAIL_TEMPLATES = ( ) +class EmailMessage: + recipient: str + to_addrs: t.List[str] + body: str + + class NotifyEmail(NotifyBase): """ A wrapper to Email Notifications @@ -303,15 +343,6 @@ class NotifyEmail(NotifyBase): # Default Notify Format notify_format = NotifyFormat.HTML - # Default Non-Encryption Port - default_port = 25 - - # Default Secure Port - default_secure_port = 587 - - # Default Secure Mode - default_secure_mode = SecureMailMode.STARTTLS - # Default SMTP Timeout (in seconds) socket_connect_timeout = 15 @@ -373,7 +404,7 @@ class NotifyEmail(NotifyBase): 'name': { 'name': _('From Name'), 'type': 'string', - 'map_to': 'from_name', + 'map_to': 'from_addr', }, 'cc': { 'name': _('Carbon Copy'), @@ -410,24 +441,16 @@ class NotifyEmail(NotifyBase): }, } - def __init__(self, smtp_host=None, from_name=None, - from_addr=None, secure_mode=None, targets=None, cc=None, - bcc=None, reply_to=None, headers=None, **kwargs): + def __init__(self, smtp_host=None, from_addr=None, secure_mode=None, + targets=None, cc=None, bcc=None, reply_to=None, headers=None, + **kwargs): """ Initialize Email Object The smtp_host and secure_mode can be automatically detected depending on how the URL was built """ - super(NotifyEmail, self).__init__(**kwargs) - - # Handle SMTP vs SMTPS (Secure vs UnSecure) - if not self.port: - if self.secure: - self.port = self.default_secure_port - - else: - self.port = self.default_port + super().__init__(**kwargs) # Acquire Email 'To' self.targets = list() @@ -451,40 +474,49 @@ class NotifyEmail(NotifyBase): # Now we want to construct the To and From email # addresses from the URL provided - self.from_addr = from_addr + self.from_addr = [False, ''] - if self.user and not self.from_addr: - # detect our email address - self.from_addr = '{}@{}'.format( + if self.user and self.host: + # Prepare the bases of our email + self.from_addr = [self.app_id, '{}@{}'.format( re.split(r'[\s@]+', self.user)[0], self.host, - ) + )] + + if from_addr: + result = is_email(from_addr) + if result: + self.from_addr = ( + result['name'] if result['name'] else False, + result['full_email']) + else: + self.from_addr[0] = from_addr - result = is_email(self.from_addr) + result = is_email(self.from_addr[1]) if not result: # Parse Source domain based on from_addr - msg = 'Invalid ~From~ email specified: {}'.format(self.from_addr) + msg = 'Invalid ~From~ email specified: {}'.format( + '{} <{}>'.format(self.from_addr[0], self.from_addr[1]) + if self.from_addr[0] else '{}'.format(self.from_addr[1])) self.logger.warning(msg) raise TypeError(msg) - # Store our email address - self.from_addr = result['full_email'] - - # Set our from name - self.from_name = from_name if from_name else result['name'] - # Store our lookup - self.names[self.from_addr] = \ - self.from_name if self.from_name else False + self.names[self.from_addr[1]] = self.from_addr[0] # Now detect the SMTP Server self.smtp_host = \ smtp_host if isinstance(smtp_host, str) else '' # Now detect secure mode - self.secure_mode = self.default_secure_mode \ - if not isinstance(secure_mode, str) \ - else secure_mode.lower() + if secure_mode: + self.secure_mode = None \ + if not isinstance(secure_mode, str) \ + else secure_mode.lower() + else: + self.secure_mode = SecureMailMode.INSECURE \ + if not self.secure else self.template_args['mode']['default'] + if self.secure_mode not in SECURE_MODES: msg = 'The secure mode specified ({}) is invalid.'\ .format(secure_mode) @@ -508,8 +540,7 @@ class NotifyEmail(NotifyBase): else: # If our target email list is empty we want to add ourselves to it - self.targets.append( - (self.from_name if self.from_name else False, self.from_addr)) + self.targets.append((False, self.from_addr[1])) # Validate recipients (cc:) and drop bad ones: for recipient in parse_emails(cc): @@ -562,6 +593,15 @@ class NotifyEmail(NotifyBase): # Apply any defaults based on certain known configurations self.NotifyEmailDefaults(secure_mode=secure_mode, **kwargs) + if not self.secure and self.secure_mode != SecureMailMode.INSECURE: + # Enable Secure mode if not otherwise set + self.secure = True + + if not self.port: + # Assign our port based on our secure_mode if not otherwise + # detected + self.port = SECURE_MODES[self.secure_mode]['default_port'] + # if there is still no smtp_host then we fall back to the hostname if not self.smtp_host: self.smtp_host = self.host @@ -625,11 +665,11 @@ class NotifyEmail(NotifyBase): if login_type: # only apply additional logic to our user if a login_type # was specified. - if is_email(self.user) and \ - WebBaseLogin.EMAIL not in login_type: - # Email specified but login type - # not supported; switch it to user id - self.user = match.group('id') + if is_email(self.user): + if WebBaseLogin.EMAIL not in login_type: + # Email specified but login type + # not supported; switch it to user id + self.user = match.group('id') elif WebBaseLogin.USERID not in login_type: # user specified but login type @@ -656,18 +696,14 @@ class NotifyEmail(NotifyBase): Perform Email Notification """ - # Initialize our default from name - from_name = self.from_name if self.from_name else self.app_desc - - # error tracking (used for function return) - has_error = False - if not self.targets: # There is no one to email; we're done self.logger.warning( 'There are no Email recipients to notify') return False + messages: t.List[EmailMessage] = [] + # Create a copy of the targets list emails = list(self.targets) while len(emails): @@ -700,7 +736,9 @@ class NotifyEmail(NotifyBase): for addr in reply_to] self.logger.debug( - 'Email From: {} <{}>'.format(from_name, self.from_addr)) + 'Email From: {}'.format( + formataddr(self.from_addr, charset='utf-8'))) + self.logger.debug('Email To: {}'.format(to_addr)) if cc: self.logger.debug('Email Cc: {}'.format(', '.join(cc))) @@ -763,9 +801,7 @@ class NotifyEmail(NotifyBase): base[k] = Header(v, self._get_charset(v)) base['Subject'] = Header(title, self._get_charset(title)) - base['From'] = formataddr( - (from_name if from_name else False, self.from_addr), - charset='utf-8') + base['From'] = formataddr(self.from_addr, charset='utf-8') base['To'] = formataddr((to_name, to_addr), charset='utf-8') base['Message-ID'] = make_msgid(domain=self.smtp_host) base['Date'] = \ @@ -778,58 +814,79 @@ class NotifyEmail(NotifyBase): if reply_to: base['Reply-To'] = ','.join(reply_to) - # bind the socket variable to the current namespace - socket = None + message = EmailMessage( + recipient=to_addr, + to_addrs=[to_addr] + list(cc) + list(bcc), + body=base.as_string()) + messages.append(message) - # Always call throttle before any remote server i/o is made - self.throttle() + return self.submit(messages) - try: - self.logger.debug('Connecting to remote SMTP server...') - socket_func = smtplib.SMTP - if self.secure and self.secure_mode == SecureMailMode.SSL: - self.logger.debug('Securing connection with SSL...') - socket_func = smtplib.SMTP_SSL + def submit(self, messages: t.List[EmailMessage]): - socket = socket_func( - self.smtp_host, - self.port, - None, - timeout=self.socket_connect_timeout, - ) + # error tracking (used for function return) + has_error = False - if self.secure and self.secure_mode == SecureMailMode.STARTTLS: - # Handle Secure Connections - self.logger.debug('Securing connection with STARTTLS...') - socket.starttls() + # bind the socket variable to the current namespace + socket = None - if self.user and self.password: - # Apply Login credetials - self.logger.debug('Applying user credentials...') - socket.login(self.user, self.password) + # Always call throttle before any remote server i/o is made + self.throttle() - # Send the email - socket.sendmail( - self.from_addr, - [to_addr] + list(cc) + list(bcc), - base.as_string()) + try: + self.logger.debug('Connecting to remote SMTP server...') + socket_func = smtplib.SMTP + if self.secure_mode == SecureMailMode.SSL: + self.logger.debug('Securing connection with SSL...') + socket_func = smtplib.SMTP_SSL - self.logger.info( - 'Sent Email notification to "{}".'.format(to_addr)) + socket = socket_func( + self.smtp_host, + self.port, + None, + timeout=self.socket_connect_timeout, + ) - except (SocketError, smtplib.SMTPException, RuntimeError) as e: - self.logger.warning( - 'A Connection error occurred sending Email ' - 'notification to {}.'.format(self.smtp_host)) - self.logger.debug('Socket Exception: %s' % str(e)) + if self.secure_mode == SecureMailMode.STARTTLS: + # Handle Secure Connections + self.logger.debug('Securing connection with STARTTLS...') + socket.starttls() + + if self.user and self.password: + # Apply Login credetials + self.logger.debug('Applying user credentials...') + socket.login(self.user, self.password) + + # Send the emails + for message in messages: + try: + socket.sendmail( + self.from_addr[1], + message.to_addrs, + message.body) + + self.logger.info( + f'Sent Email notification to "{message.recipient}".') + except (SocketError, smtplib.SMTPException, RuntimeError) as e: + self.logger.warning( + f'Sending email to "{message.recipient}" failed. ' + f'Reason: {e}') + + # Mark as failure + has_error = True + + except (SocketError, smtplib.SMTPException, RuntimeError) as e: + self.logger.warning( + f'Connection error while submitting email to {self.smtp_host}.' + f' Reason: {e}') - # Mark our failure - has_error = True + # Mark as failure + has_error = True - finally: - # Gracefully terminate the connection with the server - if socket is not None: # pragma: no branch - socket.quit() + finally: + # Gracefully terminate the connection with the server + if socket is not None: # pragma: no branch + socket.quit() return not has_error @@ -839,12 +896,7 @@ class NotifyEmail(NotifyBase): """ # Define an URL parameters - params = { - 'from': self.from_addr, - 'mode': self.secure_mode, - 'smtp': self.smtp_host, - 'user': self.user, - } + params = {} # Append our headers into our parameters params.update({'+{}'.format(k): v for k, v in self.headers.items()}) @@ -852,30 +904,60 @@ class NotifyEmail(NotifyBase): # Extend our parameters params.update(self.url_parameters(privacy=privacy, *args, **kwargs)) - if self.from_name: - params['name'] = self.from_name + from_addr = None + if len(self.targets) == 1 and self.targets[0][1] != self.from_addr[1]: + # A custom email was provided + from_addr = self.from_addr[1] + + if self.smtp_host != self.host: + # Apply our SMTP Host only if it differs from the provided hostname + params['smtp'] = self.smtp_host + + if self.secure: + # Mode is only requried if we're dealing with a secure connection + params['mode'] = self.secure_mode + + if self.from_addr[0] and self.from_addr[0] != self.app_id: + # A custom name was provided + params['from'] = self.from_addr[0] if not from_addr else \ + formataddr((self.from_addr[0], from_addr), charset='utf-8') + + elif from_addr: + params['from'] = formataddr((False, from_addr), charset='utf-8') + + elif not self.user: + params['from'] = \ + formataddr((False, self.from_addr[1]), charset='utf-8') if len(self.cc) > 0: # Handle our Carbon Copy Addresses - params['cc'] = ','.join( - ['{}{}'.format( - '' if not e not in self.names - else '{}:'.format(self.names[e]), e) for e in self.cc]) + params['cc'] = ','.join([ + formataddr( + (self.names[e] if e in self.names else False, e), + # Swap comma for it's escaped url code (if detected) since + # we're using that as a delimiter + charset='utf-8').replace(',', '%2C') + for e in self.cc]) if len(self.bcc) > 0: # Handle our Blind Carbon Copy Addresses - params['bcc'] = ','.join( - ['{}{}'.format( - '' if not e not in self.names - else '{}:'.format(self.names[e]), e) for e in self.bcc]) + params['bcc'] = ','.join([ + formataddr( + (self.names[e] if e in self.names else False, e), + # Swap comma for it's escaped url code (if detected) since + # we're using that as a delimiter + charset='utf-8').replace(',', '%2C') + for e in self.bcc]) if self.reply_to: # Handle our Reply-To Addresses - params['reply'] = ','.join( - ['{}{}'.format( - '' if not e not in self.names - else '{}:'.format(self.names[e]), e) - for e in self.reply_to]) + params['reply'] = ','.join([ + formataddr( + (self.names[e] if e in self.names else False, e), + # Swap comma for it's escaped url code (if detected) since + # we're using that as a delimiter + charset='utf-8').replace(',', '%2C') + for e in self.reply_to]) # pull email suffix from username (if present) user = None if not self.user else self.user.split('@')[0] @@ -895,14 +977,13 @@ class NotifyEmail(NotifyBase): ) # Default Port setup - default_port = \ - self.default_secure_port if self.secure else self.default_port + default_port = SECURE_MODES[self.secure_mode]['default_port'] # a simple boolean check as to whether we display our target emails # or not has_targets = \ not (len(self.targets) == 1 - and self.targets[0][1] == self.from_addr) + and self.targets[0][1] == self.from_addr[1]) return '{schema}://{auth}{hostname}{port}/{targets}?{params}'.format( schema=self.secure_protocol if self.secure else self.protocol, @@ -918,6 +999,13 @@ class NotifyEmail(NotifyBase): 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 def parse_url(url): """ @@ -946,14 +1034,24 @@ class NotifyEmail(NotifyBase): if 'from' in results['qsd'] and len(results['qsd']['from']): from_addr = NotifyEmail.unquote(results['qsd']['from']) + if 'name' in results['qsd'] and len(results['qsd']['name']): + # Depricate use of both `from=` and `name=` in the same url as + # they will be synomomus of one another in the future. + from_addr = formataddr( + (NotifyEmail.unquote(results['qsd']['name']), from_addr), + charset='utf-8') + logger.warning( + 'Email name= and from= are synonymous; ' + 'use one or the other.') + + elif 'name' in results['qsd'] and len(results['qsd']['name']): + # Extract from name to associate with from address + 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']) - if 'name' in results['qsd'] and len(results['qsd']['name']): - # Extract from name to associate with from address - results['from_name'] = NotifyEmail.unquote(results['qsd']['name']) - # Store SMTP Host if specified if 'smtp' in results['qsd'] and len(results['qsd']['smtp']): # Extract the smtp server diff --git a/libs/apprise/plugins/NotifyEmby.py b/libs/apprise/plugins/NotifyEmby.py index d609149a9..23d4c6114 100644 --- a/libs/apprise/plugins/NotifyEmby.py +++ b/libs/apprise/plugins/NotifyEmby.py @@ -1,30 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -# For this plugin to work correct, the Emby server must be set up to allow -# for remote connections. +# 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. # Emby Docker configuration: https://hub.docker.com/r/emby/embyserver/ # Authentication: https://github.com/MediaBrowser/Emby/wiki/Authentication @@ -117,7 +121,7 @@ class NotifyEmby(NotifyBase): Initialize Emby Object """ - super(NotifyEmby, self).__init__(**kwargs) + super().__init__(**kwargs) if self.secure: self.schema = 'https' diff --git a/libs/apprise/plugins/NotifyEnigma2.py b/libs/apprise/plugins/NotifyEnigma2.py index f47b67180..10d581792 100644 --- a/libs/apprise/plugins/NotifyEnigma2.py +++ b/libs/apprise/plugins/NotifyEnigma2.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # Sources # - https://dreambox.de/en/ @@ -155,7 +162,7 @@ class NotifyEnigma2(NotifyBase): 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(NotifyEnigma2, self).__init__(**kwargs) + super().__init__(**kwargs) try: self.timeout = int(timeout) diff --git a/libs/apprise/plugins/NotifyFCM/__init__.py b/libs/apprise/plugins/NotifyFCM/__init__.py index eb21028ea..d8857d340 100644 --- a/libs/apprise/plugins/NotifyFCM/__init__.py +++ b/libs/apprise/plugins/NotifyFCM/__init__.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2021 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # For this plugin to work correct, the FCM server must be set up to allow # for remote connections. @@ -218,7 +225,7 @@ class NotifyFCM(NotifyBase): Initialize Firebase Cloud Messaging """ - super(NotifyFCM, self).__init__(**kwargs) + super().__init__(**kwargs) if mode is None: # Detect our mode @@ -548,6 +555,12 @@ class NotifyFCM(NotifyBase): params=NotifyFCM.urlencode(params), ) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + return len(self.targets) + @staticmethod def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyFCM/color.py b/libs/apprise/plugins/NotifyFCM/color.py index 1f096ab03..46d0f2a71 100644 --- a/libs/apprise/plugins/NotifyFCM/color.py +++ b/libs/apprise/plugins/NotifyFCM/color.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2021 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # New priorities are defined here: # - https://firebase.google.com/docs/reference/fcm/rest/v1/\ diff --git a/libs/apprise/plugins/NotifyFCM/common.py b/libs/apprise/plugins/NotifyFCM/common.py index 91bc2a0c5..0ec10eec6 100644 --- a/libs/apprise/plugins/NotifyFCM/common.py +++ b/libs/apprise/plugins/NotifyFCM/common.py @@ -1,27 +1,35 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2021 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. + class FCMMode: """ Define the Firebase Cloud Messaging Modes diff --git a/libs/apprise/plugins/NotifyFCM/oauth.py b/libs/apprise/plugins/NotifyFCM/oauth.py index a948d2fe0..a76bc6987 100644 --- a/libs/apprise/plugins/NotifyFCM/oauth.py +++ b/libs/apprise/plugins/NotifyFCM/oauth.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2021 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # # To generate a private key file for your service account: # diff --git a/libs/apprise/plugins/NotifyFCM/priority.py b/libs/apprise/plugins/NotifyFCM/priority.py index b6652f278..81976cb63 100644 --- a/libs/apprise/plugins/NotifyFCM/priority.py +++ b/libs/apprise/plugins/NotifyFCM/priority.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2021 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # New priorities are defined here: # - https://firebase.google.com/docs/reference/fcm/rest/v1/\ diff --git a/libs/apprise/plugins/NotifyFaast.py b/libs/apprise/plugins/NotifyFaast.py index d34b4800d..3e55e1200 100644 --- a/libs/apprise/plugins/NotifyFaast.py +++ b/libs/apprise/plugins/NotifyFaast.py @@ -1,26 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CON +# 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. import requests @@ -84,7 +92,7 @@ class NotifyFaast(NotifyBase): """ Initialize Faast Object """ - super(NotifyFaast, self).__init__(**kwargs) + super().__init__(**kwargs) # Store the Authentication Token self.authtoken = validate_regex(authtoken) diff --git a/libs/apprise/plugins/NotifyFlock.py b/libs/apprise/plugins/NotifyFlock.py index 2c68cc1c6..60b337e82 100644 --- a/libs/apprise/plugins/NotifyFlock.py +++ b/libs/apprise/plugins/NotifyFlock.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # To use this plugin, you need to first access https://dev.flock.com/webhooks # Specifically https://dev.flock.com/webhooks/incoming @@ -145,7 +152,7 @@ class NotifyFlock(NotifyBase): """ Initialize Flock Object """ - super(NotifyFlock, self).__init__(**kwargs) + super().__init__(**kwargs) # Build ourselves a target list self.targets = list() @@ -327,6 +334,13 @@ class NotifyFlock(NotifyBase): 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 def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyForm.py b/libs/apprise/plugins/NotifyForm.py index 7ea4202c1..3ef8d21b4 100644 --- a/libs/apprise/plugins/NotifyForm.py +++ b/libs/apprise/plugins/NotifyForm.py @@ -1,28 +1,36 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2022 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - +# 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. + +import re import requests from .NotifyBase import NotifyBase @@ -32,13 +40,24 @@ from ..common import NotifyType 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 METHODS = ( 'POST', 'GET', 'DELETE', 'PUT', - 'HEAD' + 'HEAD', + 'PATCH' ) @@ -47,6 +66,27 @@ class NotifyForm(NotifyBase): A wrapper for Form Notifications """ + # Support + # - file* + # - file? + # - file*name + # - file?name + # - ?file + # - *file + # - file + # The code will convert the ? or * to the digit increments + __attach_as_re = re.compile( + r'((?P<match1>(?P<id1a>[a-z0-9_-]+)?' + r'(?P<wc1>[*?+$:.%]+)(?P<id1b>[a-z0-9_-]+))' + r'|(?P<match2>(?P<id2>[a-z0-9_-]+)(?P<wc2>[*?+$:.%]?)))', + re.IGNORECASE) + + # Our count + attach_as_count = '{:02d}' + + # the default attach_as value + attach_as_default = f'file{attach_as_count}' + # The default descriptive name associated with the Notification service_name = 'Form' @@ -66,6 +106,12 @@ class NotifyForm(NotifyBase): # local anyway 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 templates = ( '{schema}://{host}', @@ -111,6 +157,12 @@ class NotifyForm(NotifyBase): 'values': METHODS, 'default': METHODS[0], }, + 'attach-as': { + 'name': _('Attach File As'), + 'type': 'string', + 'default': 'file*', + 'map_to': 'attach_as', + }, }) # Define any kwargs we're using @@ -123,9 +175,14 @@ class NotifyForm(NotifyBase): 'name': _('Payload Extras'), 'prefix': ':', }, + 'params': { + 'name': _('GET Params'), + 'prefix': '-', + }, } - def __init__(self, headers=None, method=None, payload=None, **kwargs): + def __init__(self, headers=None, method=None, payload=None, params=None, + attach_as=None, **kwargs): """ Initialize Form Object @@ -133,7 +190,7 @@ class NotifyForm(NotifyBase): additionally include as part of the server headers to post with """ - super(NotifyForm, self).__init__(**kwargs) + super().__init__(**kwargs) self.fullpath = kwargs.get('fullpath') if not isinstance(self.fullpath, str): @@ -147,15 +204,72 @@ class NotifyForm(NotifyBase): self.logger.warning(msg) raise TypeError(msg) + # Custom File Attachment Over-Ride Support + if not isinstance(attach_as, str): + # Default value + self.attach_as = self.attach_as_default + self.attach_multi_support = True + + else: + result = self.__attach_as_re.match(attach_as.strip()) + if not result: + msg = 'The attach-as specified ({}) is invalid.'.format( + attach_as) + self.logger.warning(msg) + raise TypeError(msg) + + self.attach_as = '' + self.attach_multi_support = False + if result.group('match1'): + if result.group('id1a'): + self.attach_as += result.group('id1a') + + self.attach_as += self.attach_as_count + self.attach_multi_support = True + self.attach_as += result.group('id1b') + + else: # result.group('match2'): + self.attach_as += result.group('id2') + if result.group('wc2'): + self.attach_as += self.attach_as_count + 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 = {} + if params: + # Store our extra headers + self.params.update(params) + self.headers = {} if headers: # Store our extra headers self.headers.update(headers) + self.payload_overrides = {} self.payload_extras = {} if payload: # Store our extra payload entries 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 @@ -175,9 +289,18 @@ class NotifyForm(NotifyBase): # Append our headers into our parameters params.update({'+{}'.format(k): v for k, v in self.headers.items()}) + # Append our GET params into our parameters + params.update({'-{}'.format(k): v for k, v in self.params.items()}) + # Append our payload extra's into our parameters params.update( {':{}'.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: + # Provide Attach-As extension details + params['attach-as'] = self.attach_as # Determine Authentication auth = '' @@ -212,6 +335,7 @@ class NotifyForm(NotifyBase): Perform Form Notification """ + # Prepare HTTP Headers headers = { 'User-Agent': self.app_id, } @@ -233,7 +357,8 @@ class NotifyForm(NotifyBase): try: files.append(( - 'file{:02d}'.format(no), ( + self.attach_as.format(no) + if self.attach_multi_support else self.attach_as, ( attachment.name, open(attachment.path, 'rb'), attachment.mimetype) @@ -246,22 +371,24 @@ class NotifyForm(NotifyBase): self.logger.debug('I/O Exception: %s' % str(e)) return False - finally: - for file in files: - # Ensure all files are closed - if file[1][1]: - file[1][1].close() + if not self.attach_multi_support and no > 1: + self.logger.warning( + 'Multiple attachments provided while ' + 'form:// Multi-Attachment Support not enabled') # prepare Form Object - payload = { - # 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! - 'version': '1.0', - 'title': title, - 'message': body, - 'type': notify_type, - } + payload = {} + + for key, value in ( + (FORMPayloadField.VERSION, self.form_version), + (FORMPayloadField.TITLE, title), + (FORMPayloadField.MESSAGE, body), + (FORMPayloadField.MESSAGETYPE, 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 payload.update(self.payload_extras) @@ -289,10 +416,14 @@ class NotifyForm(NotifyBase): if self.method == 'GET': method = requests.get + payload.update(self.params) elif self.method == 'PUT': method = requests.put + elif self.method == 'PATCH': + method = requests.patch + elif self.method == 'DELETE': method = requests.delete @@ -307,7 +438,7 @@ class NotifyForm(NotifyBase): url, files=None if not files else files, data=payload if self.method != 'GET' else None, - params=payload if self.method == 'GET' else None, + params=payload if self.method == 'GET' else self.params, headers=headers, auth=auth, verify=self.verify_certificate, @@ -377,6 +508,16 @@ class NotifyForm(NotifyBase): results['headers'] = {NotifyForm.unquote(x): NotifyForm.unquote(y) for x, y in results['qsd+'].items()} + # Add our GET paramters in the event the user wants to pass these along + results['params'] = {NotifyForm.unquote(x): NotifyForm.unquote(y) + for x, y in results['qsd-'].items()} + + # Allow Attach-As Support which over-rides the name of the filename + # posted with the form:// + # the default is file01, file02, file03, etc + if 'attach-as' in results['qsd'] and len(results['qsd']['attach-as']): + results['attach_as'] = results['qsd']['attach-as'] + # Set method if not otherwise set if 'method' in results['qsd'] and len(results['qsd']['method']): results['method'] = NotifyForm.unquote(results['qsd']['method']) diff --git a/libs/apprise/plugins/NotifyGitter.py b/libs/apprise/plugins/NotifyGitter.py index 577959836..805d69c8c 100644 --- a/libs/apprise/plugins/NotifyGitter.py +++ b/libs/apprise/plugins/NotifyGitter.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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: @@ -137,7 +144,7 @@ class NotifyGitter(NotifyBase): """ Initialize Gitter Object """ - super(NotifyGitter, self).__init__(**kwargs) + super().__init__(**kwargs) # Secret Key (associated with project) self.token = validate_regex( @@ -382,6 +389,12 @@ class NotifyGitter(NotifyBase): [NotifyGitter.quote(x, safe='') for x in self.targets]), params=NotifyGitter.urlencode(params)) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + return len(self.targets) + @staticmethod def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyGnome.py b/libs/apprise/plugins/NotifyGnome.py index b7bffd2c1..9476c78a3 100644 --- a/libs/apprise/plugins/NotifyGnome.py +++ b/libs/apprise/plugins/NotifyGnome.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. from __future__ import absolute_import from __future__ import print_function @@ -54,8 +61,8 @@ except (ImportError, ValueError, AttributeError): # be in microsoft windows, or we just don't have the python-gobject # library available to us (or maybe one we don't support)? - # Alternativey A ValueError will get thrown upon calling - # gi.require_version() if the requested Notify namespace isn't available + # Alternatively, a `ValueError` will get raised upon calling + # gi.require_version() if the requested Notify namespace isn't available. pass @@ -164,7 +171,7 @@ class NotifyGnome(NotifyBase): Initialize Gnome Object """ - super(NotifyGnome, self).__init__(**kwargs) + super().__init__(**kwargs) # The urgency of the message self.urgency = int( @@ -175,12 +182,9 @@ class NotifyGnome(NotifyBase): if str(urgency).lower().startswith(k)), NotifyGnome.template_args['urgency']['default'])) - # Track whether or not we want to send an image with our notification - # or not. + # Track whether we want to add an image to the notification. self.include_image = include_image - return - def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs): """ Perform Gnome Notification @@ -214,15 +218,15 @@ class NotifyGnome(NotifyBase): except Exception as e: self.logger.warning( - "Could not load Gnome notification icon ({}): {}" - .format(icon_path, e)) + "Could not load notification icon (%s).", icon_path) + self.logger.debug(f'Gnome Exception: {e}') notification.show() self.logger.info('Sent Gnome notification.') - except Exception: + except Exception as e: self.logger.warning('Failed to send Gnome notification.') - self.logger.exception('Gnome Exception') + self.logger.debug(f'Gnome Exception: {e}') return False return True diff --git a/libs/apprise/plugins/NotifyGoogleChat.py b/libs/apprise/plugins/NotifyGoogleChat.py index 2cba98405..f65b6541e 100644 --- a/libs/apprise/plugins/NotifyGoogleChat.py +++ b/libs/apprise/plugins/NotifyGoogleChat.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2021 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # For this to work correctly you need to create a webhook. You'll also # need a GSuite account (there are free trials if you don't have one) @@ -80,8 +87,7 @@ class NotifyGoogleChat(NotifyBase): setup_url = 'https://github.com/caronc/apprise/wiki/Notify_googlechat' # Google Chat Webhook - notify_url = 'https://chat.googleapis.com/v1/spaces/{workspace}/messages' \ - '?key={key}&token={token}' + notify_url = 'https://chat.googleapis.com/v1/spaces/{workspace}/messages' # Default Notify Format notify_format = NotifyFormat.MARKDOWN @@ -96,6 +102,7 @@ class NotifyGoogleChat(NotifyBase): # Define object templates templates = ( '{schema}://{workspace}/{webhook_key}/{webhook_token}', + '{schema}://{workspace}/{webhook_key}/{webhook_token}/{thread_key}', ) # Define our template tokens @@ -118,6 +125,11 @@ class NotifyGoogleChat(NotifyBase): 'private': True, 'required': True, }, + 'thread_key': { + 'name': _('Thread Key'), + 'type': 'string', + 'private': True, + }, }) # Define our template arguments @@ -131,14 +143,18 @@ class NotifyGoogleChat(NotifyBase): 'token': { 'alias_of': 'webhook_token', }, + 'thread': { + 'alias_of': 'thread_key', + }, }) - def __init__(self, workspace, webhook_key, webhook_token, **kwargs): + def __init__(self, workspace, webhook_key, webhook_token, + thread_key=None, **kwargs): """ Initialize Google Chat Object """ - super(NotifyGoogleChat, self).__init__(**kwargs) + super().__init__(**kwargs) # Workspace (associated with project) self.workspace = validate_regex(workspace) @@ -164,6 +180,16 @@ class NotifyGoogleChat(NotifyBase): self.logger.warning(msg) raise TypeError(msg) + if thread_key: + self.thread_key = validate_regex(thread_key) + if not self.thread_key: + msg = 'An invalid Google Chat Thread Key ' \ + '({}) was specified.'.format(thread_key) + self.logger.warning(msg) + raise TypeError(msg) + else: + self.thread_key = None + return def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs): @@ -185,13 +211,21 @@ class NotifyGoogleChat(NotifyBase): # Construct Notify URL notify_url = self.notify_url.format( workspace=self.workspace, - key=self.webhook_key, - token=self.webhook_token, ) + params = { + # Prepare our URL Parameters + 'token': self.webhook_token, + 'key': self.webhook_key, + } + + if self.thread_key: + params['threadKey'] = self.thread_key + self.logger.debug('Google Chat POST URL: %s (cert_verify=%r)' % ( notify_url, self.verify_certificate, )) + self.logger.debug('Google Chat Parameters: %s' % str(params)) self.logger.debug('Google Chat Payload: %s' % str(payload)) # Always call throttle before any remote server i/o is made @@ -199,6 +233,7 @@ class NotifyGoogleChat(NotifyBase): try: r = requests.post( notify_url, + params=params, data=dumps(payload), headers=headers, verify=self.verify_certificate, @@ -242,11 +277,13 @@ class NotifyGoogleChat(NotifyBase): # Set our parameters params = self.url_parameters(privacy=privacy, *args, **kwargs) - return '{schema}://{workspace}/{key}/{token}/?{params}'.format( + return '{schema}://{workspace}/{key}/{token}/{thread}?{params}'.format( schema=self.secure_protocol, workspace=self.pprint(self.workspace, privacy, safe=''), key=self.pprint(self.webhook_key, privacy, safe=''), token=self.pprint(self.webhook_token, privacy, safe=''), + thread='' if not self.thread_key + else self.pprint(self.thread_key, privacy, safe=''), params=NotifyGoogleChat.urlencode(params), ) @@ -258,6 +295,7 @@ class NotifyGoogleChat(NotifyBase): Syntax: gchat://workspace/webhook_key/webhook_token + gchat://workspace/webhook_key/webhook_token/thread_key """ results = NotifyBase.parse_url(url, verify_host=False) @@ -277,6 +315,9 @@ class NotifyGoogleChat(NotifyBase): # Store our Webhook Token results['webhook_token'] = tokens.pop(0) if tokens else None + # Store our Thread Key + results['thread_key'] = tokens.pop(0) if tokens else None + # Support arguments as overrides (if specified) if 'workspace' in results['qsd']: results['workspace'] = \ @@ -290,6 +331,17 @@ class NotifyGoogleChat(NotifyBase): results['webhook_token'] = \ NotifyGoogleChat.unquote(results['qsd']['token']) + if 'thread' in results['qsd']: + results['thread_key'] = \ + NotifyGoogleChat.unquote(results['qsd']['thread']) + + elif 'threadkey' in results['qsd']: + # Support Google Chat's Thread Key (if set) + # keys are always made lowercase; so check above is attually + # testing threadKey successfully as well + results['thread_key'] = \ + NotifyGoogleChat.unquote(results['qsd']['threadkey']) + return results @staticmethod @@ -298,6 +350,8 @@ class NotifyGoogleChat(NotifyBase): Support https://chat.googleapis.com/v1/spaces/{workspace}/messages '?key={key}&token={token} + https://chat.googleapis.com/v1/spaces/{workspace}/messages + '?key={key}&token={token}&threadKey={thread} """ result = re.match( diff --git a/libs/apprise/plugins/NotifyGotify.py b/libs/apprise/plugins/NotifyGotify.py index 077289230..379225681 100644 --- a/libs/apprise/plugins/NotifyGotify.py +++ b/libs/apprise/plugins/NotifyGotify.py @@ -1,30 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -# For this plugin to work correct, the Gotify server must be set up to allow -# for remote connections. +# 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. # Gotify Docker configuration: https://hub.docker.com/r/gotify/server # Example: https://github.com/gotify/server/blob/\ @@ -155,7 +159,7 @@ class NotifyGotify(NotifyBase): Initialize Gotify Object """ - super(NotifyGotify, self).__init__(**kwargs) + super().__init__(**kwargs) # Token (associated with project) self.token = validate_regex(token) diff --git a/libs/apprise/plugins/NotifyGrowl.py b/libs/apprise/plugins/NotifyGrowl.py index 921c48529..9240d62c5 100644 --- a/libs/apprise/plugins/NotifyGrowl.py +++ b/libs/apprise/plugins/NotifyGrowl.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2020 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. from .NotifyBase import NotifyBase from ..URLBase import PrivacyMode @@ -187,7 +194,7 @@ class NotifyGrowl(NotifyBase): """ Initialize Growl Object """ - super(NotifyGrowl, self).__init__(**kwargs) + super().__init__(**kwargs) if not self.port: self.port = self.default_port diff --git a/libs/apprise/plugins/NotifyGuilded.py b/libs/apprise/plugins/NotifyGuilded.py index 603bd9fea..8bb9aeeaa 100644 --- a/libs/apprise/plugins/NotifyGuilded.py +++ b/libs/apprise/plugins/NotifyGuilded.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # For this to work correctly you need to create a webhook. To do this just # click on the little gear icon next to the channel you're part of. From diff --git a/libs/apprise/plugins/NotifyHomeAssistant.py b/libs/apprise/plugins/NotifyHomeAssistant.py index bb49462fc..a403356ab 100644 --- a/libs/apprise/plugins/NotifyHomeAssistant.py +++ b/libs/apprise/plugins/NotifyHomeAssistant.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2021 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # You must generate a "Long-Lived Access Token". This can be done from your # Home Assistant Profile page. @@ -115,7 +122,7 @@ class NotifyHomeAssistant(NotifyBase): """ Initialize Home Assistant Object """ - super(NotifyHomeAssistant, self).__init__(**kwargs) + super().__init__(**kwargs) self.fullpath = kwargs.get('fullpath', '') diff --git a/libs/apprise/plugins/NotifyIFTTT.py b/libs/apprise/plugins/NotifyIFTTT.py index b735a4d07..04c6911ef 100644 --- a/libs/apprise/plugins/NotifyIFTTT.py +++ b/libs/apprise/plugins/NotifyIFTTT.py @@ -1,29 +1,35 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# IFTTT (If-This-Then-That) +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# This code is licensed under the MIT License. +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 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. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. + # # 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' @@ -147,7 +153,7 @@ class NotifyIFTTT(NotifyBase): reference to Value1, Value2, and/or Value3 """ - super(NotifyIFTTT, self).__init__(**kwargs) + super().__init__(**kwargs) # Webhook ID (associated with project) self.webhook_id = validate_regex(webhook_id) @@ -306,6 +312,12 @@ class NotifyIFTTT(NotifyBase): params=NotifyIFTTT.urlencode(params), ) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + return len(self.events) + @staticmethod def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyJSON.py b/libs/apprise/plugins/NotifyJSON.py index c9ce1edc1..f1a9cc04e 100644 --- a/libs/apprise/plugins/NotifyJSON.py +++ b/libs/apprise/plugins/NotifyJSON.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import requests import base64 @@ -34,13 +41,25 @@ from ..common import NotifyType 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 METHODS = ( 'POST', 'GET', 'DELETE', 'PUT', - 'HEAD' + 'HEAD', + 'PATCH' ) @@ -68,6 +87,12 @@ class NotifyJSON(NotifyBase): # local anyway 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 templates = ( '{schema}://{host}', @@ -125,9 +150,14 @@ class NotifyJSON(NotifyBase): 'name': _('Payload Extras'), 'prefix': ':', }, + 'params': { + 'name': _('GET Params'), + 'prefix': '-', + }, } - def __init__(self, headers=None, method=None, payload=None, **kwargs): + def __init__(self, headers=None, method=None, payload=None, params=None, + **kwargs): """ Initialize JSON Object @@ -135,7 +165,7 @@ class NotifyJSON(NotifyBase): additionally include as part of the server headers to post with """ - super(NotifyJSON, self).__init__(**kwargs) + super().__init__(**kwargs) self.fullpath = kwargs.get('fullpath') if not isinstance(self.fullpath, str): @@ -149,15 +179,44 @@ class NotifyJSON(NotifyBase): self.logger.warning(msg) raise TypeError(msg) + # 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 = { + JSONPayloadField.VERSION: JSONPayloadField.VERSION, + JSONPayloadField.TITLE: JSONPayloadField.TITLE, + JSONPayloadField.MESSAGE: JSONPayloadField.MESSAGE, + JSONPayloadField.ATTACHMENTS: JSONPayloadField.ATTACHMENTS, + JSONPayloadField.MESSAGETYPE: JSONPayloadField.MESSAGETYPE, + } + + self.params = {} + if params: + # Store our extra headers + self.params.update(params) + self.headers = {} if headers: # Store our extra headers self.headers.update(headers) + self.payload_overrides = {} self.payload_extras = {} if payload: # Store our extra payload entries 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].strip() + self.payload_overrides[key] = \ + self.payload_extras[key].strip() + del self.payload_extras[key] return @@ -177,9 +236,14 @@ class NotifyJSON(NotifyBase): # Append our headers into our parameters params.update({'+{}'.format(k): v for k, v in self.headers.items()}) + # Append our GET params into our parameters + params.update({'-{}'.format(k): v for k, v in self.params.items()}) + # Append our payload extra's into our parameters params.update( {':{}'.format(k): v for k, v in self.payload_extras.items()}) + params.update( + {':{}'.format(k): v for k, v in self.payload_overrides.items()}) # Determine Authentication auth = '' @@ -214,6 +278,7 @@ class NotifyJSON(NotifyBase): Perform JSON Notification """ + # Prepare HTTP Headers headers = { 'User-Agent': self.app_id, 'Content-Type': 'application/json' @@ -253,16 +318,18 @@ class NotifyJSON(NotifyBase): return False # prepare JSON Object - payload = { - # 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! - 'version': '1.0', - 'title': title, - 'message': body, - 'attachments': attachments, - 'type': notify_type, - } + payload = {} + for key, value in ( + (JSONPayloadField.VERSION, self.json_version), + (JSONPayloadField.TITLE, title), + (JSONPayloadField.MESSAGE, body), + (JSONPayloadField.ATTACHMENTS, attachments), + (JSONPayloadField.MESSAGETYPE, 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 payload.update(self.payload_extras) @@ -294,6 +361,9 @@ class NotifyJSON(NotifyBase): elif self.method == 'PUT': method = requests.put + elif self.method == 'PATCH': + method = requests.patch + elif self.method == 'DELETE': method = requests.delete @@ -307,6 +377,7 @@ class NotifyJSON(NotifyBase): r = method( url, data=dumps(payload), + params=self.params, headers=headers, auth=auth, verify=self.verify_certificate, @@ -364,6 +435,10 @@ class NotifyJSON(NotifyBase): results['headers'] = {NotifyJSON.unquote(x): NotifyJSON.unquote(y) for x, y in results['qsd+'].items()} + # Add our GET paramters in the event the user wants to pass these along + results['params'] = {NotifyJSON.unquote(x): NotifyJSON.unquote(y) + for x, y in results['qsd-'].items()} + # Set method if not otherwise set if 'method' in results['qsd'] and len(results['qsd']['method']): results['method'] = NotifyJSON.unquote(results['qsd']['method']) diff --git a/libs/apprise/plugins/NotifyJoin.py b/libs/apprise/plugins/NotifyJoin.py index 9ac2f244b..e6210a5f3 100644 --- a/libs/apprise/plugins/NotifyJoin.py +++ b/libs/apprise/plugins/NotifyJoin.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # Join URL: http://joaoapps.com/join/ # To use this plugin, you need to first access (make sure your browser allows @@ -194,7 +201,7 @@ class NotifyJoin(NotifyBase): """ Initialize Join Object """ - super(NotifyJoin, self).__init__(**kwargs) + super().__init__(**kwargs) # Track whether or not we want to send an image with our notification # or not. @@ -366,6 +373,12 @@ class NotifyJoin(NotifyBase): for x in self.targets]), params=NotifyJoin.urlencode(params)) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + return len(self.targets) + @staticmethod def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyKavenegar.py b/libs/apprise/plugins/NotifyKavenegar.py index 97c69366e..8905e2431 100644 --- a/libs/apprise/plugins/NotifyKavenegar.py +++ b/libs/apprise/plugins/NotifyKavenegar.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2020 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # To use this service you will need a Kavenegar account from their website # at https://kavenegar.com/ @@ -149,7 +156,7 @@ class NotifyKavenegar(NotifyBase): """ Initialize Kavenegar Object """ - super(NotifyKavenegar, self).__init__(**kwargs) + super().__init__(**kwargs) # API Key (associated with project) self.apikey = validate_regex( @@ -317,6 +324,12 @@ class NotifyKavenegar(NotifyBase): [NotifyKavenegar.quote(x, safe='') for x in self.targets]), params=NotifyKavenegar.urlencode(params)) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + return len(self.targets) + @staticmethod def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyKumulos.py b/libs/apprise/plugins/NotifyKumulos.py index 8506aef3d..27e0995c9 100644 --- a/libs/apprise/plugins/NotifyKumulos.py +++ b/libs/apprise/plugins/NotifyKumulos.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # To use this plugin, you must have a Kumulos account set up. Add a client # and link it with your phone using the phone app (using your Companion App @@ -104,7 +111,7 @@ class NotifyKumulos(NotifyBase): """ Initialize Kumulos Object """ - super(NotifyKumulos, self).__init__(**kwargs) + super().__init__(**kwargs) # API Key (associated with project) self.apikey = validate_regex( diff --git a/libs/apprise/plugins/NotifyLametric.py b/libs/apprise/plugins/NotifyLametric.py index 1c4eaa040..1b98b6946 100644 --- a/libs/apprise/plugins/NotifyLametric.py +++ b/libs/apprise/plugins/NotifyLametric.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2020 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # For LaMetric to work, you need to first setup a custom application on their # website. it can be done as follows: @@ -467,7 +474,7 @@ class NotifyLametric(NotifyBase): """ Initialize LaMetric Object """ - super(NotifyLametric, self).__init__(**kwargs) + super().__init__(**kwargs) self.mode = mode.strip().lower() \ if isinstance(mode, str) \ diff --git a/libs/apprise/plugins/NotifyLine.py b/libs/apprise/plugins/NotifyLine.py index 7cb660978..817a998c8 100644 --- a/libs/apprise/plugins/NotifyLine.py +++ b/libs/apprise/plugins/NotifyLine.py @@ -1,27 +1,35 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2022 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. + # # API Docs: https://developers.line.biz/en/reference/messaging-api/ @@ -114,7 +122,7 @@ class NotifyLine(NotifyBase): """ Initialize Line Object """ - super(NotifyLine, self).__init__(**kwargs) + super().__init__(**kwargs) # Long-Lived Access token (generated from User Profile) self.token = validate_regex(token) @@ -259,6 +267,12 @@ class NotifyLine(NotifyBase): params=NotifyLine.urlencode(params), ) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + return len(self.targets) + @staticmethod def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyMQTT.py b/libs/apprise/plugins/NotifyMQTT.py index 9ea1e44b2..c8ee7cbce 100644 --- a/libs/apprise/plugins/NotifyMQTT.py +++ b/libs/apprise/plugins/NotifyMQTT.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2021 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # PAHO MQTT Documentation: # https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php @@ -132,20 +139,6 @@ class NotifyMQTT(NotifyBase): # through their network flow at once. mqtt_inflight_messages = 200 - # Taken from https://golang.org/src/crypto/x509/root_linux.go - CA_CERTIFICATE_FILE_LOCATIONS = [ - # Debian/Ubuntu/Gentoo etc. - "/etc/ssl/certs/ca-certificates.crt", - # Fedora/RHEL 6 - "/etc/pki/tls/certs/ca-bundle.crt", - # OpenSUSE - "/etc/ssl/ca-bundle.pem", - # OpenELEC - "/etc/pki/tls/cacert.pem", - # CentOS/RHEL 7 - "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", - ] - # Define object templates templates = ( '{schema}://{user}@{host}/{topic}', @@ -223,7 +216,7 @@ class NotifyMQTT(NotifyBase): Initialize MQTT Object """ - super(NotifyMQTT, self).__init__(**kwargs) + super().__init__(**kwargs) # Initialize topics self.topics = parse_list(targets) @@ -264,6 +257,9 @@ class NotifyMQTT(NotifyBase): self.ca_certs = None if self.secure: # verify SSL key or abort + # TODO: There is no error reporting or aborting here? + # It could be useful to inform the user _where_ Apprise + # tried to find the root CA certificates file. self.ca_certs = next( (cert for cert in self.CA_CERTIFICATE_FILE_LOCATIONS if isfile(cert)), None) @@ -316,9 +312,9 @@ class NotifyMQTT(NotifyBase): if self.secure: if self.ca_certs is None: - self.logger.warning( - 'MQTT Secure comunication can not be verified; ' - 'no local CA certificate file') + self.logger.error( + 'MQTT secure communication can not be verified, ' + 'CA certificates file missing') return False self.client.tls_set( @@ -328,7 +324,7 @@ class NotifyMQTT(NotifyBase): ciphers=None) # Set our TLS Verify Flag - self.client.tls_insecure_set(self.verify_certificate) + self.client.tls_insecure_set(not self.verify_certificate) # Establish our connection if self.client.connect( @@ -480,6 +476,12 @@ class NotifyMQTT(NotifyBase): params=NotifyMQTT.urlencode(params), ) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + return len(self.topics) + @staticmethod def parse_url(url): """ @@ -528,3 +530,38 @@ class NotifyMQTT(NotifyBase): # return results return results + + @property + def CA_CERTIFICATE_FILE_LOCATIONS(self): + """ + Return possible locations to root certificate authority (CA) bundles. + + Taken from https://golang.org/src/crypto/x509/root_linux.go + TODO: Maybe refactor to a general utility function? + """ + candidates = [ + # Debian/Ubuntu/Gentoo etc. + "/etc/ssl/certs/ca-certificates.crt", + # Fedora/RHEL 6 + "/etc/pki/tls/certs/ca-bundle.crt", + # OpenSUSE + "/etc/ssl/ca-bundle.pem", + # OpenELEC + "/etc/pki/tls/cacert.pem", + # CentOS/RHEL 7 + "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", + # macOS Homebrew; brew install ca-certificates + "/usr/local/etc/ca-certificates/cert.pem", + ] + + # Certifi provides Mozilla’s carefully curated collection of Root + # Certificates for validating the trustworthiness of SSL certificates + # while verifying the identity of TLS hosts. It has been extracted from + # the Requests project. + try: + import certifi + candidates.append(certifi.where()) + except ImportError: # pragma: no cover + pass + + return candidates diff --git a/libs/apprise/plugins/NotifyMSG91.py b/libs/apprise/plugins/NotifyMSG91.py index acbfa8d42..75834c399 100644 --- a/libs/apprise/plugins/NotifyMSG91.py +++ b/libs/apprise/plugins/NotifyMSG91.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # Create an account https://msg91.com/ if you don't already have one # @@ -156,7 +163,7 @@ class NotifyMSG91(NotifyBase): """ Initialize MSG91 Object """ - super(NotifyMSG91, self).__init__(**kwargs) + super().__init__(**kwargs) # Authentication Key (associated with project) self.authkey = validate_regex( @@ -322,6 +329,12 @@ class NotifyMSG91(NotifyBase): [NotifyMSG91.quote(x, safe='') for x in self.targets]), params=NotifyMSG91.urlencode(params)) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + return len(self.targets) + @staticmethod def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyMSTeams.py b/libs/apprise/plugins/NotifyMSTeams.py index 65631037c..19f9fe34f 100644 --- a/libs/apprise/plugins/NotifyMSTeams.py +++ b/libs/apprise/plugins/NotifyMSTeams.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2020 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # To use this plugin, you need to create a webhook; you can read more about # this here: @@ -214,7 +221,7 @@ class NotifyMSTeams(NotifyBase): template arguments that can not be over-ridden are: `body`, `title`, and `type`. """ - super(NotifyMSTeams, self).__init__(**kwargs) + super().__init__(**kwargs) try: self.version = int(version) diff --git a/libs/apprise/plugins/NotifyMacOSX.py b/libs/apprise/plugins/NotifyMacOSX.py index dfd8080f6..59c0620a3 100644 --- a/libs/apprise/plugins/NotifyMacOSX.py +++ b/libs/apprise/plugins/NotifyMacOSX.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2020 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. from __future__ import absolute_import from __future__ import print_function @@ -39,13 +46,15 @@ from ..AppriseLocale import gettext_lazy as _ # Default our global support flag NOTIFY_MACOSX_SUPPORT_ENABLED = False + +# TODO: The module will be easier to test without module-level code. if platform.system() == 'Darwin': # Check this is Mac OS X 10.8, or higher major, minor = platform.mac_ver()[0].split('.')[:2] - # Toggle our enabled flag if verion is correct and executable + # Toggle our enabled flag, if version is correct and executable # found. This is done in such a way to provide verbosity to the - # end user so they know why it may or may not work for them. + # end user, so they know why it may or may not work for them. NOTIFY_MACOSX_SUPPORT_ENABLED = \ (int(major) > 10 or (int(major) == 10 and int(minor) >= 8)) @@ -95,6 +104,8 @@ class NotifyMacOSX(NotifyBase): notify_paths = ( '/opt/homebrew/bin/terminal-notifier', '/usr/local/bin/terminal-notifier', + '/usr/bin/terminal-notifier', + '/bin/terminal-notifier', ) # Define object templates @@ -117,26 +128,32 @@ class NotifyMacOSX(NotifyBase): 'name': _('Sound'), 'type': 'string', }, + 'click': { + 'name': _('Open/Click URL'), + 'type': 'string', + }, }) - def __init__(self, sound=None, include_image=True, **kwargs): + def __init__(self, sound=None, include_image=True, click=None, **kwargs): """ Initialize MacOSX Object """ - super(NotifyMacOSX, self).__init__(**kwargs) + super().__init__(**kwargs) - # Track whether or not we want to send an image with our notification - # or not. + # Track whether we want to add an image to the notification. self.include_image = include_image - # Acquire the notify path + # Acquire the path to the `terminal-notifier` program. self.notify_path = next( # pragma: no branch (p for p in self.notify_paths if os.access(p, os.X_OK)), None) + # Click URL + # Allow user to provide the `--open` argument on the notify wrapper + self.click = click + # Set sound object (no q/a for now) self.sound = sound - return def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs): """ @@ -160,6 +177,9 @@ class NotifyMacOSX(NotifyBase): if title: cmd.extend(['-title', title]) + if self.click: + cmd.extend(['-open', self.click]) + # The sound to play if self.sound: cmd.extend(['-sound', self.sound]) @@ -201,6 +221,9 @@ class NotifyMacOSX(NotifyBase): 'image': 'yes' if self.include_image else 'no', } + if self.click: + params['click'] = self.click + # Extend our parameters params.update(self.url_parameters(privacy=privacy, *args, **kwargs)) @@ -228,6 +251,10 @@ class NotifyMacOSX(NotifyBase): results['include_image'] = \ parse_bool(results['qsd'].get('image', True)) + # Support 'click' + if 'click' in results['qsd'] and len(results['qsd']['click']): + results['click'] = NotifyMacOSX.unquote(results['qsd']['click']) + # Support 'sound' if 'sound' in results['qsd'] and len(results['qsd']['sound']): results['sound'] = NotifyMacOSX.unquote(results['qsd']['sound']) diff --git a/libs/apprise/plugins/NotifyMailgun.py b/libs/apprise/plugins/NotifyMailgun.py index 855d65e1c..3139e3416 100644 --- a/libs/apprise/plugins/NotifyMailgun.py +++ b/libs/apprise/plugins/NotifyMailgun.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # Signup @ https://www.mailgun.com/ # @@ -60,6 +67,7 @@ from ..utils import parse_emails from ..utils import parse_bool from ..utils import is_email from ..utils import validate_regex +from ..logger import logger from ..AppriseLocale import gettext_lazy as _ # Provide some known codes Mailgun uses and what they translate to: @@ -116,9 +124,6 @@ class NotifyMailgun(NotifyBase): # Default Notify Format notify_format = NotifyFormat.HTML - # The default region to use if one isn't otherwise specified - mailgun_default_region = MailgunRegion.US - # The maximum amount of emails that can reside within a single # batch transfer default_batch_size = 2000 @@ -158,7 +163,10 @@ class NotifyMailgun(NotifyBase): 'name': { 'name': _('From Name'), 'type': 'string', - 'map_to': 'from_name', + 'map_to': 'from_addr', + }, + 'from': { + 'alias_of': 'name', }, 'region': { 'name': _('Region Name'), @@ -197,13 +205,13 @@ class NotifyMailgun(NotifyBase): }, } - def __init__(self, apikey, targets, cc=None, bcc=None, from_name=None, + def __init__(self, apikey, targets, cc=None, bcc=None, from_addr=None, region_name=None, headers=None, tokens=None, batch=False, **kwargs): """ Initialize Mailgun Object """ - super(NotifyMailgun, self).__init__(**kwargs) + super().__init__(**kwargs) # API Key (associated with project) self.apikey = validate_regex(apikey) @@ -246,7 +254,8 @@ class NotifyMailgun(NotifyBase): # Store our region try: - self.region_name = self.mailgun_default_region \ + self.region_name = \ + NotifyMailgun.template_args['region']['default'] \ if region_name is None else region_name.lower() if self.region_name not in MAILGUN_REGIONS: @@ -260,12 +269,20 @@ class NotifyMailgun(NotifyBase): raise TypeError(msg) # Get our From username (if specified) - self.from_name = from_name - - # Get our from email address - self.from_addr = '{user}@{host}'.format(user=self.user, host=self.host) - - if not is_email(self.from_addr): + self.from_addr = [ + self.app_id, '{user}@{host}'.format( + user=self.user, host=self.host)] + + if from_addr: + result = is_email(from_addr) + if result: + self.from_addr = ( + result['name'] if result['name'] else False, + result['full_email']) + else: + self.from_addr[0] = from_addr + + if not is_email(self.from_addr[1]): # Parse Source domain based on from_addr msg = 'Invalid ~From~ email format: {}'.format(self.from_addr) self.logger.warning(msg) @@ -288,8 +305,7 @@ class NotifyMailgun(NotifyBase): else: # If our target email list is empty we want to add ourselves to it - self.targets.append( - (self.from_name if self.from_name else False, self.from_addr)) + self.targets.append((False, self.from_addr[1])) # Validate recipients (cc:) and drop bad ones: for recipient in parse_emails(cc): @@ -383,9 +399,7 @@ class NotifyMailgun(NotifyBase): return False - reply_to = formataddr( - (self.from_name if self.from_name else False, - self.from_addr), charset='utf-8') + reply_to = formataddr(self.from_addr, charset='utf-8') # Prepare our payload payload = { @@ -581,9 +595,9 @@ class NotifyMailgun(NotifyBase): # Extend our parameters params.update(self.url_parameters(privacy=privacy, *args, **kwargs)) - if self.from_name is not None: - # from_name specified; pass it back on the url - params['name'] = self.from_name + if self.from_addr[0]: + # from_addr specified; pass it back on the url + params['name'] = self.from_addr[0] if self.cc: # Handle our Carbon Copy Addresses @@ -613,6 +627,20 @@ class NotifyMailgun(NotifyBase): safe='') for e in self.targets]), 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 def parse_url(url): """ @@ -637,13 +665,28 @@ class NotifyMailgun(NotifyBase): # We're done - no API Key found results['apikey'] = None - if 'name' in results['qsd'] and len(results['qsd']['name']): + # Attempt to detect 'from' email address + if 'from' in results['qsd'] and len(results['qsd']['from']): + results['from_addr'] = \ + NotifyMailgun.unquote(results['qsd']['from']) + + if 'name' in results['qsd'] and len(results['qsd']['name']): + # Depricate use of both `from=` and `name=` in the same url as + # they will be synomomus of one another in the future. + results['from_addr'] = formataddr( + (NotifyMailgun.unquote(results['qsd']['name']), + results['from_addr']), charset='utf-8') + logger.warning( + 'Mailgun name= and from= are synonymous; ' + 'use one or the other.') + + elif 'name' in results['qsd'] and len(results['qsd']['name']): # Extract from name to associate with from address - results['from_name'] = \ + results['from_addr'] = \ NotifyMailgun.unquote(results['qsd']['name']) if 'region' in results['qsd'] and len(results['qsd']['region']): - # Extract from name to associate with from address + # Acquire region if defined results['region_name'] = \ NotifyMailgun.unquote(results['qsd']['region']) diff --git a/libs/apprise/plugins/NotifyMastodon.py b/libs/apprise/plugins/NotifyMastodon.py new file mode 100644 index 000000000..74d13952a --- /dev/null +++ b/libs/apprise/plugins/NotifyMastodon.py @@ -0,0 +1,990 @@ +# -*- coding: utf-8 -*- +# BSD 3-Clause License +# +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> +# +# 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. + +import re +import requests +from copy import deepcopy +from json import dumps, loads +from datetime import datetime + +from .NotifyBase import NotifyBase +from ..URLBase import PrivacyMode +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 _ +from ..attachment.AttachBase import AttachBase + +# Accept: +# - @username +# - username +# - [email protected] +# - @[email protected] +IS_USER = re.compile( + r'^\s*@?(?P<user>[A-Z0-9_]+(?:@(?P<host>[A-Z0-9_.-]+))?)$', re.I) + +USER_DETECTION_RE = re.compile( + r'(@[A-Z0-9_]+(?:@[A-Z0-9_.-]+)?)(?=$|[\s,.&()\[\]]+)', re.I) + + +class MastodonMessageVisibility: + """ + The visibility of any status message made + """ + # post visibility defaults to the accounts default-visibilty setting + DEFAULT = 'default' + + # post will be visible only to mentioned users + # similar to a Twitter DM + DIRECT = 'direct' + + # post will be visible only to followers + PRIVATE = 'private' + + # post will be public but not appear on the public timeline + UNLISTED = 'unlisted' + + # post will be public + PUBLIC = 'public' + + +# Define the types in a list for validation purposes +MASTODON_MESSAGE_VISIBILITIES = ( + MastodonMessageVisibility.DEFAULT, + MastodonMessageVisibility.DIRECT, + MastodonMessageVisibility.PRIVATE, + MastodonMessageVisibility.UNLISTED, + MastodonMessageVisibility.PUBLIC, +) + + +class NotifyMastodon(NotifyBase): + """ + A wrapper for Notify Mastodon Notifications + """ + + # The default descriptive name associated with the Notification + service_name = 'Mastodon' + + # The services URL + service_url = 'https://joinmastodon.org' + + # The default protocol + protocol = ('mastodon', 'toot') + + # The default secure protocol + secure_protocol = ('mastodons', 'toots') + + # A URL that takes you to the setup/help of the specific protocol + setup_url = 'https://github.com/caronc/apprise/wiki/Notify_mastodon' + + # Allows the user to specify the NotifyImageSize object; this is supported + # through the webhook + image_size = NotifyImageSize.XY_128 + + # it is documented on the site that the maximum images per toot + # is 4 (unless it's a GIF, then it's only 1) + __toot_non_gif_images_batch = 4 + + # Mastodon API Reference To Acquire Current Users Information + # See: https://docs.joinmastodon.org/methods/accounts/ + # Requires Scope Element: read:accounts + mastodon_whoami = '/api/v1/accounts/verify_credentials' + + # URL for posting media files + mastodon_media = '/api/v1/media' + + # URL for posting status messages + mastodon_toot = '/api/v1/statuses' + + # URL for posting direct messages + mastodon_dm = '/api/v1/dm' + + # The title is not used + title_maxlen = 0 + + # The maximum size of the message + body_maxlen = 500 + + # Default to text + notify_format = NotifyFormat.TEXT + + # Mastodon is kind enough to return how many more requests we're allowed to + # continue to make within it's header response as: + # X-Rate-Limit-Reset: The epoc time (in seconds) we can expect our + # rate-limit to be reset. + # X-Rate-Limit-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 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 + ratelimit_remaining = 1 + + # Define object templates + templates = ( + '{schema}://{token}@{host}', + '{schema}://{token}@{host}:{port}', + '{schema}://{token}@{host}/{targets}', + '{schema}://{token}@{host}:{port}/{targets}', + ) + + # Define our template arguments + template_tokens = dict(NotifyBase.template_tokens, **{ + 'host': { + 'name': _('Hostname'), + 'type': 'string', + 'required': True, + }, + 'token': { + 'name': _('Access Token'), + 'type': 'string', + 'required': True, + }, + 'port': { + 'name': _('Port'), + 'type': 'int', + 'min': 1, + 'max': 65535, + }, + 'target_user': { + 'name': _('Target User'), + 'type': 'string', + 'prefix': '@', + 'map_to': 'targets', + }, + 'targets': { + 'name': _('Targets'), + 'type': 'list:string', + }, + }) + + # Define our template arguments + template_args = dict(NotifyBase.template_args, **{ + 'token': { + 'alias_of': 'token', + }, + 'visibility': { + 'name': _('Visibility'), + 'type': 'choice:string', + 'values': MASTODON_MESSAGE_VISIBILITIES, + 'default': MastodonMessageVisibility.DEFAULT, + }, + 'cache': { + 'name': _('Cache Results'), + 'type': 'bool', + 'default': True, + }, + 'batch': { + 'name': _('Batch Mode'), + 'type': 'bool', + 'default': True, + }, + 'sensitive': { + 'name': _('Sensitive Attachments'), + 'type': 'bool', + 'default': False, + }, + 'spoiler': { + 'name': _('Spoiler Text'), + 'type': 'string', + }, + 'key': { + 'name': _('Idempotency-Key'), + 'type': 'string', + }, + 'language': { + 'name': _('Language Code'), + 'type': 'string', + }, + 'to': { + 'alias_of': 'targets', + }, + }) + + def __init__(self, token=None, targets=None, batch=True, + sensitive=None, spoiler=None, visibility=None, cache=True, + key=None, language=None, **kwargs): + """ + Initialize Notify Mastodon Object + """ + super().__init__(**kwargs) + + # Set our schema + self.schema = 'https' if self.secure else 'http' + + # Initialize our cache value + self._whoami_cache = None + + self.token = validate_regex(token) + if not self.token: + msg = 'An invalid Mastodon Access Token was specified.' + self.logger.warning(msg) + raise TypeError(msg) + + if visibility: + # Input is a string; attempt to get the lookup from our + # sound mapping + vis = 'invalid' if not isinstance(visibility, str) \ + else visibility.lower().strip() + + # This little bit of black magic allows us to match against + # against multiple versions of the same string + # ... etc + self.visibility = \ + next((v for v in MASTODON_MESSAGE_VISIBILITIES + if v.startswith(vis)), None) + + if self.visibility not in MASTODON_MESSAGE_VISIBILITIES: + msg = 'The Mastodon visibility specified ({}) is invalid.' \ + .format(visibility) + self.logger.warning(msg) + raise TypeError(msg) + + else: + self.visibility = \ + self.template_args['visibility']['default'] + + # Prepare our URL + self.api_url = '%s://%s' % (self.schema, self.host) + + if isinstance(self.port, int): + self.api_url += ':%d' % self.port + + # Set Cache Flag + self.cache = cache + + # Prepare Image Batch Mode Flag + self.batch = self.template_args['batch']['default'] \ + if batch is None else batch + + # Images to be marked sensitive + self.sensitive = self.template_args['sensitive']['default'] \ + if sensitive is None else sensitive + + # Text marked as being a spoiler + self.spoiler = spoiler if isinstance(spoiler, str) else None + + # Idempotency Key + self.idempotency_key = key if isinstance(key, str) else None + + # Over-ride default language (ISO 639) (e.g: en, fr, es, etc) + self.language = language if isinstance(language, str) else None + + # Our target users + self.targets = [] + + # Track any errors + has_error = False + + # Identify our targets + for target in parse_list(targets): + match = IS_USER.match(target) + if match and match.group('user'): + self.targets.append('@' + match.group('user')) + continue + + has_error = True + self.logger.warning( + 'Dropped invalid Mastodon user ({}) specified.'.format(target), + ) + + if has_error and not self.targets: + # We have specified that we want to notify one or more individual + # and we failed to load any of them. Since it's also valid to + # notify no one at all (which means we notify ourselves), it's + # important we don't switch from the users original intentions + msg = 'No Mastodon targets to notify.' + self.logger.warning(msg) + raise TypeError(msg) + + return + + def url(self, privacy=False, *args, **kwargs): + """ + Returns the URL built dynamically based on specified arguments. + """ + + # Define any URL parameters + params = { + 'visibility': self.visibility, + 'batch': 'yes' if self.batch else 'no', + 'sensitive': 'yes' if self.sensitive else 'no', + } + + # Extend our parameters + params.update(self.url_parameters(privacy=privacy, *args, **kwargs)) + + if self.spoiler: + # Our Spoiler if one was specified + params['spoiler'] = self.spoiler + + if self.idempotency_key: + # Our Idempotency Key + params['key'] = self.idempotency_key + + if self.language: + # Override Language + params['language'] = self.language + + default_port = 443 if self.secure else 80 + + return '{schema}://{token}@{host}{port}/{targets}?{params}'.format( + schema=self.secure_protocol[0] + if self.secure else self.protocol[0], + token=self.pprint( + self.token, privacy, mode=PrivacyMode.Secret, safe=''), + # never encode hostname since we're expecting it to be a valid one + host=self.host, + port='' if self.port is None or self.port == default_port + else ':{}'.format(self.port), + targets='/'.join( + [NotifyMastodon.quote(x, safe='@') for x in self.targets]), + 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, + **kwargs): + """ + wrapper to _send since we can alert more then one channel + """ + + # Build a list of our attachments + attachments = [] + + # Smart Target Detection for Direct Messages; this prevents us from + # adding @user entries that were already placed in the message body + users = set(USER_DETECTION_RE.findall(body)) + targets = users - set(self.targets.copy()) + if not self.targets and self.visibility == \ + MastodonMessageVisibility.DIRECT: + + result = self._whoami() + if not result: + # Could not access our status + return False + + myself = '@' + next(iter(result.keys())) + if myself in users: + targets.remove(myself) + + else: + targets.add(myself) + + if attach: + # We need to upload our payload first so that we can source it + # in remaining messages + for attachment in attach: + + # 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 + + # + # Images (PNG, JPG, GIF) up to 8MB. + # - Images will be downscaled to 1.6 megapixels (enough for a + # 1280x1280 image). + # - Up to 4 images can be attached. + # - Animated GIFs are converted to soundless MP4s like on + # Imgur/Gfycat (GIFV). + # - You can also upload soundless MP4 and WebM, which will + # be handled the same way. + # Videos (MP4, M4V, MOV, WebM) up to 40MB. + # - Video will be transcoded to H.264 MP4 with a maximum + # bitrate of 1300kbps and framerate of 60fps. + # Audio (MP3, OGG, WAV, FLAC, OPUS, AAC, M4A, 3GP) up to 40MB. + # - Audio will be transcoded to MP3 using V2 VBR (roughly + # 192kbps). + if not re.match(r'^(image|video|audio)/.*', + attachment.mimetype, re.I): + # Only support images at this time + self.logger.warning( + 'Ignoring unsupported Mastodon attachment {}.'.format( + attachment.url(privacy=True))) + continue + + self.logger.debug( + 'Preparing Mastodon attachment {}'.format( + attachment.url(privacy=True))) + + # Upload our image and get our id associated with it + postokay, response = self._request( + self.mastodon_media, + payload=attachment, + ) + + if not postokay: + # We can't post our attachment + if response and 'authorized scopes' \ + in response.get('error', ''): + self.logger.warning( + 'Failed to Send Attachment to Mastodon: ' + 'missing scope: write:media') + + # All other failures should cause us to abort + return False + + if not (isinstance(response, dict) + and response.get('id')): + self.logger.debug( + 'Could not attach the file to Mastodon: %s (mime=%s)', + attachment.name, attachment.mimetype) + continue + + # If we get here, our output will look something like this: + # { + # 'id': '12345', + # 'type': 'image', + # 'url': 'https://.../6dad4663a.jpeg', + # 'preview_url': 'https://.../adde6dad4663a.jpeg', + # 'remote_url': None, + # 'preview_remote_url': None, + # 'text_url': None, + # 'meta': { + # 'original': { + # 'width': 640, + # 'height': 640, + # 'size': '640x640', + # 'aspect': 1.0 + # }, + # 'small': { + # 'width': 400, + # 'height': 400, + # 'size': '400x400', + # 'aspect': 1.0 + # } + # }, + # 'description': None, + # 'blurhash': 'UmIsdJnT^mX4V@XQofnQ~Ebq%4o3ofnQjZbt' + # } + response.update({ + # Update our response to additionally include the + # attachment details + 'file_name': attachment.name, + 'file_mime': attachment.mimetype, + 'file_path': attachment.path, + }) + + # Save our pre-prepared payload for attachment posting + attachments.append(response) + + payload = { + 'status': '{} {}'.format(' '.join(targets), body) + if targets else body, + 'sensitive': self.sensitive, + } + + # Handle Visibility Flag + if self.visibility != MastodonMessageVisibility.DEFAULT: + payload['visibility'] = self.visibility + + # Set Spoiler text (if set) + if self.spoiler: + payload['spoiler_text'] = self.spoiler + + # Set Idempotency-Key (if set) + if self.idempotency_key: + payload['Idempotency-Key'] = self.idempotency_key + + # Set Language + if self.language: + payload['language'] = self.language + + payloads = [] + if not attachments: + payloads.append(payload) + + else: + # Group our images if batch is set to do so + batch_size = 1 if not self.batch \ + else self.__toot_non_gif_images_batch + + # Track our batch control in our message generation + batches = [] + batch = [] + for attachment in attachments: + batch.append(attachment['id']) + # Mastodon supports batching images together. This allows + # the batching of multiple images together. Mastodon also + # makes it clear that you can't batch `gif` files; they need + # to be separate. So the below preserves the ordering that + # a user passed their attachments in. if 4-non-gif images + # are passed, they are all part of a single message. + # + # however, if they pass in image, gif, image, gif. The + # gif's inbetween break apart the batches so this would + # produce 4 separate toots. + # + # If you passed in, image, image, gif, image. <- This would + # produce 3 images (as the first 2 images could be lumped + # together as a batch) + if not re.match( + r'^image/(png|jpe?g)', attachment['file_mime'], re.I) \ + or len(batch) >= batch_size: + batches.append(batch) + batch = [] + + if batch: + batches.append(batch) + + for no, media_ids in enumerate(batches): + _payload = deepcopy(payload) + _payload['media_ids'] = media_ids + + if no: + # strip text and replace it with the image representation + _payload['status'] = \ + '{:02d}/{:02d}'.format(no + 1, len(batches)) + # No longer sensitive information + _payload['sensitive'] = False + if self.idempotency_key: + # Support multiposts while a Idempotency Key has been + # defined + _payload['Idempotency-Key'] = '{}-part{:02d}'.format( + self.idempotency_key, no) + payloads.append(_payload) + + # Error Tracking + has_error = False + + for no, payload in enumerate(payloads, start=1): + postokay, response = self._request(self.mastodon_toot, payload) + if not postokay: + # Track our error + has_error = True + + # We can't post our attachment + if response and 'authorized scopes' \ + in response.get('error', ''): + self.logger.warning( + 'Failed to Send Status to Mastodon: ' + 'missing scope: write:statuses') + + continue + + # Example Attachment Output: + # { + # "id":"109315796435904505", + # "created_at":"2022-11-09T20:44:39.017Z", + # "in_reply_to_id":null, + # "in_reply_to_account_id":null, + # "sensitive":false, + # "spoiler_text":"", + # "visibility":"public", + # "language":"en", + # "uri":"https://host/users/caronc/statuses/109315796435904505", + # "url":"https://host/@caronc/109315796435904505", + # "replies_count":0, + # "reblogs_count":0, + # "favourites_count":0, + # "edited_at":null, + # "favourited":false, + # "reblogged":false, + # "muted":false, + # "bookmarked":false, + # "pinned":false, + # "content":"<p>test</p>", + # "reblog":null, + # "application":{ + # "name":"Apprise Notifications", + # "website":"https://github.com/caronc/apprise" + # }, + # "account":{ + # "id":"109310334138718878", + # "username":"caronc", + # "acct":"caronc", + # "display_name":"Chris", + # "locked":false, + # "bot":false, + # "discoverable":false, + # "group":false, + # "created_at":"2022-11-08T00:00:00.000Z", + # "note":"content", + # "url":"https://host/@caronc", + # "avatar":"https://host/path/file.png", + # "avatar_static":"https://host/path/file.png", + # "header":"https://host/headers/original/missing.png", + # "header_static":"https://host/path/missing.png", + # "followers_count":0, + # "following_count":0, + # "statuses_count":15, + # "last_status_at":"2022-11-09", + # "emojis":[ + # + # ], + # "fields":[ + # + # ] + # }, + # "media_attachments":[ + # { + # "id":"109315796405707501", + # "type":"image", + # "url":"https://host/path/file.jpeg", + # "preview_url":"https://host/path/file.jpeg", + # "remote_url":null, + # "preview_remote_url":null, + # "text_url":null, + # "meta":{ + # "original":{ + # "width":640, + # "height":640, + # "size":"640x640", + # "aspect":1.0 + # }, + # "small":{ + # "width":400, + # "height":400, + # "size":"400x400", + # "aspect":1.0 + # } + # }, + # "description":null, + # "blurhash":"UmIsdJnT^mX4V@XQofnQ~Ebq%4o3ofnQjZbt" + # } + # ], + # "mentions":[ + # + # ], + # "tags":[ + # + # ], + # "emojis":[ + # + # ], + # "card":null, + # "poll":null + # } + + try: + url = '{}/web/@{}'.format( + self.api_url, + response['account']['username']) + + except (KeyError, TypeError): + url = 'unknown' + + self.logger.debug( + 'Mastodon [%.2d/%.2d] (%d attached) delivered to %s', + no, len(payloads), len(payload.get('media_ids', [])), url) + + self.logger.info( + 'Sent [%.2d/%.2d] Mastodon notification as public toot.', + no, len(payloads)) + + return not has_error + + def _whoami(self, lazy=True): + """ + Looks details of current authenticated user + + """ + + if lazy and self._whoami_cache is not None: + # Use cached response + return self._whoami_cache + + # Send Mastodon Whoami request + postokay, response = self._request( + self.mastodon_whoami, + method='GET', + ) + + if postokay: + # Sample Response: + # { + # 'id': '12345', + # 'username': 'caronc', + # 'acct': 'caronc', + # 'display_name': 'Chris', + # 'locked': False, + # 'bot': False, + # 'discoverable': False, + # 'group': False, + # 'created_at': '2022-11-08T00:00:00.000Z', + # 'note': 'details', + # 'url': 'https://noc.social/@caronc', + # 'avatar': 'https://host/path/image.png', + # 'avatar_static': 'https://host/path/image.png', + # 'header': 'https://host/path/missing.png', + # 'header_static': 'https://host/path/missing.png', + # 'followers_count': 0, + # 'following_count': 0, + # 'statuses_count': 2, + # 'last_status_at': '2022-11-09', + # 'source': { + # 'privacy': 'public', + # 'sensitive': False, + # 'language': None, + # 'note': 'details', + # 'fields': [], + # 'follow_requests_count': 0 + # }, + # 'emojis': [], + # 'fields': [] + # } + try: + # Cache our response for future references + self._whoami_cache = { + response['username']: response['id']} + + except (TypeError, KeyError): + pass + + elif response and 'authorized scopes' in response.get('error', ''): + self.logger.warning( + 'Failed to lookup Mastodon Auth details; ' + 'missing scope: read:accounts') + + return self._whoami_cache if postokay else {} + + def _request(self, path, payload=None, method='POST'): + """ + Wrapper to Mastodon API requests object + """ + + headers = { + 'User-Agent': self.app_id, + 'Authorization': f'Bearer {self.token}', + } + + data = None + files = None + + # Prepare our message + url = '{}{}'.format(self.api_url, path) + + # Some Debug Logging + self.logger.debug('Mastodon {} URL: {} (cert_verify={})'.format( + method, url, self.verify_certificate)) + + # Open our attachment path if required: + if isinstance(payload, AttachBase): + # prepare payload + files = { + 'file': (payload.name, open(payload.path, 'rb'), + 'application/octet-stream')} + + # Provide a description + data = { + 'description': payload.name, + } + + else: + headers['Content-Type'] = 'application/json' + data = dumps(payload) + self.logger.debug('Mastodon Payload: %s' % str(payload)) + + # Default content response object + content = {} + + # 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 + # Mastodon 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) + + # acquire our request mode + fn = requests.post if method == 'POST' else requests.get + + try: + r = fn( + url, + data=data, + files=files, + headers=headers, + verify=self.verify_certificate, + timeout=self.request_timeout, + ) + + try: + content = loads(r.content) + + except (AttributeError, TypeError, ValueError): + # ValueError = r.content is Unparsable + # TypeError = r.content is None + # AttributeError = r is None + content = {} + + if r.status_code not in ( + requests.codes.ok, requests.codes.created, + requests.codes.accepted): + + # We had a problem + status_str = \ + NotifyMastodon.http_response_code_lookup(r.status_code) + + self.logger.warning( + 'Failed to send Mastodon {} 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: + # Capture rate limiting if possible + self.ratelimit_remaining = \ + int(r.headers.get('X-RateLimit-Remaining')) + self.ratelimit_reset = datetime.utcfromtimestamp( + int(r.headers.get('X-RateLimit-Limit'))) + + 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 Mastodon {} to {}: '. + format(method, url)) + self.logger.debug('Socket Exception: %s' % str(e)) + + # Mark our failure + return (False, content) + + except (OSError, IOError) as e: + self.logger.warning( + 'An I/O error occurred while handling {}.'.format( + payload.name if isinstance(payload, AttachBase) + else payload)) + self.logger.debug('I/O Exception: %s' % str(e)) + return (False, content) + + finally: + # Close our file (if it's open) stored in the second element + # of our files tuple (index 1) + if files: + files['file'][1].close() + + return (True, content) + + @staticmethod + def parse_url(url): + """ + Parses the URL and returns enough arguments that can allow + us to re-instantiate this object. + + """ + results = NotifyBase.parse_url(url) + if not results: + # We're done early as we couldn't load the results + return results + + if 'token' in results['qsd'] and len(results['qsd']['token']): + results['token'] = NotifyMastodon.unquote(results['qsd']['token']) + + elif not results['password'] and results['user']: + results['token'] = NotifyMastodon.unquote(results['user']) + + # Apply our targets + results['targets'] = NotifyMastodon.split_path(results['fullpath']) + + # The defined Mastodon visibility + if 'visibility' in results['qsd'] and \ + len(results['qsd']['visibility']): + # Simplified version + results['visibility'] = \ + NotifyMastodon.unquote(results['qsd']['visibility']) + + elif results['schema'].startswith('toot'): + results['visibility'] = MastodonMessageVisibility.PUBLIC + + # Get Idempotency Key (if specified) + if 'key' in results['qsd'] and len(results['qsd']['key']): + results['key'] = \ + NotifyMastodon.unquote(results['qsd']['key']) + + # Get Spoiler Text + if 'spoiler' in results['qsd'] and len(results['qsd']['spoiler']): + results['spoiler'] = \ + NotifyMastodon.unquote(results['qsd']['spoiler']) + + # Get Language (if specified) + if 'language' in results['qsd'] and len(results['qsd']['language']): + results['language'] = \ + NotifyMastodon.unquote(results['qsd']['language']) + + # Get Sensitive Flag (for Attachments) + results['sensitive'] = \ + parse_bool(results['qsd'].get( + 'sensitive', + NotifyMastodon.template_args['sensitive']['default'])) + + # Get Batch Mode Flag + results['batch'] = \ + parse_bool(results['qsd'].get( + 'batch', NotifyMastodon.template_args['batch']['default'])) + + # The 'to' makes it easier to use yaml configuration + if 'to' in results['qsd'] and len(results['qsd']['to']): + results['targets'] += \ + NotifyMastodon.parse_list(results['qsd']['to']) + + return results diff --git a/libs/apprise/plugins/NotifyMatrix.py b/libs/apprise/plugins/NotifyMatrix.py index f62b16e0a..c0b524a0d 100644 --- a/libs/apprise/plugins/NotifyMatrix.py +++ b/libs/apprise/plugins/NotifyMatrix.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # Great sources # - https://github.com/matrix-org/matrix-python-sdk @@ -128,7 +135,14 @@ class NotifyMatrix(NotifyBase): image_size = NotifyImageSize.XY_32 # The maximum allowable characters allowed in the body per message - body_maxlen = 1000 + # https://spec.matrix.org/v1.6/client-server-api/#size-limits + # The complete event MUST NOT be larger than 65536 bytes, when formatted + # with the federation event format, including any signatures, and encoded + # as Canonical JSON. + # + # To gracefully allow for some overhead' we'll define a max body length + # of just slighty lower then the limit of the full message itself. + body_maxlen = 65000 # Throttle a wee-bit to avoid thrashing request_rate_per_sec = 0.5 @@ -239,7 +253,7 @@ class NotifyMatrix(NotifyBase): """ Initialize Matrix Object """ - super(NotifyMatrix, self).__init__(**kwargs) + super().__init__(**kwargs) # Prepare a list of rooms to connect and notify self.rooms = parse_list(targets) @@ -1182,6 +1196,13 @@ class NotifyMatrix(NotifyBase): 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 def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyMatterMost.py b/libs/apprise/plugins/NotifyMatterMost.py index 9c776b512..e62f653c4 100644 --- a/libs/apprise/plugins/NotifyMatterMost.py +++ b/libs/apprise/plugins/NotifyMatterMost.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # Create an incoming webhook; the website will provide you with something like: # http://localhost:8065/hooks/yobjmukpaw3r3urc5h6i369yima @@ -145,7 +152,7 @@ class NotifyMattermost(NotifyBase): """ Initialize Mattermost Object """ - super(NotifyMattermost, self).__init__(**kwargs) + super().__init__(**kwargs) if self.secure: self.schema = 'https' diff --git a/libs/apprise/plugins/NotifyMessageBird.py b/libs/apprise/plugins/NotifyMessageBird.py index 4b1da524e..f477df489 100644 --- a/libs/apprise/plugins/NotifyMessageBird.py +++ b/libs/apprise/plugins/NotifyMessageBird.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # Create an account https://messagebird.com if you don't already have one # @@ -115,7 +122,7 @@ class NotifyMessageBird(NotifyBase): """ Initialize MessageBird Object """ - super(NotifyMessageBird, self).__init__(**kwargs) + super().__init__(**kwargs) # API Key (associated with project) self.apikey = validate_regex( @@ -304,6 +311,13 @@ class NotifyMessageBird(NotifyBase): [NotifyMessageBird.quote(x, safe='') for x in self.targets]), 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 def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyMisskey.py b/libs/apprise/plugins/NotifyMisskey.py new file mode 100644 index 000000000..54c4e628a --- /dev/null +++ b/libs/apprise/plugins/NotifyMisskey.py @@ -0,0 +1,310 @@ +# -*- coding: utf-8 -*- +# BSD 3-Clause License +# +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> +# +# 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. +# 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: +# https://misskey-hub.net/en/instances.html +# +# - For this plugin, I tested using https://misskey.sda1.net and created an +# account. +# +# 2. Generate an API Key: +# - Settings > API > Generate Key +# - Name it whatever you want +# - Assign it 'AT LEAST': +# a. Compose or delete chat messages +# b. Compose or delete notes +# +# +# This plugin also supports taking the URL (as identified above) directly +# as well. + +import requests +from json import dumps + +from .NotifyBase import NotifyBase +from ..common import NotifyType +from ..utils import validate_regex +from ..AppriseLocale import gettext_lazy as _ + + +class MisskeyVisibility: + """ + The visibility of any note created + """ + # post will be public + PUBLIC = 'public' + + HOME = 'home' + + FOLLOWERS = 'followers' + + PRIVATE = 'private' + + SPECIFIED = 'specified' + + +# Define the types in a list for validation purposes +MISSKEY_VISIBILITIES = ( + MisskeyVisibility.PUBLIC, + MisskeyVisibility.HOME, + MisskeyVisibility.FOLLOWERS, + MisskeyVisibility.PRIVATE, + MisskeyVisibility.SPECIFIED, +) + + +class NotifyMisskey(NotifyBase): + """ + A wrapper for Misskey Notifications + """ + + # The default descriptive name associated with the Notification + service_name = 'Misskey' + + # The services URL + service_url = 'https://misskey-hub.net/' + + # The default protocol + protocol = 'misskey' + + # The default secure protocol + secure_protocol = 'misskeys' + + # A URL that takes you to the setup/help of the specific protocol + setup_url = 'https://github.com/caronc/apprise/wiki/Notify_misskey' + + # The title is not used + title_maxlen = 0 + + # The maximum allowable characters allowed in the body per message + body_maxlen = 512 + + # Define object templates + templates = ( + '{schema}://{project_id}/{msghook}', + ) + + # Define object templates + templates = ( + '{schema}://{token}@{host}', + '{schema}://{token}@{host}:{port}', + ) + + # Define our template arguments + # Define our template arguments + template_tokens = dict(NotifyBase.template_tokens, **{ + 'host': { + 'name': _('Hostname'), + 'type': 'string', + 'required': True, + }, + 'token': { + 'name': _('Access Token'), + 'type': 'string', + 'required': True, + }, + 'port': { + 'name': _('Port'), + 'type': 'int', + 'min': 1, + 'max': 65535, + }, + }) + + # Define our template arguments + template_args = dict(NotifyBase.template_args, **{ + 'token': { + 'alias_of': 'token', + }, + 'visibility': { + 'name': _('Visibility'), + 'type': 'choice:string', + 'values': MISSKEY_VISIBILITIES, + 'default': MisskeyVisibility.PUBLIC, + }, + }) + + def __init__(self, token=None, visibility=None, **kwargs): + """ + Initialize Misskey Object + """ + super().__init__(**kwargs) + + self.token = validate_regex(token) + if not self.token: + msg = 'An invalid Misskey Access Token was specified.' + self.logger.warning(msg) + raise TypeError(msg) + + if visibility: + # Input is a string; attempt to get the lookup from our + # sound mapping + vis = 'invalid' if not isinstance(visibility, str) \ + else visibility.lower().strip() + + # This little bit of black magic allows us to match against + # against multiple versions of the same string ... etc + self.visibility = \ + next((v for v in MISSKEY_VISIBILITIES + if v.startswith(vis)), None) + + if self.visibility not in MISSKEY_VISIBILITIES: + msg = 'The Misskey visibility specified ({}) is invalid.' \ + .format(visibility) + self.logger.warning(msg) + raise TypeError(msg) + else: + self.visibility = self.template_args['visibility']['default'] + + # Prepare our URL + self.schema = 'https' if self.secure else 'http' + self.api_url = '%s://%s' % (self.schema, self.host) + + if isinstance(self.port, int): + self.api_url += ':%d' % self.port + + return + + def url(self, privacy=False, *args, **kwargs): + """ + Returns the URL built dynamically based on specified arguments. + """ + + params = { + 'visibility': self.visibility, + } + + # Extend our parameters + params.update(self.url_parameters(privacy=privacy, *args, **kwargs)) + + host = self.host + if isinstance(self.port, int): + host += ':%d' % self.port + + return '{schema}://{token}@{host}/?{params}'.format( + schema=self.secure_protocol if self.secure else self.protocol, + host=host, + token=self.pprint(self.token, privacy, safe=''), + params=NotifyMisskey.urlencode(params), + ) + + def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs): + """ + wrapper to _send since we can alert more then one channel + """ + + # prepare our headers + headers = { + 'User-Agent': self.app_id, + 'Content-Type': 'application/json', + } + + # Prepare our payload + payload = { + 'i': self.token, + 'text': body, + 'visibility': self.visibility, + } + + api_url = f'{self.api_url}/api/notes/create' + self.logger.debug('Misskey GET URL: %s (cert_verify=%r)' % ( + api_url, self.verify_certificate)) + self.logger.debug('Misskey Payload: %s' % str(payload)) + + # Always call throttle before any remote server i/o is made + self.throttle() + + try: + r = requests.post( + api_url, + headers=headers, + data=dumps(payload), + verify=self.verify_certificate, + timeout=self.request_timeout, + ) + if r.status_code != requests.codes.ok: + # We had a problem + status_str = \ + NotifyMisskey.http_response_code_lookup(r.status_code) + + self.logger.warning( + 'Failed to send Misskey 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 Misskey notification.') + + except requests.RequestException as e: + self.logger.warning( + 'A Connection error occurred sending Misskey ' + 'notification.') + self.logger.debug('Socket Exception: %s' % str(e)) + + # Return; we're done + return False + + return True + + @staticmethod + def parse_url(url): + """ + Parses the URL and returns enough arguments that can allow + us to re-instantiate this object. + + """ + + results = NotifyBase.parse_url(url) + if not results: + # We're done early as we couldn't load the results + return results + + if 'token' in results['qsd'] and len(results['qsd']['token']): + results['token'] = NotifyMisskey.unquote(results['qsd']['token']) + + elif not results['password'] and results['user']: + results['token'] = NotifyMisskey.unquote(results['user']) + + # Capture visibility if specified + if 'visibility' in results['qsd'] and \ + len(results['qsd']['visibility']): + results['visibility'] = \ + NotifyMisskey.unquote(results['qsd']['visibility']) + + return results diff --git a/libs/apprise/plugins/NotifyNextcloud.py b/libs/apprise/plugins/NotifyNextcloud.py index 6db7f2743..6bb79a7ef 100644 --- a/libs/apprise/plugins/NotifyNextcloud.py +++ b/libs/apprise/plugins/NotifyNextcloud.py @@ -1,26 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CON +# 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. import requests @@ -125,7 +133,7 @@ class NotifyNextcloud(NotifyBase): """ Initialize Nextcloud Object """ - super(NotifyNextcloud, self).__init__(**kwargs) + super().__init__(**kwargs) self.targets = parse_list(targets) if len(self.targets) == 0: @@ -304,6 +312,12 @@ class NotifyNextcloud(NotifyBase): params=NotifyNextcloud.urlencode(params), ) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + return len(self.targets) + @staticmethod def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyNextcloudTalk.py b/libs/apprise/plugins/NotifyNextcloudTalk.py index a1853795c..8a1dc4294 100644 --- a/libs/apprise/plugins/NotifyNextcloudTalk.py +++ b/libs/apprise/plugins/NotifyNextcloudTalk.py @@ -1,29 +1,38 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CON +# 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. import requests +from json import dumps from .NotifyBase import NotifyBase from ..URLBase import PrivacyMode from ..common import NotifyType @@ -106,7 +115,7 @@ class NotifyNextcloudTalk(NotifyBase): """ Initialize Nextcloud Talk Object """ - super(NotifyNextcloudTalk, self).__init__(**kwargs) + super().__init__(**kwargs) if self.user is None or self.password is None: msg = 'User and password have to be specified.' @@ -134,7 +143,9 @@ class NotifyNextcloudTalk(NotifyBase): # Prepare our Header headers = { 'User-Agent': self.app_id, - 'OCS-APIREQUEST': 'true', + 'OCS-APIRequest': 'true', + 'Accept': 'application/json', + 'Content-Type': 'application/json', } # Apply any/all header over-rides defined @@ -183,7 +194,7 @@ class NotifyNextcloudTalk(NotifyBase): try: r = requests.post( notify_url, - data=payload, + data=dumps(payload), headers=headers, auth=(self.user, self.password), verify=self.verify_certificate, @@ -252,6 +263,12 @@ class NotifyNextcloudTalk(NotifyBase): for x in self.targets]), ) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + return len(self.targets) + @staticmethod def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyNotica.py b/libs/apprise/plugins/NotifyNotica.py index 36cd77cb7..90bf7ef1c 100644 --- a/libs/apprise/plugins/NotifyNotica.py +++ b/libs/apprise/plugins/NotifyNotica.py @@ -1,26 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CON +# 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. # 1. Simply visit https://notica.us # 2. You'll be provided a new variation of the website which will look @@ -160,7 +168,7 @@ class NotifyNotica(NotifyBase): """ Initialize Notica Object """ - super(NotifyNotica, self).__init__(**kwargs) + super().__init__(**kwargs) # Token (associated with project) self.token = validate_regex(token) diff --git a/libs/apprise/plugins/NotifyNotifico.py b/libs/apprise/plugins/NotifyNotifico.py index 987661740..9b1661bf6 100644 --- a/libs/apprise/plugins/NotifyNotifico.py +++ b/libs/apprise/plugins/NotifyNotifico.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # Notifico allows you to relay notifications into IRC channels. # @@ -160,7 +167,7 @@ class NotifyNotifico(NotifyBase): """ Initialize Notifico Object """ - super(NotifyNotifico, self).__init__(**kwargs) + super().__init__(**kwargs) # Assign our message hook self.project_id = validate_regex( diff --git a/libs/apprise/plugins/NotifyNtfy.py b/libs/apprise/plugins/NotifyNtfy.py index c7b2475df..87587c041 100644 --- a/libs/apprise/plugins/NotifyNtfy.py +++ b/libs/apprise/plugins/NotifyNtfy.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2022 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # Great sources # - https://github.com/matrix-org/matrix-python-sdk @@ -40,8 +47,10 @@ from os.path import basename from .NotifyBase import NotifyBase from ..common import NotifyType +from ..common import NotifyImageSize from ..AppriseLocale import gettext_lazy as _ from ..utils import parse_list +from ..utils import parse_bool from ..utils import is_hostname from ..utils import is_ipaddr from ..utils import validate_regex @@ -65,6 +74,27 @@ NTFY_MODES = ( NtfyMode.PRIVATE, ) +# A Simple regular expression used to auto detect Auth mode if it isn't +# otherwise specified: +NTFY_AUTH_DETECT_RE = re.compile('tk_[^ \t]+', re.IGNORECASE) + + +class NtfyAuth: + """ + Define ntfy Authentication Modes + """ + # Basic auth (user and password provided) + BASIC = "basic" + + # Auth Token based + TOKEN = "token" + + +NTFY_AUTH = ( + NtfyAuth.BASIC, + NtfyAuth.TOKEN, +) + class NtfyPriority: """ @@ -142,6 +172,9 @@ class NotifyNtfy(NotifyBase): # Default upstream/cloud host if none is defined cloud_notify_url = 'https://ntfy.sh' + # Allows the user to specify the NotifyImageSize object + image_size = NotifyImageSize.XY_256 + # Message time to live (if remote client isn't around to receive it) time_to_live = 2419200 @@ -158,6 +191,8 @@ class NotifyNtfy(NotifyBase): '{schema}://{user}@{host}:{port}/{targets}', '{schema}://{user}:{password}@{host}/{targets}', '{schema}://{user}:{password}@{host}:{port}/{targets}', + '{schema}://{token}@{host}/{targets}', + '{schema}://{token}@{host}:{port}/{targets}', ) # Define our template tokens @@ -181,6 +216,11 @@ class NotifyNtfy(NotifyBase): 'type': 'string', 'private': True, }, + 'token': { + 'name': _('Token'), + 'type': 'string', + 'private': True, + }, 'topic': { 'name': _('Topic'), 'type': 'string', @@ -199,6 +239,16 @@ class NotifyNtfy(NotifyBase): 'name': _('Attach'), 'type': 'string', }, + 'image': { + 'name': _('Include Image'), + 'type': 'bool', + 'default': True, + 'map_to': 'include_image', + }, + 'avatar_url': { + 'name': _('Avatar URL'), + 'type': 'string', + }, 'filename': { 'name': _('Attach Filename'), 'type': 'string', @@ -231,6 +281,15 @@ class NotifyNtfy(NotifyBase): 'values': NTFY_MODES, 'default': NtfyMode.PRIVATE, }, + 'token': { + 'alias_of': 'token', + }, + 'auth': { + 'name': _('Authentication Type'), + 'type': 'choice:string', + 'values': NTFY_AUTH, + 'default': NtfyAuth.BASIC, + }, 'to': { 'alias_of': 'targets', }, @@ -238,11 +297,12 @@ class NotifyNtfy(NotifyBase): def __init__(self, targets=None, attach=None, filename=None, click=None, delay=None, email=None, priority=None, tags=None, mode=None, + include_image=True, avatar_url=None, auth=None, token=None, **kwargs): """ Initialize ntfy Object """ - super(NotifyNtfy, self).__init__(**kwargs) + super().__init__(**kwargs) # Prepare our mode self.mode = mode.strip().lower() \ @@ -254,6 +314,20 @@ class NotifyNtfy(NotifyBase): self.logger.warning(msg) raise TypeError(msg) + # Show image associated with notification + self.include_image = include_image + + # Prepare our authentication type + self.auth = auth.strip().lower() \ + if isinstance(auth, str) \ + else self.template_args['auth']['default'] + + if self.auth not in NTFY_AUTH: + msg = 'An invalid ntfy Authentication type ({}) was specified.' \ + .format(auth) + self.logger.warning(msg) + raise TypeError(msg) + # Attach a file (URL supported) self.attach = attach @@ -269,6 +343,9 @@ class NotifyNtfy(NotifyBase): # An email to forward notifications to self.email = email + # Save our token + self.token = token + # The Priority of the message self.priority = NotifyNtfy.template_args['priority']['default'] \ if not priority else \ @@ -280,6 +357,11 @@ class NotifyNtfy(NotifyBase): # Any optional tags to attach to the notification self.__tags = parse_list(tags) + # Avatar URL + # This allows a user to provide an over-ride to the otherwise + # dynamically generated avatar url images + self.avatar_url = avatar_url + # Build list of topics topics = parse_list(targets) self.topics = [] @@ -308,6 +390,15 @@ class NotifyNtfy(NotifyBase): self.logger.warning('There are no ntfy topics to notify') return False + # Acquire image_url + image_url = self.image_url(notify_type) + + if self.include_image and (image_url or self.avatar_url): + image_url = \ + self.avatar_url if self.avatar_url else image_url + else: + image_url = None + # Create a copy of the topics topics = list(self.topics) while len(topics) > 0: @@ -336,20 +427,23 @@ class NotifyNtfy(NotifyBase): attachment.url(privacy=True))) okay, response = self._send( - topic, body=_body, title=_title, attach=attachment) + topic, body=_body, title=_title, image_url=image_url, + attach=attachment) if not okay: # We can't post our attachment; abort immediately return False else: # Send our Notification Message - okay, response = self._send(topic, body=body, title=title) + okay, response = self._send( + topic, body=body, title=title, image_url=image_url) if not okay: # Mark our failure, but contiue to move on has_error = True return not has_error - def _send(self, topic, body=None, title=None, attach=None, **kwargs): + def _send(self, topic, body=None, title=None, attach=None, image_url=None, + **kwargs): """ Wrapper to the requests (post) object """ @@ -376,9 +470,17 @@ class NotifyNtfy(NotifyBase): else: # NotifyNtfy.PRVATE # Allow more settings to be applied now - if self.user: + if self.auth == NtfyAuth.BASIC and self.user: auth = (self.user, self.password) + elif self.auth == NtfyAuth.TOKEN: + if not self.token: + self.logger.warning('No Ntfy Token was specified') + return False, None + + # Set Token + headers['Authorization'] = f'Bearer {self.token}' + # Prepare our ntfy Template URL schema = 'https' if self.secure else 'http' @@ -397,6 +499,9 @@ class NotifyNtfy(NotifyBase): virt_payload = params notify_url += '/{topic}'.format(topic=topic) + if image_url: + headers['X-Icon'] = image_url + if title: virt_payload['title'] = title @@ -529,8 +634,13 @@ class NotifyNtfy(NotifyBase): params = { 'priority': self.priority, 'mode': self.mode, + 'image': 'yes' if self.include_image else 'no', + 'auth': self.auth, } + if self.avatar_url: + params['avatar_url'] = self.avatar_url + if self.attach is not None: params['attach'] = self.attach @@ -550,15 +660,22 @@ class NotifyNtfy(NotifyBase): # Determine Authentication auth = '' - if self.user and self.password: - auth = '{user}:{password}@'.format( - user=NotifyNtfy.quote(self.user, safe=''), - password=self.pprint( - self.password, privacy, mode=PrivacyMode.Secret, safe=''), - ) - elif self.user: - auth = '{user}@'.format( - user=NotifyNtfy.quote(self.user, safe=''), + if self.auth == NtfyAuth.BASIC: + if self.user and self.password: + auth = '{user}:{password}@'.format( + user=NotifyNtfy.quote(self.user, safe=''), + password=self.pprint( + self.password, privacy, mode=PrivacyMode.Secret, + safe=''), + ) + elif self.user: + auth = '{user}@'.format( + user=NotifyNtfy.quote(self.user, safe=''), + ) + + elif self.token: # NtfyAuth.TOKEN also + auth = '{token}@'.format( + token=self.pprint(self.token, privacy, safe=''), ) if self.mode == NtfyMode.PRIVATE: @@ -581,6 +698,12 @@ class NotifyNtfy(NotifyBase): params=NotifyNtfy.urlencode(params) ) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + return len(self.topics) + @staticmethod def parse_url(url): """ @@ -623,6 +746,15 @@ class NotifyNtfy(NotifyBase): results['tags'] = \ parse_list(NotifyNtfy.unquote(results['qsd']['tags'])) + # Boolean to include an image or not + results['include_image'] = parse_bool(results['qsd'].get( + 'image', NotifyNtfy.template_args['image']['default'])) + + # Extract avatar url if it was specified + if 'avatar_url' in results['qsd']: + results['avatar_url'] = \ + NotifyNtfy.unquote(results['qsd']['avatar_url']) + # Acquire our targets/topics results['targets'] = NotifyNtfy.split_path(results['fullpath']) @@ -631,6 +763,37 @@ class NotifyNtfy(NotifyBase): results['targets'] += \ NotifyNtfy.parse_list(results['qsd']['to']) + # Token Specified + if 'token' in results['qsd'] and len(results['qsd']['token']): + # Token presumed to be the one in use + results['auth'] = NtfyAuth.TOKEN + results['token'] = NotifyNtfy.unquote(results['qsd']['token']) + + # Auth override + if 'auth' in results['qsd'] and results['qsd']['auth']: + results['auth'] = NotifyNtfy.unquote( + results['qsd']['auth'].strip().lower()) + + if not results.get('auth') and results['user'] \ + and not results['password']: + # We can try to detect the authentication type on the formatting of + # the username. Look for tk_.* + # + # This isn't a surfire way to do things though; it's best to + # specify the auth= flag + results['auth'] = NtfyAuth.TOKEN \ + if NTFY_AUTH_DETECT_RE.match(results['user']) \ + else NtfyAuth.BASIC + + if results.get('auth') == NtfyAuth.TOKEN and not results.get('token'): + if results['user'] and not results['password']: + # Make sure we properly set our token + results['token'] = NotifyNtfy.unquote(results['user']) + + elif results['password']: + # Make sure we properly set our token + results['token'] = NotifyNtfy.unquote(results['password']) + # Mode override if 'mode' in results['qsd'] and results['qsd']['mode']: results['mode'] = NotifyNtfy.unquote( diff --git a/libs/apprise/plugins/NotifyOffice365.py b/libs/apprise/plugins/NotifyOffice365.py index 5c8ea934e..658a21526 100644 --- a/libs/apprise/plugins/NotifyOffice365.py +++ b/libs/apprise/plugins/NotifyOffice365.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2020 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # API Details: # https://docs.microsoft.com/en-us/previous-versions/office/\ @@ -173,7 +180,7 @@ class NotifyOffice365(NotifyBase): """ Initialize Office 365 Object """ - super(NotifyOffice365, self).__init__(**kwargs) + super().__init__(**kwargs) # Tenant identifier self.tenant = validate_regex( @@ -589,6 +596,12 @@ class NotifyOffice365(NotifyBase): safe='') for e in self.targets]), params=NotifyOffice365.urlencode(params)) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + return len(self.targets) + @staticmethod def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyOneSignal.py b/libs/apprise/plugins/NotifyOneSignal.py index 3d936f5be..ce56bbdd9 100644 --- a/libs/apprise/plugins/NotifyOneSignal.py +++ b/libs/apprise/plugins/NotifyOneSignal.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2020 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # One Signal requires that you've signed up with the service and # generated yourself an API Key and APP ID. @@ -44,7 +51,7 @@ from ..utils import is_email from ..AppriseLocale import gettext_lazy as _ -class OneSignalCategory(NotifyBase): +class OneSignalCategory: """ We define the different category types that we can notify via OneSignal """ @@ -85,7 +92,7 @@ class NotifyOneSignal(NotifyBase): image_size = NotifyImageSize.XY_72 # The maximum allowable batch sizes per message - maximum_batch_size = 2000 + default_batch_size = 2000 # Define object templates templates = ( @@ -114,7 +121,7 @@ class NotifyOneSignal(NotifyBase): 'private': True, 'required': True, }, - 'target_device': { + 'target_player': { 'name': _('Target Player ID'), 'type': 'string', 'map_to': 'targets', @@ -178,7 +185,7 @@ class NotifyOneSignal(NotifyBase): Initialize OneSignal """ - super(NotifyOneSignal, self).__init__(**kwargs) + super().__init__(**kwargs) # The apikey associated with the account self.apikey = validate_regex(apikey) @@ -197,7 +204,7 @@ class NotifyOneSignal(NotifyBase): raise TypeError(msg) # 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 self.include_image = include_image @@ -425,6 +432,26 @@ class NotifyOneSignal(NotifyBase): 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 def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyOpsgenie.py b/libs/apprise/plugins/NotifyOpsgenie.py index 47e580993..0639c1ed2 100644 --- a/libs/apprise/plugins/NotifyOpsgenie.py +++ b/libs/apprise/plugins/NotifyOpsgenie.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2020 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # Signup @ https://www.opsgenie.com # @@ -165,7 +172,7 @@ class NotifyOpsgenie(NotifyBase): opsgenie_default_region = OpsgenieRegion.US # The maximum allowable targets within a notification - maximum_batch_size = 50 + default_batch_size = 50 # Define object templates templates = ( @@ -262,7 +269,7 @@ class NotifyOpsgenie(NotifyBase): """ Initialize Opsgenie Object """ - super(NotifyOpsgenie, self).__init__(**kwargs) + super().__init__(**kwargs) # API Key (associated with project) self.apikey = validate_regex(apikey) @@ -301,7 +308,7 @@ class NotifyOpsgenie(NotifyBase): self.details.update(details) # 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) self.__tags = parse_list(tags) @@ -382,7 +389,7 @@ class NotifyOpsgenie(NotifyBase): has_error = False # Use body if title not set - title_body = body if not title else body + title_body = body if not title else title # Create a copy ouf our details object details = self.details.copy() @@ -529,6 +536,20 @@ class NotifyOpsgenie(NotifyBase): for x in self.targets]), 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 def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyPagerDuty.py b/libs/apprise/plugins/NotifyPagerDuty.py index deff363d5..a2417275b 100644 --- a/libs/apprise/plugins/NotifyPagerDuty.py +++ b/libs/apprise/plugins/NotifyPagerDuty.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2022 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # API Refererence: # - https://developer.pagerduty.com/api-reference/\ @@ -61,6 +68,13 @@ PAGERDUTY_SEVERITY_MAP = { NotifyType.FAILURE: PagerDutySeverity.CRITICAL, } +PAGERDUTY_SEVERITIES = ( + PagerDutySeverity.INFO, + PagerDutySeverity.WARNING, + PagerDutySeverity.CRITICAL, + PagerDutySeverity.ERROR, +) + # Priorities class PagerDutyRegion: @@ -169,6 +183,14 @@ class NotifyPagerDuty(NotifyBase): 'default': PagerDutyRegion.US, 'map_to': 'region_name', }, + # The severity is automatically determined, however you can optionally + # over-ride its value and force it to be what you want + 'severity': { + 'name': _('Severity'), + 'type': 'choice:string', + 'values': PAGERDUTY_SEVERITIES, + 'map_to': 'severity', + }, 'image': { 'name': _('Include Image'), 'type': 'bool', @@ -188,11 +210,11 @@ class NotifyPagerDuty(NotifyBase): def __init__(self, apikey, integrationkey=None, source=None, component=None, group=None, class_id=None, include_image=True, click=None, details=None, - region_name=None, **kwargs): + region_name=None, severity=None, **kwargs): """ Initialize Pager Duty Object """ - super(NotifyPagerDuty, self).__init__(**kwargs) + super().__init__(**kwargs) # Long-Lived Access token (generated from User Profile) self.apikey = validate_regex(apikey) @@ -248,6 +270,19 @@ class NotifyPagerDuty(NotifyBase): self.logger.warning(msg) raise TypeError(msg) + # The severity (if specified) + self.severity = \ + None if severity is None else next(( + s for s in PAGERDUTY_SEVERITIES + if str(s).lower().startswith(severity)), False) + + if self.severity is False: + # Invalid severity specified + msg = 'The PagerDuty severity specified ({}) is invalid.' \ + .format(severity) + self.logger.warning(msg) + raise TypeError(msg) + # A clickthrough option for notifications self.click = click @@ -289,8 +324,8 @@ class NotifyPagerDuty(NotifyBase): 'summary': body, # Set our severity - 'severity': PAGERDUTY_SEVERITY_MAP[notify_type], - + 'severity': PAGERDUTY_SEVERITY_MAP[notify_type] + if not self.severity else self.severity, # Our Alerting Source/Component 'source': self.source, @@ -400,6 +435,9 @@ class NotifyPagerDuty(NotifyBase): if self.click is not None: params['click'] = self.click + if self.severity: + params['severity'] = self.severity + # Append our custom entries our parameters params.update({'+{}'.format(k): v for k, v in self.details.items()}) @@ -464,6 +502,10 @@ class NotifyPagerDuty(NotifyBase): results['class_id'] = \ NotifyPagerDuty.unquote(results['qsd']['class']) + if 'severity' in results['qsd'] and len(results['qsd']['severity']): + results['severity'] = \ + NotifyPagerDuty.unquote(results['qsd']['severity']) + # Acquire our full path fullpath = NotifyPagerDuty.split_path(results['fullpath']) diff --git a/libs/apprise/plugins/NotifyPagerTree.py b/libs/apprise/plugins/NotifyPagerTree.py new file mode 100644 index 000000000..65a19f613 --- /dev/null +++ b/libs/apprise/plugins/NotifyPagerTree.py @@ -0,0 +1,424 @@ +# -*- coding: utf-8 -*- +# BSD 3-Clause License +# +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> +# +# 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. + +import requests +from json import dumps + +from uuid import uuid4 + +from .NotifyBase import NotifyBase +from ..common import NotifyType +from ..utils import parse_list +from ..utils import validate_regex +from ..AppriseLocale import gettext_lazy as _ + + +# Actions +class PagerTreeAction: + CREATE = 'create' + ACKNOWLEDGE = 'acknowledge' + RESOLVE = 'resolve' + + +# Urgencies +class PagerTreeUrgency: + SILENT = "silent" + LOW = "low" + MEDIUM = "medium" + HIGH = "high" + CRITICAL = "critical" + + +PAGERTREE_ACTIONS = { + PagerTreeAction.CREATE: 'create', + PagerTreeAction.ACKNOWLEDGE: 'acknowledge', + PagerTreeAction.RESOLVE: 'resolve', +} + +PAGERTREE_URGENCIES = { + # Note: This also acts as a reverse lookup mapping + PagerTreeUrgency.SILENT: 'silent', + PagerTreeUrgency.LOW: 'low', + PagerTreeUrgency.MEDIUM: 'medium', + PagerTreeUrgency.HIGH: 'high', + PagerTreeUrgency.CRITICAL: 'critical', +} +# Extend HTTP Error Messages +PAGERTREE_HTTP_ERROR_MAP = { + 402: 'Payment Required - Please subscribe or upgrade', + 403: 'Forbidden - Blocked', + 404: 'Not Found - Invalid Integration ID', + 405: 'Method Not Allowed - Integration Disabled', + 429: 'Too Many Requests - Rate Limit Exceeded', +} + + +class NotifyPagerTree(NotifyBase): + """ + A wrapper for PagerTree Notifications + """ + + # The default descriptive name associated with the Notification + service_name = 'PagerTree' + + # The services URL + service_url = 'https://pagertree.com/' + + # All PagerTree requests are secure + secure_protocol = 'pagertree' + + # A URL that takes you to the setup/help of the specific protocol + setup_url = 'https://github.com/caronc/apprise/wiki/Notify_pagertree' + + # PagerTree uses the http protocol with JSON requests + notify_url = 'https://api.pagertree.com/integration/{}' + + # Define object templates + templates = ( + '{schema}://{integration}', + ) + + # Define our template tokens + template_tokens = dict(NotifyBase.template_tokens, **{ + 'integration': { + 'name': _('Integration ID'), + 'type': 'string', + 'private': True, + 'required': True, + } + }) + + # Define our template arguments + template_args = dict(NotifyBase.template_args, **{ + 'action': { + 'name': _('Action'), + 'type': 'choice:string', + 'values': PAGERTREE_ACTIONS, + 'default': PagerTreeAction.CREATE, + }, + 'thirdparty': { + 'name': _('Third Party ID'), + 'type': 'string', + }, + 'urgency': { + 'name': _('Urgency'), + 'type': 'choice:string', + 'values': PAGERTREE_URGENCIES, + }, + 'tags': { + 'name': _('Tags'), + 'type': 'string', + }, + }) + + # Define any kwargs we're using + template_kwargs = { + 'headers': { + 'name': _('HTTP Header'), + 'prefix': '+', + }, + 'payload_extras': { + 'name': _('Payload Extras'), + 'prefix': ':', + }, + 'meta_extras': { + 'name': _('Meta Extras'), + 'prefix': '-', + }, + } + + def __init__(self, integration, action=None, thirdparty=None, + urgency=None, tags=None, headers=None, + payload_extras=None, meta_extras=None, **kwargs): + """ + Initialize PagerTree Object + """ + super().__init__(**kwargs) + + # Integration ID (associated with account) + self.integration = \ + validate_regex(integration, r'^int_[a-zA-Z0-9\-_]{7,14}$') + if not self.integration: + msg = 'An invalid PagerTree Integration ID ' \ + '({}) was specified.'.format(integration) + self.logger.warning(msg) + raise TypeError(msg) + + # thirdparty (optional, in case they want to pass the + # acknowledge or resolve action) + self.thirdparty = None + if thirdparty: + # An id was specified, we want to validate it + self.thirdparty = validate_regex(thirdparty) + if not self.thirdparty: + msg = 'An invalid PagerTree third party ID ' \ + '({}) was specified.'.format(thirdparty) + self.logger.warning(msg) + raise TypeError(msg) + + self.headers = {} + if headers: + # Store our extra headers + self.headers.update(headers) + + self.payload_extras = {} + if payload_extras: + # Store our extra payload entries + self.payload_extras.update(payload_extras) + + self.meta_extras = {} + if meta_extras: + # Store our extra payload entries + self.meta_extras.update(meta_extras) + + # Setup our action + self.action = NotifyPagerTree.template_args['action']['default'] \ + if action not in PAGERTREE_ACTIONS else \ + PAGERTREE_ACTIONS[action] + + # Setup our urgency + self.urgency = \ + None if urgency not in PAGERTREE_URGENCIES else \ + PAGERTREE_URGENCIES[urgency] + + # Any optional tags to attach to the notification + self.__tags = parse_list(tags) + + return + + def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs): + """ + Perform PagerTree Notification + """ + + # Prepare our headers + headers = { + 'User-Agent': self.app_id, + 'Content-Type': 'application/json', + } + + # Apply any/all header over-rides defined + # For things like PagerTree Token + headers.update(self.headers) + + # prepare JSON Object + payload = { + # Generate an ID (unless one was explicitly forced to be used) + 'id': self.thirdparty if self.thirdparty else str(uuid4()), + 'event_type': self.action, + } + + if self.action == PagerTreeAction.CREATE: + payload['title'] = title if title else self.app_desc + payload['description'] = body + + payload['meta'] = self.meta_extras + payload['tags'] = self.__tags + + if self.urgency is not None: + payload['urgency'] = self.urgency + + # Apply any/all payload over-rides defined + payload.update(self.payload_extras) + + # Prepare our URL based on integration + notify_url = self.notify_url.format(self.integration) + + self.logger.debug('PagerTree POST URL: %s (cert_verify=%r)' % ( + notify_url, self.verify_certificate, + )) + self.logger.debug('PagerTree 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, + ) + if r.status_code not in ( + requests.codes.ok, requests.codes.created, + requests.codes.accepted): + # We had a problem + status_str = \ + NotifyPagerTree.http_response_code_lookup( + r.status_code) + + self.logger.warning( + 'Failed to send PagerTree 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 PagerTree notification.') + + except requests.RequestException as e: + self.logger.warning( + 'A Connection error occurred sending PagerTree ' + 'notification to %s.' % self.host) + 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 = { + 'action': self.action, + } + + if self.thirdparty: + params['tid'] = self.thirdparty + + if self.urgency: + params['urgency'] = self.urgency + + if self.__tags: + params['tags'] = ','.join([x for x in self.__tags]) + + # Extend our parameters + params.update(self.url_parameters(privacy=privacy, *args, **kwargs)) + + # Headers prefixed with a '+' sign + # Append our headers into our parameters + params.update({'+{}'.format(k): v for k, v in self.headers.items()}) + + # Meta: {} prefixed with a '-' sign + # Append our meta extras into our parameters + params.update( + {'-{}'.format(k): v for k, v in self.meta_extras.items()}) + + # Payload body extras prefixed with a ':' sign + # Append our payload extras into our parameters + params.update( + {':{}'.format(k): v for k, v in self.payload_extras.items()}) + + return '{schema}://{integration}?{params}'.format( + schema=self.secure_protocol, + # never encode hostname since we're expecting it to be a valid one + integration=self.pprint(self.integration, privacy, safe=''), + params=NotifyPagerTree.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 + + # 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 + results['headers'] = { + NotifyPagerTree.unquote(x): NotifyPagerTree.unquote(y) + for x, y in results['qsd+'].items() + } + + # store any additional payload extra's defined + results['payload_extras'] = { + NotifyPagerTree.unquote(x): NotifyPagerTree.unquote(y) + for x, y in results['qsd:'].items() + } + + # store any additional meta extra's defined + results['meta_extras'] = { + NotifyPagerTree.unquote(x): NotifyPagerTree.unquote(y) + for x, y in results['qsd-'].items() + } + + # Integration ID + if 'id' in results['qsd'] and len(results['qsd']['id']): + # Shortened version of integration id + results['integration'] = \ + NotifyPagerTree.unquote(results['qsd']['id']) + + elif 'integration' in results['qsd'] and \ + len(results['qsd']['integration']): + results['integration'] = \ + NotifyPagerTree.unquote(results['qsd']['integration']) + + else: + results['integration'] = \ + NotifyPagerTree.unquote(results['host']) + + # Set our thirdparty + + if 'tid' in results['qsd'] and len(results['qsd']['tid']): + # Shortened version of thirdparty + results['thirdparty'] = \ + NotifyPagerTree.unquote(results['qsd']['tid']) + + elif 'thirdparty' in results['qsd'] and \ + len(results['qsd']['thirdparty']): + results['thirdparty'] = \ + NotifyPagerTree.unquote(results['qsd']['thirdparty']) + + # Set our urgency + if 'action' in results['qsd'] and \ + len(results['qsd']['action']): + results['action'] = \ + NotifyPagerTree.unquote(results['qsd']['action']) + + # Set our urgency + if 'urgency' in results['qsd'] and len(results['qsd']['urgency']): + results['urgency'] = \ + NotifyPagerTree.unquote(results['qsd']['urgency']) + + # Set our tags + if 'tags' in results['qsd'] and len(results['qsd']['tags']): + results['tags'] = \ + parse_list(NotifyPagerTree.unquote(results['qsd']['tags'])) + + return results diff --git a/libs/apprise/plugins/NotifyParsePlatform.py b/libs/apprise/plugins/NotifyParsePlatform.py index 40582a8dc..69efb61c7 100644 --- a/libs/apprise/plugins/NotifyParsePlatform.py +++ b/libs/apprise/plugins/NotifyParsePlatform.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2020 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # Official API reference: https://developer.gitter.im/docs/user-resource @@ -130,7 +137,7 @@ class NotifyParsePlatform(NotifyBase): """ Initialize Parse Platform Object """ - super(NotifyParsePlatform, self).__init__(**kwargs) + super().__init__(**kwargs) self.fullpath = kwargs.get('fullpath') if not isinstance(self.fullpath, str): diff --git a/libs/apprise/plugins/NotifyPopcornNotify.py b/libs/apprise/plugins/NotifyPopcornNotify.py index 7352c6676..a36aed9f9 100644 --- a/libs/apprise/plugins/NotifyPopcornNotify.py +++ b/libs/apprise/plugins/NotifyPopcornNotify.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2020 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import requests @@ -105,7 +112,7 @@ class NotifyPopcornNotify(NotifyBase): """ Initialize PopcornNotify Object """ - super(NotifyPopcornNotify, self).__init__(**kwargs) + super().__init__(**kwargs) # Access Token (associated with project) self.apikey = validate_regex( @@ -258,6 +265,21 @@ class NotifyPopcornNotify(NotifyBase): [NotifyPopcornNotify.quote(x, safe='') for x in self.targets]), 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 def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyProwl.py b/libs/apprise/plugins/NotifyProwl.py index b41d7566a..cebe07010 100644 --- a/libs/apprise/plugins/NotifyProwl.py +++ b/libs/apprise/plugins/NotifyProwl.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import requests @@ -143,7 +150,7 @@ class NotifyProwl(NotifyBase): """ Initialize Prowl Object """ - super(NotifyProwl, self).__init__(**kwargs) + super().__init__(**kwargs) # The Priority of the message self.priority = NotifyProwl.template_args['priority']['default'] \ diff --git a/libs/apprise/plugins/NotifyPushBullet.py b/libs/apprise/plugins/NotifyPushBullet.py index 53240d2e7..07b2a43a0 100644 --- a/libs/apprise/plugins/NotifyPushBullet.py +++ b/libs/apprise/plugins/NotifyPushBullet.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import requests from json import dumps @@ -115,7 +122,7 @@ class NotifyPushBullet(NotifyBase): """ Initialize PushBullet Object """ - super(NotifyPushBullet, self).__init__(**kwargs) + super().__init__(**kwargs) # Access Token (associated with project) self.accesstoken = validate_regex(accesstoken) @@ -399,6 +406,12 @@ class NotifyPushBullet(NotifyBase): targets=targets, params=NotifyPushBullet.urlencode(params)) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + return len(self.targets) + @staticmethod def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyPushSafer.py b/libs/apprise/plugins/NotifyPushSafer.py index 3f495702a..19bff2bd0 100644 --- a/libs/apprise/plugins/NotifyPushSafer.py +++ b/libs/apprise/plugins/NotifyPushSafer.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import base64 import requests @@ -400,7 +407,7 @@ class NotifyPushSafer(NotifyBase): """ Initialize PushSafer Object """ - super(NotifyPushSafer, self).__init__(**kwargs) + super().__init__(**kwargs) # # Priority @@ -787,6 +794,12 @@ class NotifyPushSafer(NotifyBase): targets=targets, params=NotifyPushSafer.urlencode(params)) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + return len(self.targets) + @staticmethod def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyPushed.py b/libs/apprise/plugins/NotifyPushed.py index c6dfe6ad1..b5ec3f6de 100644 --- a/libs/apprise/plugins/NotifyPushed.py +++ b/libs/apprise/plugins/NotifyPushed.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import re import requests @@ -120,7 +127,7 @@ class NotifyPushed(NotifyBase): Initialize Pushed Object """ - super(NotifyPushed, self).__init__(**kwargs) + super().__init__(**kwargs) # Application Key (associated with project) self.app_key = validate_regex(app_key) @@ -322,6 +329,13 @@ class NotifyPushed(NotifyBase): )]), 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 def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyPushjet.py b/libs/apprise/plugins/NotifyPushjet.py index 49e6596e6..c6e36a393 100644 --- a/libs/apprise/plugins/NotifyPushjet.py +++ b/libs/apprise/plugins/NotifyPushjet.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import requests from json import dumps @@ -102,7 +109,7 @@ class NotifyPushjet(NotifyBase): """ Initialize Pushjet Object """ - super(NotifyPushjet, self).__init__(**kwargs) + super().__init__(**kwargs) # Secret Key (associated with project) self.secret_key = validate_regex(secret_key) diff --git a/libs/apprise/plugins/NotifyPushover.py b/libs/apprise/plugins/NotifyPushover.py index 1dfb5787b..64b94774c 100644 --- a/libs/apprise/plugins/NotifyPushover.py +++ b/libs/apprise/plugins/NotifyPushover.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import re import requests @@ -158,7 +165,7 @@ class NotifyPushover(NotifyBase): notify_url = 'https://api.pushover.net/1/messages.json' # The maximum allowable characters allowed in the body per message - body_maxlen = 512 + body_maxlen = 1024 # Default Pushover sound default_pushover_sound = PushoverSound.PUSHOVER @@ -251,7 +258,7 @@ class NotifyPushover(NotifyBase): """ Initialize Pushover Object """ - super(NotifyPushover, self).__init__(**kwargs) + super().__init__(**kwargs) # Access Token (associated with project) self.token = validate_regex(token) @@ -570,6 +577,12 @@ class NotifyPushover(NotifyBase): devices=devices, params=NotifyPushover.urlencode(params)) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + return len(self.targets) + @staticmethod def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyReddit.py b/libs/apprise/plugins/NotifyReddit.py index e5709bf89..5cb22a726 100644 --- a/libs/apprise/plugins/NotifyReddit.py +++ b/libs/apprise/plugins/NotifyReddit.py @@ -1,27 +1,35 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2021 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. + # # 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...' @@ -240,7 +248,7 @@ class NotifyReddit(NotifyBase): """ Initialize Notify Reddit Object """ - super(NotifyReddit, self).__init__(**kwargs) + super().__init__(**kwargs) # Initialize subreddit list self.subreddits = set() @@ -359,6 +367,12 @@ class NotifyReddit(NotifyBase): params=NotifyReddit.urlencode(params), ) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + return len(self.subreddits) + def login(self): """ A simple wrapper to authenticate with the Reddit Server diff --git a/libs/apprise/plugins/NotifyRocketChat.py b/libs/apprise/plugins/NotifyRocketChat.py index b8337ff59..ca6b5cd83 100644 --- a/libs/apprise/plugins/NotifyRocketChat.py +++ b/libs/apprise/plugins/NotifyRocketChat.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import re import requests @@ -48,10 +55,6 @@ RC_HTTP_ERROR_MAP = { 401: 'Authentication tokens provided is invalid or missing.', } -# Used to break apart list of potential tags by their delimiter -# into a usable list. -LIST_DELIM = re.compile(r'[ \t\r\n,\\/]+') - class RocketChatAuthMode: """ @@ -188,7 +191,7 @@ class NotifyRocketChat(NotifyBase): """ Initialize Notify Rocket.Chat Object """ - super(NotifyRocketChat, self).__init__(**kwargs) + super().__init__(**kwargs) # Set our schema self.schema = 'https' if self.secure else 'http' @@ -320,7 +323,8 @@ class NotifyRocketChat(NotifyBase): auth = '{user}{webhook}@'.format( user='{}:'.format(NotifyRocketChat.quote(self.user, safe='')) if self.user else '', - webhook=self.pprint(self.webhook, privacy, safe=''), + webhook=self.pprint(self.webhook, privacy, + mode=PrivacyMode.Secret, safe=''), ) default_port = 443 if self.secure else 80 @@ -333,7 +337,7 @@ class NotifyRocketChat(NotifyBase): port='' if self.port is None or self.port == default_port else ':{}'.format(self.port), targets='/'.join( - [NotifyRocketChat.quote(x, safe='') for x in chain( + [NotifyRocketChat.quote(x, safe='@#') for x in chain( # Channels are prefixed with a pound/hashtag symbol ['#{}'.format(x) for x in self.channels], # Rooms are as is @@ -344,6 +348,13 @@ class NotifyRocketChat(NotifyBase): 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): """ wrapper to _send since we can alert more then one channel diff --git a/libs/apprise/plugins/NotifyRyver.py b/libs/apprise/plugins/NotifyRyver.py index e186a84bb..b8b34a3c4 100644 --- a/libs/apprise/plugins/NotifyRyver.py +++ b/libs/apprise/plugins/NotifyRyver.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # To use this plugin, you need to first generate a webhook. @@ -129,7 +136,7 @@ class NotifyRyver(NotifyBase): """ Initialize Ryver Object """ - super(NotifyRyver, self).__init__(**kwargs) + super().__init__(**kwargs) # API Token (associated with project) self.token = validate_regex( diff --git a/libs/apprise/plugins/NotifySES.py b/libs/apprise/plugins/NotifySES.py index 1c14d34a6..fb0017036 100644 --- a/libs/apprise/plugins/NotifySES.py +++ b/libs/apprise/plugins/NotifySES.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2021 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # API Information: # - https://docs.aws.amazon.com/ses/latest/APIReference/API_SendRawEmail.html @@ -217,7 +224,7 @@ class NotifySES(NotifyBase): """ Initialize Notify AWS SES Object """ - super(NotifySES, self).__init__(**kwargs) + super().__init__(**kwargs) # Store our AWS API Access Key self.aws_access_key_id = validate_regex(access_key_id) @@ -809,6 +816,13 @@ class NotifySES(NotifyBase): 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 def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifySMSEagle.py b/libs/apprise/plugins/NotifySMSEagle.py index 40e562691..747831e10 100644 --- a/libs/apprise/plugins/NotifySMSEagle.py +++ b/libs/apprise/plugins/NotifySMSEagle.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2022 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import re import requests @@ -66,6 +73,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): """ A wrapper for SMSEagle Notifications @@ -191,7 +214,7 @@ class NotifySMSEagle(NotifyBase): """ Initialize SMSEagle Object """ - super(NotifySMSEagle, self).__init__(**kwargs) + super().__init__(**kwargs) # Prepare Flash Mode Flag self.flash = flash @@ -396,15 +419,15 @@ class NotifySMSEagle(NotifyBase): batch_size = 1 if not self.batch else self.default_batch_size notify_by = { - 'phone': { + SMSEagleCategory.PHONE: { "method": "sms.send_sms", 'target': 'to', }, - 'group': { + SMSEagleCategory.GROUP: { "method": "sms.send_togroup", 'target': 'groupname', }, - 'contact': { + SMSEagleCategory.CONTACT: { "method": "sms.send_tocontact", 'target': 'contactname', }, @@ -413,7 +436,7 @@ class NotifySMSEagle(NotifyBase): # categories separated into a tuple since notify_by.keys() # returns an unpredicable list in Python 2.7 which causes # tests to fail every so often - for category in ('phone', 'group', 'contact'): + for category in SMSEAGLE_CATEGORIES: # Create a copy of our template payload = { 'method': notify_by[category]['method'], @@ -583,10 +606,34 @@ class NotifySMSEagle(NotifyBase): ['@{}'.format(x) for x in self.target_contacts], # Groups ['#{}'.format(x) for x in self.target_groups], + # Pass along the same invalid entries as were provided + self.invalid_targets, )]), 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 def parse_url(url): """ @@ -633,6 +680,7 @@ class NotifySMSEagle(NotifyBase): results['status'] = \ parse_bool(results['qsd'].get('status', False)) + # Get priority if 'priority' in results['qsd'] and len(results['qsd']['priority']): results['priority'] = \ NotifySMSEagle.unquote(results['qsd']['priority']) diff --git a/libs/apprise/plugins/NotifySMTP2Go.py b/libs/apprise/plugins/NotifySMTP2Go.py index 952604a7b..3634ba6a8 100644 --- a/libs/apprise/plugins/NotifySMTP2Go.py +++ b/libs/apprise/plugins/NotifySMTP2Go.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2021 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # Signup @ https://smtp2go.com (free accounts available) # @@ -159,7 +166,7 @@ class NotifySMTP2Go(NotifyBase): """ Initialize SMTP2Go Object """ - super(NotifySMTP2Go, self).__init__(**kwargs) + super().__init__(**kwargs) # API Key (associated with project) self.apikey = validate_regex(apikey) @@ -506,6 +513,21 @@ class NotifySMTP2Go(NotifyBase): safe='') for e in self.targets]), params=NotifySMTP2Go.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): """ diff --git a/libs/apprise/plugins/NotifySNS.py b/libs/apprise/plugins/NotifySNS.py index 8af0847a2..c1d2ed932 100644 --- a/libs/apprise/plugins/NotifySNS.py +++ b/libs/apprise/plugins/NotifySNS.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import re import hmac @@ -159,7 +166,7 @@ class NotifySNS(NotifyBase): """ Initialize Notify AWS SNS Object """ - super(NotifySNS, self).__init__(**kwargs) + super().__init__(**kwargs) # Store our AWS API Access Key self.aws_access_key_id = validate_regex(access_key_id) @@ -593,6 +600,12 @@ class NotifySNS(NotifyBase): params=NotifySNS.urlencode(params), ) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + return len(self.phone) + len(self.topics) + @staticmethod def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifySendGrid.py b/libs/apprise/plugins/NotifySendGrid.py index 12f829fb3..d1ae8a4d4 100644 --- a/libs/apprise/plugins/NotifySendGrid.py +++ b/libs/apprise/plugins/NotifySendGrid.py @@ -1,27 +1,35 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. + # # You will need an API Key for this plugin to work. # From the Settings -> API Keys you can click "Create API Key" if you don't @@ -159,7 +167,7 @@ class NotifySendGrid(NotifyBase): """ Initialize Notify SendGrid Object """ - super(NotifySendGrid, self).__init__(**kwargs) + super().__init__(**kwargs) # API Key (associated with project) self.apikey = validate_regex( @@ -279,6 +287,12 @@ class NotifySendGrid(NotifyBase): params=NotifySendGrid.urlencode(params), ) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + return len(self.targets) + def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs): """ Perform SendGrid Notification diff --git a/libs/apprise/plugins/NotifyServerChan.py b/libs/apprise/plugins/NotifyServerChan.py index 2af40d73f..6fa8c5570 100644 --- a/libs/apprise/plugins/NotifyServerChan.py +++ b/libs/apprise/plugins/NotifyServerChan.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2020 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import re import requests @@ -79,7 +86,7 @@ class NotifyServerChan(NotifyBase): """ Initialize ServerChan Object """ - super(NotifyServerChan, self).__init__(**kwargs) + super().__init__(**kwargs) # Token (associated with project) self.token = validate_regex( diff --git a/libs/apprise/plugins/NotifySignalAPI.py b/libs/apprise/plugins/NotifySignalAPI.py index a24425093..589499f8d 100644 --- a/libs/apprise/plugins/NotifySignalAPI.py +++ b/libs/apprise/plugins/NotifySignalAPI.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2022 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import re import requests @@ -157,7 +164,7 @@ class NotifySignalAPI(NotifyBase): """ Initialize SignalAPI Object """ - super(NotifySignalAPI, self).__init__(**kwargs) + super().__init__(**kwargs) # Prepare Batch Mode Flag self.batch = batch @@ -424,6 +431,21 @@ class NotifySignalAPI(NotifyBase): params=NotifySignalAPI.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 def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifySimplePush.py b/libs/apprise/plugins/NotifySimplePush.py index 400216e72..25066067c 100644 --- a/libs/apprise/plugins/NotifySimplePush.py +++ b/libs/apprise/plugins/NotifySimplePush.py @@ -1,27 +1,35 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. + from os import urandom from json import loads import requests @@ -125,7 +133,7 @@ class NotifySimplePush(NotifyBase): """ Initialize SimplePush Object """ - super(NotifySimplePush, self).__init__(**kwargs) + super().__init__(**kwargs) # API Key (associated with project) self.apikey = validate_regex(apikey) diff --git a/libs/apprise/plugins/NotifySinch.py b/libs/apprise/plugins/NotifySinch.py index f911cdb71..0756f76b3 100644 --- a/libs/apprise/plugins/NotifySinch.py +++ b/libs/apprise/plugins/NotifySinch.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # To use this service you will need a Sinch account to which you can get your # API_TOKEN and SERVICE_PLAN_ID right from your console/dashboard at: @@ -169,7 +176,7 @@ class NotifySinch(NotifyBase): """ Initialize Sinch Object """ - super(NotifySinch, self).__init__(**kwargs) + super().__init__(**kwargs) # The Account SID associated with the account self.service_plan_id = validate_regex( @@ -401,6 +408,13 @@ class NotifySinch(NotifyBase): [NotifySinch.quote(x, safe='') for x in self.targets]), params=NotifySinch.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 def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifySlack.py b/libs/apprise/plugins/NotifySlack.py index 3b0c733a3..0d85d25fe 100644 --- a/libs/apprise/plugins/NotifySlack.py +++ b/libs/apprise/plugins/NotifySlack.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # There are 2 ways to use this plugin... # Method 1: Via Webhook: @@ -278,7 +285,7 @@ class NotifySlack(NotifyBase): """ Initialize Slack Object """ - super(NotifySlack, self).__init__(**kwargs) + super().__init__(**kwargs) # Setup our mode self.mode = SlackMode.BOT if access_token else SlackMode.WEBHOOK @@ -347,6 +354,22 @@ class NotifySlack(NotifyBase): r'>': '>', } + # To notify a channel, one uses <!channel|channel> + self._re_channel_support = re.compile( + r'(?P<match>(?:<|\<)?[ \t]*' + r'!(?P<channel>[^| \n]+)' + r'(?:[ \t]*\|[ \t]*(?:(?P<val>[^\n]+?)[ \t]*)?(?:>|\>)' + r'|(?:>|\>)))', re.IGNORECASE) + + # The markdown in slack isn't [desc](url), it's <url|desc> + # + # To accomodate this, we need to ensure we don't escape URLs that match + self._re_url_support = re.compile( + r'(?P<match>(?:<|\<)?[ \t]*' + r'(?P<url>(?:https?|mailto)://[^| \n]+)' + r'(?:[ \t]*\|[ \t]*(?:(?P<val>[^\n]+?)[ \t]*)?(?:>|\>)' + r'|(?:>|\>)))', re.IGNORECASE) + # Iterate over above list and store content accordingly self._re_formatting_rules = re.compile( r'(' + '|'.join(self._re_formatting_map.keys()) + r')', @@ -439,6 +462,35 @@ class NotifySlack(NotifyBase): lambda x: self._re_formatting_map[x.group()], body, ) + # Support <!channel|desc>, <!channel> entries + for match in self._re_channel_support.findall(body): + # Swap back any ampersands previously updaated + channel = match[1].strip() + desc = match[2].strip() + + # Update our string + body = re.sub( + re.escape(match[0]), + '<!{channel}|{desc}>'.format( + channel=channel, desc=desc) + if desc else '<!{channel}>'.format(channel=channel), + body, + re.IGNORECASE) + + # Support <url|desc>, <url> entries + for match in self._re_url_support.findall(body): + # Swap back any ampersands previously updaated + url = match[1].replace('&', '&') + desc = match[2].strip() + + # Update our string + body = re.sub( + re.escape(match[0]), + '<{url}|{desc}>'.format(url=url, desc=desc) + if desc else '<{url}>'.format(url=url), + body, + re.IGNORECASE) + # Perform Formatting on title here; this is not needed for block # mode above title = self._re_formatting_rules.sub( # pragma: no branch @@ -803,7 +855,7 @@ class NotifySlack(NotifyBase): # The text 'ok' is returned if this is a Webhook request # So the below captures that as well. status_okay = (response and response.get('ok', False)) \ - if self.mode is SlackMode.BOT else r.text == 'ok' + if self.mode is SlackMode.BOT else r.content == b'ok' if r.status_code != requests.codes.ok or not status_okay: # We had a problem @@ -963,6 +1015,12 @@ class NotifySlack(NotifyBase): params=NotifySlack.urlencode(params), ) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + return len(self.channels) + @staticmethod def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifySparkPost.py b/libs/apprise/plugins/NotifySparkPost.py index 93ce84d52..25024bc5f 100644 --- a/libs/apprise/plugins/NotifySparkPost.py +++ b/libs/apprise/plugins/NotifySparkPost.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2020 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # Signup @ https://www.sparkpost.com # @@ -79,8 +86,10 @@ SPARKPOST_HTTP_ERROR_MAP = { } -# Priorities class SparkPostRegion: + """ + Regions + """ US = 'us' EU = 'eu' @@ -141,9 +150,6 @@ class NotifySparkPost(NotifyBase): # Default Notify Format notify_format = NotifyFormat.HTML - # The default region to use if one isn't otherwise specified - sparkpost_default_region = SparkPostRegion.US - # Define object templates templates = ( '{schema}://{user}@{host}:{apikey}/', @@ -224,7 +230,7 @@ class NotifySparkPost(NotifyBase): """ Initialize SparkPost Object """ - super(NotifySparkPost, self).__init__(**kwargs) + super().__init__(**kwargs) # API Key (associated with project) self.apikey = validate_regex(apikey) @@ -254,7 +260,7 @@ class NotifySparkPost(NotifyBase): # Store our region try: - self.region_name = self.sparkpost_default_region \ + self.region_name = self.template_args['region']['default'] \ if region_name is None else region_name.lower() if self.region_name not in SPARKPOST_REGIONS: @@ -716,6 +722,21 @@ class NotifySparkPost(NotifyBase): safe='') for e in self.targets]), params=NotifySparkPost.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): """ @@ -746,7 +767,7 @@ class NotifySparkPost(NotifyBase): NotifySparkPost.unquote(results['qsd']['name']) if 'region' in results['qsd'] and len(results['qsd']['region']): - # Extract from name to associate with from address + # Extract region results['region_name'] = \ NotifySparkPost.unquote(results['qsd']['region']) diff --git a/libs/apprise/plugins/NotifySpontit.py b/libs/apprise/plugins/NotifySpontit.py index 59c708bde..01d4e1980 100644 --- a/libs/apprise/plugins/NotifySpontit.py +++ b/libs/apprise/plugins/NotifySpontit.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2020 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # To use this service you will need a Spontit account from their website # at https://spontit.com/ @@ -148,7 +155,7 @@ class NotifySpontit(NotifyBase): """ Initialize Spontit Object """ - super(NotifySpontit, self).__init__(**kwargs) + super().__init__(**kwargs) # User ID (associated with project) user = validate_regex( @@ -343,6 +350,13 @@ class NotifySpontit(NotifyBase): [NotifySpontit.quote(x, safe='') for x in self.targets]), params=NotifySpontit.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 def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyStreamlabs.py b/libs/apprise/plugins/NotifyStreamlabs.py index e4217151f..3489519a5 100644 --- a/libs/apprise/plugins/NotifyStreamlabs.py +++ b/libs/apprise/plugins/NotifyStreamlabs.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2021 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # For this to work correctly you need to register an app # and generate an access token @@ -183,7 +190,7 @@ class NotifyStreamlabs(NotifyBase): Initialize Streamlabs Object """ - super(NotifyStreamlabs, self).__init__(**kwargs) + super().__init__(**kwargs) # access token is generated by user # using https://streamlabs.com/api/v1.0/token diff --git a/libs/apprise/plugins/NotifySyslog.py b/libs/apprise/plugins/NotifySyslog.py index 20d20b192..433aab9c5 100644 --- a/libs/apprise/plugins/NotifySyslog.py +++ b/libs/apprise/plugins/NotifySyslog.py @@ -1,27 +1,35 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. + import os import syslog import socket @@ -199,7 +207,7 @@ class NotifySyslog(NotifyBase): """ Initialize Syslog Object """ - super(NotifySyslog, self).__init__(**kwargs) + super().__init__(**kwargs) if facility: try: diff --git a/libs/apprise/plugins/NotifyTechulusPush.py b/libs/apprise/plugins/NotifyTechulusPush.py index 5dcb33e5f..0f3e79e53 100644 --- a/libs/apprise/plugins/NotifyTechulusPush.py +++ b/libs/apprise/plugins/NotifyTechulusPush.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # To use this plugin, you need to download the app # - Apple: https://itunes.apple.com/us/app/\ @@ -104,7 +111,7 @@ class NotifyTechulusPush(NotifyBase): """ Initialize Techulus Push Object """ - super(NotifyTechulusPush, self).__init__(**kwargs) + super().__init__(**kwargs) # The apikey associated with the account self.apikey = validate_regex( diff --git a/libs/apprise/plugins/NotifyTelegram.py b/libs/apprise/plugins/NotifyTelegram.py index 1317d7ca0..d5a52be60 100644 --- a/libs/apprise/plugins/NotifyTelegram.py +++ b/libs/apprise/plugins/NotifyTelegram.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # To use this plugin, you need to first access https://api.telegram.org # You need to create a bot and acquire it's Token Identifier (bot_token) @@ -305,17 +312,22 @@ class NotifyTelegram(NotifyBase): 'type': 'bool', 'default': False, }, + 'topic': { + 'name': _('Topic Thread ID'), + 'type': 'int', + }, 'to': { 'alias_of': 'targets', }, }) def __init__(self, bot_token, targets, detect_owner=True, - include_image=False, silent=None, preview=None, **kwargs): + include_image=False, silent=None, preview=None, topic=None, + **kwargs): """ Initialize Telegram Object """ - super(NotifyTelegram, self).__init__(**kwargs) + super().__init__(**kwargs) self.bot_token = validate_regex( bot_token, *self.template_tokens['bot_token']['regex'], @@ -337,6 +349,20 @@ class NotifyTelegram(NotifyBase): self.preview = self.template_args['preview']['default'] \ if preview is None else bool(preview) + if topic: + try: + self.topic = int(topic) + + except (TypeError, ValueError): + # Not a valid integer; ignore entry + err = 'The Telegram Topic ID specified ({}) is invalid.'\ + .format(topic) + self.logger.warning(err) + raise TypeError(err) + else: + # No Topic Thread + self.topic = None + # if detect_owner is set to True, we will attempt to determine who # the bot owner is based on the first person who messaged it. This # is not a fool proof way of doing things as over time Telegram removes @@ -628,6 +654,9 @@ class NotifyTelegram(NotifyBase): 'disable_web_page_preview': not self.preview, } + if self.topic: + payload['message_thread_id'] = self.topic + # Prepare Message Body if self.notify_format == NotifyFormat.MARKDOWN: payload['parse_mode'] = 'MARKDOWN' @@ -775,6 +804,9 @@ class NotifyTelegram(NotifyBase): 'preview': 'yes' if self.preview else 'no', } + if self.topic: + params['topic'] = self.topic + # Extend our parameters params.update(self.url_parameters(privacy=privacy, *args, **kwargs)) @@ -787,6 +819,12 @@ class NotifyTelegram(NotifyBase): [NotifyTelegram.quote('@{}'.format(x)) for x in self.targets]), params=NotifyTelegram.urlencode(params)) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + return len(self.targets) + @staticmethod def parse_url(url): """ @@ -856,6 +894,10 @@ class NotifyTelegram(NotifyBase): # Store our bot token results['bot_token'] = bot_token + # Support Thread Topic + if 'topic' in results['qsd'] and len(results['qsd']['topic']): + results['topic'] = results['qsd']['topic'] + # Silent (Sends the message Silently); users will receive # notification with no sound. results['silent'] = \ diff --git a/libs/apprise/plugins/NotifyTwilio.py b/libs/apprise/plugins/NotifyTwilio.py index 883cc50ba..08a3b2917 100644 --- a/libs/apprise/plugins/NotifyTwilio.py +++ b/libs/apprise/plugins/NotifyTwilio.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # To use this service you will need a Twilio account to which you can get your # AUTH_TOKEN and ACCOUNT SID right from your console/dashboard at: @@ -163,7 +170,7 @@ class NotifyTwilio(NotifyBase): """ Initialize Twilio Object """ - super(NotifyTwilio, self).__init__(**kwargs) + super().__init__(**kwargs) # The Account SID associated with the account self.account_sid = validate_regex( @@ -378,6 +385,13 @@ class NotifyTwilio(NotifyBase): [NotifyTwilio.quote(x, safe='') for x in self.targets]), params=NotifyTwilio.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 def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyTwist.py b/libs/apprise/plugins/NotifyTwist.py index d11208680..ea7b19760 100644 --- a/libs/apprise/plugins/NotifyTwist.py +++ b/libs/apprise/plugins/NotifyTwist.py @@ -1,27 +1,35 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. + # # All of the documentation needed to work with the Twist API can be found # here: https://developer.twist.com/v3/ @@ -131,7 +139,7 @@ class NotifyTwist(NotifyBase): """ Initialize Notify Twist Object """ - super(NotifyTwist, self).__init__(**kwargs) + super().__init__(**kwargs) # Initialize channels list self.channels = set() @@ -248,6 +256,12 @@ class NotifyTwist(NotifyBase): params=NotifyTwist.urlencode(params), ) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + return len(self.channels) + len(self.channel_ids) + def login(self): """ A simple wrapper to authenticate with the Twist Server diff --git a/libs/apprise/plugins/NotifyTwitter.py b/libs/apprise/plugins/NotifyTwitter.py index f26d30fa4..7862d0042 100644 --- a/libs/apprise/plugins/NotifyTwitter.py +++ b/libs/apprise/plugins/NotifyTwitter.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # See https://developer.twitter.com/en/docs/direct-messages/\ # sending-and-receiving/api-reference/new-event.html @@ -75,7 +82,7 @@ class NotifyTwitter(NotifyBase): service_url = 'https://twitter.com/' # The default secure protocol is twitter. - secure_protocol = 'twitter' + secure_protocol = ('twitter', 'tweet') # A URL that takes you to the setup/help of the specific protocol setup_url = 'https://github.com/caronc/apprise/wiki/Notify_twitter' @@ -194,7 +201,7 @@ class NotifyTwitter(NotifyBase): Initialize Twitter Object """ - super(NotifyTwitter, self).__init__(**kwargs) + super().__init__(**kwargs) self.ckey = validate_regex(ckey) if not self.ckey: @@ -221,21 +228,21 @@ class NotifyTwitter(NotifyBase): raise TypeError(msg) # Store our webhook mode - self.mode = None \ + self.mode = self.template_args['mode']['default'] \ if not isinstance(mode, str) else mode.lower() - # Set Cache Flag - self.cache = cache - - # Prepare Image Batch Mode Flag - self.batch = batch - if self.mode not in TWITTER_MESSAGE_MODES: msg = 'The Twitter message mode specified ({}) is invalid.' \ .format(mode) self.logger.warning(msg) raise TypeError(msg) + # Set Cache Flag + self.cache = cache + + # Prepare Image Batch Mode Flag + self.batch = batch + # Track any errors has_error = False @@ -249,7 +256,7 @@ class NotifyTwitter(NotifyBase): has_error = True self.logger.warning( - 'Dropped invalid user ({}) specified.'.format(target), + 'Dropped invalid Twitter user ({}) specified.'.format(target), ) if has_error and not self.targets: @@ -261,6 +268,10 @@ class NotifyTwitter(NotifyBase): self.logger.warning(msg) raise TypeError(msg) + # Initialize our cache values + self._whoami_cache = None + self._user_cache = {} + return def send(self, body, title='', notify_type=NotifyType.INFO, attach=None, @@ -293,7 +304,7 @@ class NotifyTwitter(NotifyBase): continue self.logger.debug( - 'Preparing Twiter attachment {}'.format( + 'Preparing Twitter attachment {}'.format( attachment.url(privacy=True))) # Upload our image and get our id associated with it @@ -536,16 +547,9 @@ class NotifyTwitter(NotifyBase): """ - # Prepare a whoami key; this is to prevent conflict with other - # NotifyTwitter declarations that may or may not use a different - # set of authentication keys - whoami_key = '{}{}{}{}'.format( - self.ckey, self.csecret, self.akey, self.asecret) - - if lazy and hasattr(NotifyTwitter, '_whoami_cache') \ - and whoami_key in getattr(NotifyTwitter, '_whoami_cache'): + if lazy and self._whoami_cache is not None: # Use cached response - return getattr(NotifyTwitter, '_whoami_cache')[whoami_key] + return self._whoami_cache # Contains a mapping of screen_name to id results = {} @@ -560,22 +564,11 @@ class NotifyTwitter(NotifyBase): if postokay: try: results[response['screen_name']] = response['id'] + self._whoami_cache = { + response['screen_name']: response['id'], + } - if lazy: - # Cache our response for future references - if not hasattr(NotifyTwitter, '_whoami_cache'): - setattr( - NotifyTwitter, '_whoami_cache', - {whoami_key: results}) - else: - getattr(NotifyTwitter, '_whoami_cache')\ - .update({whoami_key: results}) - - # Update our user cache as well - if not hasattr(NotifyTwitter, '_user_cache'): - setattr(NotifyTwitter, '_user_cache', results) - else: - getattr(NotifyTwitter, '_user_cache').update(results) + self._user_cache.update(results) except (TypeError, KeyError): pass @@ -595,10 +588,10 @@ class NotifyTwitter(NotifyBase): # Build a unique set of names names = parse_list(screen_name) - if lazy and hasattr(NotifyTwitter, '_user_cache'): + if lazy and self._user_cache: # Use cached response - results = {k: v for k, v in getattr( - NotifyTwitter, '_user_cache').items() if k in names} + results = { + k: v for k, v in self._user_cache.items() if k in names} # limit our names if they already exist in our cache names = [name for name in names if name not in results] @@ -612,7 +605,7 @@ class NotifyTwitter(NotifyBase): # https://developer.twitter.com/en/docs/accounts-and-users/\ # follow-search-get-users/api-reference/get-users-lookup for i in range(0, len(names), 100): - # Send Twitter DM + # Look up our names by their screen_name postokay, response = self._fetch( self.twitter_lookup, payload={ @@ -635,11 +628,7 @@ class NotifyTwitter(NotifyBase): # Cache our response for future use; this saves on un-nessisary extra # hits against the Twitter API when we already know the answer - if lazy: - if not hasattr(NotifyTwitter, '_user_cache'): - setattr(NotifyTwitter, '_user_cache', results) - else: - getattr(NotifyTwitter, '_user_cache').update(results) + self._user_cache.update(results) return results @@ -686,7 +675,7 @@ class NotifyTwitter(NotifyBase): # 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 + # Twitter server. One would hope we're on NTP and our clocks are # the same allowing this to role smoothly: now = datetime.utcnow() @@ -804,13 +793,9 @@ class NotifyTwitter(NotifyBase): # Extend our parameters params.update(self.url_parameters(privacy=privacy, *args, **kwargs)) - if len(self.targets) > 0: - params['to'] = ','.join( - [NotifyTwitter.quote(x, safe='') for x in self.targets]) - return '{schema}://{ckey}/{csecret}/{akey}/{asecret}' \ '/{targets}/?{params}'.format( - schema=self.secure_protocol, + schema=self.secure_protocol[0], ckey=self.pprint(self.ckey, privacy, safe=''), csecret=self.pprint( self.csecret, privacy, mode=PrivacyMode.Secret, safe=''), @@ -818,10 +803,17 @@ class NotifyTwitter(NotifyBase): asecret=self.pprint( self.asecret, privacy, mode=PrivacyMode.Secret, safe=''), targets='/'.join( - [NotifyTwitter.quote('@{}'.format(target), safe='') + [NotifyTwitter.quote('@{}'.format(target), safe='@') for target in self.targets]), params=NotifyTwitter.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 def parse_url(url): """ @@ -834,34 +826,31 @@ class NotifyTwitter(NotifyBase): # We're done early as we couldn't load the results return results - # The first token is stored in the hostname - consumer_key = NotifyTwitter.unquote(results['host']) - # Acquire remaining tokens tokens = NotifyTwitter.split_path(results['fullpath']) - # Now fetch the remaining tokens - try: - consumer_secret, access_token_key, access_token_secret = \ - tokens[0:3] + # The consumer token is stored in the hostname + results['ckey'] = NotifyTwitter.unquote(results['host']) - except (ValueError, AttributeError, IndexError): - # Force some bad values that will get caught - # in parsing later - consumer_secret = None - access_token_key = None - access_token_secret = None + # + # Now fetch the remaining tokens + # - results['ckey'] = consumer_key - results['csecret'] = consumer_secret - results['akey'] = access_token_key - results['asecret'] = access_token_secret + # Consumer Secret + results['csecret'] = tokens.pop(0) if tokens else None + # Access Token Key + results['akey'] = tokens.pop(0) if tokens else None + # Access Token Secret + results['asecret'] = tokens.pop(0) if tokens else None # The defined twitter mode if 'mode' in results['qsd'] and len(results['qsd']['mode']): results['mode'] = \ NotifyTwitter.unquote(results['qsd']['mode']) + elif results['schema'].startswith('tweet'): + results['mode'] = TwitterMessageMode.TWEET + results['targets'] = [] # if a user has been defined, add it to the list of targets @@ -869,7 +858,7 @@ class NotifyTwitter(NotifyBase): results['targets'].append(results.get('user')) # Store any remaining items as potential targets - results['targets'].extend(tokens[3:]) + results['targets'].extend(tokens) # Get Cache Flag (reduces lookup hits) if 'cache' in results['qsd'] and len(results['qsd']['cache']): diff --git a/libs/apprise/plugins/NotifyVoipms.py b/libs/apprise/plugins/NotifyVoipms.py new file mode 100644 index 000000000..a4ec5ae1b --- /dev/null +++ b/libs/apprise/plugins/NotifyVoipms.py @@ -0,0 +1,379 @@ +# -*- coding: utf-8 -*- +# BSD 3-Clause License +# +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> +# +# 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. + +# Create an account https://voip.ms/ if you don't already have one +# +# Enable API and set an API password here: +# - https://voip.ms/m/api.php +# +# Read more about VoIP.ms API here: +# - https://voip.ms/m/apidocs.php + +import requests +from json import loads + +from .NotifyBase import NotifyBase +from ..common import NotifyType +from ..utils import is_phone_no +from ..utils import is_email +from ..utils import parse_phone_no +from ..AppriseLocale import gettext_lazy as _ + + +class NotifyVoipms(NotifyBase): + """ + A wrapper for Voipms Notifications + """ + + # The default descriptive name associated with the Notification + service_name = 'VoIPms' + + # The services URL + service_url = 'https://voip.ms' + + # The default protocol + secure_protocol = 'voipms' + + # A URL that takes you to the setup/help of the specific protocol + setup_url = 'https://github.com/caronc/apprise/wiki/Notify_voipms' + + # Voipms uses the http protocol with JSON requests + notify_url = 'https://voip.ms/api/v1/rest.php' + + # 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}://{password}:{email}', + '{schema}://{password}:{email}/{from_phone}/{targets}', + ) + + # Define our template tokens + template_tokens = dict(NotifyBase.template_tokens, **{ + 'email': { + 'name': _('User Email'), + 'type': 'string', + 'required': True, + }, + 'password': { + 'name': _('Password'), + 'type': 'string', + 'private': True, + 'required': True, + }, + 'from_phone': { + 'name': _('From Phone No'), + 'type': 'string', + 'regex': (r'^\+?[0-9\s)(+-]+$', 'i'), + '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', + }, + }) + + # Define our template arguments + template_args = dict(NotifyBase.template_args, **{ + 'to': { + 'alias_of': 'targets', + }, + 'from': { + 'alias_of': 'from_phone', + }, + }) + + def __init__(self, email, source=None, targets=None, **kwargs): + """ + Initialize Voipms Object + """ + super().__init__(**kwargs) + + # Validate our params here. + + if self.password is None: + msg = 'Password has to be specified.' + self.logger.warning(msg) + raise TypeError(msg) + + # User is the email associated with the account + result = is_email(email) + if not result: + msg = 'An invalid Voipms user email: ' \ + '({}) was specified.'.format(email) + self.logger.warning(msg) + raise TypeError(msg) + self.email = result['full_email'] + + # Validate our source Phone # + result = is_phone_no(source) + if not result: + msg = 'An invalid Voipms source phone # ' \ + '({}) was specified.'.format(source) + self.logger.warning(msg) + raise TypeError(msg) + + # Source Phone # only supports +1 country code + # Allow 7 digit phones (presume they're local with +1 country code) + if result['country'] and result['country'] != '1': + msg = 'Voipms only supports +1 country code ' \ + '({}) was specified.'.format(source) + self.logger.warning(msg) + raise TypeError(msg) + + # Store our source phone number (without country code) + self.source = result['area'] + result['line'] + + # Parse our targets + self.targets = list() + + if targets: + for target in parse_phone_no(targets): + # Validate targets and drop bad ones: + result = is_phone_no(target) + + # Target Phone # only supports +1 country code + if result['country'] != '1': + self.logger.warning( + 'Dropped invalid phone # ' + '({}) specified.'.format(target), + ) + continue + + # store valid phone number + self.targets.append(result['area'] + result['line']) + + else: + # Send a message to ourselves + self.targets.append(self.source) + + return + + def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs): + """ + Perform Voipms Notification + """ + + if len(self.targets) == 0: + # There were no services to notify + self.logger.warning('There were no Voipms targets to notify.') + return False + + # error tracking (used for function return) + has_error = False + + # Prepare our headers + headers = { + 'User-Agent': self.app_id, + 'Content-Type': 'application/x-www-form-urlencoded', + } + + # Prepare our payload + payload = { + 'api_username': self.email, + 'api_password': self.password, + 'did': self.source, + 'message': body, + 'method': 'sendSMS', + + # Gets filled in the loop below + 'dst': None + } + + # Create a copy of the targets list + targets = list(self.targets) + + while len(targets): + # Get our target to notify + target = targets.pop(0) + + # Add target Phone # + payload['dst'] = target + + # Some Debug Logging + self.logger.debug('Voipms GET URL: {} (cert_verify={})'.format( + self.notify_url, self.verify_certificate)) + self.logger.debug('Voipms Payload: {}' .format(payload)) + + # Always call throttle before any remote server i/o is made + self.throttle() + + response = {'status': 'unknown', 'message': ''} + + try: + r = requests.get( + self.notify_url, + params=payload, + headers=headers, + verify=self.verify_certificate, + timeout=self.request_timeout, + ) + + try: + response = loads(r.content) + + except (AttributeError, TypeError, ValueError): + # ValueError = r.content is Unparsable + # TypeError = r.content is None + # AttributeError = r is None + pass + + if r.status_code != requests.codes.ok: + # We had a problem + status_str = \ + NotifyVoipms.http_response_code_lookup( + r.status_code) + + self.logger.warning( + 'Failed to send Voipms notification to {}: ' + '{}{}error={}.'.format( + target, + status_str, + ', ' if status_str else '', + r.status_code)) + + self.logger.debug( + 'Response Details:\r\n{}'.format(r.content)) + + # Mark our failure + has_error = True + continue + + # Voipms sends 200 OK even if there is an error + # check if status in response and if it is not success + + if response is not None and response['status'] != 'success': + self.logger.warning( + 'Failed to send Voipms notification to {}: ' + 'status: {}, message: {}'.format( + target, response['status'], response['message']) + ) + + # Mark our failure + has_error = True + continue + else: + self.logger.info( + 'Sent Voipms notification to %s' % target) + + except requests.RequestException as e: + self.logger.warning( + 'A Connection error occurred sending Voipms:%s ' + 'notification.' % target + ) + self.logger.debug('Socket Exception: %s' % str(e)) + + # Mark our failure + has_error = True + continue + + return not has_error + + def url(self, privacy=False, *args, **kwargs): + """ + Returns the URL built dynamically based on specified arguments. + """ + + # Define any URL parameters + params = self.url_parameters(privacy=privacy, *args, **kwargs) + + schemaStr = \ + '{schema}://{password}:{email}/{from_phone}/{targets}/?{params}' + return schemaStr.format( + schema=self.secure_protocol, + email=self.email, + password=self.pprint(self.password, privacy, safe=''), + from_phone='1' + self.pprint(self.source, privacy, safe=''), + targets='/'.join( + ['1' + NotifyVoipms.quote(x, safe='') for x in self.targets]), + params=NotifyVoipms.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 + 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['targets'] = \ + NotifyVoipms.split_path(results['fullpath']) + + if 'from' in results['qsd'] and len(results['qsd']['from']): + results['source'] = \ + NotifyVoipms.unquote(results['qsd']['from']) + + elif results['targets']: + # The from phone no is the first entry in the list otherwise + results['source'] = results['targets'].pop(0) + + # Swap user for pass since our input is: password:email + # where email is user@hostname (or user@domain) + user = results['password'] + password = results['user'] + results['password'] = password + results['user'] = user + + results['email'] = '{}@{}'.format( + NotifyVoipms.unquote(user), + NotifyVoipms.unquote(results['host']), + ) + + if 'to' in results['qsd'] and len(results['qsd']['to']): + results['targets'] += \ + NotifyVoipms.parse_phone_no(results['qsd']['to']) + + return results diff --git a/libs/apprise/plugins/NotifyVonage.py b/libs/apprise/plugins/NotifyVonage.py index 1dea3157d..bc3ab0647 100644 --- a/libs/apprise/plugins/NotifyVonage.py +++ b/libs/apprise/plugins/NotifyVonage.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # Sign-up with https://dashboard.nexmo.com/ # @@ -142,7 +149,7 @@ class NotifyVonage(NotifyBase): """ Initialize Vonage Object """ - super(NotifyVonage, self).__init__(**kwargs) + super().__init__(**kwargs) # API Key (associated with project) self.apikey = validate_regex( @@ -327,6 +334,13 @@ class NotifyVonage(NotifyBase): [NotifyVonage.quote(x, safe='') for x in self.targets]), params=NotifyVonage.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 def parse_url(url): """ diff --git a/libs/apprise/plugins/NotifyWebexTeams.py b/libs/apprise/plugins/NotifyWebexTeams.py index 5e8021330..6b953b711 100644 --- a/libs/apprise/plugins/NotifyWebexTeams.py +++ b/libs/apprise/plugins/NotifyWebexTeams.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # At the time I created this plugin, their website had lots of issues with the # Firefox Browser. I fell back to Chrome and had no problems. @@ -88,7 +95,7 @@ class NotifyWebexTeams(NotifyBase): service_url = 'https://webex.teams.com/' # The default secure protocol - secure_protocol = 'wxteams' + secure_protocol = ('wxteams', 'webex') # A URL that takes you to the setup/help of the specific protocol setup_url = 'https://github.com/caronc/apprise/wiki/Notify_wxteams' @@ -117,7 +124,7 @@ class NotifyWebexTeams(NotifyBase): 'type': 'string', 'private': True, 'required': True, - 'regex': (r'^[a-z0-9]{80}$', 'i'), + 'regex': (r'^[a-z0-9]{80,160}$', 'i'), }, }) @@ -125,7 +132,7 @@ class NotifyWebexTeams(NotifyBase): """ Initialize Webex Teams Object """ - super(NotifyWebexTeams, self).__init__(**kwargs) + super().__init__(**kwargs) # The token associated with the account self.token = validate_regex( @@ -213,7 +220,7 @@ class NotifyWebexTeams(NotifyBase): params = self.url_parameters(privacy=privacy, *args, **kwargs) return '{schema}://{token}/?{params}'.format( - schema=self.secure_protocol, + schema=self.secure_protocol[0], token=self.pprint(self.token, privacy, safe=''), params=NotifyWebexTeams.urlencode(params), ) @@ -242,14 +249,15 @@ class NotifyWebexTeams(NotifyBase): """ result = re.match( - r'^https?://api\.ciscospark\.com/v[1-9][0-9]*/webhooks/incoming/' + r'^https?://(api\.ciscospark\.com|webexapis\.com)' + r'/v[1-9][0-9]*/webhooks/incoming/' r'(?P<webhook_token>[A-Z0-9_-]+)/?' r'(?P<params>\?.+)?$', url, re.I) if result: return NotifyWebexTeams.parse_url( '{schema}://{webhook_token}/{params}'.format( - schema=NotifyWebexTeams.secure_protocol, + schema=NotifyWebexTeams.secure_protocol[0], webhook_token=result.group('webhook_token'), params='' if not result.group('params') else result.group('params'))) diff --git a/libs/apprise/plugins/NotifyWindows.py b/libs/apprise/plugins/NotifyWindows.py index 1c599e14b..70f438894 100644 --- a/libs/apprise/plugins/NotifyWindows.py +++ b/libs/apprise/plugins/NotifyWindows.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. from __future__ import absolute_import from __future__ import print_function @@ -113,7 +120,7 @@ class NotifyWindows(NotifyBase): Initialize Windows Object """ - super(NotifyWindows, self).__init__(**kwargs) + super().__init__(**kwargs) # Number of seconds to display notification for self.duration = self.default_popup_duration_sec \ @@ -135,7 +142,7 @@ class NotifyWindows(NotifyBase): win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, nid) win32api.PostQuitMessage(0) - return None + return 0 def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs): """ diff --git a/libs/apprise/plugins/NotifyXBMC.py b/libs/apprise/plugins/NotifyXBMC.py index 22f4219c0..963a74d88 100644 --- a/libs/apprise/plugins/NotifyXBMC.py +++ b/libs/apprise/plugins/NotifyXBMC.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import requests from json import dumps @@ -131,7 +138,7 @@ class NotifyXBMC(NotifyBase): """ Initialize XBMC/KODI Object """ - super(NotifyXBMC, self).__init__(**kwargs) + super().__init__(**kwargs) # Number of seconds to display notification for self.duration = self.template_args['duration']['default'] \ diff --git a/libs/apprise/plugins/NotifyXML.py b/libs/apprise/plugins/NotifyXML.py index 9fea49da1..04cdac10e 100644 --- a/libs/apprise/plugins/NotifyXML.py +++ b/libs/apprise/plugins/NotifyXML.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import re import requests @@ -34,13 +41,24 @@ from ..common import NotifyType from ..AppriseLocale import gettext_lazy as _ +class XMLPayloadField: + """ + Identifies the fields available in the JSON Payload + """ + VERSION = 'Version' + TITLE = 'Subject' + MESSAGE = 'Message' + MESSAGETYPE = 'MessageType' + + # Defines the method to send the notification METHODS = ( 'POST', 'GET', 'DELETE', 'PUT', - 'HEAD' + 'HEAD', + 'PATCH' ) @@ -70,7 +88,8 @@ class NotifyXML(NotifyBase): # XSD Information xsd_ver = '1.1' - xsd_url = 'https://raw.githubusercontent.com/caronc/apprise/master' \ + xsd_default_url = \ + 'https://raw.githubusercontent.com/caronc/apprise/master' \ '/apprise/assets/NotifyXML-{version}.xsd' # Define object templates @@ -130,9 +149,14 @@ class NotifyXML(NotifyBase): 'name': _('Payload Extras'), 'prefix': ':', }, + 'params': { + 'name': _('GET Params'), + 'prefix': '-', + }, } - def __init__(self, headers=None, method=None, payload=None, **kwargs): + def __init__(self, headers=None, method=None, payload=None, params=None, + **kwargs): """ Initialize XML Object @@ -140,7 +164,7 @@ class NotifyXML(NotifyBase): additionally include as part of the server headers to post with """ - super(NotifyXML, self).__init__(**kwargs) + super().__init__(**kwargs) self.payload = """<?xml version='1.0' encoding='utf-8'?> <soapenv:Envelope @@ -148,7 +172,7 @@ class NotifyXML(NotifyBase): xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <soapenv:Body> - <Notification xmlns:xsi="{{XSD_URL}}"> + <Notification{{XSD_URL}}> {{CORE}} {{ATTACHMENTS}} </Notification> @@ -167,11 +191,32 @@ class NotifyXML(NotifyBase): self.logger.warning(msg) raise TypeError(msg) + # 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 = { + XMLPayloadField.VERSION: XMLPayloadField.VERSION, + XMLPayloadField.TITLE: XMLPayloadField.TITLE, + XMLPayloadField.MESSAGE: XMLPayloadField.MESSAGE, + XMLPayloadField.MESSAGETYPE: XMLPayloadField.MESSAGETYPE, + } + + self.params = {} + if params: + # Store our extra headers + self.params.update(params) + self.headers = {} if headers: # Store our extra headers self.headers.update(headers) + # Set our xsd url + self.xsd_url = self.xsd_default_url.format(version=self.xsd_ver) + + self.payload_overrides = {} self.payload_extras = {} if payload: # Store our extra payload entries (but tidy them up since they will @@ -183,8 +228,20 @@ class NotifyXML(NotifyBase): 'Ignoring invalid XML Stanza element name({})' .format(k)) continue - self.payload_extras[key] = v + # 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] = v + self.payload_overrides[key] = v + + # Over-ride XSD URL as data is no longer known + self.xsd_url = None + + else: + self.payload_extras[key] = v return def url(self, privacy=False, *args, **kwargs): @@ -203,9 +260,14 @@ class NotifyXML(NotifyBase): # Append our headers into our parameters params.update({'+{}'.format(k): v for k, v in self.headers.items()}) + # Append our GET params into our parameters + params.update({'-{}'.format(k): v for k, v in self.params.items()}) + # Append our payload extra's into our parameters params.update( {':{}'.format(k): v for k, v in self.payload_extras.items()}) + params.update( + {':{}'.format(k): v for k, v in self.payload_overrides.items()}) # Determine Authentication auth = '' @@ -240,7 +302,7 @@ class NotifyXML(NotifyBase): Perform XML Notification """ - # prepare XML Object + # Prepare HTTP Headers headers = { 'User-Agent': self.app_id, 'Content-Type': 'application/xml' @@ -252,14 +314,21 @@ class NotifyXML(NotifyBase): # Our XML Attachmement subsitution xml_attachments = '' - # Our Payload Base - payload_base = { - 'Version': self.xsd_ver, - 'Subject': NotifyXML.escape_html(title, whitespace=False), - 'MessageType': NotifyXML.escape_html( - notify_type, whitespace=False), - 'Message': NotifyXML.escape_html(body, whitespace=False), - } + payload_base = {} + + for key, value in ( + (XMLPayloadField.VERSION, self.xsd_ver), + (XMLPayloadField.TITLE, NotifyXML.escape_html( + title, whitespace=False)), + (XMLPayloadField.MESSAGE, NotifyXML.escape_html( + body, whitespace=False)), + (XMLPayloadField.MESSAGETYPE, NotifyXML.escape_html( + notify_type, whitespace=False))): + + if not self.payload_map[key]: + # Do not store element in payload response + continue + payload_base[self.payload_map[key]] = value # Apply our payload extras payload_base.update( @@ -307,7 +376,8 @@ class NotifyXML(NotifyBase): ''.join(attachments) + '</Attachments>' re_map = { - '{{XSD_URL}}': self.xsd_url.format(version=self.xsd_ver), + '{{XSD_URL}}': + f' xmlns:xsi="{self.xsd_url}"' if self.xsd_url else '', '{{ATTACHMENTS}}': xml_attachments, '{{CORE}}': xml_base, } @@ -347,6 +417,9 @@ class NotifyXML(NotifyBase): elif self.method == 'PUT': method = requests.put + elif self.method == 'PATCH': + method = requests.patch + elif self.method == 'DELETE': method = requests.delete @@ -417,6 +490,10 @@ class NotifyXML(NotifyBase): results['headers'] = {NotifyXML.unquote(x): NotifyXML.unquote(y) for x, y in results['qsd+'].items()} + # Add our GET paramters in the event the user wants to pass these along + results['params'] = {NotifyXML.unquote(x): NotifyXML.unquote(y) + for x, y in results['qsd-'].items()} + # Set method if not otherwise set if 'method' in results['qsd'] and len(results['qsd']['method']): results['method'] = NotifyXML.unquote(results['qsd']['method']) diff --git a/libs/apprise/plugins/NotifyZulip.py b/libs/apprise/plugins/NotifyZulip.py index 662771379..f9521ae19 100644 --- a/libs/apprise/plugins/NotifyZulip.py +++ b/libs/apprise/plugins/NotifyZulip.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. # To use this plugin, you must have a ZulipChat bot defined; See here: # https://zulipchat.com/help/add-a-bot-or-integration @@ -172,7 +179,7 @@ class NotifyZulip(NotifyBase): """ Initialize Zulip Object """ - super(NotifyZulip, self).__init__(**kwargs) + super().__init__(**kwargs) # our default hostname self.hostname = self.default_hostname @@ -352,6 +359,12 @@ class NotifyZulip(NotifyBase): params=NotifyZulip.urlencode(params), ) + def __len__(self): + """ + Returns the number of targets associated with this notification + """ + return len(self.targets) + @staticmethod def parse_url(url): """ diff --git a/libs/apprise/plugins/__init__.py b/libs/apprise/plugins/__init__.py index 9abc3e061..5560568b7 100644 --- a/libs/apprise/plugins/__init__.py +++ b/libs/apprise/plugins/__init__.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import os import re @@ -31,10 +38,7 @@ from os.path import dirname from os.path import abspath # Used for testing -from . import NotifyEmail as NotifyEmailBase - -# NotifyBase object is passed in as a module not class -from . import NotifyBase +from .NotifyBase import NotifyBase from ..common import NotifyImageSize from ..common import NOTIFY_IMAGE_SIZES @@ -53,9 +57,6 @@ __all__ = [ 'NotifyImageSize', 'NOTIFY_IMAGE_SIZES', 'NotifyType', 'NOTIFY_TYPES', 'NotifyBase', - # NotifyEmail Base Module (used for NotifyEmail testing) - 'NotifyEmailBase', - # Tokenizer 'url_to_dict', ] @@ -115,9 +116,6 @@ def __load_matrix(path=abspath(dirname(__file__)), name='apprise.plugins'): # Add our module name to our __all__ __all__.append(plugin_name) - # Load our module into memory so it's accessible to all - globals()[plugin_name] = plugin - fn = getattr(plugin, 'schemas', None) schemas = set([]) if not callable(fn) else fn(plugin) @@ -147,8 +145,6 @@ def __reset_matrix(): # Iterate over our module map so we can clear out our __all__ and globals for plugin_name in common.NOTIFY_MODULE_MAP.keys(): - # Clear out globals - del globals()[plugin_name] # Remove element from plugins __all__.remove(plugin_name) diff --git a/libs/apprise/py3compat/__init__.py b/libs/apprise/py3compat/__init__.py deleted file mode 100644 index e69de29bb..000000000 --- a/libs/apprise/py3compat/__init__.py +++ /dev/null diff --git a/libs/apprise/py3compat/asyncio.py b/libs/apprise/py3compat/asyncio.py deleted file mode 100644 index a53139062..000000000 --- a/libs/apprise/py3compat/asyncio.py +++ /dev/null @@ -1,140 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2020 Chris Caron <[email protected]> -# All rights reserved. -# -# This code is licensed under the MIT License. -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. - -import sys -import asyncio -from functools import partial -from ..URLBase import URLBase -from ..logger import logger - - -# A global flag that tracks if we are Python v3.7 or higher -ASYNCIO_RUN_SUPPORT = \ - sys.version_info.major > 3 or \ - (sys.version_info.major == 3 and sys.version_info.minor >= 7) - - -async def notify(coroutines): - """ - An async wrapper to the AsyncNotifyBase.async_notify() calls allowing us - to call gather() and collect the responses - """ - - # Create log entry - logger.info( - 'Notifying {} service(s) asynchronously.'.format(len(coroutines))) - - results = await asyncio.gather(*coroutines, return_exceptions=True) - - # Returns True if all notifications succeeded, otherwise False is - # returned. - failed = any(not status or isinstance(status, Exception) - for status in results) - return not failed - - -def tosync(cor, debug=False): - """ - Await a coroutine from non-async code. - """ - - if ASYNCIO_RUN_SUPPORT: - try: - loop = asyncio.get_running_loop() - - except RuntimeError: - # There is no existing event loop, so we can start our own. - return asyncio.run(cor, debug=debug) - - else: - # Enable debug mode - loop.set_debug(debug) - - # Run the coroutine and wait for the result. - task = loop.create_task(cor) - return asyncio.ensure_future(task, loop=loop) - - else: - # The Deprecated Way (<= Python v3.6) - try: - # acquire access to our event loop - loop = asyncio.get_event_loop() - - except RuntimeError: - # This happens if we're inside a thread of another application - # where there is no running event_loop(). Pythong v3.7 and - # higher automatically take care of this case for us. But for - # the lower versions we need to do the following: - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - - # Enable debug mode - loop.set_debug(debug) - - return loop.run_until_complete(cor) - - -async def toasyncwrapvalue(v): - """ - Create a coroutine that, when run, returns the provided value. - """ - - return v - - -async def toasyncwrap(fn): - """ - Create a coroutine that, when run, executes the provided function. - """ - - return fn() - - -class AsyncNotifyBase(URLBase): - """ - asyncio wrapper for the NotifyBase object - """ - - async def async_notify(self, *args, **kwargs): - """ - Async Notification Wrapper - """ - - loop = asyncio.get_event_loop() - - try: - return await loop.run_in_executor( - None, partial(self.notify, *args, **kwargs)) - - except TypeError: - # These are our internally thrown notifications - pass - - 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("Notification Exception") - - return False diff --git a/libs/apprise/utils.py b/libs/apprise/utils.py index 334711de9..561a5a232 100644 --- a/libs/apprise/utils.py +++ b/libs/apprise/utils.py @@ -1,27 +1,34 @@ # -*- coding: utf-8 -*- +# BSD 3-Clause License # -# Copyright (C) 2019 Chris Caron <[email protected]> -# All rights reserved. +# Apprise - Push Notification Library. +# Copyright (c) 2023, Chris Caron <[email protected]> # -# This code is licensed under the MIT License. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: # -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files(the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and / or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions : +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. # -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. +# 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. # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. +# 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. import re import sys @@ -47,10 +54,6 @@ def import_module(path, name): """ Load our module based on path """ - # if path.endswith('test_module_detection0/a/hook.py'): - # import pdb - # pdb.set_trace() - spec = importlib.util.spec_from_file_location(name, path) try: module = importlib.util.module_from_spec(spec) @@ -60,7 +63,13 @@ def import_module(path, name): except Exception as e: # module isn't loadable - del sys.modules[name] + try: + del sys.modules[name] + + except KeyError: + # nothing to clean up + pass + module = None logger.debug( @@ -153,10 +162,10 @@ URL_DETAILS_RE = re.compile( GET_EMAIL_RE = re.compile( - r'(([\s"\']+)?(?P<name>[^:<"\']+)?[:<\s"\']+)?' + r'(([\s"\']+)?(?P<name>[^:<\'"]+)?[:<\s\'"]+)?' r'(?P<full_email>((?P<label>[^+]+)\+)?' - r'(?P<email>(?P<userid>[a-z0-9$%=_~-]+' - r'(?:\.[a-z0-9$%+=_~-]+)' + r'(?P<email>(?P<userid>[a-z0-9_!#$%&*/=?%`{|}~^-]+' + r'(?:\.[a-z0-9_!#$%&\'*/=?%`{|}~^-]+)' r'*)@(?P<domain>(' r'(?:[a-z0-9](?:[a-z0-9_-]*[a-z0-9])?\.)+' r'[a-z0-9](?:[a-z0-9_-]*[a-z0-9]))|' @@ -188,7 +197,7 @@ URL_DETECTION_RE = re.compile( EMAIL_DETECTION_RE = re.compile( r'[\s,]*([^@]+@.*?)(?=$|[\s,]+' - + r'(?:[^:<]+?[:<\s]+?)?' + r'(?:[^:<]+?[:<\s]+?)?' r'[^@\s,]+@[^\s,]+)', re.IGNORECASE) @@ -197,6 +206,9 @@ UUID4_RE = re.compile( r'[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}', re.IGNORECASE) +# Validate if we're a loadable Python file or not +VALID_PYTHON_FILE_RE = re.compile(r'.+\.py(o|c)?$', re.IGNORECASE) + # validate_regex() utilizes this mapping to track and re-use pre-complied # regular expressions REGEX_VALIDATE_LOOKUP = {} @@ -519,7 +531,7 @@ def tidy_path(path): return path -def parse_qsd(qs, simple=False): +def parse_qsd(qs, simple=False, plus_to_space=False): """ Query String Dictionary Builder @@ -541,6 +553,11 @@ def parse_qsd(qs, simple=False): if simple is set to true, then a ONE dictionary is returned and is not sub-parsed for additional elements + + plus_to_space will cause all `+` references to become a space as + per normal URL Encoded defininition. Normal URL parsing applies + this, but `+` is very actively used character with passwords, + api keys, tokens, etc. So Apprise does not do this by default. """ # Our return result set: @@ -575,7 +592,7 @@ def parse_qsd(qs, simple=False): key = unquote(key) key = '' if not key else key - val = nv[1].replace('+', ' ') + val = nv[1].replace('+', ' ') if plus_to_space else nv[1] val = unquote(val) val = '' if not val else val.strip() @@ -609,7 +626,7 @@ def parse_qsd(qs, simple=False): def parse_url(url, default_schema='http', verify_host=True, strict_port=False, - simple=False): + simple=False, plus_to_space=False): """A function that greatly simplifies the parsing of a url specified by the end user. @@ -722,7 +739,8 @@ def parse_url(url, default_schema='http', verify_host=True, strict_port=False, # Parse Query Arugments ?val=key&key=val # while ensuring that all keys are lowercase if qsdata: - result.update(parse_qsd(qsdata, simple=simple)) + result.update(parse_qsd( + qsdata, simple=simple, plus_to_space=plus_to_space)) # Now do a proper extraction of data; http:// is just substitued in place # to allow urlparse() to function as expected, we'll swap this back to the @@ -1556,6 +1574,11 @@ def module_detection(paths, cache=True): # Since our plugin name can conflict (as a module) with another # we want to generate random strings to avoid steping on # another's namespace + if not (path and VALID_PYTHON_FILE_RE.match(path)): + # Ignore file/module type + logger.trace('Plugin Scan: Skipping %s', path) + return None + module_name = hashlib.sha1(path.encode('utf-8')).hexdigest() module_pyname = "{prefix}.{name}".format( prefix='apprise.custom.module', name=module_name) diff --git a/libs/version.txt b/libs/version.txt index ad74cae19..878cf53d2 100644 --- a/libs/version.txt +++ b/libs/version.txt @@ -1,7 +1,7 @@ # Bazarr dependencies aniso8601==9.0.1 argparse==1.4.0 -apprise==1.1.0 +apprise==1.4.0 apscheduler==3.9.1 attrs==22.1.0 charamel==1.0.0 |