From a6328761c13ff9574577a247d06fc3b5988d1f73 Mon Sep 17 00:00:00 2001 From: norawennerstrom Date: Mon, 2 Mar 2026 20:26:36 +0100 Subject: [PATCH 1/3] Fix: change 'winners' from list to tuple --- bot/exts/holidays/easter/egghead_quiz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bot/exts/holidays/easter/egghead_quiz.py b/bot/exts/holidays/easter/egghead_quiz.py index 61bba3a09..cce098f53 100644 --- a/bot/exts/holidays/easter/egghead_quiz.py +++ b/bot/exts/holidays/easter/egghead_quiz.py @@ -106,7 +106,7 @@ async def eggquiz(self, ctx: commands.Context) -> None: # with the correct answer, so stop looping over reactions. break - winners = [u for u in users if not u.bot] + winners = tuple(u for u in users if not u.bot) points_earned = {} for u in winners: From 8a4ac27a484f0e2167b7a7c092f923f18e10bad0 Mon Sep 17 00:00:00 2001 From: norawennerstrom Date: Mon, 2 Mar 2026 21:30:40 +0100 Subject: [PATCH 2/3] Fix: call 'increment' directly without checking that cache contains user ID --- bot/utils/leaderboard.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/bot/utils/leaderboard.py b/bot/utils/leaderboard.py index 5437295d6..a35f68c31 100644 --- a/bot/utils/leaderboard.py +++ b/bot/utils/leaderboard.py @@ -69,10 +69,7 @@ async def add_points(bot: Bot, user_id: int, points: int, game_name: str) -> tup # update persistent global total points_cache = await _get_points_cache() - if await points_cache.contains(user_id): - await points_cache.increment(user_id, points_earned) - else: - await points_cache.set(user_id, points_earned) + await points_cache.increment(user_id, points_earned) new_total = int(await points_cache.get(user_id)) return (new_total, points_earned) From e07b1812b64d20cfb72dab2d8d6fcba97c193c99 Mon Sep 17 00:00:00 2001 From: norawennerstrom Date: Mon, 2 Mar 2026 23:28:53 +0100 Subject: [PATCH 3/3] Fix: move points_cache from cog to global variable in bot.utils.leaderboard --- bot/exts/fun/leaderboard.py | 15 +++++++++++---- bot/utils/leaderboard.py | 32 ++++++++++++-------------------- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/bot/exts/fun/leaderboard.py b/bot/exts/fun/leaderboard.py index b92826be8..88ea7dde3 100644 --- a/bot/exts/fun/leaderboard.py +++ b/bot/exts/fun/leaderboard.py @@ -6,7 +6,7 @@ from bot.bot import Bot from bot.constants import Colours, MODERATION_ROLES from bot.utils.decorators import with_role -from bot.utils.leaderboard import get_daily_leaderboard, get_leaderboard, get_user_points, get_user_rank +from bot.utils.leaderboard import POINTS_CACHE, get_daily_leaderboard, get_leaderboard, get_user_points, get_user_rank from bot.utils.pagination import LinePaginator DUCK_COIN_THUMBNAIL = ( @@ -58,10 +58,12 @@ async def interaction_check(self, interaction: Interaction) -> bool: @ui.button(label="Confirm", style=ButtonStyle.danger) async def confirm(self, interaction: Interaction, _button: ui.Button) -> None: """Clear the leaderboard on confirmation.""" - from bot.utils.leaderboard import _get_points_cache + if POINTS_CACHE is None: + await interaction.response.send_message("Leaderboard cache is not initialized.") + self.stop() + return - points_cache = await _get_points_cache() - await points_cache.clear() + await POINTS_CACHE.clear() await interaction.response.send_message("Leaderboard has been cleared.") self.stop() @@ -80,6 +82,11 @@ class Leaderboard(commands.Cog): def __init__(self, bot: Bot): self.bot = bot + async def cog_load(self) -> None: + """Register the global cache when the cog loads.""" + from bot.utils import leaderboard + leaderboard.POINTS_CACHE = self.points_cache + @commands.group(name="leaderboard", aliases=("lb", "points"), invoke_without_command=True) async def leaderboard_command(self, ctx: commands.Context) -> None: """Show the global game points leaderboard.""" diff --git a/bot/utils/leaderboard.py b/bot/utils/leaderboard.py index a35f68c31..f223780bc 100644 --- a/bot/utils/leaderboard.py +++ b/bot/utils/leaderboard.py @@ -19,18 +19,15 @@ # Prefix for daily cap keys stored directly in Redis (not via RedisCache). _DAILY_KEY_PREFIX = "leaderboard:daily" +#Global points cache, set by the Leaderboard cog on load. +POINTS_CACHE: RedisCache | None = None + def _daily_key(user_id: int, game_name: str) -> str: """Build a namespaced Redis key for daily point tracking.""" return f"{_DAILY_KEY_PREFIX}:{user_id}:{game_name}" -async def _get_points_cache() -> RedisCache: - """Get the persistent points cache from the Leaderboard cog.""" - from bot.exts.fun.leaderboard import Leaderboard - return Leaderboard.points_cache - - async def add_points(bot: Bot, user_id: int, points: int, game_name: str) -> tuple[int, int]: """ Add points to a user's global leaderboard score. @@ -68,10 +65,9 @@ async def add_points(bot: Bot, user_id: int, points: int, game_name: str) -> tup await redis.set(daily_key, earned_today + points_earned, ex=ttl) # update persistent global total - points_cache = await _get_points_cache() - await points_cache.increment(user_id, points_earned) + await POINTS_CACHE.increment(user_id, points_earned) - new_total = int(await points_cache.get(user_id)) + new_total = int(await POINTS_CACHE.get(user_id)) return (new_total, points_earned) @@ -82,18 +78,16 @@ async def remove_points(bot: Bot, user_id: int, points: int) -> int: Score will not go below 0. Returns the user's new total score, or 0 if the cog is not loaded. """ - if points <= 0 or bot.get_cog("Leaderboard") is None: + if points <= 0 or bot.get_cog("Leaderboard") is None or POINTS_CACHE is None: return await get_user_points(bot, user_id) - points_cache = await _get_points_cache() - - current = await points_cache.get(user_id) + current = await POINTS_CACHE.get(user_id) if not current: return 0 current = int(current) to_remove = min(points, current) - await points_cache.decrement(user_id, to_remove) + await POINTS_CACHE.decrement(user_id, to_remove) return current - to_remove @@ -104,11 +98,10 @@ async def get_leaderboard(bot: Bot) -> list[tuple[int, int]]: Returns a list of (user_id, score) tuples sorted by score descending. """ - if bot.get_cog("Leaderboard") is None: + if bot.get_cog("Leaderboard") is None or POINTS_CACHE is None: return [] - points_cache = await _get_points_cache() - records = await points_cache.items() + records = await POINTS_CACHE.items() return sorted( ((int(user_id), int(score)) for user_id, score in records if int(score) > 0), @@ -173,9 +166,8 @@ async def get_user_rank( async def get_user_points(bot: Bot, user_id: int) -> int: """Get a specific user's total points.""" - if bot.get_cog("Leaderboard") is None: + if bot.get_cog("Leaderboard") is None or POINTS_CACHE is None: return 0 - points_cache = await _get_points_cache() - score = await points_cache.get(user_id) + score = await POINTS_CACHE.get(user_id) return int(score) if score else 0