mirror of https://github.com/caronc/apprise
Discord user/role ping support added (#1004)
parent
c49976237a
commit
ccb97bc92e
|
@ -60,6 +60,11 @@ from ..AppriseLocale import gettext_lazy as _
|
||||||
from ..attachment.AttachBase import AttachBase
|
from ..attachment.AttachBase import AttachBase
|
||||||
|
|
||||||
|
|
||||||
|
# Used to detect user/role IDs
|
||||||
|
USER_ROLE_DETECTION_RE = re.compile(
|
||||||
|
r'\s*(?:<@(?P<role>&?)(?P<id>[0-9]+)>|@(?P<value>[a-z0-9]+))', re.I)
|
||||||
|
|
||||||
|
|
||||||
class NotifyDiscord(NotifyBase):
|
class NotifyDiscord(NotifyBase):
|
||||||
"""
|
"""
|
||||||
A wrapper to Discord Notifications
|
A wrapper to Discord Notifications
|
||||||
|
@ -336,6 +341,33 @@ class NotifyDiscord(NotifyBase):
|
||||||
payload['content'] = \
|
payload['content'] = \
|
||||||
body if not title else "{}\r\n{}".format(title, body)
|
body if not title else "{}\r\n{}".format(title, body)
|
||||||
|
|
||||||
|
# parse for user id's <@123> and role IDs <@&456>
|
||||||
|
results = USER_ROLE_DETECTION_RE.findall(body)
|
||||||
|
if results:
|
||||||
|
payload['allow_mentions'] = {
|
||||||
|
'parse': [],
|
||||||
|
'users': [],
|
||||||
|
'roles': [],
|
||||||
|
}
|
||||||
|
|
||||||
|
_content = []
|
||||||
|
for (is_role, no, value) in results:
|
||||||
|
if value:
|
||||||
|
payload['allow_mentions']['parse'].append(value)
|
||||||
|
_content.append(f'@{value}')
|
||||||
|
|
||||||
|
elif is_role:
|
||||||
|
payload['allow_mentions']['roles'].append(no)
|
||||||
|
_content.append(f'<@&{no}>')
|
||||||
|
|
||||||
|
else: # is_user
|
||||||
|
payload['allow_mentions']['users'].append(no)
|
||||||
|
_content.append(f'<@{no}>')
|
||||||
|
|
||||||
|
if self.notify_format == NotifyFormat.MARKDOWN:
|
||||||
|
# Add pingable elements to content field
|
||||||
|
payload['content'] = '👉 ' + ' '.join(_content)
|
||||||
|
|
||||||
if not self._send(payload, params=params):
|
if not self._send(payload, params=params):
|
||||||
# We failed to post our message
|
# We failed to post our message
|
||||||
return False
|
return False
|
||||||
|
@ -360,16 +392,21 @@ class NotifyDiscord(NotifyBase):
|
||||||
'wait': True,
|
'wait': True,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
#
|
||||||
# Remove our text/title based content for attachment use
|
# Remove our text/title based content for attachment use
|
||||||
|
#
|
||||||
if 'embeds' in payload:
|
if 'embeds' in payload:
|
||||||
# Markdown
|
|
||||||
del payload['embeds']
|
del payload['embeds']
|
||||||
|
|
||||||
if 'content' in payload:
|
if 'content' in payload:
|
||||||
# Markdown
|
|
||||||
del payload['content']
|
del payload['content']
|
||||||
|
|
||||||
|
if 'allow_mentions' in payload:
|
||||||
|
del payload['allow_mentions']
|
||||||
|
|
||||||
|
#
|
||||||
# Send our attachments
|
# Send our attachments
|
||||||
|
#
|
||||||
for attachment in attach:
|
for attachment in attach:
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
'Posting Discord Attachment {}'.format(attachment.name))
|
'Posting Discord Attachment {}'.format(attachment.name))
|
||||||
|
|
|
@ -32,6 +32,7 @@ from datetime import datetime, timedelta
|
||||||
from datetime import timezone
|
from datetime import timezone
|
||||||
import pytest
|
import pytest
|
||||||
import requests
|
import requests
|
||||||
|
from json import loads
|
||||||
|
|
||||||
from apprise.plugins.NotifyDiscord import NotifyDiscord
|
from apprise.plugins.NotifyDiscord import NotifyDiscord
|
||||||
from helpers import AppriseURLTester
|
from helpers import AppriseURLTester
|
||||||
|
@ -184,6 +185,113 @@ def test_plugin_discord_urls():
|
||||||
AppriseURLTester(tests=apprise_url_tests).run_all()
|
AppriseURLTester(tests=apprise_url_tests).run_all()
|
||||||
|
|
||||||
|
|
||||||
|
@mock.patch('requests.post')
|
||||||
|
def test_plugin_discord_notifications(mock_post):
|
||||||
|
"""
|
||||||
|
NotifyDiscord() Notifications/Ping Support
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Initialize some generic (but valid) tokens
|
||||||
|
webhook_id = 'A' * 24
|
||||||
|
webhook_token = 'B' * 64
|
||||||
|
|
||||||
|
# Prepare Mock
|
||||||
|
mock_post.return_value = requests.Request()
|
||||||
|
mock_post.return_value.status_code = requests.codes.ok
|
||||||
|
|
||||||
|
# Test our header parsing when not lead with a header
|
||||||
|
body = """
|
||||||
|
# Heading
|
||||||
|
@everyone and @admin, wake and meet our new user <@123>; <@&456>"
|
||||||
|
"""
|
||||||
|
|
||||||
|
results = NotifyDiscord.parse_url(
|
||||||
|
f'discord://{webhook_id}/{webhook_token}/?format=markdown')
|
||||||
|
|
||||||
|
assert isinstance(results, dict)
|
||||||
|
assert results['user'] is None
|
||||||
|
assert results['webhook_id'] == webhook_id
|
||||||
|
assert results['webhook_token'] == webhook_token
|
||||||
|
assert results['password'] is None
|
||||||
|
assert results['port'] is None
|
||||||
|
assert results['host'] == webhook_id
|
||||||
|
assert results['fullpath'] == f'/{webhook_token}/'
|
||||||
|
assert results['path'] == f'/{webhook_token}/'
|
||||||
|
assert results['query'] is None
|
||||||
|
assert results['schema'] == 'discord'
|
||||||
|
assert results['url'] == f'discord://{webhook_id}/{webhook_token}/'
|
||||||
|
|
||||||
|
instance = NotifyDiscord(**results)
|
||||||
|
assert isinstance(instance, NotifyDiscord)
|
||||||
|
|
||||||
|
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] == \
|
||||||
|
f'https://discord.com/api/webhooks/{webhook_id}/{webhook_token}'
|
||||||
|
|
||||||
|
payload = loads(details[1]['data'])
|
||||||
|
|
||||||
|
assert 'allow_mentions' in payload
|
||||||
|
assert 'users' in payload['allow_mentions']
|
||||||
|
assert len(payload['allow_mentions']['users']) == 1
|
||||||
|
assert '123' in payload['allow_mentions']['users']
|
||||||
|
assert 'roles' in payload['allow_mentions']
|
||||||
|
assert len(payload['allow_mentions']['roles']) == 1
|
||||||
|
assert '456' in payload['allow_mentions']['roles']
|
||||||
|
assert 'parse' in payload['allow_mentions']
|
||||||
|
assert len(payload['allow_mentions']['parse']) == 2
|
||||||
|
assert 'everyone' in payload['allow_mentions']['parse']
|
||||||
|
assert 'admin' in payload['allow_mentions']['parse']
|
||||||
|
|
||||||
|
# Reset our object
|
||||||
|
mock_post.reset_mock()
|
||||||
|
|
||||||
|
results = NotifyDiscord.parse_url(
|
||||||
|
f'discord://{webhook_id}/{webhook_token}/?format=text')
|
||||||
|
|
||||||
|
assert isinstance(results, dict)
|
||||||
|
assert results['user'] is None
|
||||||
|
assert results['webhook_id'] == webhook_id
|
||||||
|
assert results['webhook_token'] == webhook_token
|
||||||
|
assert results['password'] is None
|
||||||
|
assert results['port'] is None
|
||||||
|
assert results['host'] == webhook_id
|
||||||
|
assert results['fullpath'] == f'/{webhook_token}/'
|
||||||
|
assert results['path'] == f'/{webhook_token}/'
|
||||||
|
assert results['query'] is None
|
||||||
|
assert results['schema'] == 'discord'
|
||||||
|
assert results['url'] == f'discord://{webhook_id}/{webhook_token}/'
|
||||||
|
|
||||||
|
instance = NotifyDiscord(**results)
|
||||||
|
assert isinstance(instance, NotifyDiscord)
|
||||||
|
|
||||||
|
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] == \
|
||||||
|
f'https://discord.com/api/webhooks/{webhook_id}/{webhook_token}'
|
||||||
|
|
||||||
|
payload = loads(details[1]['data'])
|
||||||
|
|
||||||
|
assert 'allow_mentions' in payload
|
||||||
|
assert 'users' in payload['allow_mentions']
|
||||||
|
assert len(payload['allow_mentions']['users']) == 1
|
||||||
|
assert '123' in payload['allow_mentions']['users']
|
||||||
|
assert 'roles' in payload['allow_mentions']
|
||||||
|
assert len(payload['allow_mentions']['roles']) == 1
|
||||||
|
assert '456' in payload['allow_mentions']['roles']
|
||||||
|
assert 'parse' in payload['allow_mentions']
|
||||||
|
assert len(payload['allow_mentions']['parse']) == 2
|
||||||
|
assert 'everyone' in payload['allow_mentions']['parse']
|
||||||
|
assert 'admin' in payload['allow_mentions']['parse']
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('requests.post')
|
@mock.patch('requests.post')
|
||||||
def test_plugin_discord_general(mock_post):
|
def test_plugin_discord_general(mock_post):
|
||||||
"""
|
"""
|
||||||
|
@ -564,6 +672,26 @@ def test_plugin_discord_attachments(mock_post):
|
||||||
'https://discord.com/api/webhooks/{}/{}'.format(
|
'https://discord.com/api/webhooks/{}/{}'.format(
|
||||||
webhook_id, webhook_token)
|
webhook_id, webhook_token)
|
||||||
|
|
||||||
|
# Reset our object
|
||||||
|
mock_post.reset_mock()
|
||||||
|
|
||||||
|
# Test notifications with mentions and attachments in it
|
||||||
|
assert obj.notify(
|
||||||
|
body='Say hello to <@1234>!', notify_type=NotifyType.INFO,
|
||||||
|
attach=attach) is True
|
||||||
|
|
||||||
|
# Test our call count
|
||||||
|
assert mock_post.call_count == 2
|
||||||
|
assert mock_post.call_args_list[0][0][0] == \
|
||||||
|
'https://discord.com/api/webhooks/{}/{}'.format(
|
||||||
|
webhook_id, webhook_token)
|
||||||
|
assert mock_post.call_args_list[1][0][0] == \
|
||||||
|
'https://discord.com/api/webhooks/{}/{}'.format(
|
||||||
|
webhook_id, webhook_token)
|
||||||
|
|
||||||
|
# Reset our object
|
||||||
|
mock_post.reset_mock()
|
||||||
|
|
||||||
# An invalid attachment will cause a failure
|
# An invalid attachment will cause a failure
|
||||||
path = os.path.join(TEST_VAR_DIR, '/invalid/path/to/an/invalid/file.jpg')
|
path = os.path.join(TEST_VAR_DIR, '/invalid/path/to/an/invalid/file.jpg')
|
||||||
attach = AppriseAttachment(path)
|
attach = AppriseAttachment(path)
|
||||||
|
|
Loading…
Reference in New Issue