diff --git a/mautrix_telegram/abstract_user.py b/mautrix_telegram/abstract_user.py index 64752a78..a78f5945 100644 --- a/mautrix_telegram/abstract_user.py +++ b/mautrix_telegram/abstract_user.py @@ -20,6 +20,7 @@ import logging import platform import time +from telethon.sessions import Session from telethon.tl.patched import MessageService, Message from telethon.tl.types import ( Channel, ChannelForbidden, Chat, ChatForbidden, MessageActionChannelMigrateFrom, PeerUser, @@ -29,7 +30,7 @@ from telethon.tl.types import ( UpdateReadHistoryOutbox, UpdateShortChatMessage, UpdateShortMessage, UpdateUserName, UpdateUserPhoto, UpdateUserStatus, UpdateUserTyping, User, UserStatusOffline, UserStatusOnline) -from mautrix.types import UserID +from mautrix.types import UserID, PresenceState from mautrix.errors import MatrixError from mautrix.appservice import AppService from alchemysession import AlchemySessionContainer @@ -135,6 +136,8 @@ class AbstractUser(ABC): sysversion = config["telegram.device_info.system_version"] appversion = config["telegram.device_info.app_version"] + assert isinstance(self.session, Session) + self.client = MautrixTelegramClient( session=self.session, @@ -335,9 +338,9 @@ class AbstractUser(ABC): async def update_status(self, update: UpdateUserStatus) -> None: puppet = pu.Puppet.get(TelegramID(update.user_id)) if isinstance(update.status, UserStatusOnline): - await puppet.default_mxid_intent.set_presence("online") + await puppet.default_mxid_intent.set_presence(PresenceState.ONLINE) elif isinstance(update.status, UserStatusOffline): - await puppet.default_mxid_intent.set_presence("offline") + await puppet.default_mxid_intent.set_presence(PresenceState.OFFLINE) else: self.log.warning("Unexpected user status update: %s", update) return diff --git a/mautrix_telegram/portal/__init__.py b/mautrix_telegram/portal/__init__.py index ae0d3a16..d0ccc839 100644 --- a/mautrix_telegram/portal/__init__.py +++ b/mautrix_telegram/portal/__init__.py @@ -1,7 +1,7 @@ from .base import BasePortal, init as init_base -from .portal_matrix import PortalMatrix -from .portal_metadata import PortalMetadata -from .portal_telegram import PortalTelegram +from .portal_matrix import PortalMatrix, init as init_matrix +from .portal_metadata import PortalMetadata, init as init_metadata +from .portal_telegram import PortalTelegram, init as init_telegram from .deduplication import init as init_dedup from ..context import Context @@ -13,6 +13,9 @@ class Portal(BasePortal, PortalMatrix, PortalTelegram, PortalMetadata): def init(context: Context) -> None: init_base(context) init_dedup(context) + init_metadata(context) + init_telegram(context) + init_matrix(context) -__all__ = ["Portal", "BasePortal", "init"] +__all__ = ["Portal", "init"] diff --git a/mautrix_telegram/portal/__init__.pyi b/mautrix_telegram/portal/__init__.pyi index 293b4301..0a500031 100644 --- a/mautrix_telegram/portal/__init__.pyi +++ b/mautrix_telegram/portal/__init__.pyi @@ -1,7 +1,7 @@ from typing import Union -from .base import BasePortal, init as init_base +from .base import BasePortal from .portal_matrix import PortalMatrix from .portal_metadata import PortalMetadata from .portal_telegram import PortalTelegram -Portal = Union[BasePortal, PortalMatrix, PortalTelegram, PortalMetadata] +Portal = Union[BasePortal, PortalMatrix, PortalMetadata, PortalTelegram] diff --git a/mautrix_telegram/portal/base.py b/mautrix_telegram/portal/base.py index d4daf024..5561e0d6 100644 --- a/mautrix_telegram/portal/base.py +++ b/mautrix_telegram/portal/base.py @@ -14,23 +14,24 @@ # 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, Any, TYPE_CHECKING -from abc import ABC +from abc import ABC, abstractmethod import asyncio import logging import json import re from telethon.tl.functions.messages import ExportChatInviteRequest -from telethon.tl.patched import Message, MessageService from telethon.tl.types import (Channel, ChannelFull, Chat, ChatFull, ChatInviteEmpty, InputChannel, InputPeerChannel, InputPeerChat, InputPeerUser, InputUser, PeerChannel, PeerChat, PeerUser, TypeChat, TypeInputPeer, TypePeer, - TypeUser, TypeUserFull, User, UserFull, TypeInputChannel, - Photo, Document, TypePhotoSize, PhotoSize, InputPhotoFileLocation) + TypeUser, TypeUserFull, User, UserFull, TypeInputChannel, Photo, + Document, TypePhotoSize, PhotoSize, InputPhotoFileLocation, + TypeChatParticipant, TypeChannelParticipant, PhotoEmpty, ChatPhoto, + ChatPhotoEmpty) from mautrix.errors import MatrixRequestError, IntentError from mautrix.appservice import AppService, IntentAPI -from mautrix.types import RoomID, UserID, EventType +from mautrix.types import RoomID, RoomAlias, UserID, EventType, PowerLevelStateEventContent from ..types import TelegramID from ..context import Context @@ -45,9 +46,11 @@ if TYPE_CHECKING: from ..config import Config from . import Portal -config: Optional['Config'] = None +TypeParticipant = Union[TypeChatParticipant, TypeChannelParticipant] +TypeChatPhoto = Union[ChatPhoto, ChatPhotoEmpty, Photo, PhotoEmpty] +InviteList = Union[UserID, List[UserID]] -TypeMessage = Union[Message, MessageService] +config: Optional['Config'] = None class BasePortal(ABC): @@ -86,6 +89,8 @@ class BasePortal(ABC): deleted: bool log: logging.Logger + alias: Optional[RoomAlias] + dedup: PortalDedup send_lock: PortalSendLock @@ -169,7 +174,7 @@ class BasePortal(ABC): local = util.recursive_get(self.local_config, key) if local is not None: return local - return self.config[f"bridge.{key}"] + return config[f"bridge.{key}"] @staticmethod def _get_largest_photo_size(photo: Union[Photo, Document] @@ -412,6 +417,50 @@ class BasePortal(ABC): type_name if create else None) # endregion + # region Abstract methods (cross-called in matrix/metadata/telegram classes) + + @abstractmethod + async def update_matrix_room(self, user: 'AbstractUser', entity: Union[TypeChat, User], + direct: bool, puppet: p.Puppet = None, + levels: PowerLevelStateEventContent = None, + users: List[User] = None, + participants: List[TypeParticipant] = None) -> None: + pass + + @abstractmethod + async def create_matrix_room(self, user: 'AbstractUser', entity: TypeChat = None, + invites: InviteList = None, update_if_exists: bool = True, + synchronous: bool = False) -> Optional[str]: + pass + + @abstractmethod + async def _add_telegram_user(self, user_id: TelegramID, source: Optional['AbstractUser'] = None + ) -> None: + pass + + @abstractmethod + async def _delete_telegram_user(self, user_id: TelegramID, sender: p.Puppet) -> None: + pass + + @abstractmethod + async def _update_title(self, title: str, save: bool = False) -> bool: + pass + + @abstractmethod + async def _update_avatar(self, user: 'AbstractUser', photo: Union[TypeChatPhoto], + save: bool = False) -> bool: + pass + + @abstractmethod + def _migrate_and_save_telegram(self, new_id: TelegramID) -> None: + pass + + @abstractmethod + def handle_matrix_power_levels(self, sender: 'u.User', new_levels: PowerLevelStateEventContent, + old_levels: PowerLevelStateEventContent) -> Awaitable[None]: + pass + + # endregion def init(context: Context) -> None: diff --git a/mautrix_telegram/portal/portal_matrix.py b/mautrix_telegram/portal/portal_matrix.py index 886ee1ce..a0139619 100644 --- a/mautrix_telegram/portal/portal_matrix.py +++ b/mautrix_telegram/portal/portal_matrix.py @@ -16,6 +16,7 @@ from typing import Awaitable, Dict, List, Optional, Tuple, Union, Any, TYPE_CHECKING from html import escape as escape_html from string import Template +from abc import ABC import mimetypes import magic @@ -38,22 +39,26 @@ from telethon.tl.types import ( from mautrix.types import (EventID, RoomID, UserID, ContentURI, MessageType, TextMessageEventContent, Format) -from mautrix.bridge import BasePortal as AbstractPortal +from mautrix.bridge import BasePortal as MautrixBasePortal from ..types import TelegramID from ..db import Message as DBMessage from ..util import sane_mimetypes +from ..context import Context from .. import puppet as p, user as u, formatter, util from .base import BasePortal if TYPE_CHECKING: from ..abstract_user import AbstractUser from ..tgclient import MautrixTelegramClient + from ..config import Config TypeMessage = Union[Message, MessageService] +config: Optional['Config'] = None -class PortalMatrix(BasePortal, AbstractPortal): + +class PortalMatrix(BasePortal, MautrixBasePortal, ABC): @staticmethod def _get_file_meta(body: str, mime: str) -> str: try: @@ -104,9 +109,7 @@ class PortalMatrix(BasePortal, AbstractPortal): prev_displayname=prev_displayname) async def get_displayname(self, user: 'u.User') -> str: - # FIXME this doesn't seem to support per-room names or use cache in mautrix 0.4 - return (await self.main_intent.get_displayname(self.mxid, user.mxid) - or user.mxid) + return await self.main_intent.get_room_displayname(self.mxid, user.mxid) or user.mxid def set_typing(self, user: 'u.User', typing: bool = True, action: type = SendMessageTypingAction) -> Awaitable[bool]: @@ -462,7 +465,7 @@ class PortalMatrix(BasePortal, AbstractPortal): 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) + uploaded = await sender.client.upload_file(file, file_name=f"avatar{ext}") photo = InputChatUploadedPhoto(file=uploaded) if self.peer_type == "chat": @@ -485,8 +488,8 @@ class PortalMatrix(BasePortal, AbstractPortal): old_room = self.mxid self.migrate_and_save_matrix(new_room) await self.main_intent.join_room(new_room) - entity = None # type: TypeInputPeer - user = None # type: AbstractUser + entity: Optional[TypeInputPeer] = None + user: Optional[AbstractUser] = None if self.bot and self.has_bot: user = self.bot entity = await self.get_input_entity(self.bot) @@ -505,10 +508,7 @@ class PortalMatrix(BasePortal, AbstractPortal): self.log.error( "Failed to fully migrate to upgraded Matrix room: no Telegram user found.") return - users, participants = await self._get_users(self.bot, entity) - await self.sync_telegram_users(user, users) - levels = await self.main_intent.get_power_levels(self.mxid) - await self.update_telegram_participants(participants, levels) + await self.update_matrix_room(user, entity, direct=self.peer_type == "user") self.log.info(f"Upgraded room from {old_room} to {self.mxid}") def migrate_and_save_matrix(self, new_id: RoomID) -> None: @@ -519,3 +519,8 @@ class PortalMatrix(BasePortal, AbstractPortal): self.mxid = new_id self.db_instance.update(mxid=self.mxid) self.by_mxid[self.mxid] = self + + +def init(context: Context) -> None: + global config + config = context.config diff --git a/mautrix_telegram/portal/portal_metadata.py b/mautrix_telegram/portal/portal_metadata.py index 1061b814..d995c55e 100644 --- a/mautrix_telegram/portal/portal_metadata.py +++ b/mautrix_telegram/portal/portal_metadata.py @@ -14,6 +14,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, TYPE_CHECKING +from abc import ABC import asyncio from telethon.tl.functions.messages import (AddChatUserRequest, CreateChatRequest, @@ -22,28 +23,29 @@ from telethon.tl.functions.channels import (CreateChannelRequest, GetParticipant InviteToChannelRequest, UpdateUsernameRequest) from telethon.errors import ChatAdminRequiredError from telethon.tl.types import ( - Channel, ChatBannedRights, Document, ChannelParticipantsRecent, ChannelParticipantsSearch, - ChatPhoto, PhotoEmpty, InputChannel, InputPhotoFileLocation, InputUser, ChatPhotoEmpty, - PeerUser, Photo, TypeChannelParticipant, TypeChat, TypeChatParticipant, TypeInputPeer, - TypePhotoSize, TypeUser, PhotoSize, User, InputPeerPhotoFileLocation) + Channel, ChatBannedRights, ChannelParticipantsRecent, ChannelParticipantsSearch, ChatPhoto, + PhotoEmpty, InputChannel, InputUser, ChatPhotoEmpty, PeerUser, Photo, TypeChat, TypeInputPeer, + TypeUser, User, InputPeerPhotoFileLocation, ChatParticipantAdmin, ChannelParticipantAdmin, + ChatParticipantCreator, ChannelParticipantCreator) from mautrix.errors import MForbidden -from mautrix.types import (RoomID, UserID, RoomCreatePreset, ContentURI, EventType, Membership, +from mautrix.types import (RoomID, UserID, RoomCreatePreset, EventType, Membership, Member, PowerLevelStateEventContent, RoomAlias) from ..types import TelegramID +from ..context import Context from .. import puppet as p, user as u, util -from .base import BasePortal +from .base import BasePortal, InviteList, TypeParticipant, TypeChatPhoto if TYPE_CHECKING: from ..abstract_user import AbstractUser + from ..config import Config from . import Portal -InviteList = Union[UserID, List[UserID]] -TypeParticipant = Union[TypeChatParticipant, TypeChannelParticipant] +config: Optional['Config'] = None -class PortalMetadata(BasePortal): +class PortalMetadata(BasePortal, ABC): _room_create_lock: asyncio.Lock def __init__(self, *args, **kwargs) -> None: @@ -52,7 +54,7 @@ class PortalMetadata(BasePortal): # region Matrix -> Telegram - async def _get_telegram_users_in_matrix_room(self) -> List[TelegramID]: + async def _get_telegram_users_in_matrix_room(self) -> List[Union[InputUser, PeerUser]]: user_tgids = set() user_mxids = await self.main_intent.get_room_members(self.mxid, (Membership.JOIN, Membership.INVITE)) @@ -66,7 +68,7 @@ class PortalMetadata(BasePortal): puppet_id = p.Puppet.get_id_from_mxid(user) if puppet_id: user_tgids.add(puppet_id) - return list(user_tgids) + return [PeerUser(user_id) for user_id in user_tgids] async def upgrade_telegram_chat(self, source: 'u.User') -> None: if self.peer_type != "chat": @@ -81,10 +83,10 @@ class PortalMetadata(BasePortal): if not entity: raise ValueError("Upgrade may have failed: output channel not found.") self.peer_type = "channel" - self.migrate_and_save_telegram(TelegramID(entity.id)) + self._migrate_and_save_telegram(TelegramID(entity.id)) await self.update_info(source, entity) - def migrate_and_save_telegram(self, new_id: TelegramID) -> None: + def _migrate_and_save_telegram(self, new_id: TelegramID) -> None: try: del self.by_tgid[self.tgid_full] except KeyError: @@ -107,7 +109,7 @@ class PortalMetadata(BasePortal): raise ValueError("Only channels and supergroups have usernames.") await source.client( UpdateUsernameRequest(await self.get_input_entity(source), username)) - if await self.update_username(username): + if await self._update_username(username): self.save() async def create_telegram_chat(self, source: 'u.User', supergroup: bool = False) -> None: @@ -153,7 +155,7 @@ class PortalMetadata(BasePortal): if levels.get_user_level(self.main_intent.mxid) == 100: levels = self._get_base_power_levels(levels, entity) await self.main_intent.set_power_levels(self.mxid, levels) - await self.handle_matrix_power_levels(source, levels["users"], {}) + await self.handle_matrix_power_levels(source, levels, PowerLevelStateEventContent()) async def invite_telegram(self, source: 'u.User', puppet: Union[p.Puppet, 'AbstractUser']) -> None: @@ -195,7 +197,7 @@ class PortalMetadata(BasePortal): await self.update_info(user, entity) if not users or not participants: users, participants = await self._get_users(user, entity) - await self.sync_telegram_users(user, users) + await self._sync_telegram_users(user, users) await self.update_telegram_participants(participants, levels) else: if not puppet: @@ -382,7 +384,7 @@ class PortalMetadata(BasePortal): return changed async def update_telegram_participants(self, participants: List[TypeParticipant], - levels: dict = None) -> None: + levels: PowerLevelStateEventContent = None) -> None: if not levels: levels = await self.main_intent.get_power_levels(self.mxid) if self._participants_to_power_levels(participants, levels): @@ -400,7 +402,7 @@ class PortalMetadata(BasePortal): return None return self.alias_template.format(groupname=username) - def add_bot_chat(self, bot: User) -> None: + def _add_bot_chat(self, bot: User) -> None: if self.bot and bot.id == self.bot.tgid: self.bot.add_chat(self.tgid, self.peer_type) return @@ -409,7 +411,7 @@ class PortalMetadata(BasePortal): if user and user.is_bot: user.register_portal(self) - async def sync_telegram_users(self, source: 'AbstractUser', users: List[User]) -> None: + async def _sync_telegram_users(self, source: 'AbstractUser', users: List[User]) -> None: allowed_tgids = set() skip_deleted = config["bridge.skip_deleted_members"] for entity in users: @@ -417,7 +419,7 @@ class PortalMetadata(BasePortal): continue puppet = p.Puppet.get(TelegramID(entity.id)) if entity.bot: - self.add_bot_chat(entity) + self._add_bot_chat(entity) allowed_tgids.add(entity.id) await puppet.intent.ensure_joined(self.mxid) await puppet.update_info(source, entity) @@ -454,8 +456,8 @@ class PortalMetadata(BasePortal): "You had left this Telegram chat.") continue - async def add_telegram_user(self, user_id: TelegramID, source: Optional['AbstractUser'] = None - ) -> None: + async def _add_telegram_user(self, user_id: TelegramID, source: Optional['AbstractUser'] = None + ) -> None: puppet = p.Puppet.get(user_id) if source: entity: User = await source.client.get_entity(PeerUser(user_id)) @@ -467,7 +469,7 @@ class PortalMetadata(BasePortal): user.register_portal(self) await self.invite_to_matrix(user.mxid) - async def delete_telegram_user(self, user_id: TelegramID, sender: p.Puppet) -> None: + async def _delete_telegram_user(self, user_id: TelegramID, sender: p.Puppet) -> None: puppet = p.Puppet.get(user_id) user = u.User.get_by_tgid(user_id) kick_message = (f"Kicked by {sender.displayname}" @@ -492,72 +494,68 @@ class PortalMetadata(BasePortal): async def update_info(self, user: 'AbstractUser', entity: TypeChat = None) -> None: if self.peer_type == "user": - self.log.warning(f"Called update_info() for direct chat portal") + self.log.warning("Called update_info() for direct chat portal") return - self.log.debug(f"Updating info") + self.log.debug("Updating info") if not entity: entity = await self.get_entity(user) - self.log.debug("Fetched data: %s", entity) + self.log.debug(f"Fetched data: {entity}") changed = False if self.peer_type == "channel": - changed = await self.update_username(entity.username) or changed + changed = await self._update_username(entity.username) or changed # TODO update about text # changed = self.update_about(entity.about) or changed - changed = await self.update_title(entity.title) or changed + changed = await self._update_title(entity.title) or changed if isinstance(entity.photo, ChatPhoto): - changed = await self.update_avatar(user, entity.photo) or changed + changed = await self._update_avatar(user, entity.photo) or changed if changed: self.save() - async def update_username(self, username: str, save: bool = False) -> bool: - if self.username != username: - if self.username: - await self.main_intent.remove_room_alias(self._get_alias_localpart()) - self.username = username or None - if self.username: - await self.main_intent.add_room_alias(self.mxid, self._get_alias_localpart()) - if Portal.public_portals: - await self.main_intent.set_join_rule(self.mxid, "public") - else: - await self.main_intent.set_join_rule(self.mxid, "invite") + async def _update_username(self, username: str, save: bool = False) -> bool: + if self.username == username: + return False - if save: - self.save() - return True - return False + if self.username: + await self.main_intent.remove_room_alias(self._get_alias_localpart()) + self.username = username or None + if self.username: + await self.main_intent.add_room_alias(self.mxid, self._get_alias_localpart()) + if Portal.public_portals: + await self.main_intent.set_join_rule(self.mxid, "public") + else: + await self.main_intent.set_join_rule(self.mxid, "invite") - async def update_about(self, about: str, save: bool = False) -> bool: - if self.about != about: - self.about = about - await self.main_intent.set_room_topic(self.mxid, self.about) - if save: - self.save() - return True - return False - - async def update_title(self, title: str, save: bool = False) -> bool: - if self.title != title: - self.title = title - await self.main_intent.set_room_name(self.mxid, self.title) - if save: - self.save() - return True - return False - - async def remove_avatar(self, _: 'AbstractUser', save: bool = False) -> None: - await self.main_intent.set_room_avatar(self.mxid, None) - self.photo_id = None if save: self.save() + return True - async def update_avatar(self, user: 'AbstractUser', - photo: Union[ChatPhoto, ChatPhotoEmpty, Photo, PhotoEmpty], - save: bool = False) -> bool: + async def _update_about(self, about: str, save: bool = False) -> bool: + if self.about == about: + return False + + self.about = about + await self.main_intent.set_room_topic(self.mxid, self.about) + if save: + self.save() + return True + + async def _update_title(self, title: str, save: bool = False) -> bool: + if self.title == title: + return False + + self.title = title + await self.main_intent.set_room_name(self.mxid, self.title) + if save: + self.save() + return True + + async def _update_avatar(self, user: 'AbstractUser', photo: TypeChatPhoto, save: bool = False + ) -> bool: if isinstance(photo, ChatPhoto): loc = InputPeerPhotoFileLocation( peer=await self.get_input_entity(user), @@ -576,7 +574,7 @@ class PortalMetadata(BasePortal): 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, ContentURI("")) + await self.main_intent.set_room_avatar(self.mxid, None) self.photo_id = "" if save: self.save() @@ -593,6 +591,7 @@ class PortalMetadata(BasePortal): async def _get_users(self, user: 'AbstractUser', entity: Union[TypeInputPeer, InputUser, TypeChat, TypeUser, InputChannel] ) -> Tuple[List[TypeUser], List[TypeParticipant]]: + # TODO replace with client.get_participants if self.peer_type == "chat": chat = await user.client(GetFullChatRequest(chat_id=self.tgid)) return chat.users, chat.full_chat.participants.participants @@ -610,8 +609,8 @@ class PortalMetadata(BasePortal): entity, ChannelParticipantsRecent(), offset=0, limit=limit, hash=0)) return response.users, response.participants elif limit > 200 or limit == -1: - users = [] # type: List[TypeUser] - participants = [] # type: List[TypeParticipant] + users: List[TypeUser] = [] + participants: List[TypeParticipant] = [] offset = 0 remaining_quota = limit if limit > 0 else 1000000 query = (ChannelParticipantsSearch("") if limit == -1 @@ -635,3 +634,8 @@ class PortalMetadata(BasePortal): return [], [] # endregion + + +def init(context: Context) -> None: + global config + config = context.config diff --git a/mautrix_telegram/portal/portal_telegram.py b/mautrix_telegram/portal/portal_telegram.py index abdc2d80..1ec977b9 100644 --- a/mautrix_telegram/portal/portal_telegram.py +++ b/mautrix_telegram/portal/portal_telegram.py @@ -15,6 +15,7 @@ # along with this program. If not, see . from typing import Awaitable, Dict, List, Optional, Tuple, Union, TYPE_CHECKING from html import escape as escape_html +from abc import ABC import random import mimetypes import codecs @@ -33,7 +34,7 @@ from telethon.tl.types import ( MessageMediaDocument, MessageMediaGeo, MessageMediaPhoto, MessageMediaUnsupported, MessageMediaGame, PeerUser, PhotoCachedSize, TypeChannelParticipant, TypeChatParticipant, TypeDocumentAttribute, TypeMessageAction, TypePhotoSize, PhotoSize, UpdateChatUserTyping, - UpdateUserTyping, MessageEntityPre) + UpdateUserTyping, MessageEntityPre, ChatPhotoEmpty) from mautrix.appservice import IntentAPI from mautrix.types import EventID, UserID, ImageInfo, ThumbnailInfo @@ -41,17 +42,21 @@ from mautrix.types import EventID, UserID, ImageInfo, ThumbnailInfo from ..types import TelegramID from ..db import Message as DBMessage, TelegramFile as DBTelegramFile from ..util import sane_mimetypes +from ..context import Context from .. import puppet as p, user as u, formatter, util from .base import BasePortal if TYPE_CHECKING: from ..abstract_user import AbstractUser + from ..config import Config InviteList = Union[UserID, List[UserID]] TypeParticipant = Union[TypeChatParticipant, TypeChannelParticipant] +config: Optional['Config'] = None -class PortalTelegram(BasePortal): + +class PortalTelegram(BasePortal, ABC): _temp_pinned_message_id: Optional[TelegramID] _temp_pinned_message_id_space: Optional[TelegramID] _temp_pinned_message_sender: Optional['p.Puppet'] @@ -509,21 +514,21 @@ class PortalTelegram(BasePortal): if should_ignore or not self.mxid: return if isinstance(action, MessageActionChatEditTitle): - await self.update_title(action.title, save=True) + await self._update_title(action.title, save=True) elif isinstance(action, MessageActionChatEditPhoto): - await self.update_avatar(source, action.photo, save=True) + await self._update_avatar(source, action.photo, save=True) elif isinstance(action, MessageActionChatDeletePhoto): - await self.remove_avatar(source, save=True) + await self._update_avatar(source, ChatPhotoEmpty(), save=True) elif isinstance(action, MessageActionChatAddUser): for user_id in action.users: - await self.add_telegram_user(TelegramID(user_id), source) + await self._add_telegram_user(TelegramID(user_id), source) elif isinstance(action, MessageActionChatJoinedByLink): - await self.add_telegram_user(sender.id, source) + await self._add_telegram_user(sender.id, source) elif isinstance(action, MessageActionChatDeleteUser): - await self.delete_telegram_user(TelegramID(action.user_id), sender) + await self._delete_telegram_user(TelegramID(action.user_id), sender) elif isinstance(action, MessageActionChatMigrateTo): self.peer_type = "channel" - self.migrate_and_save_telegram(TelegramID(action.channel_id)) + self._migrate_and_save_telegram(TelegramID(action.channel_id)) await sender.intent.send_emote(self.mxid, "upgraded this group to a supergroup.") elif isinstance(action, MessageActionPinMessage): await self.receive_telegram_pin_sender(sender) @@ -577,3 +582,8 @@ class PortalTelegram(BasePortal): levels["events"]["m.room.name"] = level levels["events"]["m.room.avatar"] = level await self.main_intent.set_power_levels(self.mxid, levels) + + +def init(context: Context) -> None: + global config + config = context.config