async def handle_transaction( self, txn_id: str, *, events: list[JSON], extra_data: JSON, ephemeral: list[JSON] | None = None, device_otk_count: dict[UserID, DeviceOTKCount] | None = None, device_lists: DeviceLists | None = None, ) -> JSON: for raw_edu in ephemeral or []: try: edu = EphemeralEvent.deserialize(raw_edu) except SerializerError: self.log.exception("Failed to deserialize ephemeral event %s", raw_edu) else: self.handle_matrix_event(edu) for raw_event in events: try: self._fix_prev_content(raw_event) event = Event.deserialize(raw_event) except SerializerError: self.log.exception("Failed to deserialize event %s", raw_event) else: self.handle_matrix_event(event) return {}
async def handle_transaction(self, txn_id: str, events: List[JSON]) -> None: for raw_event in events: try: self._fix_prev_content(raw_event) event = Event.deserialize(raw_event) except SerializerError: self.log.exception("Failed to deserialize event %s", raw_event) else: self.handle_matrix_event(event)
async def get_messages( self, room_id: RoomID, direction: PaginationDirection, from_token: SyncToken, to_token: Optional[SyncToken] = None, limit: Optional[int] = None, filter_json: Optional[str] = None) -> PaginatedMessages: """ Get a list of message and state events for a room. Pagination parameters are used to paginate history in the room. See also: `/messages API reference`_ Args: room_id: The ID of the room to get events from. direction: The direction to return events from. from_token: The token to start returning events from. This token can be obtained from a ``prev_batch`` token returned for each room by the `sync endpoint`_, or from a ``start`` or ``end`` token returned by a previous request to this endpoint. to_token: The token to stop returning events at. limit: The maximum number of events to return. Defaults to 10. filter_json: A JSON RoomEventFilter_ to filter returned events with. Returns: .. _RoomEventFilter: https://matrix.org/docs/spec/client_server/r0.5.0#filtering .. _sync endpoint: https://matrix.org/docs/spec/client_server/r0.5.0#get-matrix-client-r0-sync .. _/messages API reference: https://matrix.org/docs/spec/client_server/r0.5.0#get-matrix-client-r0-rooms-roomid-messages """ query_params = { "from": from_token, "dir": direction.value, } if to_token: query_params["to"] = to_token if limit: query_params["limit"] = str(limit) if filter: query_params["filter"] = filter_json content = await self.api.request(Method.GET, Path.rooms[room_id].messages, query_params=query_params) try: return PaginatedMessages( content["start"], content["end"], [Event.deserialize(event) for event in content["chunk"]]) except KeyError: if "start" not in content: raise MatrixResponseError("`start` not in response.") elif "end" not in content: raise MatrixResponseError("`start` not in response.") raise MatrixResponseError("`content` not in response.") except SerializerError as e: raise MatrixResponseError("Invalid events in response") from e
def decrypt(self, event: EncryptedEvent) -> MessageEvent: serialized = event.serialize() event = self.client.decrypt_event( NioEvent.parse_encrypted_event(serialized)) try: event.source["content"]["m.relates_to"] = serialized["content"][ "m.relates_to"] except KeyError: pass return Event.deserialize(event.source)
def _handle_sync(self, sync_resp: dict) -> None: # Get events from rooms -> join -> [room_id] -> ephemeral -> events (array) ephemeral_events = ( event for room_id, data in sync_resp.get("rooms", {}).get("join", {}).items() for event in self._filter_events(room_id, data.get("ephemeral", {}).get("events", [])) ) # Get events from presence -> events (array) presence_events = sync_resp.get("presence", {}).get("events", []) # Deserialize and handle all events for event in chain(ephemeral_events, presence_events): asyncio.create_task(self.mx.try_handle_sync_event(Event.deserialize(event)))
def handle_sync(self, sync_resp: Dict) -> None: # Get events from rooms -> join -> [room_id] -> ephemeral -> events (array) ephemeral_events = (event for room_id, data in sync_resp.get( "rooms", {}).get("join", {}).items() for event in self._filter_events( room_id, data.get("ephemeral", {}).get("events", []))) # Get events from presence -> events (array) presence_events = sync_resp.get("presence", {}).get("events", []) # Deserialize and handle all events coro = asyncio.gather(*[ self.mx.try_handle_event(Event.deserialize(event)) for event in chain(ephemeral_events, presence_events) ], loop=self.loop) asyncio.ensure_future(coro, loop=self.loop)
async def handle_transaction( self, txn_id: str, events: List[JSON], ephemeral: Optional[List[JSON]] = None) -> None: for raw_edu in ephemeral or []: try: edu = EphemeralEvent.deserialize(raw_edu) except SerializerError: self.log.exception("Failed to deserialize ephemeral event %s", raw_edu) else: self.handle_matrix_event(edu) for raw_event in events: try: self._fix_prev_content(raw_event) event = Event.deserialize(raw_event) except SerializerError: self.log.exception("Failed to deserialize event %s", raw_event) else: self.handle_matrix_event(event)
async def get_event(self, room_id: RoomID, event_id: EventID) -> Event: """ Get a single event based on ``room_id``/``event_id``. You must have permission to retrieve this event e.g. by being a member in the room for this event. See also: `/event/{eventId} API reference`_ Args: room_id: The ID of the room the event is in. event_id: The event ID to get. Returns: The event. .. _/event/{eventId} API reference: https://matrix.org/docs/spec/client_server/r0.5.0#get-matrix-client-r0-rooms-roomid-event-eventid """ content = await self.api.request(Method.GET, Path.rooms[room_id].event[event_id]) try: return Event.deserialize(content) except SerializerError as e: raise MatrixResponseError("Invalid event in response") from e
async def get_event(self, room_id: RoomID, event_id: EventID) -> Event: """ Get a single event based on ``room_id``/``event_id``. You must have permission to retrieve this event e.g. by being a member in the room for this event. See also: `API reference <https://spec.matrix.org/v1.1/client-server-api/#get_matrixclientv3roomsroomideventeventid>`__ Args: room_id: The ID of the room the event is in. event_id: The event ID to get. Returns: The event. """ content = await self.api.request( Method.GET, Path.v3.rooms[room_id].event[event_id], metrics_method="getEvent") try: return Event.deserialize(content) except SerializerError as e: raise MatrixResponseError("Invalid event in response") from e
def handle_sync(self, data: JSON) -> List[asyncio.Task]: """ Handle a /sync object. Args: data: The data from a /sync request. """ tasks = [] otk_count = data.get("device_one_time_keys_count", {}) tasks += self.dispatch_internal_event( InternalEventType.DEVICE_OTK_COUNT, custom_type=DeviceOTKCount(curve25519=otk_count.get("curve25519", 0), signed_curve25519=otk_count.get("signed_curve25519", 0))) device_lists = data.get("device_lists", {}) tasks += self.dispatch_internal_event(InternalEventType.DEVICE_LISTS, custom_type=DeviceLists( changed=device_lists.get("changed", []), left=device_lists.get("left", []))) for raw_event in data.get("account_data", {}).get("events", []): tasks += self.dispatch_event(AccountDataEvent.deserialize(raw_event), source=SyncStream.ACCOUNT_DATA) for raw_event in data.get("ephemeral", {}).get("events", []): tasks += self.dispatch_event(EphemeralEvent.deserialize(raw_event), source=SyncStream.EPHEMERAL) for raw_event in data.get("to_device", {}).get("events", []): tasks += self.dispatch_event(ToDeviceEvent.deserialize(raw_event), source=SyncStream.TO_DEVICE) rooms = data.get("rooms", {}) for room_id, room_data in rooms.get("join", {}).items(): for raw_event in room_data.get("state", {}).get("events", []): raw_event["room_id"] = room_id tasks += self.dispatch_event(StateEvent.deserialize(raw_event), source=SyncStream.JOINED_ROOM | SyncStream.STATE) for raw_event in room_data.get("timeline", {}).get("events", []): raw_event["room_id"] = room_id tasks += self.dispatch_event(Event.deserialize(raw_event), source=SyncStream.JOINED_ROOM | SyncStream.TIMELINE) for room_id, room_data in rooms.get("invite", {}).items(): events: List[Dict[str, Any]] = room_data.get("invite_state", {}).get("events", []) for raw_event in events: raw_event["room_id"] = room_id raw_invite = next(raw_event for raw_event in events if raw_event.get("type", "") == "m.room.member" and raw_event.get("state_key", "") == self.mxid) # These aren't required by the spec, so make sure they're set raw_invite.setdefault("event_id", None) raw_invite.setdefault("origin_server_ts", int(time() * 1000)) raw_invite.setdefault("unsigned", {}) invite = StateEvent.deserialize(raw_invite) invite.unsigned.invite_room_state = [StrippedStateEvent.deserialize(raw_event) for raw_event in events if raw_event != raw_invite] tasks += self.dispatch_event(invite, source=SyncStream.INVITED_ROOM | SyncStream.STATE) for room_id, room_data in rooms.get("leave", {}).items(): for raw_event in room_data.get("timeline", {}).get("events", []): if "state_key" in raw_event: raw_event["room_id"] = room_id tasks += self.dispatch_event(StateEvent.deserialize(raw_event), source=SyncStream.LEFT_ROOM | SyncStream.TIMELINE) return tasks
async def decrypt_megolm_event(self, evt: EncryptedEvent) -> Event: """ Decrypt an event that was encrypted using Megolm. Args: evt: The whole encrypted event. Returns: The decrypted event, including some unencrypted metadata from the input event. Raises: DecryptionError: If decryption failed. """ if not isinstance(evt.content, EncryptedMegolmEventContent): raise DecryptionError("Unsupported event content class") elif evt.content.algorithm != EncryptionAlgorithm.MEGOLM_V1: raise DecryptionError("Unsupported event encryption algorithm") session = await self.crypto_store.get_group_session(evt.room_id, evt.content.sender_key, evt.content.session_id) if session is None: # TODO check if olm session is wedged raise DecryptionError("Failed to decrypt megolm event: no session with given ID found") try: plaintext, index = session.decrypt(evt.content.ciphertext) except olm.OlmGroupSessionError as e: raise DecryptionError("Failed to decrypt megolm event") from e if not await self.crypto_store.validate_message_index(evt.content.sender_key, evt.content.session_id, evt.event_id, index, evt.timestamp): raise DecryptionError("Duplicate message index") verified = False if ((evt.content.device_id == self.client.device_id and session.signing_key == self.account.signing_key and evt.content.sender_key == self.account.identity_key)): verified = True else: device = await self.crypto_store.get_device(evt.sender, evt.content.device_id) if device and device.trust == TrustState.VERIFIED and not session.forwarding_chain: if ((device.signing_key != session.signing_key or device.identity_key != evt.content.sender_key)): raise DecryptionError("Device keys in event and verified device info " "do not match") verified = True # else: TODO query device keys? try: data = json.loads(plaintext) room_id = data["room_id"] event_type = data["type"] content = data["content"] except json.JSONDecodeError as e: raise DecryptionError("Failed to parse megolm payload") from e except KeyError as e: raise DecryptionError("Megolm payload is missing fields") from e if room_id != evt.room_id: raise DecryptionError("Encrypted megolm event is not intended for this room") result = Event.deserialize({ "room_id": evt.room_id, "event_id": evt.event_id, "sender": evt.sender, "origin_server_ts": evt.timestamp, "type": event_type, "content": content, }) result.unsigned = evt.unsigned if evt.content.relates_to: if hasattr(result.content, "relates_to"): if not result.content.relates_to: result.content.relates_to = evt.content.relates_to elif "m.relates_to" not in result.content: result.content["m.relates_to"] = evt.content.relates_to.serialize() result.type = result.type.with_class(evt.type.t_class) result["mautrix"] = { "verified": verified, } return result
async def get_messages( self, room_id: RoomID, direction: PaginationDirection, from_token: SyncToken, to_token: SyncToken | None = None, limit: int | None = None, filter_json: str | dict | RoomEventFilter | None = None, ) -> PaginatedMessages: """ Get a list of message and state events for a room. Pagination parameters are used to paginate history in the room. See also: `API reference <https://spec.matrix.org/v1.1/client-server-api/#get_matrixclientv3roomsroomidmessages>`__ Args: room_id: The ID of the room to get events from. direction: The direction to return events from. from_token: The token to start returning events from. This token can be obtained from a ``prev_batch`` token returned for each room by the `sync endpoint`_, or from a ``start`` or ``end`` token returned by a previous request to this endpoint. to_token: The token to stop returning events at. limit: The maximum number of events to return. Defaults to 10. filter_json: A JSON RoomEventFilter_ to filter returned events with. Returns: .. _RoomEventFilter: https://spec.matrix.org/v1.1/client-server-api/#filtering .. _sync endpoint: https://spec.matrix.org/v1.1/client-server-api/#get_matrixclientv3sync """ if isinstance(filter_json, Serializable): filter_json = filter_json.json() elif isinstance(filter_json, dict): filter_json = json.dumps(filter_json) query_params = { "from": from_token, "dir": direction.value, "to": to_token, "limit": str(limit) if limit else None, "filter": filter_json, } content = await self.api.request( Method.GET, Path.v3.rooms[room_id].messages, query_params=query_params, metrics_method="getMessages", ) try: return PaginatedMessages( content["start"], content["end"], [Event.deserialize(event) for event in content["chunk"]], ) except KeyError: if "start" not in content: raise MatrixResponseError("`start` not in response.") elif "end" not in content: raise MatrixResponseError("`start` not in response.") raise MatrixResponseError("`content` not in response.") except SerializerError as e: raise MatrixResponseError("Invalid events in response") from e