fix: Enhance error handling and logging in LevelingCog UI components to ensure valid construction and prevent None returns

This commit is contained in:
Slipstream 2025-05-31 15:13:56 -06:00
parent d6876f0e8a
commit fa5f39b1d3
Signed by: slipstream
GPG Key ID: 13E498CE010AC6FD

View File

@ -404,10 +404,9 @@ class LevelingCog(commands.Cog):
print("DEBUG: target_member is None in LevelCheckView.__init__")
# Main container for all elements, providing the accent color
main_container = ui.Container(accent_colour=None)
if not main_container:
print("DEBUG: ui.Container returned None in LevelCheckView")
return
main_container = ui.Container(accent_colour=discord.Colour.blue())
if main_container is None:
raise AssertionError("ui.Container returned None in LevelCheckView; ensure accent_colour is valid")
self.add_item(main_container) # Add the main container to the view
# Prepare thumbnail accessory
@ -418,24 +417,31 @@ class LevelingCog(commands.Cog):
# Section to hold the user's name and level/XP, with the thumbnail as accessory
# This section will be added to the main_container
user_info_section = ui.Section(accessory=thumbnail_accessory)
if user_info_section is None:
raise AssertionError("ui.Section returned None in LevelCheckView")
main_container.add_item(user_info_section)
# Add text components to the user_info_section
user_info_section.add_item(ui.TextDisplay(f"**{target_member.display_name}'s Level**"))
user_info_section.add_item(ui.TextDisplay(f"**Level:** {level}\n**XP:** {xp} / {xp_needed}"))
name_display = ui.TextDisplay(f"**{target_member.display_name}'s Level**")
if name_display is None:
raise AssertionError("ui.TextDisplay returned None for name in LevelCheckView")
user_info_section.add_item(name_display)
level_display = ui.TextDisplay(f"**Level:** {level}\n**XP:** {xp} / {xp_needed}")
if level_display is None:
raise AssertionError("ui.TextDisplay returned None for level info in LevelCheckView")
user_info_section.add_item(level_display)
# Add remaining components directly to the main_container
separator = ui.Separator(spacing=discord.SeparatorSpacing.small)
if separator:
main_container.add_item(separator)
else:
print("DEBUG: ui.Separator returned None in LevelCheckView")
if separator is None:
raise AssertionError("ui.Separator returned None in LevelCheckView")
main_container.add_item(separator)
progress_text = ui.TextDisplay(f"**Progress to Level {next_level}:**\n[{bar}] {progress_percent}%")
if progress_text:
main_container.add_item(progress_text)
else:
print("DEBUG: ui.TextDisplay returned None in LevelCheckView")
if progress_text is None:
raise AssertionError("ui.TextDisplay returned None in LevelCheckView")
main_container.add_item(progress_text)
view = LevelCheckView(target, level, xp, xp_needed, next_level, bar, int(progress * 100))
await ctx.send(view=view)
@ -464,16 +470,26 @@ class LevelingCog(commands.Cog):
super().__init__()
# Main container for all elements, providing the accent color
main_container = ui.Container(accent_colour=discord.Color.gold())
main_container = ui.Container(accent_colour=discord.Colour.gold())
if main_container is None:
raise AssertionError("ui.Container returned None in LeaderboardView; ensure accent_colour is valid")
self.add_item(main_container) # Add the main container to the view
main_container.add_item(ui.TextDisplay(f"**{guild_name} Level Leaderboard**"))
title_display = ui.TextDisplay(f"**{guild_name} Level Leaderboard**")
if title_display is None:
raise AssertionError("ui.TextDisplay returned None for title in LeaderboardView")
main_container.add_item(title_display)
sep = ui.Separator(spacing=discord.SeparatorSpacing.small)
if sep:
main_container.add_item(sep)
if sep is None:
raise AssertionError("ui.Separator returned None in LeaderboardView")
main_container.add_item(sep)
if not sorted_leaderboard_data:
main_container.add_item(ui.TextDisplay("The leaderboard is empty!"))
empty_display = ui.TextDisplay("The leaderboard is empty!")
if empty_display is None:
raise AssertionError("ui.TextDisplay returned None for empty message in LeaderboardView")
main_container.add_item(empty_display)
else:
for i, (user_id, data) in enumerate(sorted_leaderboard_data[:10], 1):
member = guild_members_dict.get(user_id)
@ -482,19 +498,35 @@ class LevelingCog(commands.Cog):
# Each user's entry gets its own section and is added to the main_container
user_section = ui.Section(accessory=None)
if user_section is None:
raise AssertionError("ui.Section returned None in LeaderboardView")
main_container.add_item(user_section)
# Add text components to the user_section
user_section.add_item(ui.TextDisplay(f"**{i}. {member.display_name}**"))
user_section.add_item(ui.TextDisplay(f"Level: {data['level']} | XP: {data['xp']}"))
rank_display = ui.TextDisplay(f"**{i}. {member.display_name}**")
if rank_display is None:
raise AssertionError("ui.TextDisplay returned None for rank in LeaderboardView")
user_section.add_item(rank_display)
level_display = ui.TextDisplay(f"Level: {data['level']} | XP: {data['xp']}")
if level_display is None:
raise AssertionError("ui.TextDisplay returned None for level in LeaderboardView")
user_section.add_item(level_display)
# Add separator to the main_container
if i < len(sorted_leaderboard_data[:10]): # not the last row
separator = ui.Separator(spacing=discord.SeparatorSpacing.small)
if separator:
main_container.add_item(separator)
if separator is None:
raise AssertionError("ui.Separator returned None between rows in LeaderboardView")
main_container.add_item(separator)
view = LeaderboardView(ctx.guild.name, sorted_data, guild_members)
# Double-check the view is dispatchable and properly constructed
if view is None:
return await ctx.send("❌ Failed to build leaderboard layout. Please try again.")
# Send the view
await ctx.send(view=view)
@level.command(name="register_role", description="Register a role for a specific level")
@ -552,39 +584,62 @@ class LevelingCog(commands.Cog):
def __init__(self, guild: discord.Guild, level_roles_data: dict):
super().__init__()
main_container = ui.Container(accent_colour=discord.Color.blue())
main_container = ui.Container(accent_colour=discord.Colour.blue())
if main_container is None:
raise AssertionError("ui.Container returned None in ListLevelRolesView; ensure accent_colour is valid")
self.add_item(main_container)
main_container.add_item(ui.TextDisplay(f"**Level Roles for {guild.name}**"))
title_display = ui.TextDisplay(f"**Level Roles for {guild.name}**")
if title_display is None:
raise AssertionError("ui.TextDisplay returned None for title in ListLevelRolesView")
main_container.add_item(title_display)
sep = ui.Separator(spacing=discord.SeparatorSpacing.small)
if sep:
main_container.add_item(sep)
if sep is None:
raise AssertionError("ui.Separator returned None in ListLevelRolesView")
main_container.add_item(sep)
if not level_roles_data: # Should be caught by the check above, but good practice
main_container.add_item(ui.TextDisplay("No level roles are registered for this server."))
empty_display = ui.TextDisplay("No level roles are registered for this server.")
if empty_display is None:
raise AssertionError("ui.TextDisplay returned None for empty message in ListLevelRolesView")
main_container.add_item(empty_display)
return
sorted_roles_items = sorted(level_roles_data.items())
for level, role_data_or_id in sorted_roles_items:
role_section = ui.Section(accessory=None) # Explicitly pass accessory=None
role_section.add_item(ui.TextDisplay(f"**Level {level}:**"))
if role_section is None:
raise AssertionError("ui.Section returned None in ListLevelRolesView")
level_title = ui.TextDisplay(f"**Level {level}:**")
if level_title is None:
raise AssertionError("ui.TextDisplay returned None for level title in ListLevelRolesView")
role_section.add_item(level_title)
if isinstance(role_data_or_id, dict): # Gendered roles
for gender, role_id in role_data_or_id.items():
role = guild.get_role(role_id)
role_name = role.mention if role else f"Unknown Role (ID: {role_id})"
role_section.add_item(ui.TextDisplay(f" - {gender.capitalize()}: {role_name}"))
gender_display = ui.TextDisplay(f" - {gender.capitalize()}: {role_name}")
if gender_display is None:
raise AssertionError("ui.TextDisplay returned None for gender role in ListLevelRolesView")
role_section.add_item(gender_display)
else: # Regular role
role = guild.get_role(role_data_or_id)
role_name = role.mention if role else f"Unknown Role (ID: {role_data_or_id})"
role_section.add_item(ui.TextDisplay(f" {role_name}"))
role_display = ui.TextDisplay(f" {role_name}")
if role_display is None:
raise AssertionError("ui.TextDisplay returned None for regular role in ListLevelRolesView")
role_section.add_item(role_display)
main_container.add_item(role_section)
if level != sorted_roles_items[-1][0]: # Add separator if not the last item
separator = ui.Separator(spacing=discord.SeparatorSpacing.small)
if separator:
main_container.add_item(separator)
if separator is None:
raise AssertionError("ui.Separator returned None between roles in ListLevelRolesView")
main_container.add_item(separator)
view = ListLevelRolesView(ctx.guild, self.level_roles[ctx.guild.id])
@ -744,26 +799,25 @@ class LevelingCog(commands.Cog):
def __init__(self, config_data: dict, prefix: str):
super().__init__()
main_container = ui.Container(accent_colour=discord.Color.blue())
main_container = ui.Container(accent_colour=discord.Colour.blue())
if main_container is None:
raise AssertionError("ui.Container returned None in XPConfigView; ensure accent_colour is valid")
self.add_item(main_container)
title_text = ui.TextDisplay("**XP Configuration Settings**")
if title_text:
main_container.add_item(title_text)
else:
print("DEBUG: ui.TextDisplay returned None in XPConfigView (title)")
if title_text is None:
raise AssertionError("ui.TextDisplay returned None for title in XPConfigView")
main_container.add_item(title_text)
desc_text = ui.TextDisplay("Current XP settings for the leveling system:")
if desc_text:
main_container.add_item(desc_text)
else:
print("DEBUG: ui.TextDisplay returned None in XPConfigView (description)")
if desc_text is None:
raise AssertionError("ui.TextDisplay returned None for description in XPConfigView")
main_container.add_item(desc_text)
separator = ui.Separator(spacing=discord.SeparatorSpacing.small)
if separator:
main_container.add_item(separator)
else:
print("DEBUG: ui.Separator returned None in XPConfigView")
if separator is None:
raise AssertionError("ui.Separator returned None in XPConfigView")
main_container.add_item(separator)
settings_to_display = [
("XP Per Message", str(config_data["xp_per_message"])),
@ -775,20 +829,24 @@ class LevelingCog(commands.Cog):
for name, value_str in settings_to_display:
setting_section = ui.Section(accessory=None) # Explicitly pass accessory=None
setting_section.add_item(ui.TextDisplay(f"**{name}:** {value_str}"))
if setting_section is None:
raise AssertionError("ui.Section returned None in XPConfigView")
setting_display = ui.TextDisplay(f"**{name}:** {value_str}")
if setting_display is None:
raise AssertionError("ui.TextDisplay returned None for setting in XPConfigView")
setting_section.add_item(setting_display)
main_container.add_item(setting_section)
separator = ui.Separator(spacing=discord.SeparatorSpacing.small)
if separator:
main_container.add_item(separator)
else:
print("DEBUG: ui.Separator returned None in XPConfigView (bottom)")
if separator is None:
raise AssertionError("ui.Separator returned None at bottom in XPConfigView")
main_container.add_item(separator)
help_text = ui.TextDisplay(f"Use {prefix}level config <setting> <value> to change a setting")
if help_text:
main_container.add_item(help_text)
else:
print("DEBUG: ui.TextDisplay returned None in XPConfigView (help)")
if help_text is None:
raise AssertionError("ui.TextDisplay returned None for help text in XPConfigView")
main_container.add_item(help_text)
# Attempt to get the prefix
try:
@ -1053,41 +1111,85 @@ class LevelingCog(commands.Cog):
def __init__(self, created_roles_list: list, updated_roles_list: list, has_pronoun_roles_flag: bool):
super().__init__()
main_container = ui.Container(accent_colour=discord.Color.gold())
main_container = ui.Container(accent_colour=discord.Colour.gold())
if main_container is None:
raise AssertionError("ui.Container returned None in MedievalRolesSetupView; ensure accent_colour is valid")
self.add_item(main_container)
main_container.add_item(ui.TextDisplay("**Medieval Level Roles Setup**"))
main_container.add_item(ui.TextDisplay("The following roles have been set up for the medieval leveling system:"))
title_display = ui.TextDisplay("**Medieval Level Roles Setup**")
if title_display is None:
raise AssertionError("ui.TextDisplay returned None for title in MedievalRolesSetupView")
main_container.add_item(title_display)
desc_display = ui.TextDisplay("The following roles have been set up for the medieval leveling system:")
if desc_display is None:
raise AssertionError("ui.TextDisplay returned None for description in MedievalRolesSetupView")
main_container.add_item(desc_display)
sep = ui.Separator(spacing=discord.SeparatorSpacing.small)
if sep:
main_container.add_item(sep)
if sep is None:
raise AssertionError("ui.Separator returned None in MedievalRolesSetupView")
main_container.add_item(sep)
if created_roles_list:
created_section = ui.Section(accessory=None) # Explicitly pass accessory=None
created_section.add_item(ui.TextDisplay("**Created Roles:**"))
if created_section is None:
raise AssertionError("ui.Section returned None for created roles in MedievalRolesSetupView")
created_title = ui.TextDisplay("**Created Roles:**")
if created_title is None:
raise AssertionError("ui.TextDisplay returned None for created roles title in MedievalRolesSetupView")
created_section.add_item(created_title)
# For potentially long lists, join with newline. TextDisplay handles multiline.
created_section.add_item(ui.TextDisplay("\n".join(created_roles_list) if created_roles_list else "None"))
created_list = ui.TextDisplay("\n".join(created_roles_list) if created_roles_list else "None")
if created_list is None:
raise AssertionError("ui.TextDisplay returned None for created roles list in MedievalRolesSetupView")
created_section.add_item(created_list)
main_container.add_item(created_section)
if created_roles_list: # Only add separator if there are created roles
separator = ui.Separator(spacing=discord.SeparatorSpacing.small)
if separator:
main_container.add_item(separator)
if separator is None:
raise AssertionError("ui.Separator returned None after created roles in MedievalRolesSetupView")
main_container.add_item(separator)
if updated_roles_list:
updated_section = ui.Section(accessory=None) # Explicitly pass accessory=None
updated_section.add_item(ui.TextDisplay("**Updated Roles:**"))
updated_section.add_item(ui.TextDisplay("\n".join(updated_roles_list) if updated_roles_list else "None"))
if updated_section is None:
raise AssertionError("ui.Section returned None for updated roles in MedievalRolesSetupView")
updated_title = ui.TextDisplay("**Updated Roles:**")
if updated_title is None:
raise AssertionError("ui.TextDisplay returned None for updated roles title in MedievalRolesSetupView")
updated_section.add_item(updated_title)
updated_list = ui.TextDisplay("\n".join(updated_roles_list) if updated_roles_list else "None")
if updated_list is None:
raise AssertionError("ui.TextDisplay returned None for updated roles list in MedievalRolesSetupView")
updated_section.add_item(updated_list)
main_container.add_item(updated_section)
if updated_roles_list: # Only add separator if there are updated roles
separator = ui.Separator(spacing=discord.SeparatorSpacing.small)
if separator:
main_container.add_item(separator)
if separator is None:
raise AssertionError("ui.Separator returned None after updated roles in MedievalRolesSetupView")
main_container.add_item(separator)
gender_detection_section = ui.Section(accessory=None) # Explicitly pass accessory=None
gender_detection_section.add_item(ui.TextDisplay("**Gender Detection:**"))
if gender_detection_section is None:
raise AssertionError("ui.Section returned None for gender detection in MedievalRolesSetupView")
gender_title = ui.TextDisplay("**Gender Detection:**")
if gender_title is None:
raise AssertionError("ui.TextDisplay returned None for gender detection title in MedievalRolesSetupView")
gender_detection_section.add_item(gender_title)
gender_text = "Gender-specific roles will be assigned based on pronoun roles." if has_pronoun_roles_flag else "No pronoun roles detected. Using default titles."
gender_detection_section.add_item(ui.TextDisplay(gender_text))
gender_desc = ui.TextDisplay(gender_text)
if gender_desc is None:
raise AssertionError("ui.TextDisplay returned None for gender detection description in MedievalRolesSetupView")
gender_detection_section.add_item(gender_desc)
main_container.add_item(gender_detection_section)
view = MedievalRolesSetupView(created_roles, updated_roles, has_pronoun_roles)