diff --git a/.gitignore b/.gitignore index f8b73e7..29dba5a 100644 --- a/.gitignore +++ b/.gitignore @@ -137,4 +137,3 @@ dmypy.json # Cython debug symbols cython_debug/ - diff --git a/setup.cfg b/setup.cfg index bbe0b4e..0baf1c8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,7 +8,7 @@ url = https://gitea.auro.re/histausse/matrix-bot [options] packages = matrix_bot -python_requires = >=3.6 +python_requires = >=3.9.2 package_dir = =src install_requires = matrix-nio>=0.18.7 diff --git a/src/matrix_bot/client.py b/src/matrix_bot/client.py index 6e8afe8..7fdd26d 100644 --- a/src/matrix_bot/client.py +++ b/src/matrix_bot/client.py @@ -3,10 +3,85 @@ The Client class. Connect to the matrix server and handle interactions with the server. """ +import asyncio +import nio +from typing import ( + Optional, + NoReturn, + Union +) +from .utils import ( + Room, + RoomAlias, + RoomId +) class Client: """ Connect to the matrix server and handle interactions with the server. + + allowed_rooms: dict of the rooms where the bot is allowed to connect, indexed + by id (the name starting with '!'). If set to None, the bot connect to + all room where it is invited. """ - pass + + __client: nio.AsyncClient + allowed_rooms: Optional[dict[RoomId, Room]] + + def __init__( + self, + username: str, + homeserver: str, + password: str, + allowed_rooms_names: Optional[list[Union[RoomAlias, RoomId]]]=None + ): + """ + Initialize the Client. + username: the username used by the bot + homeserver: the matrix home server of the bot (expl: "https://matrix.org") + password: the password of the user + allowed_rooms: the list of the rooms where the bot is allowed to connect + (given by room id (expl: '!xxx:matrix.org') of room alias (expl: + '#xxx:matrix.org')) + """ + loop = asyncio.get_event_loop() + self.__client = nio.AsyncClient( + homeserver, + username + ) + resp = loop.run_until_complete(self.__client.login(password)) + if isinstance(resp, nio.responses.LoginError): + raise RuntimeError(f"Fail to connect: {resp.message}") + + # Where is the async map when you need it? + self.allowed_rooms = None + if allowed_rooms_names: + self.allowed_rooms = {} + for room_name in allowed_rooms_names: + room = loop.run_until_complete(self.resolve_room(room_name)) + if room.id in self.allowed_rooms: + self.allowed_rooms[room.id].aliases.update(room.aliases) + else: + self.allowed_rooms[room.id] = room + + + async def resolve_room( + self, + room_name: Union[RoomAlias, RoomId] + )->Room: + """ + Lookup a room from its id or alias. + """ + if len(room_name) == 0: + raise ValueError(f"Invalid room_name: {room_name}") + if room_name[0] == '!': + return Room(id=room_name) + elif room_name[0] != '#': + raise ValueError(f"Invalid room_name: {room_name}") + else: + resp = await self.__client.room_resolve_alias(room_name) + if isinstance(resp, nio.responses.RoomResolveAliasError): + raise RuntimeError(f"Error while resolving alias: {resp.message}") + return Room(id=resp.room_id,aliases={room_name}) + diff --git a/src/matrix_bot/utils.py b/src/matrix_bot/utils.py new file mode 100644 index 0000000..15e7a4e --- /dev/null +++ b/src/matrix_bot/utils.py @@ -0,0 +1,19 @@ +""" +Utilities for the bot. +""" + +from dataclasses import ( + dataclass, + field +) + +RoomAlias = str +RoomId = str + +@dataclass +class Room: + """ Class representing a room. + """ + id: RoomId + aliases: set[RoomAlias] = field(default_factory=set) + diff --git a/tests/test.py b/tests/test.py new file mode 100644 index 0000000..7222d4c --- /dev/null +++ b/tests/test.py @@ -0,0 +1,19 @@ +""" +Not really tests, because I don't know how to testt without a whole matrix server. +""" + +import os +from dotenv import load_dotenv +from matrix_bot.client import Client +from getpass import getpass + +if __name__ == "__main__": + load_dotenv() + client = Client( + os.environ["MUSER"], + os.environ["HOMESERVER"], + os.environ["PASSWD"], + os.environ["ROOMS"].split(",") + ) + print(client.allowed_rooms) +