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()
while True:
try:
success_count, fail_count = await self._perform_full_sync()
log.info(f"Completed automatic leaderboard sync: {success_count} users with 10,000+ credits synced, {fail_count} failures")
await self._perform_full_sync()
log.info("Completed automatic leaderboard sync")
await asyncio.sleep(21600) # 6 hours in seconds
except asyncio.CancelledError:
break
@ -137,71 +137,27 @@ class Leaderboard(commands.Cog):
success_count = 0
fail_count = 0
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 member in guild.members:
if member.bot or member.id in processed_users:
continue
try:
# Get total credits across all guilds
credits = await self.get_user_credits(member)
# Only sync users with 10,000+ credits
if credits >= min_credits:
users_to_sync.append({
"member": member,
"credits": credits
})
log.debug(f"Queued for sync: {member} ({member.id}) with {credits} credits")
# Update API
if await self._try_api_update(str(member.id), str(member), credits):
success_count += 1
else:
fail_count += 1
processed_users.add(member.id)
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
# 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
async def cog_load(self):
@ -210,26 +166,35 @@ class Leaderboard(commands.Cog):
async def get_user_credits(self, user: discord.Member) -> int:
"""Get a user's total credits from all servers."""
total_credits = 0
try:
# Get the user's balance
balance = await bank.get_balance(user)
log.info(f"Credit check for {user} ({user.id}): {balance} credits")
return balance
# Get credits from all servers the user is in
for guild in self.bot.guilds:
try:
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:
log.error(f"Error getting credits for {user}: {e}")
return 0
log.error(f"Error getting total credits for {user}: {e}")
return total_credits
async def get_all_balances(self) -> List[dict]:
"""Get all users' credit balances across all servers."""
all_users = {}
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
for guild in self.bot.guilds:
log.debug(f"Processing guild: {guild.name} ({guild.id})")
for member in guild.members:
if member.bot or member.id in processed_users:
continue
@ -237,25 +202,20 @@ class Leaderboard(commands.Cog):
# Skip opted-out users
try:
if await self.config.user(member).opted_out():
log.debug(f"User {member} ({member.id}) is opted out")
continue
except Exception as e:
log.error(f"Error checking opt-out status for {member}: {e}")
continue
try:
credits = await bank.get_balance(member)
total_credits = await self.get_user_credits(member)
# Debug logging for credit calculation
log.info(f"User {member} ({member.id}) has {credits} credits (minimum: {min_credits})")
if credits >= min_credits:
if total_credits >= min_credits:
all_users[member.id] = {
"userId": str(member.id),
"username": str(member),
"points": credits
"points": total_credits
}
log.info(f"Added {member} to leaderboard with {credits} credits")
processed_users.add(member.id)
except Exception as e:
@ -270,14 +230,10 @@ class Leaderboard(commands.Cog):
)
# Debug logging
log.info(f"Found {len(all_users)} users with {min_credits}+ credits")
if sorted_users:
log.info("Top users on leaderboard:")
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!")
log.info(f"Found {len(sorted_users)} users with {min_credits}+ credits")
for user in sorted_users[:10]: # Log top 10 for debugging
log.info(f"User {user['username']} has {user['points']} credits")
return sorted_users
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
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(
f"{self.api_base_url}/leaderboard",
headers={
@ -358,21 +311,17 @@ class Leaderboard(commands.Cog):
async def show_leaderboard(self, ctx: commands.Context, page: int = 1):
"""Show the global credits leaderboard."""
async with ctx.typing():
log.info(f"Leaderboard requested in guild {ctx.guild.name} ({ctx.guild.id})")
leaderboard_data = await self.get_all_balances()
if not leaderboard_data:
# Debug info in log
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
return await ctx.send("No users have 10,000 or more credits!")
items_per_page = 10
chunks = [leaderboard_data[i:i + items_per_page]
for i in range(0, len(leaderboard_data), items_per_page)]
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 = []
for page_num, entries in enumerate(chunks, 1):
@ -453,7 +402,7 @@ class Leaderboard(commands.Cog):
credits = await self.get_user_credits(member)
# 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
leaderboard_data = await self.get_all_balances()
@ -476,10 +425,7 @@ class Leaderboard(commands.Cog):
f"**ID:** {member.id}"
)
else:
embed.description = (
f"{member.mention} has **{humanize_number(credits)}** credits\n"
f"*Need {humanize_number(10000 - credits)} more credits to appear on the leaderboard*"
)
embed.description = f"{member.mention} has less than 10,000 credits ({humanize_number(credits)} total)"
embed.set_thumbnail(url=member.display_avatar.url)
await ctx.send(embed=embed)
@ -487,13 +433,13 @@ class Leaderboard(commands.Cog):
@globalboard.command(name="resync")
@commands.is_owner()
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"):
await ctx.send("API is not configured.")
return
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:
success_count, fail_count = await self._perform_full_sync()
@ -501,9 +447,8 @@ class Leaderboard(commands.Cog):
embed = discord.Embed(
title="🔄 Global Leaderboard Resync Complete",
description=(
f"Successfully updated: **{success_count}** users with 10,000+ credits\n"
f"Failed to update: **{fail_count}** users\n\n"
"Note: Updates may take a few moments to appear on the website."
f"Successfully updated: **{success_count}** users\n"
f"Failed to update: **{fail_count}** users"
),
color=await ctx.embed_color(),
timestamp=datetime.now(timezone.utc)