Refactor leaderboard sync process in Leaderboard cog to streamline user credit aggregation and improve logging. Remove unnecessary checks for minimum credits during sync, and enhance error handling for API updates. Update user credit retrieval to sum credits across all guilds, ensuring accurate leaderboard representation.
Some checks are pending
Run pre-commit / Run pre-commit (push) Waiting to run

This commit is contained in:
Valerie 2025-05-26 09:29:56 -04:00
parent 7cc05adc71
commit 76fd3bf053

View file

@ -122,8 +122,8 @@ class Leaderboard(commands.Cog):
await self.bot.wait_until_ready() await self.bot.wait_until_ready()
while True: while True:
try: try:
success_count, fail_count = await self._perform_full_sync() await self._perform_full_sync()
log.info(f"Completed automatic leaderboard sync: {success_count} users with 10,000+ credits synced, {fail_count} failures") log.info("Completed automatic leaderboard sync")
await asyncio.sleep(21600) # 6 hours in seconds await asyncio.sleep(21600) # 6 hours in seconds
except asyncio.CancelledError: except asyncio.CancelledError:
break break
@ -137,71 +137,27 @@ class Leaderboard(commands.Cog):
success_count = 0 success_count = 0
fail_count = 0 fail_count = 0
processed_users = set() processed_users = set()
users_to_sync = []
min_credits = 10000 # Minimum credits to show on leaderboard
# First, collect all eligible users and their credits
for guild in self.bot.guilds: for guild in self.bot.guilds:
for member in guild.members: for member in guild.members:
if member.bot or member.id in processed_users: if member.bot or member.id in processed_users:
continue continue
try: try:
# Get total credits across all guilds
credits = await self.get_user_credits(member) credits = await self.get_user_credits(member)
# Only sync users with 10,000+ credits # Update API
if credits >= min_credits: if await self._try_api_update(str(member.id), str(member), credits):
users_to_sync.append({ success_count += 1
"member": member, else:
"credits": credits fail_count += 1
})
log.debug(f"Queued for sync: {member} ({member.id}) with {credits} credits")
processed_users.add(member.id) processed_users.add(member.id)
except Exception as e: except Exception as e:
log.error(f"Error processing user {member} ({member.id}): {e}") log.error(f"Error syncing user {member} ({member.id}): {e}")
fail_count += 1 fail_count += 1
# Debug logging
log.info(f"Found {len(users_to_sync)} users with {min_credits}+ credits to sync")
# Now perform the sync for eligible users
for user_data in users_to_sync:
member = user_data["member"]
try:
if await self._try_api_update(
str(member.id),
str(member),
user_data["credits"]
):
success_count += 1
log.debug(f"Successfully synced {member} ({member.id}) with {user_data['credits']} credits")
else:
fail_count += 1
log.error(f"Failed to sync {member} ({member.id})")
except Exception as e:
log.error(f"Error syncing user {member} ({member.id}): {e}")
fail_count += 1
# Verify the sync by checking the API
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:
log.info(f"API verification: {len(data['leaderboard'])} users in leaderboard after sync")
if len(data['leaderboard']) != len(users_to_sync):
log.warning(f"Mismatch in user count: {len(users_to_sync)} synced vs {len(data['leaderboard'])} in API")
else:
log.error("API verification failed: Invalid response format")
else:
log.error(f"API verification failed: Status {resp.status}")
except Exception as e:
log.error(f"API verification failed: {e}")
return success_count, fail_count return success_count, fail_count
async def cog_load(self): async def cog_load(self):
@ -210,26 +166,35 @@ class Leaderboard(commands.Cog):
async def get_user_credits(self, user: discord.Member) -> int: async def get_user_credits(self, user: discord.Member) -> int:
"""Get a user's total credits from all servers.""" """Get a user's total credits from all servers."""
total_credits = 0
try: try:
# Get the user's balance # Get credits from all servers the user is in
balance = await bank.get_balance(user) for guild in self.bot.guilds:
log.info(f"Credit check for {user} ({user.id}): {balance} credits") try:
return balance member = guild.get_member(user.id)
if member: # If user is in this guild
guild_credits = await bank.get_balance(member)
total_credits += guild_credits
except Exception as e:
log.error(f"Error getting bank balance for {user} in {guild}: {e}")
continue
except Exception as e: except Exception as e:
log.error(f"Error getting credits for {user}: {e}") log.error(f"Error getting total credits for {user}: {e}")
return 0
return total_credits
async def get_all_balances(self) -> List[dict]: async def get_all_balances(self) -> List[dict]:
"""Get all users' credit balances across all servers.""" """Get all users' credit balances across all servers."""
all_users = {} all_users = {}
min_credits = 10000 # Minimum credits to show on leaderboard - matches API's MIN_CREDITS min_credits = 10000 # Minimum credits to show on leaderboard - matches API's MIN_CREDITS
processed_users = set()
log.info("Starting to collect user balances...") # First get all unique members across all guilds
processed_users = set()
# Process each guild # Process each guild
for guild in self.bot.guilds: for guild in self.bot.guilds:
log.debug(f"Processing guild: {guild.name} ({guild.id})")
for member in guild.members: for member in guild.members:
if member.bot or member.id in processed_users: if member.bot or member.id in processed_users:
continue continue
@ -237,25 +202,20 @@ class Leaderboard(commands.Cog):
# Skip opted-out users # Skip opted-out users
try: try:
if await self.config.user(member).opted_out(): if await self.config.user(member).opted_out():
log.debug(f"User {member} ({member.id}) is opted out")
continue continue
except Exception as e: except Exception as e:
log.error(f"Error checking opt-out status for {member}: {e}") log.error(f"Error checking opt-out status for {member}: {e}")
continue continue
try: try:
credits = await bank.get_balance(member) total_credits = await self.get_user_credits(member)
# Debug logging for credit calculation if total_credits >= min_credits:
log.info(f"User {member} ({member.id}) has {credits} credits (minimum: {min_credits})")
if credits >= min_credits:
all_users[member.id] = { all_users[member.id] = {
"userId": str(member.id), "userId": str(member.id),
"username": str(member), "username": str(member),
"points": credits "points": total_credits
} }
log.info(f"Added {member} to leaderboard with {credits} credits")
processed_users.add(member.id) processed_users.add(member.id)
except Exception as e: except Exception as e:
@ -270,14 +230,10 @@ class Leaderboard(commands.Cog):
) )
# Debug logging # Debug logging
log.info(f"Found {len(all_users)} users with {min_credits}+ credits") log.info(f"Found {len(sorted_users)} users with {min_credits}+ credits")
if sorted_users: for user in sorted_users[:10]: # Log top 10 for debugging
log.info("Top users on leaderboard:") log.info(f"User {user['username']} has {user['points']} credits")
for user in sorted_users[:10]:
log.info(f"User {user['username']} ({user['userId']}) has {user['points']} credits")
else:
log.warning("No users found with sufficient credits!")
return sorted_users return sorted_users
async def _try_api_update(self, user_id: str, username: str, credits: int) -> bool: async def _try_api_update(self, user_id: str, username: str, credits: int) -> bool:
@ -292,9 +248,6 @@ class Leaderboard(commands.Cog):
# Get opt-out status # Get opt-out status
opted_out = await self.config.user_from_id(int(user_id)).opted_out() opted_out = await self.config.user_from_id(int(user_id)).opted_out()
# Debug logging before update
log.debug(f"Sending API update for {username} ({user_id}): {credits} credits, opted_out: {opted_out}")
async with self.session.post( async with self.session.post(
f"{self.api_base_url}/leaderboard", f"{self.api_base_url}/leaderboard",
headers={ headers={
@ -358,21 +311,17 @@ class Leaderboard(commands.Cog):
async def show_leaderboard(self, ctx: commands.Context, page: int = 1): async def show_leaderboard(self, ctx: commands.Context, page: int = 1):
"""Show the global credits leaderboard.""" """Show the global credits leaderboard."""
async with ctx.typing(): async with ctx.typing():
log.info(f"Leaderboard requested in guild {ctx.guild.name} ({ctx.guild.id})")
leaderboard_data = await self.get_all_balances() leaderboard_data = await self.get_all_balances()
if not leaderboard_data: if not leaderboard_data:
# Debug info in log return await ctx.send("No users have 10,000 or more credits!")
log.warning(f"No users with 10,000+ credits found in guild {ctx.guild.id}")
await ctx.send("No users have 10,000 or more credits! Check your balance with `[p]glb credits`")
return
items_per_page = 10 items_per_page = 10
chunks = [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 chunks: if not chunks:
return await ctx.send("No users have 10,000 or more credits! Check your balance with `[p]glb credits`") return await ctx.send("No users have 10,000 or more credits!")
embeds = [] embeds = []
for page_num, entries in enumerate(chunks, 1): for page_num, entries in enumerate(chunks, 1):
@ -453,7 +402,7 @@ class Leaderboard(commands.Cog):
credits = await self.get_user_credits(member) credits = await self.get_user_credits(member)
# Debug logging # Debug logging
log.info(f"Credits command used - User {member} ({member.id}) has {credits} credits") log.info(f"Credits check for {member}: {credits} credits")
# Get user's rank # Get user's rank
leaderboard_data = await self.get_all_balances() leaderboard_data = await self.get_all_balances()
@ -476,10 +425,7 @@ class Leaderboard(commands.Cog):
f"**ID:** {member.id}" f"**ID:** {member.id}"
) )
else: else:
embed.description = ( embed.description = f"{member.mention} has less than 10,000 credits ({humanize_number(credits)} total)"
f"{member.mention} has **{humanize_number(credits)}** credits\n"
f"*Need {humanize_number(10000 - credits)} more credits to appear on the leaderboard*"
)
embed.set_thumbnail(url=member.display_avatar.url) embed.set_thumbnail(url=member.display_avatar.url)
await ctx.send(embed=embed) await ctx.send(embed=embed)
@ -487,13 +433,13 @@ class Leaderboard(commands.Cog):
@globalboard.command(name="resync") @globalboard.command(name="resync")
@commands.is_owner() @commands.is_owner()
async def resync_leaderboard(self, ctx: commands.Context): async def resync_leaderboard(self, ctx: commands.Context):
"""Force a resync of all users with 10,000+ credits to the API.""" """Force a resync of all users' credits with the API (if configured)."""
if not self.session or not self.admin_secret or not self.admin_secret.get("admin_secret"): if not self.session or not self.admin_secret or not self.admin_secret.get("admin_secret"):
await ctx.send("API is not configured.") await ctx.send("API is not configured.")
return return
async with ctx.typing(): async with ctx.typing():
message = await ctx.send("🔄 Starting global leaderboard resync (10,000+ credits only)...") message = await ctx.send("🔄 Starting global leaderboard resync...")
try: try:
success_count, fail_count = await self._perform_full_sync() success_count, fail_count = await self._perform_full_sync()
@ -501,9 +447,8 @@ class Leaderboard(commands.Cog):
embed = discord.Embed( embed = discord.Embed(
title="🔄 Global Leaderboard Resync Complete", title="🔄 Global Leaderboard Resync Complete",
description=( description=(
f"Successfully updated: **{success_count}** users with 10,000+ credits\n" f"Successfully updated: **{success_count}** users\n"
f"Failed to update: **{fail_count}** users\n\n" f"Failed to update: **{fail_count}** users"
"Note: Updates may take a few moments to appear on the website."
), ),
color=await ctx.embed_color(), color=await ctx.embed_color(),
timestamp=datetime.now(timezone.utc) timestamp=datetime.now(timezone.utc)