diff --git a/mautrix_telegram/config.py b/mautrix_telegram/config.py index d9a24a2f..f69cc5c4 100644 --- a/mautrix_telegram/config.py +++ b/mautrix_telegram/config.py @@ -123,6 +123,7 @@ class Config(BaseBridgeConfig): copy("bridge.animated_sticker.args") copy("bridge.encryption.allow") copy("bridge.encryption.default") + copy("bridge.private_chat_portal_meta") copy("bridge.initial_power_level_overrides.group") copy("bridge.initial_power_level_overrides.user") diff --git a/mautrix_telegram/example-config.yaml b/mautrix_telegram/example-config.yaml index 70bc1111..110d4189 100644 --- a/mautrix_telegram/example-config.yaml +++ b/mautrix_telegram/example-config.yaml @@ -207,6 +207,9 @@ bridge: # Default to encryption, force-enable encryption in all portals the bridge creates # This will cause the bridge bot to be in private chats for the encryption to work properly. default: false + # Whether or not to explicitly set the avatar and room name for private + # chat portal rooms. This will be implicitly enabled if encryption.default is true. + private_chat_portal_meta: false # Overrides for base power levels. initial_power_level_overrides: diff --git a/mautrix_telegram/portal/base.py b/mautrix_telegram/portal/base.py index ef6fce32..d5520d71 100644 --- a/mautrix_telegram/portal/base.py +++ b/mautrix_telegram/portal/base.py @@ -69,6 +69,7 @@ class BasePortal(ABC): sync_channel_members: bool = True sync_matrix_state: bool = True public_portals: bool = False + private_chat_portal_meta: bool = False alias_template: SimpleTemplate[str] hs_domain: str @@ -518,6 +519,7 @@ def init(context: Context) -> None: BasePortal.sync_channel_members = config["bridge.sync_channel_members"] BasePortal.sync_matrix_state = config["bridge.sync_matrix_state"] BasePortal.public_portals = config["bridge.public_portals"] + BasePortal.private_chat_portal_meta = config["bridge.private_chat_portal_meta"] BasePortal.filter_mode = config["bridge.filter.mode"] BasePortal.filter_list = config["bridge.filter.list"] BasePortal.hs_domain = config["homeserver.domain"] diff --git a/mautrix_telegram/portal/metadata.py b/mautrix_telegram/portal/metadata.py index 3032d195..1ca971f8 100644 --- a/mautrix_telegram/portal/metadata.py +++ b/mautrix_telegram/portal/metadata.py @@ -13,7 +13,7 @@ # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -from typing import List, Optional, Tuple, Union, Callable, TYPE_CHECKING +from typing import List, Optional, Tuple, Union, Callable, Awaitable, TYPE_CHECKING from abc import ABC import asyncio @@ -26,7 +26,7 @@ from telethon.tl.types import ( Channel, ChatBannedRights, ChannelParticipantsRecent, ChannelParticipantsSearch, ChatPhoto, PhotoEmpty, InputChannel, InputUser, ChatPhotoEmpty, PeerUser, Photo, TypeChat, TypeInputPeer, TypeUser, User, InputPeerPhotoFileLocation, ChatParticipantAdmin, ChannelParticipantAdmin, - ChatParticipantCreator, ChannelParticipantCreator) + ChatParticipantCreator, ChannelParticipantCreator, UserProfilePhoto, UserProfilePhotoEmpty) from mautrix.errors import MForbidden from mautrix.types import (RoomID, UserID, RoomCreatePreset, EventType, Membership, Member, @@ -218,10 +218,17 @@ class PortalMetadata(BasePortal, ABC): puppet = p.Puppet.get(self.tgid) await puppet.update_info(user, entity) await puppet.intent_for(self).join_room(self.mxid) + if self.encrypted or self.private_chat_portal_meta: + # The bridge bot needs to join for e2ee, but that messes up the default name + # generation. If/when canonical DMs happen, this might not be necessary anymore. + changed = await self._update_title(puppet.displayname) + changed = await self._update_avatar(user, entity.photo) or changed + if changed: + self.save() if self.sync_matrix_state: await self.sync_matrix_members() - async def create_matrix_room(self, user: 'AbstractUser', entity: TypeChat = None, + async def create_matrix_room(self, user: 'AbstractUser', entity: Union[TypeChat, User] = None, invites: InviteList = None, update_if_exists: bool = True, synchronous: bool = False) -> Optional[str]: if self.mxid: @@ -245,8 +252,8 @@ class PortalMetadata(BasePortal, ABC): except Exception: self.log.exception("Fatal error creating Matrix room") - async def _create_matrix_room(self, user: 'AbstractUser', entity: TypeChat, invites: InviteList - ) -> Optional[RoomID]: + async def _create_matrix_room(self, user: 'AbstractUser', entity: Union[TypeChat, User], + invites: InviteList) -> Optional[RoomID]: direct = self.peer_type == "user" if invites is None: @@ -274,6 +281,8 @@ class PortalMetadata(BasePortal, ABC): self.about = "Your Telegram cloud storage chat" puppet = p.Puppet.get(self.tgid) if direct else None + if puppet: + await puppet.update_info(user, entity) self._main_intent = puppet.intent_for(self) if direct else self.az.intent if self.peer_type == "channel": @@ -340,9 +349,8 @@ class PortalMetadata(BasePortal, ABC): }) if direct: invites.append(self.az.bot_mxid) - # The bridge bot needs to join for e2ee, but that messes up the default name - # generation. If/when canonical DMs happen, this might not be necessary anymore. - self.title = puppet.displayname + if direct and (self.encrypted or self.private_chat_portal_meta): + self.title = puppet.displayname if config["appservice.community_id"]: initial_state.append({ "type": "m.room.related_groups", @@ -587,12 +595,12 @@ class PortalMetadata(BasePortal, ABC): self.log.warning("Called update_info() for direct chat portal") return + changed = False self.log.debug("Updating info") try: if not entity: entity = await self.get_entity(user) self.log.debug(f"Fetched data: {entity}") - changed = False if self.peer_type == "channel": changed = self.megagroup != entity.megagroup or changed @@ -631,7 +639,7 @@ class PortalMetadata(BasePortal, ABC): return True async def _try_use_intent(self, sender: Optional['p.Puppet'], - action: Callable[[IntentAPI], None]) -> None: + action: Callable[[IntentAPI], Awaitable[None]]) -> None: if sender: try: await action(sender.intent_for(self)) @@ -666,18 +674,19 @@ class PortalMetadata(BasePortal, ABC): async def _update_avatar(self, user: 'AbstractUser', photo: TypeChatPhoto, sender: Optional['p.Puppet'] = None, save: bool = False) -> bool: - if isinstance(photo, ChatPhoto): + if isinstance(photo, (ChatPhoto, UserProfilePhoto)): 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}" + photo_id = (f"{loc.volume_id}-{loc.local_id}" if isinstance(photo, ChatPhoto) + else photo.photo_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)): + elif isinstance(photo, (UserProfilePhotoEmpty, ChatPhotoEmpty, PhotoEmpty)): photo_id = "" loc = None else: diff --git a/mautrix_telegram/puppet.py b/mautrix_telegram/puppet.py index 1e22c50b..9f8249f8 100644 --- a/mautrix_telegram/puppet.py +++ b/mautrix_telegram/puppet.py @@ -242,8 +242,7 @@ class Puppet(CustomPuppetMixin): try: changed = await self.update_displayname(source, info) or changed - if isinstance(info.photo, UserProfilePhoto): - changed = await self.update_avatar(source, info.photo) or changed + changed = await self.update_avatar(source, info.photo) or changed except Exception: self.log.exception(f"Failed to update info from source {source.tgid}") @@ -294,10 +293,13 @@ class Puppet(CustomPuppetMixin): if self.disable_updates: return False - if isinstance(photo, UserProfilePhotoEmpty): + if photo is None or isinstance(photo, UserProfilePhotoEmpty): photo_id = "" - else: + elif isinstance(photo, UserProfilePhoto): photo_id = str(photo.photo_id) + else: + self.log.warning(f"Unknown user profile photo type: {type(photo)}") + return False if self.photo_id != photo_id: if not photo_id: self.photo_id = ""