Joining the Lotus Guild Space (join_rule: public) triggered a welcome DM;
reacting to it made the bot invite that user into a fixed room list. Now
that #general is published to the public room directory, any stranger can
join the Space and trigger this.
The room list excluded the invite-only rooms, but it included Voice, whose
join rule is `knock`. An invite bypasses a knock gate, so a stranger could
skip the approval step that rule exists to enforce.
Removes welcome.py, the Space-join watcher in Callbacks.member, both
welcome-reaction hooks, and the admin `cleanwelcome` command. The bot no
longer issues invites automatically anywhere. The PL50+ `invite` and
`inviteall` commands are untouched: those are deliberate admin actions.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#general is now published to the public room directory, so the bot is
reachable by strangers. Two hardening changes:
- Invites are only accepted from INVITE_ALLOWED_USERS (defaults to
ADMIN_USERS). Anything else is declined via room_leave so hostile
invites do not accumulate as pending. Previously any invite from
anyone was auto-joined.
- Commands and the passive scramble/riddle answer checks only run in
COMMAND_ROOMS (#commands and #management). The gate sits early in
Callbacks.message, so the bot stays silent everywhere else, including
#general. Set COMMAND_ROOMS="*" to restore the old behaviour.
Reaction handlers are deliberately left ungated so the welcome flow
keeps working; game votes elsewhere cannot match an active game.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- 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>
nio has a dedicated ReactionEvent type with .reacts_to and .key attributes.
The callback was registered for UnknownEvent so reaction events were silently
dropped. Register for ReactionEvent and use its native attributes; keep the
UnknownEvent fallback for edge cases.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove unused imports: logging from bot.py and config.py, RoomMessageText/
UnknownEvent from callbacks.py, functools.partial and MAX_INPUT_LENGTH from
commands.py. Rename unused local variables to _ (resp in cmd_ping, symbols in
render_keyboard_plain, guesses_left in two wordle functions). Move wordle import
to top of commands.py to fix E402.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All bot source files from LXC 151 (/opt/matrixbot) are now tracked here.
Secrets (.env, credentials.json), venv dirs, and runtime state files
(nio_store, welcome_state.json, wordle_stats.json) are excluded via .gitignore.
Includes deploy.sh to sync files to /opt/matrixbot and restart the service.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>