mirror of
https://github.com/caronc/apprise.git
synced 2024-12-03 13:33:36 +01:00
Persistent Storage (#1131)
This commit is contained in:
parent
5cee11ac84
commit
827db528d0
2
.github/workflows/codeql-analysis.yml
vendored
2
.github/workflows/codeql-analysis.yml
vendored
@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
2
.github/workflows/pkgbuild.yml
vendored
2
.github/workflows/pkgbuild.yml
vendored
@ -49,7 +49,7 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Acquire sources
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build package
|
||||
run: |
|
||||
|
4
.github/workflows/tests.yml
vendored
4
.github/workflows/tests.yml
vendored
@ -75,7 +75,7 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Acquire sources
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install prerequisites (Linux)
|
||||
if: runner.os == 'Linux'
|
||||
@ -137,7 +137,7 @@ jobs:
|
||||
coverage report
|
||||
|
||||
- name: Upload coverage data
|
||||
uses: codecov/codecov-action@v3
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
files: ./coverage.xml
|
||||
fail_ci_if_error: false
|
||||
|
115
README.md
115
README.md
@ -534,9 +534,121 @@ aobj.add('foobar://')
|
||||
# Send our notification out through our foobar://
|
||||
aobj.notify("test")
|
||||
```
|
||||
|
||||
You can read more about creating your own custom notifications and/or hooks [here](https://github.com/caronc/apprise/wiki/decorator_notify).
|
||||
|
||||
# Persistent Storage
|
||||
|
||||
Persistent storage allows Apprise to cache re-occurring actions optionaly to disk. This can greatly reduce the overhead used to send a notification.
|
||||
|
||||
There are 3 options Apprise can operate using this:
|
||||
1. `AUTO`: Flush any gathered data for persistent storage on demand. This option is incredibly light weight. This is the default behavior for all CLI usage. Content can be manually flushed to disk using this option as well should a developer choose to do so. The CLI uses this option by default and only writes anything accumulated to disk after all of it's notifications have completed.
|
||||
1. `FLUSH`: Flushes any gathered data for persistent storage as often as it is acquired.
|
||||
1. `MEMORY`: Only store information in memory, never write to disk. This is the option one would set if they simply wish to disable Persistent Storage entirely. By default this is the mode used by the API and is at the developers discretion to enable one of the other options.
|
||||
|
||||
## CLI Persistent Storage Commands
|
||||
Persistent storage is set to `AUTO` mode by default.
|
||||
|
||||
Specifying the keyword `storage` will assume that all subseqent calls are related to the storage subsection of Apprise.
|
||||
```bash
|
||||
# List all of the occupied space used by Apprise's Persistent Storage:
|
||||
apprise storage list
|
||||
|
||||
# list is the default option, so the following does the same thing:
|
||||
apprise storage
|
||||
|
||||
# You can prune all of your storage older then 30 days
|
||||
# and not accessed for this period like so:
|
||||
apprise storage prune
|
||||
|
||||
# You can do a hard reset (and wipe all persistent storage) with:
|
||||
apprise storage clean
|
||||
|
||||
```
|
||||
|
||||
You can also filter your results by adding tags and/or URL Identifiers. When you get a listing (`apprise storage list`), you may see:
|
||||
```
|
||||
# example output of 'apprise storage list':
|
||||
1. f7077a65 0.00B unused
|
||||
- matrixs://abcdef:****@synapse.example12.com/%23general?image=no&mode=off&version=3&msgtype...
|
||||
tags: team
|
||||
|
||||
2. 0e873a46 81.10B active
|
||||
- tgram://W...U//?image=False&detect=yes&silent=no&preview=no&content=before&mdv=v1&format=m...
|
||||
tags: personal
|
||||
|
||||
3. abcd123 12.00B stale
|
||||
|
||||
```
|
||||
The states are:
|
||||
- `unused`: This plugin has not commited anything to disk for reuse/cache purposes
|
||||
- `active`: This plugin has written content to disk. Or at the very least, it has prepared a persistent storage location it can write into.
|
||||
- `stale`: The system detected a location where a URL may have possibly written to in the past, but there is nothing linking to it using the URLs provided. It is likely wasting space or is no longer of any use.
|
||||
|
||||
You can use this information to filter your results by specifying _URL ID_ values after your command. For example:
|
||||
```bash
|
||||
# The below commands continue with the example already identified above
|
||||
# the following would match abcd123 (even though just ab was provided)
|
||||
# The output would only list the 'stale' entry above
|
||||
apprise storage list ab
|
||||
|
||||
# knowing our filter is safe, we could remove it
|
||||
# the below command would not obstruct our other to URLs and would only
|
||||
# remove our stale one:
|
||||
apprise storage clean ab
|
||||
|
||||
# Entries can be filtered by tag as well:
|
||||
apprise storage list --tag=team
|
||||
|
||||
# You can match on multiple URL ID's as well:
|
||||
# The followin would actually match the URL ID's of 1. and .2 above
|
||||
apprise storage list f 0
|
||||
```
|
||||
|
||||
For more information on persistent storage, [visit here](https://github.com/caronc/apprise/wiki/persistent_storage).
|
||||
|
||||
|
||||
## API Persistent Storage Commands
|
||||
By default, no persistent storage is set to be in `MEMORY` mode for those building from within the Apprise API.
|
||||
It's at the developers discretion to enable it. But should you choose to do so, it's as easy as including the information in the `AppriseAsset()` object prior to the initialization of your `Apprise()` instance.
|
||||
|
||||
For example:
|
||||
```python
|
||||
from apprise import Apprise
|
||||
from apprise import AppriseAsset
|
||||
from apprise import PersistentStoreMode
|
||||
|
||||
# Prepare a location the persistent storage can write to
|
||||
# This immediately assumes you wish to write in AUTO mode
|
||||
asset = AppriseAsset(storage_path="/path/to/save/data")
|
||||
|
||||
# If you want to be more explicit and set more options, then
|
||||
# you may do the following
|
||||
asset = AppriseAsset(
|
||||
# Set our storage path directory (minimum requirement to enable it)
|
||||
storage_path="/path/to/save/data",
|
||||
|
||||
# Set the mode... the options are:
|
||||
# 1. PersistentStoreMode.MEMORY
|
||||
# - disable persistent storage from writing to disk
|
||||
# 2. PersistentStoreMode.AUTO
|
||||
# - write to disk on demand
|
||||
# 3. PersistentStoreMode.FLUSH
|
||||
# - write to disk always and often
|
||||
storage_mode=PersistentStoreMode.FLUSH
|
||||
|
||||
# the URL IDs are by default 8 characters in length, there is
|
||||
# really no reason to change this. You can increase/decrease
|
||||
# it's value here. Must be > 2; default is 8 if not specified
|
||||
storage_idlen=6,
|
||||
)
|
||||
|
||||
# Now that we've got our asset, we just work with our Apprise object as we
|
||||
# normally do
|
||||
aobj = Apprise(asset=asset)
|
||||
```
|
||||
|
||||
For more information on persistent storage, [visit here](https://github.com/caronc/apprise/wiki/persistent_storage).
|
||||
|
||||
# Want To Learn More?
|
||||
|
||||
If you're interested in reading more about this and other methods on how to customize your own notifications, please check out the following links:
|
||||
@ -545,6 +657,7 @@ If you're interested in reading more about this and other methods on how to cust
|
||||
* 🔧 [Troubleshooting](https://github.com/caronc/apprise/wiki/Troubleshooting)
|
||||
* ⚙️ [Configuration File Help](https://github.com/caronc/apprise/wiki/config)
|
||||
* ⚡ [Create Your Own Custom Notifications](https://github.com/caronc/apprise/wiki/decorator_notify)
|
||||
* 💾 [Persistent Storage](https://github.com/caronc/apprise/wiki/persistent_storage)
|
||||
* 🌎 [Apprise API/Web Interface](https://github.com/caronc/apprise-api)
|
||||
* 🎉 [Showcase](https://github.com/caronc/apprise/wiki/showcase)
|
||||
|
||||
|
@ -48,16 +48,20 @@ from .common import ContentIncludeMode
|
||||
from .common import CONTENT_INCLUDE_MODES
|
||||
from .common import ContentLocation
|
||||
from .common import CONTENT_LOCATIONS
|
||||
from .common import PersistentStoreMode
|
||||
from .common import PERSISTENT_STORE_MODES
|
||||
|
||||
from .url import URLBase
|
||||
from .url import PrivacyMode
|
||||
from .plugins.base import NotifyBase
|
||||
from .config.base import ConfigBase
|
||||
from .attachment.base import AttachBase
|
||||
from . import exception
|
||||
|
||||
from .apprise import Apprise
|
||||
from .locale import AppriseLocale
|
||||
from .asset import AppriseAsset
|
||||
from .persistent_store import PersistentStore
|
||||
from .apprise_config import AppriseConfig
|
||||
from .apprise_attachment import AppriseAttachment
|
||||
from .manager_attachment import AttachmentManager
|
||||
@ -77,6 +81,10 @@ __all__ = [
|
||||
# Core
|
||||
'Apprise', 'AppriseAsset', 'AppriseConfig', 'AppriseAttachment', 'URLBase',
|
||||
'NotifyBase', 'ConfigBase', 'AttachBase', 'AppriseLocale',
|
||||
'PersistentStore',
|
||||
|
||||
# Exceptions
|
||||
'exception',
|
||||
|
||||
# Reference
|
||||
'NotifyType', 'NotifyImageSize', 'NotifyFormat', 'OverflowMode',
|
||||
@ -84,6 +92,7 @@ __all__ = [
|
||||
'ConfigFormat', 'CONFIG_FORMATS',
|
||||
'ContentIncludeMode', 'CONTENT_INCLUDE_MODES',
|
||||
'ContentLocation', 'CONTENT_LOCATIONS',
|
||||
'PersistentStoreMode', 'PERSISTENT_STORE_MODES',
|
||||
'PrivacyMode',
|
||||
|
||||
# Managers
|
||||
|
@ -33,6 +33,7 @@ from os.path import dirname
|
||||
from os.path import isfile
|
||||
from os.path import abspath
|
||||
from .common import NotifyType
|
||||
from .common import PersistentStoreMode
|
||||
from .manager_plugins import NotificationManager
|
||||
|
||||
|
||||
@ -157,6 +158,22 @@ class AppriseAsset:
|
||||
# By default, no paths are scanned.
|
||||
__plugin_paths = []
|
||||
|
||||
# Optionally set the location of the persistent storage
|
||||
# By default there is no path and thus persistent storage is not used
|
||||
__storage_path = None
|
||||
|
||||
# Optionally define the default salt to apply to all persistent storage
|
||||
# namespace generation (unless over-ridden)
|
||||
__storage_salt = b''
|
||||
|
||||
# Optionally define the namespace length of the directories created by
|
||||
# the storage. If this is set to zero, then the length is pre-determined
|
||||
# by the generator (sha1, md5, sha256, etc)
|
||||
__storage_idlen = 8
|
||||
|
||||
# Set storage to auto
|
||||
__storage_mode = PersistentStoreMode.AUTO
|
||||
|
||||
# All internal/system flags are prefixed with an underscore (_)
|
||||
# These can only be initialized using Python libraries and are not picked
|
||||
# up from (yaml) configuration files (if set)
|
||||
@ -171,7 +188,9 @@ class AppriseAsset:
|
||||
# A unique identifer we can use to associate our calling source
|
||||
_uid = str(uuid4())
|
||||
|
||||
def __init__(self, plugin_paths=None, **kwargs):
|
||||
def __init__(self, plugin_paths=None, storage_path=None,
|
||||
storage_mode=None, storage_salt=None,
|
||||
storage_idlen=None, **kwargs):
|
||||
"""
|
||||
Asset Initialization
|
||||
|
||||
@ -187,8 +206,49 @@ class AppriseAsset:
|
||||
|
||||
if plugin_paths:
|
||||
# Load any decorated modules if defined
|
||||
self.__plugin_paths = plugin_paths
|
||||
N_MGR.module_detection(plugin_paths)
|
||||
|
||||
if storage_path:
|
||||
# Define our persistent storage path
|
||||
self.__storage_path = storage_path
|
||||
|
||||
if storage_mode:
|
||||
# Define how our persistent storage behaves
|
||||
self.__storage_mode = storage_mode
|
||||
|
||||
if isinstance(storage_idlen, int):
|
||||
# Define the number of characters utilized from our namespace lengh
|
||||
if storage_idlen < 0:
|
||||
# Unsupported type
|
||||
raise ValueError(
|
||||
'AppriseAsset storage_idlen(): Value must '
|
||||
'be an integer and > 0')
|
||||
|
||||
# Store value
|
||||
self.__storage_idlen = storage_idlen
|
||||
|
||||
if storage_salt is not None:
|
||||
# Define the number of characters utilized from our namespace lengh
|
||||
|
||||
if isinstance(storage_salt, bytes):
|
||||
self.__storage_salt = storage_salt
|
||||
|
||||
elif isinstance(storage_salt, str):
|
||||
try:
|
||||
self.__storage_salt = storage_salt.encode(self.encoding)
|
||||
|
||||
except UnicodeEncodeError:
|
||||
# Bad data; don't pass it along
|
||||
raise ValueError(
|
||||
'AppriseAsset namespace_salt(): '
|
||||
'Value provided could not be encoded')
|
||||
|
||||
else: # Unsupported
|
||||
raise ValueError(
|
||||
'AppriseAsset namespace_salt(): Value provided must be '
|
||||
'string or bytes object')
|
||||
|
||||
def color(self, notify_type, color_type=None):
|
||||
"""
|
||||
Returns an HTML mapped color based on passed in notify type
|
||||
@ -356,3 +416,40 @@ class AppriseAsset:
|
||||
|
||||
"""
|
||||
return int(value.lstrip('#'), 16)
|
||||
|
||||
@property
|
||||
def plugin_paths(self):
|
||||
"""
|
||||
Return the plugin paths defined
|
||||
"""
|
||||
return self.__plugin_paths
|
||||
|
||||
@property
|
||||
def storage_path(self):
|
||||
"""
|
||||
Return the persistent storage path defined
|
||||
"""
|
||||
return self.__storage_path
|
||||
|
||||
@property
|
||||
def storage_mode(self):
|
||||
"""
|
||||
Return the persistent storage mode defined
|
||||
"""
|
||||
|
||||
return self.__storage_mode
|
||||
|
||||
@property
|
||||
def storage_salt(self):
|
||||
"""
|
||||
Return the provided namespace salt; this is always of type bytes
|
||||
"""
|
||||
return self.__storage_salt
|
||||
|
||||
@property
|
||||
def storage_idlen(self):
|
||||
"""
|
||||
Return the persistent storage id length
|
||||
"""
|
||||
|
||||
return self.__storage_idlen
|
||||
|
@ -29,6 +29,7 @@
|
||||
import re
|
||||
import os
|
||||
from .base import AttachBase
|
||||
from ..utils import path_decode
|
||||
from ..common import ContentLocation
|
||||
from ..locale import gettext_lazy as _
|
||||
|
||||
@ -57,7 +58,10 @@ class AttachFile(AttachBase):
|
||||
|
||||
# Store path but mark it dirty since we have not performed any
|
||||
# verification at this point.
|
||||
self.dirty_path = os.path.expanduser(path)
|
||||
self.dirty_path = path_decode(path)
|
||||
|
||||
# Track our file as it was saved
|
||||
self.__original_path = os.path.normpath(path)
|
||||
return
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
@ -77,7 +81,7 @@ class AttachFile(AttachBase):
|
||||
params['name'] = self._name
|
||||
|
||||
return 'file://{path}{params}'.format(
|
||||
path=self.quote(self.dirty_path),
|
||||
path=self.quote(self.__original_path),
|
||||
params='?{}'.format(self.urlencode(params, safe='/'))
|
||||
if params else '',
|
||||
)
|
||||
|
436
apprise/cli.py
436
apprise/cli.py
@ -27,26 +27,27 @@
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import click
|
||||
import textwrap
|
||||
import logging
|
||||
import platform
|
||||
import sys
|
||||
import os
|
||||
import shutil
|
||||
import re
|
||||
|
||||
from os.path import isfile
|
||||
from os.path import exists
|
||||
from os.path import expanduser
|
||||
from os.path import expandvars
|
||||
|
||||
from . import NotifyType
|
||||
from . import NotifyFormat
|
||||
from . import Apprise
|
||||
from . import AppriseAsset
|
||||
from . import AppriseConfig
|
||||
from . import PersistentStore
|
||||
|
||||
from .utils import parse_list
|
||||
from .utils import dir_size, bytes_to_str, parse_list, path_decode
|
||||
from .common import NOTIFY_TYPES
|
||||
from .common import NOTIFY_FORMATS
|
||||
from .common import PERSISTENT_STORE_MODES
|
||||
from .common import PersistentStoreState
|
||||
from .common import ContentLocation
|
||||
from .logger import logger
|
||||
|
||||
@ -104,67 +105,94 @@ DEFAULT_PLUGIN_PATHS = (
|
||||
'/var/lib/apprise/plugins',
|
||||
)
|
||||
|
||||
#
|
||||
# Persistent Storage
|
||||
#
|
||||
DEFAULT_STORAGE_PATH = '~/.local/share/apprise/cache'
|
||||
|
||||
# Detect Windows
|
||||
if platform.system() == 'Windows':
|
||||
# Default Config Search Path for Windows Users
|
||||
DEFAULT_CONFIG_PATHS = (
|
||||
expandvars('%APPDATA%\\Apprise\\apprise'),
|
||||
expandvars('%APPDATA%\\Apprise\\apprise.conf'),
|
||||
expandvars('%APPDATA%\\Apprise\\apprise.yml'),
|
||||
expandvars('%APPDATA%\\Apprise\\apprise.yaml'),
|
||||
expandvars('%LOCALAPPDATA%\\Apprise\\apprise'),
|
||||
expandvars('%LOCALAPPDATA%\\Apprise\\apprise.conf'),
|
||||
expandvars('%LOCALAPPDATA%\\Apprise\\apprise.yml'),
|
||||
expandvars('%LOCALAPPDATA%\\Apprise\\apprise.yaml'),
|
||||
'%APPDATA%\\Apprise\\apprise',
|
||||
'%APPDATA%\\Apprise\\apprise.conf',
|
||||
'%APPDATA%\\Apprise\\apprise.yml',
|
||||
'%APPDATA%\\Apprise\\apprise.yaml',
|
||||
'%LOCALAPPDATA%\\Apprise\\apprise',
|
||||
'%LOCALAPPDATA%\\Apprise\\apprise.conf',
|
||||
'%LOCALAPPDATA%\\Apprise\\apprise.yml',
|
||||
'%LOCALAPPDATA%\\Apprise\\apprise.yaml',
|
||||
|
||||
#
|
||||
# Global Support
|
||||
#
|
||||
|
||||
# C:\ProgramData\Apprise
|
||||
expandvars('%ALLUSERSPROFILE%\\Apprise\\apprise'),
|
||||
expandvars('%ALLUSERSPROFILE%\\Apprise\\apprise.conf'),
|
||||
expandvars('%ALLUSERSPROFILE%\\Apprise\\apprise.yml'),
|
||||
expandvars('%ALLUSERSPROFILE%\\Apprise\\apprise.yaml'),
|
||||
'%ALLUSERSPROFILE%\\Apprise\\apprise',
|
||||
'%ALLUSERSPROFILE%\\Apprise\\apprise.conf',
|
||||
'%ALLUSERSPROFILE%\\Apprise\\apprise.yml',
|
||||
'%ALLUSERSPROFILE%\\Apprise\\apprise.yaml',
|
||||
|
||||
# C:\Program Files\Apprise
|
||||
expandvars('%PROGRAMFILES%\\Apprise\\apprise'),
|
||||
expandvars('%PROGRAMFILES%\\Apprise\\apprise.conf'),
|
||||
expandvars('%PROGRAMFILES%\\Apprise\\apprise.yml'),
|
||||
expandvars('%PROGRAMFILES%\\Apprise\\apprise.yaml'),
|
||||
'%PROGRAMFILES%\\Apprise\\apprise',
|
||||
'%PROGRAMFILES%\\Apprise\\apprise.conf',
|
||||
'%PROGRAMFILES%\\Apprise\\apprise.yml',
|
||||
'%PROGRAMFILES%\\Apprise\\apprise.yaml',
|
||||
|
||||
# C:\Program Files\Common Files
|
||||
expandvars('%COMMONPROGRAMFILES%\\Apprise\\apprise'),
|
||||
expandvars('%COMMONPROGRAMFILES%\\Apprise\\apprise.conf'),
|
||||
expandvars('%COMMONPROGRAMFILES%\\Apprise\\apprise.yml'),
|
||||
expandvars('%COMMONPROGRAMFILES%\\Apprise\\apprise.yaml'),
|
||||
'%COMMONPROGRAMFILES%\\Apprise\\apprise',
|
||||
'%COMMONPROGRAMFILES%\\Apprise\\apprise.conf',
|
||||
'%COMMONPROGRAMFILES%\\Apprise\\apprise.yml',
|
||||
'%COMMONPROGRAMFILES%\\Apprise\\apprise.yaml',
|
||||
)
|
||||
|
||||
# Default Plugin Search Path for Windows Users
|
||||
DEFAULT_PLUGIN_PATHS = (
|
||||
expandvars('%APPDATA%\\Apprise\\plugins'),
|
||||
expandvars('%LOCALAPPDATA%\\Apprise\\plugins'),
|
||||
'%APPDATA%\\Apprise\\plugins',
|
||||
'%LOCALAPPDATA%\\Apprise\\plugins',
|
||||
|
||||
#
|
||||
# Global Support
|
||||
#
|
||||
|
||||
# C:\ProgramData\Apprise\plugins
|
||||
expandvars('%ALLUSERSPROFILE%\\Apprise\\plugins'),
|
||||
'%ALLUSERSPROFILE%\\Apprise\\plugins',
|
||||
# C:\Program Files\Apprise\plugins
|
||||
expandvars('%PROGRAMFILES%\\Apprise\\plugins'),
|
||||
'%PROGRAMFILES%\\Apprise\\plugins',
|
||||
# C:\Program Files\Common Files
|
||||
expandvars('%COMMONPROGRAMFILES%\\Apprise\\plugins'),
|
||||
'%COMMONPROGRAMFILES%\\Apprise\\plugins',
|
||||
)
|
||||
|
||||
#
|
||||
# Persistent Storage
|
||||
#
|
||||
DEFAULT_STORAGE_PATH = '%APPDATA%/Apprise/cache'
|
||||
|
||||
def print_help_msg(command):
|
||||
"""
|
||||
Prints help message when -h or --help is specified.
|
||||
|
||||
class PersistentStorageMode:
|
||||
"""
|
||||
with click.Context(command) as ctx:
|
||||
click.echo(command.get_help(ctx))
|
||||
Persistent Storage Modes
|
||||
"""
|
||||
# List all detected configuration loaded
|
||||
LIST = 'list'
|
||||
|
||||
# Prune persistent storage based on age
|
||||
PRUNE = 'prune'
|
||||
|
||||
# Reset all (reguardless of age)
|
||||
CLEAR = 'clear'
|
||||
|
||||
|
||||
# Define the types in a list for validation purposes
|
||||
PERSISTENT_STORAGE_MODES = (
|
||||
PersistentStorageMode.LIST,
|
||||
PersistentStorageMode.PRUNE,
|
||||
PersistentStorageMode.CLEAR,
|
||||
)
|
||||
|
||||
if os.environ.get('APPRISE_STORAGE', '').strip():
|
||||
# Over-ride Default Storage Path
|
||||
DEFAULT_STORAGE_PATH = os.environ.get('APPRISE_STORAGE')
|
||||
|
||||
|
||||
def print_version_msg():
|
||||
@ -180,7 +208,106 @@ def print_version_msg():
|
||||
click.echo('\n'.join(result))
|
||||
|
||||
|
||||
@click.command(context_settings=CONTEXT_SETTINGS)
|
||||
class CustomHelpCommand(click.Command):
|
||||
def format_help(self, ctx, formatter):
|
||||
# Custom help message
|
||||
content = (
|
||||
'Send a notification to all of the specified servers '
|
||||
'identified by their URLs',
|
||||
'the content provided within the title, body and '
|
||||
'notification-type.',
|
||||
'',
|
||||
'For a list of all of the supported services and information on '
|
||||
'how to use ',
|
||||
'them, check out at https://github.com/caronc/apprise')
|
||||
|
||||
for line in content:
|
||||
formatter.write_text(line)
|
||||
|
||||
# Display options and arguments in the default format
|
||||
self.format_options(ctx, formatter)
|
||||
self.format_epilog(ctx, formatter)
|
||||
|
||||
# Custom 'Actions:' section after the 'Options:'
|
||||
formatter.write_text('')
|
||||
formatter.write_text('Actions:')
|
||||
|
||||
actions = [(
|
||||
'storage', 'Access the persistent storage disk administration',
|
||||
[(
|
||||
'list',
|
||||
'List all URL IDs associated with detected URL(s). '
|
||||
'This is also the default action ran if nothing is provided',
|
||||
), (
|
||||
'prune',
|
||||
'Eliminates stale entries found based on '
|
||||
'--storage-prune-days (-SPD)',
|
||||
), (
|
||||
'clean',
|
||||
'Removes any persistent data created by Apprise',
|
||||
)],
|
||||
)]
|
||||
|
||||
#
|
||||
# Some variables
|
||||
#
|
||||
|
||||
# actions are indented this many spaces
|
||||
# sub actions double this value
|
||||
action_indent = 2
|
||||
|
||||
# label padding (for alignment)
|
||||
action_label_width = 10
|
||||
|
||||
space = ' '
|
||||
space_re = re.compile(r'\r*\n')
|
||||
cols = 80
|
||||
indent = 10
|
||||
|
||||
# Format each action and its subactions
|
||||
for action, description, sub_actions in actions:
|
||||
# Our action indent
|
||||
ai = ' ' * action_indent
|
||||
# Format the main action description
|
||||
formatted_description = space_re.split(textwrap.fill(
|
||||
description, width=(cols - indent - action_indent),
|
||||
initial_indent=space * indent,
|
||||
subsequent_indent=space * indent))
|
||||
for no, line in enumerate(formatted_description):
|
||||
if not no:
|
||||
formatter.write_text(
|
||||
f'{ai}{action:<{action_label_width}}{line}')
|
||||
|
||||
else: # pragma: no cover
|
||||
# Note: no branch is set intentionally since this is not
|
||||
# tested since in 2024.08.13 when this was set up
|
||||
# it never entered this area of the code. But we
|
||||
# know it works because we repeat this process with
|
||||
# our sub-options below
|
||||
formatter.write_text(
|
||||
f'{ai}{space:<{action_label_width}}{line}')
|
||||
|
||||
# Format each subaction
|
||||
ai = ' ' * (action_indent * 2)
|
||||
for action, description in sub_actions:
|
||||
formatted_description = space_re.split(textwrap.fill(
|
||||
description, width=(cols - indent - (action_indent * 3)),
|
||||
initial_indent=space * (indent - action_indent),
|
||||
subsequent_indent=space * (indent - action_indent)))
|
||||
|
||||
for no, line in enumerate(formatted_description):
|
||||
if not no:
|
||||
formatter.write_text(
|
||||
f'{ai}{action:<{action_label_width}}{line}')
|
||||
else:
|
||||
formatter.write_text(
|
||||
f'{ai}{space:<{action_label_width}}{line}')
|
||||
|
||||
# Include any epilog or additional text
|
||||
self.format_epilog(ctx, formatter)
|
||||
|
||||
|
||||
@click.command(context_settings=CONTEXT_SETTINGS, cls=CustomHelpCommand)
|
||||
@click.option('--body', '-b', default=None, type=str,
|
||||
help='Specify the message body. If no body is specified then '
|
||||
'content is read from <stdin>.')
|
||||
@ -190,23 +317,43 @@ def print_version_msg():
|
||||
@click.option('--plugin-path', '-P', default=None, type=str, multiple=True,
|
||||
metavar='PLUGIN_PATH',
|
||||
help='Specify one or more plugin paths to scan.')
|
||||
@click.option('--storage-path', '-S', default=DEFAULT_STORAGE_PATH, type=str,
|
||||
metavar='STORAGE_PATH',
|
||||
help='Specify the path to the persistent storage location '
|
||||
'(default={}).'.format(DEFAULT_STORAGE_PATH))
|
||||
@click.option('--storage-prune-days', '-SPD', default=30,
|
||||
type=int,
|
||||
help='Define the number of days the storage prune '
|
||||
'should run using. Setting this to zero (0) will eliminate '
|
||||
'all accumulated content. By default this value is 30 (days).')
|
||||
@click.option('--storage-uid-length', '-SUL', default=8,
|
||||
type=int,
|
||||
help='Define the number of unique characters to store persistent'
|
||||
'cache in. By default this value is 6 (characters).')
|
||||
@click.option('--storage-mode', '-SM', default=PERSISTENT_STORE_MODES[0],
|
||||
type=str, metavar='MODE',
|
||||
help='Persistent disk storage write mode (default={}). '
|
||||
'Possible values are "{}", and "{}".'.format(
|
||||
PERSISTENT_STORE_MODES[0], '", "'.join(
|
||||
PERSISTENT_STORE_MODES[:-1]),
|
||||
PERSISTENT_STORE_MODES[-1]))
|
||||
@click.option('--config', '-c', default=None, type=str, multiple=True,
|
||||
metavar='CONFIG_URL',
|
||||
help='Specify one or more configuration locations.')
|
||||
@click.option('--attach', '-a', default=None, type=str, multiple=True,
|
||||
metavar='ATTACHMENT_URL',
|
||||
help='Specify one or more attachment.')
|
||||
@click.option('--notification-type', '-n', default=NotifyType.INFO, type=str,
|
||||
@click.option('--notification-type', '-n', default=NOTIFY_TYPES[0], type=str,
|
||||
metavar='TYPE',
|
||||
help='Specify the message type (default={}). '
|
||||
'Possible values are "{}", and "{}".'.format(
|
||||
NotifyType.INFO, '", "'.join(NOTIFY_TYPES[:-1]),
|
||||
NOTIFY_TYPES[0], '", "'.join(NOTIFY_TYPES[:-1]),
|
||||
NOTIFY_TYPES[-1]))
|
||||
@click.option('--input-format', '-i', default=NotifyFormat.TEXT, type=str,
|
||||
@click.option('--input-format', '-i', default=NOTIFY_FORMATS[0], type=str,
|
||||
metavar='FORMAT',
|
||||
help='Specify the message input format (default={}). '
|
||||
'Possible values are "{}", and "{}".'.format(
|
||||
NotifyFormat.TEXT, '", "'.join(NOTIFY_FORMATS[:-1]),
|
||||
NOTIFY_FORMATS[0], '", "'.join(NOTIFY_FORMATS[:-1]),
|
||||
NOTIFY_FORMATS[-1]))
|
||||
@click.option('--theme', '-T', default='default', type=str, metavar='THEME',
|
||||
help='Specify the default theme.')
|
||||
@ -241,10 +388,12 @@ def print_version_msg():
|
||||
help='Display the apprise version and exit.')
|
||||
@click.argument('urls', nargs=-1,
|
||||
metavar='SERVER_URL [SERVER_URL2 [SERVER_URL3]]',)
|
||||
def main(body, title, config, attach, urls, notification_type, theme, tag,
|
||||
@click.pass_context
|
||||
def main(ctx, body, title, config, attach, urls, notification_type, theme, tag,
|
||||
input_format, dry_run, recursion_depth, verbose, disable_async,
|
||||
details, interpret_escapes, interpret_emojis, plugin_path, debug,
|
||||
version):
|
||||
details, interpret_escapes, interpret_emojis, plugin_path,
|
||||
storage_path, storage_mode, storage_prune_days, storage_uid_length,
|
||||
debug, version):
|
||||
"""
|
||||
Send a notification to all of the specified servers identified by their
|
||||
URLs the content provided within the title, body and notification-type.
|
||||
@ -253,7 +402,7 @@ def main(body, title, config, attach, urls, notification_type, theme, tag,
|
||||
use them, check out at https://github.com/caronc/apprise
|
||||
"""
|
||||
# Note: Click ignores the return values of functions it wraps, If you
|
||||
# want to return a specific error code, you must call sys.exit()
|
||||
# want to return a specific error code, you must call ctx.exit()
|
||||
# as you will see below.
|
||||
|
||||
debug = True if debug else False
|
||||
@ -297,7 +446,7 @@ def main(body, title, config, attach, urls, notification_type, theme, tag,
|
||||
|
||||
if version:
|
||||
print_version_msg()
|
||||
sys.exit(0)
|
||||
ctx.exit(0)
|
||||
|
||||
# Simple Error Checking
|
||||
notification_type = notification_type.strip().lower()
|
||||
@ -307,7 +456,7 @@ def main(body, title, config, attach, urls, notification_type, theme, tag,
|
||||
.format(notification_type))
|
||||
# 2 is the same exit code returned by Click if there is a parameter
|
||||
# issue. For consistency, we also return a 2
|
||||
sys.exit(2)
|
||||
ctx.exit(2)
|
||||
|
||||
input_format = input_format.strip().lower()
|
||||
if input_format not in NOTIFY_FORMATS:
|
||||
@ -316,13 +465,31 @@ def main(body, title, config, attach, urls, notification_type, theme, tag,
|
||||
.format(input_format))
|
||||
# 2 is the same exit code returned by Click if there is a parameter
|
||||
# issue. For consistency, we also return a 2
|
||||
sys.exit(2)
|
||||
ctx.exit(2)
|
||||
|
||||
storage_mode = storage_mode.strip().lower()
|
||||
if storage_mode not in PERSISTENT_STORE_MODES:
|
||||
logger.error(
|
||||
'The --storage-mode (-SM) value of {} is not supported.'
|
||||
.format(storage_mode))
|
||||
# 2 is the same exit code returned by Click if there is a parameter
|
||||
# issue. For consistency, we also return a 2
|
||||
ctx.exit(2)
|
||||
|
||||
if not plugin_path:
|
||||
# Prepare a default set of plugin path
|
||||
plugin_path = \
|
||||
next((path for path in DEFAULT_PLUGIN_PATHS
|
||||
if exists(expanduser(path))), None)
|
||||
[path for path in DEFAULT_PLUGIN_PATHS
|
||||
if exists(path_decode(path))]
|
||||
|
||||
if storage_uid_length < 2:
|
||||
logger.error(
|
||||
'The --storage-uid-length (-SUL) value can not be lower '
|
||||
'then two (2).')
|
||||
|
||||
# 2 is the same exit code returned by Click if there is a
|
||||
# parameter issue. For consistency, we also return a 2
|
||||
ctx.exit(2)
|
||||
|
||||
# Prepare our asset
|
||||
asset = AppriseAsset(
|
||||
@ -346,6 +513,15 @@ def main(body, title, config, attach, urls, notification_type, theme, tag,
|
||||
|
||||
# Load our plugins
|
||||
plugin_paths=plugin_path,
|
||||
|
||||
# Load our persistent storage path
|
||||
storage_path=path_decode(storage_path),
|
||||
|
||||
# Our storage URL ID Length
|
||||
storage_idlen=storage_uid_length,
|
||||
|
||||
# Define if we flush to disk as soon as possible or not when required
|
||||
storage_mode=storage_mode
|
||||
)
|
||||
|
||||
# Create our Apprise object
|
||||
@ -429,7 +605,7 @@ def main(body, title, config, attach, urls, notification_type, theme, tag,
|
||||
# new line padding between entries
|
||||
click.echo()
|
||||
|
||||
sys.exit(0)
|
||||
ctx.exit(0)
|
||||
# end if details()
|
||||
|
||||
# The priorities of what is accepted are parsed in order below:
|
||||
@ -439,7 +615,7 @@ def main(body, title, config, attach, urls, notification_type, theme, tag,
|
||||
# 4. Configuration by environment variable: APPRISE_CONFIG
|
||||
# 5. Default Configuration File(s) (if found)
|
||||
#
|
||||
if urls:
|
||||
elif urls and not 'storage'.startswith(urls[0]):
|
||||
if tag:
|
||||
# Ignore any tags specified
|
||||
logger.warning(
|
||||
@ -483,20 +659,145 @@ def main(body, title, config, attach, urls, notification_type, theme, tag,
|
||||
else:
|
||||
# Load default configuration
|
||||
a.add(AppriseConfig(
|
||||
paths=[f for f in DEFAULT_CONFIG_PATHS if isfile(expanduser(f))],
|
||||
paths=[f for f in DEFAULT_CONFIG_PATHS if isfile(path_decode(f))],
|
||||
asset=asset, recursion=recursion_depth))
|
||||
|
||||
if len(a) == 0 and not urls:
|
||||
logger.error(
|
||||
'You must specify at least one server URL or populated '
|
||||
'configuration file.')
|
||||
print_help_msg(main)
|
||||
sys.exit(1)
|
||||
click.echo(ctx.get_help())
|
||||
ctx.exit(1)
|
||||
|
||||
# each --tag entry comprises of a comma separated 'and' list
|
||||
# we or each of of the --tag and sets specified.
|
||||
tags = None if not tag else [parse_list(t) for t in tag]
|
||||
|
||||
# Determine if we're dealing with URLs or url_ids based on the first
|
||||
# entry provided.
|
||||
if urls and 'storage'.startswith(urls[0]):
|
||||
#
|
||||
# Storage Mode
|
||||
# - urls are now to be interpreted as best matching namespaces
|
||||
#
|
||||
if storage_prune_days < 0:
|
||||
logger.error(
|
||||
'The --storage-prune-days (-SPD) value can not be lower '
|
||||
'then zero (0).')
|
||||
|
||||
# 2 is the same exit code returned by Click if there is a
|
||||
# parameter issue. For consistency, we also return a 2
|
||||
ctx.exit(2)
|
||||
|
||||
# Number of columns to assume in the terminal. In future, maybe this
|
||||
# can be detected and made dynamic. The actual column count is 80, but
|
||||
# 5 characters are already reserved for the counter on the left
|
||||
(columns, _) = shutil.get_terminal_size(fallback=(80, 24))
|
||||
|
||||
filter_uids = urls[1:]
|
||||
action = PERSISTENT_STORAGE_MODES[0]
|
||||
if filter_uids:
|
||||
_action = next( # pragma: no branch
|
||||
(a for a in PERSISTENT_STORAGE_MODES
|
||||
if a.startswith(filter_uids[0])), None)
|
||||
|
||||
if _action:
|
||||
# pop top entry
|
||||
filter_uids = filter_uids[1:]
|
||||
action = _action
|
||||
|
||||
# Get our detected URL IDs
|
||||
uids = {}
|
||||
for plugin in (a if not tags else a.find(tag=tags)):
|
||||
_id = plugin.url_id()
|
||||
if not _id:
|
||||
continue
|
||||
|
||||
if filter_uids and next(
|
||||
(False for n in filter_uids if _id.startswith(n)), True):
|
||||
continue
|
||||
|
||||
if _id not in uids:
|
||||
uids[_id] = {
|
||||
'plugins': [plugin],
|
||||
'state': PersistentStoreState.UNUSED,
|
||||
'size': 0,
|
||||
}
|
||||
|
||||
else:
|
||||
# It's possible to have more then one URL point to the same
|
||||
# location (thus match against the same url id more then once
|
||||
uids[_id]['plugins'].append(plugin)
|
||||
|
||||
if action == PersistentStorageMode.LIST:
|
||||
detected_uid = PersistentStore.disk_scan(
|
||||
# Use our asset path as it has already been properly parsed
|
||||
path=asset.storage_path,
|
||||
|
||||
# Provide filter if specified
|
||||
namespace=filter_uids,
|
||||
)
|
||||
for _id in detected_uid:
|
||||
size, _ = dir_size(os.path.join(asset.storage_path, _id))
|
||||
if _id in uids:
|
||||
uids[_id]['state'] = PersistentStoreState.ACTIVE
|
||||
uids[_id]['size'] = size
|
||||
|
||||
elif not tags:
|
||||
uids[_id] = {
|
||||
'plugins': [],
|
||||
# No cross reference (wasted space?)
|
||||
'state': PersistentStoreState.STALE,
|
||||
# Acquire disk space
|
||||
'size': size,
|
||||
}
|
||||
|
||||
for idx, (uid, meta) in enumerate(uids.items()):
|
||||
fg = "green" \
|
||||
if meta['state'] == PersistentStoreState.ACTIVE else (
|
||||
"red"
|
||||
if meta['state'] == PersistentStoreState.STALE else
|
||||
"white")
|
||||
|
||||
if idx > 0:
|
||||
# New line
|
||||
click.echo()
|
||||
click.echo("{: 4d}. ".format(idx + 1), nl=False)
|
||||
click.echo(click.style("{:<52} {:<8} {}".format(
|
||||
uid, bytes_to_str(meta['size']), meta['state']),
|
||||
fg=fg, bold=True))
|
||||
|
||||
for entry in meta['plugins']:
|
||||
url = entry.url(privacy=True)
|
||||
click.echo("{:>7} {}".format(
|
||||
'-',
|
||||
url if len(url) <= (columns - 8) else '{}...'.format(
|
||||
url[:columns - 11])))
|
||||
|
||||
if entry.tags:
|
||||
click.echo("{:>10}: {}".format(
|
||||
'tags', ', '.join(entry.tags)))
|
||||
|
||||
else: # PersistentStorageMode.PRUNE or PersistentStorageMode.CLEAR
|
||||
if action == PersistentStorageMode.CLEAR:
|
||||
storage_prune_days = 0
|
||||
|
||||
# clean up storage
|
||||
results = PersistentStore.disk_prune(
|
||||
# Use our asset path as it has already been properly parsed
|
||||
path=asset.storage_path,
|
||||
# Provide our namespaces if they exist
|
||||
namespace=None if not filter_uids else filter_uids,
|
||||
# Convert expiry from days to seconds
|
||||
expires=storage_prune_days * 60 * 60 * 24,
|
||||
action=not dry_run)
|
||||
|
||||
ctx.exit(0)
|
||||
# end if disk_prune()
|
||||
|
||||
ctx.exit(0)
|
||||
# end if storage()
|
||||
|
||||
if not dry_run:
|
||||
if body is None:
|
||||
logger.trace('No --body (-b) specified; reading from stdin')
|
||||
@ -508,10 +809,10 @@ def main(body, title, config, attach, urls, notification_type, theme, tag,
|
||||
body=body, title=title, notify_type=notification_type, tag=tags,
|
||||
attach=attach)
|
||||
else:
|
||||
# Number of rows to assume in the terminal. In future, maybe this can
|
||||
# be detected and made dynamic. The actual row count is 80, but 5
|
||||
# characters are already reserved for the counter on the left
|
||||
rows = 75
|
||||
# Number of columns to assume in the terminal. In future, maybe this
|
||||
# can be detected and made dynamic. The actual column count is 80, but
|
||||
# 5 characters are already reserved for the counter on the left
|
||||
(columns, _) = shutil.get_terminal_size(fallback=(80, 24))
|
||||
|
||||
# Initialize our URL response; This is populated within the for/loop
|
||||
# below; but plays a factor at the end when we need to determine if
|
||||
@ -520,11 +821,18 @@ def main(body, title, config, attach, urls, notification_type, theme, tag,
|
||||
|
||||
for idx, server in enumerate(a.find(tag=tags)):
|
||||
url = server.url(privacy=True)
|
||||
click.echo("{: 3d}. {}".format(
|
||||
click.echo("{: 4d}. {}".format(
|
||||
idx + 1,
|
||||
url if len(url) <= rows else '{}...'.format(url[:rows - 3])))
|
||||
url if len(url) <= (columns - 8) else '{}...'.format(
|
||||
url[:columns - 9])))
|
||||
|
||||
# Share our URL ID
|
||||
click.echo("{:>10}: {}".format(
|
||||
'uid', '- n/a -' if not server.url_id()
|
||||
else server.url_id()))
|
||||
|
||||
if server.tags:
|
||||
click.echo("{} - {}".format(' ' * 5, ', '.join(server.tags)))
|
||||
click.echo("{:>10}: {}".format('tags', ', '.join(server.tags)))
|
||||
|
||||
# Initialize a default response of nothing matched, otherwise
|
||||
# if we matched at least one entry, we can return True
|
||||
@ -537,11 +845,11 @@ def main(body, title, config, attach, urls, notification_type, theme, tag,
|
||||
|
||||
# Exit code 3 is used since Click uses exit code 2 if there is an
|
||||
# error with the parameters specified
|
||||
sys.exit(3)
|
||||
ctx.exit(3)
|
||||
|
||||
elif result is False:
|
||||
# At least 1 notification service failed to send
|
||||
sys.exit(1)
|
||||
ctx.exit(1)
|
||||
|
||||
# else: We're good!
|
||||
sys.exit(0)
|
||||
ctx.exit(0)
|
||||
|
@ -187,6 +187,42 @@ CONTENT_LOCATIONS = (
|
||||
ContentLocation.INACCESSIBLE,
|
||||
)
|
||||
|
||||
|
||||
class PersistentStoreMode:
|
||||
# Allow persistent storage; write on demand
|
||||
AUTO = 'auto'
|
||||
|
||||
# Always flush every change to disk after it's saved. This has higher i/o
|
||||
# but enforces disk reflects what was set immediately
|
||||
FLUSH = 'flush'
|
||||
|
||||
# memory based store only
|
||||
MEMORY = 'memory'
|
||||
|
||||
|
||||
PERSISTENT_STORE_MODES = (
|
||||
PersistentStoreMode.AUTO,
|
||||
PersistentStoreMode.FLUSH,
|
||||
PersistentStoreMode.MEMORY,
|
||||
)
|
||||
|
||||
|
||||
class PersistentStoreState:
|
||||
"""
|
||||
Defines the persistent states describing what has been cached
|
||||
"""
|
||||
# Persistent Directory is actively cross-referenced against a matching URL
|
||||
ACTIVE = 'active'
|
||||
|
||||
# Persistent Directory is no longer being used or has no cross-reference
|
||||
STALE = 'stale'
|
||||
|
||||
# Persistent Directory is not utilizing any disk space at all, however
|
||||
# it potentially could if the plugin it successfully cross-references
|
||||
# is utilized
|
||||
UNUSED = 'unused'
|
||||
|
||||
|
||||
# This is a reserved tag that is automatically assigned to every
|
||||
# Notification Plugin
|
||||
MATCH_ALL_TAG = 'all'
|
||||
|
@ -29,6 +29,7 @@
|
||||
import re
|
||||
import os
|
||||
from .base import ConfigBase
|
||||
from ..utils import path_decode
|
||||
from ..common import ConfigFormat
|
||||
from ..common import ContentIncludeMode
|
||||
from ..locale import gettext_lazy as _
|
||||
@ -59,7 +60,10 @@ class ConfigFile(ConfigBase):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# Store our file path as it was set
|
||||
self.path = os.path.abspath(os.path.expanduser(path))
|
||||
self.path = path_decode(path)
|
||||
|
||||
# Track the file as it was saved
|
||||
self.__original_path = os.path.normpath(path)
|
||||
|
||||
# Update the config path to be relative to our file we just loaded
|
||||
self.config_path = os.path.dirname(self.path)
|
||||
@ -89,7 +93,7 @@ class ConfigFile(ConfigBase):
|
||||
params['format'] = self.config_format
|
||||
|
||||
return 'file://{path}{params}'.format(
|
||||
path=self.quote(self.path),
|
||||
path=self.quote(self.__original_path),
|
||||
params='?{}'.format(self.urlencode(params)) if params else '',
|
||||
)
|
||||
|
||||
|
53
apprise/exception.py
Normal file
53
apprise/exception.py
Normal file
@ -0,0 +1,53 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# BSD 2-Clause License
|
||||
#
|
||||
# Apprise - Push Notification Library.
|
||||
# Copyright (c) 2024, Chris Caron <lead2gold@gmail.com>
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
#
|
||||
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
import errno
|
||||
|
||||
|
||||
class AppriseException(Exception):
|
||||
"""
|
||||
Base Apprise Exception Class
|
||||
"""
|
||||
def __init__(self, message, error_code=0):
|
||||
super().__init__(message)
|
||||
self.error_code = error_code
|
||||
|
||||
|
||||
class AppriseDiskIOError(AppriseException):
|
||||
"""
|
||||
Thrown when an disk i/o error occurs
|
||||
"""
|
||||
def __init__(self, message, error_code=errno.EIO):
|
||||
super().__init__(message, error_code=error_code)
|
||||
|
||||
|
||||
class AppriseFileNotFound(AppriseDiskIOError, FileNotFoundError):
|
||||
"""
|
||||
Thrown when a persistent write occured in MEMORY mode
|
||||
"""
|
||||
def __init__(self, message):
|
||||
super().__init__(message, error_code=errno.ENOENT)
|
@ -36,6 +36,7 @@ import threading
|
||||
from .utils import import_module
|
||||
from .utils import Singleton
|
||||
from .utils import parse_list
|
||||
from .utils import path_decode
|
||||
from os.path import dirname
|
||||
from os.path import abspath
|
||||
from os.path import join
|
||||
@ -373,7 +374,7 @@ class PluginManager(metaclass=Singleton):
|
||||
return
|
||||
|
||||
for _path in paths:
|
||||
path = os.path.abspath(os.path.expanduser(_path))
|
||||
path = path_decode(_path)
|
||||
if (cache and path in self._paths_previously_scanned) \
|
||||
or not os.path.exists(path):
|
||||
# We're done as we've already scanned this
|
||||
|
1676
apprise/persistent_store.py
Normal file
1676
apprise/persistent_store.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -354,6 +354,15 @@ class NotifyAfricasTalking(NotifyBase):
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.appuser, self.apikey)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -729,6 +729,15 @@ class NotifyAprs(NotifyBase):
|
||||
params=NotifyAprs.urlencode(params),
|
||||
)
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.user, self.password, self.locale)
|
||||
|
||||
def __len__(self):
|
||||
"""
|
||||
Returns the number of targets associated with this notification
|
||||
|
@ -395,6 +395,18 @@ class NotifyBark(NotifyBase):
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol if self.secure else self.protocol,
|
||||
self.user, self.password, self.host, self.port,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -38,7 +38,9 @@ from ..common import NotifyFormat
|
||||
from ..common import NOTIFY_FORMATS
|
||||
from ..common import OverflowMode
|
||||
from ..common import OVERFLOW_MODES
|
||||
from ..common import PersistentStoreMode
|
||||
from ..locale import gettext_lazy as _
|
||||
from ..persistent_store import PersistentStore
|
||||
from ..apprise_attachment import AppriseAttachment
|
||||
|
||||
|
||||
@ -130,12 +132,19 @@ class NotifyBase(URLBase):
|
||||
# of lines. Setting this to zero disables this feature.
|
||||
body_max_line_count = 0
|
||||
|
||||
# Persistent storage default html settings
|
||||
persistent_storage = True
|
||||
|
||||
# Default Notify Format
|
||||
notify_format = NotifyFormat.TEXT
|
||||
|
||||
# Default Overflow Mode
|
||||
overflow_mode = OverflowMode.UPSTREAM
|
||||
|
||||
# Our default is to no not use persistent storage beyond in-memory
|
||||
# reference
|
||||
storage_mode = PersistentStoreMode.MEMORY
|
||||
|
||||
# Default Emoji Interpretation
|
||||
interpret_emojis = False
|
||||
|
||||
@ -197,6 +206,16 @@ class NotifyBase(URLBase):
|
||||
# runtime.
|
||||
'_lookup_default': 'interpret_emojis',
|
||||
},
|
||||
'store': {
|
||||
'name': _('Persistent Storage'),
|
||||
# Use Persistent Storage
|
||||
'type': 'bool',
|
||||
# Provide a default
|
||||
'default': persistent_storage,
|
||||
# look up default using the following parent class value at
|
||||
# runtime.
|
||||
'_lookup_default': 'persistent_storage',
|
||||
},
|
||||
})
|
||||
|
||||
#
|
||||
@ -268,6 +287,9 @@ class NotifyBase(URLBase):
|
||||
# are turned off (no user over-rides allowed)
|
||||
#
|
||||
|
||||
# Our Persistent Storage object is initialized on demand
|
||||
self.__store = None
|
||||
|
||||
# Take a default
|
||||
self.interpret_emojis = self.asset.interpret_emojis
|
||||
if 'emojis' in kwargs:
|
||||
@ -301,6 +323,14 @@ class NotifyBase(URLBase):
|
||||
# Provide override
|
||||
self.overflow_mode = overflow
|
||||
|
||||
# Prepare our Persistent Storage switch
|
||||
self.persistent_storage = parse_bool(
|
||||
kwargs.get('store', NotifyBase.persistent_storage))
|
||||
if not self.persistent_storage:
|
||||
# Enforce the disabling of cache (ortherwise defaults are use)
|
||||
self.url_identifier = False
|
||||
self.__cached_url_identifier = None
|
||||
|
||||
def image_url(self, notify_type, logo=False, extension=None,
|
||||
image_size=None):
|
||||
"""
|
||||
@ -726,6 +756,10 @@ class NotifyBase(URLBase):
|
||||
'overflow': self.overflow_mode,
|
||||
}
|
||||
|
||||
# Persistent Storage Setting
|
||||
if self.persistent_storage != NotifyBase.persistent_storage:
|
||||
params['store'] = 'yes' if self.persistent_storage else 'no'
|
||||
|
||||
params.update(super().url_parameters(*args, **kwargs))
|
||||
|
||||
# return default parameters
|
||||
@ -778,6 +812,10 @@ class NotifyBase(URLBase):
|
||||
# Allow emoji's override
|
||||
if 'emojis' in results['qsd']:
|
||||
results['emojis'] = parse_bool(results['qsd'].get('emojis'))
|
||||
# Store our persistent storage boolean
|
||||
|
||||
if 'store' in results['qsd']:
|
||||
results['store'] = results['qsd']['store']
|
||||
|
||||
return results
|
||||
|
||||
@ -798,3 +836,29 @@ class NotifyBase(URLBase):
|
||||
should return the same set of results that parse_url() does.
|
||||
"""
|
||||
return None
|
||||
|
||||
@property
|
||||
def store(self):
|
||||
"""
|
||||
Returns a pointer to our persistent store for use.
|
||||
|
||||
The best use cases are:
|
||||
self.store.get('key')
|
||||
self.store.set('key', 'value')
|
||||
self.store.delete('key1', 'key2', ...)
|
||||
|
||||
You can also access the keys this way:
|
||||
self.store['key']
|
||||
|
||||
And clear them:
|
||||
del self.store['key']
|
||||
|
||||
"""
|
||||
if self.__store is None:
|
||||
# Initialize our persistent store for use
|
||||
self.__store = PersistentStore(
|
||||
namespace=self.url_id(),
|
||||
path=self.asset.storage_path,
|
||||
mode=self.asset.storage_mode)
|
||||
|
||||
return self.__store
|
||||
|
@ -341,6 +341,15 @@ class NotifyBoxcar(NotifyBase):
|
||||
params=NotifyBoxcar.urlencode(params),
|
||||
)
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.access, self.secret)
|
||||
|
||||
def __len__(self):
|
||||
"""
|
||||
Returns the number of targets associated with this notification
|
||||
|
@ -413,6 +413,19 @@ class NotifyBulkSMS(NotifyBase):
|
||||
for x in self.groups])),
|
||||
params=NotifyBulkSMS.urlencode(params))
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol,
|
||||
self.user if self.user else None,
|
||||
self.password if self.password else None,
|
||||
)
|
||||
|
||||
def __len__(self):
|
||||
"""
|
||||
Returns the number of targets associated with this notification
|
||||
|
@ -304,6 +304,15 @@ class NotifyBulkVS(NotifyBase):
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.source, self.user, self.password)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -378,6 +378,15 @@ class NotifyBurstSMS(NotifyBase):
|
||||
[NotifyBurstSMS.quote(x, safe='') for x in self.targets]),
|
||||
params=NotifyBurstSMS.urlencode(params))
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.apikey, self.secret, self.source)
|
||||
|
||||
def __len__(self):
|
||||
"""
|
||||
Returns the number of targets associated with this notification
|
||||
|
@ -181,6 +181,15 @@ class NotifyChantify(NotifyBase):
|
||||
params=NotifyChantify.urlencode(params),
|
||||
)
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.token)
|
||||
|
||||
@staticmethod
|
||||
def parse_url(url):
|
||||
"""
|
||||
|
@ -285,6 +285,15 @@ class NotifyClickSend(NotifyBase):
|
||||
params=NotifyClickSend.urlencode(params),
|
||||
)
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.user, self.password)
|
||||
|
||||
def __len__(self):
|
||||
"""
|
||||
Returns the number of targets associated with this notification
|
||||
|
@ -272,62 +272,6 @@ class NotifyForm(NotifyBase):
|
||||
|
||||
return
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
"""
|
||||
|
||||
# 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()})
|
||||
|
||||
# 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 = ''
|
||||
if self.user and self.password:
|
||||
auth = '{user}:{password}@'.format(
|
||||
user=NotifyForm.quote(self.user, safe=''),
|
||||
password=self.pprint(
|
||||
self.password, privacy, mode=PrivacyMode.Secret, safe=''),
|
||||
)
|
||||
elif self.user:
|
||||
auth = '{user}@'.format(
|
||||
user=NotifyForm.quote(self.user, safe=''),
|
||||
)
|
||||
|
||||
default_port = 443 if self.secure else 80
|
||||
|
||||
return '{schema}://{auth}{hostname}{port}{fullpath}?{params}'.format(
|
||||
schema=self.secure_protocol if self.secure else self.protocol,
|
||||
auth=auth,
|
||||
# never encode hostname since we're expecting it to be a valid one
|
||||
hostname=self.host,
|
||||
port='' if self.port is None or self.port == default_port
|
||||
else ':{}'.format(self.port),
|
||||
fullpath=NotifyForm.quote(self.fullpath, safe='/')
|
||||
if self.fullpath else '/',
|
||||
params=NotifyForm.urlencode(params),
|
||||
)
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,
|
||||
**kwargs):
|
||||
"""
|
||||
@ -486,6 +430,76 @@ class NotifyForm(NotifyBase):
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol if self.secure else self.protocol,
|
||||
self.user, self.password, self.host,
|
||||
self.port if self.port else (443 if self.secure else 80),
|
||||
self.fullpath.rstrip('/'),
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
"""
|
||||
|
||||
# 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()})
|
||||
|
||||
# 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 = ''
|
||||
if self.user and self.password:
|
||||
auth = '{user}:{password}@'.format(
|
||||
user=NotifyForm.quote(self.user, safe=''),
|
||||
password=self.pprint(
|
||||
self.password, privacy, mode=PrivacyMode.Secret, safe=''),
|
||||
)
|
||||
elif self.user:
|
||||
auth = '{user}@'.format(
|
||||
user=NotifyForm.quote(self.user, safe=''),
|
||||
)
|
||||
|
||||
default_port = 443 if self.secure else 80
|
||||
|
||||
return '{schema}://{auth}{hostname}{port}{fullpath}?{params}'.format(
|
||||
schema=self.secure_protocol if self.secure else self.protocol,
|
||||
auth=auth,
|
||||
# never encode hostname since we're expecting it to be a valid one
|
||||
hostname=self.host,
|
||||
port='' if self.port is None or self.port == default_port
|
||||
else ':{}'.format(self.port),
|
||||
fullpath=NotifyForm.quote(self.fullpath, safe='/')
|
||||
if self.fullpath else '/',
|
||||
params=NotifyForm.urlencode(params),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def parse_url(url):
|
||||
"""
|
||||
|
@ -195,56 +195,6 @@ class NotifyJSON(NotifyBase):
|
||||
|
||||
return
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
"""
|
||||
|
||||
# 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()})
|
||||
|
||||
# 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()})
|
||||
|
||||
# Determine Authentication
|
||||
auth = ''
|
||||
if self.user and self.password:
|
||||
auth = '{user}:{password}@'.format(
|
||||
user=NotifyJSON.quote(self.user, safe=''),
|
||||
password=self.pprint(
|
||||
self.password, privacy, mode=PrivacyMode.Secret, safe=''),
|
||||
)
|
||||
elif self.user:
|
||||
auth = '{user}@'.format(
|
||||
user=NotifyJSON.quote(self.user, safe=''),
|
||||
)
|
||||
|
||||
default_port = 443 if self.secure else 80
|
||||
|
||||
return '{schema}://{auth}{hostname}{port}{fullpath}?{params}'.format(
|
||||
schema=self.secure_protocol if self.secure else self.protocol,
|
||||
auth=auth,
|
||||
# never encode hostname since we're expecting it to be a valid one
|
||||
hostname=self.host,
|
||||
port='' if self.port is None or self.port == default_port
|
||||
else ':{}'.format(self.port),
|
||||
fullpath=NotifyJSON.quote(self.fullpath, safe='/')
|
||||
if self.fullpath else '/',
|
||||
params=NotifyJSON.urlencode(params),
|
||||
)
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,
|
||||
**kwargs):
|
||||
"""
|
||||
@ -395,6 +345,70 @@ class NotifyJSON(NotifyBase):
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol if self.secure else self.protocol,
|
||||
self.user, self.password, self.host,
|
||||
self.port if self.port else (443 if self.secure else 80),
|
||||
self.fullpath.rstrip('/'),
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
"""
|
||||
|
||||
# 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()})
|
||||
|
||||
# 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()})
|
||||
|
||||
# Determine Authentication
|
||||
auth = ''
|
||||
if self.user and self.password:
|
||||
auth = '{user}:{password}@'.format(
|
||||
user=NotifyJSON.quote(self.user, safe=''),
|
||||
password=self.pprint(
|
||||
self.password, privacy, mode=PrivacyMode.Secret, safe=''),
|
||||
)
|
||||
elif self.user:
|
||||
auth = '{user}@'.format(
|
||||
user=NotifyJSON.quote(self.user, safe=''),
|
||||
)
|
||||
|
||||
default_port = 443 if self.secure else 80
|
||||
|
||||
return '{schema}://{auth}{hostname}{port}{fullpath}?{params}'.format(
|
||||
schema=self.secure_protocol if self.secure else self.protocol,
|
||||
auth=auth,
|
||||
# never encode hostname since we're expecting it to be a valid one
|
||||
hostname=self.host,
|
||||
port='' if self.port is None or self.port == default_port
|
||||
else ':{}'.format(self.port),
|
||||
fullpath=NotifyJSON.quote(self.fullpath, safe='/')
|
||||
if self.fullpath else '/',
|
||||
params=NotifyJSON.urlencode(params),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def parse_url(url):
|
||||
"""
|
||||
|
@ -242,58 +242,6 @@ class NotifyXML(NotifyBase):
|
||||
|
||||
return
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
"""
|
||||
|
||||
# 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()})
|
||||
|
||||
# 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 = ''
|
||||
if self.user and self.password:
|
||||
auth = '{user}:{password}@'.format(
|
||||
user=NotifyXML.quote(self.user, safe=''),
|
||||
password=self.pprint(
|
||||
self.password, privacy, mode=PrivacyMode.Secret, safe=''),
|
||||
)
|
||||
elif self.user:
|
||||
auth = '{user}@'.format(
|
||||
user=NotifyXML.quote(self.user, safe=''),
|
||||
)
|
||||
|
||||
default_port = 443 if self.secure else 80
|
||||
|
||||
return '{schema}://{auth}{hostname}{port}{fullpath}?{params}'.format(
|
||||
schema=self.secure_protocol if self.secure else self.protocol,
|
||||
auth=auth,
|
||||
# never encode hostname since we're expecting it to be a valid one
|
||||
hostname=self.host,
|
||||
port='' if self.port is None or self.port == default_port
|
||||
else ':{}'.format(self.port),
|
||||
fullpath=NotifyXML.quote(self.fullpath, safe='/')
|
||||
if self.fullpath else '/',
|
||||
params=NotifyXML.urlencode(params),
|
||||
)
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, attach=None,
|
||||
**kwargs):
|
||||
"""
|
||||
@ -467,6 +415,72 @@ class NotifyXML(NotifyBase):
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol if self.secure else self.protocol,
|
||||
self.user, self.password, self.host,
|
||||
self.port if self.port else (443 if self.secure else 80),
|
||||
self.fullpath.rstrip('/'),
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
"""
|
||||
|
||||
# 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()})
|
||||
|
||||
# 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 = ''
|
||||
if self.user and self.password:
|
||||
auth = '{user}:{password}@'.format(
|
||||
user=NotifyXML.quote(self.user, safe=''),
|
||||
password=self.pprint(
|
||||
self.password, privacy, mode=PrivacyMode.Secret, safe=''),
|
||||
)
|
||||
elif self.user:
|
||||
auth = '{user}@'.format(
|
||||
user=NotifyXML.quote(self.user, safe=''),
|
||||
)
|
||||
|
||||
default_port = 443 if self.secure else 80
|
||||
|
||||
return '{schema}://{auth}{hostname}{port}{fullpath}?{params}'.format(
|
||||
schema=self.secure_protocol if self.secure else self.protocol,
|
||||
auth=auth,
|
||||
# never encode hostname since we're expecting it to be a valid one
|
||||
hostname=self.host,
|
||||
port='' if self.port is None or self.port == default_port
|
||||
else ':{}'.format(self.port),
|
||||
fullpath=NotifyXML.quote(self.fullpath, safe='/')
|
||||
if self.fullpath else '/',
|
||||
params=NotifyXML.urlencode(params),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def parse_url(url):
|
||||
"""
|
||||
|
@ -354,6 +354,15 @@ class NotifyD7Networks(NotifyBase):
|
||||
[NotifyD7Networks.quote(x, safe='') for x in self.targets]),
|
||||
params=NotifyD7Networks.urlencode(params))
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.token)
|
||||
|
||||
def __len__(self):
|
||||
"""
|
||||
Returns the number of targets associated with this notification
|
||||
|
@ -346,6 +346,15 @@ class NotifyDapnet(NotifyBase):
|
||||
params=NotifyDapnet.urlencode(params),
|
||||
)
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.user, self.password)
|
||||
|
||||
def __len__(self):
|
||||
"""
|
||||
Returns the number of targets associated with this notification
|
||||
|
@ -173,7 +173,6 @@ class NotifyDBus(NotifyBase):
|
||||
# object if we were to reference, we wouldn't be backwards compatible with
|
||||
# Python v2. So converting the result set back into a list makes us
|
||||
# compatible
|
||||
# TODO: Review after dropping support for Python 2.
|
||||
protocol = list(MAINLOOP_MAP.keys())
|
||||
|
||||
# A URL that takes you to the setup/help of the specific protocol
|
||||
@ -196,6 +195,10 @@ class NotifyDBus(NotifyBase):
|
||||
dbus_interface = 'org.freedesktop.Notifications'
|
||||
dbus_setting_location = '/org/freedesktop/Notifications'
|
||||
|
||||
# No URL Identifier will be defined for this service as there simply isn't
|
||||
# enough details to uniquely identify one dbus:// from another.
|
||||
url_identifier = False
|
||||
|
||||
# Define object templates
|
||||
templates = (
|
||||
'{schema}://',
|
||||
|
@ -310,6 +310,15 @@ class NotifyDingTalk(NotifyBase):
|
||||
[NotifyDingTalk.quote(x, safe='') for x in self.targets]),
|
||||
args=NotifyDingTalk.urlencode(args))
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.secret, self.token)
|
||||
|
||||
def __len__(self):
|
||||
"""
|
||||
Returns the number of targets associated with this notification
|
||||
|
@ -607,6 +607,15 @@ class NotifyDiscord(NotifyBase):
|
||||
params=NotifyDiscord.urlencode(params),
|
||||
)
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.webhook_id, self.webhook_token)
|
||||
|
||||
@staticmethod
|
||||
def parse_url(url):
|
||||
"""
|
||||
|
@ -1031,6 +1031,20 @@ class NotifyEmail(NotifyBase):
|
||||
params=NotifyEmail.urlencode(params),
|
||||
)
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol if self.secure else self.protocol,
|
||||
self.user, self.password, self.host,
|
||||
self.port if self.port
|
||||
else SECURE_MODES[self.secure_mode]['default_port'],
|
||||
)
|
||||
|
||||
def __len__(self):
|
||||
"""
|
||||
Returns the number of targets associated with this notification
|
||||
|
@ -593,6 +593,18 @@ class NotifyEmby(NotifyBase):
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol, self.user, self.password, self.host,
|
||||
self.port if self.port else (443 if self.secure else 80),
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -181,6 +181,20 @@ class NotifyEnigma2(NotifyBase):
|
||||
|
||||
return
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol,
|
||||
self.user, self.password, self.host,
|
||||
self.port if self.port else (443 if self.secure else 80),
|
||||
self.fullpath.rstrip('/'),
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -507,6 +507,15 @@ class NotifyFCM(NotifyBase):
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.mode, self.apikey, self.project)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -192,6 +192,15 @@ class NotifyFeishu(NotifyBase):
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.token)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -308,6 +308,15 @@ class NotifyFlock(NotifyBase):
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.token)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -103,6 +103,15 @@ class NotifyFreeMobile(NotifyBase):
|
||||
|
||||
return
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.user, self.password)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -132,6 +132,10 @@ class NotifyGnome(NotifyBase):
|
||||
# cause any title (if defined) to get placed into the message body.
|
||||
title_maxlen = 0
|
||||
|
||||
# No URL Identifier will be defined for this service as there simply isn't
|
||||
# enough details to uniquely identify one dbus:// from another.
|
||||
url_identifier = False
|
||||
|
||||
# Define object templates
|
||||
templates = (
|
||||
'{schema}://',
|
||||
|
@ -265,6 +265,18 @@ class NotifyGoogleChat(NotifyBase):
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol, self.workspace, self.webhook_key,
|
||||
self.webhook_token,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -265,6 +265,20 @@ class NotifyGotify(NotifyBase):
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol if self.secure else self.protocol,
|
||||
self.user, self.password, self.host,
|
||||
self.port if self.port else (443 if self.secure else 80),
|
||||
self.fullpath.rstrip('/'),
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -338,6 +338,19 @@ class NotifyGrowl(NotifyBase):
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol if self.secure else self.protocol,
|
||||
self.user, self.password, self.host,
|
||||
self.port if self.port else self.default_port,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -179,8 +179,8 @@ class NotifyHomeAssistant(NotifyBase):
|
||||
if isinstance(self.port, int):
|
||||
url += ':%d' % self.port
|
||||
|
||||
url += '' if not self.fullpath else '/' + self.fullpath.strip('/')
|
||||
url += '/api/services/persistent_notification/create'
|
||||
url += self.fullpath.rstrip('/') + \
|
||||
'/api/services/persistent_notification/create'
|
||||
|
||||
self.logger.debug('Home Assistant POST URL: %s (cert_verify=%r)' % (
|
||||
url, self.verify_certificate,
|
||||
@ -231,6 +231,22 @@ class NotifyHomeAssistant(NotifyBase):
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol if self.secure else self.protocol,
|
||||
self.user, self.password, self.host,
|
||||
self.port if self.port else (
|
||||
443 if self.secure else self.default_insecure_port),
|
||||
self.fullpath.rstrip('/'),
|
||||
self.accesstoken,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
@ -302,7 +318,7 @@ class NotifyHomeAssistant(NotifyBase):
|
||||
results['accesstoken'] = fullpath.pop() if fullpath else None
|
||||
|
||||
# Re-assemble our full path
|
||||
results['fullpath'] = '/'.join(fullpath)
|
||||
results['fullpath'] = '/' + '/'.join(fullpath) if fullpath else ''
|
||||
|
||||
# Allow the specification of a unique notification_id so that
|
||||
# it will always replace the last one sent.
|
||||
|
@ -253,6 +253,15 @@ class NotifyHttpSMS(NotifyBase):
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.source, self.apikey)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -287,6 +287,15 @@ class NotifyIFTTT(NotifyBase):
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.webhook_id)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -345,6 +345,15 @@ class NotifyJoin(NotifyBase):
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.apikey)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -304,6 +304,15 @@ class NotifyKavenegar(NotifyBase):
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.source, self.apikey)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -198,6 +198,15 @@ class NotifyKumulos(NotifyBase):
|
||||
return False
|
||||
return True
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.apikey, self.serverkey)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -783,6 +783,29 @@ class NotifyLametric(NotifyBase):
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
if self.mode == LametricMode.DEVICE:
|
||||
return (
|
||||
self.secure_protocol if self.secure else self.protocol,
|
||||
self.user, self.lametric_apikey, self.host,
|
||||
self.port if self.port else (
|
||||
443 if self.secure else
|
||||
self.template_tokens['port']['default']),
|
||||
)
|
||||
|
||||
return (
|
||||
self.protocol,
|
||||
self.lametric_app_access_token,
|
||||
self.lametric_app_id,
|
||||
self.lametric_app_ver,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
@ -871,6 +894,9 @@ class NotifyLametric(NotifyBase):
|
||||
results['password'] = results['user']
|
||||
results['user'] = None
|
||||
|
||||
# Get unquoted entries
|
||||
entries = NotifyLametric.split_path(results['fullpath'])
|
||||
|
||||
# Priority Handling
|
||||
if 'priority' in results['qsd'] and results['qsd']['priority']:
|
||||
results['priority'] = NotifyLametric.unquote(
|
||||
@ -913,6 +939,10 @@ class NotifyLametric(NotifyBase):
|
||||
results['app_ver'] = \
|
||||
NotifyLametric.unquote(results['qsd']['app_ver'])
|
||||
|
||||
elif entries:
|
||||
# Store our app id
|
||||
results['app_ver'] = entries.pop(0)
|
||||
|
||||
if 'token' in results['qsd'] and results['qsd']['token']:
|
||||
# Extract Application Access Token from an argument
|
||||
results['app_token'] = \
|
||||
|
@ -241,6 +241,15 @@ class NotifyLine(NotifyBase):
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.token)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -324,6 +324,24 @@ class NotifyLunaSea(NotifyBase):
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
secure = self.secure_protocol[0] \
|
||||
if self.mode == LunaSeaMode.CLOUD else (
|
||||
self.secure_protocol[0] if self.secure else self.protocol[0])
|
||||
return (
|
||||
secure,
|
||||
self.host if self.mode == LunaSeaMode.PRIVATE else None,
|
||||
self.port if self.port else (443 if self.secure else 80),
|
||||
self.user if self.user else None,
|
||||
self.password if self.password else None,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -92,6 +92,10 @@ class NotifyMacOSX(NotifyBase):
|
||||
# content to display
|
||||
body_max_line_count = 10
|
||||
|
||||
# No URL Identifier will be defined for this service as there simply isn't
|
||||
# enough details to uniquely identify one dbus:// from another.
|
||||
url_identifier = False
|
||||
|
||||
# The possible paths to the terminal-notifier
|
||||
notify_paths = (
|
||||
'/opt/homebrew/bin/terminal-notifier',
|
||||
|
@ -579,6 +579,17 @@ class NotifyMailgun(NotifyBase):
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol, self.host, self.apikey, self.region_name,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -336,6 +336,18 @@ class NotifyMastodon(NotifyBase):
|
||||
|
||||
return
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol[0], self.token, self.host,
|
||||
self.port if self.port else (443 if self.secure else 80),
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -42,6 +42,7 @@ from ..url import PrivacyMode
|
||||
from ..common import NotifyType
|
||||
from ..common import NotifyImageSize
|
||||
from ..common import NotifyFormat
|
||||
from ..common import PersistentStoreMode
|
||||
from ..utils import parse_bool
|
||||
from ..utils import parse_list
|
||||
from ..utils import is_hostname
|
||||
@ -175,6 +176,13 @@ class NotifyMatrix(NotifyBase):
|
||||
# the server doesn't remind us how long we shoul wait for
|
||||
default_wait_ms = 1000
|
||||
|
||||
# Our default is to no not use persistent storage beyond in-memory
|
||||
# reference
|
||||
storage_mode = PersistentStoreMode.AUTO
|
||||
|
||||
# Keep our cache for 20 days
|
||||
default_cache_expiry_sec = 60 * 60 * 24 * 20
|
||||
|
||||
# Define object templates
|
||||
templates = (
|
||||
# Targets are ignored when using t2bot mode; only a token is required
|
||||
@ -299,10 +307,6 @@ class NotifyMatrix(NotifyBase):
|
||||
# Place an image inline with the message body
|
||||
self.include_image = include_image
|
||||
|
||||
# maintain a lookup of room alias's we already paired with their id
|
||||
# to speed up future requests
|
||||
self._room_cache = {}
|
||||
|
||||
# Setup our mode
|
||||
self.mode = self.template_args['mode']['default'] \
|
||||
if not isinstance(mode, str) else mode.lower()
|
||||
@ -342,6 +346,7 @@ class NotifyMatrix(NotifyBase):
|
||||
.format(self.host)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
else:
|
||||
# Verify port if specified
|
||||
if self.port is not None and not (
|
||||
@ -353,6 +358,23 @@ class NotifyMatrix(NotifyBase):
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
#
|
||||
# Initialize from cache if present
|
||||
#
|
||||
if self.mode != MatrixWebhookMode.T2BOT:
|
||||
# our home server gets populated after a login/registration
|
||||
self.home_server = self.store.get('home_server')
|
||||
|
||||
# our user_id gets populated after a login/registration
|
||||
self.user_id = self.store.get('user_id')
|
||||
|
||||
# This gets initialized after a login/registration
|
||||
self.access_token = self.store.get('access_token')
|
||||
|
||||
# This gets incremented for each request made against the v3 API
|
||||
self.transaction_id = 0 if not self.access_token \
|
||||
else self.store.get('transaction_id', 0)
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
Perform Matrix Notification
|
||||
@ -695,6 +717,9 @@ class NotifyMatrix(NotifyBase):
|
||||
# recognized as retransmissions and ignored
|
||||
if self.version == MatrixVersion.V3:
|
||||
self.transaction_id += 1
|
||||
self.store.set(
|
||||
'transaction_id', self.transaction_id,
|
||||
expires=self.default_cache_expiry_sec)
|
||||
|
||||
if not postokay:
|
||||
# Notify our user
|
||||
@ -811,7 +836,18 @@ class NotifyMatrix(NotifyBase):
|
||||
self.home_server = response.get('home_server')
|
||||
self.user_id = response.get('user_id')
|
||||
|
||||
self.store.set(
|
||||
'access_token', self.access_token,
|
||||
expires=self.default_cache_expiry_sec)
|
||||
self.store.set(
|
||||
'home_server', self.home_server,
|
||||
expires=self.default_cache_expiry_sec)
|
||||
self.store.set(
|
||||
'user_id', self.user_id,
|
||||
expires=self.default_cache_expiry_sec)
|
||||
|
||||
if self.access_token is not None:
|
||||
# Store our token into our store
|
||||
self.logger.debug(
|
||||
'Registered successfully with Matrix server.')
|
||||
return True
|
||||
@ -870,6 +906,18 @@ class NotifyMatrix(NotifyBase):
|
||||
|
||||
self.logger.debug(
|
||||
'Authenticated successfully with Matrix server.')
|
||||
|
||||
# Store our token into our store
|
||||
self.store.set(
|
||||
'access_token', self.access_token,
|
||||
expires=self.default_cache_expiry_sec)
|
||||
self.store.set(
|
||||
'home_server', self.home_server,
|
||||
expires=self.default_cache_expiry_sec)
|
||||
self.store.set(
|
||||
'user_id', self.user_id,
|
||||
expires=self.default_cache_expiry_sec)
|
||||
|
||||
return True
|
||||
|
||||
def _logout(self):
|
||||
@ -907,8 +955,9 @@ class NotifyMatrix(NotifyBase):
|
||||
self.home_server = None
|
||||
self.user_id = None
|
||||
|
||||
# Clear our room cache
|
||||
self._room_cache = {}
|
||||
# clear our tokens
|
||||
self.store.clear(
|
||||
'access_token', 'home_server', 'user_id', 'transaction_id')
|
||||
|
||||
self.logger.debug(
|
||||
'Unauthenticated successfully with Matrix server.')
|
||||
@ -948,9 +997,13 @@ class NotifyMatrix(NotifyBase):
|
||||
)
|
||||
|
||||
# Check our cache for speed:
|
||||
if room_id in self._room_cache:
|
||||
try:
|
||||
# We're done as we've already joined the channel
|
||||
return self._room_cache[room_id]['id']
|
||||
return self.store[room_id]['id']
|
||||
|
||||
except KeyError:
|
||||
# No worries, we'll try to acquire the info
|
||||
pass
|
||||
|
||||
# Build our URL
|
||||
path = '/join/{}'.format(NotifyMatrix.quote(room_id))
|
||||
@ -959,10 +1012,10 @@ class NotifyMatrix(NotifyBase):
|
||||
postokay, _ = self._fetch(path, payload=payload)
|
||||
if postokay:
|
||||
# Cache our entry for fast access later
|
||||
self._room_cache[room_id] = {
|
||||
self.store.set(room_id, {
|
||||
'id': room_id,
|
||||
'home_server': home_server,
|
||||
}
|
||||
})
|
||||
|
||||
return room_id if postokay else None
|
||||
|
||||
@ -984,9 +1037,13 @@ class NotifyMatrix(NotifyBase):
|
||||
room = '#{}:{}'.format(result.group('room'), home_server)
|
||||
|
||||
# Check our cache for speed:
|
||||
if room in self._room_cache:
|
||||
try:
|
||||
# We're done as we've already joined the channel
|
||||
return self._room_cache[room]['id']
|
||||
return self.store[room]['id']
|
||||
|
||||
except KeyError:
|
||||
# No worries, we'll try to acquire the info
|
||||
pass
|
||||
|
||||
# If we reach here, we need to join the channel
|
||||
|
||||
@ -997,11 +1054,12 @@ class NotifyMatrix(NotifyBase):
|
||||
postokay, response = self._fetch(path, payload=payload)
|
||||
if postokay:
|
||||
# Cache our entry for fast access later
|
||||
self._room_cache[room] = {
|
||||
self.store.set(room, {
|
||||
'id': response.get('room_id'),
|
||||
'home_server': home_server,
|
||||
}
|
||||
return self._room_cache[room]['id']
|
||||
})
|
||||
|
||||
return response.get('room_id')
|
||||
|
||||
# Try to create the channel
|
||||
return self._room_create(room)
|
||||
@ -1056,10 +1114,10 @@ class NotifyMatrix(NotifyBase):
|
||||
return None
|
||||
|
||||
# Cache our entry for fast access later
|
||||
self._room_cache[response.get('room_alias')] = {
|
||||
self.store.set(response.get('room_alias'), {
|
||||
'id': response.get('room_id'),
|
||||
'home_server': home_server,
|
||||
}
|
||||
})
|
||||
|
||||
return response.get('room_id')
|
||||
|
||||
@ -1292,6 +1350,11 @@ class NotifyMatrix(NotifyBase):
|
||||
# nothing to do
|
||||
return
|
||||
|
||||
if self.store.mode != PersistentStoreMode.MEMORY:
|
||||
# We no longer have to log out as we have persistant storage to
|
||||
# re-use our credentials with
|
||||
return
|
||||
|
||||
try:
|
||||
self._logout()
|
||||
|
||||
@ -1336,6 +1399,22 @@ class NotifyMatrix(NotifyBase):
|
||||
# the end user if we don't have to.
|
||||
pass
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol if self.secure else self.protocol,
|
||||
self.host if self.mode != MatrixWebhookMode.T2BOT
|
||||
else self.access_token,
|
||||
self.port if self.port else (443 if self.secure else 80),
|
||||
self.user if self.mode != MatrixWebhookMode.T2BOT else None,
|
||||
self.password if self.mode != MatrixWebhookMode.T2BOT else None,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -223,8 +223,8 @@ class NotifyMattermost(NotifyBase):
|
||||
payload['channel'] = channel
|
||||
|
||||
url = '{}://{}:{}{}/hooks/{}'.format(
|
||||
self.schema, self.host, self.port, self.fullpath,
|
||||
self.token)
|
||||
self.schema, self.host, self.port,
|
||||
self.fullpath.rstrip('/'), self.token)
|
||||
|
||||
self.logger.debug('Mattermost POST URL: %s (cert_verify=%r)' % (
|
||||
url, self.verify_certificate,
|
||||
@ -286,6 +286,18 @@ class NotifyMattermost(NotifyBase):
|
||||
# Return our overall status
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol if self.secure else self.protocol,
|
||||
self.token, self.host, self.port, self.fullpath,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -291,6 +291,15 @@ class NotifyMessageBird(NotifyBase):
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.apikey, self.source)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -191,6 +191,18 @@ class NotifyMisskey(NotifyBase):
|
||||
|
||||
return
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol if self.secure else self.protocol,
|
||||
self.token, self.host, self.port,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -429,6 +429,23 @@ class NotifyMQTT(NotifyBase):
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol if self.secure else self.protocol,
|
||||
self.user, self.password, self.host,
|
||||
self.port if self.port else (
|
||||
self.mqtt_secure_port if self.secure
|
||||
else self.mqtt_insecure_port),
|
||||
self.fullpath.rstrip('/'),
|
||||
self.client_id,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -310,6 +310,15 @@ class NotifyMSG91(NotifyBase):
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.template, self.authkey)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -468,6 +468,19 @@ class NotifyMSTeams(NotifyBase):
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol,
|
||||
self.team if self.version > 1 else None,
|
||||
self.token_a, self.token_b, self.token_c,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -278,6 +278,18 @@ class NotifyNextcloud(NotifyBase):
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol if self.secure else self.protocol,
|
||||
self.user, self.password, self.host, self.port,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -253,6 +253,18 @@ class NotifyNextcloudTalk(NotifyBase):
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol if self.secure else self.protocol,
|
||||
self.user, self.password, self.host, self.port,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -278,6 +278,19 @@ class NotifyNotica(NotifyBase):
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol if self.secure else self.protocol,
|
||||
self.mode, self.token, self.user, self.password, self.host,
|
||||
self.port,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -199,6 +199,18 @@ class NotifyNotifiarr(NotifyBase):
|
||||
|
||||
return
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol if self.secure else self.protocol,
|
||||
self.apikey,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -197,6 +197,15 @@ class NotifyNotifico(NotifyBase):
|
||||
)
|
||||
return
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.project_id, self.msghook)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -656,6 +656,34 @@ class NotifyNtfy(NotifyBase):
|
||||
|
||||
return False, response
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
|
||||
kwargs = [
|
||||
self.secure_protocol if self.mode == NtfyMode.CLOUD else (
|
||||
self.secure_protocol if self.secure else self.protocol),
|
||||
self.host if self.mode == NtfyMode.PRIVATE else '',
|
||||
443 if self.mode == NtfyMode.CLOUD else (
|
||||
self.port if self.port else (443 if self.secure else 80)),
|
||||
]
|
||||
|
||||
if self.mode == NtfyMode.PRIVATE:
|
||||
if self.auth == NtfyAuth.BASIC:
|
||||
kwargs.extend([
|
||||
self.user if self.user else None,
|
||||
self.password if self.password else None,
|
||||
])
|
||||
|
||||
elif self.token: # NtfyAuth.TOKEN also
|
||||
kwargs.append(self.token)
|
||||
|
||||
return kwargs
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -558,6 +558,18 @@ class NotifyOffice365(NotifyBase):
|
||||
|
||||
return (True, content)
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol, self.email, self.tenant, self.client_id,
|
||||
self.secret,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -474,6 +474,17 @@ class NotifyOneSignal(NotifyBase):
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol, self.template_id, self.app, self.apikey,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -47,10 +47,12 @@
|
||||
# API Integration Docs: https://docs.opsgenie.com/docs/api-integration
|
||||
|
||||
import requests
|
||||
from json import dumps
|
||||
from json import dumps, loads
|
||||
import hashlib
|
||||
|
||||
from .base import NotifyBase
|
||||
from ..common import NotifyType
|
||||
from ..common import NotifyType, NOTIFY_TYPES
|
||||
from ..common import PersistentStoreMode
|
||||
from ..utils import validate_regex
|
||||
from ..utils import is_uuid
|
||||
from ..utils import parse_list
|
||||
@ -76,6 +78,47 @@ OPSGENIE_CATEGORIES = (
|
||||
)
|
||||
|
||||
|
||||
class OpsgenieAlertAction:
|
||||
"""
|
||||
Defines the supported actions
|
||||
"""
|
||||
# Use mapping (specify :key=arg to over-ride)
|
||||
MAP = 'map'
|
||||
|
||||
# Create new alert (default)
|
||||
NEW = 'new'
|
||||
|
||||
# Close Alert
|
||||
CLOSE = 'close'
|
||||
|
||||
# Delete Alert
|
||||
DELETE = 'delete'
|
||||
|
||||
# Acknowledge Alert
|
||||
ACKNOWLEDGE = 'acknowledge'
|
||||
|
||||
# Add note to alert
|
||||
NOTE = 'note'
|
||||
|
||||
|
||||
OPSGENIE_ACTIONS = (
|
||||
OpsgenieAlertAction.MAP,
|
||||
OpsgenieAlertAction.NEW,
|
||||
OpsgenieAlertAction.CLOSE,
|
||||
OpsgenieAlertAction.DELETE,
|
||||
OpsgenieAlertAction.ACKNOWLEDGE,
|
||||
OpsgenieAlertAction.NOTE,
|
||||
)
|
||||
|
||||
# Map all support Apprise Categories to Opsgenie Categories
|
||||
OPSGENIE_ALERT_MAP = {
|
||||
NotifyType.INFO: OpsgenieAlertAction.CLOSE,
|
||||
NotifyType.SUCCESS: OpsgenieAlertAction.CLOSE,
|
||||
NotifyType.WARNING: OpsgenieAlertAction.NEW,
|
||||
NotifyType.FAILURE: OpsgenieAlertAction.NEW,
|
||||
}
|
||||
|
||||
|
||||
# Regions
|
||||
class OpsgenieRegion:
|
||||
US = 'us'
|
||||
@ -160,6 +203,10 @@ class NotifyOpsgenie(NotifyBase):
|
||||
# The maximum length of the body
|
||||
body_maxlen = 15000
|
||||
|
||||
# Our default is to no not use persistent storage beyond in-memory
|
||||
# reference
|
||||
storage_mode = PersistentStoreMode.AUTO
|
||||
|
||||
# If we don't have the specified min length, then we don't bother using
|
||||
# the body directive
|
||||
opsgenie_body_minlen = 130
|
||||
@ -170,10 +217,24 @@ class NotifyOpsgenie(NotifyBase):
|
||||
# The maximum allowable targets within a notification
|
||||
default_batch_size = 50
|
||||
|
||||
# Defines our default message mapping
|
||||
opsgenie_message_map = {
|
||||
# Add a note to existing alert
|
||||
NotifyType.INFO: OpsgenieAlertAction.NOTE,
|
||||
# Close existing alert
|
||||
NotifyType.SUCCESS: OpsgenieAlertAction.CLOSE,
|
||||
# Create notice
|
||||
NotifyType.WARNING: OpsgenieAlertAction.NEW,
|
||||
# Create notice
|
||||
NotifyType.FAILURE: OpsgenieAlertAction.NEW,
|
||||
}
|
||||
|
||||
# Define object templates
|
||||
templates = (
|
||||
'{schema}://{apikey}',
|
||||
'{schema}://{user}@{apikey}',
|
||||
'{schema}://{apikey}/{targets}',
|
||||
'{schema}://{user}@{apikey}/{targets}',
|
||||
)
|
||||
|
||||
# Define our template tokens
|
||||
@ -184,6 +245,10 @@ class NotifyOpsgenie(NotifyBase):
|
||||
'private': True,
|
||||
'required': True,
|
||||
},
|
||||
'user': {
|
||||
'name': _('Username'),
|
||||
'type': 'string',
|
||||
},
|
||||
'target_escalation': {
|
||||
'name': _('Target Escalation'),
|
||||
'prefix': '^',
|
||||
@ -249,6 +314,12 @@ class NotifyOpsgenie(NotifyBase):
|
||||
'to': {
|
||||
'alias_of': 'targets',
|
||||
},
|
||||
'action': {
|
||||
'name': _('Action'),
|
||||
'type': 'choice:string',
|
||||
'values': OPSGENIE_ACTIONS,
|
||||
'default': OPSGENIE_ACTIONS[0],
|
||||
}
|
||||
})
|
||||
|
||||
# Map of key-value pairs to use as custom properties of the alert.
|
||||
@ -257,11 +328,15 @@ class NotifyOpsgenie(NotifyBase):
|
||||
'name': _('Details'),
|
||||
'prefix': '+',
|
||||
},
|
||||
'mapping': {
|
||||
'name': _('Action Mapping'),
|
||||
'prefix': ':',
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self, apikey, targets, region_name=None, details=None,
|
||||
priority=None, alias=None, entity=None, batch=False,
|
||||
tags=None, **kwargs):
|
||||
tags=None, action=None, mapping=None, **kwargs):
|
||||
"""
|
||||
Initialize Opsgenie Object
|
||||
"""
|
||||
@ -298,6 +373,41 @@ class NotifyOpsgenie(NotifyBase):
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
if action and isinstance(action, str):
|
||||
self.action = next(
|
||||
(a for a in OPSGENIE_ACTIONS if a.startswith(action)), None)
|
||||
if self.action not in OPSGENIE_ACTIONS:
|
||||
msg = 'The Opsgenie action specified ({}) is invalid.'\
|
||||
.format(action)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
else:
|
||||
self.action = self.template_args['action']['default']
|
||||
|
||||
# Store our mappings
|
||||
self.mapping = self.opsgenie_message_map.copy()
|
||||
if mapping and isinstance(mapping, dict):
|
||||
for _k, _v in mapping.items():
|
||||
# Get our mapping
|
||||
k = next((t for t in NOTIFY_TYPES if t.startswith(_k)), None)
|
||||
if not k:
|
||||
msg = 'The Opsgenie mapping key specified ({}) ' \
|
||||
'is invalid.'.format(_k)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
_v_lower = _v.lower()
|
||||
v = next((v for v in OPSGENIE_ACTIONS[1:]
|
||||
if v.startswith(_v_lower)), None)
|
||||
if not v:
|
||||
msg = 'The Opsgenie mapping value (assigned to {}) ' \
|
||||
'specified ({}) is invalid.'.format(k, _v)
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
|
||||
# Update our mapping
|
||||
self.mapping[k] = v
|
||||
|
||||
self.details = {}
|
||||
if details:
|
||||
# Store our extra details
|
||||
@ -367,115 +477,234 @@ class NotifyOpsgenie(NotifyBase):
|
||||
if is_uuid(target) else
|
||||
{'type': OpsgenieCategory.USER, 'username': target})
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
def _fetch(self, method, url, payload, params=None):
|
||||
"""
|
||||
Perform Opsgenie Notification
|
||||
Performs server retrieval/update and returns JSON Response
|
||||
"""
|
||||
|
||||
headers = {
|
||||
'User-Agent': self.app_id,
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'GenieKey {}'.format(self.apikey),
|
||||
}
|
||||
|
||||
# Some Debug Logging
|
||||
self.logger.debug(
|
||||
'Opsgenie POST URL: {} (cert_verify={})'.format(
|
||||
url, self.verify_certificate))
|
||||
self.logger.debug('Opsgenie Payload: {}' .format(payload))
|
||||
|
||||
# Initialize our response object
|
||||
content = {}
|
||||
|
||||
# Always call throttle before any remote server i/o is made
|
||||
self.throttle()
|
||||
try:
|
||||
r = method(
|
||||
url,
|
||||
data=dumps(payload),
|
||||
params=params,
|
||||
headers=headers,
|
||||
verify=self.verify_certificate,
|
||||
timeout=self.request_timeout,
|
||||
)
|
||||
|
||||
# A Response might look like:
|
||||
# {
|
||||
# "result": "Request will be processed",
|
||||
# "took": 0.302,
|
||||
# "requestId": "43a29c5c-3dbf-4fa4-9c26-f4f71023e120"
|
||||
# }
|
||||
|
||||
try:
|
||||
# Update our response object
|
||||
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.accepted, requests.codes.ok):
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(
|
||||
r.status_code)
|
||||
|
||||
self.logger.warning(
|
||||
'Failed to send Opsgenie notification:'
|
||||
'{}{}error={}.'.format(
|
||||
status_str,
|
||||
', ' if status_str else '',
|
||||
r.status_code))
|
||||
|
||||
self.logger.debug(
|
||||
'Response Details:\r\n{}'.format(r.content))
|
||||
|
||||
return (False, content.get('requestId'))
|
||||
|
||||
# If we reach here; the message was sent
|
||||
self.logger.info('Sent Opsgenie notification')
|
||||
self.logger.debug(
|
||||
'Response Details:\r\n{}'.format(r.content))
|
||||
|
||||
return (True, content.get('requestId'))
|
||||
|
||||
except requests.RequestException as e:
|
||||
self.logger.warning(
|
||||
'A Connection error occurred sending Opsgenie '
|
||||
'notification.')
|
||||
self.logger.debug('Socket Exception: %s' % str(e))
|
||||
|
||||
return (False, content.get('requestId'))
|
||||
|
||||
def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
|
||||
"""
|
||||
Perform Opsgenie Notification
|
||||
"""
|
||||
|
||||
# Get our Opsgenie Action
|
||||
action = OPSGENIE_ALERT_MAP[notify_type] \
|
||||
if self.action == OpsgenieAlertAction.MAP else self.action
|
||||
|
||||
# Prepare our URL as it's based on our hostname
|
||||
notify_url = OPSGENIE_API_LOOKUP[self.region_name]
|
||||
|
||||
# Initialize our has_error flag
|
||||
has_error = False
|
||||
|
||||
# Use body if title not set
|
||||
title_body = body if not title else title
|
||||
# Default method is to post
|
||||
method = requests.post
|
||||
|
||||
# Create a copy ouf our details object
|
||||
details = self.details.copy()
|
||||
if 'type' not in details:
|
||||
details['type'] = notify_type
|
||||
# For indexing in persistent store
|
||||
key = hashlib.sha1(
|
||||
(self.entity if self.entity else (
|
||||
self.alias if self.alias else (
|
||||
title if title else self.app_id)))
|
||||
.encode('utf-8')).hexdigest()[0:10]
|
||||
|
||||
# Prepare our payload
|
||||
payload = {
|
||||
'source': self.app_desc,
|
||||
'message': title_body,
|
||||
'description': body,
|
||||
'details': details,
|
||||
'priority': 'P{}'.format(self.priority),
|
||||
}
|
||||
# Get our Opsgenie Request IDs
|
||||
request_ids = self.store.get(key, [])
|
||||
if not isinstance(request_ids, list):
|
||||
request_ids = []
|
||||
|
||||
# Use our body directive if we exceed the minimum message
|
||||
# limitation
|
||||
if len(payload['message']) > self.opsgenie_body_minlen:
|
||||
payload['message'] = '{}...'.format(
|
||||
title_body[:self.opsgenie_body_minlen - 3])
|
||||
if action == OpsgenieAlertAction.NEW:
|
||||
# Create a copy ouf our details object
|
||||
details = self.details.copy()
|
||||
if 'type' not in details:
|
||||
details['type'] = notify_type
|
||||
|
||||
if self.__tags:
|
||||
payload['tags'] = self.__tags
|
||||
# Use body if title not set
|
||||
title_body = body if not title else title
|
||||
|
||||
if self.entity:
|
||||
payload['entity'] = self.entity
|
||||
# Prepare our payload
|
||||
payload = {
|
||||
'source': self.app_desc,
|
||||
'message': title_body,
|
||||
'description': body,
|
||||
'details': details,
|
||||
'priority': 'P{}'.format(self.priority),
|
||||
}
|
||||
|
||||
if self.alias:
|
||||
payload['alias'] = self.alias
|
||||
# Use our body directive if we exceed the minimum message
|
||||
# limitation
|
||||
if len(payload['message']) > self.opsgenie_body_minlen:
|
||||
payload['message'] = '{}...'.format(
|
||||
title_body[:self.opsgenie_body_minlen - 3])
|
||||
|
||||
length = len(self.targets) if self.targets else 1
|
||||
for index in range(0, length, self.batch_size):
|
||||
if self.targets:
|
||||
# If there were no targets identified, then we simply
|
||||
# just iterate once without the responders set
|
||||
payload['responders'] = \
|
||||
self.targets[index:index + self.batch_size]
|
||||
if self.__tags:
|
||||
payload['tags'] = self.__tags
|
||||
|
||||
# Some Debug Logging
|
||||
self.logger.debug(
|
||||
'Opsgenie POST URL: {} (cert_verify={})'.format(
|
||||
notify_url, self.verify_certificate))
|
||||
self.logger.debug('Opsgenie Payload: {}' .format(payload))
|
||||
if self.entity:
|
||||
payload['entity'] = self.entity
|
||||
|
||||
# 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 self.alias:
|
||||
payload['alias'] = self.alias
|
||||
|
||||
if r.status_code not in (
|
||||
requests.codes.accepted, requests.codes.ok):
|
||||
status_str = \
|
||||
NotifyBase.http_response_code_lookup(
|
||||
r.status_code)
|
||||
if self.user:
|
||||
payload['user'] = self.user
|
||||
|
||||
self.logger.warning(
|
||||
'Failed to send Opsgenie notification:'
|
||||
'{}{}error={}.'.format(
|
||||
status_str,
|
||||
', ' if status_str else '',
|
||||
r.status_code))
|
||||
# reset our request IDs - we will re-populate them
|
||||
request_ids = []
|
||||
|
||||
self.logger.debug(
|
||||
'Response Details:\r\n{}'.format(r.content))
|
||||
length = len(self.targets) if self.targets else 1
|
||||
for index in range(0, length, self.batch_size):
|
||||
if self.targets:
|
||||
# If there were no targets identified, then we simply
|
||||
# just iterate once without the responders set
|
||||
payload['responders'] = \
|
||||
self.targets[index:index + self.batch_size]
|
||||
|
||||
# Mark our failure
|
||||
# Perform our post
|
||||
success, request_id = self._fetch(
|
||||
method, notify_url, payload)
|
||||
|
||||
if success and request_id:
|
||||
# Save our response
|
||||
request_ids.append(request_id)
|
||||
|
||||
else:
|
||||
has_error = True
|
||||
continue
|
||||
|
||||
# If we reach here; the message was sent
|
||||
self.logger.info('Sent Opsgenie notification')
|
||||
self.logger.debug(
|
||||
'Response Details:\r\n{}'.format(r.content))
|
||||
# Store our entries for a maximum of 60 days
|
||||
self.store.set(key, request_ids, expires=60 * 60 * 24 * 60)
|
||||
|
||||
except requests.RequestException as e:
|
||||
self.logger.warning(
|
||||
'A Connection error occurred sending Opsgenie '
|
||||
'notification.')
|
||||
self.logger.debug('Socket Exception: %s' % str(e))
|
||||
# Mark our failure
|
||||
has_error = True
|
||||
elif request_ids:
|
||||
# Prepare our payload
|
||||
payload = {
|
||||
'source': self.app_desc,
|
||||
'note': body,
|
||||
}
|
||||
|
||||
if self.user:
|
||||
payload['user'] = self.user
|
||||
|
||||
# Prepare our Identifier type
|
||||
params = {
|
||||
'identifierType': 'id',
|
||||
}
|
||||
|
||||
for request_id in request_ids:
|
||||
if action == OpsgenieAlertAction.DELETE:
|
||||
# Update our URL
|
||||
url = f'{notify_url}/{request_id}'
|
||||
method = requests.delete
|
||||
|
||||
elif action == OpsgenieAlertAction.ACKNOWLEDGE:
|
||||
url = f'{notify_url}/{request_id}/acknowledge'
|
||||
|
||||
elif action == OpsgenieAlertAction.CLOSE:
|
||||
url = f'{notify_url}/{request_id}/close'
|
||||
|
||||
else: # action == OpsgenieAlertAction.CLOSE:
|
||||
url = f'{notify_url}/{request_id}/notes'
|
||||
|
||||
# Perform our post
|
||||
success, _ = self._fetch(method, url, payload, params)
|
||||
|
||||
if not success:
|
||||
has_error = True
|
||||
|
||||
if not has_error and action == OpsgenieAlertAction.DELETE:
|
||||
# Remove cached entry
|
||||
self.store.clear(key)
|
||||
|
||||
else:
|
||||
self.logger.info(
|
||||
'No Opsgenie notification sent due to (nothing to %s) '
|
||||
'condition', self.action)
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.region_name, self.apikey)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
@ -483,6 +712,7 @@ class NotifyOpsgenie(NotifyBase):
|
||||
|
||||
# Define any URL parameters
|
||||
params = {
|
||||
'action': self.action,
|
||||
'region': self.region_name,
|
||||
'priority':
|
||||
OPSGENIE_PRIORITIES[self.template_args['priority']['default']]
|
||||
@ -506,6 +736,10 @@ class NotifyOpsgenie(NotifyBase):
|
||||
# Append our details into our parameters
|
||||
params.update({'+{}'.format(k): v for k, v in self.details.items()})
|
||||
|
||||
# Append our assignment extra's into our parameters
|
||||
params.update(
|
||||
{':{}'.format(k): v for k, v in self.mapping.items()})
|
||||
|
||||
# Extend our parameters
|
||||
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
|
||||
|
||||
@ -522,8 +756,9 @@ class NotifyOpsgenie(NotifyBase):
|
||||
NotifyOpsgenie.template_tokens['target_team']['prefix'],
|
||||
}
|
||||
|
||||
return '{schema}://{apikey}/{targets}/?{params}'.format(
|
||||
return '{schema}://{user}{apikey}/{targets}/?{params}'.format(
|
||||
schema=self.secure_protocol,
|
||||
user='{}@'.format(self.user) if self.user else '',
|
||||
apikey=self.pprint(self.apikey, privacy, safe=''),
|
||||
targets='/'.join(
|
||||
[NotifyOpsgenie.quote('{}{}'.format(
|
||||
@ -608,4 +843,14 @@ class NotifyOpsgenie(NotifyBase):
|
||||
if 'to' in results['qsd'] and len(results['qsd']['to']):
|
||||
results['targets'].append(results['qsd']['to'])
|
||||
|
||||
# Store our action (if defined)
|
||||
if 'action' in results['qsd'] and len(results['qsd']['action']):
|
||||
results['action'] = \
|
||||
NotifyOpsgenie.unquote(results['qsd']['action'])
|
||||
|
||||
# store any custom mapping defined
|
||||
results['mapping'] = \
|
||||
{NotifyOpsgenie.unquote(x): NotifyOpsgenie.unquote(y)
|
||||
for x, y in results['qsd:'].items()}
|
||||
|
||||
return results
|
||||
|
@ -412,6 +412,18 @@ class NotifyPagerDuty(NotifyBase):
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol, self.integration_key, self.apikey,
|
||||
self.source,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -299,6 +299,15 @@ class NotifyPagerTree(NotifyBase):
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.integration)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -257,6 +257,19 @@ class NotifyParsePlatform(NotifyBase):
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol if self.secure else self.protocol,
|
||||
self.application_id, self.master_key, self.host, self.port,
|
||||
self.fullpath,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -242,6 +242,15 @@ class NotifyPopcornNotify(NotifyBase):
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.apikey)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -250,6 +250,15 @@ class NotifyProwl(NotifyBase):
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.apikey, self.providerkey)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -386,6 +386,15 @@ class NotifyPushBullet(NotifyBase):
|
||||
if files:
|
||||
files['file'][1].close()
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.accesstoken)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -180,6 +180,18 @@ class NotifyPushDeer(NotifyBase):
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol if self.secure else self.protocol,
|
||||
self.push_key, self.host, self.port,
|
||||
)
|
||||
|
||||
def url(self, privacy=False):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -303,6 +303,15 @@ class NotifyPushed(NotifyBase):
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.app_key, self.app_secret)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -117,6 +117,18 @@ class NotifyPushjet(NotifyBase):
|
||||
|
||||
return
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol if self.secure else self.protocol,
|
||||
self.user, self.password, self.host, self.port, self.secret_key,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -171,6 +171,15 @@ class NotifyPushMe(NotifyBase):
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.token)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -549,6 +549,15 @@ class NotifyPushover(NotifyBase):
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.user_key, self.token)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -756,6 +756,18 @@ class NotifyPushSafer(NotifyBase):
|
||||
|
||||
return False, response
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol if self.secure else self.protocol,
|
||||
self.privatekey,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -310,6 +310,15 @@ class NotifyPushy(NotifyBase):
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.apikey)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -324,6 +324,18 @@ class NotifyReddit(NotifyBase):
|
||||
|
||||
return
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol, self.client_id, self.client_secret,
|
||||
self.user, self.password,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -354,6 +354,15 @@ class NotifyRevolt(NotifyBase):
|
||||
|
||||
return (True, content)
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.bot_token)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -319,6 +319,23 @@ class NotifyRocketChat(NotifyBase):
|
||||
|
||||
return
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol if self.secure else self.protocol,
|
||||
self.host,
|
||||
self.port if self.port else (443 if self.secure else 80),
|
||||
self.user,
|
||||
self.password if self.mode in (
|
||||
RocketChatAuthMode.BASIC, RocketChatAuthMode.TOKEN)
|
||||
else self.webhook,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -300,6 +300,19 @@ class NotifyRSyslog(NotifyBase):
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.protocol, self.host,
|
||||
self.port if self.port
|
||||
else self.template_tokens['port']['default'],
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -272,6 +272,15 @@ class NotifyRyver(NotifyBase):
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.organization, self.token)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -243,6 +243,15 @@ class NotifySendGrid(NotifyBase):
|
||||
|
||||
return
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.apikey, self.from_email)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -149,6 +149,15 @@ class NotifyServerChan(NotifyBase):
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.token)
|
||||
|
||||
def url(self, privacy=False):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -770,6 +770,18 @@ class NotifySES(NotifyBase):
|
||||
|
||||
return response
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol, self.from_addr, self.aws_access_key_id,
|
||||
self.aws_secret_access_key, self.aws_region_name,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -356,6 +356,18 @@ class NotifySFR(NotifyBase):
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol if self.secure else self.protocol,
|
||||
self.user, self.password, self.space_id,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -372,6 +372,18 @@ class NotifySignalAPI(NotifyBase):
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol if self.secure else self.protocol,
|
||||
self.user, self.password, self.host, self.port, self.source,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -283,6 +283,15 @@ class NotifySimplePush(NotifyBase):
|
||||
|
||||
return True
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.user, self.password, self.apikey)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -381,6 +381,18 @@ class NotifySinch(NotifyBase):
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol if self.secure else self.protocol,
|
||||
self.service_plan_id, self.api_token, self.source,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -326,6 +326,7 @@ class NotifySlack(NotifyBase):
|
||||
self.mode = SlackMode.BOT if access_token else SlackMode.WEBHOOK
|
||||
|
||||
if self.mode is SlackMode.WEBHOOK:
|
||||
self.access_token = None
|
||||
self.token_a = validate_regex(
|
||||
token_a, *self.template_tokens['token_a']['regex'])
|
||||
if not self.token_a:
|
||||
@ -350,6 +351,9 @@ class NotifySlack(NotifyBase):
|
||||
self.logger.warning(msg)
|
||||
raise TypeError(msg)
|
||||
else:
|
||||
self.token_a = None
|
||||
self.token_b = None
|
||||
self.token_c = None
|
||||
self.access_token = validate_regex(
|
||||
access_token, *self.template_tokens['access_token']['regex'])
|
||||
if not self.access_token:
|
||||
@ -1018,6 +1022,18 @@ class NotifySlack(NotifyBase):
|
||||
# Return the response for processing
|
||||
return response
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol, self.token_a, self.token_b, self.token_c,
|
||||
self.access_token,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -564,6 +564,18 @@ class NotifySMSEagle(NotifyBase):
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol if self.secure else self.protocol,
|
||||
self.token, self.host, self.port,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -314,6 +314,15 @@ class NotifySMSManager(NotifyBase):
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol[0], self.apikey)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -464,6 +464,15 @@ class NotifySMTP2Go(NotifyBase):
|
||||
|
||||
return not has_error
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (self.secure_protocol, self.user, self.host, self.apikey)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
@ -573,6 +573,18 @@ class NotifySNS(NotifyBase):
|
||||
|
||||
return response
|
||||
|
||||
@property
|
||||
def url_identifier(self):
|
||||
"""
|
||||
Returns all of the identifiers that make this URL unique from
|
||||
another simliar one. Targets or end points should never be identified
|
||||
here.
|
||||
"""
|
||||
return (
|
||||
self.secure_protocol, self.aws_access_key_id,
|
||||
self.aws_secret_access_key, self.aws_region_name,
|
||||
)
|
||||
|
||||
def url(self, privacy=False, *args, **kwargs):
|
||||
"""
|
||||
Returns the URL built dynamically based on specified arguments.
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user