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