From cbc2fb5ba98b347a24dfbf02fad093c369ecf3ea Mon Sep 17 00:00:00 2001 From: Valerie Date: Mon, 26 May 2025 05:35:50 -0400 Subject: [PATCH] Implement API connection testing and enhance user credit retrieval in Leaderboard cog Add a test for the API connection during initialization, improving error logging for connection failures. Update the user credit retrieval method to aggregate credits from all servers the user is a member of, enhancing accuracy. Introduce a cleanup command for removing old leaderboard entries, with appropriate error handling and user feedback for API interactions. --- leaderboard/leaderboard.py | 106 ++++++++++++++++++++++++++++++++----- 1 file changed, 94 insertions(+), 12 deletions(-) diff --git a/leaderboard/leaderboard.py b/leaderboard/leaderboard.py index 1104a50..e626f31 100644 --- a/leaderboard/leaderboard.py +++ b/leaderboard/leaderboard.py @@ -100,21 +100,35 @@ class Leaderboard(commands.Cog): self.admin_secret = await self.bot.get_shared_api_tokens("ruby_api") if self.admin_secret.get("admin_secret"): self.session = aiohttp.ClientSession() + # Test API connection + async with self.session.get( + f"{self.api_base_url}/leaderboard", + headers={"Authorization": self.admin_secret["admin_secret"]} + ) as resp: + if resp.status != 200: + log.error(f"Failed to connect to API: Status {resp.status}") except Exception as e: log.error(f"Failed to initialize API connection: {e}") - # Don't return anything - initialization is optional async def cog_load(self): """Initialize the cog when it's loaded.""" await self.initialize() async def get_user_credits(self, user: discord.Member) -> int: - """Get a user's total credits from Red's bank system.""" - try: - return await bank.get_balance(user) - except Exception as e: - log.error(f"Error getting bank balance for {user}: {e}") - return 0 + """Get a user's total credits from all servers.""" + total_credits = 0 + + # Get credits from all servers the user is in + for guild in self.bot.guilds: + if user in guild.members: # Only check servers where the user is a member + try: + credits = await bank.get_balance(user) + total_credits += credits + except Exception as e: + log.error(f"Error getting bank balance for {user} in {guild}: {e}") + continue + + return total_credits async def get_all_balances(self) -> List[dict]: """Get all users' credit balances across all servers.""" @@ -164,23 +178,61 @@ class Leaderboard(commands.Cog): return False try: + if credits < 0: + credits = 0 # API doesn't accept negative values + async with self.session.post( f"{self.api_base_url}/leaderboard", headers={ - "Authorization": self.admin_secret.get("admin_secret"), + "Authorization": self.admin_secret["admin_secret"], "Content-Type": "application/json" }, json={ - "userId": user_id, - "username": username, + "userId": str(user_id), + "username": str(username), "points": credits } ) as resp: - return resp.status == 200 + if resp.status == 200: + data = await resp.json() + if data.get("success"): + log.info(f"Updated leaderboard for {username}: {credits} credits") + return True + else: + log.error(f"API update failed: {data.get('error', 'Unknown error')}") + elif resp.status == 401: + log.error("Unauthorized: Invalid admin secret") + else: + log.error(f"API update failed: Status {resp.status}") + return False except Exception as e: log.error(f"API update failed: {e}") return False + async def _get_api_leaderboard(self) -> Optional[list]: + """Fetch the leaderboard from the API.""" + if not self.session or not self.admin_secret or not self.admin_secret.get("admin_secret"): + return None + + try: + async with self.session.get( + f"{self.api_base_url}/leaderboard", + headers={"Authorization": self.admin_secret["admin_secret"]} + ) as resp: + if resp.status == 200: + data = await resp.json() + if "leaderboard" in data: + return data["leaderboard"] + log.error("Invalid API response format") + elif resp.status == 401: + log.error("Unauthorized: Invalid admin secret") + else: + log.error(f"Failed to fetch leaderboard: Status {resp.status}") + return None + except Exception as e: + log.error(f"Error fetching leaderboard: {e}") + return None + @commands.group(name="globalboard", aliases=["glb"]) async def globalboard(self, ctx: commands.Context): """Global leaderboard commands.""" @@ -332,7 +384,37 @@ class Leaderboard(commands.Cog): if member.bot: return - await self._try_api_update(str(member.id), str(member), after) + total_credits = await self.get_user_credits(member) + await self._try_api_update(str(member.id), str(member), total_credits) + + @globalboard.command(name="cleanup") + @commands.is_owner() + async def cleanup_leaderboard(self, ctx: commands.Context): + """Clean up old leaderboard entries (older than 30 days).""" + if not self.session or not self.admin_secret or not self.admin_secret.get("admin_secret"): + await ctx.send("API is not configured.") + return + + try: + async with self.session.delete( + f"{self.api_base_url}/leaderboard", + headers={"Authorization": self.admin_secret["admin_secret"]} + ) as resp: + if resp.status == 200: + data = await resp.json() + if data.get("success"): + await ctx.send( + f"Cleaned up {data['removedCount']} old entries. " + f"{data['remainingCount']} entries remaining." + ) + else: + await ctx.send("Failed to clean up leaderboard.") + elif resp.status == 401: + await ctx.send("Unauthorized: Invalid admin secret") + else: + await ctx.send(f"Failed to clean up leaderboard: Status {resp.status}") + except Exception as e: + await ctx.send(f"Error cleaning up leaderboard: {e}") def cog_unload(self): """Clean up when cog is unloaded."""