From 83ff9bf61d399189933f3f6b79329a1d931acd24 Mon Sep 17 00:00:00 2001 From: miruka Date: Fri, 8 Nov 2019 08:30:11 -0400 Subject: [PATCH] Put all nio callbacks in a new separate class --- src/python/nio_callbacks.py | 339 ------------------------------------ 1 file changed, 339 deletions(-) delete mode 100644 src/python/nio_callbacks.py diff --git a/src/python/nio_callbacks.py b/src/python/nio_callbacks.py deleted file mode 100644 index 53ae3ca9..00000000 --- a/src/python/nio_callbacks.py +++ /dev/null @@ -1,339 +0,0 @@ -import asyncio -import json -import logging as log -from contextlib import suppress -from dataclasses import dataclass, field -from datetime import datetime -from typing import Optional, Tuple - -import nio - -from . import utils -from .html_filter import HTML_FILTER -from .matrix_client import MatrixClient -from .models.items import Account, Room, TypeSpecifier - - -@dataclass -class NioCallbacks: - client: MatrixClient = field() - - - def __post_init__(self) -> None: - c = self.client - - for name, class_ in utils.classes_defined_in(nio.responses).items(): - with suppress(AttributeError): - c.add_response_callback(getattr(self, f"on{name}"), class_) - - for name, class_ in utils.classes_defined_in(nio.events).items(): - with suppress(AttributeError): - c.add_event_callback(getattr(self, f"on{name}"), class_) - - c.add_ephemeral_callback( - self.onTypingNoticeEvent, nio.events.TypingNoticeEvent, - ) - - - async def onSyncResponse(self, resp: nio.SyncResponse) -> None: - c = self.client - - for room_id, info in resp.rooms.join.items(): - if room_id not in c.past_tokens: - c.past_tokens[room_id] = info.timeline.prev_batch - - # TODO: way of knowing if a nio.MatrixRoom is left - for room_id, info in resp.rooms.leave.items(): - # TODO: handle in nio, these are rooms that were left before - # starting the client. - if room_id not in c.all_rooms: - log.warning("Left room not in MatrixClient.rooms: %r", room_id) - continue - - # TODO: handle left events in nio async client - for ev in info.timeline.events: - if isinstance(ev, nio.RoomMemberEvent): - await self.onRoomMemberEvent(c.all_rooms[room_id], ev) - - await c.register_nio_room(c.all_rooms[room_id], left=True) - - if not c.first_sync_done.is_set(): - asyncio.ensure_future(c.load_rooms_without_visible_events()) - - c.first_sync_done.set() - c.first_sync_date = datetime.now() - c.models[Account][c.user_id].first_sync_done = True - - - async def onErrorResponse(self, resp: nio.ErrorResponse) -> None: - # TODO: show something in the client, must be seen on login screen too - try: - log.warning("%s - %s", resp, json.dumps(vars(resp), indent=4)) - except Exception: - log.warning(repr(resp)) - - - # Callbacks for nio room events - # Content: %1 is the sender, %2 the target (ev.state_key). - - async def onRoomMessageText(self, room, ev) -> None: - co = HTML_FILTER.filter( - ev.formatted_body - if ev.format == "org.matrix.custom.html" else - utils.plain2html(ev.body), - ) - await self.client.register_nio_event(room, ev, content=co) - - - async def onRoomMessageEmote(self, room, ev) -> None: - co = HTML_FILTER.filter_inline( - ev.formatted_body - if ev.format == "org.matrix.custom.html" else - utils.plain2html(ev.body), - ) - await self.client.register_nio_event(room, ev, content=co) - - - async def onRoomMessageUnknown(self, room, ev) -> None: - co = "%1 sent a message this client doesn't understand." - await self.client.register_nio_event(room, ev, content=co) - - - async def onRoomMessageMedia(self, room, ev) -> None: - info = ev.source["content"].get("info", {}) - media_crypt_dict = ev.source["content"].get("file", {}) - thumb_info = info.get("thumbnail_info", {}) - thumb_crypt_dict = info.get("thumbnail_file", {}) - - await self.client.register_nio_event( - room, - ev, - content = "", - inline_content = ev.body, - - media_url = ev.url, - media_title = ev.body, - media_width = info.get("w") or 0, - media_height = info.get("h") or 0, - media_duration = info.get("duration") or 0, - media_size = info.get("size") or 0, - media_mime = info.get("mimetype") or 0, - media_crypt_dict = media_crypt_dict, - - thumbnail_url = - info.get("thumbnail_url") or thumb_crypt_dict.get("url") or "", - - thumbnail_width = thumb_info.get("w") or 0, - thumbnail_height = thumb_info.get("h") or 0, - thumbnail_crypt_dict = thumb_crypt_dict, - ) - - - async def onRoomEncryptedMedia(self, room, ev) -> None: - await self.onRoomMessageMedia(room, ev) - - - async def onRoomCreateEvent(self, room, ev) -> None: - co = "%1 allowed users on other matrix servers to join this room." \ - if ev.federate else \ - "%1 blocked users on other matrix servers from joining this room." - await self.client.register_nio_event(room, ev, content=co) - - - async def onRoomGuestAccessEvent(self, room, ev) -> None: - allowed = "allowed" if ev.guest_access else "forbad" - co = f"%1 {allowed} guests to join the room." - await self.client.register_nio_event(room, ev, content=co) - - - async def onRoomJoinRulesEvent(self, room, ev) -> None: - access = "public" if ev.join_rule == "public" else "invite-only" - co = f"%1 made the room {access}." - await self.client.register_nio_event(room, ev, content=co) - - - async def onRoomHistoryVisibilityEvent(self, room, ev) -> None: - if ev.history_visibility == "shared": - to = "all room members" - elif ev.history_visibility == "world_readable": - to = "any member or outsider" - elif ev.history_visibility == "joined": - to = "all room members, since the time they joined" - elif ev.history_visibility == "invited": - to = "all room members, since the time they were invited" - else: - to = "???" - log.warning("Invalid visibility - %s", - json.dumps(vars(ev), indent=4)) - - co = f"%1 made future room history visible to {to}." - await self.client.register_nio_event(room, ev, content=co) - - - async def onPowerLevelsEvent(self, room, ev) -> None: - co = "%1 changed the room's permissions." # TODO: improve - await self.client.register_nio_event(room, ev, content=co) - - - async def process_room_member_event( - self, room, ev, - ) -> Optional[Tuple[TypeSpecifier, str]]: - - if ev.prev_content == ev.content: - return None - - prev = ev.prev_content - now = ev.content - membership = ev.membership - prev_membership = ev.prev_membership - ev_date = datetime.fromtimestamp(ev.server_timestamp / 1000) - - member_change = TypeSpecifier.membership_change - - # Membership changes - if not prev or membership != prev_membership: - reason = f" Reason: {now['reason']}" if now.get("reason") else "" - - if membership == "join": - return ( - member_change, - "%1 accepted their invitation." - if prev and prev_membership == "invite" else - "%1 joined the room.", - ) - - if membership == "invite": - return (member_change, "%1 invited %2 to the room.") - - if membership == "leave": - if ev.state_key == ev.sender: - return ( - member_change, - f"%1 declined their invitation.{reason}" - if prev and prev_membership == "invite" else - f"%1 left the room.{reason}", - ) - - return ( - member_change, - - f"%1 withdrew %2's invitation.{reason}" - if prev and prev_membership == "invite" else - - f"%1 unbanned %2 from the room.{reason}" - if prev and prev_membership == "ban" else - - f"%1 kicked out %2 from the room.{reason}", - ) - - if membership == "ban": - return (member_change, f"%1 banned %2 from the room.{reason}") - - # Profile changes - changed = [] - - if prev and now["avatar_url"] != prev["avatar_url"]: - changed.append("profile picture") # TODO: s - - if prev and now["displayname"] != prev["displayname"]: - changed.append('display name from "{}" to "{}"'.format( - prev["displayname"] or ev.state_key, - now["displayname"] or ev.state_key, - )) - - if changed: - # Update our account profile if the event is newer than last update - if ev.state_key == self.client.user_id: - account = self.client.models[Account][self.client.user_id] - updated = account.profile_updated - - if not updated or updated < ev_date: - account.profile_updated = ev_date - account.display_name = now["displayname"] or "" - account.avatar_url = now["avatar_url"] or "" - - # Hide profile events from the timeline - XXX - self.client.skipped_events[room.room_id] += 1 - return None - - return ( - TypeSpecifier.profile_change, - "%1 changed their {}.".format(" and ".join(changed)), - ) - - log.warning("Unknown member event: %s", json.dumps(vars(ev), indent=4)) - return None - - - async def onRoomMemberEvent(self, room, ev) -> None: - type_and_content = await self.process_room_member_event(room, ev) - - if type_and_content is None: - # This is run from register_nio_event otherwise - await self.client.register_nio_room(room) - else: - type_specifier, content = type_and_content - await self.client.register_nio_event( - room, ev, content=content, type_specifier=type_specifier, - ) - - - async def onRoomAliasEvent(self, room, ev) -> None: - co = f"%1 set the room's main address to {ev.canonical_alias}." - await self.client.register_nio_event(room, ev, content=co) - - - async def onRoomNameEvent(self, room, ev) -> None: - co = f"%1 changed the room's name to \"{ev.name}\"." - await self.client.register_nio_event(room, ev, content=co) - - - async def onRoomTopicEvent(self, room, ev) -> None: - topic = HTML_FILTER.filter_inline(ev.topic) - co = f"%1 changed the room's topic to \"{topic}\"." - await self.client.register_nio_event(room, ev, content=co) - - - async def onRoomEncryptionEvent(self, room, ev) -> None: - co = "%1 turned on encryption for this room." - await self.client.register_nio_event(room, ev, content=co) - - - async def onMegolmEvent(self, room, ev) -> None: - co = "%1 sent an undecryptable message." - await self.client.register_nio_event(room, ev, content=co) - - - async def onBadEvent(self, room, ev) -> None: - co = "%1 sent a malformed event." - await self.client.register_nio_event(room, ev, content=co) - - - async def onUnknownBadEvent(self, room, ev) -> None: - co = "%1 sent an event this client doesn't understand." - await self.client.register_nio_event(room, ev, content=co) - - - # Callbacks for nio invite events - - async def onInviteEvent(self, room, ev) -> None: - await self.client.register_nio_room(room) - - - # Callbacks for nio ephemeral events - - async def onTypingNoticeEvent(self, room, ev) -> None: - # Prevent recent past typing notices from being shown for a split - # second on client startup: - if not self.client.first_sync_done.is_set(): - return - - if room.room_id not in self.client.models[Room, self.client.user_id]: - return - - room_item = self.client.models[Room, self.client.user_id][room.room_id] - - room_item.typing_members = sorted( - room.user_name(user_id) for user_id in ev.users - if user_id not in self.client.backend.clients - )