514 lines
30 KiB
Python
514 lines
30 KiB
Python
import discord
|
|
from discord.ext import commands
|
|
from discord.ui import View, Select, select
|
|
import json
|
|
import os
|
|
from typing import List, Dict, Optional, Set, Tuple
|
|
import asyncio # Added for sleep
|
|
|
|
# Role structure expected (based on role_creator_cog)
|
|
# Using original category names from role_creator_cog as keys
|
|
EXPECTED_ROLES: Dict[str, List[str]] = {
|
|
"Colors": ["Red", "Blue", "Green", "Yellow", "Purple", "Orange", "Pink", "Black", "White"],
|
|
"Regions": ["NA East", "NA West", "EU", "Asia", "Oceania", "South America"],
|
|
"Pronouns": ["He/Him", "She/Her", "They/Them", "Ask Pronouns"],
|
|
"Interests": ["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"],
|
|
"Gaming Platforms": ["PC", "PlayStation", "Xbox", "Nintendo Switch", "Mobile"],
|
|
"Favorite Vocaloids": ["Hatsune Miku", "Kasane Teto", "Akita Neru", "Kagamine Rin", "Kagamine Len", "Megurine Luka", "Kaito", "Meiko", "Gumi", "Kaai Yuki"],
|
|
"Notifications": ["Announcements"]
|
|
}
|
|
|
|
# Mapping creator categories to selector categories (for single-choice logic etc.)
|
|
# and providing display names/embed titles
|
|
CATEGORY_DETAILS = {
|
|
"Colors": {"selector_category": "color", "title": "🎨 Color Roles", "description": "Choose your favorite color role.", "color": discord.Color.green(), "max_values": 1},
|
|
"Regions": {"selector_category": "region", "title": "🌍 Region Roles", "description": "Select your region.", "color": discord.Color.orange(), "max_values": 1},
|
|
"Pronouns": {"selector_category": "name", "title": "📛 Pronoun Roles", "description": "Select your pronoun roles.", "color": discord.Color.blue(), "max_values": 4}, # Allow multiple pronouns
|
|
"Interests": {"selector_category": "interests", "title": "💡 Interests", "description": "Select your interests.", "color": discord.Color.purple(), "max_values": 16}, # Allow multiple (Increased max_values again)
|
|
"Gaming Platforms": {"selector_category": "gaming", "title": "🎮 Gaming Platforms", "description": "Select your gaming platforms.", "color": discord.Color.dark_grey(), "max_values": 5}, # Allow multiple
|
|
"Favorite Vocaloids": {"selector_category": "vocaloid", "title": "🎤 Favorite Vocaloids", "description": "Select your favorite Vocaloids.", "color": discord.Color.teal(), "max_values": 10}, # Allow multiple
|
|
"Notifications": {"selector_category": "notifications", "title": "🔔 Notifications", "description": "Opt-in for notifications.", "color": discord.Color.light_grey(), "max_values": 1} # Allow multiple (or single if only one role)
|
|
}
|
|
|
|
# --- Persistent View Definition ---
|
|
class RoleSelectorView(View):
|
|
def __init__(self, category_roles: List[discord.Role], selector_category_name: str, max_values: int = 1):
|
|
super().__init__(timeout=None)
|
|
self.category_role_ids: Set[int] = {role.id for role in category_roles}
|
|
self.selector_category_name = selector_category_name
|
|
self.custom_id = f"persistent_role_select_view_{selector_category_name}"
|
|
self.select_chunk_map: Dict[str, Set[int]] = {} # Map custom_id to role IDs in that chunk
|
|
|
|
# Split roles into chunks of 25 for multiple select menus if needed
|
|
self.role_chunks = [category_roles[i:i + 25] for i in range(0, len(category_roles), 25)]
|
|
num_chunks = len(self.role_chunks)
|
|
|
|
# Ensure total max_values doesn't exceed the total number of roles
|
|
total_max_values = min(max_values, len(category_roles))
|
|
# For multi-select, min_values is typically 0 unless explicitly required otherwise
|
|
# For single-select categories, min_values should be 0 to allow deselecting by choosing nothing
|
|
# Note: Discord UI might enforce min_values=1 if max_values=1. Let's keep min_values=0 for flexibility.
|
|
actual_min_values = 0
|
|
|
|
for i, chunk in enumerate(self.role_chunks):
|
|
options = [discord.SelectOption(label=role.name, value=str(role.id)) for role in chunk]
|
|
chunk_role_ids = {role.id for role in chunk}
|
|
if not options:
|
|
continue
|
|
|
|
# Determine max_values for this specific select menu
|
|
# If multiple selects, allow selecting up to total_max_values across all of them.
|
|
# Each individual select menu still has a max_values limit of 25.
|
|
chunk_max_values = min(total_max_values, len(options)) # Allow selecting up to the total allowed, but capped by options in this chunk
|
|
|
|
placeholder = f"Select {selector_category_name} role(s)..."
|
|
if num_chunks > 1:
|
|
placeholder = f"Select {selector_category_name} role(s) ({i+1}/{num_chunks})..."
|
|
|
|
# Custom ID needs to be unique per select menu but linkable to the category
|
|
select_custom_id = f"role_select_dropdown_{selector_category_name}_{i}"
|
|
self.select_chunk_map[select_custom_id] = chunk_role_ids # Store mapping
|
|
|
|
select_component = Select(
|
|
placeholder=placeholder,
|
|
min_values=actual_min_values, # Allow selecting zero from any individual dropdown
|
|
max_values=chunk_max_values, # Max selectable from *this* dropdown
|
|
options=options,
|
|
custom_id=select_custom_id
|
|
)
|
|
select_component.callback = self.select_callback
|
|
self.add_item(select_component)
|
|
|
|
async def select_callback(self, interaction: discord.Interaction):
|
|
# Callback logic remains largely the same, but needs to handle potentially
|
|
# Callback logic needs to handle selections from one dropdown without
|
|
# affecting selections made via other dropdowns in the same view/category.
|
|
|
|
await interaction.response.defer(ephemeral=True, thinking=True)
|
|
|
|
member = interaction.user
|
|
guild = interaction.guild
|
|
if not isinstance(member, discord.Member) or not guild:
|
|
await interaction.followup.send("This interaction must be used within a server.", ephemeral=True)
|
|
return
|
|
|
|
# --- Identify interacted dropdown and its roles ---
|
|
interacted_custom_id = interaction.data['custom_id']
|
|
# Find the corresponding chunk role IDs using the stored map
|
|
interacted_chunk_role_ids: Set[int] = set()
|
|
if hasattr(self, 'select_chunk_map') and interacted_custom_id in self.select_chunk_map:
|
|
interacted_chunk_role_ids = self.select_chunk_map[interacted_custom_id]
|
|
else:
|
|
# Fallback or error handling if map isn't populated (shouldn't happen in normal flow)
|
|
print(f"Warning: Could not find chunk map for custom_id {interacted_custom_id} in view {self.custom_id}")
|
|
# Attempt to find the component and its options as a less reliable fallback
|
|
for component in self.children:
|
|
if isinstance(component, Select) and component.custom_id == interacted_custom_id:
|
|
interacted_chunk_role_ids = {int(opt.value) for opt in component.options}
|
|
break
|
|
if not interacted_chunk_role_ids:
|
|
await interaction.followup.send("An internal error occurred trying to identify the roles for this dropdown.", ephemeral=True)
|
|
return
|
|
|
|
|
|
selected_values = interaction.data.get('values', [])
|
|
current_selector_category = self.selector_category_name
|
|
|
|
# --- Calculate changes based on interaction ---
|
|
selected_role_ids_from_interaction = {int(value) for value in selected_values}
|
|
|
|
# Get all roles the member currently has within this entire category
|
|
member_category_role_ids = {role.id for role in member.roles if role.id in self.category_role_ids}
|
|
|
|
# Roles to add are those selected in this interaction that the member doesn't already have
|
|
roles_to_add_ids = selected_role_ids_from_interaction - member_category_role_ids
|
|
|
|
# Roles to remove are those from *this specific dropdown's chunk* that the member *had*, but are *no longer selected* in this interaction.
|
|
member_roles_in_interacted_chunk = member_category_role_ids.intersection(interacted_chunk_role_ids)
|
|
roles_to_remove_ids = member_roles_in_interacted_chunk - selected_role_ids_from_interaction
|
|
|
|
# --- Single-choice category handling ---
|
|
is_single_choice = current_selector_category in ['color', 'region', 'notifications'] # Add more if needed
|
|
if is_single_choice and roles_to_add_ids:
|
|
# Ensure only one role is being added
|
|
if len(roles_to_add_ids) > 1:
|
|
await interaction.followup.send(f"Error: Cannot select multiple roles for the '{current_selector_category}' category.", ephemeral=True)
|
|
return # Stop processing
|
|
role_to_add_id = list(roles_to_add_ids)[0]
|
|
|
|
# Identify all other roles in the category the member currently has (excluding the one being added)
|
|
other_member_roles_in_category = member_category_role_ids - {role_to_add_id}
|
|
# Add these other roles to the removal set
|
|
roles_to_remove_ids.update(other_member_roles_in_category)
|
|
# Ensure only the single selected role is in the add set
|
|
roles_to_add_ids = {role_to_add_id}
|
|
|
|
# --- Convert IDs to Role objects ---
|
|
roles_to_add = {guild.get_role(role_id) for role_id in roles_to_add_ids if guild.get_role(role_id)}
|
|
roles_to_remove = {guild.get_role(role_id) for role_id in roles_to_remove_ids if guild.get_role(role_id)}
|
|
|
|
# --- Apply changes and provide feedback ---
|
|
added_names = []
|
|
removed_names = []
|
|
error_messages = []
|
|
|
|
try:
|
|
# Perform removals first
|
|
if roles_to_remove:
|
|
await member.remove_roles(*roles_to_remove, reason=f"Deselected/changed via {current_selector_category} role selector ({interacted_custom_id})")
|
|
removed_names = [r.name for r in roles_to_remove if r]
|
|
# Then perform additions
|
|
if roles_to_add:
|
|
await member.add_roles(*roles_to_add, reason=f"Selected via {current_selector_category} role selector ({interacted_custom_id})")
|
|
added_names = [r.name for r in roles_to_add if r]
|
|
|
|
# Construct feedback message
|
|
if added_names or removed_names:
|
|
feedback = "Your roles have been updated!"
|
|
if added_names:
|
|
feedback += f"\n+ Added: {', '.join(added_names)}"
|
|
if removed_names:
|
|
feedback += f"\n- Removed: {', '.join(removed_names)}"
|
|
elif selected_values: # Roles were selected, but no changes needed (already had them)
|
|
feedback = f"No changes needed for the roles selected in this dropdown."
|
|
else: # No roles selected in this interaction
|
|
if member_roles_in_interacted_chunk: # Had roles from this chunk, now removed
|
|
feedback = f"Roles deselected from this dropdown."
|
|
else: # Had no roles from this chunk, selected none
|
|
feedback = f"No roles selected in this dropdown."
|
|
|
|
|
|
await interaction.followup.send(feedback, ephemeral=True)
|
|
|
|
except discord.Forbidden:
|
|
error_messages.append("I don't have permission to manage roles.")
|
|
except discord.HTTPException as e:
|
|
error_messages.append(f"An error occurred while updating roles: {e}")
|
|
except Exception as e:
|
|
error_messages.append(f"An unexpected error occurred: {e}")
|
|
print(f"Error in role selector callback: {e}")
|
|
|
|
if error_messages:
|
|
await interaction.followup.send("\n".join(error_messages), ephemeral=True)
|
|
|
|
class RoleSelectorCog(commands.Cog):
|
|
def __init__(self, bot):
|
|
self.bot = bot
|
|
self.bot.loop.create_task(self.register_persistent_views())
|
|
|
|
def _get_guild_roles_by_name(self, guild: discord.Guild) -> Dict[str, discord.Role]:
|
|
return {role.name.lower(): role for role in guild.roles}
|
|
|
|
def _get_dynamic_roles_per_category(self, guild: discord.Guild) -> Dict[str, List[discord.Role]]:
|
|
"""Dynamically fetches roles and groups them by the original creator category."""
|
|
guild_roles_map = self._get_guild_roles_by_name(guild)
|
|
categorized_roles: Dict[str, List[discord.Role]] = {cat: [] for cat in EXPECTED_ROLES.keys()}
|
|
missing_roles = []
|
|
|
|
for creator_category, role_names in EXPECTED_ROLES.items():
|
|
for role_name in role_names:
|
|
role = guild_roles_map.get(role_name.lower())
|
|
if role:
|
|
categorized_roles[creator_category].append(role)
|
|
else:
|
|
missing_roles.append(f"'{role_name}' (Category: {creator_category})")
|
|
|
|
if missing_roles:
|
|
print(f"Warning: Roles not found in guild '{guild.name}' ({guild.id}): {', '.join(missing_roles)}")
|
|
|
|
# Sort roles within each category alphabetically by name for consistent order
|
|
for category in categorized_roles:
|
|
categorized_roles[category].sort(key=lambda r: r.name)
|
|
|
|
return categorized_roles
|
|
|
|
async def register_persistent_views(self):
|
|
"""Registers persistent views dynamically for each category."""
|
|
await self.bot.wait_until_ready()
|
|
print("RoleSelectorCog: Registering persistent views...")
|
|
registered_count = 0
|
|
guild_count = 0
|
|
for guild in self.bot.guilds:
|
|
guild_count += 1
|
|
print(f"Processing guild for view registration: {guild.name} ({guild.id})")
|
|
roles_by_creator_category = self._get_dynamic_roles_per_category(guild)
|
|
|
|
for creator_category, role_list in roles_by_creator_category.items():
|
|
if role_list and creator_category in CATEGORY_DETAILS:
|
|
details = CATEGORY_DETAILS[creator_category]
|
|
selector_category = details["selector_category"]
|
|
max_values = details["max_values"]
|
|
try:
|
|
# Register a view for this specific category
|
|
self.bot.add_view(RoleSelectorView(role_list, selector_category, max_values=max_values))
|
|
registered_count += 1
|
|
except Exception as e:
|
|
print(f" - Error registering view for '{creator_category}' in guild {guild.id}: {e}")
|
|
elif not role_list and creator_category in CATEGORY_DETAILS:
|
|
print(f" - No roles found for category '{creator_category}' in guild {guild.id}, skipping view registration.")
|
|
elif creator_category not in CATEGORY_DETAILS:
|
|
print(f" - Warning: Category '{creator_category}' found in EXPECTED_ROLES but not in CATEGORY_DETAILS. Cannot register view.")
|
|
|
|
print(f"RoleSelectorCog: Finished registering {registered_count} persistent views across {guild_count} guild(s).")
|
|
|
|
@commands.command(name="create_role_embeds")
|
|
@commands.is_owner()
|
|
async def create_role_embeds(self, ctx: commands.Context, channel: Optional[discord.TextChannel] = None):
|
|
"""Creates embeds with persistent dropdowns for each role category. (Owner Only)"""
|
|
target_channel = channel or ctx.channel
|
|
guild = ctx.guild
|
|
if not guild:
|
|
await ctx.send("This command can only be used in a server.")
|
|
return
|
|
|
|
initial_message = await ctx.send(f"Fetching roles and creating embeds in {target_channel.mention}...")
|
|
|
|
roles_by_creator_category = self._get_dynamic_roles_per_category(guild)
|
|
|
|
if not any(roles_by_creator_category.values()):
|
|
await initial_message.edit(content="No roles matching the expected names were found in this server. Please run the `create_roles` command first.")
|
|
return
|
|
|
|
sent_messages = 0
|
|
# --- Create Embeds and attach Persistent Views for each category ---
|
|
for creator_category, role_list in roles_by_creator_category.items():
|
|
if role_list and creator_category in CATEGORY_DETAILS:
|
|
details = CATEGORY_DETAILS[creator_category]
|
|
selector_category = details["selector_category"]
|
|
max_values = details["max_values"]
|
|
|
|
embed = discord.Embed(
|
|
title=details["title"],
|
|
description=details["description"],
|
|
color=details["color"]
|
|
)
|
|
# Create a new view instance for sending
|
|
view = RoleSelectorView(role_list, selector_category, max_values=max_values)
|
|
try:
|
|
await target_channel.send(embed=embed, view=view)
|
|
sent_messages += 1
|
|
except discord.Forbidden:
|
|
await ctx.send(f"Error: Missing permissions to send messages in {target_channel.mention}.")
|
|
await initial_message.delete() # Clean up initial message
|
|
return
|
|
except discord.HTTPException as e:
|
|
await ctx.send(f"Error sending embed for '{creator_category}': {e}")
|
|
elif not role_list and creator_category in CATEGORY_DETAILS:
|
|
print(f"Skipping embed for empty category '{creator_category}' in guild {guild.id}")
|
|
|
|
if sent_messages > 0:
|
|
await initial_message.edit(content=f"Created {sent_messages} role selection embed(s) in {target_channel.mention} successfully!")
|
|
else:
|
|
await initial_message.edit(content=f"No roles found for any category to create embeds in {target_channel.mention}.")
|
|
|
|
@commands.command(name="update_role_selectors")
|
|
@commands.is_owner()
|
|
async def update_role_selectors(self, ctx: commands.Context, channel: Optional[discord.TextChannel] = None):
|
|
"""Updates existing role selector messages in a channel with the current roles. (Owner Only)"""
|
|
target_channel = channel or ctx.channel
|
|
guild = ctx.guild
|
|
if not guild:
|
|
await ctx.send("This command must be used within a server.")
|
|
return
|
|
|
|
await ctx.send(f"Starting update process for role selectors in {target_channel.mention}...")
|
|
|
|
roles_by_creator_category = self._get_dynamic_roles_per_category(guild)
|
|
updated_messages = 0
|
|
checked_messages = 0
|
|
errors = 0
|
|
|
|
try:
|
|
async for message in target_channel.history(limit=200): # Check recent messages
|
|
checked_messages += 1
|
|
if message.author == self.bot.user and message.embeds and message.components:
|
|
# Check if the message has a view with a select menu matching our pattern
|
|
view_component = message.components[0] # Assuming the view is the first component row
|
|
if not isinstance(view_component, discord.ActionRow) or not view_component.children:
|
|
continue
|
|
|
|
first_item = view_component.children[0]
|
|
if isinstance(first_item, discord.ui.Select) and first_item.custom_id and first_item.custom_id.startswith("role_select_dropdown_"):
|
|
selector_category_name = first_item.custom_id.split("role_select_dropdown_")[1]
|
|
|
|
# Find the original creator category based on the selector category name
|
|
creator_category = None
|
|
for cat, details in CATEGORY_DETAILS.items():
|
|
if details["selector_category"] == selector_category_name:
|
|
creator_category = cat
|
|
break
|
|
|
|
if creator_category and creator_category in roles_by_creator_category:
|
|
current_roles = roles_by_creator_category[creator_category]
|
|
if not current_roles:
|
|
print(f"Skipping update for {selector_category_name} in message {message.id} - no roles found for this category anymore.")
|
|
continue # Skip if no roles exist for this category now
|
|
|
|
details = CATEGORY_DETAILS[creator_category]
|
|
max_values = details["max_values"]
|
|
|
|
# Create a new view with the updated roles
|
|
new_view = RoleSelectorView(current_roles, selector_category_name, max_values=max_values)
|
|
|
|
# Check if the options or max_values actually changed to avoid unnecessary edits
|
|
select_in_old_message = first_item
|
|
select_in_new_view = new_view.children[0] if new_view.children and isinstance(new_view.children[0], discord.ui.Select) else None
|
|
|
|
if select_in_new_view:
|
|
old_options = {(opt.label, str(opt.value)) for opt in select_in_old_message.options}
|
|
new_options = {(opt.label, str(opt.value)) for opt in select_in_new_view.options}
|
|
old_max_values = select_in_old_message.max_values
|
|
new_max_values = select_in_new_view.max_values
|
|
|
|
if old_options != new_options or old_max_values != new_max_values:
|
|
try:
|
|
await message.edit(view=new_view)
|
|
print(f"Updated role selector for '{selector_category_name}' in message {message.id} (Options changed: {old_options != new_options}, Max values changed: {old_max_values != new_max_values})")
|
|
updated_messages += 1
|
|
except discord.Forbidden:
|
|
print(f"Error: Missing permissions to edit message {message.id} in {target_channel.name}")
|
|
errors += 1
|
|
except discord.HTTPException as e:
|
|
print(f"Error: Failed to edit message {message.id}: {e}")
|
|
errors += 1
|
|
except Exception as e:
|
|
print(f"Unexpected error editing message {message.id}: {e}")
|
|
errors += 1
|
|
else:
|
|
print(f"Skipping update for {selector_category_name} in message {message.id} - options and max_values unchanged.")
|
|
else:
|
|
print(f"Error: Could not find Select component in the newly generated view for category '{selector_category_name}'. Skipping message {message.id}.")
|
|
# else: # Debugging if needed
|
|
# print(f"Message {message.id} has select menu '{selector_category_name}' but no matching category found in current config.")
|
|
# else: # Debugging if needed
|
|
# print(f"Message {message.id} from bot has components, but first item is not a recognized select menu.")
|
|
# else: # Debugging if needed
|
|
# if message.author == self.bot.user:
|
|
# print(f"Message {message.id} from bot skipped (Embeds: {bool(message.embeds)}, Components: {bool(message.components)})")
|
|
|
|
|
|
except discord.Forbidden:
|
|
await ctx.send(f"Error: I don't have permissions to read message history in {target_channel.mention}.")
|
|
return
|
|
except Exception as e:
|
|
await ctx.send(f"An unexpected error occurred during the update process: {e}")
|
|
print(f"Unexpected error in update_role_selectors: {e}")
|
|
return
|
|
|
|
await ctx.send(f"Role selector update process finished for {target_channel.mention}.\n"
|
|
f"Checked: {checked_messages} messages.\n"
|
|
f"Updated: {updated_messages} selectors.\n"
|
|
f"Errors: {errors}")
|
|
|
|
@commands.command(name="recreate_role_embeds")
|
|
@commands.is_owner()
|
|
async def recreate_role_embeds(self, ctx: commands.Context, channel: Optional[discord.TextChannel] = None):
|
|
"""Deletes existing role selectors in a channel and creates new ones. (Owner Only)"""
|
|
target_channel = channel or ctx.channel
|
|
guild = ctx.guild
|
|
if not guild:
|
|
await ctx.send("This command must be used within a server.")
|
|
return
|
|
|
|
initial_status_msg = await ctx.send(f"Starting recreation process for role selectors in {target_channel.mention}...")
|
|
|
|
# --- Step 1: Find and Delete Existing Selectors ---
|
|
deleted_messages = 0
|
|
checked_messages = 0
|
|
deletion_errors = 0
|
|
messages_to_delete = []
|
|
|
|
try:
|
|
await initial_status_msg.edit(content=f"Searching for existing role selectors in {target_channel.mention} (checking last 500 messages)...")
|
|
async for message in target_channel.history(limit=500): # Check a reasonable number of messages
|
|
checked_messages += 1
|
|
# --- MODIFIED: Delete any message sent by the bot ---
|
|
if message.author == self.bot.user:
|
|
messages_to_delete.append(message)
|
|
# --- END MODIFICATION ---
|
|
|
|
if messages_to_delete:
|
|
await initial_status_msg.edit(content=f"Found {len(messages_to_delete)} messages from the bot. Deleting...")
|
|
# Delete messages one by one to handle potential rate limits and errors better
|
|
for msg in messages_to_delete:
|
|
try:
|
|
await msg.delete()
|
|
deleted_messages += 1
|
|
await asyncio.sleep(1) # Add a small delay to avoid rate limits
|
|
except discord.Forbidden:
|
|
print(f"Error: Missing permissions to delete message {msg.id} in {target_channel.name}")
|
|
deletion_errors += 1
|
|
except discord.NotFound:
|
|
print(f"Warning: Message {msg.id} not found (already deleted?).")
|
|
# Don't count as an error, but maybe decrement deleted_messages if needed?
|
|
except discord.HTTPException as e:
|
|
print(f"Error: Failed to delete message {msg.id}: {e}")
|
|
deletion_errors += 1
|
|
except Exception as e:
|
|
print(f"Unexpected error deleting message {msg.id}: {e}")
|
|
deletion_errors += 1
|
|
await initial_status_msg.edit(content=f"Deleted {deleted_messages} messages. Errors during deletion: {deletion_errors}.")
|
|
else:
|
|
await initial_status_msg.edit(content="No existing role selector messages found to delete.")
|
|
|
|
await asyncio.sleep(2) # Brief pause before creating new ones
|
|
|
|
except discord.Forbidden:
|
|
await initial_status_msg.edit(content=f"Error: I don't have permissions to read message history or delete messages in {target_channel.mention}.")
|
|
return
|
|
except Exception as e:
|
|
await initial_status_msg.edit(content=f"An unexpected error occurred during deletion: {e}")
|
|
print(f"Unexpected error in recreate_role_embeds (deletion phase): {e}")
|
|
return
|
|
|
|
# --- Step 2: Create New Embeds (similar to create_role_embeds) ---
|
|
await initial_status_msg.edit(content=f"Fetching roles and creating new embeds in {target_channel.mention}...")
|
|
|
|
roles_by_creator_category = self._get_dynamic_roles_per_category(guild)
|
|
|
|
if not any(roles_by_creator_category.values()):
|
|
await initial_status_msg.edit(content="No roles matching the expected names were found in this server. Cannot create new embeds. Please run the `create_roles` command first.")
|
|
return
|
|
|
|
sent_messages = 0
|
|
creation_errors = 0
|
|
for creator_category, role_list in roles_by_creator_category.items():
|
|
if role_list and creator_category in CATEGORY_DETAILS:
|
|
details = CATEGORY_DETAILS[creator_category]
|
|
selector_category = details["selector_category"]
|
|
max_values = details["max_values"]
|
|
|
|
embed = discord.Embed(
|
|
title=details["title"],
|
|
description=details["description"],
|
|
color=details["color"]
|
|
)
|
|
view = RoleSelectorView(role_list, selector_category, max_values=max_values)
|
|
try:
|
|
await target_channel.send(embed=embed, view=view)
|
|
sent_messages += 1
|
|
await asyncio.sleep(0.5) # Small delay between sends
|
|
except discord.Forbidden:
|
|
await ctx.send(f"Error: Missing permissions to send messages in {target_channel.mention}. Aborting creation.")
|
|
creation_errors += 1
|
|
break # Stop trying if permissions are missing
|
|
except discord.HTTPException as e:
|
|
await ctx.send(f"Error sending embed for '{creator_category}': {e}")
|
|
creation_errors += 1
|
|
except Exception as e:
|
|
print(f"Unexpected error sending embed for '{creator_category}': {e}")
|
|
creation_errors += 1
|
|
|
|
elif not role_list and creator_category in CATEGORY_DETAILS:
|
|
print(f"Skipping new embed for empty category '{creator_category}' in guild {guild.id}")
|
|
|
|
final_message = f"Role selector recreation process finished for {target_channel.mention}.\n" \
|
|
f"Deleted: {deleted_messages} (Errors: {deletion_errors})\n" \
|
|
f"Created: {sent_messages} (Errors: {creation_errors})"
|
|
await initial_status_msg.edit(content=final_message)
|
|
|
|
|
|
async def setup(bot):
|
|
await bot.add_cog(RoleSelectorCog(bot))
|
|
print("RoleSelectorCog loaded. Persistent views will be registered once the bot is ready.")
|