2018-06-10 12:30:51 +03:00
|
|
|
import itertools
|
|
|
|
import re
|
2019-05-03 22:37:27 +03:00
|
|
|
import typing
|
2018-06-10 12:30:51 +03:00
|
|
|
|
2021-09-26 20:58:42 +03:00
|
|
|
from .._misc import helpers, utils
|
2021-09-13 21:37:29 +03:00
|
|
|
from ..types import _custom
|
2021-09-26 20:58:42 +03:00
|
|
|
from .. import _tl
|
2018-06-10 12:30:51 +03:00
|
|
|
|
2019-05-03 22:37:27 +03:00
|
|
|
if typing.TYPE_CHECKING:
|
|
|
|
from .telegramclient import TelegramClient
|
|
|
|
|
2018-06-10 12:30:51 +03:00
|
|
|
|
2021-09-11 14:33:27 +03:00
|
|
|
async def _replace_with_mention(self: 'TelegramClient', entities, i, user):
|
|
|
|
"""
|
|
|
|
Helper method to replace ``entities[i]`` to mention ``user``,
|
|
|
|
or do nothing if it can't be found.
|
|
|
|
"""
|
|
|
|
try:
|
2021-09-12 13:16:02 +03:00
|
|
|
entities[i] = _tl.InputMessageEntityMentionName(
|
2021-09-11 14:33:27 +03:00
|
|
|
entities[i].offset, entities[i].length,
|
|
|
|
await self.get_input_entity(user)
|
|
|
|
)
|
|
|
|
return True
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
return False
|
|
|
|
|
|
|
|
async def _parse_message_text(self: 'TelegramClient', message, parse_mode):
|
|
|
|
"""
|
|
|
|
Returns a (parsed message, entities) tuple depending on ``parse_mode``.
|
|
|
|
"""
|
|
|
|
if parse_mode == ():
|
|
|
|
parse_mode = self._parse_mode
|
|
|
|
else:
|
|
|
|
parse_mode = utils.sanitize_parse_mode(parse_mode)
|
|
|
|
|
|
|
|
if not parse_mode:
|
|
|
|
return message, []
|
|
|
|
|
|
|
|
original_message = message
|
|
|
|
message, msg_entities = parse_mode.parse(message)
|
|
|
|
if original_message and not message and not msg_entities:
|
|
|
|
raise ValueError("Failed to parse message")
|
|
|
|
|
|
|
|
for i in reversed(range(len(msg_entities))):
|
|
|
|
e = msg_entities[i]
|
2021-09-12 13:16:02 +03:00
|
|
|
if isinstance(e, _tl.MessageEntityTextUrl):
|
2021-09-11 14:33:27 +03:00
|
|
|
m = re.match(r'^@|\+|tg://user\?id=(\d+)', e.url)
|
|
|
|
if m:
|
|
|
|
user = int(m.group(1)) if m.group(1) else e.url
|
2021-09-12 15:09:53 +03:00
|
|
|
is_mention = await _replace_with_mention(self, msg_entities, i, user)
|
2018-11-24 21:39:05 +03:00
|
|
|
if not is_mention:
|
|
|
|
del msg_entities[i]
|
2021-09-12 13:16:02 +03:00
|
|
|
elif isinstance(e, (_tl.MessageEntityMentionName,
|
|
|
|
_tl.InputMessageEntityMentionName)):
|
2021-09-12 15:09:53 +03:00
|
|
|
is_mention = await _replace_with_mention(self, msg_entities, i, e.user_id)
|
2021-09-11 14:33:27 +03:00
|
|
|
if not is_mention:
|
|
|
|
del msg_entities[i]
|
|
|
|
|
|
|
|
return message, msg_entities
|
|
|
|
|
|
|
|
def _get_response_message(self: 'TelegramClient', request, result, input_chat):
|
|
|
|
"""
|
|
|
|
Extracts the response message known a request and Update result.
|
|
|
|
The request may also be the ID of the message to match.
|
|
|
|
|
|
|
|
If ``request is None`` this method returns ``{id: message}``.
|
|
|
|
|
|
|
|
If ``request.random_id`` is a list, this method returns a list too.
|
|
|
|
"""
|
2021-09-12 13:16:02 +03:00
|
|
|
if isinstance(result, _tl.UpdateShort):
|
2021-09-11 14:33:27 +03:00
|
|
|
updates = [result.update]
|
|
|
|
entities = {}
|
2021-09-12 13:16:02 +03:00
|
|
|
elif isinstance(result, (_tl.Updates, _tl.UpdatesCombined)):
|
2021-09-11 14:33:27 +03:00
|
|
|
updates = result.updates
|
|
|
|
entities = {utils.get_peer_id(x): x
|
|
|
|
for x in
|
|
|
|
itertools.chain(result.users, result.chats)}
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
random_to_id = {}
|
|
|
|
id_to_message = {}
|
|
|
|
for update in updates:
|
2021-09-12 13:16:02 +03:00
|
|
|
if isinstance(update, _tl.UpdateMessageID):
|
2021-09-11 14:33:27 +03:00
|
|
|
random_to_id[update.random_id] = update.id
|
|
|
|
|
|
|
|
elif isinstance(update, (
|
2021-09-12 13:16:02 +03:00
|
|
|
_tl.UpdateNewChannelMessage, _tl.UpdateNewMessage)):
|
2021-09-13 21:37:29 +03:00
|
|
|
update.message = _custom.Message._new(self, update.message, entities, input_chat)
|
2021-09-11 14:33:27 +03:00
|
|
|
|
|
|
|
# Pinning a message with `updatePinnedMessage` seems to
|
|
|
|
# always produce a service message we can't map so return
|
|
|
|
# it directly. The same happens for kicking users.
|
|
|
|
#
|
|
|
|
# It could also be a list (e.g. when sending albums).
|
|
|
|
#
|
|
|
|
# TODO this method is getting messier and messier as time goes on
|
|
|
|
if hasattr(request, 'random_id') or utils.is_list_like(request):
|
|
|
|
id_to_message[update.message.id] = update.message
|
|
|
|
else:
|
|
|
|
return update.message
|
2018-06-10 12:30:51 +03:00
|
|
|
|
2021-09-12 13:16:02 +03:00
|
|
|
elif (isinstance(update, _tl.UpdateEditMessage)
|
2021-09-11 14:33:27 +03:00
|
|
|
and helpers._entity_type(request.peer) != helpers._EntityType.CHANNEL):
|
2021-09-13 21:37:29 +03:00
|
|
|
update.message = _custom.Message._new(self, update.message, entities, input_chat)
|
2020-02-01 17:32:52 +03:00
|
|
|
|
2021-09-11 14:33:27 +03:00
|
|
|
# Live locations use `sendMedia` but Telegram responds with
|
|
|
|
# `updateEditMessage`, which means we won't have `id` field.
|
|
|
|
if hasattr(request, 'random_id'):
|
2021-03-31 11:30:24 +03:00
|
|
|
id_to_message[update.message.id] = update.message
|
2021-09-11 14:33:27 +03:00
|
|
|
elif request.id == update.message.id:
|
|
|
|
return update.message
|
2019-09-06 14:45:31 +03:00
|
|
|
|
2021-09-12 13:16:02 +03:00
|
|
|
elif (isinstance(update, _tl.UpdateEditChannelMessage)
|
2021-09-11 14:33:27 +03:00
|
|
|
and utils.get_peer_id(request.peer) ==
|
|
|
|
utils.get_peer_id(update.message.peer_id)):
|
|
|
|
if request.id == update.message.id:
|
2021-09-13 21:37:29 +03:00
|
|
|
return _custom.Message._new(self, update.message, entities, input_chat)
|
2021-09-11 14:33:27 +03:00
|
|
|
|
2021-09-12 13:16:02 +03:00
|
|
|
elif isinstance(update, _tl.UpdateNewScheduledMessage):
|
2021-09-11 14:33:27 +03:00
|
|
|
# Scheduled IDs may collide with normal IDs. However, for a
|
|
|
|
# single request there *shouldn't* be a mix between "some
|
|
|
|
# scheduled and some not".
|
2021-09-13 21:37:29 +03:00
|
|
|
id_to_message[update.message.id] = _custom.Message._new(self, update.message, entities, input_chat)
|
2021-09-11 14:33:27 +03:00
|
|
|
|
2021-09-12 13:16:02 +03:00
|
|
|
elif isinstance(update, _tl.UpdateMessagePoll):
|
2021-09-11 14:33:27 +03:00
|
|
|
if request.media.poll.id == update.poll_id:
|
2021-09-13 21:37:29 +03:00
|
|
|
return _custom.Message._new(self, _tl.Message(
|
2021-09-11 14:33:27 +03:00
|
|
|
id=request.id,
|
|
|
|
peer_id=utils.get_peer(request.peer),
|
2021-09-12 13:16:02 +03:00
|
|
|
media=_tl.MessageMediaPoll(
|
2021-09-11 14:33:27 +03:00
|
|
|
poll=update.poll,
|
|
|
|
results=update.results
|
2021-09-18 17:10:01 +03:00
|
|
|
),
|
|
|
|
date=None,
|
|
|
|
message=''
|
2021-09-13 21:37:29 +03:00
|
|
|
), entities, input_chat)
|
2020-10-23 12:02:30 +03:00
|
|
|
|
2021-09-11 14:33:27 +03:00
|
|
|
if request is None:
|
|
|
|
return id_to_message
|
2019-09-06 14:45:31 +03:00
|
|
|
|
2021-09-11 14:33:27 +03:00
|
|
|
random_id = request if isinstance(request, (int, list)) else getattr(request, 'random_id', None)
|
|
|
|
if random_id is None:
|
|
|
|
# Can happen when pinning a message does not actually produce a service message.
|
|
|
|
self._log[__name__].warning(
|
|
|
|
'No random_id in %s to map to, returning None message for %s', request, result)
|
|
|
|
return None
|
2019-05-17 13:18:31 +03:00
|
|
|
|
2021-09-11 14:33:27 +03:00
|
|
|
if not utils.is_list_like(random_id):
|
|
|
|
msg = id_to_message.get(random_to_id.get(random_id))
|
2019-05-17 13:18:31 +03:00
|
|
|
|
2021-09-11 14:33:27 +03:00
|
|
|
if not msg:
|
2021-03-31 11:30:24 +03:00
|
|
|
self._log[__name__].warning(
|
2021-09-11 14:33:27 +03:00
|
|
|
'Request %s had missing message mapping %s', request, result)
|
|
|
|
|
|
|
|
return msg
|
|
|
|
|
|
|
|
try:
|
|
|
|
return [id_to_message[random_to_id[rnd]] for rnd in random_id]
|
|
|
|
except KeyError:
|
|
|
|
# Sometimes forwards fail (`MESSAGE_ID_INVALID` if a message gets
|
|
|
|
# deleted or `WORKER_BUSY_TOO_LONG_RETRY` if there are issues at
|
|
|
|
# Telegram), in which case we get some "missing" message mappings.
|
|
|
|
# Log them with the hope that we can better work around them.
|
|
|
|
#
|
|
|
|
# This also happens when trying to forward messages that can't
|
|
|
|
# be forwarded because they don't exist (0, service, deleted)
|
|
|
|
# among others which could be (like deleted or existing).
|
|
|
|
self._log[__name__].warning(
|
|
|
|
'Request %s had missing message mappings %s', request, result)
|
|
|
|
|
|
|
|
return [
|
|
|
|
id_to_message.get(random_to_id[rnd])
|
|
|
|
if rnd in random_to_id
|
|
|
|
else None
|
|
|
|
for rnd in random_id
|
|
|
|
]
|