Refactored the way Notifiarr Discord users are mentioned (#1153)

This commit is contained in:
Chris Caron 2024-07-10 06:11:58 -04:00 committed by GitHub
parent e053484fd1
commit 631ce107cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 97 additions and 68 deletions

View File

@ -37,6 +37,7 @@ from ..locale import gettext_lazy as _
from ..common import NotifyImageSize from ..common import NotifyImageSize
from ..utils import parse_list, parse_bool from ..utils import parse_list, parse_bool
from ..utils import validate_regex from ..utils import validate_regex
from .discord import USER_ROLE_DETECTION_RE
# Used to break path apart into list of channels # Used to break path apart into list of channels
CHANNEL_LIST_DELIM = re.compile(r'[ \t\r\n,#\\/]+') CHANNEL_LIST_DELIM = re.compile(r'[ \t\r\n,#\\/]+')
@ -118,14 +119,6 @@ class NotifyNotifiarr(NotifyBase):
'apikey': { 'apikey': {
'alias_of': 'apikey', 'alias_of': 'apikey',
}, },
'discord_user': {
'name': _('Ping Discord User'),
'type': 'int',
},
'discord_role': {
'name': _('Ping Discord Role'),
'type': 'int',
},
'event': { 'event': {
'name': _('Discord Event ID'), 'name': _('Discord Event ID'),
'type': 'int', 'type': 'int',
@ -149,7 +142,6 @@ class NotifyNotifiarr(NotifyBase):
}) })
def __init__(self, apikey=None, include_image=None, def __init__(self, apikey=None, include_image=None,
discord_user=None, discord_role=None,
event=None, targets=None, source=None, **kwargs): event=None, targets=None, source=None, **kwargs):
""" """
Initialize Notifiarr Object Initialize Notifiarr Object
@ -172,30 +164,6 @@ class NotifyNotifiarr(NotifyBase):
if isinstance(include_image, bool) \ if isinstance(include_image, bool) \
else self.template_args['image']['default'] 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) # Prepare our source (if set)
self.source = validate_regex(source) self.source = validate_regex(source)
@ -244,12 +212,6 @@ class NotifyNotifiarr(NotifyBase):
if self.source: if self.source:
params['source'] = 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: if self.event:
params['event'] = self.event params['event'] = self.event
@ -287,6 +249,29 @@ class NotifyNotifiarr(NotifyBase):
# Acquire image_url # Acquire image_url
image_url = self.image_url(notify_type) 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']): for idx, channel in enumerate(self.targets['channels']):
# prepare Notifiarr Object # prepare Notifiarr Object
payload = { payload = {
@ -301,14 +286,17 @@ class NotifyNotifiarr(NotifyBase):
'discord': { 'discord': {
'color': self.color(notify_type), 'color': self.color(notify_type),
'ping': { 'ping': {
'pingUser': self.discord_user # Only 1 user is supported, so truncate the rest
if not idx and self.discord_user else 0, 'pingUser': 0 if not mentions['pingUser']
'pingRole': self.discord_role else mentions['pingUser'][0],
if not idx and self.discord_role else 0, # Only 1 role is supported, so truncate the rest
'pingRole': 0 if not mentions['pingRole']
else mentions['pingRole'][0],
}, },
'text': { 'text': {
'title': title, 'title': title,
'content': '', 'content': '' if not mentions['content']
else '👉 ' + ' '.join(mentions['content']),
'description': body, 'description': body,
'footer': self.app_desc, 'footer': self.app_desc,
}, },
@ -410,17 +398,6 @@ class NotifyNotifiarr(NotifyBase):
# Get channels # Get channels
results['targets'] = NotifyNotifiarr.split_path(results['fullpath']) 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 \ if 'event' in results['qsd'] and \
len(results['qsd']['event']): len(results['qsd']['event']):
results['event'] = \ results['event'] = \

View File

@ -27,9 +27,11 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
import requests import requests
from unittest import mock
from apprise.plugins.notifiarr import NotifyNotifiarr from apprise.plugins.notifiarr import NotifyNotifiarr
from helpers import AppriseURLTester from helpers import AppriseURLTester
from json import loads
from inspect import cleandoc
# Disable logging for a cleaner testing output # Disable logging for a cleaner testing output
import logging import logging
@ -52,12 +54,6 @@ apprise_url_tests = (
# Our expected url(privacy=True) startswith() response: # Our expected url(privacy=True) startswith() response:
'privacy_url': 'notifiarr://a...y', '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', { ('notifiarr://apikey/1234/?event=invalid', {
'instance': TypeError, 'instance': TypeError,
}), }),
@ -131,11 +127,6 @@ apprise_url_tests = (
# Our expected url(privacy=True) startswith() response: # Our expected url(privacy=True) startswith() response:
'privacy_url': 'notifiarr://m...y/#123/#325', '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/', { ('notifiarr://apikey/123/', {
'instance': NotifyNotifiarr, 'instance': NotifyNotifiarr,
}), }),
@ -160,7 +151,7 @@ apprise_url_tests = (
) )
def test_plugin_custom_notifiarr_urls(): def test_plugin_notifiarr_urls():
""" """
NotifyNotifiarr() Apprise URLs NotifyNotifiarr() Apprise URLs
@ -168,3 +159,64 @@ def test_plugin_custom_notifiarr_urls():
# Run our general tests # Run our general tests
AppriseURLTester(tests=apprise_url_tests).run_all() 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},
},
}