Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored the way Notifiarr Discord users are mentioned #1153

Merged
merged 1 commit into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 32 additions & 55 deletions apprise/plugins/notifiarr.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from ..common import NotifyImageSize
from ..utils import parse_list, parse_bool
from ..utils import validate_regex
from .discord import USER_ROLE_DETECTION_RE

# Used to break path apart into list of channels
CHANNEL_LIST_DELIM = re.compile(r'[ \t\r\n,#\\/]+')
Expand Down Expand Up @@ -118,14 +119,6 @@ class NotifyNotifiarr(NotifyBase):
'apikey': {
'alias_of': 'apikey',
},
'discord_user': {
'name': _('Ping Discord User'),
'type': 'int',
},
'discord_role': {
'name': _('Ping Discord Role'),
'type': 'int',
},
'event': {
'name': _('Discord Event ID'),
'type': 'int',
Expand All @@ -149,7 +142,6 @@ class NotifyNotifiarr(NotifyBase):
})

def __init__(self, apikey=None, include_image=None,
discord_user=None, discord_role=None,
event=None, targets=None, source=None, **kwargs):
"""
Initialize Notifiarr Object
Expand All @@ -172,30 +164,6 @@ def __init__(self, apikey=None, include_image=None,
if isinstance(include_image, bool) \
else self.template_args['image']['default']

# Set up our user if specified
self.discord_user = 0
if discord_user:
try:
self.discord_user = int(discord_user)

except (ValueError, TypeError):
msg = 'An invalid Notifiarr User ID ' \
'({}) was specified.'.format(discord_user)
self.logger.warning(msg)
raise TypeError(msg)

# Set up our role if specified
self.discord_role = 0
if discord_role:
try:
self.discord_role = int(discord_role)

except (ValueError, TypeError):
msg = 'An invalid Notifiarr Role ID ' \
'({}) was specified.'.format(discord_role)
self.logger.warning(msg)
raise TypeError(msg)

# Prepare our source (if set)
self.source = validate_regex(source)

Expand Down Expand Up @@ -244,12 +212,6 @@ def url(self, privacy=False, *args, **kwargs):
if self.source:
params['source'] = self.source

if self.discord_user:
params['discord_user'] = self.discord_user

if self.discord_role:
params['discord_role'] = self.discord_role

if self.event:
params['event'] = self.event

Expand Down Expand Up @@ -287,6 +249,29 @@ def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
# Acquire image_url
image_url = self.image_url(notify_type)

# Define our mentions
mentions = {
'pingUser': [],
'pingRole': [],
'content': [],
}

# parse for user id's <@123> and role IDs <@&456>
results = USER_ROLE_DETECTION_RE.findall(body)
if results:
for (is_role, no, value) in results:
if value:
# @everybody, @admin, etc - unsupported
mentions['content'].append(f'@{value}')

elif is_role:
mentions['pingRole'].append(no)
mentions['content'].append(f'<@&{no}>')

else: # is_user
mentions['pingUser'].append(no)
mentions['content'].append(f'<@{no}>')

for idx, channel in enumerate(self.targets['channels']):
# prepare Notifiarr Object
payload = {
Expand All @@ -301,14 +286,17 @@ def send(self, body, title='', notify_type=NotifyType.INFO, **kwargs):
'discord': {
'color': self.color(notify_type),
'ping': {
'pingUser': self.discord_user
if not idx and self.discord_user else 0,
'pingRole': self.discord_role
if not idx and self.discord_role else 0,
# Only 1 user is supported, so truncate the rest
'pingUser': 0 if not mentions['pingUser']
else mentions['pingUser'][0],
# Only 1 role is supported, so truncate the rest
'pingRole': 0 if not mentions['pingRole']
else mentions['pingRole'][0],
},
'text': {
'title': title,
'content': '',
'content': '' if not mentions['content']
else '👉 ' + ' '.join(mentions['content']),
'description': body,
'footer': self.app_desc,
},
Expand Down Expand Up @@ -410,17 +398,6 @@ def parse_url(url):
# Get channels
results['targets'] = NotifyNotifiarr.split_path(results['fullpath'])

if 'discord_user' in results['qsd'] and \
len(results['qsd']['discord_user']):
results['discord_user'] = \
NotifyNotifiarr.unquote(
results['qsd']['discord_user'])

if 'discord_role' in results['qsd'] and \
len(results['qsd']['discord_role']):
results['discord_role'] = \
NotifyNotifiarr.unquote(results['qsd']['discord_role'])

if 'event' in results['qsd'] and \
len(results['qsd']['event']):
results['event'] = \
Expand Down
78 changes: 65 additions & 13 deletions test/test_plugin_notifiarr.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@
# POSSIBILITY OF SUCH DAMAGE.

import requests

from unittest import mock
from apprise.plugins.notifiarr import NotifyNotifiarr
from helpers import AppriseURLTester
from json import loads
from inspect import cleandoc

# Disable logging for a cleaner testing output
import logging
Expand All @@ -52,12 +54,6 @@
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'notifiarr://a...y',
}),
('notifiarr://apikey/1234/?discord_user=invalid', {
'instance': TypeError,
}),
('notifiarr://apikey/1234/?discord_role=invalid', {
'instance': TypeError,
}),
('notifiarr://apikey/1234/?event=invalid', {
'instance': TypeError,
}),
Expand Down Expand Up @@ -131,11 +127,6 @@
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'notifiarr://m...y/#123/#325',
}),
('notifiarr://12/?key=myapikey&discord_user=23'
'&discord_role=12&event=123', {
'instance': NotifyNotifiarr,
# Our expected url(privacy=True) startswith() response:
'privacy_url': 'notifiarr://m...y/#12'}),
('notifiarr://apikey/123/', {
'instance': NotifyNotifiarr,
}),
Expand All @@ -160,11 +151,72 @@
)


def test_plugin_custom_notifiarr_urls():
def test_plugin_notifiarr_urls():
"""
NotifyNotifiarr() Apprise URLs

