From b6c92790d60b7b8eeac438eac7508bcd9f457cf7 Mon Sep 17 00:00:00 2001 From: alemi Date: Mon, 29 Jan 2024 16:52:39 +0100 Subject: [PATCH] feat: initial async appservice rewrite --- src/aioappsrv/app.py | 99 +++++++++++++++++++++++++++++++++++++++++ src/aioappsrv/matrix.py | 52 +++++++++++++++------- 2 files changed, 134 insertions(+), 17 deletions(-) create mode 100644 src/aioappsrv/app.py diff --git a/src/aioappsrv/app.py b/src/aioappsrv/app.py new file mode 100644 index 0000000..60b539d --- /dev/null +++ b/src/aioappsrv/app.py @@ -0,0 +1,99 @@ +import asyncio +import logging + +from typing import Callable, Awaitable + +from aiohttp import ClientSession, web + +from .matrix import Event, EventType + +class AppService: + _rx: web.Application + _tx: ClientSession + _callbacks: dict[EventType, dict[str, Callable]] + + as_token: str + hs_token: str + base_url: str + user_id: str + server_name: str + + logger: logging.Logger + + def __init__( + self, + as_token: str, + hs_token: str, + base_url: str, + user_id: str, + server_name: str, + logger: logging.Logger | None = None, + ): + self._rx = web.Application() + + self.as_token = as_token + self.hs_token = hs_token + self.base_url = base_url + self.user_id = user_id + self.server_name = server_name + + self.logger = logger if logger is not None else logging.getLogger(__file__) + + self._rx.add_routes([ + web.put('/transactions/{transaction}', lambda req: self.handler(req)) + ]) + + async def handler(self, request: web.Request) -> web.Response: + self.logger.debug("handling webhook callback %s", request) + hs_token = request.query.getone("access_token") + if not hs_token: + return web.Response(status=401) + if hs_token != self.hs_token: + return web.Response(status=403) + body = await request.json() + for doc in body["events"]: + event = Event(**doc) + if event.type not in EventType: + self.logger.warn("unhandled event of type %s, ignoring", event["type"]) + continue + self.logger.debug("dispatching event %s for %s", event.type) + asyncio.get_event_loop().create_task( + self._callbacks[event.type][event.room_id](event) + ) + + return web.Response() + + def callback(self, room: str, event: EventType | str = EventType.MESSAGE) -> Callable: + def wrapper(func: Callable[[Event], Awaitable[None]]): + if isinstance(event, str): + key = EventType[event] + else: + key = event + self._callbacks[key][room] = func + return func + return wrapper + + async def join_room(self): + pass + + async def leave_room(self): + pass + + async def send_message(self): + pass + + async def redact_message(self): + pass + + async def start(self, host: str = "localhost", port: int = 25511): + runner = web.AppRunner(self._rx) + await runner.setup() + site = web.TCPSite(runner, host, port) + await site.start() + + + +# utility function in case we need to block forever +async def idle(): + while True: + await asyncio.sleep(3600) diff --git a/src/aioappsrv/matrix.py b/src/aioappsrv/matrix.py index 3f3f66f..15fbc66 100644 --- a/src/aioappsrv/matrix.py +++ b/src/aioappsrv/matrix.py @@ -1,28 +1,46 @@ +from typing import Any +from enum import Enum from dataclasses import dataclass - @dataclass class User: avatar_url: str = "" display_name: str = "" +class EventType(Enum): + MESSAGE = "m.room.message" + MEMBER = "m.room.member" + REDACTION = "m.room.redaction" +@dataclass class Event: - def __init__(self, event: dict): - content = event.get("content", {}) + content: dict[str, Any] + type: EventType + event_id: str + origin_server_ts: int + room_id: str + sender: str + state_key: str | None = None + redacts: str | None = None + unsigned: dict | None = None - self.attachment = content.get("url") - self.body = content.get("body", "").strip() - self.formatted_body = content.get("formatted_body", "") - self.id = event["event_id"] - self.is_direct = content.get("is_direct", False) - self.redacts = event.get("redacts", "") - self.room_id = event["room_id"] - self.sender = event["sender"] - self.state_key = event.get("state_key", "") - rel = content.get("m.relates_to", {}) - - self.relates_to = rel.get("event_id") - self.reltype = rel.get("rel_type") - self.new_body = content.get("m.new_content", {}).get("body", "") +# class Event: +# def __init__(self, event: dict): +# content = event.get("content", {}) +# +# self.attachment = content.get("url") +# self.body = content.get("body", "").strip() +# self.formatted_body = content.get("formatted_body", "") +# self.id = event["event_id"] +# self.is_direct = content.get("is_direct", False) +# self.redacts = event.get("redacts", "") +# self.room_id = event["room_id"] +# self.sender = event["sender"] +# self.state_key = event.get("state_key", "") +# +# rel = content.get("m.relates_to", {}) +# +# self.relates_to = rel.get("event_id") +# self.reltype = rel.get("rel_type") +# self.new_body = content.get("m.new_content", {}).get("body", "")