171 lines
6.3 KiB
Python
171 lines
6.3 KiB
Python
"""
|
||
Plugin for welcoming new users to the room.
|
||
|
||
Features:
|
||
* Automatically greets users when they first join the target room.
|
||
* !welcome command – manually trigger the welcome message for yourself.
|
||
* Per-user cooldown to prevent welcome spam on repeated part/join cycles.
|
||
|
||
Restricted to room: !NXdVjDXPxXowPkrJJY:matrix.org
|
||
"""
|
||
|
||
import logging
|
||
import time
|
||
import simplematrixbotlib as botlib
|
||
import nio
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Configuration
|
||
# ---------------------------------------------------------------------------
|
||
|
||
ALLOWED_ROOM_ID = "!NXdVjDXPxXowPkrJJY:matrix.org"
|
||
|
||
# How many seconds must pass before a user can receive another auto-welcome.
|
||
# Default: 10 minutes. Raise this if you still see abuse.
|
||
WELCOME_COOLDOWN_SECONDS = 600
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Cooldown state
|
||
# Maps Matrix user ID → monotonic timestamp of last auto-welcome sent.
|
||
# Resets on bot restart, which is fine — a fresh start is a clean slate.
|
||
# ---------------------------------------------------------------------------
|
||
|
||
_last_welcomed: dict[str, float] = {}
|
||
|
||
|
||
def _is_on_cooldown(user_id: str) -> bool:
|
||
"""Return True if the user was welcomed recently and should be skipped."""
|
||
last = _last_welcomed.get(user_id)
|
||
if last is None:
|
||
return False
|
||
return (time.monotonic() - last) < WELCOME_COOLDOWN_SECONDS
|
||
|
||
|
||
def _record_welcome(user_id: str) -> None:
|
||
"""Mark that this user was just welcomed."""
|
||
_last_welcomed[user_id] = time.monotonic()
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Internal helpers
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def _build_welcome_message(display_name: str) -> str:
|
||
"""Return the Markdown welcome message for a given display name."""
|
||
return f"Hey **{display_name}**, welcome in — so glad to have you here! 🎉 Feel free to tell us a bit about what you're into (self‑hosting, security, homelabs, programming… or just lurk and say hi whenever 🍄)"
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Plugin setup - called by funguy.py's run() after the bot object is created
|
||
# ---------------------------------------------------------------------------
|
||
|
||
def setup(bot):
|
||
"""
|
||
Register the member-join listener.
|
||
|
||
funguy.py must call plugin_module.setup(self.bot) for each loaded plugin
|
||
(after self.bot is created) for this listener to fire.
|
||
"""
|
||
|
||
@bot.listener.on_custom_event(nio.RoomMemberEvent)
|
||
async def _on_member_event(room, event):
|
||
logging.debug(
|
||
"RoomMemberEvent received: room=%s, sender=%s, membership=%s",
|
||
room.room_id, event.sender, event.membership,
|
||
)
|
||
|
||
# Only the configured room
|
||
if room.room_id != ALLOWED_ROOM_ID:
|
||
return
|
||
|
||
# Only on actual join (not invite / leave / ban)
|
||
if event.membership != "join":
|
||
return
|
||
|
||
# Ignore the bot itself
|
||
if event.sender == bot.async_client.user_id:
|
||
logging.debug("Ignoring bot's own join event")
|
||
return
|
||
|
||
# Check if this is a genuine first join by looking for previous membership
|
||
is_first_join = True
|
||
|
||
# Check prev_content directly on the event
|
||
if hasattr(event, 'prev_content') and event.prev_content:
|
||
prev_membership = event.prev_content.get('membership')
|
||
if prev_membership is not None:
|
||
is_first_join = False
|
||
logging.debug(f"User {event.sender} had previous membership '{prev_membership}' in prev_content")
|
||
|
||
# Check unsigned field (some events store prev_content there)
|
||
if is_first_join and hasattr(event, 'unsigned') and event.unsigned:
|
||
prev_content = event.unsigned.get('prev_content', {})
|
||
prev_membership = prev_content.get('membership')
|
||
if prev_membership is not None:
|
||
is_first_join = False
|
||
logging.debug(f"User {event.sender} had previous membership '{prev_membership}' in unsigned")
|
||
|
||
if not is_first_join:
|
||
logging.info(f"Skipping welcome for {event.sender} - not a first join (name change or rejoin)")
|
||
return
|
||
|
||
# --- Cooldown check ---
|
||
if _is_on_cooldown(event.sender):
|
||
remaining = WELCOME_COOLDOWN_SECONDS - (
|
||
time.monotonic() - _last_welcomed[event.sender]
|
||
)
|
||
logging.info(
|
||
"Skipping welcome for %s – cooldown active (%.0fs remaining)",
|
||
event.sender, remaining,
|
||
)
|
||
return
|
||
|
||
# Derive a friendly display name from the Matrix ID (@name:server → name)
|
||
display_name = event.sender.split(":")[0].lstrip("@")
|
||
|
||
logging.info(
|
||
"User %s joined room %s – sending welcome", event.sender, room.room_id
|
||
)
|
||
try:
|
||
await bot.api.send_markdown_message(
|
||
room.room_id, _build_welcome_message(display_name)
|
||
)
|
||
_record_welcome(event.sender) # Only recorded after a successful send
|
||
logging.info("Welcome message sent successfully to %s", display_name)
|
||
except Exception as exc:
|
||
logging.error("Failed to send welcome message: %s", exc)
|
||
|
||
logging.info("Join listener registered for room %s", ALLOWED_ROOM_ID)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Plugin entry point - called by FunguyBot for every message event
|
||
# ---------------------------------------------------------------------------
|
||
|
||
async def handle_command(room, message, bot, prefix, config):
|
||
"""Handle the !welcome command (not subject to the auto-welcome cooldown)."""
|
||
|
||
match = botlib.MessageMatch(room, message, bot, prefix)
|
||
|
||
if not (
|
||
match.is_not_from_this_bot()
|
||
and match.prefix()
|
||
and match.command("welcome")
|
||
):
|
||
return
|
||
|
||
if room.room_id != ALLOWED_ROOM_ID:
|
||
await bot.api.send_text_message(
|
||
room.room_id,
|
||
"The !welcome command only works in the designated room.",
|
||
)
|
||
return
|
||
|
||
sender = str(message.sender)
|
||
display_name = sender.split(":")[0].lstrip("@")
|
||
|
||
await bot.api.send_markdown_message(
|
||
room.room_id, _build_welcome_message(display_name)
|
||
)
|
||
logging.info("Sent manual !welcome to %s", sender)
|