From b862399bfbd7f2c37892c7b785d152d26c23241f Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Mon, 22 Jan 2018 21:49:21 +0200 Subject: [PATCH] Refactor and add region comments --- mautrix_appservice/intent_api.py | 19 +++++-- mautrix_telegram/formatter.py | 7 +++ mautrix_telegram/portal.py | 89 ++++++++++++++++++++------------ mautrix_telegram/user.py | 84 ++++++++++++++++++++---------- 4 files changed, 135 insertions(+), 64 deletions(-) diff --git a/mautrix_appservice/intent_api.py b/mautrix_appservice/intent_api.py index 99f628ff..aaf0dba5 100644 --- a/mautrix_appservice/intent_api.py +++ b/mautrix_appservice/intent_api.py @@ -152,6 +152,8 @@ class IntentAPI: else: raise ValueError("IntentAPI#user() is only available for base intent objects.") + # region User actions + def set_display_name(self, name): self._ensure_registered() return self.client.set_display_name(self.mxid, name) @@ -165,9 +167,8 @@ class IntentAPI: mime_type = mime_type or magic.from_buffer(photo_data, mime=True) return self.client.media_upload(photo_data, mime_type) - def set_typing(self, room_id, is_typing=True, timeout=5000): - self._ensure_joined(room_id) - return self.client.set_typing(room_id, is_typing, timeout) + # endregion + # region Room actions def create_room(self, alias=None, is_public=False, name=None, topic=None, is_direct=False, invitees=()): @@ -180,8 +181,6 @@ class IntentAPI: } if info: content["info"] = info - self._ensure_joined(room_id) - self._ensure_has_power_level_for(room_id, "m.room.avatar") return self.send_state_event(room_id, "m.room.avatar", content) def set_room_name(self, room_id, name): @@ -189,6 +188,10 @@ class IntentAPI: self._ensure_has_power_level_for(room_id, "m.room.name") return self.client.set_room_name(room_id, name) + def set_typing(self, room_id, is_typing=True, timeout=5000): + self._ensure_joined(room_id) + return self.client.set_typing(room_id, is_typing, timeout) + def send_text(self, room_id, text, html=None, notice=False): if html: return self.send_message(room_id, { @@ -227,6 +230,9 @@ class IntentAPI: return [membership["state_key"] for membership in memberships["chunk"] if membership["content"]["membership"] == "join"] + # endregion + # region Ensure functions + def _ensure_joined(self, room_id, ignore_cache=False): if not ignore_cache and self.memberships.get(room_id, "") == "join": return @@ -255,4 +261,7 @@ class IntentAPI: self.registered = True def _ensure_has_power_level_for(self, room_id, event_type): + # TODO implement pass + + # endregion diff --git a/mautrix_telegram/formatter.py b/mautrix_telegram/formatter.py index d98e7a77..6390c208 100644 --- a/mautrix_telegram/formatter.py +++ b/mautrix_telegram/formatter.py @@ -24,6 +24,8 @@ from .db import Message as DBMessage log = None +# region Matrix to Telegram + class MessageEntityReply(MessageEntityUnknown): def __init__(self, offset=0, length=0, msg_id=0): super().__init__(offset, length) @@ -186,6 +188,9 @@ def matrix_to_telegram(html, user_id=None): log.exception("Failed to convert Matrix format:\nhtml=%s", html) +# endregion +# region Telegram to Matrix + def telegram_event_to_matrix(evt, source): text = evt.message html = telegram_to_matrix(evt.message, evt.entities) if evt.entities else None @@ -301,6 +306,8 @@ def _telegram_to_matrix(text, entities): return "".join(html) +# endregion + def init(context): global log _, _, parent_log, _ = context diff --git a/mautrix_telegram/portal.py b/mautrix_telegram/portal.py index 1548fb73..a484db00 100644 --- a/mautrix_telegram/portal.py +++ b/mautrix_telegram/portal.py @@ -40,11 +40,26 @@ class Portal: if mxid: self.by_mxid[mxid] = self + @property + def peer(self): + if self.peer_type == "user": + return PeerUser(user_id=self.tgid) + elif self.peer_type == "chat": + return PeerChat(chat_id=self.tgid) + elif self.peer_type == "channel": + return PeerChannel(channel_id=self.tgid) + + # region Matrix room info updating + def get_main_intent(self): direct = self.peer_type == "user" puppet = p.Puppet.get(self.tgid) if direct else None return puppet.intent if direct else self.az.intent + def invite_matrix(self, users=[]): + # TODO implement + pass + def create_room(self, user, entity=None, invites=[], update_if_exists=True): self.log.debug("Creating room for %d", self.tgid) if not entity: @@ -117,6 +132,20 @@ class Portal: if changed: self.save() + def get_users(self, user, entity): + if self.peer_type == "chat": + return user.client(GetFullChatRequest(chat_id=self.tgid)).users + elif self.peer_type == "channel": + participants = user.client(GetParticipantsRequest( + entity, ChannelParticipantsRecent(), offset=0, limit=100, hash=0 + )) + return participants.users + elif self.peer_type == "user": + return [entity] + + # endregion + # region Matrix event handling + def handle_matrix_message(self, sender, message, event_id): type = message["msgtype"] if type == "m.text": @@ -136,10 +165,16 @@ class Portal: DBMessage(tgid=response.id, mx_room=self.mxid, mxid=event_id, user=sender.tgid)) self.db.commit() + # endregion + # region Telegram event handling + def handle_telegram_typing(self, user, event): user.intent.set_typing(self.mxid, is_typing=True) def handle_telegram_message(self, source, sender, evt): + if not self.mxid: + self.create_room(self, invites=[source.mxid]) + self.log.debug("Sending %s to %s by %d", evt.message, self.mxid, sender.id) if evt.message: text, html = formatter.telegram_event_to_matrix(evt, source) @@ -148,6 +183,20 @@ class Portal: user=source.tgid)) self.db.commit() + def handle_telegram_action(self, source, sender, action): + if not self.mxid: + return + + intent = self.get_main_intent() + action_type = type(action) + if action_type == MessageActionChatEditTitle: + if self.update_title(action.title, intent): + self.save() + elif action_type == MessageActionChatEditPhoto: + largest_size = max(action.photo.sizes, key=lambda photo: photo.size) + if self.update_avatar(source, largest_size.location, intent): + self.save() + def update_title(self, title, intent=None): if self.title != title: self.title = title @@ -175,39 +224,8 @@ class Portal: return True return False - def handle_telegram_action(self, source, sender, action): - intent = self.get_main_intent() - action_type = type(action) - if action_type == MessageActionChatEditTitle: - if self.update_title(action.title, intent): - self.save() - elif action_type == MessageActionChatEditPhoto: - largest_size = max(action.photo.sizes, key=lambda photo: photo.size) - if self.update_avatar(source, largest_size.location, intent): - self.save() - - @property - def peer(self): - if self.peer_type == "user": - return PeerUser(user_id=self.tgid) - elif self.peer_type == "chat": - return PeerChat(chat_id=self.tgid) - elif self.peer_type == "channel": - return PeerChannel(channel_id=self.tgid) - - def get_users(self, user, entity): - if self.peer_type == "chat": - return user.client(GetFullChatRequest(chat_id=self.tgid)).users - elif self.peer_type == "channel": - participants = user.client(GetParticipantsRequest( - entity, ChannelParticipantsRecent(), offset=0, limit=100, hash=0 - )) - return participants.users - elif self.peer_type == "user": - return [entity] - - def invite_matrix(self, users=[]): - pass + # endregion + # region Database conversion def to_db(self): return self.db.merge(DBPortal(tgid=self.tgid, peer_type=self.peer_type, mxid=self.mxid, @@ -223,6 +241,9 @@ class Portal: return Portal(db_portal.tgid, db_portal.peer_type, db_portal.mxid, db_portal.username, db_portal.title, db_portal.photo_id) + # endregion + # region Class instance lookup + @classmethod def get_by_mxid(cls, mxid): try: @@ -280,6 +301,8 @@ class Portal: raise ValueError(f"Unknown entity type {entity_type.__name__}") return cls.get_by_tgid(id, type_name) + # endregion + def init(context): global config diff --git a/mautrix_telegram/user.py b/mautrix_telegram/user.py index 5f301fcd..ce04d276 100644 --- a/mautrix_telegram/user.py +++ b/mautrix_telegram/user.py @@ -44,6 +44,8 @@ class User: def logged_in(self): return self.client.is_user_authorized() + # region Database conversion + def to_db(self): return self.db.merge(DBUser(mxid=self.mxid, tgid=self.tgid, tg_username=self.username)) @@ -55,6 +57,9 @@ class User: def from_db(cls, db_user): return User(db_user.mxid, db_user.tgid, db_user.tg_username) + # endregion + # region Telegram connection management + def start(self): self.client = TelegramClient(self.mxid, config["telegram.api_id"], @@ -67,6 +72,14 @@ class User: self.client.add_update_handler(self.update_catch) return self + def stop(self): + self.client.disconnect() + self.client = None + self.connected = False + + # endregion + # region Telegram actions that need custom methods + def update_info(self, info=None): info = info or self.client.get_me() self.username = info.username @@ -84,11 +97,6 @@ class User: pass return self.client.log_out() - def stop(self): - self.client.disconnect() - self.client = None - self.connected = False - def send_message(self, entity, message, reply_to=None, entities=None, link_preview=True): entity = self.client.get_input_entity(entity) @@ -124,6 +132,9 @@ class User: portal = po.Portal.get_by_entity(entity) portal.create_room(self, entity, invites=[self.mxid]) + # endregion + # region Telegram update handling + def update_catch(self, update): try: self.update(update) @@ -132,36 +143,52 @@ class User: def update(self, update): update_type = type(update) + + if update_type in {UpdateShortChatMessage, UpdateShortMessage, UpdateNewMessage, + UpdateNewChannelMessage}: + return self.update_message(update) + elif update_type in {UpdateChatUserTyping, UpdateUserTyping}: + return self.update_typing(update) + elif update_type == UpdateUserStatus: + return self.update_status(update) + else: + self.log.debug("Unhandled update: %s", update) + return + + def get_message_details(self, update): + update_type = type(update) if update_type == UpdateShortChatMessage: portal = po.Portal.get_by_tgid(update.chat_id, "chat") sender = pu.Puppet.get(update.from_id) elif update_type == UpdateShortMessage: portal = po.Portal.get_by_tgid(update.user_id, "user") sender = pu.Puppet.get(self.tgid if update.out else update.user_id) - elif update_type == UpdateChatUserTyping or update_type == UpdateUserTyping: - if update_type == UpdateUserTyping: - portal = po.Portal.get_by_tgid(update.user_id, "user") - else: - portal = po.Portal.get_by_tgid(update.chat_id, "chat") - sender = pu.Puppet.get(update.user_id) - return portal.handle_telegram_typing(sender, update) - elif update_type == UpdateUserStatus: - puppet = pu.Puppet.get(update.user_id) - if isinstance(update.status, UserStatusOnline): - puppet.intent.set_presence("online") - elif isinstance(update.status, UserStatusOffline): - puppet.intent.set_presence("offline") - return - elif update_type == UpdateNewMessage or update_type == UpdateNewChannelMessage: + elif update_type in {UpdateNewMessage, UpdateNewChannelMessage}: update = update.message sender = pu.Puppet.get(update.from_id) portal = po.Portal.get_by_entity(update.to_id) - else: - self.log.debug("Unhandled update: %s", update) - return + return update, sender, portal - if not portal.mxid: - portal.create_room(self, invites=[self.mxid]) + def update_typing(self, update): + update_type = type(update) + if update_type == UpdateUserTyping: + portal = po.Portal.get_by_tgid(update.user_id, "user") + else: + portal = po.Portal.get_by_tgid(update.chat_id, "chat") + sender = pu.Puppet.get(update.user_id) + return portal.handle_telegram_typing(sender, update) + + def update_status(self, update): + puppet = pu.Puppet.get(update.user_id) + status = type(update.status) + if status == UserStatusOnline: + puppet.intent.set_presence("online") + elif status == UserStatusOffline: + puppet.intent.set_presence("offline") + return + + def update_message(self, update): + update, sender, portal = self.get_message_details(update) if isinstance(update, MessageService): self.log.debug("Handling action portal=%s sender=%s action=%s", portal, sender, @@ -169,9 +196,12 @@ class User: portal.handle_telegram_action(self, sender, update.action) else: self.log.debug("Handling message portal=%s sender=%s update=%s", portal, sender, - update) + update) portal.handle_telegram_message(self, sender, update) + # endregion + # region Class instance lookup + @classmethod def get_by_mxid(cls, mxid, create=True): try: @@ -215,6 +245,8 @@ class User: return cls.from_db(puppet) return None + # endregion + def init(context): global config