mirror of
https://github.com/caronc/apprise.git
synced 2025-02-17 18:51:05 +01:00
tgram:// support for topic values as target arguments (#1028)
This commit is contained in:
parent
9dcf769397
commit
d6e0d2ee07
@ -74,8 +74,10 @@ TELEGRAM_IMAGE_XY = NotifyImageSize.XY_256
|
|||||||
# Chat ID is required
|
# Chat ID is required
|
||||||
# If the Chat ID is positive, then it's addressed to a single person
|
# If the Chat ID is positive, then it's addressed to a single person
|
||||||
# If the Chat ID is negative, then it's targeting a group
|
# If the Chat ID is negative, then it's targeting a group
|
||||||
|
# We can support :topic (an integer) if specified as well
|
||||||
IS_CHAT_ID_RE = re.compile(
|
IS_CHAT_ID_RE = re.compile(
|
||||||
r'^(@*(?P<idno>-?[0-9]{1,32})|(?P<name>[a-z_-][a-z0-9_-]+))$',
|
r'^((?P<idno>-?[0-9]{1,32})|(@|%40)?(?P<name>[a-z_-][a-z0-9_-]+))'
|
||||||
|
r'((:|%3A)(?P<topic>[0-9]+))?$',
|
||||||
re.IGNORECASE,
|
re.IGNORECASE,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -360,9 +362,6 @@ class NotifyTelegram(NotifyBase):
|
|||||||
self.logger.warning(err)
|
self.logger.warning(err)
|
||||||
raise TypeError(err)
|
raise TypeError(err)
|
||||||
|
|
||||||
# Parse our list
|
|
||||||
self.targets = parse_list(targets)
|
|
||||||
|
|
||||||
# Define whether or not we should make audible alarms
|
# Define whether or not we should make audible alarms
|
||||||
self.silent = self.template_args['silent']['default'] \
|
self.silent = self.template_args['silent']['default'] \
|
||||||
if silent is None else bool(silent)
|
if silent is None else bool(silent)
|
||||||
@ -403,15 +402,41 @@ class NotifyTelegram(NotifyBase):
|
|||||||
# URL later to directly include the user that we should message.
|
# URL later to directly include the user that we should message.
|
||||||
self.detect_owner = detect_owner
|
self.detect_owner = detect_owner
|
||||||
|
|
||||||
if self.user:
|
# Parse our list
|
||||||
# Treat this as a channel too
|
self.targets = []
|
||||||
self.targets.append(self.user)
|
for target in parse_list(targets):
|
||||||
|
results = IS_CHAT_ID_RE.match(target)
|
||||||
|
if not results:
|
||||||
|
self.logger.warning(
|
||||||
|
'Dropped invalid Telegram chat/group ({}) specified.'
|
||||||
|
.format(target),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ensure we don't fall back to owner detection
|
||||||
|
self.detect_owner = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
topic = int(
|
||||||
|
results.group('topic')
|
||||||
|
if results.group('topic') else self.topic)
|
||||||
|
|
||||||
|
except TypeError:
|
||||||
|
# No worries
|
||||||
|
topic = None
|
||||||
|
|
||||||
|
if results.group('name') is not None:
|
||||||
|
# Name
|
||||||
|
self.targets.append(('@%s' % results.group('name'), topic))
|
||||||
|
|
||||||
|
else: # ID
|
||||||
|
self.targets.append((int(results.group('idno')), topic))
|
||||||
|
|
||||||
# Track whether or not we want to send an image with our notification
|
# Track whether or not we want to send an image with our notification
|
||||||
# or not.
|
# or not.
|
||||||
self.include_image = include_image
|
self.include_image = include_image
|
||||||
|
|
||||||
def send_media(self, chat_id, notify_type, attach=None):
|
def send_media(self, target, notify_type, attach=None):
|
||||||
"""
|
"""
|
||||||
Sends a sticker based on the specified notify type
|
Sends a sticker based on the specified notify type
|
||||||
|
|
||||||
@ -470,9 +495,12 @@ class NotifyTelegram(NotifyBase):
|
|||||||
# content can arrive together.
|
# content can arrive together.
|
||||||
self.throttle()
|
self.throttle()
|
||||||
|
|
||||||
|
# Extract our target
|
||||||
|
chat_id, topic = target
|
||||||
|
|
||||||
payload = {'chat_id': chat_id}
|
payload = {'chat_id': chat_id}
|
||||||
if self.topic:
|
if topic:
|
||||||
payload['message_thread_id'] = self.topic
|
payload['message_thread_id'] = topic
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(path, 'rb') as f:
|
with open(path, 'rb') as f:
|
||||||
@ -658,7 +686,7 @@ class NotifyTelegram(NotifyBase):
|
|||||||
_id = self.detect_bot_owner()
|
_id = self.detect_bot_owner()
|
||||||
if _id:
|
if _id:
|
||||||
# Permanently store our id in our target list for next time
|
# Permanently store our id in our target list for next time
|
||||||
self.targets.append(str(_id))
|
self.targets.append((str(_id), None))
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
'Update your Telegram Apprise URL to read: '
|
'Update your Telegram Apprise URL to read: '
|
||||||
'{}'.format(self.url(privacy=True)))
|
'{}'.format(self.url(privacy=True)))
|
||||||
@ -681,26 +709,23 @@ class NotifyTelegram(NotifyBase):
|
|||||||
'sendMessage'
|
'sendMessage'
|
||||||
)
|
)
|
||||||
|
|
||||||
payload = {
|
_payload = {
|
||||||
# Notification Audible Control
|
# Notification Audible Control
|
||||||
'disable_notification': self.silent,
|
'disable_notification': self.silent,
|
||||||
# Display Web Page Preview (if possible)
|
# Display Web Page Preview (if possible)
|
||||||
'disable_web_page_preview': not self.preview,
|
'disable_web_page_preview': not self.preview,
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.topic:
|
|
||||||
payload['message_thread_id'] = self.topic
|
|
||||||
|
|
||||||
# Prepare Message Body
|
# Prepare Message Body
|
||||||
if self.notify_format == NotifyFormat.MARKDOWN:
|
if self.notify_format == NotifyFormat.MARKDOWN:
|
||||||
payload['parse_mode'] = 'MARKDOWN'
|
_payload['parse_mode'] = 'MARKDOWN'
|
||||||
|
|
||||||
payload['text'] = body
|
_payload['text'] = body
|
||||||
|
|
||||||
else: # HTML
|
else: # HTML
|
||||||
|
|
||||||
# Use Telegram's HTML mode
|
# Use Telegram's HTML mode
|
||||||
payload['parse_mode'] = 'HTML'
|
_payload['parse_mode'] = 'HTML'
|
||||||
for r, v, m in self.__telegram_escape_html_entries:
|
for r, v, m in self.__telegram_escape_html_entries:
|
||||||
|
|
||||||
if 'html' in m:
|
if 'html' in m:
|
||||||
@ -712,7 +737,7 @@ class NotifyTelegram(NotifyBase):
|
|||||||
body = r.sub(v, body)
|
body = r.sub(v, body)
|
||||||
|
|
||||||
# Prepare our payload based on HTML or TEXT
|
# Prepare our payload based on HTML or TEXT
|
||||||
payload['text'] = body
|
_payload['text'] = body
|
||||||
|
|
||||||
# Handle payloads without a body specified (but an attachment present)
|
# Handle payloads without a body specified (but an attachment present)
|
||||||
attach_content = \
|
attach_content = \
|
||||||
@ -721,41 +746,31 @@ class NotifyTelegram(NotifyBase):
|
|||||||
# Create a copy of the chat_ids list
|
# Create a copy of the chat_ids list
|
||||||
targets = list(self.targets)
|
targets = list(self.targets)
|
||||||
while len(targets):
|
while len(targets):
|
||||||
chat_id = targets.pop(0)
|
target = targets.pop(0)
|
||||||
chat_id = IS_CHAT_ID_RE.match(chat_id)
|
chat_id, topic = target
|
||||||
if not chat_id:
|
|
||||||
self.logger.warning(
|
|
||||||
"The specified chat_id '%s' is invalid; skipping." % (
|
|
||||||
chat_id,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Flag our error
|
# Printable chat_id details
|
||||||
has_error = True
|
pchat_id = f'{chat_id}' if not topic else f'{chat_id}:{topic}'
|
||||||
continue
|
|
||||||
|
|
||||||
if chat_id.group('name') is not None:
|
payload = _payload.copy()
|
||||||
# Name
|
payload['chat_id'] = chat_id
|
||||||
payload['chat_id'] = '@%s' % chat_id.group('name')
|
if topic:
|
||||||
|
payload['message_thread_id'] = topic
|
||||||
else:
|
|
||||||
# ID
|
|
||||||
payload['chat_id'] = int(chat_id.group('idno'))
|
|
||||||
|
|
||||||
if self.include_image is True:
|
if self.include_image is True:
|
||||||
# Define our path
|
# Define our path
|
||||||
if not self.send_media(payload['chat_id'], notify_type):
|
if not self.send_media(target, notify_type):
|
||||||
# We failed to send the image associated with our
|
# We failed to send the image associated with our
|
||||||
notify_type
|
notify_type
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Failed to send Telegram type image to {}.',
|
'Failed to send Telegram type image to {}.',
|
||||||
payload['chat_id'])
|
pchat_id)
|
||||||
|
|
||||||
if attach and self.attachment_support and \
|
if attach and self.attachment_support and \
|
||||||
attach_content == TelegramContentPlacement.AFTER:
|
attach_content == TelegramContentPlacement.AFTER:
|
||||||
# Send our attachments now (if specified and if it exists)
|
# Send our attachments now (if specified and if it exists)
|
||||||
if not self._send_attachments(
|
if not self._send_attachments(
|
||||||
chat_id=payload['chat_id'], notify_type=notify_type,
|
target, notify_type=notify_type,
|
||||||
attach=attach):
|
attach=attach):
|
||||||
|
|
||||||
has_error = True
|
has_error = True
|
||||||
@ -803,7 +818,7 @@ class NotifyTelegram(NotifyBase):
|
|||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Failed to send Telegram notification to {}: '
|
'Failed to send Telegram notification to {}: '
|
||||||
'{}, error={}.'.format(
|
'{}, error={}.'.format(
|
||||||
payload['chat_id'],
|
pchat_id,
|
||||||
error_msg if error_msg else status_str,
|
error_msg if error_msg else status_str,
|
||||||
r.status_code))
|
r.status_code))
|
||||||
|
|
||||||
@ -817,7 +832,7 @@ class NotifyTelegram(NotifyBase):
|
|||||||
except requests.RequestException as e:
|
except requests.RequestException as e:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'A connection error occurred sending Telegram:%s ' % (
|
'A connection error occurred sending Telegram:%s ' % (
|
||||||
payload['chat_id']) + 'notification.'
|
pchat_id) + 'notification.'
|
||||||
)
|
)
|
||||||
self.logger.debug('Socket Exception: %s' % str(e))
|
self.logger.debug('Socket Exception: %s' % str(e))
|
||||||
|
|
||||||
@ -833,7 +848,7 @@ class NotifyTelegram(NotifyBase):
|
|||||||
# it was identified to send the content before the attachments
|
# it was identified to send the content before the attachments
|
||||||
# which is now done.
|
# which is now done.
|
||||||
if not self._send_attachments(
|
if not self._send_attachments(
|
||||||
chat_id=payload['chat_id'],
|
target=target,
|
||||||
notify_type=notify_type,
|
notify_type=notify_type,
|
||||||
attach=attach):
|
attach=attach):
|
||||||
|
|
||||||
@ -842,14 +857,14 @@ class NotifyTelegram(NotifyBase):
|
|||||||
|
|
||||||
return not has_error
|
return not has_error
|
||||||
|
|
||||||
def _send_attachments(self, chat_id, notify_type, attach):
|
def _send_attachments(self, target, notify_type, attach):
|
||||||
"""
|
"""
|
||||||
Sends our attachments
|
Sends our attachments
|
||||||
"""
|
"""
|
||||||
has_error = False
|
has_error = False
|
||||||
# Send our attachments now (if specified and if it exists)
|
# Send our attachments now (if specified and if it exists)
|
||||||
for attachment in attach:
|
for attachment in attach:
|
||||||
if not self.send_media(chat_id, notify_type, attach=attachment):
|
if not self.send_media(target, notify_type, attach=attachment):
|
||||||
|
|
||||||
# We failed; don't continue
|
# We failed; don't continue
|
||||||
has_error = True
|
has_error = True
|
||||||
@ -880,13 +895,21 @@ class NotifyTelegram(NotifyBase):
|
|||||||
# Extend our parameters
|
# Extend our parameters
|
||||||
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
|
params.update(self.url_parameters(privacy=privacy, *args, **kwargs))
|
||||||
|
|
||||||
|
targets = []
|
||||||
|
for (chat_id, _topic) in self.targets:
|
||||||
|
topic = _topic if _topic else self.topic
|
||||||
|
|
||||||
|
targets.append(''.join(
|
||||||
|
[NotifyTelegram.quote(f'{chat_id}', safe='@')
|
||||||
|
if isinstance(chat_id, str) else f'{chat_id}',
|
||||||
|
'' if not topic else f':{topic}']))
|
||||||
|
|
||||||
# No need to check the user token because the user automatically gets
|
# No need to check the user token because the user automatically gets
|
||||||
# appended into the list of chat ids
|
# appended into the list of chat ids
|
||||||
return '{schema}://{bot_token}/{targets}/?{params}'.format(
|
return '{schema}://{bot_token}/{targets}/?{params}'.format(
|
||||||
schema=self.secure_protocol,
|
schema=self.secure_protocol,
|
||||||
bot_token=self.pprint(self.bot_token, privacy, safe=''),
|
bot_token=self.pprint(self.bot_token, privacy, safe=''),
|
||||||
targets='/'.join(
|
targets='/'.join(targets),
|
||||||
[NotifyTelegram.quote('@{}'.format(x)) for x in self.targets]),
|
|
||||||
params=NotifyTelegram.urlencode(params))
|
params=NotifyTelegram.urlencode(params))
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
@ -987,6 +1010,7 @@ class NotifyTelegram(NotifyBase):
|
|||||||
|
|
||||||
# Include images with our message
|
# Include images with our message
|
||||||
results['detect_owner'] = \
|
results['detect_owner'] = \
|
||||||
parse_bool(results['qsd'].get('detect', True))
|
parse_bool(
|
||||||
|
results['qsd'].get('detect', not results['targets']))
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
@ -243,7 +243,7 @@ def test_plugin_telegram_general(mock_post):
|
|||||||
invalid_bot_token = 'abcd:123'
|
invalid_bot_token = 'abcd:123'
|
||||||
|
|
||||||
# Chat ID
|
# Chat ID
|
||||||
chat_ids = 'l2g, lead2gold'
|
chat_ids = 'l2g:1234, lead2gold'
|
||||||
|
|
||||||
# Prepare Mock
|
# Prepare Mock
|
||||||
mock_post.return_value = requests.Request()
|
mock_post.return_value = requests.Request()
|
||||||
@ -397,7 +397,7 @@ def test_plugin_telegram_general(mock_post):
|
|||||||
|
|
||||||
obj = NotifyTelegram(bot_token=bot_token, targets='12345')
|
obj = NotifyTelegram(bot_token=bot_token, targets='12345')
|
||||||
assert len(obj.targets) == 1
|
assert len(obj.targets) == 1
|
||||||
assert obj.targets[0] == '12345'
|
assert obj.targets[0] == (12345, None)
|
||||||
|
|
||||||
# Test the escaping of characters since Telegram escapes stuff for us to
|
# Test the escaping of characters since Telegram escapes stuff for us to
|
||||||
# which we need to consider
|
# which we need to consider
|
||||||
@ -440,7 +440,7 @@ def test_plugin_telegram_general(mock_post):
|
|||||||
|
|
||||||
assert obj.notify(title='hello', body='world') is True
|
assert obj.notify(title='hello', body='world') is True
|
||||||
assert len(obj.targets) == 1
|
assert len(obj.targets) == 1
|
||||||
assert obj.targets[0] == '532389719'
|
assert obj.targets[0] == ('532389719', None)
|
||||||
|
|
||||||
# Do the test again, but without the expected (parsed response)
|
# Do the test again, but without the expected (parsed response)
|
||||||
mock_post.return_value.content = dumps({
|
mock_post.return_value.content = dumps({
|
||||||
@ -550,7 +550,7 @@ def test_plugin_telegram_general(mock_post):
|
|||||||
'tgram://123456789:ABCdefghijkl123456789opqyz/-123456789525')
|
'tgram://123456789:ABCdefghijkl123456789opqyz/-123456789525')
|
||||||
assert isinstance(obj, NotifyTelegram)
|
assert isinstance(obj, NotifyTelegram)
|
||||||
assert len(obj.targets) == 1
|
assert len(obj.targets) == 1
|
||||||
assert '-123456789525' in obj.targets
|
assert (-123456789525, None) in obj.targets
|
||||||
|
|
||||||
|
|
||||||
@mock.patch('requests.post')
|
@mock.patch('requests.post')
|
||||||
|
Loading…
Reference in New Issue
Block a user