Ruby-Cogs/levelup/common/formatter.py
Valerie 477974d53c
Some checks are pending
Run pre-commit / Run pre-commit (push) Waiting to run
Upload 2 Cogs & Update README
2025-05-23 01:30:53 -04:00

318 lines
12 KiB
Python

import math
import typing as t
from datetime import datetime
from io import StringIO
import discord
from redbot.core.bot import Red
from redbot.core.i18n import Translator
from redbot.core.utils.chat_formatting import humanize_number
from ..common import utils
from ..common.models import DB, GuildSettings, Profile, ProfileWeekly, WeeklySettings
_ = Translator("LevelUp", __file__)
def get_user_position(
guild: discord.Guild,
conf: GuildSettings,
lbtype: t.Literal["lb", "weekly"],
target_user: int,
key: str,
) -> dict:
"""Get the position of a user in the leaderboard
Args:
lb (t.Dict[int, t.Union[Profile, ProfileWeekly]]): The leaderboard
target_user (int): The user's ID
key (str): The key to sort by
Returns:
int: The user's position
"""
if lbtype == "weekly":
lb: t.Dict[int, ProfileWeekly] = conf.users_weekly
elif not conf.prestigedata:
lb: t.Dict[int, Profile] = conf.users
else:
lb: t.Dict[int, Profile] = {}
for user_id in list(conf.users.keys()):
profile = (
conf.users[user_id].model_copy()
if hasattr(conf.users[user_id], "model_copy")
else conf.users[user_id].copy()
)
if profile.prestige and conf.prestigelevel:
profile.xp += profile.prestige * conf.algorithm.get_xp(conf.prestigelevel)
profile.level += profile.prestige * conf.prestigelevel
lb[user_id] = profile
valid_users = {k: v for k, v in lb.items() if guild.get_member(k)}
sorted_users = sorted(valid_users.items(), key=lambda x: getattr(x[1], key), reverse=True)
for idx, (uid, _) in enumerate(sorted_users):
if uid == target_user:
position = idx + 1
break
else:
position = -1
total = sum([getattr(x[1], key) for x in sorted_users])
percent = getattr(lb[target_user], key) / total * 100 if total else 0
return {"position": position, "total": total, "percent": percent}
def get_role_leaderboard(rolegroups: t.Dict[int, float], color: discord.Color) -> t.List[discord.Embed]:
"""Format and return the role leaderboard
Args:
rolegroups (t.Dict[int, float]): The role leaderboard
Returns:
t.List[discord.Embed]: A list of embeds
"""
sorted_roles = sorted(rolegroups.items(), key=lambda x: x[1], reverse=True)
filtered_roles = [x for x in sorted_roles if x[1] > 0]
embeds = []
count = len(filtered_roles)
pages = math.ceil(count / 10)
start = 0
stop = 10
for idx in range(pages):
stop = min(count, stop)
buffer = StringIO()
for i, (role_id, xp) in enumerate(filtered_roles[start:stop], start=start):
buffer.write(f"**{i + 1}.** <@&{role_id}> `{humanize_number(int(xp))}`xp\n")
embed = discord.Embed(
title=_("Role Leaderboard"),
description=buffer.getvalue(),
color=color,
).set_footer(text=_("Page {}").format(f"{idx + 1}/{pages}"))
embeds.append(embed)
start += 10
stop += 10
return embeds
def get_leaderboard(
bot: Red,
guild: discord.Guild,
db: DB,
stat: str,
lbtype: str,
is_global: bool,
member: discord.Member = None,
use_displayname: bool = True,
dashboard: bool = False,
color: discord.Color = discord.Color.random(),
query: str = None,
) -> t.Union[t.List[discord.Embed], t.Dict[str, t.Any], str]:
"""Format and return the leaderboard
Args:
bot (Red)
guild (discord.Guild)
db (DB)
stat (str): The stat to display (xp, messages, voice, stars)
lbtype (str): The type of leaderboard (weekly, lb)
is_global (bool): Whether to display global stats
member (discord.Member, optional): Person running the command. Defaults to None.
use_displayname (bool, optional): If false, uses username. Defaults to True.
dashboard (bool, optional): True when called by the dashboard integration. Defaults to False.
color (discord.Color, optional): Defaults to discord.Color.random().
Returns:
t.Union[t.List[discord.Embed], t.Dict[str, t.Any], str]: If called from dashboard returns a dict, else returns a list of embeds or a string
"""
stat = stat.lower()
color = member.color if member else color
conf = db.get_conf(guild)
lb: t.Dict[int, t.Union[Profile, ProfileWeekly]]
weekly: WeeklySettings = None
if lbtype == "weekly":
title = _("Weekly ")
lb = db.get_conf(guild).users_weekly
weekly = db.get_conf(guild).weeklysettings
elif lbtype == "lb" and is_global:
title = _("Global LevelUp ")
# Add up all the guilds
lb: t.Dict[int, Profile] = {}
for guild_id in db.configs.keys():
guild_conf: GuildSettings = db.configs[guild_id]
for user_id in guild_conf.users.keys():
profile: Profile = guild_conf.users[user_id]
if user_id not in lb:
lb[user_id] = profile.model_copy() if hasattr(profile, "model_copy") else profile.copy()
else:
lb[user_id].xp += profile.xp
lb[user_id].messages += profile.messages
lb[user_id].voice += profile.voice
lb[user_id].stars += profile.stars
if "xp" in stat and profile.prestige and guild_conf.prestigelevel:
lb[user_id].xp += profile.prestige * guild_conf.algorithm.get_xp(guild_conf.prestigelevel)
lb[user_id].level += profile.prestige * guild_conf.prestigelevel
elif "xp" in stat and conf.prestigelevel and conf.prestigedata:
title = _("LevelUp ")
lb = {}
for user_id in conf.users.keys():
profile: Profile = conf.users[user_id]
lb[user_id] = profile.model_copy() if hasattr(profile, "model_copy") else profile.copy()
if profile.prestige:
lb[user_id].xp += profile.prestige * conf.algorithm.get_xp(conf.prestigelevel)
lb[user_id].level += profile.prestige * conf.prestigelevel
else:
title = _("LevelUp ")
lb = db.get_conf(guild).users.copy()
if "v" in stat:
title += _("Voice Leaderboard")
key = "voice"
emoji = conf.emojis.get("mic", bot)
statname = _("Voicetime")
elif "m" in stat:
title += _("Message Leaderboard")
key = "messages"
emoji = conf.emojis.get("chat", bot)
statname = _("Messages")
elif "s" in stat:
title += _("Star Leaderboard")
key = "stars"
emoji = conf.emojis.get("star", bot)
statname = _("Stars")
else:
title += _("Exp Leaderboard")
key = "xp"
emoji = conf.emojis.get("bulb", bot)
statname = _("Experience")
if is_global:
valid_users: t.Dict[int, t.Union[Profile, ProfileWeekly]] = {k: v for k, v in lb.items() if bot.get_user(k)}
else:
valid_users: t.Dict[int, t.Union[Profile, ProfileWeekly]] = {k: v for k, v in lb.items() if guild.get_member(k)}
filtered_users: t.Dict[int, t.Union[Profile, ProfileWeekly]] = {
k: v for k, v in valid_users.items() if getattr(v, key) > 0
}
if not filtered_users and not dashboard:
txt = _("There is no data for the {} leaderboard yet").format(
_("weekly {}").format(statname) if lbtype == "weekly" else statname
)
return txt
sorted_users = sorted(filtered_users.items(), key=lambda x: getattr(x[1], key), reverse=True)
usercount = len(sorted_users)
func = utils.humanize_delta if "v" in stat else humanize_number
total: str = func(round(sum([getattr(x, key) for x in filtered_users.values()])))
for idx, (user_id, stats) in enumerate(sorted_users):
if member and user_id == member.id:
you = _(" | You: {}").format(f"{idx + 1}/{len(sorted_users)}")
break
else:
you = ""
if lbtype == "weekly":
if dashboard:
desc = _("➣ Total {}: {}\n").format(statname, f"`{total}`{emoji}")
else:
desc = _("➣ **Total {}:** {}\n").format(statname, f"`{total}`{emoji}")
if dashboard:
if weekly.last_reset:
ts = datetime.fromtimestamp(weekly.last_reset).strftime("%m/%d/%Y @ %I:%M:%S %p")
desc += _("➣ Last Reset: {}\n").format(ts)
if weekly.autoreset:
ts = datetime.fromtimestamp(weekly.next_reset).strftime("%m/%d/%Y @ %I:%M:%S %p")
delta = utils.humanize_delta(weekly.next_reset - int(datetime.now().timestamp()))
desc += _("➣ Next Reset: {} ({})\n").format(ts, delta)
else:
if weekly.last_reset:
ts = weekly.last_reset
desc += _("➣ **Last Reset:** {}\n").format(f"<t:{ts}:d> (<t:{ts}:R>)")
if weekly.autoreset:
ts = weekly.next_reset
desc += _("➣ **Next Reset:** {}\n").format(f"<t:{ts}:d> (<t:{ts}:R>)")
desc += "\n"
else:
if dashboard:
desc = _("Total {}: {}\n").format(statname, f"`{total}`{emoji}")
else:
desc = _("**Total {}:** {}\n\n").format(statname, f"`{total}`{emoji}")
if dashboard:
# Format for when dashboard integration calls this function
payload = {
"title": title,
"description": desc.strip(),
"stat": statname,
"total": total,
"type": lbtype,
"user_position": you,
"stats": [],
}
for idx, (user_id, stats) in enumerate(sorted_users):
user_obj = bot.get_user(user_id) if is_global else guild.get_member(user_id)
user = (user_obj.display_name if use_displayname else user_obj.name) if user_obj else user_id
if query:
if query.startswith("#"):
# User search by position
position_query = query[1:]
if idx + 1 != int(position_query):
continue
elif query.isdigit():
# User search by ID or position
if user_id != int(query) and idx + 1 != int(query):
continue
else:
# User search by name
if query.lower() not in str(user).lower():
continue
place = idx + 1
if key == "voice":
stat = utils.humanize_delta(round(getattr(stats, key)))
else:
stat = utils.abbreviate_number(round(getattr(stats, key)))
if key == "xp" and lbtype != "weekly" and not is_global:
stat += f" 🎖{stats.level}"
entry = {"position": place, "name": user, "id": user_id, "stat": stat}
payload["stats"].append(entry)
return payload
embeds = []
pages = math.ceil(len(sorted_users) / 10)
start = 0
stop = 10
for idx in range(pages):
stop = min(usercount, stop)
buffer = StringIO()
for i in range(start, stop):
user_id, stats = sorted_users[i]
user_obj = bot.get_user(user_id) if is_global else guild.get_member(user_id)
name = (user_obj.display_name if use_displayname else user_obj.name) if user_obj else user_id
place = i + 1
if key == "voice":
stat = utils.humanize_delta(round(getattr(stats, key)))
else:
stat = utils.abbreviate_number(round(getattr(stats, key)))
if key == "xp" and lbtype != "weekly" and not is_global:
stat += f" 🎖{stats.level}"
buffer.write(f"**{place}**. {name} (`{stat}`)\n")
embed = discord.Embed(
title=title,
description=desc + buffer.getvalue(),
color=color,
).set_footer(text=_("Page {}").format(f"{idx + 1}/{pages}{you}"), icon_url=guild.icon)
embeds.append(embed)
start += 10
stop += 10
return embeds