Skip to content

Commit

Permalink
OneSignal to support custom data in payload (#1163)
Browse files Browse the repository at this point in the history
Co-authored-by: phantom943 <[email protected]>
  • Loading branch information
caronc and phantom943 committed Jul 16, 2024
1 parent 631ce10 commit d22ce8d
Show file tree
Hide file tree
Showing 2 changed files with 218 additions and 12 deletions.
113 changes: 101 additions & 12 deletions apprise/plugins/one_signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class NotifyOneSignal(NotifyBase):
setup_url = 'https://github.com/caronc/apprise/wiki/Notify_onesignal'

# Notification
notify_url = "https://onesignal.com/api/v1/notifications"
notify_url = "https://api.onesignal.com/notifications"

# Allows the user to specify the NotifyImageSize object
image_size = NotifyImageSize.XY_72
Expand Down Expand Up @@ -161,6 +161,12 @@ class NotifyOneSignal(NotifyBase):
'type': 'bool',
'default': False,
},
'contents': {
'name': _('Enable Contents'),
'type': 'bool',
'default': True,
'map_to': 'use_contents',
},
'template': {
'alias_of': 'template',
},
Expand All @@ -175,9 +181,21 @@ class NotifyOneSignal(NotifyBase):
},
})

# Define our token control
template_kwargs = {
'custom': {
'name': _('Custom Data'),
'prefix': ':',
},
'postback': {
'name': _('Postback Data'),
'prefix': '+',
},
}

