From eef498d47ac360319aec68d062eb60bb986b05b6 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 19 Jul 2019 00:17:57 +0300 Subject: [PATCH] More migrations to mautrix-python --- mautrix_telegram/abstract_user.py | 23 ++- mautrix_telegram/bot.py | 11 +- mautrix_telegram/config.py | 4 - mautrix_telegram/db/telegram_file.py | 4 +- mautrix_telegram/portal.py | 266 +++++++++++++++------------ optional-requirements.txt | 2 +- 6 files changed, 172 insertions(+), 138 deletions(-) diff --git a/mautrix_telegram/abstract_user.py b/mautrix_telegram/abstract_user.py index 2a7f849a..c15dcaf8 100644 --- a/mautrix_telegram/abstract_user.py +++ b/mautrix_telegram/abstract_user.py @@ -24,17 +24,19 @@ from telethon.tl.patched import MessageService, Message from telethon.tl.types import ( Channel, ChannelForbidden, Chat, ChatForbidden, MessageActionChannelMigrateFrom, PeerUser, TypeUpdate, UpdateChannelPinnedMessage, UpdateChatPinnedMessage, UpdateChatParticipantAdmin, - UpdateChatParticipants, UpdateChatUserTyping, UpdateDeleteChannelMessages, UpdateDeleteMessages, - UpdateEditChannelMessage, UpdateEditMessage, UpdateNewChannelMessage, UpdateNewMessage, + UpdateChatParticipants, UpdateChatUserTyping, UpdateDeleteChannelMessages, UpdateNewMessage, + UpdateDeleteMessages, UpdateEditChannelMessage, UpdateEditMessage, UpdateNewChannelMessage, UpdateReadHistoryOutbox, UpdateShortChatMessage, UpdateShortMessage, UpdateUserName, UpdateUserPhoto, UpdateUserStatus, UpdateUserTyping, User, UserStatusOffline, UserStatusOnline) -from mautrix_appservice import MatrixRequestError, AppService +from mautrix.types import UserID +from mautrix.errors import MatrixError +from mautrix.appservice import AppService from alchemysession import AlchemySessionContainer from . import portal as po, puppet as pu, __version__ from .db import Message as DBMessage -from .types import TelegramID, MatrixUserID +from .types import TelegramID from .tgclient import MautrixTelegramClient if TYPE_CHECKING: @@ -69,7 +71,7 @@ class AbstractUser(ABC): ignore_incoming_bot_events: bool = True client: Optional[MautrixTelegramClient] - mxid: Optional[MatrixUserID] + mxid: Optional[UserID] tgid: Optional[TelegramID] username: Optional['str'] @@ -140,8 +142,10 @@ class AbstractUser(ABC): api_hash=config["telegram.api_hash"], app_version=__version__ if appversion == "auto" else appversion, - system_version=MautrixTelegramClient.__version__ if sysversion == "auto" else sysversion, - device_model=f"{platform.system()} {platform.release()}" if device == "auto" else device, + system_version=(MautrixTelegramClient.__version__ + if sysversion == "auto" else sysversion), + device_model=(f"{platform.system()} {platform.release()}" + if device == "auto" else device), timeout=config["telegram.connection.timeout"], connection_retries=config["telegram.connection.retries"], @@ -197,7 +201,8 @@ class AbstractUser(ABC): raise NotImplementedError() async def is_logged_in(self) -> bool: - return self.client and self.client.is_connected() and await self.client.is_user_authorized() + return (self.client and self.client.is_connected() + and await self.client.is_user_authorized()) async def has_full_access(self, allow_bot: bool = False) -> bool: return (self.puppet_whitelisted @@ -368,7 +373,7 @@ class AbstractUser(ABC): return try: await portal.main_intent.redact(message.mx_room, message.mxid) - except MatrixRequestError: + except MatrixError: pass async def delete_message(self, update: UpdateDeleteMessages) -> None: diff --git a/mautrix_telegram/bot.py b/mautrix_telegram/bot.py index 51078388..6ac2b138 100644 --- a/mautrix_telegram/bot.py +++ b/mautrix_telegram/bot.py @@ -27,7 +27,8 @@ from telethon.tl.functions.messages import GetChatsRequest, GetFullChatRequest from telethon.tl.functions.channels import GetChannelsRequest, GetParticipantRequest from telethon.errors import ChannelInvalidError, ChannelPrivateError -from .types import MatrixUserID +from mautrix.types import UserID + from .abstract_user import AbstractUser from .db import BotChat from .types import TelegramID @@ -50,7 +51,7 @@ class Bot(AbstractUser): tg_whitelist: List[int] whitelist_group_admins: bool _me_info: Optional[User] - _me_mxid: Optional[MatrixUserID] + _me_mxid: Optional[UserID] def __init__(self, token: str) -> None: super().__init__() @@ -70,7 +71,7 @@ class Bot(AbstractUser): self._me_info = None self._me_mxid = None - async def get_me(self, use_cache: bool = True) -> Tuple[User, MatrixUserID]: + async def get_me(self, use_cache: bool = True) -> Tuple[User, UserID]: if not use_cache or not self._me_mxid: self._me_info = await self.client.get_me() self._me_mxid = pu.Puppet.get_mxid_from_id(TelegramID(self._me_info.id)) @@ -186,7 +187,7 @@ class Bot(AbstractUser): "Portal is not public. Use `/invite ` to get an invite.") async def handle_command_invite(self, portal: po.Portal, reply: ReplyFunc, - mxid_input: MatrixUserID) -> Message: + mxid_input: UserID) -> Message: if len(mxid_input) == 0: return await reply("Usage: `/invite `") elif not portal.mxid: @@ -194,7 +195,7 @@ class Bot(AbstractUser): "Create one with /portal first.") if not self.mxid_regex.match(mxid_input): return await reply("That doesn't look like a Matrix ID.") - user = await u.User.get_by_mxid(MatrixUserID(mxid_input)).ensure_started() + user = await u.User.get_by_mxid(mxid_input).ensure_started() if not user.relaybot_whitelisted: return await reply("That user is not whitelisted to use the bridge.") elif await user.is_logged_in(): diff --git a/mautrix_telegram/config.py b/mautrix_telegram/config.py index 276e4e6a..e23ae7a6 100644 --- a/mautrix_telegram/config.py +++ b/mautrix_telegram/config.py @@ -34,10 +34,6 @@ class Config(BaseBridgeConfig): except KeyError: return super().__getitem__(key) - @staticmethod - def _new_token() -> str: - return "".join(random.choice(string.ascii_lowercase + string.digits) for _ in range(64)) - def do_update(self, helper: ConfigUpdateHelper) -> None: copy, copy_dict, base = helper diff --git a/mautrix_telegram/db/telegram_file.py b/mautrix_telegram/db/telegram_file.py index 32253517..f0e4b045 100644 --- a/mautrix_telegram/db/telegram_file.py +++ b/mautrix_telegram/db/telegram_file.py @@ -16,6 +16,8 @@ from sqlalchemy import Column, ForeignKey, Integer, BigInteger, String, Boolean from typing import Optional +from mautrix.types import ContentURI + from .base import Base @@ -23,7 +25,7 @@ class TelegramFile(Base): __tablename__ = "telegram_file" id = Column(String, primary_key=True) - mxc = Column(String) + mxc: ContentURI = Column(String) mime_type = Column(String) was_converted = Column(Boolean) timestamp = Column(BigInteger) diff --git a/mautrix_telegram/portal.py b/mautrix_telegram/portal.py index cf2342b4..b11ec5ee 100644 --- a/mautrix_telegram/portal.py +++ b/mautrix_telegram/portal.py @@ -13,11 +13,11 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from typing import Awaitable, Dict, List, Optional, Pattern, Tuple, Union, cast, TYPE_CHECKING, Any -from collections import deque -from datetime import datetime -from string import Template +from typing import (Awaitable, Dict, List, Optional, Pattern, Tuple, Union, Any, Deque, cast, + TYPE_CHECKING) from html import escape as escape_html +from collections import deque +from string import Template import asyncio import random import mimetypes @@ -37,7 +37,7 @@ from telethon.tl.functions.messages import ( EditChatPhotoRequest, EditChatTitleRequest, ExportChatInviteRequest, GetFullChatRequest, UpdatePinnedMessageRequest, MigrateChatRequest, SetTypingRequest, EditChatAboutRequest) from telethon.tl.functions.channels import ( - CreateChannelRequest, EditAdminRequest, EditBannedRequest, EditPhotoRequest, EditTitleRequest, + CreateChannelRequest, EditAdminRequest, EditPhotoRequest, EditTitleRequest, GetParticipantsRequest, InviteToChannelRequest, JoinChannelRequest, LeaveChannelRequest, UpdateUsernameRequest) from telethon.tl.functions.messages import ReadHistoryRequest as ReadMessageHistoryRequest @@ -63,9 +63,12 @@ from telethon.tl.types import ( TypePhotoSize, TypeUpdates, TypeUser, PhotoSize, TypeUserFull, UpdateChatUserTyping, UpdateNewChannelMessage, UpdateNewMessage, UpdateUserTyping, User, UserFull, MessageEntityPre, InputMediaUploadedDocument, InputPeerPhotoFileLocation) -from mautrix_appservice import MatrixRequestError, IntentError, AppService, IntentAPI -from .types import MatrixEventID, MatrixRoomID, MatrixUserID, TelegramID +from mautrix.errors import MatrixRequestError, IntentError +from mautrix.appservice import AppService, IntentAPI +from mautrix.types import EventID, RoomID, UserID, RoomCreatePreset, ContentURI, MessageType + +from .types import TelegramID from .context import Context from .db import Portal as DBPortal, Message as DBMessage, TelegramFile as DBTelegramFile from .util import ignore_coro, sane_mimetypes @@ -77,70 +80,96 @@ if TYPE_CHECKING: from .config import Config from .tgclient import MautrixTelegramClient -config = None # type: Config +config: Optional[Config] = None TypeMessage = Union[Message, MessageService] TypeParticipant = Union[TypeChatParticipant, TypeChannelParticipant] -DedupMXID = Tuple[MatrixEventID, TelegramID] -InviteList = Union[MatrixUserID, List[MatrixUserID]] +DedupMXID = Tuple[EventID, TelegramID] +InviteList = Union[UserID, List[UserID]] class Portal: - base_log = logging.getLogger("mau.portal") # type: logging.Logger - az = None # type: AppService - bot = None # type: Bot - loop = None # type: asyncio.AbstractEventLoop + base_log: logging.Logger = logging.getLogger("mau.portal") + az: AppService = None + bot: Bot = None + loop: asyncio.AbstractEventLoop = None # Config cache - filter_mode = None # type: str - filter_list = None # type: List[str] + filter_mode: str = None + filter_list: List[str] = None - public_portals = False # type: bool - max_initial_member_sync = -1 # type: int - sync_channel_members = True # type: bool - sync_matrix_state = True # type: bool + public_portals: bool = False + max_initial_member_sync: int = -1 + sync_channel_members: bool = True + sync_matrix_state: bool = True - dedup_pre_db_check = False # type: bool - dedup_cache_queue_length = 20 # type: int + dedup_pre_db_check: bool = False + dedup_cache_queue_length: int = 20 - alias_template = None # type: str - mx_alias_regex = None # type: Pattern - hs_domain = None # type: str + alias_template: str = None + mx_alias_regex: Pattern = None + hs_domain: str = None # Instance cache - by_mxid = {} # type: Dict[MatrixRoomID, Portal] - by_tgid = {} # type: Dict[Tuple[TelegramID, TelegramID], Portal] + by_mxid: Dict[RoomID, 'Portal'] = {} + by_tgid: Dict[Tuple[TelegramID, TelegramID], 'Portal'] = {} + + mxid: Optional[RoomID] + tgid: TelegramID + tg_receiver: TelegramID + peer_type: str + username: str + megagroup: bool + title: Optional[str] + about: Optional[str] + photo_id: Optional[str] + local_config: Dict[str, Any] + deleted: bool + log: logging.Logger + + _db_instance: DBPortal + _main_intent: Optional[IntentAPI] + _room_create_lock: asyncio.Lock + _temp_pinned_message_id: Optional[TelegramID] + _temp_pinned_message_id_space: Optional[TelegramID] + _temp_pinned_message_sender: Optional['p.Puppet'] + + _dedup: Deque[str] + _dedup_mxid: Dict[str, DedupMXID] + _dedup_action: Deque[str] + + _send_locks: Dict[int, asyncio.Lock] def __init__(self, tgid: TelegramID, peer_type: str, tg_receiver: Optional[TelegramID] = None, - mxid: Optional[MatrixRoomID] = None, username: Optional[str] = None, + mxid: Optional[RoomID] = None, username: Optional[str] = None, megagroup: Optional[bool] = False, title: Optional[str] = None, about: Optional[str] = None, photo_id: Optional[str] = None, local_config: Optional[str] = None, db_instance: DBPortal = None) -> None: - self.mxid = mxid # type: Optional[MatrixRoomID] - self.tgid = tgid # type: TelegramID - self.tg_receiver = tg_receiver or tgid # type: TelegramID - self.peer_type = peer_type # type: str - self.username = username # type: str - self.megagroup = megagroup # type: bool - self.title = title # type: Optional[str] - self.about = about # type: str - self.photo_id = photo_id # type: str - self.local_config = json.loads(local_config or "{}") # type: Dict[str, Any] - self._db_instance = db_instance # type: DBPortal - self.deleted = False # type: bool + self.mxid = mxid + self.tgid = tgid + self.tg_receiver = tg_receiver or tgid + self.peer_type = peer_type + self.username = username + self.megagroup = megagroup + self.title = title + self.about = about + self.photo_id = photo_id + self.local_config = json.loads(local_config or "{}") + self._db_instance = db_instance + self.deleted = False self.log = self.base_log.getChild(self.tgid_log) if self.tgid else self.base_log - self._main_intent = None # type: IntentAPI - self._room_create_lock = asyncio.Lock() # type: asyncio.Lock - self._temp_pinned_message_id = None # type: Optional[int] - self._temp_pinned_message_id_space = None # type: Optional[TelegramID] - self._temp_pinned_message_sender = None # type: Optional[p.Puppet] + self._main_intent = None + self._room_create_lock = asyncio.Lock() + self._temp_pinned_message_id = None + self._temp_pinned_message_id_space = None + self._temp_pinned_message_sender = None - self._dedup = deque() # type: deque - self._dedup_mxid = {} # type: Dict[str, DedupMXID] - self._dedup_action = deque() # type: deque + self._dedup = deque() + self._dedup_mxid = {} + self._dedup_action = deque() - self._send_locks = {} # type: Dict[int, asyncio.Lock] + self._send_locks = {} if tgid: self.by_tgid[self.tgid_full] = self @@ -260,7 +289,7 @@ class Portal: try: found_mxid = self._dedup_mxid[evt_hash] except KeyError: - return MatrixEventID("None"), TelegramID(0) + return EventID("None"), TelegramID(0) if found_mxid != expected_mxid: return found_mxid @@ -304,13 +333,11 @@ class Portal: # region Matrix room info updating async def invite_to_matrix(self, users: InviteList) -> None: - if isinstance(users, str): - await self.main_intent.invite(self.mxid, users, check_cache=True) - elif isinstance(users, list): + if isinstance(users, list): for user in users: - await self.main_intent.invite(self.mxid, user, check_cache=True) + await self.main_intent.invite_user(self.mxid, user, check_cache=True) else: - raise ValueError("Invalid invite identifier given to invite_matrix()") + await self.main_intent.invite_user(self.mxid, users, check_cache=True) async def update_matrix_room(self, user: 'AbstractUser', entity: Union[TypeChat, User], direct: bool, puppet: p.Puppet = None, levels: Dict = None, @@ -348,7 +375,7 @@ class Portal: return await self._create_matrix_room(user, entity, invites) async def _create_matrix_room(self, user: 'AbstractUser', entity: TypeChat, invites: InviteList - ) -> Optional[MatrixRoomID]: + ) -> Optional[RoomID]: direct = self.peer_type == "user" if self.mxid: @@ -375,11 +402,11 @@ class Portal: self.megagroup = entity.megagroup if self.peer_type == "channel" and entity.username: - public = Portal.public_portals + preset = RoomCreatePreset.PUBLIC alias = self._get_alias_localpart(entity.username) self.username = entity.username else: - public = False + preset = RoomCreatePreset.PRIVATE # TODO invite link alias? alias = None @@ -402,13 +429,13 @@ class Portal: "content": {"groups": [config["appservice.community_id"]]}, }) - room_id = await self.main_intent.create_room(alias=alias, is_public=public, + room_id = await self.main_intent.create_room(alias_localpart=alias, preset=preset, is_direct=direct, invitees=invites or [], name=self.title, initial_state=initial_state) if not room_id: raise Exception(f"Failed to create room") - self.mxid = MatrixRoomID(room_id) + self.mxid = RoomID(room_id) self.by_mxid[self.mxid] = self self.save() self.az.state_store.set_power_levels(self.mxid, power_levels) @@ -510,7 +537,7 @@ class Portal: and Portal.max_initial_member_sync == -1 and (self.megagroup or self.peer_type != "channel")) if trust_member_list: - joined_mxids = cast(List[MatrixUserID], + joined_mxids = cast(List[UserID], await self.main_intent.get_room_members(self.mxid)) for user_mxid in joined_mxids: if user_mxid == self.az.bot_mxid: @@ -519,7 +546,7 @@ class Portal: if puppet_id and puppet_id not in allowed_tgids: if self.bot and puppet_id == self.bot.tgid: self.bot.remove_chat(self.tgid) - await self.main_intent.kick(self.mxid, user_mxid, + await self.main_intent.kick_user(self.mxid, user_mxid, "User had left this Telegram chat.") continue mx_user = u.User.get_by_mxid(user_mxid, create=False) @@ -527,7 +554,7 @@ class Portal: mx_user.unregister_portal(self) if mx_user and not self.has_bot and mx_user.tgid not in allowed_tgids: - await self.main_intent.kick(self.mxid, mx_user.mxid, + await self.main_intent.kick_user(self.mxid, mx_user.mxid, "You had left this Telegram chat.") continue @@ -551,12 +578,12 @@ class Portal: if sender and sender.tgid != puppet.tgid else "Left Telegram chat") if sender and sender.tgid != puppet.tgid: - await self.main_intent.kick(self.mxid, puppet.mxid, kick_message) + await self.main_intent.kick_user(self.mxid, puppet.mxid, kick_message) else: await puppet.intent.leave_room(self.mxid) if user: user.unregister_portal(self) - await self.main_intent.kick(self.mxid, user.mxid, kick_message) + await self.main_intent.kick_user(self.mxid, user.mxid, kick_message) async def update_info(self, user: 'AbstractUser', entity: TypeChat = None) -> None: if self.peer_type == "user": @@ -625,7 +652,8 @@ class Portal: return None, None if isinstance(photo, Document) and not photo.thumbs: return None, None - largest = max(photo.sizes if isinstance(photo, Photo) else photo.thumbs, + + largest = max(photo.thumbs if isinstance(photo, Document) else photo.sizes, key=(lambda photo2: (len(photo2.bytes) if not isinstance(photo2, PhotoSize) else photo2.size))) @@ -663,7 +691,7 @@ class Portal: raise ValueError(f"Unknown photo type {type(photo)}") if self.photo_id != photo_id: if not photo_id: - await self.main_intent.set_room_avatar(self.mxid, "") + await self.main_intent.set_room_avatar(self.mxid, ContentURI("")) self.photo_id = "" if save: self.save() @@ -739,7 +767,7 @@ class Portal: authenticated = [] # type: List[u.User] has_bot = self.has_bot for member_str in members: - member = MatrixUserID(member_str) + member = UserID(member_str) if p.Puppet.get_id_from_mxid(member) or member == self.main_intent.mxid: continue user = await u.User.get_by_mxid(member).ensure_started() # type: u.User @@ -749,20 +777,20 @@ class Portal: return authenticated @staticmethod - async def cleanup_room(intent: IntentAPI, room_id: str, message: str = "Portal deleted", + async def cleanup_room(intent: IntentAPI, room_id: RoomID, message: str = "Portal deleted", puppets_only: bool = False) -> None: try: members = await intent.get_room_members(room_id) except MatrixRequestError: members = [] for user in members: - puppet = p.Puppet.get_by_mxid(MatrixUserID(user), create=False) + puppet = p.Puppet.get_by_mxid(UserID(user), create=False) if user != intent.mxid and (not puppets_only or puppet): try: if puppet: await puppet.intent.leave_room(room_id) else: - await intent.kick(room_id, user, message) + await intent.kick_user(room_id, user, message) except (MatrixRequestError, IntentError): pass await intent.leave_room(room_id) @@ -816,7 +844,7 @@ class Portal: } async def name_change_matrix(self, user: 'u.User', displayname: str, prev_displayname: str, - event_id: MatrixEventID) -> None: + event_id: EventID) -> None: async with self.require_send_lock(self.bot.tgid): message = await self._get_state_change_message( "name_change", user, @@ -830,6 +858,7 @@ class Portal: self.is_duplicate(response, (event_id, space)) async def get_displayname(self, user: 'u.User') -> str: + # FIXME mautrix4 return (await self.main_intent.get_displayname(self.mxid, user.mxid) or user.mxid) @@ -851,7 +880,7 @@ class Portal: return user.client(SetTypingRequest( self.peer, action() if typing else SendMessageCancelAction())) - async def mark_read(self, user: 'u.User', event_id: MatrixEventID) -> None: + async def mark_read(self, user: 'u.User', event_id: EventID) -> None: if user.is_bot: return space = self.tgid if self.peer_type == "channel" else user.tgid @@ -886,7 +915,7 @@ class Portal: if not ban: await source.client.edit_permissions(channel, target, view_messages=True) - async def leave_matrix(self, user: 'u.User', event_id: MatrixEventID) -> None: + async def leave_matrix(self, user: 'u.User', event_id: EventID) -> None: if await user.needs_relaybot(self): if not self.has_bot: return @@ -915,7 +944,7 @@ class Portal: channel = await self.get_input_entity(user) await user.client(LeaveChannelRequest(channel=channel)) - async def join_matrix(self, user: 'u.User', event_id: MatrixEventID) -> None: + async def join_matrix(self, user: 'u.User', event_id: EventID) -> None: if await user.needs_relaybot(self): async with self.require_send_lock(self.bot.tgid): message = await self._get_state_change_message("join", user) @@ -993,7 +1022,7 @@ class Portal: except KeyError: return None - async def _handle_matrix_text(self, sender_id: TelegramID, event_id: MatrixEventID, + async def _handle_matrix_text(self, sender_id: TelegramID, event_id: EventID, space: TelegramID, client: 'MautrixTelegramClient', message: Dict, reply_to: TelegramID) -> None: lock = self.require_send_lock(sender_id) @@ -1015,18 +1044,18 @@ class Portal: link_preview=lp) self._add_telegram_message_to_db(event_id, space, 0, response) - async def _handle_matrix_file(self, msgtype: str, sender_id: TelegramID, - event_id: MatrixEventID, space: TelegramID, + async def _handle_matrix_file(self, msgtype: MessageType, sender_id: TelegramID, + event_id: EventID, space: TelegramID, client: 'MautrixTelegramClient', message: dict, reply_to: TelegramID) -> None: - file = await self.main_intent.download_file(message["url"]) + file = await self.main_intent.download_media(message["url"]) info = message.get("info", {}) mime = info.get("mimetype", None) w, h = None, None - if msgtype == "m.sticker": + if msgtype == MessageType.STICKER: if mime != "image/gif": mime, file, w, h = util.convert_image(file, source_mime=mime, target_type="webp") else: @@ -1048,14 +1077,8 @@ class Portal: max_image_size=config["bridge.image_as_file_size"] * 1000 ** 2) lock = self.require_send_lock(sender_id) async with lock: - relates_to = message.get("m.relates_to", None) or {} - if relates_to.get("rel_type", None) == "m.replace": - orig_msg = DBMessage.get_by_mxid(relates_to.get("event_id", ""), self.mxid, space) - if orig_msg: - response = await client.edit_message(self.peer, orig_msg.tgid, - caption, file=media) - self._add_telegram_message_to_db(event_id, space, -1, response) - return + if await self._matrix_document_edit(client, message, space, caption, media, event_id): + return try: response = await client.send_media(self.peer, media, reply_to=reply_to, caption=caption) @@ -1066,7 +1089,20 @@ class Portal: caption=caption) self._add_telegram_message_to_db(event_id, space, 0, response) - async def _handle_matrix_location(self, sender_id: TelegramID, event_id: MatrixEventID, + async def _matrix_document_edit(self, client: 'MautrixTelegramClient', message: dict, + space: TelegramID, caption: str, media: Any, event_id: EventID + ) -> bool: + relates_to = message.get("m.relates_to", None) or {} + if relates_to.get("rel_type", None) == "m.replace": + orig_msg = DBMessage.get_by_mxid(relates_to.get("event_id", ""), self.mxid, space) + if orig_msg: + response = await client.edit_message(self.peer, orig_msg.tgid, + caption, file=media) + self._add_telegram_message_to_db(event_id, space, -1, response) + return True + return False + + async def _handle_matrix_location(self, sender_id: TelegramID, event_id: EventID, space: TelegramID, client: 'MautrixTelegramClient', message: Dict[str, Any], reply_to: TelegramID) -> None: try: @@ -1080,19 +1116,13 @@ class Portal: lock = self.require_send_lock(sender_id) async with lock: - relates_to = message.get("m.relates_to", None) or {} - if relates_to.get("rel_type", None) == "m.replace": - orig_msg = DBMessage.get_by_mxid(relates_to.get("event_id", ""), self.mxid, space) - if orig_msg: - response = await client.edit_message(self.peer, orig_msg.tgid, - caption, file=media) - self._add_telegram_message_to_db(event_id, space, -1, response) - return + if await self._matrix_document_edit(client, message, space, caption, media, event_id): + return response = await client.send_media(self.peer, media, reply_to=reply_to, caption=caption, entities=entities) self._add_telegram_message_to_db(event_id, space, 0, response) - def _add_telegram_message_to_db(self, event_id: MatrixEventID, space: TelegramID, + def _add_telegram_message_to_db(self, event_id: EventID, space: TelegramID, edit_index: int, response: TypeMessage) -> None: self.log.debug("Handled Matrix message: %s", response) self.is_duplicate(response, (event_id, space), force_hash=edit_index != 0) @@ -1107,7 +1137,7 @@ class Portal: edit_index=edit_index).insert() async def handle_matrix_message(self, sender: 'u.User', message: Dict[str, Any], - event_id: MatrixEventID) -> None: + event_id: EventID) -> None: if "body" not in message or "msgtype" not in message: self.log.debug(f"Ignoring message {event_id} in {self.mxid} without body or msgtype") return @@ -1146,7 +1176,7 @@ class Portal: self.log.debug(f"Unhandled Matrix event: {message}") async def handle_matrix_pin(self, sender: 'u.User', - pinned_message: Optional[MatrixEventID]) -> None: + pinned_message: Optional[EventID]) -> None: if self.peer_type != "chat" and self.peer_type != "channel": return try: @@ -1162,7 +1192,7 @@ class Portal: except ChatNotModifiedError: pass - async def handle_matrix_deletion(self, deleter: 'u.User', event_id: MatrixEventID) -> None: + async def handle_matrix_deletion(self, deleter: 'u.User', event_id: EventID) -> None: real_deleter = deleter if not await deleter.needs_relaybot(self) else self.bot space = self.tgid if self.peer_type == "channel" else real_deleter.tgid message = DBMessage.get_by_mxid(event_id, self.mxid, space) @@ -1190,7 +1220,7 @@ class Portal: user_id=user_id, admin_rights=rights)) async def handle_matrix_power_levels(self, sender: 'u.User', - new_users: Dict[MatrixUserID, int], + new_users: Dict[UserID, int], old_users: Dict[str, int]) -> None: # TODO handle all power level changes and bridge exact admin rights to supergroups/channels for user, level in new_users.items(): @@ -1228,12 +1258,12 @@ class Portal: self.title = title self.save() - async def handle_matrix_avatar(self, sender: 'u.User', url: str) -> None: + async def handle_matrix_avatar(self, sender: 'u.User', url: ContentURI) -> None: if self.peer_type not in ("chat", "channel"): # Invalid peer type return - file = await self.main_intent.download_file(url) + file = await self.main_intent.download_media(url) mime = magic.from_buffer(file, mime=True) ext = sane_mimetypes.guess_extension(mime) uploaded = await sender.client.upload_file(file, file_name=f"avatar{ext}", use_cache=False) @@ -1255,7 +1285,7 @@ class Portal: self.save() break - async def handle_matrix_upgrade(self, new_room: MatrixRoomID) -> None: + async def handle_matrix_upgrade(self, new_room: RoomID) -> None: old_room = self.mxid self.migrate_and_save_matrix(new_room) await self.main_intent.join_room(new_room) @@ -1267,7 +1297,7 @@ class Portal: if not entity: user_mxids = await self.main_intent.get_room_members(self.mxid) for user_str in user_mxids: - user_id = MatrixUserID(user_str) + user_id = UserID(user_str) if user_id == self.az.bot_mxid: continue user = u.User.get_by_mxid(user_id, create=False) @@ -1299,7 +1329,7 @@ class Portal: user_tgids = set() user_mxids = await self.main_intent.get_room_members(self.mxid, ("join", "invite")) for user_str in user_mxids: - user = MatrixUserID(user_str) + user = UserID(user_str) if user == self.az.bot_mxid: continue mx_user = u.User.get_by_mxid(user, create=False) @@ -1405,7 +1435,7 @@ class Portal: return None async def handle_telegram_photo(self, source: 'AbstractUser', intent: IntentAPI, evt: Message, - relates_to: Dict = None) -> Optional[Dict]: + relates_to: Dict = None) -> Optional[EventID]: loc, largest_size = self._get_largest_photo_size(evt.media.photo) file = await util.transfer_file_to_matrix(source.client, intent, loc) if not file: @@ -1503,7 +1533,7 @@ class Portal: return info, name async def handle_telegram_document(self, source: 'AbstractUser', intent: IntentAPI, - evt: Message, relates_to: dict = None) -> Optional[Dict]: + evt: Message, relates_to: dict = None) -> Optional[EventID]: document = evt.media.document attrs = self._parse_telegram_document_attributes(document.attributes) @@ -1552,7 +1582,7 @@ class Portal: return await intent.send_file(**kwargs) def handle_telegram_location(self, _: 'AbstractUser', intent: IntentAPI, evt: Message, - relates_to: dict = None) -> Awaitable[dict]: + relates_to: dict = None) -> Awaitable[EventID]: location = evt.media.geo long = location.long lat = location.lat @@ -1580,7 +1610,7 @@ class Portal: }, timestamp=evt.date, external_url=self.get_external_url(evt)) async def handle_telegram_text(self, source: 'AbstractUser', intent: IntentAPI, is_bot: bool, - evt: Message) -> dict: + evt: Message) -> EventID: self.log.debug(f"Sending {evt.message} to {self.mxid} by {intent.mxid}") text, html, relates_to = await formatter.telegram_to_matrix(evt, source, self.main_intent) await intent.set_typing(self.mxid, is_typing=False) @@ -1590,7 +1620,7 @@ class Portal: external_url=self.get_external_url(evt)) async def handle_telegram_unsupported(self, source: 'AbstractUser', intent: IntentAPI, - evt: Message, relates_to: dict = None) -> dict: + evt: Message, relates_to: dict = None) -> EventID: override_text = ("This message is not supported on your version of Mautrix-Telegram. " "Please check https://github.com/tulir/mautrix-telegram or ask your " "bridge administrator about possible updates.") @@ -1607,7 +1637,7 @@ class Portal: }, timestamp=evt.date, external_url=self.get_external_url(evt)) async def handle_telegram_poll(self, source: 'AbstractUser', intent: IntentAPI, evt: Message, - relates_to: dict) -> dict: + relates_to: dict) -> EventID: poll = evt.media.poll # type: Poll poll_id = self._encode_msgid(source, evt) @@ -1658,7 +1688,7 @@ class Portal: return base64.b64encode(play_id).decode("utf-8").rstrip("=") async def handle_telegram_game(self, source: 'AbstractUser', intent: IntentAPI, - evt: Message, relates_to: dict = None): + evt: Message, relates_to: dict = None) -> EventID: game = evt.media.game play_id = self._encode_msgid(source, evt) command = f"!tg play {play_id}" @@ -1693,7 +1723,7 @@ class Portal: tg_space = self.tgid if self.peer_type == "channel" else source.tgid - temporary_identifier = MatrixEventID( + temporary_identifier = EventID( f"${random.randint(1000000000000, 9999999999999)}TGBRIDGEDITEMP") duplicate_found = self.is_duplicate(evt, (temporary_identifier, tg_space), force_hash=True) if duplicate_found: @@ -1758,7 +1788,7 @@ class Portal: tg_space = self.tgid if self.peer_type == "channel" else source.tgid - temporary_identifier = MatrixEventID( + temporary_identifier = EventID( f"${random.randint(1000000000000, 9999999999999)}TGBRIDGETEMP") duplicate_found = self.is_duplicate(evt, (temporary_identifier, tg_space)) if duplicate_found: @@ -2034,7 +2064,7 @@ class Portal: self.log = self.base_log.getChild(str(self.tgid)) self.log.info(f"Telegram chat upgraded from {old_id}") - def migrate_and_save_matrix(self, new_id: MatrixRoomID) -> None: + def migrate_and_save_matrix(self, new_id: RoomID) -> None: try: del self.by_mxid[self.mxid] except KeyError: @@ -2073,7 +2103,7 @@ class Portal: # region Class instance lookup @classmethod - def get_by_mxid(cls, mxid: MatrixRoomID) -> Optional['Portal']: + def get_by_mxid(cls, mxid: RoomID) -> Optional['Portal']: try: return cls.by_mxid[mxid] except KeyError: diff --git a/optional-requirements.txt b/optional-requirements.txt index 13017dc0..b8751b97 100644 --- a/optional-requirements.txt +++ b/optional-requirements.txt @@ -1,4 +1,4 @@ cryptg Pillow moviepy -prometheus-client +prometheus_client