mirror of
https://github.com/caronc/apprise.git
synced 2025-06-27 13:11:36 +02:00
Added attach-as option to form:// for upstream filename over-ride (#827)
This commit is contained in:
parent
f7cc732c31
commit
704f7db53a
@ -30,6 +30,7 @@
|
|||||||
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
# POSSIBILITY OF SUCH DAMAGE.
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
import re
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from .NotifyBase import NotifyBase
|
from .NotifyBase import NotifyBase
|
||||||
@ -45,7 +46,8 @@ METHODS = (
|
|||||||
'GET',
|
'GET',
|
||||||
'DELETE',
|
'DELETE',
|
||||||
'PUT',
|
'PUT',
|
||||||
'HEAD'
|
'HEAD',
|
||||||
|
'PATCH'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -54,6 +56,27 @@ class NotifyForm(NotifyBase):
|
|||||||
A wrapper for Form Notifications
|
A wrapper for Form Notifications
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# Support
|
||||||
|
# - file*
|
||||||
|
# - file?
|
||||||
|
# - file*name
|
||||||
|
# - file?name
|
||||||
|
# - ?file
|
||||||
|
# - *file
|
||||||
|
# - file
|
||||||
|
# The code will convert the ? or * to the digit increments
|
||||||
|
__attach_as_re = re.compile(
|
||||||
|
r'((?P<match1>(?P<id1a>[a-z0-9_-]+)?'
|
||||||
|
r'(?P<wc1>[*?+$:.%]+)(?P<id1b>[a-z0-9_-]+))'
|
||||||
|
r'|(?P<match2>(?P<id2>[a-z0-9_-]+)(?P<wc2>[*?+$:.%]?)))',
|
||||||
|
re.IGNORECASE)
|
||||||
|
|
||||||
|
# Our count
|
||||||
|
attach_as_count = '{:02d}'
|
||||||
|
|
||||||
|
# the default attach_as value
|
||||||
|
attach_as_default = f'file{attach_as_count}'
|
||||||
|
|
||||||
# The default descriptive name associated with the Notification
|
# The default descriptive name associated with the Notification
|
||||||
service_name = 'Form'
|
service_name = 'Form'
|
||||||
|
|
||||||
@ -118,6 +141,12 @@ class NotifyForm(NotifyBase):
|
|||||||
'values': METHODS,
|
'values': METHODS,
|
||||||
'default': METHODS[0],
|
'default': METHODS[0],
|
||||||
},
|
},
|
||||||
|
'attach-as': {
|
||||||
|
'name': _('Attach File As'),
|
||||||
|
'type': 'string',
|
||||||
|
'default': 'file*',
|
||||||
|
'map_to': 'attach_as',
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
# Define any kwargs we're using
|
# Define any kwargs we're using
|
||||||
@ -137,7 +166,7 @@ class NotifyForm(NotifyBase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, headers=None, method=None, payload=None, params=None,
|
def __init__(self, headers=None, method=None, payload=None, params=None,
|
||||||
**kwargs):
|
attach_as=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize Form Object
|
Initialize Form Object
|
||||||
|
|
||||||
@ -159,6 +188,36 @@ class NotifyForm(NotifyBase):
|
|||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
# Custom File Attachment Over-Ride Support
|
||||||
|
if not isinstance(attach_as, str):
|
||||||
|
# Default value
|
||||||
|
self.attach_as = self.attach_as_default
|
||||||
|
self.attach_multi_support = True
|
||||||
|
|
||||||
|
else:
|
||||||
|
result = self.__attach_as_re.match(attach_as.strip())
|
||||||
|
if not result:
|
||||||
|
msg = 'The attach-as specified ({}) is invalid.'.format(
|
||||||
|
attach_as)
|
||||||
|
self.logger.warning(msg)
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
|
self.attach_as = ''
|
||||||
|
self.attach_multi_support = False
|
||||||
|
if result.group('match1'):
|
||||||
|
if result.group('id1a'):
|
||||||
|
self.attach_as += result.group('id1a')
|
||||||
|
|
||||||
|
self.attach_as += self.attach_as_count
|
||||||
|
self.attach_multi_support = True
|
||||||
|
self.attach_as += result.group('id1b')
|
||||||
|
|
||||||
|
else: # result.group('match2'):
|
||||||
|
self.attach_as += result.group('id2')
|
||||||
|
if result.group('wc2'):
|
||||||
|
self.attach_as += self.attach_as_count
|
||||||
|
self.attach_multi_support = True
|
||||||
|
|
||||||
self.params = {}
|
self.params = {}
|
||||||
if params:
|
if params:
|
||||||
# Store our extra headers
|
# Store our extra headers
|
||||||
@ -199,6 +258,10 @@ class NotifyForm(NotifyBase):
|
|||||||
params.update(
|
params.update(
|
||||||
{':{}'.format(k): v for k, v in self.payload_extras.items()})
|
{':{}'.format(k): v for k, v in self.payload_extras.items()})
|
||||||
|
|
||||||
|
if self.attach_as != self.attach_as_default:
|
||||||
|
# Provide Attach-As extension details
|
||||||
|
params['attach-as'] = self.attach_as
|
||||||
|
|
||||||
# Determine Authentication
|
# Determine Authentication
|
||||||
auth = ''
|
auth = ''
|
||||||
if self.user and self.password:
|
if self.user and self.password:
|
||||||
@ -254,7 +317,8 @@ class NotifyForm(NotifyBase):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
files.append((
|
files.append((
|
||||||
'file{:02d}'.format(no), (
|
self.attach_as.format(no)
|
||||||
|
if self.attach_multi_support else self.attach_as, (
|
||||||
attachment.name,
|
attachment.name,
|
||||||
open(attachment.path, 'rb'),
|
open(attachment.path, 'rb'),
|
||||||
attachment.mimetype)
|
attachment.mimetype)
|
||||||
@ -267,6 +331,11 @@ class NotifyForm(NotifyBase):
|
|||||||
self.logger.debug('I/O Exception: %s' % str(e))
|
self.logger.debug('I/O Exception: %s' % str(e))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if not self.attach_multi_support and no > 1:
|
||||||
|
self.logger.warning(
|
||||||
|
'Multiple attachments provided while '
|
||||||
|
'form:// Multi-Attachment Support not enabled')
|
||||||
|
|
||||||
# prepare Form Object
|
# prepare Form Object
|
||||||
payload = {
|
payload = {
|
||||||
# Version: Major.Minor, Major is only updated if the entire
|
# Version: Major.Minor, Major is only updated if the entire
|
||||||
@ -309,6 +378,9 @@ class NotifyForm(NotifyBase):
|
|||||||
elif self.method == 'PUT':
|
elif self.method == 'PUT':
|
||||||
method = requests.put
|
method = requests.put
|
||||||
|
|
||||||
|
elif self.method == 'PATCH':
|
||||||
|
method = requests.patch
|
||||||
|
|
||||||
elif self.method == 'DELETE':
|
elif self.method == 'DELETE':
|
||||||
method = requests.delete
|
method = requests.delete
|
||||||
|
|
||||||
@ -397,6 +469,12 @@ class NotifyForm(NotifyBase):
|
|||||||
results['params'] = {NotifyForm.unquote(x): NotifyForm.unquote(y)
|
results['params'] = {NotifyForm.unquote(x): NotifyForm.unquote(y)
|
||||||
for x, y in results['qsd-'].items()}
|
for x, y in results['qsd-'].items()}
|
||||||
|
|
||||||
|
# Allow Attach-As Support which over-rides the name of the filename
|
||||||
|
# posted with the form://
|
||||||
|
# the default is file01, file02, file03, etc
|
||||||
|
if 'attach-as' in results['qsd'] and len(results['qsd']['attach-as']):
|
||||||
|
results['attach_as'] = results['qsd']['attach-as']
|
||||||
|
|
||||||
# Set method if not otherwise set
|
# Set method if not otherwise set
|
||||||
if 'method' in results['qsd'] and len(results['qsd']['method']):
|
if 'method' in results['qsd'] and len(results['qsd']['method']):
|
||||||
results['method'] = NotifyForm.unquote(results['qsd']['method'])
|
results['method'] = NotifyForm.unquote(results['qsd']['method'])
|
||||||
|
@ -47,7 +47,8 @@ METHODS = (
|
|||||||
'GET',
|
'GET',
|
||||||
'DELETE',
|
'DELETE',
|
||||||
'PUT',
|
'PUT',
|
||||||
'HEAD'
|
'HEAD',
|
||||||
|
'PATCH'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -315,6 +316,9 @@ class NotifyJSON(NotifyBase):
|
|||||||
elif self.method == 'PUT':
|
elif self.method == 'PUT':
|
||||||
method = requests.put
|
method = requests.put
|
||||||
|
|
||||||
|
elif self.method == 'PATCH':
|
||||||
|
method = requests.patch
|
||||||
|
|
||||||
elif self.method == 'DELETE':
|
elif self.method == 'DELETE':
|
||||||
method = requests.delete
|
method = requests.delete
|
||||||
|
|
||||||
|
@ -316,12 +316,7 @@ class NotifyVoipms(NotifyBase):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Define any URL parameters
|
# Define any URL parameters
|
||||||
params = {
|
params = self.url_parameters(privacy=privacy, *args, **kwargs)
|
||||||
'method': 'sendSMS'
|
|
||||||
}
|
|
||||||
|
|
||||||
# Extend our parameters
|
|
||||||
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
|
|
||||||
|
|
||||||
schemaStr = \
|
schemaStr = \
|
||||||
'{schema}://{password}:{email}/{from_phone}/{targets}/?{params}'
|
'{schema}://{password}:{email}/{from_phone}/{targets}/?{params}'
|
||||||
|
@ -47,7 +47,8 @@ METHODS = (
|
|||||||
'GET',
|
'GET',
|
||||||
'DELETE',
|
'DELETE',
|
||||||
'PUT',
|
'PUT',
|
||||||
'HEAD'
|
'HEAD',
|
||||||
|
'PATCH'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -367,6 +368,9 @@ class NotifyXML(NotifyBase):
|
|||||||
elif self.method == 'PUT':
|
elif self.method == 'PUT':
|
||||||
method = requests.put
|
method = requests.put
|
||||||
|
|
||||||
|
elif self.method == 'PATCH':
|
||||||
|
method = requests.patch
|
||||||
|
|
||||||
elif self.method == 'DELETE':
|
elif self.method == 'DELETE':
|
||||||
method = requests.delete
|
method = requests.delete
|
||||||
|
|
||||||
|
@ -268,8 +268,9 @@ class AppriseURLTester:
|
|||||||
@mock.patch('requests.head')
|
@mock.patch('requests.head')
|
||||||
@mock.patch('requests.put')
|
@mock.patch('requests.put')
|
||||||
@mock.patch('requests.delete')
|
@mock.patch('requests.delete')
|
||||||
def __notify(self, url, obj, meta, asset, mock_del, mock_put, mock_head,
|
@mock.patch('requests.patch')
|
||||||
mock_post, mock_get):
|
def __notify(self, url, obj, meta, asset, mock_patch, mock_del, mock_put,
|
||||||
|
mock_head, mock_post, mock_get):
|
||||||
"""
|
"""
|
||||||
Perform notification testing against object specified
|
Perform notification testing against object specified
|
||||||
"""
|
"""
|
||||||
@ -326,6 +327,7 @@ class AppriseURLTester:
|
|||||||
mock_get.return_value = robj
|
mock_get.return_value = robj
|
||||||
mock_post.return_value = robj
|
mock_post.return_value = robj
|
||||||
mock_head.return_value = robj
|
mock_head.return_value = robj
|
||||||
|
mock_patch.return_value = robj
|
||||||
mock_del.return_value = robj
|
mock_del.return_value = robj
|
||||||
mock_put.return_value = robj
|
mock_put.return_value = robj
|
||||||
|
|
||||||
@ -336,6 +338,7 @@ class AppriseURLTester:
|
|||||||
mock_del.return_value.status_code = requests_response_code
|
mock_del.return_value.status_code = requests_response_code
|
||||||
mock_post.return_value.status_code = requests_response_code
|
mock_post.return_value.status_code = requests_response_code
|
||||||
mock_get.return_value.status_code = requests_response_code
|
mock_get.return_value.status_code = requests_response_code
|
||||||
|
mock_patch.return_value.status_code = requests_response_code
|
||||||
|
|
||||||
# Handle our default text response
|
# Handle our default text response
|
||||||
mock_get.return_value.content = requests_response_content
|
mock_get.return_value.content = requests_response_content
|
||||||
@ -343,12 +346,14 @@ class AppriseURLTester:
|
|||||||
mock_del.return_value.content = requests_response_content
|
mock_del.return_value.content = requests_response_content
|
||||||
mock_put.return_value.content = requests_response_content
|
mock_put.return_value.content = requests_response_content
|
||||||
mock_head.return_value.content = requests_response_content
|
mock_head.return_value.content = requests_response_content
|
||||||
|
mock_patch.return_value.content = requests_response_content
|
||||||
|
|
||||||
mock_get.return_value.text = requests_response_text
|
mock_get.return_value.text = requests_response_text
|
||||||
mock_post.return_value.text = requests_response_text
|
mock_post.return_value.text = requests_response_text
|
||||||
mock_put.return_value.text = requests_response_text
|
mock_put.return_value.text = requests_response_text
|
||||||
mock_del.return_value.text = requests_response_text
|
mock_del.return_value.text = requests_response_text
|
||||||
mock_head.return_value.text = requests_response_text
|
mock_head.return_value.text = requests_response_text
|
||||||
|
mock_patch.return_value.text = requests_response_text
|
||||||
|
|
||||||
# Ensure there is no side effect set
|
# Ensure there is no side effect set
|
||||||
mock_post.side_effect = None
|
mock_post.side_effect = None
|
||||||
@ -356,6 +361,7 @@ class AppriseURLTester:
|
|||||||
mock_put.side_effect = None
|
mock_put.side_effect = None
|
||||||
mock_head.side_effect = None
|
mock_head.side_effect = None
|
||||||
mock_get.side_effect = None
|
mock_get.side_effect = None
|
||||||
|
mock_patch.side_effect = None
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Handle exception testing; first we turn the boolean flag
|
# Handle exception testing; first we turn the boolean flag
|
||||||
@ -454,6 +460,7 @@ class AppriseURLTester:
|
|||||||
mock_del.side_effect = _exception
|
mock_del.side_effect = _exception
|
||||||
mock_put.side_effect = _exception
|
mock_put.side_effect = _exception
|
||||||
mock_get.side_effect = _exception
|
mock_get.side_effect = _exception
|
||||||
|
mock_patch.side_effect = _exception
|
||||||
|
|
||||||
try:
|
try:
|
||||||
assert obj.notify(
|
assert obj.notify(
|
||||||
@ -498,6 +505,7 @@ class AppriseURLTester:
|
|||||||
mock_put.side_effect = _exception
|
mock_put.side_effect = _exception
|
||||||
mock_head.side_effect = _exception
|
mock_head.side_effect = _exception
|
||||||
mock_get.side_effect = _exception
|
mock_get.side_effect = _exception
|
||||||
|
mock_patch.side_effect = _exception
|
||||||
|
|
||||||
try:
|
try:
|
||||||
assert obj.notify(
|
assert obj.notify(
|
||||||
|
@ -91,6 +91,9 @@ apprise_url_tests = (
|
|||||||
('form://user@localhost?method=delete', {
|
('form://user@localhost?method=delete', {
|
||||||
'instance': NotifyForm,
|
'instance': NotifyForm,
|
||||||
}),
|
}),
|
||||||
|
('form://user@localhost?method=patch', {
|
||||||
|
'instance': NotifyForm,
|
||||||
|
}),
|
||||||
|
|
||||||
# Custom payload options
|
# Custom payload options
|
||||||
('form://localhost:8080?:key=value&:key2=value2', {
|
('form://localhost:8080?:key=value&:key2=value2', {
|
||||||
@ -230,6 +233,73 @@ def test_plugin_custom_form_attachments(mock_post):
|
|||||||
body='body', title='title', notify_type=NotifyType.INFO,
|
body='body', title='title', notify_type=NotifyType.INFO,
|
||||||
attach=attach) is False
|
attach=attach) is False
|
||||||
|
|
||||||
|
#
|
||||||
|
# Test attach-as
|
||||||
|
#
|
||||||
|
|
||||||
|
# Assign our mock object our return value
|
||||||
|
mock_post.return_value = okay_response
|
||||||
|
mock_post.side_effect = None
|
||||||
|
|
||||||
|
obj = Apprise.instantiate(
|
||||||
|
'form://user@localhost.localdomain/?attach-as=file')
|
||||||
|
assert isinstance(obj, NotifyForm)
|
||||||
|
|
||||||
|
# Test Single Valid Attachment
|
||||||
|
path = os.path.join(TEST_VAR_DIR, 'apprise-test.gif')
|
||||||
|
attach = AppriseAttachment(path)
|
||||||
|
assert obj.notify(
|
||||||
|
body='body', title='title', notify_type=NotifyType.INFO,
|
||||||
|
attach=attach) is True
|
||||||
|
|
||||||
|
# Test Valid Attachment (load 3) (produces a warning)
|
||||||
|
path = (
|
||||||
|
os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
|
||||||
|
os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
|
||||||
|
os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
|
||||||
|
)
|
||||||
|
attach = AppriseAttachment(path)
|
||||||
|
assert obj.notify(
|
||||||
|
body='body', title='title', notify_type=NotifyType.INFO,
|
||||||
|
attach=attach) is True
|
||||||
|
|
||||||
|
# Test our other variations of accepted values
|
||||||
|
# we support *, :, ?, ., +, %, and $
|
||||||
|
for attach_as in (
|
||||||
|
'file*', '*file', 'file*file',
|
||||||
|
'file:', ':file', 'file:file',
|
||||||
|
'file?', '?file', 'file?file',
|
||||||
|
'file.', '.file', 'file.file',
|
||||||
|
'file+', '+file', 'file+file',
|
||||||
|
'file$', '$file', 'file$file'):
|
||||||
|
|
||||||
|
obj = Apprise.instantiate(
|
||||||
|
f'form://user@localhost.localdomain/?attach-as={attach_as}')
|
||||||
|
assert isinstance(obj, NotifyForm)
|
||||||
|
|
||||||
|
# Test Single Valid Attachment
|
||||||
|
path = os.path.join(TEST_VAR_DIR, 'apprise-test.gif')
|
||||||
|
attach = AppriseAttachment(path)
|
||||||
|
assert obj.notify(
|
||||||
|
body='body', title='title', notify_type=NotifyType.INFO,
|
||||||
|
attach=attach) is True
|
||||||
|
|
||||||
|
# Test Valid Attachment (load 3) (produces a warning)
|
||||||
|
path = (
|
||||||
|
os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
|
||||||
|
os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
|
||||||
|
os.path.join(TEST_VAR_DIR, 'apprise-test.gif'),
|
||||||
|
)
|
||||||
|
attach = AppriseAttachment(path)
|
||||||
|
assert obj.notify(
|
||||||
|
body='body', title='title', notify_type=NotifyType.INFO,
|
||||||
|
attach=attach) is True
|
||||||
|
|
||||||
|
# Test invalid attach-as input
|
||||||
|
obj = Apprise.instantiate(
|
||||||
|
'form://user@localhost.localdomain/?attach-as={')
|
||||||
|
assert obj is None
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('requests.post')
|
@mock.patch('requests.post')
|
||||||
@mock.patch('requests.get')
|
@mock.patch('requests.get')
|
||||||
|
@ -93,6 +93,9 @@ apprise_url_tests = (
|
|||||||
('json://user@localhost?method=delete', {
|
('json://user@localhost?method=delete', {
|
||||||
'instance': NotifyJSON,
|
'instance': NotifyJSON,
|
||||||
}),
|
}),
|
||||||
|
('json://user@localhost?method=patch', {
|
||||||
|
'instance': NotifyJSON,
|
||||||
|
}),
|
||||||
|
|
||||||
# Continue testing other cases
|
# Continue testing other cases
|
||||||
('json://localhost:8080', {
|
('json://localhost:8080', {
|
||||||
|
@ -92,6 +92,9 @@ apprise_url_tests = (
|
|||||||
('xml://user@localhost?method=delete', {
|
('xml://user@localhost?method=delete', {
|
||||||
'instance': NotifyXML,
|
'instance': NotifyXML,
|
||||||
}),
|
}),
|
||||||
|
('xml://user@localhost?method=patch', {
|
||||||
|
'instance': NotifyXML,
|
||||||
|
}),
|
||||||
|
|
||||||
# Continue testing other cases
|
# Continue testing other cases
|
||||||
('xml://localhost:8080', {
|
('xml://localhost:8080', {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user