"""

# Run our general tests
AppriseURLTester(tests=apprise_url_tests).run_all()


@mock.patch('requests.post')
def test_plugin_notifiarr_notifications(mock_post):
"""
NotifyNotifiarr() Notifications/Ping Support

"""

# Test our header parsing when not lead with a header
body = cleandoc("""
# Heading
@everyone and @admin, wake and meet our new user <@123> and <@987>;
Attention Roles: <@&456> and <@&765>
""")

# Prepare a good response
response = mock.Mock()
response.status_code = requests.codes.ok

# Prepare Mock return object
mock_post.return_value = response

results = NotifyNotifiarr.parse_url('notifiarr://apikey/12345')

instance = NotifyNotifiarr(**results)
assert isinstance(instance, NotifyNotifiarr)

response = instance.send(body=body)
assert response is True
assert mock_post.call_count == 1

details = mock_post.call_args_list[0]
assert details[0][0] == \
'https://notifiarr.com/api/v1/notification/apprise'

payload = loads(details[1]['data'])

# First role and first user stored
assert payload == {
'source': 'Apprise',
'type': 'info',
'notification': {'update': False, 'name': 'Apprise', 'event': ''},
'discord': {
'color': '#3AA3E3', 'ping': {
# Only supports 1 entry each; so first one is parsed
'pingUser': '123',
'pingRole': '456',
},
'text': {
'title': '',
'content': '👉 @everyone @admin <@123> <@987> <@&456> <@&765>',
'description':
'# Heading\n@everyone and @admin, wake and meet our new '
'user <@123> and <@987>;\nAttention Roles: <@&456> and '
'<@&765>\n ',
'footer': 'Apprise Notifications',
},
'ids': {'channel': 12345},
},
}
Loading