pythondiscord.pyleaderboard

discord.py - top 10 specific roles leaderboard


What Im trying to do is to have a leaderboard with who has the most roles like:

member1 - 10
member2 - 7
member3 - 4
...

The main code that works is this:

data = []
for member in ctx.guild.members:
    name, score = member.name, len(member.roles)
    data.append((name,score))
data.sort(key=lambda t: t[1], reverse=True)
await ctx.send("Leaderboard")
for i, (name, score) in enumerate(data[:10]):
    await ctx.send(f"{i+1}. {name} - {score}")

But I dont want to count the role Member,Admin. So I tried to make the following:

@client.command()
async def top(ctx):
  data = []
  mroles = []
  final = []
  banned = ['Admin','Member']
  for member in ctx.guild.members:
    for roles in member.roles:
      name, score = member.name, roles.name
      mroles.append(score)
  
      for roles in mroles:
        if roles in banned:
          continue
        else:
          final.append(roles)
      
      data.append((name,len(final)))
  data.sort(key=lambda t: t[1], reverse=True)
  await ctx.send("Leaderboard")
  for i, (name, score) in enumerate(data[:10]):
      await ctx.send(f"{i+1}. {name} - {score}")

But the output is some crazy values and the name of the member x2:

 1. testuser - 16671
 2. testuser - 16532
 3. testuser2 - 16393
 4. testuser2 - 16255

Any clue? I burn my mind for hours i cant make it work.


Solution

  • You never reset mroles or final. Combined with the nested for loops, this causes it to get crammed with items and increase really fast as you continue.

    The names also appear multiple times because your data.append((name,len(final))) is running inside of for roles in member.roles - meaning they will get another entry in the leaderboard for every role they have.

    Here's a fast way to do it:

    @client.command()
    async def top(ctx):
        data = []
        banned_roles = {'Admin', 'Member'}  # this should be a set, because speed
    
        for member in ctx.guild.members:
            total_role_count = len(member.roles)
            # Uncomment this line if you don't want the "@everyone" role to be included. Yes, this is an actual role.
            #total_role_count -= 1
    
            # fast O(n) way to compute # of banned roles
            # it finds the roles that are banned by intersecting the member's roles and the banned roles
            # then take the length of that and subtract it from the total number
            # you could probably also do this using `set.difference`
            role_names = {role.name for role in member.roles}
            banned_count = len(role_names.intersection(banned_roles))
    
            data.append((member.name, total_role_count - banned_count))
    
        data.sort(key=lambda t: t[1], reverse=True)
        # instead of sending all the messages, send a big message with newlines in it
        # this avoids the ratelimit of 5 messages every 5 seconds
        text = '\n'.join([f"{i+1}. {name} - {score}" for i, (name, score) in enumerate(data[:10])])
        await ctx.send(f'Leaderboard\n\n{text}')