From 0f935477eb224760cd0e1fe2b0f119f6fbb519c6 Mon Sep 17 00:00:00 2001 From: driftywinds Date: Wed, 14 May 2025 05:51:03 +0000 Subject: [PATCH] added matrix version of the bot --- matrix/.env | 6 ++ matrix/MTbot.py | 174 ++++++++++++++++++++++++++++++++++++++++ matrix/requirements.txt | 3 + 3 files changed, 183 insertions(+) create mode 100644 matrix/.env create mode 100644 matrix/MTbot.py create mode 100644 matrix/requirements.txt diff --git a/matrix/.env b/matrix/.env new file mode 100644 index 0000000..0e7379c --- /dev/null +++ b/matrix/.env @@ -0,0 +1,6 @@ +API_BASE_URL= +BEARER_TOKEN= +MATRIX_HOMESERVER=https://matrix.example.com +MATRIX_USER=@marketplacebaybot:matrix.example.com +MATRIX_PASSWORD=MyBotPassowrd +ALLOWED_ROOMS=#marketplacebay:matrix.example.com,#anotherroom:matrix.example.com,!sjahdfg2343646511:matrix.example.com # room IDs starting with ! are the internal IDs of the room diff --git a/matrix/MTbot.py b/matrix/MTbot.py new file mode 100644 index 0000000..7dc71bc --- /dev/null +++ b/matrix/MTbot.py @@ -0,0 +1,174 @@ +import os +import requests +import base64 +import re +import json +import asyncio +from datetime import datetime +from nio import AsyncClient, MatrixRoom, RoomMessageText, RoomMessageFormatted +from dotenv import load_dotenv + +# MATRIX DIRECTUS BOT + +load_dotenv() + +# Configuration +API_BASE_URL = os.getenv("API_BASE_URL") # Configure in .env +BEARER_TOKEN = os.getenv("BEARER_TOKEN") # Configure in .env +MATRIX_HOMESERVER = os.getenv("MATRIX_HOMESERVER") # Configure in .env +MATRIX_USER = os.getenv("MATRIX_USER") # Configure in .env +MATRIX_PASSWORD = os.getenv("MATRIX_PASSWORD") # Configure in .env +BOT_USER_ID = os.getenv("MATRIX_USER") # Use the bot's Matrix ID to ignore its own messages +ALLOWED_ROOMS = os.getenv("ALLOWED_ROOMS", "").split(",") # Comma-separated list of allowed room IDs + +# UUID validation pattern +UUID_PATTERN = re.compile(r'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$') + +# In-memory search tracking +active_searches = {} + +# Track bot startup time +BOT_START_TIME = datetime.now() + +async def log_activity(message: str) -> None: + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + print(f"[{timestamp}] {message}") + +async def send_response(client: AsyncClient, room_id: str, message: str) -> None: + await client.room_send( + room_id=room_id, + message_type="m.room.message", + content={ + "msgtype": "m.text", + "body": message + } + ) + await log_activity(f"Sent response: '{message}'") + +async def process_message(client: AsyncClient, room: MatrixRoom, event: RoomMessageText) -> None: + # Ignore messages from the bot itself + if event.sender == BOT_USER_ID: + return + + room_id = room.room_id + # Check if the room is allowed + if room_id not in ALLOWED_ROOMS: + await log_activity(f"Ignored message from unauthorized room: {room_id}") + return + + # Ignore messages sent before the bot started + if event.server_timestamp / 1000 < BOT_START_TIME.timestamp(): + await log_activity(f"Ignored old message from {room_id}") + return + + user_message = event.body.strip().lower() + sender = event.sender + await log_activity(f"Received message from {sender} in {room_id}: '{user_message}'") + + # Handle cancellation + if user_message in ['cancel', 'abort']: + active_searches.pop(room_id, None) + await send_response(client, room_id, "❌ Operation cancelled. You can start a new search.") + return + + # Check if room has an active search + if room_id in active_searches: + try: + index = int(user_message) - 1 + search_results = active_searches[room_id] + if 0 <= index < len(search_results): + selected_uuid = search_results[index]['uuid'] + del active_searches[room_id] # Clear active search after selection + await process_uuid(client, room_id, selected_uuid) + return + else: + await send_response(client, room_id, "❌ Invalid index. Please enter a valid number or type 'cancel' to abort.") + return + except ValueError: + await send_response(client, room_id, "❌ Please enter a valid number or type 'cancel' to abort.") + return + + # Check if input is UUID + if UUID_PATTERN.match(user_message): + await process_uuid(client, room_id, user_message) + else: + await perform_search(client, room_id, user_message) + +async def perform_search(client: AsyncClient, room_id: str, query: str) -> None: + try: + headers = {"Authorization": f"Bearer {BEARER_TOKEN}"} + search_params = { + "filter": json.dumps({ + "_or": [ + {"name": {"_icontains": query}}, + {"creator": {"_icontains": query}} + ] + }), + "fields": "uuid,name,creator,version,download_hash", + "limit": "10" + } + + response = requests.get(url=f"{API_BASE_URL}", headers=headers, params=search_params) + response.raise_for_status() + + data = response.json().get('data', []) + if not data: + await send_response(client, room_id, "🔍 No results found for your query.") + return + + active_searches[room_id] = data # Track active search for the room + results_list = [f"{idx + 1}. {item.get('name', 'N/A')} by {item.get('creator', 'N/A')}" for idx, item in enumerate(data)] + response_message = "🔍 Search Results:\n" + "\n".join(results_list) + "\n\nReply with the number to view details, or type 'cancel' to abort." + await send_response(client, room_id, response_message) + except Exception as e: + await log_activity(f"Search error: {str(e)}") + await send_response(client, room_id, "⚠️ Error processing search") + +async def process_uuid(client: AsyncClient, room_id: str, uuid: str) -> None: + try: + headers = {"Authorization": f"Bearer {BEARER_TOKEN}"} + response = requests.get(f"{API_BASE_URL}{uuid}", headers=headers) + response.raise_for_status() + + data = response.json().get('data', {}) + if not data: + await send_response(client, room_id, "❌ No information found for this UUID.") + return + + download_hashes = data.get('download_hash', '') + decoded_urls = [] + + if download_hashes: + for i, hash_part in enumerate(download_hashes.split(','), 1): + try: + decoded = base64.b64decode(hash_part.strip()).decode('utf-8') + decoded_urls.append(f"{i}. {decoded}") + except: + decoded_urls.append(f"{i}. Invalid hash") + + formatted_message = ( + f"UUID: {data.get('uuid', 'N/A')}\n" + f"Name: {data.get('name', 'N/A')}\n" + f"Creator: {data.get('creator', 'N/A')}\n" + f"Version: {data.get('version', 'N/A')}\n" + ) + + if decoded_urls: + formatted_message += "Download URLs:\n" + "\n".join(decoded_urls) + else: + formatted_message += "No download URLs found" + + await send_response(client, room_id, f"📦 Addon Information:\n{formatted_message}") + except Exception as e: + await log_activity(f"UUID processing error: {str(e)}") + await send_response(client, room_id, "⚠️ Error fetching UUID details") + +async def main() -> None: + client = AsyncClient(MATRIX_HOMESERVER, MATRIX_USER) + await client.login(MATRIX_PASSWORD) + await log_activity(f"Logged in as {MATRIX_USER}") + client.add_event_callback(lambda room, event: process_message(client, room, event), RoomMessageText) + await client.sync_forever(timeout=30000) + +if __name__ == '__main__': + asyncio.run(main()) diff --git a/matrix/requirements.txt b/matrix/requirements.txt new file mode 100644 index 0000000..81ea76e --- /dev/null +++ b/matrix/requirements.txt @@ -0,0 +1,3 @@ +matrix-nio +python-dotenv +requests