Even even even more migrations to mautrix-python
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -14,23 +14,24 @@
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
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
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user