Add Matrix bot Phase 1: core setup + fun commands
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>
2026-02-11 20:42:28 -05:00
|
|
|
import logging
|
|
|
|
|
from functools import wraps
|
|
|
|
|
|
Add Wordle, welcome system, integrations, and update roadmap
- Add Wordle game engine with daily puzzles, hard mode, stats, and share
- Add welcome module (react-to-join onboarding, Space join DMs)
- Add Ollama LLM integration (!ask), Minecraft RCON whitelist (!minecraft)
- Add !trivia, !champion, !agent, !health commands
- Add DM routing for Wordle (games in DMs, share to public room)
- Update README: reflect Phase 4 completion, hookshot webhook setup,
infrastructure migration (LXC 151/109 to large1), Spam and Stuff room,
all 12 webhook connections with UUIDs and transform notes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 10:29:36 -05:00
|
|
|
from nio import AsyncClient, RoomMessageText, UnknownEvent
|
Add Matrix bot Phase 1: core setup + fun commands
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>
2026-02-11 20:42:28 -05:00
|
|
|
|
|
|
|
|
from config import BOT_PREFIX, MATRIX_USER_ID
|
2026-02-11 20:52:57 -05:00
|
|
|
from commands import COMMANDS, metrics
|
Add Wordle, welcome system, integrations, and update roadmap
- Add Wordle game engine with daily puzzles, hard mode, stats, and share
- Add welcome module (react-to-join onboarding, Space join DMs)
- Add Ollama LLM integration (!ask), Minecraft RCON whitelist (!minecraft)
- Add !trivia, !champion, !agent, !health commands
- Add DM routing for Wordle (games in DMs, share to public room)
- Update README: reflect Phase 4 completion, hookshot webhook setup,
infrastructure migration (LXC 151/109 to large1), Spam and Stuff room,
all 12 webhook connections with UUIDs and transform notes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 10:29:36 -05:00
|
|
|
from welcome import handle_welcome_reaction, handle_space_join, SPACE_ROOM_ID
|
Add Matrix bot Phase 1: core setup + fun commands
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>
2026-02-11 20:42:28 -05:00
|
|
|
|
|
|
|
|
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)
|
2026-02-11 20:52:57 -05:00
|
|
|
metrics.record_error(func.__name__)
|
Add Matrix bot Phase 1: core setup + fun commands
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>
2026-02-11 20:42:28 -05:00
|
|
|
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
|
2026-02-11 20:52:57 -05:00
|
|
|
metrics.record_command(cmd_name)
|
Add Matrix bot Phase 1: core setup + fun commands
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>
2026-02-11 20:42:28 -05:00
|
|
|
wrapped = handle_command_errors(handler)
|
|
|
|
|
await wrapped(self.client, room.room_id, event.sender, args)
|
Add Wordle, welcome system, integrations, and update roadmap
- Add Wordle game engine with daily puzzles, hard mode, stats, and share
- Add welcome module (react-to-join onboarding, Space join DMs)
- Add Ollama LLM integration (!ask), Minecraft RCON whitelist (!minecraft)
- Add !trivia, !champion, !agent, !health commands
- Add DM routing for Wordle (games in DMs, share to public room)
- Update README: reflect Phase 4 completion, hookshot webhook setup,
infrastructure migration (LXC 151/109 to large1), Spam and Stuff room,
all 12 webhook connections with UUIDs and transform notes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 10:29:36 -05:00
|
|
|
|
|
|
|
|
async def reaction(self, room, event):
|
|
|
|
|
"""Handle m.reaction events (sent as UnknownEvent by matrix-nio)."""
|
|
|
|
|
# Ignore events from before startup
|
|
|
|
|
if self.startup_sync_token is None:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Ignore our own reactions
|
|
|
|
|
if event.sender == MATRIX_USER_ID:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# m.reaction events come as UnknownEvent with type "m.reaction"
|
|
|
|
|
if not hasattr(event, "source"):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
content = event.source.get("content", {})
|
|
|
|
|
relates_to = content.get("m.relates_to", {})
|
|
|
|
|
if relates_to.get("rel_type") != "m.annotation":
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
reacted_event_id = relates_to.get("event_id", "")
|
|
|
|
|
key = relates_to.get("key", "")
|
|
|
|
|
|
|
|
|
|
await handle_welcome_reaction(
|
|
|
|
|
self.client, room.room_id, event.sender, reacted_event_id, key
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
async def member(self, room, event):
|
|
|
|
|
"""Handle m.room.member events — watch for Space joins."""
|
|
|
|
|
# Ignore events from before startup
|
|
|
|
|
if self.startup_sync_token is None:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Only care about the Space
|
|
|
|
|
if room.room_id != SPACE_ROOM_ID:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Ignore our own membership changes
|
|
|
|
|
if event.state_key == MATRIX_USER_ID:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Only trigger on joins (not leaves, bans, etc.)
|
|
|
|
|
if event.membership != "join":
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Check if this is a new join (prev was not "join")
|
|
|
|
|
prev = event.prev_membership if hasattr(event, "prev_membership") else None
|
|
|
|
|
if prev == "join":
|
|
|
|
|
return # Already was a member, this is a profile update or similar
|
|
|
|
|
|
|
|
|
|
await handle_space_join(self.client, event.state_key)
|