From 5fba658c66b96a24ff6c6f75b0418938acffa7e5 Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Thu, 20 Jun 2019 21:42:22 +0300 Subject: [PATCH] Update to telethon 1.8. Fixes #334 --- mautrix_telegram/abstract_user.py | 2 +- mautrix_telegram/portal.py | 86 +++++++++++++++++--------- mautrix_telegram/puppet.py | 35 +++++++++-- mautrix_telegram/util/file_transfer.py | 12 ++-- setup.py | 2 +- 5 files changed, 96 insertions(+), 41 deletions(-) diff --git a/mautrix_telegram/abstract_user.py b/mautrix_telegram/abstract_user.py index 5094769c..dd9f3d02 100644 --- a/mautrix_telegram/abstract_user.py +++ b/mautrix_telegram/abstract_user.py @@ -309,7 +309,7 @@ class AbstractUser(ABC): if await puppet.update_displayname(self, update): puppet.save() elif isinstance(update, UpdateUserPhoto): - if await puppet.update_avatar(self, update.photo.photo_big): + if await puppet.update_avatar(self, update.photo): puppet.save() else: self.log.warning("Unexpected other user info update: %s", update) diff --git a/mautrix_telegram/portal.py b/mautrix_telegram/portal.py index aced8f22..c3a022e0 100644 --- a/mautrix_telegram/portal.py +++ b/mautrix_telegram/portal.py @@ -47,13 +47,13 @@ from telethon.errors import (ChatAdminRequiredError, ChatNotModifiedError, Photo PhotoInvalidDimensionsError, PhotoSaveFileInvalidError) from telethon.tl.patched import Message, MessageService from telethon.tl.types import ( - Channel, ChatAdminRights, ChatBannedRights, ChannelFull, ChannelParticipantAdmin, + Channel, ChatAdminRights, ChatBannedRights, ChannelFull, ChannelParticipantAdmin, Document, ChannelParticipantCreator, ChannelParticipantsRecent, ChannelParticipantsSearch, Chat, ChatFull, ChatInviteEmpty, ChatParticipantAdmin, ChatParticipantCreator, ChatPhoto, Poll, - DocumentAttributeFilename, DocumentAttributeImageSize, DocumentAttributeSticker, - DocumentAttributeVideo, FileLocation, GeoPoint, InputChannel, InputChatUploadedPhoto, + DocumentAttributeFilename, DocumentAttributeImageSize, DocumentAttributeSticker, PhotoEmpty, + DocumentAttributeVideo, GeoPoint, InputChannel, InputChatUploadedPhoto, InputPhotoFileLocation, InputPeerChannel, InputPeerChat, InputPeerUser, InputUser, InputUserSelf, MessageMediaPoll, - MessageActionChannelCreate, MessageActionChatAddUser, MessageActionChatCreate, + MessageActionChannelCreate, MessageActionChatAddUser, MessageActionChatCreate, ChatPhotoEmpty, MessageActionChatDeletePhoto, MessageActionChatDeleteUser, MessageActionChatEditPhoto, MessageActionChatEditTitle, MessageActionChatJoinedByLink, MessageActionChatMigrateTo, MessageActionPinMessage, MessageActionGameScore, MessageMediaContact, MessageMediaDocument, @@ -63,7 +63,7 @@ from telethon.tl.types import ( TypeDocumentAttribute, TypeInputPeer, TypeMessageAction, TypeMessageEntity, TypePeer, TypePhotoSize, TypeUpdates, TypeUser, PhotoSize, TypeUserFull, UpdateChatUserTyping, UpdateNewChannelMessage, UpdateNewMessage, UpdateUserTyping, User, UserFull, MessageEntityPre, - InputMediaUploadedDocument) + InputMediaUploadedDocument, InputPeerPhotoFileLocation) from mautrix_appservice import MatrixRequestError, IntentError, AppService, IntentAPI from .types import MatrixEventID, MatrixRoomID, MatrixUserID, TelegramID @@ -575,7 +575,7 @@ class Portal: changed = await self.update_title(entity.title) or changed if isinstance(entity.photo, ChatPhoto): - changed = await self.update_avatar(user, entity.photo.photo_big) or changed + changed = await self.update_avatar(user, entity.photo) or changed if changed: self.save() @@ -616,12 +616,21 @@ class Portal: return False @staticmethod - def _get_largest_photo_size(photo: Union[Photo, List[TypePhotoSize]] - ) -> Optional[TypePhotoSize]: + def _get_largest_photo_size(photo: Union[Photo, Document] + ) -> Tuple[Optional[InputPhotoFileLocation], + Optional[TypePhotoSize]]: if not photo: - return None - return max(photo.sizes if isinstance(photo, Photo) else photo, key=(lambda photo2: ( - len(photo2.bytes) if not isinstance(photo2, PhotoSize) else photo2.size))) + return None, None + largest = max(photo.sizes if isinstance(photo, Photo) else photo.thumbs, + key=(lambda photo2: (len(photo2.bytes) + if not isinstance(photo2, PhotoSize) + else photo2.size))) + return InputPhotoFileLocation( + id=photo.id, + access_hash=photo.access_hash, + file_reference=photo.file_reference, + thumb_size=largest.type, + ), largest async def remove_avatar(self, _: 'AbstractUser', save: bool = False) -> None: await self.main_intent.set_room_avatar(self.mxid, None) @@ -629,11 +638,33 @@ class Portal: if save: self.save() - async def update_avatar(self, user: 'AbstractUser', photo: FileLocation, + async def update_avatar(self, user: 'AbstractUser', + photo: Union[ChatPhoto, ChatPhotoEmpty, Photo, PhotoEmpty], save: bool = False) -> bool: - photo_id = f"{photo.volume_id}-{photo.local_id}" + if isinstance(photo, ChatPhoto): + loc = InputPeerPhotoFileLocation( + peer=await self.get_input_entity(user), + local_id=photo.photo_big.local_id, + volume_id=photo.photo_big.volume_id, + big=True + ) + photo_id = f"{loc.volume_id}-{loc.local_id}" + elif isinstance(photo, Photo): + loc, largest = self._get_largest_photo_size(photo) + photo_id = f"{largest.location.volume_id}-{largest.location.local_id}" + elif isinstance(photo, (ChatPhotoEmpty, PhotoEmpty)): + photo_id = "" + loc = None + else: + raise ValueError(f"Unknown photo type {type(photo)}") if self.photo_id != photo_id: - file = await util.transfer_file_to_matrix(user.client, self.main_intent, photo) + if not photo_id: + await self.main_intent.set_room_avatar(self.mxid, "") + self.photo_id = "" + if save: + self.save() + return True + file = await util.transfer_file_to_matrix(user.client, self.main_intent, loc) if file: await self.main_intent.set_room_avatar(self.mxid, file.mxc) self.photo_id = photo_id @@ -1212,8 +1243,8 @@ class Portal: and isinstance(update.message, MessageService) and isinstance(update.message.action, MessageActionChatEditPhoto)) if is_photo_update: - loc = self._get_largest_photo_size(update.message.action.photo).location - self.photo_id = f"{loc.volume_id}-{loc.local_id}" + loc, size = self._get_largest_photo_size(update.message.action.photo) + self.photo_id = f"{size.location.volume_id}-{size.location.local_id}" self.save() break @@ -1368,8 +1399,8 @@ class Portal: async def handle_telegram_photo(self, source: 'AbstractUser', intent: IntentAPI, evt: Message, relates_to: Dict = None) -> Optional[Dict]: - largest_size = self._get_largest_photo_size(evt.media.photo) - file = await util.transfer_file_to_matrix(source.client, intent, largest_size.location) + 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: return None if self.get_config("inline_images") and (evt.message @@ -1429,7 +1460,7 @@ class Portal: @staticmethod def _parse_telegram_document_meta(evt: Message, file: DBTelegramFile, attrs: Dict, - thumb: TypePhotoSize) -> Tuple[Dict, str]: + thumb_size: TypePhotoSize) -> Tuple[Dict, str]: document = evt.media.document name = evt.message or attrs["name"] if attrs["is_sticker"]: @@ -1461,8 +1492,8 @@ class Portal: info["thumbnail_url"] = file.thumbnail.mxc info["thumbnail_info"] = { "mimetype": file.thumbnail.mime_type, - "h": file.thumbnail.height or thumb.h, - "w": file.thumbnail.width or thumb.w, + "h": file.thumbnail.height or thumb_size.h, + "w": file.thumbnail.width or thumb_size.w, "size": file.thumbnail.size, } @@ -1473,16 +1504,16 @@ class Portal: document = evt.media.document attrs = self._parse_telegram_document_attributes(document.attributes) - thumb = self._get_largest_photo_size(document.thumbs) - if thumb and not isinstance(thumb, (PhotoSize, PhotoCachedSize)): - self.log.debug(f"Unsupported thumbnail type {type(thumb)}") + thumb_loc, thumb_size = self._get_largest_photo_size(document) + if thumb_size and not isinstance(thumb_size, (PhotoSize, PhotoCachedSize)): + self.log.debug(f"Unsupported thumbnail type {type(thumb_size)}") thumb = None - file = await util.transfer_file_to_matrix(source.client, intent, document, thumb, + file = await util.transfer_file_to_matrix(source.client, intent, document, thumb_loc, is_sticker=attrs["is_sticker"]) if not file: return None - info, name = self._parse_telegram_document_meta(evt, file, attrs, thumb) + info, name = self._parse_telegram_document_meta(evt, file, attrs, thumb_size) await intent.set_typing(self.mxid, is_typing=False) @@ -1819,8 +1850,7 @@ class Portal: if isinstance(action, MessageActionChatEditTitle): await self.update_title(action.title, save=True) elif isinstance(action, MessageActionChatEditPhoto): - largest_size = self._get_largest_photo_size(action.photo) - await self.update_avatar(source, largest_size.location, save=True) + await self.update_avatar(source, action.photo, save=True) elif isinstance(action, MessageActionChatDeletePhoto): await self.remove_avatar(source, save=True) elif isinstance(action, MessageActionChatAddUser): diff --git a/mautrix_telegram/puppet.py b/mautrix_telegram/puppet.py index 7676a251..b715ada0 100644 --- a/mautrix_telegram/puppet.py +++ b/mautrix_telegram/puppet.py @@ -22,7 +22,8 @@ import asyncio import logging import re -from telethon.tl.types import UserProfilePhoto, User, FileLocation, UpdateUserName, PeerUser +from telethon.tl.types import (UserProfilePhoto, User, UpdateUserName, PeerUser, TypeInputPeer, + InputPeerPhotoFileLocation, UserProfilePhotoEmpty) from mautrix_appservice import AppService, IntentAPI, IntentError, MatrixRequestError from .types import MatrixUserID, TelegramID @@ -113,6 +114,9 @@ class Puppet: match = regex.match(self.displayname) return match.group(1) or self.displayname + def get_input_entity(self, user: 'AbstractUser') -> Awaitable[TypeInputPeer]: + return user.client.get_input_entity(PeerUser(user_id=self.tgid)) + # region Custom puppet management def _fresh_intent(self) -> IntentAPI: return (self.az.intent.user(self.custom_mxid, self.access_token) @@ -348,7 +352,7 @@ class Puppet: changed = await self.update_displayname(source, info) or changed if isinstance(info.photo, UserProfilePhoto): - changed = await self.update_avatar(source, info.photo.photo_big) or changed + changed = await self.update_avatar(source, info.photo) or changed self.is_bot = info.bot @@ -384,13 +388,32 @@ class Puppet: return True return False - async def update_avatar(self, source: 'AbstractUser', photo: FileLocation) -> bool: + async def update_avatar(self, source: 'AbstractUser', + photo: Union[UserProfilePhoto, UserProfilePhotoEmpty]) -> bool: if self.disable_updates: return False - photo_id = f"{photo.volume_id}-{photo.local_id}" + + if isinstance(photo, UserProfilePhotoEmpty): + photo_id = "" + else: + photo_id = str(photo.photo_id) if self.photo_id != photo_id: - file = await util.transfer_file_to_matrix(source.client, self.default_mxid_intent, - photo) + if not photo_id: + self.photo_id = "" + try: + await self.default_mxid_intent.set_avatar("") + except MatrixRequestError: + self.log.exception("Failed to set avatar") + self.photo_id = "" + return True + + loc = InputPeerPhotoFileLocation( + peer=await self.get_input_entity(source), + local_id=photo.photo_big.local_id, + volume_id=photo.photo_big.volume_id, + big=True + ) + file = await util.transfer_file_to_matrix(source.client, self.default_mxid_intent, loc) if file: self.photo_id = photo_id try: diff --git a/mautrix_telegram/util/file_transfer.py b/mautrix_telegram/util/file_transfer.py index fea25088..27d33c5d 100644 --- a/mautrix_telegram/util/file_transfer.py +++ b/mautrix_telegram/util/file_transfer.py @@ -23,8 +23,9 @@ import asyncio import magic from sqlalchemy.exc import IntegrityError, InvalidRequestError -from telethon.tl.types import (Document, FileLocation, InputFileLocation, InputDocumentFileLocation, - TypePhotoSize, PhotoSize, PhotoCachedSize) +from telethon.tl.types import (Document, InputFileLocation, InputDocumentFileLocation, + TypePhotoSize, PhotoSize, PhotoCachedSize, InputPhotoFileLocation, + InputPeerPhotoFileLocation) from telethon.errors import (AuthBytesInvalidError, AuthKeyInvalidError, LocationInvalidError, SecurityError) from mautrix_appservice import IntentAPI @@ -47,7 +48,8 @@ except ImportError: log = logging.getLogger("mau.util") # type: logging.Logger -TypeLocation = Union[Document, InputDocumentFileLocation, FileLocation, InputFileLocation] +TypeLocation = Union[Document, InputDocumentFileLocation, InputPeerPhotoFileLocation, + InputFileLocation, InputPhotoFileLocation] def convert_image(file: bytes, source_mime: str = "image/webp", target_type: str = "png", @@ -99,9 +101,9 @@ def _read_video_thumbnail(data: bytes, video_ext: str = "mp4", frame_ext: str = def _location_to_id(location: TypeLocation) -> str: - if isinstance(location, (Document, InputDocumentFileLocation)): + if isinstance(location, (Document, InputDocumentFileLocation, InputPhotoFileLocation)): return f"{location.id}-{location.access_hash}" - elif isinstance(location, (FileLocation, InputFileLocation)): + elif isinstance(location, (InputFileLocation, InputPeerPhotoFileLocation)): return f"{location.volume_id}-{location.local_id}" diff --git a/setup.py b/setup.py index 4ca57a12..2464b42c 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,7 @@ setuptools.setup( "ruamel.yaml>=0.15.35,<0.16", "future-fstrings>=0.4.2", "python-magic>=0.4.15,<0.5", - "telethon>=1.5.5,<1.7", + "telethon>=1.7,<1.9", "telethon-session-sqlalchemy>=0.2.14,<0.3", ], extras_require=extras,