Refactor Leaderboard cog to improve session management and error handling. Initialize aiohttp session in setup, enhance admin secret validation, and provide clearer error messages for API interactions. Update leaderboard command responses for better user feedback and handling of empty data scenarios.
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
899bfc7bc0
commit
4b17dfedc3
1 changed files with 74 additions and 22 deletions
|
@ -15,7 +15,7 @@ class Leaderboard(commands.Cog):
|
||||||
def __init__(self, bot: Red):
|
def __init__(self, bot: Red):
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
self.config = Config.get_conf(self, identifier=867530999, force_registration=True)
|
self.config = Config.get_conf(self, identifier=867530999, force_registration=True)
|
||||||
self.session = aiohttp.ClientSession()
|
self.session = None # Initialize in setup
|
||||||
self.api_base_url = "https://ruby.valerie.lol/api"
|
self.api_base_url = "https://ruby.valerie.lol/api"
|
||||||
self.admin_secret = None
|
self.admin_secret = None
|
||||||
self.cache_time = 300 # 5 minutes cache
|
self.cache_time = 300 # 5 minutes cache
|
||||||
|
@ -33,17 +33,29 @@ class Leaderboard(commands.Cog):
|
||||||
self.config.register_guild(**default_guild)
|
self.config.register_guild(**default_guild)
|
||||||
|
|
||||||
def cog_unload(self):
|
def cog_unload(self):
|
||||||
asyncio.create_task(self.session.close())
|
if self.session:
|
||||||
|
asyncio.create_task(self.session.close())
|
||||||
|
|
||||||
async def initialize(self):
|
async def initialize(self):
|
||||||
"""Load the admin secret from bot config."""
|
"""Load the admin secret from bot config and initialize session."""
|
||||||
self.admin_secret = await self.bot.get_shared_api_tokens("ruby_api")
|
self.admin_secret = await self.bot.get_shared_api_tokens("ruby_api")
|
||||||
if not self.admin_secret.get("admin_secret"):
|
if not self.admin_secret.get("admin_secret"):
|
||||||
log.error("No admin secret found. Leaderboard functionality will be limited.")
|
log.error("No admin secret found. Please set it using [p]set api ruby_api admin_secret,<your_secret>")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Initialize aiohttp session
|
||||||
|
if not self.session:
|
||||||
|
self.session = aiohttp.ClientSession()
|
||||||
|
return True
|
||||||
|
|
||||||
async def _get_leaderboard(self) -> Optional[list]:
|
async def _get_leaderboard(self) -> Optional[list]:
|
||||||
"""Fetch the global leaderboard from the API."""
|
"""Fetch the global leaderboard from the API."""
|
||||||
if not self.admin_secret:
|
if not self.admin_secret or not self.admin_secret.get("admin_secret"):
|
||||||
|
log.error("Admin secret not configured")
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not self.session:
|
||||||
|
log.error("Session not initialized")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -59,19 +71,33 @@ class Leaderboard(commands.Cog):
|
||||||
) as resp:
|
) as resp:
|
||||||
if resp.status == 200:
|
if resp.status == 200:
|
||||||
data = await resp.json()
|
data = await resp.json()
|
||||||
self._cache["leaderboard"] = data.get("leaderboard", [])
|
if not isinstance(data, dict) or "leaderboard" not in data:
|
||||||
|
log.error("Invalid API response format")
|
||||||
|
return None
|
||||||
|
self._cache["leaderboard"] = data["leaderboard"]
|
||||||
self._last_update["leaderboard"] = now
|
self._last_update["leaderboard"] = now
|
||||||
return self._cache["leaderboard"]
|
return self._cache["leaderboard"]
|
||||||
else:
|
elif resp.status == 401:
|
||||||
log.error(f"Failed to fetch leaderboard: {resp.status}")
|
log.error("Unauthorized: Invalid admin secret")
|
||||||
return None
|
return None
|
||||||
|
else:
|
||||||
|
log.error(f"Failed to fetch leaderboard: Status {resp.status}")
|
||||||
|
return None
|
||||||
|
except aiohttp.ClientError as e:
|
||||||
|
log.error(f"API connection error: {e}")
|
||||||
|
return None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(f"Error fetching leaderboard: {e}")
|
log.error(f"Unexpected error fetching leaderboard: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def _update_points(self, user_id: str, username: str, points: int) -> bool:
|
async def _update_points(self, user_id: str, username: str, points: int) -> bool:
|
||||||
"""Update a user's points in the global leaderboard."""
|
"""Update a user's points in the global leaderboard."""
|
||||||
if not self.admin_secret:
|
if not self.admin_secret or not self.admin_secret.get("admin_secret"):
|
||||||
|
log.error("Admin secret not configured")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self.session:
|
||||||
|
log.error("Session not initialized")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -87,16 +113,29 @@ class Leaderboard(commands.Cog):
|
||||||
"points": points
|
"points": points
|
||||||
}
|
}
|
||||||
) as resp:
|
) as resp:
|
||||||
return resp.status == 200
|
if resp.status == 200:
|
||||||
|
# Clear cache to ensure fresh data on next fetch
|
||||||
|
self._cache.pop("leaderboard", None)
|
||||||
|
return True
|
||||||
|
elif resp.status == 401:
|
||||||
|
log.error("Unauthorized: Invalid admin secret")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
log.error(f"Failed to update points: Status {resp.status}")
|
||||||
|
return False
|
||||||
|
except aiohttp.ClientError as e:
|
||||||
|
log.error(f"API connection error: {e}")
|
||||||
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(f"Error updating points: {e}")
|
log.error(f"Unexpected error updating points: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@commands.group(name="globalboard", aliases=["glb"])
|
@commands.group(name="globalboard", aliases=["glb"])
|
||||||
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:
|
||||||
await ctx.send_help(ctx.command)
|
# Don't send help here, just show the base command response
|
||||||
|
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):
|
||||||
|
@ -105,12 +144,20 @@ class Leaderboard(commands.Cog):
|
||||||
leaderboard_data = await self._get_leaderboard()
|
leaderboard_data = await self._get_leaderboard()
|
||||||
|
|
||||||
if not leaderboard_data:
|
if not leaderboard_data:
|
||||||
return await ctx.send("Failed to fetch leaderboard data.")
|
if not self.admin_secret or not self.admin_secret.get("admin_secret"):
|
||||||
|
return await ctx.send("Leaderboard is not configured. Please contact the bot administrator.")
|
||||||
|
return await ctx.send("Failed to fetch leaderboard data. Please try again later.")
|
||||||
|
|
||||||
|
if not leaderboard_data: # Empty leaderboard
|
||||||
|
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]
|
pages = [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
|
||||||
|
return await ctx.send("The leaderboard is currently empty!")
|
||||||
|
|
||||||
if not 1 <= page <= len(pages):
|
if not 1 <= page <= len(pages):
|
||||||
return await ctx.send(f"Invalid page number. Please choose between 1 and {len(pages)}.")
|
return await ctx.send(f"Invalid page number. Please choose between 1 and {len(pages)}.")
|
||||||
|
|
||||||
|
@ -121,10 +168,13 @@ class Leaderboard(commands.Cog):
|
||||||
start_pos = (page - 1) * items_per_page
|
start_pos = (page - 1) * items_per_page
|
||||||
|
|
||||||
for i, entry in enumerate(entries, start=start_pos + 1):
|
for i, entry in enumerate(entries, start=start_pos + 1):
|
||||||
username = entry["username"]
|
username = entry.get("username", "Unknown User")
|
||||||
points = entry["points"]
|
points = entry.get("points", 0)
|
||||||
lines.append(f"{i}. {username}: {points:,} points")
|
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)})"
|
header = f"🏆 Global Leaderboard (Page {page}/{len(pages)})"
|
||||||
footer = f"Use {ctx.prefix}globalboard show <page> to view other pages"
|
footer = f"Use {ctx.prefix}globalboard show <page> to view other pages"
|
||||||
|
|
||||||
|
@ -138,21 +188,23 @@ class Leaderboard(commands.Cog):
|
||||||
leaderboard_data = await self._get_leaderboard()
|
leaderboard_data = await self._get_leaderboard()
|
||||||
|
|
||||||
if not leaderboard_data:
|
if not leaderboard_data:
|
||||||
return await ctx.send("Failed to fetch leaderboard data.")
|
if not self.admin_secret or not self.admin_secret.get("admin_secret"):
|
||||||
|
return await ctx.send("Leaderboard is not configured. Please contact the bot administrator.")
|
||||||
|
return await ctx.send("Failed to fetch leaderboard data. Please try again later.")
|
||||||
|
|
||||||
user_data = next(
|
user_data = next(
|
||||||
(entry for entry in leaderboard_data if entry["userId"] == str(member.id)),
|
(entry for entry in leaderboard_data if entry.get("userId") == str(member.id)),
|
||||||
None
|
None
|
||||||
)
|
)
|
||||||
|
|
||||||
if user_data:
|
if user_data:
|
||||||
rank = next(
|
rank = next(
|
||||||
(i for i, entry in enumerate(leaderboard_data, 1)
|
(i for i, entry in enumerate(leaderboard_data, 1)
|
||||||
if entry["userId"] == str(member.id)),
|
if entry.get("userId") == str(member.id)),
|
||||||
None
|
None
|
||||||
)
|
)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
f"🏆 **{member.display_name}** has **{user_data['points']:,}** points "
|
f"🏆 **{member.display_name}** has **{user_data.get('points', 0):,}** points "
|
||||||
f"(Rank: #{rank:,})"
|
f"(Rank: #{rank:,})"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
@ -189,11 +241,11 @@ class Leaderboard(commands.Cog):
|
||||||
|
|
||||||
if leaderboard_data:
|
if leaderboard_data:
|
||||||
user_data = next(
|
user_data = next(
|
||||||
(entry for entry in leaderboard_data if entry["userId"] == str(message.author.id)),
|
(entry for entry in leaderboard_data if entry.get("userId") == str(message.author.id)),
|
||||||
None
|
None
|
||||||
)
|
)
|
||||||
if user_data:
|
if user_data:
|
||||||
current_points = user_data["points"]
|
current_points = user_data.get("points", 0)
|
||||||
|
|
||||||
# Update points
|
# Update points
|
||||||
new_points = current_points + points_per_message
|
new_points = current_points + points_per_message
|
||||||
|
|
Loading…
Add table
Reference in a new issue