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