Enhance automatic leaderboard sync process in Leaderboard cog by refining user credit aggregation and error handling. Implement detailed logging for successes and failures during sync, and ensure opted-out users are included in the sync process. Improve API verification after sync to confirm data integrity. Update user credit retrieval to streamline balance fetching across servers.
Some checks are pending
Run pre-commit / Run pre-commit (push) Waiting to run

This commit is contained in:
Valerie 2025-05-26 06:06:05 -04:00
parent 9b8d4be301
commit 7eddd891bc

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:
await self._perform_full_sync() success_count, fail_count = await self._perform_full_sync()
log.info("Completed automatic leaderboard sync") log.info(f"Completed automatic leaderboard sync: {success_count} successes, {fail_count} failures")
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,27 +137,69 @@ 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 = []
# First, collect all 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)
opted_out = await self.config.user(member).opted_out()
# Update API # Only include users with sufficient credits or who are opted out
if await self._try_api_update(str(member.id), str(member), credits): # (we need to sync opted-out status even for users below threshold)
success_count += 1 if credits >= 10000 or opted_out:
else: users_to_sync.append({
fail_count += 1 "member": member,
"credits": credits,
"opted_out": opted_out
})
processed_users.add(member.id) processed_users.add(member.id)
except Exception as e: except Exception as e:
log.error(f"Error syncing user {member} ({member.id}): {e}") log.error(f"Error processing user {member} ({member.id}): {e}")
fail_count += 1 fail_count += 1
# Debug logging
log.info(f"Found {len(users_to_sync)} users to sync")
# Now perform the sync
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})")
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")
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):
@ -166,38 +208,18 @@ 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 credits from the user's current server # Use Red's bank API to get the user's balance
try: # This automatically handles the global bank if enabled
total_credits = await bank.get_balance(user) return await bank.get_balance(user)
except Exception as e:
log.error(f"Error getting balance for {user} in current guild: {e}")
# Then add credits from all other servers
for guild in self.bot.guilds:
if guild.id != user.guild.id: # Skip current guild
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: except Exception as e:
log.error(f"Error getting total credits for {user}: {e}") log.error(f"Error getting 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
# First get all unique members across all guilds
processed_users = set() processed_users = set()
# Process each guild # Process each guild
@ -215,13 +237,16 @@ class Leaderboard(commands.Cog):
continue continue
try: try:
total_credits = await self.get_user_credits(member) credits = await self.get_user_credits(member)
if total_credits >= min_credits: # Debug logging for credit calculation
log.debug(f"Calculated credits for {member} ({member.id}): {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": total_credits "points": credits
} }
processed_users.add(member.id) processed_users.add(member.id)
@ -255,6 +280,9 @@ 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={
@ -409,7 +437,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 check for {member}: {credits} credits") log.info(f"Credits check for {member} ({member.id}): {credits} credits")
# Get user's rank # Get user's rank
leaderboard_data = await self.get_all_balances() leaderboard_data = await self.get_all_balances()
@ -455,7 +483,8 @@ class Leaderboard(commands.Cog):
title="🔄 Global Leaderboard Resync Complete", title="🔄 Global Leaderboard Resync Complete",
description=( description=(
f"Successfully updated: **{success_count}** users\n" f"Successfully updated: **{success_count}** users\n"
f"Failed to update: **{fail_count}** users" f"Failed to update: **{fail_count}** users\n\n"
"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)