From b688f7a605a1b97f72809ba3ea06e5f43af4a79a Mon Sep 17 00:00:00 2001 From: Valerie Date: Mon, 26 May 2025 04:20:46 -0400 Subject: [PATCH] Refactor leaderboard data fetching in Leaderboard cog to retrieve global leaderboard from API, enhancing error handling and caching. Update point management logic to calculate points dynamically and improve user interaction by updating points on member data changes. --- leaderboard/leaderboard.py | 147 ++++++++++++++++++++++++------------- 1 file changed, 95 insertions(+), 52 deletions(-) diff --git a/leaderboard/leaderboard.py b/leaderboard/leaderboard.py index 23694df..c211ff3 100644 --- a/leaderboard/leaderboard.py +++ b/leaderboard/leaderboard.py @@ -133,41 +133,93 @@ class Leaderboard(commands.Cog): return total_points async def _get_leaderboard(self) -> Optional[list]: - """Fetch and combine leaderboard data from bank and LevelUp.""" + """Fetch the global leaderboard from the API.""" + 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 + try: - all_users = {} - - # Get all guild members - for guild in self.bot.guilds: - for member in guild.members: - if member.bot: - continue - - points = await self.get_user_points(member) - - if points > 0: - all_users[str(member.id)] = { - "userId": str(member.id), - "username": str(member), - "points": points - } - - # Sort by points and convert to list - sorted_users = sorted( - all_users.values(), - key=lambda x: x["points"], - reverse=True - ) - - return sorted_users + now = datetime.now(timezone.utc).timestamp() + # Return cached data if available and fresh + if self._cache and now - self._last_update.get("leaderboard", 0) < self.cache_time: + return self._cache.get("leaderboard") + + async with self.session.get( + f"{self.api_base_url}/leaderboard", + headers={"Authorization": self.admin_secret.get("admin_secret")} + ) as resp: + if resp.status == 200: + data = await resp.json() + 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 + return self._cache["leaderboard"] + elif resp.status == 401: + log.error("Unauthorized: Invalid admin secret") + return None + else: + log.error(f"Failed to fetch leaderboard: Status {resp.status}") + return None except Exception as e: - log.error(f"Error generating leaderboard: {e}") + log.error(f"Error fetching leaderboard: {e}") return None - async def _update_points(self, user_id: str, username: str, points: int) -> bool: - """Update points is no longer needed as points are pulled from bank and LevelUp.""" - return True + async def _update_points(self, user_id: str, username: str, points: int = None) -> bool: + """Update a user's points in the global leaderboard.""" + 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 + + try: + # If points not provided, calculate from Red's systems + if points is None: + member = None + for guild in self.bot.guilds: + member = guild.get_member(int(user_id)) + if member: + break + + if not member: + return False + + points = await self.get_user_points(member) + + async with self.session.post( + f"{self.api_base_url}/leaderboard", + headers={ + "Authorization": self.admin_secret.get("admin_secret"), + "Content-Type": "application/json" + }, + json={ + "userId": user_id, + "username": username, + "points": points + } + ) as resp: + 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 Exception as e: + log.error(f"Error updating points: {e}") + return False @commands.group(name="globalboard", aliases=["glb"]) async def globalboard(self, ctx: commands.Context): @@ -271,14 +323,22 @@ class Leaderboard(commands.Cog): await ctx.send(embed=embed) @commands.Cog.listener() - async def on_message(self, message): - """Award points for activity.""" + async def on_member_update(self, before: discord.Member, after: discord.Member): + """Update points when member's data changes.""" + if before.bot: + return + + # Update points in API when bank balance or XP changes + await self._update_points(str(after.id), str(after)) + + @commands.Cog.listener() + async def on_message(self, message: discord.Message): + """Update points periodically during user activity.""" if message.author.bot or not message.guild: return # Get guild settings guild_settings = await self.config.guild(message.guild).all() - points_per_message = guild_settings["points_per_message"] min_length = guild_settings["min_message_length"] cooldown = guild_settings["cooldown"] @@ -295,22 +355,5 @@ class Leaderboard(commands.Cog): self._last_update[f"msg_{message.author.id}"] = now - # Get current points - leaderboard_data = await self._get_leaderboard() - current_points = 0 - - if leaderboard_data: - user_data = next( - (entry for entry in leaderboard_data if entry.get("userId") == str(message.author.id)), - None - ) - if user_data: - current_points = user_data.get("points", 0) - - # Update points - new_points = current_points + points_per_message - await self._update_points( - str(message.author.id), - str(message.author), - new_points - ) \ No newline at end of file + # Update points in API + await self._update_points(str(message.author.id), str(message.author)) \ No newline at end of file