diff --git a/mautrix_telegram/abstract_user.py b/mautrix_telegram/abstract_user.py index 980473ba..053bc89f 100644 --- a/mautrix_telegram/abstract_user.py +++ b/mautrix_telegram/abstract_user.py @@ -31,7 +31,7 @@ from telethon.tl.types import ( UpdateEditChannelMessage, UpdateEditMessage, UpdateNewChannelMessage, UpdateReadHistoryOutbox, UpdateShortChatMessage, UpdateShortMessage, UpdateUserName, UpdateUserPhoto, UpdateUserStatus, UpdateUserTyping, User, UserStatusOffline, UserStatusOnline, UpdateReadHistoryInbox, - UpdateReadChannelInbox) + UpdateReadChannelInbox, MessageEmpty) from mautrix.types import UserID, PresenceState from mautrix.errors import MatrixError @@ -164,6 +164,7 @@ class AbstractUser(ABC): request_retries=config["telegram.connection.request_retries"], connection=connection, proxy=proxy, + raise_last_call_error=True, loop=self.loop, base_logger=base_logger @@ -387,12 +388,15 @@ class AbstractUser(ABC): elif isinstance(update, (UpdateNewMessage, UpdateNewChannelMessage, UpdateEditMessage, UpdateEditChannelMessage)): update = update.message + if isinstance(update, MessageEmpty): + return update, None, None if isinstance(update.to_id, PeerUser) and not update.out: portal = po.Portal.get_by_tgid(update.from_id, peer_type="user", tg_receiver=self.tgid) else: portal = po.Portal.get_by_entity(update.to_id, receiver_id=self.tgid) - sender = pu.Puppet.get(update.from_id) if update.from_id else None + sender = (pu.Puppet.get(TelegramID(update.from_id.user_id)) + if isinstance(update.from_id, PeerUser) else None) else: self.log.warning("Unexpected message type in User#get_message_details: " f"{type(update)}") diff --git a/mautrix_telegram/formatter/from_telegram.py b/mautrix_telegram/formatter/from_telegram.py index bae332e7..16d2194b 100644 --- a/mautrix_telegram/formatter/from_telegram.py +++ b/mautrix_telegram/formatter/from_telegram.py @@ -22,7 +22,7 @@ from telethon.tl.types import (MessageEntityMention, MessageEntityMentionName, M MessageEntityEmail, MessageEntityTextUrl, MessageEntityBold, MessageEntityItalic, MessageEntityCode, MessageEntityPre, MessageEntityBotCommand, MessageEntityHashtag, MessageEntityCashtag, - MessageEntityPhone, TypeMessageEntity, PeerChannel, + MessageEntityPhone, TypeMessageEntity, PeerChannel, PeerChat, MessageEntityBlockquote, MessageEntityStrike, MessageFwdHeader, MessageEntityUnderline, PeerUser) from telethon.tl.custom import Message @@ -45,11 +45,11 @@ log: logging.Logger = logging.getLogger("mau.fmt.tg") def telegram_reply_to_matrix(evt: Message, source: 'AbstractUser') -> Optional[RelatesTo]: - if evt.reply_to_msg_id: - space = (evt.to_id.channel_id - if isinstance(evt, Message) and isinstance(evt.to_id, PeerChannel) + if evt.reply_to: + space = (evt.peer_id.channel_id + if isinstance(evt, Message) and isinstance(evt.peer_id, PeerChannel) else source.tgid) - msg = DBMessage.get_one_by_tgid(TelegramID(evt.reply_to_msg_id), space) + msg = DBMessage.get_one_by_tgid(TelegramID(evt.reply_to.reply_to_msg_id), space) if msg: return RelatesTo(rel_type=RelationType.REFERENCE, event_id=msg.mxid) return None @@ -61,15 +61,15 @@ async def _add_forward_header(source: 'AbstractUser', content: TextMessageEventC content.format = Format.HTML content.formatted_body = escape(content.body) fwd_from_html, fwd_from_text = None, None - if fwd_from.from_id: - user = u.User.get_by_tgid(TelegramID(fwd_from.from_id)) + if isinstance(fwd_from.from_id, PeerUser): + user = u.User.get_by_tgid(TelegramID(fwd_from.from_id.user_id)) if user: fwd_from_text = user.displayname or user.mxid fwd_from_html = (f"" f"{escape(fwd_from_text)}") if not fwd_from_text: - puppet = pu.Puppet.get(TelegramID(fwd_from.from_id), create=False) + puppet = pu.Puppet.get(TelegramID(fwd_from.from_id.user_id), create=False) if puppet and puppet.displayname: fwd_from_text = puppet.displayname or puppet.mxid fwd_from_html = (f"" @@ -77,14 +77,16 @@ async def _add_forward_header(source: 'AbstractUser', content: TextMessageEventC if not fwd_from_text: try: - user = await source.client.get_entity(PeerUser(fwd_from.from_id)) + user = await source.client.get_entity(fwd_from.from_id) if user: fwd_from_text = pu.Puppet.get_displayname(user, False) fwd_from_html = f"{escape(fwd_from_text)}" except (ValueError, RPCError): fwd_from_text = fwd_from_html = "unknown user" - elif fwd_from.channel_id: - portal = po.Portal.get_by_tgid(TelegramID(fwd_from.channel_id)) + elif isinstance(fwd_from.from_id, (PeerChannel, PeerChat)): + from_id = (fwd_from.from_id.chat_id if isinstance(fwd_from.from_id, PeerChat) + else fwd_from.from_id.channel_id) + portal = po.Portal.get_by_tgid(TelegramID(from_id)) if portal: fwd_from_text = portal.title if portal.alias: @@ -94,7 +96,7 @@ async def _add_forward_header(source: 'AbstractUser', content: TextMessageEventC fwd_from_html = f"channel {escape(fwd_from_text)}" else: try: - channel = await source.client.get_entity(PeerChannel(fwd_from.channel_id)) + channel = await source.client.get_entity(fwd_from.from_id) if channel: fwd_from_text = f"channel {channel.title}" fwd_from_html = f"channel {escape(channel.title)}" @@ -116,11 +118,11 @@ async def _add_forward_header(source: 'AbstractUser', content: TextMessageEventC async def _add_reply_header(source: 'AbstractUser', content: TextMessageEventContent, evt: Message, main_intent: IntentAPI): - space = (evt.to_id.channel_id - if isinstance(evt, Message) and isinstance(evt.to_id, PeerChannel) + space = (evt.peer_id.channel_id + if isinstance(evt, Message) and isinstance(evt.peer_id, PeerChannel) else source.tgid) - msg = DBMessage.get_one_by_tgid(TelegramID(evt.reply_to_msg_id), space) + msg = DBMessage.get_one_by_tgid(TelegramID(evt.reply_to.reply_to_msg_id), space) if not msg: return @@ -162,7 +164,7 @@ async def telegram_to_matrix(evt: Message, source: "AbstractUser", if evt.fwd_from: await _add_forward_header(source, content, evt.fwd_from) - if evt.reply_to_msg_id and not no_reply_fallback: + if evt.reply_to and not no_reply_fallback: await _add_reply_header(source, content, evt, main_intent) if isinstance(evt, Message) and evt.post and evt.post_author: diff --git a/mautrix_telegram/portal/matrix.py b/mautrix_telegram/portal/matrix.py index 1a26b659..97c2cb76 100644 --- a/mautrix_telegram/portal/matrix.py +++ b/mautrix_telegram/portal/matrix.py @@ -87,9 +87,9 @@ class PortalMatrix(BasePortal, ABC): message = await self._get_state_change_message(event, user, **kwargs) if not message: return - response = await self.bot.client.send_message( - self.peer, message, - parse_mode=self._matrix_event_to_entities) + message, entities = formatter.matrix_to_telegram(message) + response = await self.bot.client.send_message(self.peer, message, + formatting_entities=entities) space = self.tgid if self.peer_type == "channel" else self.bot.tgid self.dedup.check(response, (event_id, space)) @@ -231,18 +231,22 @@ class PortalMatrix(BasePortal, ABC): async def _handle_matrix_text(self, sender_id: TelegramID, event_id: EventID, space: TelegramID, client: 'MautrixTelegramClient', content: TextMessageEventContent, reply_to: TelegramID) -> None: + if content.formatted_body and content.format == Format.HTML: + message, entities = formatter.matrix_to_telegram(content.formatted_body) + else: + message, entities = formatter.matrix_text_to_telegram(content.body) async with self.send_lock(sender_id): lp = self.get_config("telegram_link_preview") if content.get_edit(): orig_msg = DBMessage.get_by_mxid(content.get_edit(), self.mxid, space) if orig_msg: - response = await client.edit_message(self.peer, orig_msg.tgid, content, - parse_mode=self._matrix_event_to_entities, + response = await client.edit_message(self.peer, orig_msg.tgid, message, + formatting_entities=entities, link_preview=lp) self._add_telegram_message_to_db(event_id, space, -1, response) return - response = await client.send_message(self.peer, content, reply_to=reply_to, - parse_mode=self._matrix_event_to_entities, + response = await client.send_message(self.peer, message, reply_to=reply_to, + formatting_entities=entities, link_preview=lp) self._add_telegram_message_to_db(event_id, space, 0, response) await self._send_delivery_receipt(event_id) @@ -297,7 +301,13 @@ class PortalMatrix(BasePortal, ABC): media = InputMediaUploadedDocument(file=file_handle, attributes=attributes, mime_type=mime or "application/octet-stream") - caption, entities = self._matrix_event_to_entities(caption) if caption else (None, None) + if caption: + if caption.formatted_body and caption.format == Format.HTML: + caption, entities = formatter.matrix_to_telegram(caption.formatted_body) + else: + caption, entities = formatter.matrix_text_to_telegram(content.body) + else: + caption, entities = None, None async with self.send_lock(sender_id): if await self._matrix_document_edit(client, content, space, caption, media, event_id): @@ -336,7 +346,7 @@ class PortalMatrix(BasePortal, ABC): except (KeyError, ValueError): self.log.exception("Failed to parse location") return None - caption, entities = self._matrix_event_to_entities(content) + caption, entities = formatter.matrix_text_to_telegram(content.body) media = MessageMediaGeo(geo=GeoPoint(lat, long, access_hash=0)) async with self.send_lock(sender_id): @@ -372,7 +382,8 @@ class PortalMatrix(BasePortal, ABC): await self._handle_matrix_message(sender, content, event_id) except RPCError as e: if config["bridge.delivery_error_reports"]: - await self._send_bridge_error(f"\u26a0 Your message may not have been bridged: {e}") + await self._send_bridge_error( + f"\u26a0 Your message may not have been bridged: {e}") raise async def _handle_matrix_message(self, sender: 'u.User', content: MessageEventContent, @@ -586,6 +597,7 @@ class PortalMatrix(BasePortal, ABC): self.log.warning(f"Failed to set room name", exc_info=True) return ok + def init(context: Context) -> None: global config config = context.config diff --git a/mautrix_telegram/portal/metadata.py b/mautrix_telegram/portal/metadata.py index 197baf87..ed240309 100644 --- a/mautrix_telegram/portal/metadata.py +++ b/mautrix_telegram/portal/metadata.py @@ -29,7 +29,7 @@ from telethon.tl.types import ( ChatParticipantCreator, ChannelParticipantCreator, UserProfilePhoto, UserProfilePhotoEmpty) from mautrix.errors import MForbidden -from mautrix.types import (RoomID, UserID, RoomCreatePreset, EventType, Membership, Member, +from mautrix.types import (RoomID, UserID, RoomCreatePreset, EventType, Membership, PowerLevelStateEventContent, RoomTopicStateEventContent, RoomNameStateEventContent, RoomAvatarStateEventContent, StateEventContent, EventID) diff --git a/mautrix_telegram/portal/telegram.py b/mautrix_telegram/portal/telegram.py index 17ae91d5..a504878c 100644 --- a/mautrix_telegram/portal/telegram.py +++ b/mautrix_telegram/portal/telegram.py @@ -99,8 +99,7 @@ class PortalTelegram(BasePortal, ABC): encrypt=self.encrypted) if not file: return None - if self.get_config("inline_images") and (evt.message - or evt.fwd_from or evt.reply_to_msg_id): + if self.get_config("inline_images") and (evt.message or evt.fwd_from or evt.reply_to): content = await formatter.telegram_to_matrix( evt, source, self.main_intent, prefix_html=f"Inline Telegram photo
", @@ -439,12 +438,12 @@ class PortalTelegram(BasePortal, ABC): "max_file_size": min(config["bridge.max_document_size"], 2000) * 1024 * 1024 } - async def backfill(self, source: 'AbstractUser', is_initial: bool = False, + async def backfill(self, source: 'u.User', is_initial: bool = False, limit: Optional[int] = None, last_id: Optional[int] = None) -> None: async with self.backfill_method_lock: await self._locked_backfill(source, is_initial, limit, last_id) - async def _locked_backfill(self, source: 'AbstractUser', is_initial: bool = False, + async def _locked_backfill(self, source: 'u.User', is_initial: bool = False, limit: Optional[int] = None, last_id: Optional[int] = None) -> None: limit = limit or (config["bridge.backfill.initial_limit"] if is_initial else config["bridge.backfill.missed_limit"]) @@ -481,7 +480,7 @@ class PortalTelegram(BasePortal, ABC): with self.backfill_lock: await self._backfill(source, min_id, limit) - async def _backfill(self, source: 'AbstractUser', min_id: Optional[int], limit: int) -> None: + async def _backfill(self, source: 'u.User', min_id: Optional[int], limit: int) -> None: self.backfill_leave = set() if ((self.peer_type == "user" and self.tgid != source.tgid and config["bridge.backfill.invite_own_puppet"])): @@ -514,7 +513,8 @@ class PortalTelegram(BasePortal, ABC): self.log.debug(f"Iterating all messages starting with {min_id} (approx: {limit})") messages = client.iter_messages(entity, reverse=True, min_id=min_id) async for message in messages: - sender = p.Puppet.get(message.from_id) if message.from_id else None + sender = (p.Puppet.get(message.from_id.user_id) + if isinstance(message.from_id, PeerUser) else None) # TODO handle service messages? await self.handle_telegram_message(source, sender, message) count += 1 @@ -522,7 +522,8 @@ class PortalTelegram(BasePortal, ABC): self.log.debug(f"Fetching up to {limit} most recent messages") messages = await client.get_messages(entity, limit=limit) for message in reversed(messages): - sender = p.Puppet.get(message.from_id) if message.from_id else None + sender = (p.Puppet.get(TelegramID(message.from_id.user_id)) + if isinstance(message.from_id, PeerUser) else None) await self.handle_telegram_message(source, sender, message) count += 1 return count @@ -533,7 +534,7 @@ class PortalTelegram(BasePortal, ABC): self.log.trace("Got telegram message %d, but no room exists, creating...", evt.id) await self.create_matrix_room(source, invites=[source.mxid], update_if_exists=False) - if (self.peer_type == "user" and sender.tgid == self.tg_receiver + if (self.peer_type == "user" and sender and sender.tgid == self.tg_receiver and not sender.is_real_user and not await self.az.state_store.is_joined(self.mxid, sender.mxid)): self.log.debug(f"Ignoring private chat message {evt.id}@{source.tgid} as receiver does" diff --git a/requirements.txt b/requirements.txt index a8089eac..83720268 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,6 @@ python-magic>=0.4,<0.5 commonmark>=0.8,<0.10 aiohttp>=3,<3.7 yarl<1.6 -mautrix==0.8.0.beta7 -telethon>=1.16,<1.17 +mautrix==0.8.0.beta9 +telethon>=1.17,<1.18 telethon-session-sqlalchemy>=0.2.14,<0.3