From 0d662a03a11a40313644e02d3795a9c4f8b6abf7 Mon Sep 17 00:00:00 2001 From: Joao Figueiredo Date: Mon, 21 Apr 2025 23:55:23 +0100 Subject: [PATCH] first charge --- .env.example | 5 ++++ .gitignore | 1 + Dockerfile | 14 +++++++++ docker-compose.yml | 12 ++++++++ main.py | 75 ++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 2 ++ 6 files changed, 109 insertions(+) create mode 100644 .env.example create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 main.py create mode 100644 requirements.txt diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ca01beb --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +# LOG_LEVEL options: CRITICAL, ERROR, WARNING, INFO, DEBUG, NOTSET +HOMESERVER_URL = "https://matrix.org" +USER_ID = "@botbot_user:matrix.org" +PASSWORD = "botbot_password" +LOG_LEVEL=INFO \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0dbf2f2..63ec3ec 100644 --- a/.gitignore +++ b/.gitignore @@ -168,3 +168,4 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +.vscode/settings.json diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b276d97 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM python:3.11-slim + +# Set working directory +WORKDIR /app + +# Install dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy source code +COPY . . + +# Default command +CMD ["python", "main.py"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..4a8fdf8 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +services: + botbot: + build: . + env_file: + - .env + volumes: + - ./:/app # Mount source for hot-reload + - matrix_data:/app/data # Persist Matrix client store and tokens + restart: unless-stopped + +volumes: + matrix_data: \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..fd86fb4 --- /dev/null +++ b/main.py @@ -0,0 +1,75 @@ +import os +import asyncio +import logging +from dotenv import load_dotenv +from nio import AsyncClient, AsyncClientConfig, MatrixRoom, RoomMessageText +from nio.responses import LoginResponse + +# --- Load environment variables --- +load_dotenv() +HOMESERVER_URL = os.getenv("HOMESERVER_URL") +USER_ID = os.getenv("USER_ID") +PASSWORD = os.getenv("PASSWORD") +LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper() + +# --- Logging Setup --- +# Convert string level to numeric +numeric_level = getattr(logging, LOG_LEVEL, logging.INFO) +logging.basicConfig( + level=numeric_level, + format="%(asctime)s %(levelname)s %(name)s: %(message)s" +) +logger = logging.getLogger(__name__) + +async def message_callback(room: MatrixRoom, event: RoomMessageText): + """ + Called when a new text message is received. + """ + logger.debug("Message callback triggered") + if event.sender == USER_ID: + return + + logger.info("Message from %s in %s: %s", event.sender, room.display_name, event.body) + + if event.body.strip().lower() == "!ping": + await client.room_send( + room_id=room.room_id, + message_type="m.room.message", + content={"msgtype": "m.text", "body": "Pong!"} + ) + logger.info("Replied with Pong! to %s", event.sender) + +async def main(): + global client + logger.debug(HOMESERVER_URL) + logger.debug(USER_ID) + # Configure client with persistent store + config = AsyncClientConfig(store_sync_tokens=True) + client = AsyncClient( + HOMESERVER_URL, + USER_ID, + store_path="/app/data", + config=config + ) + + # Login + resp = await client.login(password=PASSWORD) + logger.debug("Login response raw: %r", resp.__dict__) + if not isinstance(resp, LoginResponse) or not resp.access_token: + error_msg = getattr(resp, 'message', repr(resp)) + logger.error("Login failed: %s", error_msg) + await client.close() + return + logger.info("Logged in successfully as %s", USER_ID) + + # Register callback and start syncing + client.add_event_callback(message_callback, RoomMessageText) + logger.info("Starting sync loop with timeout=30000ms") + await client.sync_forever(timeout=30000) + +if __name__ == "__main__": + try: + asyncio.run(main()) + except KeyboardInterrupt: + logger.info("Received interrupt, shutting down client") + asyncio.run(client.close()) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e7d6f44 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +matrix-nio[aio,http]>=0.25.0 +python-dotenv>=1.0.0 \ No newline at end of file