From e735ac100387b37bb44242932749bb7524a74532 Mon Sep 17 00:00:00 2001 From: Sarthak singh Date: Mon, 30 Oct 2023 21:17:29 +0530 Subject: [PATCH 1/9] added custom role configuration for embed --- bot/config.py | 5 +++++ bot/extensions/custom_roles/__init__.py | 0 bot/extensions/custom_roles/commands.py | 0 bot/extensions/custom_roles/events.py | 0 example.env | 3 +++ 5 files changed, 8 insertions(+) create mode 100644 bot/extensions/custom_roles/__init__.py create mode 100644 bot/extensions/custom_roles/commands.py create mode 100644 bot/extensions/custom_roles/events.py diff --git a/bot/config.py b/bot/config.py index f06a98e8..d100132a 100644 --- a/bot/config.py +++ b/bot/config.py @@ -97,6 +97,10 @@ class ErrorHandling(BaseModel): webhook_url: str +class CustomRoles(BaseModel): + log_channel_id: int + + class Settings(BaseSettings): aoc: AoC bot: Bot @@ -111,6 +115,7 @@ class Settings(BaseSettings): timathon: Timathon hastebin: Hastebin errors: ErrorHandling + custom_roles: CustomRoles class Config: env_file = ".env" diff --git a/bot/extensions/custom_roles/__init__.py b/bot/extensions/custom_roles/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bot/extensions/custom_roles/commands.py b/bot/extensions/custom_roles/commands.py new file mode 100644 index 00000000..e69de29b diff --git a/bot/extensions/custom_roles/events.py b/bot/extensions/custom_roles/events.py new file mode 100644 index 00000000..e69de29b diff --git a/example.env b/example.env index 1429bf2e..d488426d 100644 --- a/example.env +++ b/example.env @@ -67,3 +67,6 @@ TAGS__REQUIRED_ROLE_ID=0 # --- Timathon TIMATHON__CHANNEL_ID=0 TIMATHON__PARTICIPANT_ROLE_ID=0 + +# --- Custom Roles +CUSTOM_ROLES__LOG_CHANNEL_ID = 0 From 4658134fde0cb27c5180a4174c64ca2549fa7217 Mon Sep 17 00:00:00 2001 From: Sarthak singh Date: Mon, 30 Oct 2023 21:20:05 +0530 Subject: [PATCH 2/9] addded custom roles command --- bot/extensions/custom_roles/__init__.py | 9 ++ bot/extensions/custom_roles/commands.py | 108 ++++++++++++++++++++ bot/extensions/custom_roles/events.py | 58 +++++++++++ bot/models/custom_roles.py | 1 + bot/models/migrations/004_up__levelling.sql | 1 + cli.py | 1 + 6 files changed, 178 insertions(+) diff --git a/bot/extensions/custom_roles/__init__.py b/bot/extensions/custom_roles/__init__.py index e69de29b..1239ad49 100644 --- a/bot/extensions/custom_roles/__init__.py +++ b/bot/extensions/custom_roles/__init__.py @@ -0,0 +1,9 @@ +from bot.core import DiscordBot + +from .commands import CustomRoles +from .events import CustomRoleEvents + + +async def setup(bot: DiscordBot) -> None: + await bot.add_cog(CustomRoles(bot=bot)) + await bot.add_cog(CustomRoleEvents(bot=bot)) diff --git a/bot/extensions/custom_roles/commands.py b/bot/extensions/custom_roles/commands.py index e69de29b..f0efce15 100644 --- a/bot/extensions/custom_roles/commands.py +++ b/bot/extensions/custom_roles/commands.py @@ -0,0 +1,108 @@ +import datetime + +import discord +from discord import app_commands +from discord.ext import commands +from discord.utils import escape_markdown, format_dt + +from bot import core +from bot.models.custom_roles import CustomRole + + +class CustomRoles(commands.Cog): + def __init__(self, bot): + self.bot = bot + + def role_embed(self, heading: str, user: discord.Member, role: discord.Role): + embed = discord.Embed( + description=f"### {heading} {user.mention}\n\n**Name**\n{role.name}\n**Color**\n{role.color}" + f"\n**Created**\n {format_dt(role.created_at)}", + color=role.color, + timestamp=datetime.datetime.utcnow(), + ) + embed.set_footer(text=user.name) + embed.set_thumbnail(url=user.avatar) + return embed + + @app_commands.command(description="Manage custom role") + @app_commands.default_permissions(administrator=True) + @app_commands.describe(name="Updating Custom Role name", color="Updating custom role name") + async def myrole(self, interaction: core.InteractionType, name: str = None, color: str = None): + if name: + name = escape_markdown(name) + if color: + try: + color = discord.Color(int(color, 16)) + except ValueError: + return await interaction.response.send_message("Invalid HEX value", ephemeral=True) + + query = """SELECT * FROM custom_roles + WHERE guild_id = $1 AND user_id = $2""" + before = await CustomRole.fetchrow(query, interaction.guild.id, interaction.user.id) + + # Insert data if it doesn't exist + if before is None: + # Validate the name parameter + if name is None: + return await interaction.response.send_message("You don't have a custom role yet!", ephemeral=True) + + # Create and assign the role to user + discord_role = await interaction.guild.create_role(name=name, colour=color or discord.Color.default()) + await interaction.user.add_roles(discord_role) + + query = """INSERT INTO custom_roles (user_id, guild_id, role_id, name, color) + VALUES ($1, $2, $3, $4, $5) + RETURNING *""" + record = await CustomRole.fetchrow( + query, + interaction.user.id, + interaction.guild.id, + discord_role.id, + discord_role.name, + str(discord_role.color), + ) + # Persist the role + self.bot.dispatch( + "persist_roles", guild_id=interaction.guild.id, user_id=interaction.user.id, role_ids=[discord_role.id] + ) + # Log the role + self.bot.dispatch("custom_role_create", data=record) + + return await interaction.response.send_message( + embed=self.role_embed("**Custom Role has been assigned**", interaction.user, discord_role), + ephemeral=True, + ) + + # Return role information if no parameter is passed + if not name and not color: + return await interaction.response.send_message( + embed=self.role_embed("Custom Role for", interaction.user, interaction.guild.get_role(before.role_id)), + ephemeral=True, + ) + + # Editing the role + await interaction.guild.get_role(before.role_id).edit( + name=name or before.name, + colour=color or discord.Color(int(before.color.lstrip("#"), 16)), + ) + + # Update data in DB + query = """UPDATE custom_roles + SET name = $4, color = $5 + WHERE guild_id = $1 AND user_id = $2 AND role_id = $3 + RETURNING *""" + after = await CustomRole.fetchrow( + query, interaction.guild.id, interaction.user.id, before.role_id, name, color or before.color + ) + self.bot.dispatch("custom_role_update", before, after) + + return await interaction.response.send_message( + embed=self.role_embed( + "**Custom Role has been updated**", interaction.user, interaction.guild.get_role(before.role_id) + ), + ephemeral=True, + ) + + +async def setup(bot: commands.Bot): + await bot.add_cog(CustomRoles(bot=bot)) diff --git a/bot/extensions/custom_roles/events.py b/bot/extensions/custom_roles/events.py index e69de29b..83014610 100644 --- a/bot/extensions/custom_roles/events.py +++ b/bot/extensions/custom_roles/events.py @@ -0,0 +1,58 @@ +import datetime + +import discord +from discord.ext import commands + +from bot import core +from bot.config import settings +from bot.models.custom_roles import CustomRole + + +class CustomRoleEvents(commands.Cog): + """Events for Levelling in discord.""" + + def __init__(self, bot: core.DiscordBot): + self.bot = bot + self.updated_at: dict[int, datetime] = {} + + @property + def custom_roles_logs_channel(self) -> discord.TextChannel | None: + return self.bot.guild.get_channel(settings.custom_roles.log_channel_id) + + @commands.Cog.listener() + async def on_guild_role_update(self, before, after): + # TODO send an embed for updating role manually + pass + + @commands.Cog.listener() + async def on_custom_role_create(self, data: CustomRole): + """Logs the creation of new role""" + embed = discord.Embed( + description=f"### <@{data.user_id}> Custom Role Created", + color=discord.Color.brand_green(), + timestamp=datetime.datetime.utcnow(), + ) + embed.add_field(name="Name", value=data.name) + embed.add_field(name="Color", value=str(data.color)) + embed.set_thumbnail(url=(await self.bot.fetch_user(data.user_id)).avatar) + + return await self.custom_roles_logs_channel.send(embed=embed) + + @commands.Cog.listener() + async def on_custom_role_update(self, before: CustomRole, after: CustomRole): + """Logs the update of custom role.""" + embed = discord.Embed( + description=f"### <@{before.user_id}> Custom Role Updated", + color=discord.Color.brand_green(), + timestamp=datetime.datetime.utcnow(), + ) + + embed.add_field(name="Old Name", value=before.name) + embed.add_field(name="New Name", value=after.name) + embed.add_field(name="\u200B", value="\u200B") + embed.add_field(name="Old Color", value=before.color) + embed.add_field(name="New Color", value=str(after.color)) + embed.add_field(name="\u200B", value="\u200B") + embed.set_thumbnail(url=(await self.bot.fetch_user(before.user_id)).avatar) + + return await self.custom_roles_logs_channel.send(embed=embed) diff --git a/bot/models/custom_roles.py b/bot/models/custom_roles.py index 73c90fba..1e473f98 100644 --- a/bot/models/custom_roles.py +++ b/bot/models/custom_roles.py @@ -3,6 +3,7 @@ class CustomRole(Model): id: int + user_id: int guild_id: int role_id: int name: str diff --git a/bot/models/migrations/004_up__levelling.sql b/bot/models/migrations/004_up__levelling.sql index 76d851cf..0b2a5bc9 100644 --- a/bot/models/migrations/004_up__levelling.sql +++ b/bot/models/migrations/004_up__levelling.sql @@ -12,6 +12,7 @@ CREATE TABLE IF NOT EXISTS levelling_users CREATE TABLE IF NOT EXISTS custom_roles ( id BIGINT PRIMARY KEY DEFAULT create_snowflake(), + user_id BIGINT NOT NULL, guild_id BIGINT NOT NULL, role_id BIGINT NOT NULL, name VARCHAR NOT NULL, diff --git a/cli.py b/cli.py index f90ae121..79fa63e3 100644 --- a/cli.py +++ b/cli.py @@ -137,6 +137,7 @@ async def main(ctx): "bot.extensions.tags", "bot.extensions.levelling", "bot.extensions.persistent_roles", + "bot.extensions.custom_roles", "bot.cogs._help", "bot.cogs.clashofcode", "bot.cogs.roles", From 095a25fce4460ee0d52cf4211d8c9d1392fc8d71 Mon Sep 17 00:00:00 2001 From: Sarthak singh Date: Fri, 3 Nov 2023 20:37:11 +0530 Subject: [PATCH 3/9] fixing PR comments --- bot/extensions/custom_roles/commands.py | 8 +++----- bot/models/__init__.py | 2 ++ bot/models/custom_roles.py | 2 +- bot/models/migrations/004_up__levelling.sql | 1 - bot/models/migrations/005_down__custom_role.sql | 1 + bot/models/migrations/005_up__custom_roles.sql | 1 + 6 files changed, 8 insertions(+), 7 deletions(-) create mode 100644 bot/models/migrations/005_down__custom_role.sql create mode 100644 bot/models/migrations/005_up__custom_roles.sql diff --git a/bot/extensions/custom_roles/commands.py b/bot/extensions/custom_roles/commands.py index f0efce15..9a72fe32 100644 --- a/bot/extensions/custom_roles/commands.py +++ b/bot/extensions/custom_roles/commands.py @@ -6,7 +6,7 @@ from discord.utils import escape_markdown, format_dt from bot import core -from bot.models.custom_roles import CustomRole +from bot.models import CustomRole class CustomRoles(commands.Cog): @@ -15,8 +15,8 @@ def __init__(self, bot): def role_embed(self, heading: str, user: discord.Member, role: discord.Role): embed = discord.Embed( - description=f"### {heading} {user.mention}\n\n**Name**\n{role.name}\n**Color**\n{role.color}" - f"\n**Created**\n {format_dt(role.created_at)}", + description=f"### {heading} {user.mention}\n\n**Name**\n{escape_markdown(role.name)}\n**Color**\n" + f"{role.color}\n**Created**\n {format_dt(role.created_at)}", color=role.color, timestamp=datetime.datetime.utcnow(), ) @@ -28,8 +28,6 @@ def role_embed(self, heading: str, user: discord.Member, role: discord.Role): @app_commands.default_permissions(administrator=True) @app_commands.describe(name="Updating Custom Role name", color="Updating custom role name") async def myrole(self, interaction: core.InteractionType, name: str = None, color: str = None): - if name: - name = escape_markdown(name) if color: try: color = discord.Color(int(color, 16)) diff --git a/bot/models/__init__.py b/bot/models/__init__.py index 3fb6422e..2bb857fe 100644 --- a/bot/models/__init__.py +++ b/bot/models/__init__.py @@ -1,3 +1,4 @@ +from .custom_roles import CustomRole from .gconfig import FilterConfig from .levelling_ignored_channels import IgnoredChannel from .levelling_roles import LevellingRole @@ -20,4 +21,5 @@ PersistedRole, IgnoredChannel, LevellingRole, + CustomRole, ) # Fixes F401 diff --git a/bot/models/custom_roles.py b/bot/models/custom_roles.py index 1e473f98..1a51dfc2 100644 --- a/bot/models/custom_roles.py +++ b/bot/models/custom_roles.py @@ -3,7 +3,7 @@ class CustomRole(Model): id: int - user_id: int + user_id: int | None guild_id: int role_id: int name: str diff --git a/bot/models/migrations/004_up__levelling.sql b/bot/models/migrations/004_up__levelling.sql index 0b2a5bc9..76d851cf 100644 --- a/bot/models/migrations/004_up__levelling.sql +++ b/bot/models/migrations/004_up__levelling.sql @@ -12,7 +12,6 @@ CREATE TABLE IF NOT EXISTS levelling_users CREATE TABLE IF NOT EXISTS custom_roles ( id BIGINT PRIMARY KEY DEFAULT create_snowflake(), - user_id BIGINT NOT NULL, guild_id BIGINT NOT NULL, role_id BIGINT NOT NULL, name VARCHAR NOT NULL, diff --git a/bot/models/migrations/005_down__custom_role.sql b/bot/models/migrations/005_down__custom_role.sql new file mode 100644 index 00000000..d5e8c537 --- /dev/null +++ b/bot/models/migrations/005_down__custom_role.sql @@ -0,0 +1 @@ +ALTER TABLE custom_roles DROP COLUMN user_id; diff --git a/bot/models/migrations/005_up__custom_roles.sql b/bot/models/migrations/005_up__custom_roles.sql new file mode 100644 index 00000000..83165054 --- /dev/null +++ b/bot/models/migrations/005_up__custom_roles.sql @@ -0,0 +1 @@ +ALTER TABLE custom_roles ADD COLUMN user_id BIGINT; From f091151940e7148d53fca9b19f07f93834700663 Mon Sep 17 00:00:00 2001 From: Sarthak singh Date: Mon, 6 Nov 2023 23:43:38 +0530 Subject: [PATCH 4/9] updating DB when role updated manually and improving update query for custom role --- bot/extensions/custom_roles/commands.py | 8 +++----- bot/extensions/custom_roles/events.py | 18 +++++++++++++++--- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/bot/extensions/custom_roles/commands.py b/bot/extensions/custom_roles/commands.py index 9a72fe32..bd630c7e 100644 --- a/bot/extensions/custom_roles/commands.py +++ b/bot/extensions/custom_roles/commands.py @@ -86,12 +86,10 @@ async def myrole(self, interaction: core.InteractionType, name: str = None, colo # Update data in DB query = """UPDATE custom_roles - SET name = $4, color = $5 - WHERE guild_id = $1 AND user_id = $2 AND role_id = $3 + SET name = $2, color = $3 + WHERE role_id = $1 RETURNING *""" - after = await CustomRole.fetchrow( - query, interaction.guild.id, interaction.user.id, before.role_id, name, color or before.color - ) + after = await CustomRole.fetchrow(query, before.role_id, name, color or before.color) self.bot.dispatch("custom_role_update", before, after) return await interaction.response.send_message( diff --git a/bot/extensions/custom_roles/events.py b/bot/extensions/custom_roles/events.py index 83014610..4880a925 100644 --- a/bot/extensions/custom_roles/events.py +++ b/bot/extensions/custom_roles/events.py @@ -20,9 +20,17 @@ def custom_roles_logs_channel(self) -> discord.TextChannel | None: return self.bot.guild.get_channel(settings.custom_roles.log_channel_id) @commands.Cog.listener() - async def on_guild_role_update(self, before, after): - # TODO send an embed for updating role manually - pass + async def on_guild_role_update(self, before: discord.Role, after: discord.Role): + try: + if datetime.datetime.now() - self.updated_at[before.id] < datetime.timedelta(0, 10): + query = """UPDATE custom_roles + SET name = $2, color = $3 + WHERE role_id = $1 + RETURNING *""" + await CustomRole.fetchrow(query, before.id, after.name, after.color) + + except KeyError: + pass @commands.Cog.listener() async def on_custom_role_create(self, data: CustomRole): @@ -36,6 +44,8 @@ async def on_custom_role_create(self, data: CustomRole): embed.add_field(name="Color", value=str(data.color)) embed.set_thumbnail(url=(await self.bot.fetch_user(data.user_id)).avatar) + self.updated_at.setdefault(data.role_id, datetime.datetime.now()) + return await self.custom_roles_logs_channel.send(embed=embed) @commands.Cog.listener() @@ -55,4 +65,6 @@ async def on_custom_role_update(self, before: CustomRole, after: CustomRole): embed.add_field(name="\u200B", value="\u200B") embed.set_thumbnail(url=(await self.bot.fetch_user(before.user_id)).avatar) + self.updated_at.setdefault(after.role_id, datetime.datetime.now()) + return await self.custom_roles_logs_channel.send(embed=embed) From bfed7d1f83e9b0d9ae7b20609f0bde4ffe3f5e1d Mon Sep 17 00:00:00 2001 From: Sylte Date: Mon, 6 Nov 2023 22:53:25 +0100 Subject: [PATCH 5/9] Update migration to make color an integer. --- bot/models/migrations/005_down__custom_role.sql | 7 +++++++ bot/models/migrations/005_up__custom_roles.sql | 10 ++++++++++ 2 files changed, 17 insertions(+) diff --git a/bot/models/migrations/005_down__custom_role.sql b/bot/models/migrations/005_down__custom_role.sql index d5e8c537..e03e6cd7 100644 --- a/bot/models/migrations/005_down__custom_role.sql +++ b/bot/models/migrations/005_down__custom_role.sql @@ -1 +1,8 @@ ALTER TABLE custom_roles DROP COLUMN user_id; + +BEGIN; +ALTER TABLE custom_roles ADD COLUMN old_color VARCHAR NOT NULL; +UPDATE custom_roles SET old_color = CAST(color AS VARCHAR); +ALTER TABLE custom_roles DROP COLUMN color; +ALTER TABLE custom_roles RENAME COLUMN old_color TO color; +COMMIT; diff --git a/bot/models/migrations/005_up__custom_roles.sql b/bot/models/migrations/005_up__custom_roles.sql index 83165054..80d33a36 100644 --- a/bot/models/migrations/005_up__custom_roles.sql +++ b/bot/models/migrations/005_up__custom_roles.sql @@ -1 +1,11 @@ ALTER TABLE custom_roles ADD COLUMN user_id BIGINT; + + +-- Create a temporary column `new_color` and update the table with the values from this table. +-- Drop the old color column, then rename the new one and set it to non-nullable. +BEGIN; +ALTER TABLE custom_roles ADD COLUMN new_color INTEGER NOT NULL; +UPDATE custom_roles SET new_color = CAST(color AS INTEGER); +ALTER TABLE custom_roles DROP COLUMN color; +ALTER TABLE custom_roles RENAME COLUMN new_color TO color; +COMMIT; From 43cd7ebe989d81b521a6f8acb06f1e7ccdc251a4 Mon Sep 17 00:00:00 2001 From: Sylte Date: Mon, 6 Nov 2023 22:56:24 +0100 Subject: [PATCH 6/9] Update myrole command to use existing functions, update embeds and event code. --- bot/extensions/custom_roles/commands.py | 87 +++++++++++++------------ bot/extensions/custom_roles/events.py | 56 +++++++++------- bot/models/custom_roles.py | 10 +-- 3 files changed, 82 insertions(+), 71 deletions(-) diff --git a/bot/extensions/custom_roles/commands.py b/bot/extensions/custom_roles/commands.py index bd630c7e..194ea1b5 100644 --- a/bot/extensions/custom_roles/commands.py +++ b/bot/extensions/custom_roles/commands.py @@ -1,9 +1,8 @@ import datetime import discord -from discord import app_commands +from discord import app_commands, utils from discord.ext import commands -from discord.utils import escape_markdown, format_dt from bot import core from bot.models import CustomRole @@ -13,64 +12,65 @@ class CustomRoles(commands.Cog): def __init__(self, bot): self.bot = bot - def role_embed(self, heading: str, user: discord.Member, role: discord.Role): + self.color_converter = commands.ColorConverter() + + @staticmethod + def role_embed(heading: str, user: discord.Member, role: discord.Role): embed = discord.Embed( - description=f"### {heading} {user.mention}\n\n**Name**\n{escape_markdown(role.name)}\n**Color**\n" - f"{role.color}\n**Created**\n {format_dt(role.created_at)}", + title=heading, color=role.color, timestamp=datetime.datetime.utcnow(), ) - embed.set_footer(text=user.name) + embed.add_field(name="Name", value=utils.escape_markdown(role.name)) + embed.add_field(name="Color", value=str(role.color)) + embed.add_field(name="Created at", value=utils.format_dt(role.created_at)) embed.set_thumbnail(url=user.avatar) return embed - @app_commands.command(description="Manage custom role") + @app_commands.command() @app_commands.default_permissions(administrator=True) - @app_commands.describe(name="Updating Custom Role name", color="Updating custom role name") + @app_commands.describe(name="New name", color="New color") async def myrole(self, interaction: core.InteractionType, name: str = None, color: str = None): - if color: + """Manage your custom role""" + if color is not None: try: - color = discord.Color(int(color, 16)) - except ValueError: - return await interaction.response.send_message("Invalid HEX value", ephemeral=True) + color = await self.color_converter.convert(None, color) # noqa + except commands.BadColourArgument as e: + return await interaction.response.send_message(str(e), ephemeral=True) - query = """SELECT * FROM custom_roles - WHERE guild_id = $1 AND user_id = $2""" + query = "SELECT * FROM custom_roles WHERE guild_id = $1 AND user_id = $2" before = await CustomRole.fetchrow(query, interaction.guild.id, interaction.user.id) - # Insert data if it doesn't exist if before is None: - # Validate the name parameter if name is None: return await interaction.response.send_message("You don't have a custom role yet!", ephemeral=True) # Create and assign the role to user - discord_role = await interaction.guild.create_role(name=name, colour=color or discord.Color.default()) - await interaction.user.add_roles(discord_role) - - query = """INSERT INTO custom_roles (user_id, guild_id, role_id, name, color) - VALUES ($1, $2, $3, $4, $5) - RETURNING *""" - record = await CustomRole.fetchrow( - query, - interaction.user.id, - interaction.guild.id, - discord_role.id, - discord_role.name, - str(discord_role.color), + role = await interaction.guild.create_role(name=name, colour=color or discord.Color.random()) + + record = await CustomRole.ensure_exists( + guild_id=interaction.guild.id, + user_id=interaction.user.id, + role_id=role.id, + name=role.name, + color=role.color.value, ) - # Persist the role + + self.bot.dispatch("custom_role_create", custom_role=record) self.bot.dispatch( - "persist_roles", guild_id=interaction.guild.id, user_id=interaction.user.id, role_ids=[discord_role.id] + "persist_roles", + guild_id=interaction.guild.id, + user_id=interaction.user.id, + role_ids=[role.id], ) - # Log the role - self.bot.dispatch("custom_role_create", data=record) return await interaction.response.send_message( - embed=self.role_embed("**Custom Role has been assigned**", interaction.user, discord_role), + embed=self.role_embed("**Custom Role has been assigned**", interaction.user, role), ephemeral=True, ) + role = interaction.guild.get_role(before.role_id) + # Return role information if no parameter is passed if not name and not color: return await interaction.response.send_message( @@ -78,18 +78,19 @@ async def myrole(self, interaction: core.InteractionType, name: str = None, colo ephemeral=True, ) - # Editing the role - await interaction.guild.get_role(before.role_id).edit( + await role.edit( name=name or before.name, - colour=color or discord.Color(int(before.color.lstrip("#"), 16)), + colour=color or discord.Color(int(before.color)), + ) + + after = await CustomRole.ensure_exists( + guild_id=interaction.guild.id, + user_id=interaction.user.id, + role_id=role.id, + name=role.name, + color=role.color.value, ) - # Update data in DB - query = """UPDATE custom_roles - SET name = $2, color = $3 - WHERE role_id = $1 - RETURNING *""" - after = await CustomRole.fetchrow(query, before.role_id, name, color or before.color) self.bot.dispatch("custom_role_update", before, after) return await interaction.response.send_message( diff --git a/bot/extensions/custom_roles/events.py b/bot/extensions/custom_roles/events.py index 4880a925..aaad6302 100644 --- a/bot/extensions/custom_roles/events.py +++ b/bot/extensions/custom_roles/events.py @@ -1,4 +1,5 @@ import datetime +import time import discord from discord.ext import commands @@ -21,38 +22,49 @@ def custom_roles_logs_channel(self) -> discord.TextChannel | None: @commands.Cog.listener() async def on_guild_role_update(self, before: discord.Role, after: discord.Role): - try: - if datetime.datetime.now() - self.updated_at[before.id] < datetime.timedelta(0, 10): - query = """UPDATE custom_roles - SET name = $2, color = $3 - WHERE role_id = $1 - RETURNING *""" - await CustomRole.fetchrow(query, before.id, after.name, after.color) + last_update = self.updated_at.get(before.id) - except KeyError: - pass + # Ignore events less than 10 seconds since a user updated their role + if last_update is not None: + if time.time() - last_update < 10: + return + + query = """ + UPDATE custom_roles + SET name = $2, color = $3 + WHERE role_id = $1 + """ + await CustomRole.execute(query, after.id, after.name, after.color.value) @commands.Cog.listener() - async def on_custom_role_create(self, data: CustomRole): + async def on_custom_role_create(self, custom_role: CustomRole): """Logs the creation of new role""" + self.updated_at[custom_role.role_id] = time.time() + + user = await self.bot.fetch_user(custom_role.user_id) + embed = discord.Embed( - description=f"### <@{data.user_id}> Custom Role Created", + title="Custom Role Created", color=discord.Color.brand_green(), timestamp=datetime.datetime.utcnow(), ) - embed.add_field(name="Name", value=data.name) - embed.add_field(name="Color", value=str(data.color)) - embed.set_thumbnail(url=(await self.bot.fetch_user(data.user_id)).avatar) - - self.updated_at.setdefault(data.role_id, datetime.datetime.now()) + embed.set_author(name=user.display_name, icon_url=user.display_avatar) + embed.add_field(name="Name", value=custom_role.name) + embed.add_field(name="Color", value="#" + hex(custom_role.color)[2:]) + embed.set_thumbnail(url=user.avatar) + embed.set_footer(text=f"user_id: {custom_role.user_id}") return await self.custom_roles_logs_channel.send(embed=embed) @commands.Cog.listener() async def on_custom_role_update(self, before: CustomRole, after: CustomRole): """Logs the update of custom role.""" + self.updated_at[after.role_id] = time.time() + + user = await self.bot.fetch_user(after.user_id) + embed = discord.Embed( - description=f"### <@{before.user_id}> Custom Role Updated", + title="Custom Role Updated", color=discord.Color.brand_green(), timestamp=datetime.datetime.utcnow(), ) @@ -60,11 +72,9 @@ async def on_custom_role_update(self, before: CustomRole, after: CustomRole): embed.add_field(name="Old Name", value=before.name) embed.add_field(name="New Name", value=after.name) embed.add_field(name="\u200B", value="\u200B") - embed.add_field(name="Old Color", value=before.color) - embed.add_field(name="New Color", value=str(after.color)) - embed.add_field(name="\u200B", value="\u200B") - embed.set_thumbnail(url=(await self.bot.fetch_user(before.user_id)).avatar) - - self.updated_at.setdefault(after.role_id, datetime.datetime.now()) + embed.add_field(name="Old Color", value="#" + hex(before.color)[2:]) + embed.add_field(name="New Color", value="#" + hex(after.color)[2:]) + embed.set_thumbnail(url=user.avatar) + embed.set_footer(text=f"user_id: {after.user_id}") return await self.custom_roles_logs_channel.send(embed=embed) diff --git a/bot/models/custom_roles.py b/bot/models/custom_roles.py index 1a51dfc2..35736fae 100644 --- a/bot/models/custom_roles.py +++ b/bot/models/custom_roles.py @@ -7,14 +7,14 @@ class CustomRole(Model): guild_id: int role_id: int name: str - color: str + color: int @classmethod - async def ensure_exists(cls, guild_id: int, role_id: int, name: str, color: str): + async def ensure_exists(cls, guild_id: int, role_id: int, name: str, color: int, user_id: int = None): """Inserts or updates the custom role.""" query = """ - INSERT INTO custom_roles (guild_id, role_id, name, color) - VALUES ($1, $2, $3, $4) + INSERT INTO custom_roles (guild_id, role_id, name, color, user_id) + VALUES ($1, $2, $3, $4, $5) ON CONFLICT (guild_id, role_id) DO UPDATE SET name = $3, @@ -22,4 +22,4 @@ async def ensure_exists(cls, guild_id: int, role_id: int, name: str, color: str) RETURNING * """ - return await cls.fetchrow(query, guild_id, role_id, name, color) + return await cls.fetchrow(query, guild_id, role_id, name, color, user_id) From 234dd933eddf3bb1bd536a02314a78468b1697e9 Mon Sep 17 00:00:00 2001 From: Sarthak singh Date: Wed, 8 Nov 2023 18:13:35 +0530 Subject: [PATCH 7/9] adding check for name > 100 char and adding role in custom role section --- bot/config.py | 1 + bot/extensions/custom_roles/commands.py | 9 +++++++++ example.env | 1 + 3 files changed, 11 insertions(+) diff --git a/bot/config.py b/bot/config.py index d100132a..7a6fc883 100644 --- a/bot/config.py +++ b/bot/config.py @@ -99,6 +99,7 @@ class ErrorHandling(BaseModel): class CustomRoles(BaseModel): log_channel_id: int + divider_role_id: int class Settings(BaseSettings): diff --git a/bot/extensions/custom_roles/commands.py b/bot/extensions/custom_roles/commands.py index 194ea1b5..f562e3ed 100644 --- a/bot/extensions/custom_roles/commands.py +++ b/bot/extensions/custom_roles/commands.py @@ -5,6 +5,7 @@ from discord.ext import commands from bot import core +from bot.config import settings from bot.models import CustomRole @@ -38,6 +39,11 @@ async def myrole(self, interaction: core.InteractionType, name: str = None, colo except commands.BadColourArgument as e: return await interaction.response.send_message(str(e), ephemeral=True) + if len(name) > 100: + return await interaction.response.send_message( + "Role name can not be more than 100 characters.", ephemeral=True + ) + query = "SELECT * FROM custom_roles WHERE guild_id = $1 AND user_id = $2" before = await CustomRole.fetchrow(query, interaction.guild.id, interaction.user.id) @@ -48,6 +54,9 @@ async def myrole(self, interaction: core.InteractionType, name: str = None, colo # Create and assign the role to user role = await interaction.guild.create_role(name=name, colour=color or discord.Color.random()) + divider_role = interaction.guild.get_role(settings.custom_roles.divider_role_id) + await role.edit(position=divider_role.position + 1) + record = await CustomRole.ensure_exists( guild_id=interaction.guild.id, user_id=interaction.user.id, diff --git a/example.env b/example.env index d488426d..ea729670 100644 --- a/example.env +++ b/example.env @@ -70,3 +70,4 @@ TIMATHON__PARTICIPANT_ROLE_ID=0 # --- Custom Roles CUSTOM_ROLES__LOG_CHANNEL_ID = 0 +CUSTOM_ROLES__DIVIDER_ROLE_ID = 0 From 0ac4edae60ea0c8a5ddde35d85186defb2b933fe Mon Sep 17 00:00:00 2001 From: Sarthak singh Date: Wed, 8 Nov 2023 19:51:30 +0530 Subject: [PATCH 8/9] bug fix: limiting maximum length of role name to 100 --- bot/extensions/custom_roles/commands.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/bot/extensions/custom_roles/commands.py b/bot/extensions/custom_roles/commands.py index f562e3ed..d0e047ec 100644 --- a/bot/extensions/custom_roles/commands.py +++ b/bot/extensions/custom_roles/commands.py @@ -31,7 +31,9 @@ def role_embed(heading: str, user: discord.Member, role: discord.Role): @app_commands.command() @app_commands.default_permissions(administrator=True) @app_commands.describe(name="New name", color="New color") - async def myrole(self, interaction: core.InteractionType, name: str = None, color: str = None): + async def myrole( + self, interaction: core.InteractionType, name: app_commands.Range[str, 2, 100] | None, color: str = None + ): """Manage your custom role""" if color is not None: try: @@ -39,11 +41,6 @@ async def myrole(self, interaction: core.InteractionType, name: str = None, colo except commands.BadColourArgument as e: return await interaction.response.send_message(str(e), ephemeral=True) - if len(name) > 100: - return await interaction.response.send_message( - "Role name can not be more than 100 characters.", ephemeral=True - ) - query = "SELECT * FROM custom_roles WHERE guild_id = $1 AND user_id = $2" before = await CustomRole.fetchrow(query, interaction.guild.id, interaction.user.id) From aab3c6a5e52d76727dbaa208f726f4e91269a28e Mon Sep 17 00:00:00 2001 From: Sarthak singh Date: Thu, 9 Nov 2023 17:52:10 +0530 Subject: [PATCH 9/9] if the data passed is equal to the previous role data, then no updates are made --- bot/extensions/custom_roles/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/extensions/custom_roles/commands.py b/bot/extensions/custom_roles/commands.py index d0e047ec..e6b6abab 100644 --- a/bot/extensions/custom_roles/commands.py +++ b/bot/extensions/custom_roles/commands.py @@ -78,7 +78,7 @@ async def myrole( role = interaction.guild.get_role(before.role_id) # Return role information if no parameter is passed - if not name and not color: + if (name == before.name or name is None) and (color.value == before.color or color is None): return await interaction.response.send_message( embed=self.role_embed("Custom Role for", interaction.user, interaction.guild.get_role(before.role_id)), ephemeral=True,