async def send_message(self, request): access_token = self.get_access_token(request) if not access_token: return self._missing_token client = await self._find_client(access_token) if not client: return self._unknown_token room_id = request.match_info["room_id"] # The room is not in the joined rooms list, just forward it. try: room = client.rooms[room_id] encrypt = room.encrypted except KeyError: return await self.forward_to_web(request, token=client.access_token) # Don't encrypt reactions for now - they are weird and clients # need to support them like this. # TODO: Fix when MSC1849 is fully supported by clients. if request.match_info["event_type"] == "m.reaction": encrypt = False # The room isn't encrypted just forward the message. if not encrypt: return await self.forward_to_web(request, token=client.access_token) msgtype = request.match_info["event_type"] txnid = request.match_info.get("txnid", uuid4()) try: content = await request.json() except (JSONDecodeError, ContentTypeError): return self._not_json async def _send(ignore_unverified=False): try: response = await client.room_send(room_id, msgtype, content, txnid, ignore_unverified) return web.Response( status=response.transport_response.status, content_type=response.transport_response.content_type, headers=CORS_HEADERS, body=await response.transport_response.read(), ) except ClientConnectionError as e: return web.Response(status=500, text=str(e)) except SendRetryError as e: return web.Response(status=503, text=str(e)) # Aquire a semaphore here so we only send out one # UnverifiedDevicesSignal sem = client.send_semaphores[room_id] async with sem: # Even though we request the full state we don't receive room # members since we're using lazy loading. The summary is for some # reason empty so nio can't know if room members are missing from # our state. Fetch the room members here instead. if not client.room_members_fetched[room_id]: try: await client.joined_members(room_id) client.room_members_fetched[room_id] = True except ClientConnectionError as e: return web.Response(status=500, text=str(e)) try: return await _send(self.conf.ignore_verification) except OlmTrustError as e: # There are unverified/unblocked devices in the room, notify # the UI thread about this and wait for a response. queue = asyncio.Queue() client.send_decision_queues[room_id] = queue message = UnverifiedDevicesSignal(client.user_id, room_id, room.display_name) await self.send_ui_message(message) try: response = await asyncio.wait_for( queue.get(), self.unverified_send_timeout) if isinstance(response, CancelSendingMessage): # The send was canceled notify the client that sent the # request about this. info_msg = (f"Canceled message sending for room " f"{room.display_name} ({room_id}).") logger.info(info_msg) await self.send_response(response.message_id, client.user_id, "m.ok", info_msg) return web.Response(status=503, text=str(e)) elif isinstance(response, SendAnywaysMessage): # We are sending and ignoring devices along the way. info_msg = (f"Ignoring unverified devices and sending " f"message to room " f"{room.display_name} ({room_id}).") logger.info(info_msg) await self.send_response(response.message_id, client.user_id, "m.ok", info_msg) ret = await _send(True) await client.send_update_devices( client.room_devices(room_id)) return ret except asyncio.TimeoutError: # We didn't get a response to our signal, send out an error # response. return web.Response( status=503, text=(f"Room contains unverified devices and no " f"action was taken for " f"{self.unverified_send_timeout} seconds, " f"request timed out"), ) finally: client.send_decision_queues.pop(room_id)
async def send_message(self, request): access_token = self.get_access_token(request) if not access_token: return self._missing_token client = await self._find_client(access_token) if not client: return self._unknown_token room_id = request.match_info["room_id"] try: room = client.rooms[room_id] encrypt = room.encrypted except KeyError: # The room is not in the joined rooms list, either the pan client # didn't manage to sync the state or we're not joined, in either # case send an error response. if client.has_been_synced: return web.json_response( { "errcode": "M_FORBIDDEN", "error": "You do not have permission to send the event." }, headers=CORS_HEADERS, status=403, ) else: logger.error("The internal Pantalaimon client did not manage " "to sync with the server.") return web.json_response( { "errcode": "M_UNKNOWN", "error": "The pantalaimon client did not manage to sync with " "the server", }, headers=CORS_HEADERS, status=500, ) # Don't encrypt reactions for now - they are weird and clients # need to support them like this. # TODO: Fix when MSC1849 is fully supported by clients. if request.match_info["event_type"] == "m.reaction": encrypt = False msgtype = request.match_info["event_type"] try: content = await request.json() except (JSONDecodeError, ContentTypeError): return self._not_json # The room isn't encrypted just forward the message. if not encrypt: content_msgtype = content.get("msgtype") if (content_msgtype in ["m.image", "m.video", "m.audio", "m.file"] or msgtype == "m.room.avatar"): try: content["url"] = await self._decrypt_uri( content["url"], client) if ("info" in content and "thumbnail_url" in content["info"] and not content["info"]["thumbnail_url"] == None): content["info"][ "thumbnail_url"] = await self._decrypt_uri( content["info"]["thumbnail_url"], client) return await self.forward_to_web(request, data=json.dumps(content), token=client.access_token) except ClientConnectionError as e: return web.Response(status=500, text=str(e)) except (KeyError, NotDecryptedAvailableError): return await self.forward_to_web(request, token=client.access_token) return await self.forward_to_web(request, token=client.access_token) txnid = request.match_info.get("txnid", uuid4()) async def _send(ignore_unverified=False): try: content_msgtype = content.get("msgtype") if (content_msgtype in ["m.image", "m.video", "m.audio", "m.file"] or msgtype == "m.room.avatar"): upload_info, media_info = self._get_upload_and_media_info( content["url"]) if not upload_info or not media_info: response = await client.room_send( room_id, msgtype, content, txnid, ignore_unverified) return web.Response( status=response.transport_response.status, content_type=response.transport_response. content_type, headers=CORS_HEADERS, body=await response.transport_response.read(), ) media_info.to_content(content, upload_info.mimetype) if content["info"].get("thumbnail_url", False): ( thumb_upload_info, thumb_media_info, ) = self._get_upload_and_media_info( content["info"]["thumbnail_url"]) if thumb_upload_info and thumb_media_info: thumb_media_info.to_thumbnail( content, thumb_upload_info.mimetype) response = await client.room_send(room_id, msgtype, content, txnid, ignore_unverified) else: response = await client.room_send(room_id, msgtype, content, txnid, ignore_unverified) return web.Response( status=response.transport_response.status, content_type=response.transport_response.content_type, headers=CORS_HEADERS, body=await response.transport_response.read(), ) except ClientConnectionError as e: return web.Response(status=500, text=str(e)) except SendRetryError as e: return web.Response(status=503, text=str(e)) # Aquire a semaphore here so we only send out one # UnverifiedDevicesSignal sem = client.send_semaphores[room_id] async with sem: # Even though we request the full state we don't receive room # members since we're using lazy loading. The summary is for some # reason empty so nio can't know if room members are missing from # our state. Fetch the room members here instead. if not client.room_members_fetched[room_id]: try: await client.joined_members(room_id) client.room_members_fetched[room_id] = True except ClientConnectionError as e: return web.Response(status=500, text=str(e)) try: return await _send(self.conf.ignore_verification) except OlmTrustError as e: # There are unverified/unblocked devices in the room, notify # the UI thread about this and wait for a response. queue = asyncio.Queue() client.send_decision_queues[room_id] = queue message = UnverifiedDevicesSignal(client.user_id, room_id, room.display_name) await self.send_ui_message(message) try: response = await asyncio.wait_for( queue.get(), self.unverified_send_timeout) if isinstance(response, CancelSendingMessage): # The send was canceled notify the client that sent the # request about this. info_msg = (f"Canceled message sending for room " f"{room.display_name} ({room_id}).") logger.info(info_msg) await self.send_response(response.message_id, client.user_id, "m.ok", info_msg) return web.Response(status=503, text=str(e)) elif isinstance(response, SendAnywaysMessage): # We are sending and ignoring devices along the way. info_msg = (f"Ignoring unverified devices and sending " f"message to room " f"{room.display_name} ({room_id}).") logger.info(info_msg) await self.send_response(response.message_id, client.user_id, "m.ok", info_msg) ret = await _send(True) await client.send_update_devices( client.room_devices(room_id)) return ret except asyncio.TimeoutError: # We didn't get a response to our signal, send out an error # response. return web.Response( status=503, text=(f"Room contains unverified devices and no " f"action was taken for " f"{self.unverified_send_timeout} seconds, " f"request timed out"), ) finally: client.send_decision_queues.pop(room_id)