def __init__(self, app, apikey, targets=None, include_image=True,
template=None, subtitle=None, language=None, batch=False,
**kwargs):
template=None, subtitle=None, language=None, batch=None,
use_contents=None, custom=None, postback=None, **kwargs):
"""
Initialize OneSignal
Expand All @@ -201,7 +219,14 @@ def __init__(self, app, apikey, targets=None, include_image=True,
raise TypeError(msg)

# Prepare Batch Mode Flag
self.batch_size = self.default_batch_size if batch else 1
self.batch_size = self.default_batch_size if (
batch if batch is not None else
self.template_args['batch']['default']) else 1

# Prepare Use Contents Flag
self.use_contents = True if (
use_contents if use_contents is not None else
self.template_args['contents']['default']) else False

# Place a thumbnail image inline with the message body
self.include_image = include_image
Expand Down Expand Up @@ -273,6 +298,27 @@ def __init__(self, app, apikey, targets=None, include_image=True,
'Detected OneSignal Player ID: %s' %
self.targets[OneSignalCategory.PLAYER][-1])

# Custom Data
self.custom_data = {}
if custom and isinstance(custom, dict):
self.custom_data.update(custom)

elif custom:
msg = 'The specified OneSignal Custom Data ' \
'({}) are not identified as a dictionary.'.format(custom)
self.logger.warning(msg)
raise TypeError(msg)

# Postback Data
self.postback_data = {}
if postback and isinstance(postback, dict):
self.postback_data.update(postback)

elif postback:
msg = 'The specified OneSignal Postback Data ' \
'({}) are not identified as a dictionary.'.format(postback)
self.logger.warning(msg)
raise TypeError(msg)
return

def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
Expand All @@ -291,14 +337,9 @@ def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):

payload = {
'app_id': self.app,

'headings': {
self.language: title if title else self.app_desc,
},
'contents': {
self.language: body,
},

# Sending true wakes your app from background to run custom native
# code (Apple interprets this as content-available=1).
# Note: Not applicable if the app is in the "force-quit" state
Expand All @@ -307,16 +348,40 @@ def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
'content_available': True,
}

if self.template_id:
# Store template information
payload['template_id'] = self.template_id

if not self.use_contents:
# Only if a template is defined can contents be removed
del payload['contents']

# Set our data if defined
if self.custom_data:
payload.update({
'custom_data': self.custom_data,
})

# Set our postback data if defined
if self.postback_data:
payload.update({
'data': self.postback_data,
})

if title:
# Display our title if defined
payload.update({
'headings': {
self.language: title,
}})

if self.subtitle:
payload.update({
'subtitle': {
self.language: self.subtitle,
},
})

if self.template_id:
payload['template_id'] = self.template_id

# Acquire our large_icon image URL (if set)
image_url = None if not self.include_image \
else self.image_url(notify_type)
Expand Down Expand Up @@ -406,6 +471,17 @@ def url(self, privacy=False, *args, **kwargs):
# Extend our parameters
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))

# Save our template data
params.update(
{':{}'.format(k): v for k, v in self.custom_data.items()})

# Save our postback data
params.update(
{'+{}'.format(k): v for k, v in self.postback_data.items()})

if self.use_contents != self.template_args['contents']['default']:
params['contents'] = 'yes' if self.use_contents else 'no'

return '{schema}://{tp_id}{app}@{apikey}/{targets}?{params}'.format(
schema=self.secure_protocol,
tp_id='{}:'.format(
Expand Down Expand Up @@ -485,6 +561,13 @@ def parse_url(url):
'batch',
NotifyOneSignal.template_args['batch']['default']))

# Get Use Contents Boolean (if set)
results['use_contents'] = \
parse_bool(
results['qsd'].get(
'contents',
NotifyOneSignal.template_args['contents']['default']))

# The API Key is stored in the hostname
results['apikey'] = NotifyOneSignal.unquote(results['host'])

Expand Down Expand Up @@ -516,4 +599,10 @@ def parse_url(url):
results['language'] = \
NotifyOneSignal.unquote(results['qsd']['lang'])

# Store our custom data
results['custom'] = results['qsd:']

# Store our postback data
results['postback'] = results['qsd+']

return results
117 changes: 117 additions & 0 deletions test/test_plugin_onesignal.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

from json import loads
from unittest import mock
import pytest
import requests
from apprise.plugins.one_signal import NotifyOneSignal
from helpers import AppriseURLTester
from apprise import Apprise
Expand Down Expand Up @@ -105,6 +109,16 @@
# Test Kwargs
'instance': NotifyOneSignal,
}),
('onesignal://?apikey=abc&template=tp&app=123&to=playerid&body=no'
'&:key1=val1&:key2=val2', {
# Test Kwargs
'instance': NotifyOneSignal,
}),
('onesignal://?apikey=abc&template=tp&app=123&to=playerid&body=no'
'&+key1=val1&+key2=val2', {
# Test Kwargs
'instance': NotifyOneSignal,
}),
('onesignal://appid@apikey/#segment/playerid/', {
'instance': NotifyOneSignal,
# throw a bizzare code forcing us to fail to look it up
Expand Down Expand Up @@ -244,3 +258,106 @@ def test_plugin_onesignal_edge_cases():

# Individual queries
assert len(obj) == 16

# custom must be a dictionary
with pytest.raises(TypeError):
NotifyOneSignal(
app='appid', apikey='key', targets=['@user'], custom='not-a-dict')

# postback must be a dictionary
with pytest.raises(TypeError):
NotifyOneSignal(
app='appid', apikey='key', targets=['@user'],
custom=[], postback='not-a-dict')


@mock.patch('requests.post')
def test_plugin_onesignal_notifications(mock_post):
"""
OneSignal() Notifications Support
"""
# Prepare Mock
mock_post.return_value = requests.Request()
mock_post.return_value.status_code = requests.codes.ok

# Load URL with Template
instance = Apprise.instantiate(
'onesignal://templateid:appid@apikey/@user/?:key1=value1&+key3=value3')

# Validate that it loaded okay
assert isinstance(instance, NotifyOneSignal)

response = instance.notify("hello world")
assert response is True
assert mock_post.call_count == 1
assert mock_post.call_args_list[0][0][0] == \
'https://api.onesignal.com/notifications'

details = mock_post.call_args_list[0]
payload = loads(details[1]['data'])

assert payload == {
'app_id': 'appid',
'contents': {'en': 'hello world'},
'content_available': True,
'template_id': 'templateid',
'custom_data': {'key1': 'value1'},
'data': {'key3': 'value3'},
'large_icon': 'https://github.com/caronc/apprise'
'/raw/master/apprise/assets/themes/default/apprise-info-72x72.png',
'small_icon': 'https://github.com/caronc/apprise'
'/raw/master/apprise/assets/themes/default/apprise-info-32x32.png',
'include_external_user_ids': ['@user']}

mock_post.reset_mock()

# Load URL with Template and disable body
instance = Apprise.instantiate(
'onesignal://templateid:appid@apikey/@user/?contents=no')

# Validate that it loaded okay
assert isinstance(instance, NotifyOneSignal)

response = instance.notify("hello world")
assert response is True
assert mock_post.call_count == 1
assert mock_post.call_args_list[0][0][0] == \
'https://api.onesignal.com/notifications'

details = mock_post.call_args_list[0]
payload = loads(details[1]['data'])

assert payload == {
'app_id': 'appid',
'content_available': True,
'template_id': 'templateid',
'large_icon': 'https://github.com/caronc/apprise'
'/raw/master/apprise/assets/themes/default/apprise-info-72x72.png',
'small_icon': 'https://github.com/caronc/apprise'
'/raw/master/apprise/assets/themes/default/apprise-info-32x32.png',
'include_external_user_ids': ['@user']}

# Now set a title
mock_post.reset_mock()

response = instance.notify("hello world", title="mytitle")

assert response is True
assert mock_post.call_count == 1
assert mock_post.call_args_list[0][0][0] == \
'https://api.onesignal.com/notifications'

details = mock_post.call_args_list[0]
payload = loads(details[1]['data'])

assert payload == {
'app_id': 'appid',
'headings': {'en': 'mytitle'},
'content_available': True,
'template_id': 'templateid',
'large_icon': 'https://github.com/caronc/apprise'
'/raw/master/apprise/assets/themes/default/apprise-info-72x72.png',
'small_icon': 'https://github.com/caronc/apprise'
'/raw/master/apprise/assets/themes/default/apprise-info-32x32.png',
'include_external_user_ids': ['@user']}

0 comments on commit d22ce8d

Please sign in to comment.