feat: Enhance role management with auto-creation of roles from presets and owner checks
This commit is contained in:
parent
f1364be0e5
commit
05433c0bb8
@ -16,6 +16,10 @@ from api_service.api_models import (
|
||||
GuildRoleCategoryConfig, UserCustomColorRole
|
||||
)
|
||||
|
||||
async def is_owner_check(interaction: discord.Interaction) -> bool:
|
||||
"""Checks if the interacting user is the bot owner."""
|
||||
return interaction.user.id == interaction.client.owner_id
|
||||
|
||||
# For color name validation
|
||||
try:
|
||||
from matplotlib.colors import is_color_like, to_rgb, XKCD_COLORS
|
||||
@ -398,13 +402,50 @@ class RoleSelectorCog(commands.Cog):
|
||||
await interaction.response.send_message(f"A category based on preset '{final_name}' already exists.", ephemeral=True)
|
||||
return
|
||||
|
||||
# For auto-creating roles from preset
|
||||
if not interaction.guild.me.guild_permissions.manage_roles:
|
||||
await interaction.response.send_message("I need 'Manage Roles' permission to create roles from the preset.", ephemeral=True)
|
||||
return
|
||||
|
||||
# Define color map locally for this command, similar to init_defaults
|
||||
color_map_for_creation = {
|
||||
"Red": discord.Color.red(), "Blue": discord.Color.blue(), "Green": discord.Color.green(),
|
||||
"Yellow": discord.Color.gold(), "Purple": discord.Color.purple(), "Orange": discord.Color.orange(),
|
||||
"Pink": discord.Color.fuchsia(), "Black": discord.Color(0x010101), "White": discord.Color(0xFEFEFE)
|
||||
}
|
||||
|
||||
# Defer if not already, as role creation can take time
|
||||
if not interaction.response.is_done():
|
||||
await interaction.response.defer(ephemeral=True, thinking=True)
|
||||
|
||||
created_roles_count = 0
|
||||
for preset_role_option in preset.roles:
|
||||
role_in_guild = interaction.guild.get_role(int(preset_role_option.role_id))
|
||||
if not role_in_guild:
|
||||
# Using followup for potentially multiple messages
|
||||
await interaction.followup.send(f"Warning: Role '{preset_role_option.name}' (ID: {preset_role_option.role_id}) from preset not found in this server. Skipping.", ephemeral=True)
|
||||
continue
|
||||
roles_to_add.append(GuildRole(role_id=str(role_in_guild.id), name=role_in_guild.name, emoji=preset_role_option.emoji))
|
||||
# Check if role with this NAME exists in the current guild
|
||||
existing_role_in_guild = discord.utils.get(interaction.guild.roles, name=preset_role_option.name)
|
||||
|
||||
if existing_role_in_guild:
|
||||
roles_to_add.append(GuildRole(role_id=str(existing_role_in_guild.id), name=existing_role_in_guild.name, emoji=preset_role_option.emoji))
|
||||
else:
|
||||
# Role does not exist by name, create it
|
||||
role_color = discord.Color.default()
|
||||
if preset.name.lower() == "colors" and preset_role_option.name in color_map_for_creation:
|
||||
role_color = color_map_for_creation[preset_role_option.name]
|
||||
|
||||
try:
|
||||
newly_created_role = await interaction.guild.create_role(
|
||||
name=preset_role_option.name,
|
||||
color=role_color,
|
||||
permissions=discord.Permissions.none(), # Basic permissions
|
||||
reason=f"Auto-created for preset '{preset.name}' by {interaction.user}"
|
||||
)
|
||||
roles_to_add.append(GuildRole(role_id=str(newly_created_role.id), name=newly_created_role.name, emoji=preset_role_option.emoji))
|
||||
created_roles_count += 1
|
||||
except discord.Forbidden:
|
||||
await interaction.followup.send(f"I lack permission to create the role '{preset_role_option.name}'. Skipping.", ephemeral=True)
|
||||
continue # Skip this role
|
||||
except discord.HTTPException as e:
|
||||
await interaction.followup.send(f"Failed to create role '{preset_role_option.name}': {e}. Skipping.", ephemeral=True)
|
||||
continue # Skip this role
|
||||
|
||||
final_description = preset.description
|
||||
final_max_selectable = preset.max_selectable
|
||||
@ -483,7 +524,7 @@ class RoleSelectorCog(commands.Cog):
|
||||
await interaction.response.send_message(embed=embed, ephemeral=False) # Make it visible
|
||||
|
||||
@roleselect_group.command(name="listpresets", description="Lists all available global role category presets.")
|
||||
@app_commands.checks.is_owner() # Presets are global, so owner only
|
||||
@app_commands.check(is_owner_check) # Presets are global, so owner only
|
||||
async def roleselect_listpresets(self, interaction: discord.Interaction):
|
||||
presets = db.get_all_role_category_presets()
|
||||
if not presets:
|
||||
@ -641,7 +682,7 @@ class RoleSelectorCog(commands.Cog):
|
||||
|
||||
# Role Preset Commands (Owner Only)
|
||||
@rolepreset_group.command(name="add", description="Creates a new global role category preset.")
|
||||
@app_commands.checks.is_owner()
|
||||
@app_commands.check(is_owner_check)
|
||||
@app_commands.describe(
|
||||
preset_id="A unique ID for this preset (e.g., 'color_roles', 'region_roles').",
|
||||
name="The display name for this preset.",
|
||||
@ -661,7 +702,7 @@ class RoleSelectorCog(commands.Cog):
|
||||
await interaction.response.send_message(f"Preset '{name}' (ID: {preset_id}) created. Add roles with `/rolepreset addrole`.", ephemeral=True)
|
||||
|
||||
@rolepreset_group.command(name="remove", description="Removes a global role category preset.")
|
||||
@app_commands.checks.is_owner()
|
||||
@app_commands.check(is_owner_check)
|
||||
@app_commands.describe(preset_id="The ID of the preset to remove.")
|
||||
async def rolepreset_remove(self, interaction: discord.Interaction, preset_id: str):
|
||||
if not db.get_role_category_preset(preset_id):
|
||||
@ -671,7 +712,7 @@ class RoleSelectorCog(commands.Cog):
|
||||
await interaction.response.send_message(f"Preset ID '{preset_id}' removed.", ephemeral=True)
|
||||
|
||||
@rolepreset_group.command(name="addrole", description="Adds a role (by ID or name) to a global preset.")
|
||||
@app_commands.checks.is_owner()
|
||||
@app_commands.check(is_owner_check)
|
||||
@app_commands.describe(
|
||||
preset_id="The ID of the preset to add the role to.",
|
||||
role_name_or_id="The name or ID of the role to add. The first matching role found across all servers the bot is in will be used.",
|
||||
@ -717,7 +758,7 @@ class RoleSelectorCog(commands.Cog):
|
||||
await interaction.response.send_message(f"Role '{role_display_name}' (ID: {target_role.id}) added to preset '{preset.name}'.", ephemeral=True)
|
||||
|
||||
@rolepreset_group.command(name="removerole", description="Removes a role (by ID or name) from a global preset.")
|
||||
@app_commands.checks.is_owner()
|
||||
@app_commands.check(is_owner_check)
|
||||
@app_commands.describe(
|
||||
preset_id="The ID of the preset to remove the role from.",
|
||||
role_id_or_name="The ID or name of the role to remove from the preset."
|
||||
@ -748,11 +789,105 @@ class RoleSelectorCog(commands.Cog):
|
||||
else:
|
||||
await interaction.response.send_message(f"Role matching '{role_id_or_name}' not found in preset '{preset.name}'.", ephemeral=True)
|
||||
|
||||
@rolepreset_group.command(name="init_defaults", description="Initializes default global presets based on role_creator_cog structure.")
|
||||
@app_commands.check(is_owner_check)
|
||||
async def rolepreset_init_defaults(self, interaction: discord.Interaction):
|
||||
await interaction.response.defer(ephemeral=True, thinking=True)
|
||||
|
||||
# Definitions from role_creator_cog.py
|
||||
color_map_creator = {
|
||||
"Red": discord.Color.red(), "Blue": discord.Color.blue(), "Green": discord.Color.green(),
|
||||
"Yellow": discord.Color.gold(), "Purple": discord.Color.purple(), "Orange": discord.Color.orange(),
|
||||
"Pink": discord.Color.fuchsia(), "Black": discord.Color(0x010101), "White": discord.Color(0xFEFEFE)
|
||||
}
|
||||
role_categories_creator = {
|
||||
"Colors": {"roles": ["Red", "Blue", "Green", "Yellow", "Purple", "Orange", "Pink", "Black", "White"], "max": 1, "desc": "Choose your favorite color role."},
|
||||
"Regions": {"roles": ["NA East", "NA West", "EU", "Asia", "Oceania", "South America"], "max": 1, "desc": "Select your region."},
|
||||
"Pronouns": {"roles": ["He/Him", "She/Her", "They/Them", "Ask Pronouns"], "max": 4, "desc": "Select your pronoun roles."},
|
||||
"Interests": {"roles": ["Art", "Music", "Movies", "Books", "Technology", "Science", "History", "Food", "Programming", "Anime", "Photography", "Travel", "Writing", "Cooking", "Fitness", "Nature", "Gaming", "Philosophy", "Psychology", "Design", "Machine Learning", "Cryptocurrency", "Astronomy", "Mythology", "Languages", "Architecture", "DIY Projects", "Hiking", "Streaming", "Virtual Reality", "Coding Challenges", "Board Games", "Meditation", "Urban Exploration", "Tattoo Art", "Comics", "Robotics", "3D Modeling", "Podcasts"], "max": 16, "desc": "Select your interests."},
|
||||
"Gaming Platforms": {"roles": ["PC", "PlayStation", "Xbox", "Nintendo Switch", "Mobile"], "max": 5, "desc": "Select your gaming platforms."},
|
||||
"Favorite Vocaloids": {"roles": ["Hatsune Miku", "Kasane Teto", "Akita Neru", "Kagamine Rin", "Kagamine Len", "Megurine Luka", "Kaito", "Meiko", "Gumi", "Kaai Yuki", "Adachi Rei"], "max": 10, "desc": "Select your favorite Vocaloids."},
|
||||
"Notifications": {"roles": ["Announcements"], "max": 1, "desc": "Opt-in for announcements."}
|
||||
}
|
||||
|
||||
created_presets = 0
|
||||
skipped_presets = 0
|
||||
preset_details_msg = ""
|
||||
|
||||
for idx, (category_name, cat_details) in enumerate(role_categories_creator.items()):
|
||||
preset_id = f"default_{category_name.lower().replace(' ', '_')}"
|
||||
|
||||
if db.get_role_category_preset(preset_id):
|
||||
preset_details_msg += f"Skipped: Preset '{category_name}' (ID: {preset_id}) already exists.\n"
|
||||
skipped_presets += 1
|
||||
continue
|
||||
|
||||
role_options_for_preset: List[RoleOption] = []
|
||||
for role_name_in_creator in cat_details["roles"]:
|
||||
# Find a canonical role ID from any guild for this role name
|
||||
# This is a bit naive as role names might not be unique or exist
|
||||
# The `/rolepreset addrole` command does this search more robustly.
|
||||
# For init_defaults, we'll store the name and expect `/roleselect addcategory` to handle creation.
|
||||
# The RoleOption model requires a role_id. We can use a placeholder or a convention.
|
||||
# For now, let's use the role name as a placeholder for role_id if no actual ID is found,
|
||||
# or better, skip if no role is found to ensure preset integrity.
|
||||
# The user's request was to use what's in role_creator_cog.
|
||||
# The role_creator_cog *creates* these roles.
|
||||
# The preset system should ideally reference existing roles or define names for creation.
|
||||
|
||||
# Let's simplify: the preset will store the *name*. The guild-specific setup will create it.
|
||||
# The RoleOption model has role_id and name. For presets, role_id might be less critical
|
||||
# if the expectation is that the guild-level command creates roles by name.
|
||||
# However, the current `/rolepreset addrole` finds an existing role ID.
|
||||
# To be consistent, `init_defaults` should also try to find an ID.
|
||||
|
||||
found_role_for_option: Optional[discord.Role] = None
|
||||
for g in self.bot.guilds:
|
||||
for r in g.roles:
|
||||
if r.name.lower() == role_name_in_creator.lower():
|
||||
found_role_for_option = r
|
||||
break
|
||||
if found_role_for_option:
|
||||
break
|
||||
|
||||
if found_role_for_option:
|
||||
role_options_for_preset.append(RoleOption(
|
||||
role_id=str(found_role_for_option.id), # Use ID of first found role
|
||||
name=role_name_in_creator, # Use the canonical name from creator_cog
|
||||
emoji=None # No emojis in role_creator_cog
|
||||
))
|
||||
else:
|
||||
# If no role found across all guilds, we can't create a valid RoleOption for the preset.
|
||||
# We could store the name as a placeholder, but this deviates from RoleOption model.
|
||||
# For now, we'll log this and skip adding this specific role to the preset.
|
||||
# The owner can add it manually later if needed.
|
||||
preset_details_msg += f"Warning: Role '{role_name_in_creator}' for preset '{category_name}' not found in any guild. It won't be added to the preset.\n"
|
||||
|
||||
|
||||
new_preset = RoleCategoryPreset(
|
||||
id=preset_id,
|
||||
name=category_name,
|
||||
description=cat_details["desc"],
|
||||
roles=role_options_for_preset,
|
||||
max_selectable=cat_details["max"],
|
||||
display_order=idx
|
||||
)
|
||||
db.save_role_category_preset(new_preset)
|
||||
created_presets += 1
|
||||
preset_details_msg += f"Created: Preset '{category_name}' (ID: {preset_id}) with {len(role_options_for_preset)} role options.\n"
|
||||
|
||||
final_summary = f"Default preset initialization complete.\nCreated: {created_presets}\nSkipped: {skipped_presets}\n\nDetails:\n{preset_details_msg}"
|
||||
await interaction.followup.send(final_summary, ephemeral=True)
|
||||
|
||||
|
||||
# Deprecated commands are removed as they are not slash commands and functionality is covered
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
cog = RoleSelectorCog(bot)
|
||||
await bot.add_cog(cog)
|
||||
await bot.tree.sync() # Sync slash commands
|
||||
print("RoleSelectorCog loaded. Persistent views will be registered once the bot is ready. Slash commands synced.")
|
||||
# Syncing should ideally happen once after all cogs are loaded, e.g., in main.py or a central setup.
|
||||
# If this cog is reloaded, syncing here might be okay.
|
||||
# For now, let's assume global sync happens elsewhere or is handled by bot.setup_hook if it calls tree.sync
|
||||
# await bot.tree.sync()
|
||||
print("RoleSelectorCog loaded. Persistent views will be registered. Ensure slash commands are synced globally if needed.")
|
||||
|
Loading…
x
Reference in New Issue
Block a user