feat: management polish, !cancel, !wordlestats, welcome fixes
Lint / Shell (shellcheck) (push) Successful in 11s
Lint / JS (eslint) (push) Successful in 7s
Lint / Python (ruff) (push) Failing after 5s
Lint / Python deps (pip-audit) (push) Successful in 41s
Lint / Secret scan (gitleaks) (push) Successful in 5s

- Add !cancel command (anyone cancels own blackjack; PL50+ clears all room games)
- Add !wordlestats top-level command (wraps wordle stats function)
- Add !cleanwelcome admin command to purge stale welcome DM records
- !help now hides management section from sub-PL50 users, hides !health from non-admins
- !announce uses nio room cache for join_rule instead of an API call per room
- Fix _INVITEALL_BLOCKED comment (Commands is knock-gated, not restricted)
- welcome.py: skip duplicate DM if a pending welcome already exists for the user
- welcome.py: add clean_stale_dm_messages() helper
- welcome.py: replace no-op post_welcome_message with log_ready()
- bot.py: update import/call to match welcome.py rename

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-29 14:50:14 -04:00
parent 4ef73afed2
commit 88627470c1
3 changed files with 433 additions and 21 deletions
+30 -10
View File
@@ -19,11 +19,15 @@ logger = logging.getLogger("matrixbot")
# The Space room to watch for new members
SPACE_ROOM_ID = "!-1ZBnAH-JiCOV8MGSKN77zDGTuI3pgSdy8Unu_DrDyc"
# Public channels to invite new members to (skip Management + Cool Kids)
# Public channels to invite new members to.
# Intentionally excludes: Management, Cool Kids, Spam and Stuff (invite-only),
# and Commands (knock-gated — access granted deliberately by admins).
INVITE_ROOMS = [
"!wfokQ1-pE896scu_AOcCBA2s3L4qFo-PTBAFTd0WMI0", # General (v12)
"!ou56mVZQ8ZB7AhDYPmBV5_BR28WMZ4x5zwZkPCqjq1s", # Commands (v12)
"!GK6v5cLEEnowIooQJv5jECfISUjADjt8aKhWv9VbG5U", # Memes (v12)
"!wfokQ1-pE896scu_AOcCBA2s3L4qFo-PTBAFTd0WMI0", # General
"!GK6v5cLEEnowIooQJv5jECfISUjADjt8aKhWv9VbG5U", # Memes
"!ktQu0gavhjpCMkgxk8SYdb6mnJRY-u7mY7_KfksV0SU", # Music
"!ARbRFSPNp2U0MslWTBGoTT3gbmJJ25dPRL6enQntvPo", # Voice
"!3gMjTHqV-r823ZrvXnck7waB0Pd8tiCu-zbF7mSS83E", # Voice 2
]
WELCOME_EMOJI = "\u2705" # checkmark
@@ -49,6 +53,17 @@ def _save_state(state: dict):
logger.error("Failed to save welcome state: %s", e)
def clean_stale_dm_messages() -> int:
"""Remove all pending welcome DM records. Returns count removed."""
state = _load_state()
pending = state.get("dm_welcome_messages", {})
count = len(pending)
if count:
state["dm_welcome_messages"] = {}
_save_state(state)
return count
async def handle_space_join(client: AsyncClient, sender: str):
"""Called when a new user joins the Space. DM them a welcome message."""
state = _load_state()
@@ -57,6 +72,12 @@ async def handle_space_join(client: AsyncClient, sender: str):
if sender in welcomed:
return
# Skip if we already sent them a DM they haven't reacted to yet
pending = state.get("dm_welcome_messages", {})
if any(v["user"] == sender for v in pending.values()):
logger.debug("Already have a pending welcome DM for %s, skipping", sender)
return
logger.info("New Space member %s — sending welcome DM", sender)
dm_room = await get_or_create_dm(client, sender)
@@ -66,13 +87,13 @@ async def handle_space_join(client: AsyncClient, sender: str):
plain = (
"Welcome to The Lotus Guild!\n\n"
f"React to this message with {WELCOME_EMOJI} to get invited to all channels.\n\n"
"You'll be added to General, Commands, and Memes."
f"React to this message with {WELCOME_EMOJI} to get invited to all public channels.\n\n"
"You'll be added to General, Memes, Music, and the Voice channels."
)
html = (
"<h3>Welcome to The Lotus Guild!</h3>"
f"<p>React to this message with {WELCOME_EMOJI} to get invited to all channels.</p>"
"<p>You'll be added to <b>General</b>, <b>Commands</b>, and <b>Memes</b>.</p>"
f"<p>React to this message with {WELCOME_EMOJI} to get invited to all public channels.</p>"
"<p>You'll be added to <b>General</b>, <b>Memes</b>, <b>Music</b>, and the <b>Voice</b> channels.</p>"
)
resp = await send_html(client, dm_room, plain, html)
@@ -145,6 +166,5 @@ async def handle_welcome_reaction(
await send_text(client, room_id, "You're already in all the channels!")
async def post_welcome_message(client: AsyncClient):
"""No-op kept for backward compatibility with bot.py startup."""
def log_ready():
logger.info("Welcome module ready — watching Space for new members")