Files
FunguyBot/plugins/welcome.py
T

185 lines
6.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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 (selfhosting, 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)
# ---------------------------------------------------------------------------
# Plugin Metadata
# ---------------------------------------------------------------------------
__version__ = "1.0.0"
__author__ = "Funguy Bot"
__description__ = "Room welcome message"
__help__ = """
<details>
<summary><strong>!welcome</strong> Receive welcome message</summary>
<p>Manually triggers the room's welcome message for yourself.</p>
</details>
"""