From a947e558bf59b1b246ab6d958b99546b7807a1aa Mon Sep 17 00:00:00 2001 From: Dusty Pianalto Date: Sat, 19 Oct 2019 11:38:09 -0800 Subject: [PATCH] Start building client functionality --- lib/{http.py => api.py} | 49 ++++++++++++++++++++++++----- lib/client.py | 70 +++++++++++++++++++++++++++++++++++++++++ lib/context.py | 1 + lib/events.py | 1 + lib/room.py | 1 + 5 files changed, 115 insertions(+), 7 deletions(-) rename lib/{http.py => api.py} (80%) create mode 100644 lib/client.py create mode 100644 lib/context.py create mode 100644 lib/events.py create mode 100644 lib/room.py diff --git a/lib/http.py b/lib/api.py similarity index 80% rename from lib/http.py rename to lib/api.py index b86ab74..0694d23 100644 --- a/lib/http.py +++ b/lib/api.py @@ -12,7 +12,7 @@ MATRIX_MEDIA = "/_matrix/media/r0" @dataclass -class HTTPConfig: +class APIConfig: max_retry: int = 10 max_wait_time: int = 3600 backoff_factor: float = 0.1 @@ -20,20 +20,20 @@ class HTTPConfig: proxy: str = None -class HTTP: +class API: def __init__( self, *, base_url: str, - username: str, + user_id: str, password: str = None, token: str = None, device_id: str = None, device_name: str = None, - config: HTTPConfig = HTTPConfig(), + config: APIConfig = APIConfig(), ): self.base_url = base_url - self.username = username + self.user_id = user_id self.password = password self.token = token self.device_id = device_id @@ -120,14 +120,16 @@ class HTTP: path = self.build_url("login") data = {} - if self.password: + if self.password and self.user_id: data = { "type": "m.login.password", - "identifier": {"user": self.username, "type": "m.id.user"}, + "identifier": {"user": self.user_id, "type": "m.id.user"}, "password": self.password, } elif self.token: data = {"type": "m.login.token", "token": self.token} + else: + raise RuntimeError("No valid login types configured") if self.device_id: data["device_id"] = self.device_id if self.device_name: @@ -137,6 +139,8 @@ class HTTP: resp = await self._send("post", path, data=data, headers=headers) self.access_token = resp.get("access_token") self.device_id = resp.get("device_id") + if not self.user_id: + self.user_id = resp.get("user_id") return resp async def logout(self): @@ -165,3 +169,34 @@ class HTTP: raise RuntimeWarning(f"{room_id} is not a valid room id or alias") return await self.send("PUT", path, data=content) + + async def get_joined_rooms(self): + path = self.build_url("joined_rooms") + resp = await self.send("GET", path) + if resp.get("joined_rooms"): + return resp["joined_rooms"] + else: + return [] + + async def get_sync( + self, + query_filter: str = None, + since: str = None, + full_state: bool = False, + set_presence: str = "online", + timeout: int = 10000, + ): + query = { + "full_state": full_state, + "set_presence": set_presence, + "timeout": timeout, + } + if query_filter: + query["filter"] = query_filter + if since: + query["since"] = since + + path = self.build_url("sync", query=query) + resp = await self.send("GET", path) + + return resp diff --git a/lib/client.py b/lib/client.py new file mode 100644 index 0000000..1b6a408 --- /dev/null +++ b/lib/client.py @@ -0,0 +1,70 @@ +import asyncio +from typing import Union, Optional + +from .api import API, APIConfig + + +class Client: + def __init__( + self, + prefix: Union[str, list, tuple], + homeserver: str = "https://matrixcoding.chat", + ): + self.prefix = prefix + self.homeserver = homeserver + self.username: Optional[str] = None + self.password: Optional[str] = None + self.token: Optional[str] = None + self.rooms: list = [] + self.api: Optional[API] = None + self.running: bool = False + self.sync_timeout: int = 1000 + self.sync_since: Optional[str] = None + self.sync_full_state: bool = False + self.sync_set_presence: str = "online" + self.sync_filter: Optional[str] = None + self.sync_delay: Optional[str] = None + + async def run(self, user_id: str = None, password: str = None, token: str = None): + if not password and not token: + raise RuntimeError("Either the password or a token is required") + self.api = API( + base_url=self.homeserver, user_id=user_id, password=password, token=token + ) + resp = await self.api.login() + if resp.get("errcode"): + raise RuntimeError(resp) + self.running = True + while self.running: + if self.sync_delay: + await asyncio.sleep(self.sync_delay) + await self.sync() + + async def sync(self): + resp = await self.api.get_sync( + self.sync_filter, + self.sync_since, + self.sync_full_state, + self.sync_set_presence, + self.sync_timeout, + ) + if resp.get("errcode"): + self.running = False + raise RuntimeError(resp) + self.sync_since = resp["next_batch"] + for key, value in resp.iteritems(): + if key == "next_batch": + self.sync_since = value + else: + self.process_events(key, value) + + def process_events(self, event_type: str, event: dict): + if event_type == "rooms": + joined_room_events = event["join"] + invited_rooms = event["invite"] + left_rooms = event["leave"] + # TODO process events + + def process_timeline(self, room, timeline): + # TODO process the timeline + pass diff --git a/lib/context.py b/lib/context.py new file mode 100644 index 0000000..85636af --- /dev/null +++ b/lib/context.py @@ -0,0 +1 @@ +# TODO Create Context diff --git a/lib/events.py b/lib/events.py new file mode 100644 index 0000000..119d0cb --- /dev/null +++ b/lib/events.py @@ -0,0 +1 @@ +# TODO Add Event classes diff --git a/lib/room.py b/lib/room.py new file mode 100644 index 0000000..3c27e74 --- /dev/null +++ b/lib/room.py @@ -0,0 +1 @@ +# TODO Add Room class