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.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 asyncio
import logging
from datetime import datetime, timezone
from typing import Dict, Optional
from typing import Dict, Optional, List
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):
"""Global leaderboard system for Ruby."""
@ -134,8 +195,7 @@ class Leaderboard(commands.Cog):
async def globalboard(self, ctx: commands.Context):
"""Global leaderboard commands."""
if ctx.invoked_subcommand is None:
# Don't send help here, just show the base command response
await ctx.send("Use `!help globalboard` to see available commands.")
await self.show_leaderboard(ctx)
@globalboard.command(name="show")
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!")
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)]
if not pages: # No data after pagination
if not chunks: # No data after pagination
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):
return await ctx.send(f"Invalid page number. Please choose between 1 and {len(pages)}.")
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.")
# Format leaderboard entries
description = []
start_pos = (page_num - 1) * items_per_page
header = f"🏆 Global Leaderboard (Page {page}/{len(pages)})"
footer = f"Use {ctx.prefix}globalboard show <page> to view other pages"
content = box("\n".join([header, *lines, "", footer]), lang="md")
await ctx.send(content)
for i, entry in enumerate(entries, start=start_pos + 1):
username = entry.get("username", "Unknown User")
user_id = entry.get("userId", "0")
points = entry.get("points", 0)
# 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")
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)),
None
)
await ctx.send(
f"🏆 **{member.display_name}** has **{user_data.get('points', 0):,}** points "
f"(Rank: #{rank:,})"
embed = discord.Embed(
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:
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()
async def on_message(self, message):

View file

@ -175,7 +175,9 @@ class ProfileFormatting(MixinMeta):
# Add server score/rank info
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
progress_perc = (progress / current_diff) * 100