From 520fe9c9dd7baf24ca45a13bab6ec823209a759e Mon Sep 17 00:00:00 2001 From: frarol96 Date: Sun, 10 Aug 2025 15:11:15 +0000 Subject: [PATCH] Delete modules/nick_nudge/nick_nudge.py --- modules/nick_nudge/nick_nudge.py | 331 ------------------------------- 1 file changed, 331 deletions(-) delete mode 100644 modules/nick_nudge/nick_nudge.py diff --git a/modules/nick_nudge/nick_nudge.py b/modules/nick_nudge/nick_nudge.py deleted file mode 100644 index 807771a..0000000 --- a/modules/nick_nudge/nick_nudge.py +++ /dev/null @@ -1,331 +0,0 @@ -import asyncio -import time -from typing import Optional, Tuple -import discord -from discord.ext import commands -from discord import app_commands - -from mod_perms import is_moderator_userid -from modules.common.emoji_accept import is_accept - -CHECK = '✅' # approved/verified -CROSS = '❌' # reject / no -PENDING = '✔️' # heavy check mark = pending claim -ACCEPT = {CHECK, '🫡'} -NO_MENTIONS = discord.AllowedMentions.none() - -def _ts_rel(ts: Optional[float] = None) -> str: - """Discord relative timestamp like .""" - if ts is None: - ts = time.time() - return f"" - -class NickNudgeCog(commands.Cog): - """ - Handles: - • DM nickname nudge loop (optional; unchanged behavior) - • Nickname *review* workflow for claims: - - On claim (via reaction or /nick_same): create a mod review in mod_channel with ✅/❌ - - Mods react: ✅ -> mark verified; ❌ -> clear claim and revoke Full Access - • Stores review mapping in data_manager['nick_reviews'] - """ - - def __init__(self, bot): - self.bot = bot - cfg = bot.config['DEFAULT'] - self.modlog_channel_id = int(cfg['modlog_channel_id']) - self.mod_channel_id = int(cfg['mod_channel_id']) # same review channel as pirate reports - # Optional DM nudge loop retained - try: - self.loop_enabled = cfg.getboolean('nick_nudge_loop_enabled') - except Exception: - self.loop_enabled = False - self._task = asyncio.create_task(self._nudge_loop()) if self.loop_enabled else None - - # ---------- utils ---------- - - def cog_unload(self): - try: - if self._task: - self._task.cancel() - except Exception: - pass - - async def _modlog(self, guild: discord.Guild, content: str): - ch = guild.get_channel(self.modlog_channel_id) - if ch: - try: - await ch.send(content, allowed_mentions=NO_MENTIONS) - except Exception: - pass - self.bot.data_manager.add('modlog', {'ts': time.time(), 'guild_id': guild.id, 'content': content}) - - async def _find_last_nick_change(self, guild: discord.Guild, member: discord.Member) -> Tuple[Optional[str], Optional[str]]: - """ - Best-effort: look up last nickname change via audit logs. - Returns (before_nick, after_nick) or (None, None) if not found/allowed. - """ - try: - async for entry in guild.audit_logs(limit=10, action=discord.AuditLogAction.member_update): - if entry.target.id != member.id or not entry.changes: - continue - before_n = getattr(entry.changes.before, 'get', lambda *_: None)('nick') - after_n = getattr(entry.changes.after, 'get', lambda *_: None)('nick') - if before_n is not None or after_n is not None: - return before_n, after_n - except Exception: - pass - return None, None - - # ---------- public API (called by ReactionRole cog) ---------- - - async def start_nick_review(self, guild: discord.Guild, member: discord.Member, source: str = "claim"): - """ - Create (or update) a nickname review entry for this member in the mod channel. - - source: "claim" or "nick_same" - Stores in data_manager['nick_reviews'] a record keyed by the review message_id. - """ - if not guild: - return - mod_ch = guild.get_channel(self.mod_channel_id) - if not mod_ch: - return - - before_n, after_n = await self._find_last_nick_change(guild, member) - now_ts = int(time.time()) - - # Compose review text - title = "📝 **Nickname Verification Request**" - who = f"User: {member.mention} (`{member.id}`)" - change = f"Claimed {_ts_rel(now_ts)}" - from_to = f"From: {repr(before_n) if before_n is not None else 'unknown'} → To: {repr(member.nick) if member.nick else 'None'}" - method = f"Method: {'/nick_same' if source == 'nick_same' else 'reaction'}" - instructions = "Moderators: react ✅ to **approve** or ❌ to **reject**." - content = f"{title}\n{who}\n{from_to}\n{method}\n{change}\n\n{instructions}" - - try: - msg = await mod_ch.send(content, allowed_mentions=NO_MENTIONS) - await msg.add_reaction(CHECK) - await msg.add_reaction(CROSS) - except Exception: - return - - # Persist review mapping - self.bot.data_manager.add('nick_reviews', { - 'message_id': int(msg.id), - 'guild_id': int(guild.id), - 'user_id': int(member.id), - 'before_nick': before_n if before_n is None or isinstance(before_n, str) else str(before_n), - 'claimed_nick': member.nick if member.nick else None, - 'status': 'pending', - 'source': source, - 'ts': now_ts - }) - - # Log to modlog - await self._modlog(guild, f"🔎 Nickname review opened for {member.mention} — {method} — {_ts_rel(now_ts)}.") - - # ---------- DM nudge loop (unchanged) ---------- - - async def _nudge_loop(self): - await self.bot.wait_until_ready() - while not self.bot.is_closed(): - try: - now = time.time() - for guild in self.bot.guilds: - for member in guild.members: - if member.bot or not member.joined_at: - continue - if (now - member.joined_at.timestamp()) < 24*3600: - continue - # If they already have a server nick OR already claimed/verified, skip nudging - dm = self.bot.data_manager - if (member.nick and member.nick.strip()): - continue - if member.id in dm.get('nick_verified') or member.id in dm.get('nick_claim_pending'): - continue - if member.id in dm.get('nick_nudged'): - continue - try: - dmchan = await member.create_dm() - msg = await dmchan.send( - "Hey! On this server we require your **server nickname** to match your in-game character name.\n\n" - "If your default Discord display name is already identical to your in-game name, react ✅ **or 🫡** below " - "or run `/nick_same`. Otherwise, please set your **server nickname** to your in-game name and react ✅ **or 🫡**." - ) - await msg.add_reaction(CHECK) - self.bot.data_manager.add('nick_dm_map', { - 'message_id': int(msg.id), - 'user_id': int(member.id), - 'guild_id': int(guild.id), - 'ts': now - }) - self.bot.data_manager.add('nick_nudged', int(member.id)) - await self._modlog(guild, f"📨 Sent nickname nudge to {member.mention}") - except Exception: - pass - except Exception: - pass - await asyncio.sleep(1800) # every 30 minutes - - # ---------- listeners ---------- - - @commands.Cog.listener() - async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent): - # 1) Handle DM nudge confirmations (user reacts with an accept in DM) - if payload.guild_id is None and is_accept(payload.emoji) and payload.user_id != self.bot.user.id: - entry = next((m for m in self.bot.data_manager.get('nick_dm_map') if m['message_id'] == payload.message_id), None) - if not entry: - return - guild = self.bot.get_guild(entry['guild_id']) - member = guild.get_member(entry['user_id']) if guild else None - if not member: - return - # Treat as a claim: mark pending (idempotent) and open review only on first transition - dm = self.bot.data_manager - if member.id not in dm.get('agreed_nickname'): - dm.add('agreed_nickname', int(member.id)) - dm.remove('nick_verified', lambda x: x == member.id) - newly_pending = False - if member.id not in dm.get('nick_claim_pending'): - dm.add('nick_claim_pending', int(member.id)) - newly_pending = True - - if newly_pending: - try: - await self.start_nick_review(guild, member, source="nick_same") - except Exception: - pass - - # Clean map entry - self.bot.data_manager.remove('nick_dm_map', lambda m: m['message_id'] == payload.message_id) - - # Refresh card and maybe full access (pending does NOT block full access) - rr = self.bot.get_cog('ReactionRoleCog') - if rr: - try: - await rr.maybe_apply_full_access(member) - except Exception: - pass - cards = self.bot.get_cog('UserCardsCog') - if cards: - try: - await cards.refresh_card(member) - except Exception: - pass - return - - # 2) Handle moderator review reactions in mod channel - if payload.guild_id and str(payload.emoji) in (CHECK, CROSS) and payload.user_id != self.bot.user.id: - if payload.channel_id != self.mod_channel_id: - return - guild = self.bot.get_guild(payload.guild_id) - if not guild: - return - # Only moderators can act - if not is_moderator_userid(guild, payload.user_id, self.bot): - return - - # Is this a review message? - reviews = self.bot.data_manager.get('nick_reviews') - review = next((r for r in reviews if r.get('message_id') == payload.message_id and r.get('guild_id') == guild.id), None) - if not review or review.get('status') != 'pending': - return - - member = guild.get_member(int(review['user_id'])) - if not member: - # mark closed missing - self.bot.data_manager.update('nick_reviews', lambda r: r is review, lambda r: (r.update({'status':'closed_missing'}), r)[1]) - return - - # Fetch and edit the review message content (best-effort) - try: - ch = self.bot.get_channel(payload.channel_id) - msg = await ch.fetch_message(payload.message_id) - except Exception: - msg = None - - dm = self.bot.data_manager - now_ts = int(time.time()) - approver = f"<@{payload.user_id}>" - - if str(payload.emoji) == CHECK: - # Approve: mark verified, clear pending, ensure agreed flag set - if member.id not in dm.get('agreed_nickname'): - dm.add('agreed_nickname', int(member.id)) - dm.remove('nick_claim_pending', lambda x: x == member.id) - if member.id not in dm.get('nick_verified'): - dm.add('nick_verified', int(member.id)) - - # Update review record - dm.update('nick_reviews', lambda r: r is review, lambda r: (r.update({'status': 'approved', 'decided_ts': now_ts, 'moderator_id': int(payload.user_id)}), r)[1]) - - # Edit the review message - if msg: - try: - await msg.clear_reactions() - except Exception: - pass - try: - await msg.edit(content=f"✅ **Nickname Approved** for {member.mention} by {approver} — {_ts_rel(now_ts)}") - except Exception: - pass - - # Modlog - await self._modlog(guild, - f"✅ Nickname **verified** for {member.mention} by {approver} — {_ts_rel(now_ts)}." - ) - - # Refresh roles / card - rr = self.bot.get_cog('ReactionRoleCog') - if rr: - try: - await rr.maybe_apply_full_access(member) - except Exception: - pass - cards = self.bot.get_cog('UserCardsCog') - if cards: - try: - await cards.refresh_card(member) - except Exception: - pass - - else: - # Reject: clear all nickname flags; Full Access should be revoked by maybe_apply_full_access - dm.remove('agreed_nickname', lambda x: x == member.id) - dm.remove('nick_claim_pending', lambda x: x == member.id) - dm.remove('nick_verified', lambda x: x == member.id) - - # Update review record - dm.update('nick_reviews', lambda r: r is review, lambda r: (r.update({'status': 'rejected', 'decided_ts': now_ts, 'moderator_id': int(payload.user_id)}), r)[1]) - - if msg: - try: - await msg.clear_reactions() - except Exception: - pass - try: - await msg.edit(content=f"❌ **Nickname Rejected** for {member.mention} by {approver} — {_ts_rel(now_ts)}") - except Exception: - pass - - await self._modlog(guild, - f"❌ Nickname **rejected** for {member.mention} by {approver} — {_ts_rel(now_ts)}." - ) - - # Refresh roles / card - rr = self.bot.get_cog('ReactionRoleCog') - if rr: - try: - await rr.maybe_apply_full_access(member) - except Exception: - pass - cards = self.bot.get_cog('UserCardsCog') - if cards: - try: - await cards.refresh_card(member) - except Exception: - pass - -async def setup(bot): - await bot.add_cog(NickNudgeCog(bot))