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 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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue