Modular bot using matrix-nio[e2e] with E2EE support, deployed as systemd service on Synapse LXC. Includes 10 commands: help, ping, 8ball, fortune, flip, roll, random, rps, poll, champion. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
61 lines
1.9 KiB
Python
61 lines
1.9 KiB
Python
import logging
|
|
from functools import wraps
|
|
|
|
from nio import AsyncClient, RoomMessageText
|
|
|
|
from config import BOT_PREFIX, MATRIX_USER_ID
|
|
from commands import COMMANDS
|
|
|
|
logger = logging.getLogger("matrixbot")
|
|
|
|
|
|
def handle_command_errors(func):
|
|
@wraps(func)
|
|
async def wrapper(client, room_id, sender, args):
|
|
try:
|
|
return await func(client, room_id, sender, args)
|
|
except Exception as e:
|
|
logger.error(f"Error in command {func.__name__}: {e}", exc_info=True)
|
|
try:
|
|
from utils import send_text
|
|
await send_text(client, room_id, "An unexpected error occurred. Please try again later.")
|
|
except Exception as e2:
|
|
logger.error(f"Failed to send error message: {e2}", exc_info=True)
|
|
return wrapper
|
|
|
|
|
|
class Callbacks:
|
|
def __init__(self, client: AsyncClient):
|
|
self.client = client
|
|
# Track the sync token so we ignore old messages on startup
|
|
self.startup_sync_token = None
|
|
|
|
async def message(self, room, event):
|
|
# Ignore messages from before the bot started
|
|
if self.startup_sync_token is None:
|
|
return
|
|
|
|
# Ignore our own messages
|
|
if event.sender == MATRIX_USER_ID:
|
|
return
|
|
|
|
body = event.body.strip() if event.body else ""
|
|
if not body.startswith(BOT_PREFIX):
|
|
return
|
|
|
|
# Parse command and args
|
|
without_prefix = body[len(BOT_PREFIX):]
|
|
parts = without_prefix.split(None, 1)
|
|
cmd_name = parts[0].lower() if parts else ""
|
|
args = parts[1] if len(parts) > 1 else ""
|
|
|
|
logger.info(f"Command '{cmd_name}' from {event.sender} in {room.room_id}")
|
|
|
|
handler_entry = COMMANDS.get(cmd_name)
|
|
if handler_entry is None:
|
|
return
|
|
|
|
handler, _ = handler_entry
|
|
wrapped = handle_command_errors(handler)
|
|
await wrapped(self.client, room.room_id, event.sender, args)
|