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
Some checks are pending
Run pre-commit / Run pre-commit (push) Waiting to run
This commit is contained in:
parent
24aa1e27c9
commit
719abfbc97
2 changed files with 115 additions and 32 deletions
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue