diff --git a/mautrix_telegram/abstract_user.py b/mautrix_telegram/abstract_user.py index 8cfb033b..4782bbcf 100644 --- a/mautrix_telegram/abstract_user.py +++ b/mautrix_telegram/abstract_user.py @@ -17,10 +17,12 @@ import platform import os -from .tgclient import MautrixTelegramClient -from . import __version__ from telethon.tl.types import * +from .tgclient import MautrixTelegramClient +from .db import Message as DBMessage +from . import portal as po, puppet as pu, __version__ + config = None @@ -50,14 +52,15 @@ class AbstractUser: self.client.add_update_handler(self._update_catch) async def update(self, update): - raise NotImplementedError() + return False async def post_login(self): raise NotImplementedError() async def _update_catch(self, update): try: - await self.update(update) + if not await self.update(update): + await self._update(update) except Exception: self.log.exception("Failed to handle Telegram update") @@ -95,6 +98,134 @@ class AbstractUser: self.client = None self.connected = False + # region Telegram update handling + + async def _update(self, update): + if isinstance(update, + (UpdateShortChatMessage, UpdateShortMessage, UpdateNewChannelMessage, + UpdateNewMessage, UpdateEditMessage, UpdateEditChannelMessage)): + await self.update_message(update) + elif isinstance(update, (UpdateChatUserTyping, UpdateUserTyping)): + await self.update_typing(update) + elif isinstance(update, UpdateUserStatus): + await self.update_status(update) + elif isinstance(update, (UpdateChatAdmins, UpdateChatParticipantAdmin)): + await self.update_admin(update) + elif isinstance(update, UpdateChatParticipants): + portal = po.Portal.get_by_tgid(update.participants.chat_id) + if portal and portal.mxid: + await portal.update_telegram_participants(update.participants.participants) + elif isinstance(update, UpdateChannelPinnedMessage): + portal = po.Portal.get_by_tgid(update.channel_id) + if portal and portal.mxid: + await portal.update_telegram_pin(self, update.id) + elif isinstance(update, (UpdateUserName, UpdateUserPhoto)): + await self.update_others_info(update) + elif isinstance(update, UpdateReadHistoryOutbox): + await self.update_read_receipt(update) + else: + self.log.debug("Unhandled update: %s", update) + + async def update_read_receipt(self, update): + if not isinstance(update.peer, PeerUser): + self.log.debug("Unexpected read receipt peer: %s", update.peer) + return + + portal = po.Portal.get_by_tgid(update.peer.user_id, self.tgid) + if not portal or not portal.mxid: + return + + # We check that these are user read receipts, so tg_space is always the user ID. + message = DBMessage.query.get((update.max_id, self.tgid)) + if not message: + return + + puppet = pu.Puppet.get(update.peer.user_id) + await puppet.intent.mark_read(portal.mxid, message.mxid) + + async def update_admin(self, update): + portal = po.Portal.get_by_tgid(update.chat_id, peer_type="chat") + if isinstance(update, UpdateChatAdmins): + await portal.set_telegram_admins_enabled(update.enabled) + elif isinstance(update, UpdateChatParticipantAdmin): + await portal.set_telegram_admin(update.user_id) + else: + self.log.warninng("Unexpected admin status update: %s", update) + + async def update_typing(self, update): + if isinstance(update, UpdateUserTyping): + portal = po.Portal.get_by_tgid(update.user_id, self.tgid, "user") + else: + portal = po.Portal.get_by_tgid(update.chat_id, peer_type="chat") + sender = pu.Puppet.get(update.user_id) + await portal.handle_telegram_typing(sender, update) + + async def update_others_info(self, update): + puppet = pu.Puppet.get(update.user_id) + if isinstance(update, UpdateUserName): + if await puppet.update_displayname(self, update): + puppet.save() + elif isinstance(update, UpdateUserPhoto): + if await puppet.update_avatar(self, update.photo.photo_big): + puppet.save() + else: + self.log.warninng("Unexpected other user info update: %s", update) + + async def update_status(self, update): + puppet = pu.Puppet.get(update.user_id) + if isinstance(update.status, UserStatusOnline): + await puppet.intent.set_presence("online") + elif isinstance(update.status, UserStatusOffline): + await puppet.intent.set_presence("offline") + else: + self.log.warning("Unexpected user status update: %s", update) + return + + def get_message_details(self, update): + if isinstance(update, UpdateShortChatMessage): + portal = po.Portal.get_by_tgid(update.chat_id, peer_type="chat") + sender = pu.Puppet.get(update.from_id) + elif isinstance(update, UpdateShortMessage): + portal = po.Portal.get_by_tgid(update.user_id, self.tgid, "user") + sender = pu.Puppet.get(self.tgid if update.out else update.user_id) + elif isinstance(update, (UpdateNewMessage, UpdateNewChannelMessage, + UpdateEditMessage, UpdateEditChannelMessage)): + update = update.message + if isinstance(update.to_id, PeerUser) and not update.out: + portal = po.Portal.get_by_tgid(update.from_id, peer_type="user", + tg_receiver=self.tgid) + else: + portal = po.Portal.get_by_entity(update.to_id, receiver_id=self.tgid) + sender = pu.Puppet.get(update.from_id) if update.from_id else None + else: + self.log.warning( + f"Unexpected message type in User#get_message_details: {type(update)}") + return update, None, None + return update, sender, portal + + def update_message(self, original_update): + update, sender, portal = self.get_message_details(original_update) + + if isinstance(update, MessageService): + if isinstance(update.action, MessageActionChannelMigrateFrom): + self.log.debug(f"Ignoring action %s to %s by %d", update.action, + portal.tgid_log, + sender.id) + return + self.log.debug("Handling action %s to %s by %d", update.action, portal.tgid_log, + sender.id) + return portal.handle_telegram_action(self, sender, update.action) + + user = sender.tgid if sender else "admin" + if isinstance(original_update, (UpdateEditMessage, UpdateEditChannelMessage)): + self.log.debug("Handling edit %s to %s by %s", update, portal.tgid_log, user) + return portal.handle_telegram_edit(self, sender, update) + + self.log.debug("Handling message %s to %s by %s", update, portal.tgid_log, user) + return portal.handle_telegram_message(self, sender, update) + + # endregion + def init(context): global config diff --git a/mautrix_telegram/bot.py b/mautrix_telegram/bot.py index 663d5b45..02a15b47 100644 --- a/mautrix_telegram/bot.py +++ b/mautrix_telegram/bot.py @@ -35,7 +35,7 @@ class Bot(AbstractUser): self.token = token self.whitelisted = True self._init_client() - self.chats = {(chat.id, chat.type) for chat in BotChat.query.all()} + self.chats = {chat.id: chat.type for chat in BotChat.query.all()} async def start(self): await super().start() @@ -48,30 +48,32 @@ class Bot(AbstractUser): info = await self.client.get_me() self.tgid = info.id - chat_ids = [id for (id, type) in self.chats if type == "chat"] + chat_ids = [id for id, type in self.chats.items() if type == "chat"] response = await self.client(GetChatsRequest(chat_ids)) for chat in response.chats: if isinstance(chat, ChatForbidden) or chat.left or chat.deactivated: - self.remove_chat(chat.id, "chat") + self.remove_chat(chat.id) channel_ids = [InputChannel(id, 0) - for (id, type) in self.chats + for id, type in self.chats.items() if type == "channel"] for id in channel_ids: try: await self.client(GetChannelsRequest([id])) except (ChannelPrivateError, ChannelInvalidError): - self.remove_chat(id.channel_id, "channel") + self.remove_chat(id.channel_id) def add_chat(self, id, type): - entry = (id, type) - if entry not in self.chats: - self.chats.add(entry) + if id not in self.chats: + self.chats[id] = type self.db.add(BotChat(id=id, type=type)) self.db.commit() - def remove_chat(self, id, type): - self.chats.remove((id, type)) + def remove_chat(self, id): + try: + del self.chats[id] + except KeyError: + pass self.db.delete(BotChat.query.get(id)) self.db.commit() @@ -97,7 +99,7 @@ class Bot(AbstractUser): self.add_chat(to_id, type) elif isinstance(action, MessageActionChatDeleteUser): if action.user_id == self.tgid: - self.remove_chat(to_id, type) + self.remove_chat(to_id) def is_in_chat(self, peer_id): return peer_id in self.chats diff --git a/mautrix_telegram/matrix.py b/mautrix_telegram/matrix.py index 208cfa8c..bfa88b49 100644 --- a/mautrix_telegram/matrix.py +++ b/mautrix_telegram/matrix.py @@ -169,10 +169,12 @@ class MatrixHandler: is_command, text = self.is_command(message) sender = await User.get_by_mxid(sender).ensure_started() + print(sender, sender.whitelisted) if not sender.whitelisted: return portal = Portal.get_by_mxid(room) + print(is_command, portal, sender.logged_in, portal.has_bot) if not is_command and portal and (sender.logged_in or portal.has_bot): await portal.handle_matrix_message(sender, message, event_id) return diff --git a/mautrix_telegram/portal.py b/mautrix_telegram/portal.py index 849be917..8988b08b 100644 --- a/mautrix_telegram/portal.py +++ b/mautrix_telegram/portal.py @@ -92,6 +92,7 @@ class Portal: @property def has_bot(self): + print("BOT PRINT", self.bot) return self.bot and self.bot.is_in_chat(self.tgid) def _hash_event(self, event): @@ -934,7 +935,10 @@ class Portal: else: self.log.debug("Unhandled Telegram action in %s: %s", self.title, action) - async def set_telegram_admin(self, puppet, user): + async def set_telegram_admin(self, user_id): + puppet = p.Puppet.get(user_id) + user = await u.User.get_by_tgid(user_id) + levels = await self.main_intent.get_power_levels(self.mxid) if user: levels["users"][user.mxid] = 50 diff --git a/mautrix_telegram/user.py b/mautrix_telegram/user.py index 336f0892..4ecd64f6 100644 --- a/mautrix_telegram/user.py +++ b/mautrix_telegram/user.py @@ -23,7 +23,7 @@ from telethon.tl.types.contacts import ContactsNotModified from telethon.tl.functions.contacts import GetContactsRequest, SearchRequest from mautrix_appservice import MatrixRequestError -from .db import User as DBUser, Message as DBMessage, Contact as DBContact +from .db import User as DBUser, Contact as DBContact from .abstract_user import AbstractUser from . import portal as po, puppet as pu @@ -260,129 +260,6 @@ class User(AbstractUser): self.contacts.append(puppet) self.save() - # endregion - # region Telegram update handling - - async def update(self, update): - if isinstance(update, (UpdateShortChatMessage, UpdateShortMessage, UpdateNewChannelMessage, - UpdateNewMessage, UpdateEditMessage, UpdateEditChannelMessage)): - await self.update_message(update) - elif isinstance(update, (UpdateChatUserTyping, UpdateUserTyping)): - await self.update_typing(update) - elif isinstance(update, UpdateUserStatus): - await self.update_status(update) - elif isinstance(update, (UpdateChatAdmins, UpdateChatParticipantAdmin)): - await self.update_admin(update) - elif isinstance(update, UpdateChatParticipants): - portal = po.Portal.get_by_tgid(update.participants.chat_id) - if portal and portal.mxid: - await portal.update_telegram_participants(update.participants.participants) - elif isinstance(update, UpdateChannelPinnedMessage): - portal = po.Portal.get_by_tgid(update.channel_id) - if portal and portal.mxid: - await portal.update_telegram_pin(self, update.id) - elif isinstance(update, (UpdateUserName, UpdateUserPhoto)): - await self.update_others_info(update) - elif isinstance(update, UpdateReadHistoryOutbox): - await self.update_read_receipt(update) - else: - self.log.debug("Unhandled update: %s", update) - - async def update_read_receipt(self, update): - if not isinstance(update.peer, PeerUser): - self.log.debug("Unexpected read receipt peer: %s", update.peer) - return - - portal = po.Portal.get_by_tgid(update.peer.user_id, self.tgid) - if not portal or not portal.mxid: - return - - # We check that these are user read receipts, so tg_space is always the user ID. - message = DBMessage.query.get((update.max_id, self.tgid)) - if not message: - return - - puppet = pu.Puppet.get(update.peer.user_id) - await puppet.intent.mark_read(portal.mxid, message.mxid) - - async def update_admin(self, update): - portal = po.Portal.get_by_tgid(update.chat_id, peer_type="chat") - if isinstance(update, UpdateChatAdmins): - await portal.set_telegram_admins_enabled(update.enabled) - elif isinstance(update, UpdateChatParticipantAdmin): - puppet = pu.Puppet.get(update.user_id) - user = await User.get_by_tgid(update.user_id).ensure_started() - await portal.set_telegram_admin(puppet, user) - - async def update_typing(self, update): - if isinstance(update, UpdateUserTyping): - portal = po.Portal.get_by_tgid(update.user_id, self.tgid, "user") - else: - portal = po.Portal.get_by_tgid(update.chat_id, peer_type="chat") - sender = pu.Puppet.get(update.user_id) - await portal.handle_telegram_typing(sender, update) - - async def update_others_info(self, update): - puppet = pu.Puppet.get(update.user_id) - if isinstance(update, UpdateUserName): - if await puppet.update_displayname(self, update): - puppet.save() - elif isinstance(update, UpdateUserPhoto): - if await puppet.update_avatar(self, update.photo.photo_big): - puppet.save() - - async def update_status(self, update): - puppet = pu.Puppet.get(update.user_id) - if isinstance(update.status, UserStatusOnline): - await puppet.intent.set_presence("online") - elif isinstance(update.status, UserStatusOffline): - await puppet.intent.set_presence("offline") - else: - self.log.warning("Unexpected user status update: %s", update) - return - - def get_message_details(self, update): - if isinstance(update, UpdateShortChatMessage): - portal = po.Portal.get_by_tgid(update.chat_id, peer_type="chat") - sender = pu.Puppet.get(update.from_id) - elif isinstance(update, UpdateShortMessage): - portal = po.Portal.get_by_tgid(update.user_id, self.tgid, "user") - sender = pu.Puppet.get(self.tgid if update.out else update.user_id) - elif isinstance(update, (UpdateNewMessage, UpdateNewChannelMessage, - UpdateEditMessage, UpdateEditChannelMessage)): - update = update.message - if isinstance(update.to_id, PeerUser) and not update.out: - portal = po.Portal.get_by_tgid(update.from_id, peer_type="user", - tg_receiver=self.tgid) - else: - portal = po.Portal.get_by_entity(update.to_id, receiver_id=self.tgid) - sender = pu.Puppet.get(update.from_id) if update.from_id else None - else: - self.log.warning( - f"Unexpected message type in User#get_message_details: {type(update)}") - return update, None, None - return update, sender, portal - - def update_message(self, original_update): - update, sender, portal = self.get_message_details(original_update) - - if isinstance(update, MessageService): - if isinstance(update.action, MessageActionChannelMigrateFrom): - self.log.debug(f"Ignoring action %s to %s by %d", update.action, portal.tgid_log, - sender.id) - return - self.log.debug("Handling action %s to %s by %d", update.action, portal.tgid_log, - sender.id) - return portal.handle_telegram_action(self, sender, update.action) - - user = sender.tgid if sender else "admin" - if isinstance(original_update, (UpdateEditMessage, UpdateEditChannelMessage)): - self.log.debug("Handling edit %s to %s by %s", update, portal.tgid_log, user) - return portal.handle_telegram_edit(self, sender, update) - - self.log.debug("Handling message %s to %s by %s", update, portal.tgid_log, user) - return portal.handle_telegram_message(self, sender, update) - # endregion # region Class instance lookup