Need Advice me complete a rank command with discord.py rewrite and json

so far I build a working rank(r2[prints level and exp]) and leaderboard(levels[prints the first 20 ranks on the server]) command. Now I want my users.json to also save someones rank(place) on the server and not only the exp and level. So there dont have to use the leaderboard everytime. Is there anyway that this is possible?

My code:

import asyncio
import discord
import json
import random
import time
from discord.ext import commands
from discord import Guild
intents = discord.Intents.default()
intents.members = True
intents.typing = True

client = client = discord.Client(intents=intents)

client = commands.Bot(command_prefix = 'sco!')

@client.event
async def on_member_join(member):
    with open('users.json', 'r') as f:
        users = json.load(f)

    await update_data(users, member)

    with open('users.json', 'w') as f:
        json.dump(users, f)


@client.event
async def on_message(message):
    if message.author.bot == False:
        with open('users.json', 'r') as f:
            users = json.load(f)

        await update_data(users, message.author)
        await add_experience(users, message.author, 5)
        await level_up(users, message.author, message)
        with open('users.json', 'w') as f:
            json.dump(users, f)

    await client.process_commands(message)


async def update_data(users, user):
    if not f'{user.id}' in users:
        users[f'{user.id}'] = {}
        users[f'{user.id}']['xp'] = 0
        users[f'{user.id}']['level'] = 1


async def add_experience(users, user, exp):
    users[f'{user.id}']['xp'] += exp

async def level_up(users, user, message):
    with open('users.json', 'r') as g:
        levels = json.load(g)
    experience = users[f'{user.id}']['xp']
    lvl_start = users[f'{user.id}']['level']
    lvl_end = int(experience ** (1 / 4))
    if lvl_start < lvl_end:
        await message.channel.send(f'{user.mention} ist gerade auf Level {lvl_end} gestiegen!')
        users[f'{user.id}']['level'] = lvl_end

@client.command()
async def r2(ctx, *, member:discord.Member=None):
    await ctx.channel.purge(limit=1)
    if member == None:
        memberID = ctx.author.id
        mentioned = ctx.author.name
    else:
        memberID = member.id
        mentioned = member.name
    with open('users.json', 'r') as f:
        users = json.load(f)
        xp = users[str(memberID)]['xp']
        lvl = users[str(memberID)]['level']

        embed = discord.Embed(title='')
        embed.add_field(name="Spieler", value=mentioned, inline=True)
        embed.add_field(name="Level:", value=lvl, inline=True)
        embed.add_field(name="Experience:", value=xp, inline=True)
        await ctx.send(embed=embed)

@client.command()
async def levels(ctx):
    await ctx.channel.purge(limit=1)
    with open('users.json', 'r') as f:
        data = json.load(f)

    top_spieler = {k: v for k, v in sorted(data.items(), key=lambda item: item[1]["xp"], reverse=True)}

    names = ''
    for position, user in enumerate(top_spieler):
        names += f"{position+1} - <@!{user}> mit Level: {top_spieler[user]['level']} Exp: {top_spieler[user]['xp']}n"
        embed = discord.Embed(title='')
        embed.add_field(name="Spieler", value=names, inline=False)
        if position+1 > 19:
            break
    await ctx.send(embed=embed)

My JSON (only me on the server right now)

{"395805781863170050": {"xp": 15, "level": 1}}

What r2 does: enter image description here

What levels does: enter image description here

I hope there is someone who can help me.

Answer

One way is getting all the XP’s on a list, append the new value and sort the list (in reverse), then getting the index of the new value, example:

>>> xp_list = [120, 90, 110, 150]
>>> new_xp = 100 # By logic, the "rank" would be 4
>>> 
>>> xp_list.append(new_xp) # -> [120, 90, 110, 150, 100]
>>> xp_list.sort(reverse=True) # -> [150, 120, 110, 100, 90]
>>> 
>>> rank = xp_list.index(new_xp) + 1
>>> print(rank)
4 # It's in fact 4

In a function:

def calculate_rank(user_xp: int, data: dict=None) -> int:
    if data is None:
        # Loading the data
        with open("whatever.json", "r") as f: # Change the path accordingly
            data = json.load(f)

    # Getting ONLY the XP of the users
    xp = [value["xp"] for value in data.values()] # -> [15, ...]
    # Appending the new XP
    xp.append(user_xp) 
    # Sorting the list
    xp.sort(reverse=True)
    # Returning the "rank"
    return xp.index(xp) + 1


# To use it pass the user XP
# You can also pass the data directly as a kwarg
rank = calculate_rank(10) 

You won’t need to store the rank in the JSON this way, (tbh it’s a bad option to store it, you’ll have to re-calculate all the ranks everytime someone gets more XP)