From 80c9f0d668f3af45f1430e8ab2ada661cd259bf8 Mon Sep 17 00:00:00 2001 From: Lilac-Rose Date: Tue, 10 Mar 2026 04:41:32 +0100 Subject: [PATCH] =?UTF-8?q?-=20prestige=20color:=20block=20equipping=20the?= =?UTF-8?q?=20color=20copy=20of=20your=20highest=20role,=20=20=20since=20t?= =?UTF-8?q?he=20copy=20sits=20lower=20in=20the=20hierarchy=20and=20demotes?= =?UTF-8?q?=20your=20member=20=20=20list=20position;=20now=20shows=20a=20c?= =?UTF-8?q?lear=20error=20message=20instead=20-=20emote=20credits:=20add?= =?UTF-8?q?=20/emote=5Fcredits=5Fupdate=20command=20so=20users=20can=20sub?= =?UTF-8?q?mit=20=20=20corrections=20to=20existing=20credits=20(goes=20thr?= =?UTF-8?q?ough=20the=20same=20approval=20flow,=20=20=20shows=20old=20?= =?UTF-8?q?=E2=86=92=20new=20credit=20in=20the=20approval=20embed=20and=20?= =?UTF-8?q?DMs)=20-=20emote=20credits:=20add=20is=5Fupdate/old=5Fartist=20?= =?UTF-8?q?columns=20to=20pending=5Fcredits=20=20=20with=20auto-migration?= =?UTF-8?q?=20for=20existing=20installs=20-=20bot:=20disable=20archipelago?= =?UTF-8?q?=5Fmonitor=20cog=20(unloads=20it=20if=20currently=20loaded)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 7 +++ commands/emote_credits.py | 126 ++++++++++++++++++++++++++++++++----- commands/prestige_color.py | 23 ++++++- 3 files changed, 139 insertions(+), 17 deletions(-) diff --git a/bot.py b/bot.py index b7be7c5..23e0fe4 100644 --- a/bot.py +++ b/bot.py @@ -55,11 +55,18 @@ bot.tree.add_command(xp_admin_group) # --- Cog loading --- async def load_cogs(folder: str): non_cog_files = {"add_xp.py", "database.py", "utils.py", "__init__.py", "groups.py", "loader.py"} + disabled_cogs = {"archipelago_monitor.py"} for file in glob.glob(f"{folder}/*.py"): filename = os.path.basename(file) if filename in non_cog_files: logger.debug(f"Skipping {filename} (utility file)") continue + if filename in disabled_cogs: + module_name = f"{folder}.{os.path.splitext(filename)[0]}" + if module_name in bot.extensions: + await bot.unload_extension(module_name) + logger.info(f"Unloaded disabled cog {module_name}") + continue module_name = f"{folder}.{os.path.splitext(filename)[0]}" try: if module_name in bot.extensions: diff --git a/commands/emote_credits.py b/commands/emote_credits.py index 05607d1..665cfb1 100644 --- a/commands/emote_credits.py +++ b/commands/emote_credits.py @@ -38,10 +38,19 @@ class EmoteCredits(ModerationBase, commands.Cog): artist TEXT NOT NULL, submitted_by INTEGER NOT NULL, submitted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - message_id INTEGER + message_id INTEGER, + is_update BOOLEAN DEFAULT 0, + old_artist TEXT ) """) + # migrate existing tables that predate is_update / old_artist columns + for col, definition in [("is_update", "BOOLEAN DEFAULT 0"), ("old_artist", "TEXT")]: + try: + await conn.execute(f"ALTER TABLE pending_credits ADD COLUMN {col} {definition}") + except Exception: + pass # column already exists + await conn.commit() async def get_credit(self, emote_name: str): @@ -169,6 +178,75 @@ class EmoteCredits(ModerationBase, commands.Cog): ) await interaction.followup.send(embed=embed, ephemeral=True) + @app_commands.command(name="emote_credits_update", description="Submit a correction to an existing emote or sticker credit") + @app_commands.describe( + emote="The emoji or sticker (you can type it directly!)", + artist="The corrected artist name" + ) + async def emote_credits_update(self, interaction: discord.Interaction, emote: str, artist: str): + await interaction.response.defer(ephemeral=True) + + emoji_name = self.parse_emoji_name(emote) + + existing_credit = await self.get_credit(emoji_name) + if not existing_credit: + embed = discord.Embed( + title="❌ No Existing Credit", + description=f"**{emoji_name}** doesn't have a credit yet. Use `/emote_credits_add` to submit one.", + color=discord.Color.red() + ) + await interaction.followup.send(embed=embed, ephemeral=True) + return + + if existing_credit.lower() == artist.lower(): + embed = discord.Embed( + title="⚠️ No Change", + description=f"**{emoji_name}** is already credited to **{existing_credit}**.", + color=discord.Color.orange() + ) + await interaction.followup.send(embed=embed, ephemeral=True) + return + + async with aiosqlite.connect(self.db_path) as conn: + cursor = await conn.execute( + "INSERT INTO pending_credits (emote_name, artist, submitted_by, is_update, old_artist) VALUES (?, ?, ?, 1, ?)", + (emoji_name, artist, interaction.user.id, existing_credit) + ) + submission_id = cursor.lastrowid + await conn.commit() + + approval_channel = self.bot.get_channel(self.approval_channel_id) + if not approval_channel: + await interaction.followup.send("❌ Approval channel not found. Please contact an admin.", ephemeral=True) + return + + approval_embed = discord.Embed( + title="✏️ Credit Update Submission", + color=discord.Color.blue() + ) + approval_embed.add_field(name="Emote Name", value=f"`{emoji_name}`", inline=False) + approval_embed.add_field(name="Current Credit", value=existing_credit, inline=False) + approval_embed.add_field(name="Proposed Credit", value=artist, inline=False) + approval_embed.add_field(name="Submitted By", value=interaction.user.mention, inline=False) + approval_embed.set_footer(text=f"Submission ID: {submission_id}") + + view = CreditApprovalView(self, submission_id, emoji_name, artist, interaction.user.id, is_update=True, old_artist=existing_credit) + approval_msg = await approval_channel.send(embed=approval_embed, view=view) + + async with aiosqlite.connect(self.db_path) as conn: + await conn.execute( + "UPDATE pending_credits SET message_id = ? WHERE id = ?", + (approval_msg.id, submission_id) + ) + await conn.commit() + + embed = discord.Embed( + title="✅ Update Submitted", + description=f"Your correction for **{emoji_name}** (changing credit from **{existing_credit}** to **{artist}**) has been sent for approval!", + color=discord.Color.green() + ) + await interaction.followup.send(embed=embed, ephemeral=True) + @app_commands.command(name="emote_artists", description="List all artists who have credited emotes or stickers") async def emote_artists(self, interaction: discord.Interaction): await interaction.response.defer() @@ -397,13 +475,15 @@ class EmoteCredits(ModerationBase, commands.Cog): class CreditApprovalView(discord.ui.View): - def __init__(self, cog, submission_id, emote_name, artist, submitted_by): + def __init__(self, cog, submission_id, emote_name, artist, submitted_by, is_update=False, old_artist=None): super().__init__(timeout=None) self.cog = cog self.submission_id = submission_id self.emote_name = emote_name self.artist = artist self.submitted_by = submitted_by + self.is_update = is_update + self.old_artist = old_artist @discord.ui.button(label="Approve", style=discord.ButtonStyle.green, custom_id="approve_credit") async def approve_button(self, interaction: discord.Interaction, button: discord.ui.Button): @@ -417,11 +497,15 @@ class CreditApprovalView(discord.ui.View): # Update message embed = discord.Embed( - title="✅ Credit Approved", + title="✅ Credit Update Approved" if self.is_update else "✅ Credit Approved", color=discord.Color.green() ) embed.add_field(name="Emote Name", value=f"`{self.emote_name}`", inline=False) - embed.add_field(name="Artist", value=self.artist, inline=False) + if self.is_update and self.old_artist: + embed.add_field(name="Old Credit", value=self.old_artist, inline=False) + embed.add_field(name="New Credit", value=self.artist, inline=False) + else: + embed.add_field(name="Artist", value=self.artist, inline=False) embed.add_field(name="Approved By", value=interaction.user.mention, inline=False) await interaction.response.edit_message(embed=embed, view=None) @@ -429,11 +513,18 @@ class CreditApprovalView(discord.ui.View): # Notify submitter try: submitter = await self.cog.bot.fetch_user(self.submitted_by) - notify_embed = discord.Embed( - title="✅ Your Credit Submission Was Approved!", - description=f"**{self.emote_name}** by **{self.artist}** has been added to the credits database.", - color=discord.Color.green() - ) + if self.is_update and self.old_artist: + notify_embed = discord.Embed( + title="✅ Your Credit Update Was Approved!", + description=f"The credit for **{self.emote_name}** has been updated from **{self.old_artist}** to **{self.artist}**.", + color=discord.Color.green() + ) + else: + notify_embed = discord.Embed( + title="✅ Your Credit Submission Was Approved!", + description=f"**{self.emote_name}** by **{self.artist}** has been added to the credits database.", + color=discord.Color.green() + ) await submitter.send(embed=notify_embed) except Exception: pass @@ -459,11 +550,18 @@ class CreditApprovalView(discord.ui.View): # Notify submitter try: submitter = await self.cog.bot.fetch_user(self.submitted_by) - notify_embed = discord.Embed( - title="❌ Your Credit Submission Was Denied", - description=f"Your submission for **{self.emote_name}** by **{self.artist}** was not approved.", - color=discord.Color.red() - ) + if self.is_update and self.old_artist: + notify_embed = discord.Embed( + title="❌ Your Credit Update Was Denied", + description=f"Your proposed change for **{self.emote_name}** (from **{self.old_artist}** to **{self.artist}**) was not approved.", + color=discord.Color.red() + ) + else: + notify_embed = discord.Embed( + title="❌ Your Credit Submission Was Denied", + description=f"Your submission for **{self.emote_name}** by **{self.artist}** was not approved.", + color=discord.Color.red() + ) await submitter.send(embed=notify_embed) except Exception: pass diff --git a/commands/prestige_color.py b/commands/prestige_color.py index dac3087..a17fbd0 100644 --- a/commands/prestige_color.py +++ b/commands/prestige_color.py @@ -18,7 +18,6 @@ PRESTIGE_ROLES: dict[str, int] = { "Elite Ritualist": 1296055376009101384, "Honorable Ritualist": 1213171315259736155, "Content Creator": 1038402681376612413, - "Ritual Sponsor": 881560923494547477, } # Color copies get placed just above this role in the hierarchy @@ -172,6 +171,24 @@ class PrestigeColor(commands.Cog): ) return + # Block equipping the color copy of the user's highest prestige role. + # The copy sits lower in the hierarchy than the original, so swapping it out + # would actually demote the user's display position in the member list. + member_prestige_roles = [ + guild.get_role(pid) + for pid in PRESTIGE_ROLES.values() + if guild.get_role(pid) and guild.get_role(pid) in member.roles + ] + if member_prestige_roles: + highest_prestige = max(member_prestige_roles, key=lambda r: r.position) + if highest_prestige.id == original_id: + await interaction.followup.send( + f"**{prestige}** is already your highest role — its color is already showing correctly. " + f"You can only equip a color for a role that isn't your highest.", + ephemeral=True + ) + return + all_copy_ids = set(color_ids.values()) # If they had a different color active, swap it out and give back the original. @@ -182,7 +199,7 @@ class PrestigeColor(commands.Cog): await member.remove_roles(*old_copies, reason="Switching prestige color") to_restore = [ copy_to_original[r.id] for r in old_copies - if copy_to_original.get(r.id) and copy_to_original[r.id] not in member.roles + if copy_to_original.get(r.id) ] if to_restore: await member.add_roles(*to_restore, reason="Restoring prestige role after color switch") @@ -223,7 +240,7 @@ class PrestigeColor(commands.Cog): copy_to_original = {v: guild.get_role(int(k)) for k, v in color_ids.items()} to_restore = [ copy_to_original[r.id] for r in to_remove - if copy_to_original.get(r.id) and copy_to_original[r.id] not in member.roles + if copy_to_original.get(r.id) ] if to_restore: await member.add_roles(*to_restore, reason="Restoring prestige role after color removal")