Implement interactive leaderboard navigation in Leaderboard cog. Introduce a new LeaderboardView class for paginated display of leaderboard entries, enhancing user experience with button controls for navigation. Update leaderboard command to utilize embeds for better formatting and clarity, and improve user feedback for points checking.
Some checks are pending
Run pre-commit / Run pre-commit (push) Waiting to run

This commit is contained in:
Valerie 2025-05-26 04:11:47 -04:00
parent 24aa1e27c9
commit 719abfbc97
2 changed files with 115 additions and 32 deletions

View file

@ -1,14 +1,75 @@
from redbot.core import commands, Config from redbot.core import commands, Config
from redbot.core.bot import Red from redbot.core.bot import Red
from redbot.core.utils.chat_formatting import box, pagify from redbot.core.utils.chat_formatting import box, pagify, humanize_number
import discord
from discord.ui import Button, View
import aiohttp import aiohttp
import asyncio import asyncio
import logging import logging
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Dict, Optional from typing import Dict, Optional, List
log = logging.getLogger("red.ruby.leaderboard") log = logging.getLogger("red.ruby.leaderboard")
class LeaderboardView(View):
def __init__(self, cog, ctx, pages: List[discord.Embed], timeout: float = 180.0):
super().__init__(timeout=timeout)
self.cog = cog
self.ctx = ctx
self.pages = pages
self.current_page = 0
self.message = None
# Add buttons
self.first_page.disabled = True
self.prev_page.disabled = True
if len(self.pages) == 1:
self.next_page.disabled = True
self.last_page.disabled = True
@discord.ui.button(label="", style=discord.ButtonStyle.gray)
async def first_page(self, interaction: discord.Interaction, button: discord.ui.Button):
self.current_page = 0
await self.update_page(interaction)
@discord.ui.button(label="Previous", style=discord.ButtonStyle.blurple)
async def prev_page(self, interaction: discord.Interaction, button: discord.ui.Button):
self.current_page = max(0, self.current_page - 1)
await self.update_page(interaction)
@discord.ui.button(label="Next", style=discord.ButtonStyle.blurple)
async def next_page(self, interaction: discord.Interaction, button: discord.ui.Button):
self.current_page = min(len(self.pages) - 1, self.current_page + 1)
await self.update_page(interaction)
@discord.ui.button(label="", style=discord.ButtonStyle.gray)
async def last_page(self, interaction: discord.Interaction, button: discord.ui.Button):
self.current_page = len(self.pages) - 1
await self.update_page(interaction)
async def update_page(self, interaction: discord.Interaction):
# Update button states
self.first_page.disabled = self.current_page == 0
self.prev_page.disabled = self.current_page == 0
self.next_page.disabled = self.current_page == len(self.pages) - 1
self.last_page.disabled = self.current_page == len(self.pages) - 1
await interaction.response.edit_message(embed=self.pages[self.current_page], view=self)
async def interaction_check(self, interaction: discord.Interaction) -> bool:
if interaction.user.id == self.ctx.author.id:
return True
await interaction.response.send_message("This menu is not for you!", ephemeral=True)
return False
async def on_timeout(self):
try:
for item in self.children:
item.disabled = True
await self.message.edit(view=self)
except discord.HTTPException:
pass
class Leaderboard(commands.Cog): class Leaderboard(commands.Cog):
"""Global leaderboard system for Ruby.""" """Global leaderboard system for Ruby."""
@ -134,8 +195,7 @@ class Leaderboard(commands.Cog):
async def globalboard(self, ctx: commands.Context): async def globalboard(self, ctx: commands.Context):
"""Global leaderboard commands.""" """Global leaderboard commands."""
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
# Don't send help here, just show the base command response await self.show_leaderboard(ctx)
await ctx.send("Use `!help globalboard` to see available commands.")
@globalboard.command(name="show") @globalboard.command(name="show")
async def show_leaderboard(self, ctx: commands.Context, page: int = 1): async def show_leaderboard(self, ctx: commands.Context, page: int = 1):
@ -152,34 +212,39 @@ class Leaderboard(commands.Cog):
return await ctx.send("The leaderboard is currently empty!") return await ctx.send("The leaderboard is currently empty!")
items_per_page = 10 items_per_page = 10
pages = [leaderboard_data[i:i + items_per_page] chunks = [leaderboard_data[i:i + items_per_page]
for i in range(0, len(leaderboard_data), items_per_page)] for i in range(0, len(leaderboard_data), items_per_page)]
if not pages: # No data after pagination if not chunks: # No data after pagination
return await ctx.send("The leaderboard is currently empty!") return await ctx.send("The leaderboard is currently empty!")
embeds = []
for page_num, entries in enumerate(chunks, 1):
embed = discord.Embed(
title="🏆 Global Leaderboard",
color=await ctx.embed_color()
)
if not 1 <= page <= len(pages): # Format leaderboard entries
return await ctx.send(f"Invalid page number. Please choose between 1 and {len(pages)}.") description = []
start_pos = (page_num - 1) * items_per_page
entries = pages[page - 1]
# Format leaderboard
lines = []
start_pos = (page - 1) * items_per_page
for i, entry in enumerate(entries, start=start_pos + 1):
username = entry.get("username", "Unknown User")
points = entry.get("points", 0)
lines.append(f"{i}. {username}: {points:,} points")
if not lines: # No valid entries
return await ctx.send("No valid leaderboard entries found.")
header = f"🏆 Global Leaderboard (Page {page}/{len(pages)})" for i, entry in enumerate(entries, start=start_pos + 1):
footer = f"Use {ctx.prefix}globalboard show <page> to view other pages" username = entry.get("username", "Unknown User")
user_id = entry.get("userId", "0")
content = box("\n".join([header, *lines, "", footer]), lang="md") points = entry.get("points", 0)
await ctx.send(content)
# Format each entry with position, name, points, and user ID
description.append(
f"`{i}.` <@{user_id}> • **{humanize_number(points)}** points"
)
embed.description = "\n".join(description)
embed.set_footer(text=f"Page {page_num}/{len(chunks)} • Total Users: {len(leaderboard_data)}")
embeds.append(embed)
view = LeaderboardView(self, ctx, embeds)
view.message = await ctx.send(embed=embeds[0], view=view)
@globalboard.command(name="points") @globalboard.command(name="points")
async def check_points(self, ctx: commands.Context, member: commands.MemberConverter = None): async def check_points(self, ctx: commands.Context, member: commands.MemberConverter = None):
@ -203,12 +268,28 @@ class Leaderboard(commands.Cog):
if entry.get("userId") == str(member.id)), if entry.get("userId") == str(member.id)),
None None
) )
await ctx.send(
f"🏆 **{member.display_name}** has **{user_data.get('points', 0):,}** points " embed = discord.Embed(
f"(Rank: #{rank:,})" title="🏆 Global Points",
color=await ctx.embed_color()
) )
embed.description = (
f"**User:** <@{member.id}>\n"
f"**Points:** {humanize_number(user_data.get('points', 0))}\n"
f"**Rank:** #{humanize_number(rank)}\n"
f"**ID:** {member.id}"
)
embed.set_thumbnail(url=member.display_avatar.url)
await ctx.send(embed=embed)
else: else:
await ctx.send(f"**{member.display_name}** has no points yet!") embed = discord.Embed(
title="No Points Found",
description=f"<@{member.id}> has no points yet!",
color=await ctx.embed_color()
)
embed.set_footer(text=f"ID: {member.id}")
await ctx.send(embed=embed)
@commands.Cog.listener() @commands.Cog.listener()
async def on_message(self, message): async def on_message(self, message):

View file

@ -175,7 +175,9 @@ class ProfileFormatting(MixinMeta):
# Add server score/rank info # Add server score/rank info
if stat is not None: if stat is not None:
description.append(f"Server Score: **{humanize_number(current_xp)}** (Rank #{stat})") description.append(f"Server Score: **{humanize_number(current_xp)}** XP")
if isinstance(stat, dict) and "position" in stat:
description.append(f"Rank: **#{humanize_number(stat['position'])}**")
# Add level progress bar # Add level progress bar
progress_perc = (progress / current_diff) * 100 progress_perc = (progress / current_diff) * 100