didier/data/menus/leaderboards.py

276 lines
7.9 KiB
Python

import math
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Union, Optional
import discord
import requests
from discord import ApplicationContext
from discord.ext import menus
from discord.ext.commands import Context
from data.menus.paginated import Paginated
from enums.numbers import Numbers
from functions import xp
from functions.database import currency, stats, poke, muttn
from functions.utils import get_display_name
@dataclass
class Leaderboard(Paginated, ABC):
highlight: str = None
colour: discord.Colour = discord.Colour.blue()
fetch_names: bool = True
ignore_non_pos: bool = True
def __post_init__(self):
self.data = self.process_data(self.get_data())
@abstractmethod
def get_data(self) -> list[tuple]:
pass
def process_data(self, entries: list[tuple]) -> Optional[list[tuple]]:
data = []
for i, v in enumerate(sorted(entries, key=self.get_value, reverse=True)):
entry_data = self.get_value(v)
# Leaderboard is empty
if i == 0 and entry_data == 0 and self.ignore_non_pos:
return None
# Ignore entries with no data
if self.ignore_non_pos and entry_data <= 0:
continue
data.append((self.get_key(v), f"{entry_data:,}", entry_data,))
return data
def get_key(self, data: tuple):
return data[0]
def get_value(self, data: tuple):
return data[1]
def _should_highlight(self, data) -> bool:
"""Check if an entry should be highlighted"""
if self.fetch_names:
return data == self.ctx.author.id
return data == self.highlight
def format_entry_data(self, data: tuple) -> str:
return str(data[1])
def format_entry(self, index: int, data: tuple) -> str:
name = data[0]
if self.fetch_names:
name = get_display_name(self.ctx, int(data[0]))
s = f"{index + 1}: {name} ({self.format_entry_data(data)})"
if self._should_highlight(data[0]):
return f"**{s}**"
return s
@property
def empty_description(self) -> str:
return ""
async def empty_leaderboard(self, ctx: Union[ApplicationContext, Context]):
embed = discord.Embed(colour=self.colour)
embed.set_author(name=self.title)
embed.description = self.empty_description
if isinstance(ctx, ApplicationContext):
return await ctx.respond(embed=embed)
return await ctx.reply(embed=embed, mention_author=False)
async def respond(self, **kwargs) -> discord.Message:
if self.data is None:
return await self.empty_leaderboard(self.ctx)
return await super().respond(**kwargs)
async def send(self, **kwargs) -> discord.Message:
if self.data is None:
return await self.empty_leaderboard(self.ctx)
return await super().send(**kwargs)
@dataclass
class BitcoinLeaderboard(Leaderboard):
title: str = field(default="Bitcoin Leaderboard")
def get_data(self) -> list[tuple]:
return currency.getAllRows()
def get_value(self, data: tuple):
return round(float(data[8]), 8)
@property
def empty_description(self) -> str:
return "Er zijn nog geen personen met Bitcoins."
@dataclass
class CoronaLeaderboard(Leaderboard):
colour: discord.Colour = field(default=discord.Colour.red())
fetch_names: bool = field(default=False)
highlight: str = field(default="Belgium")
title: str = field(default="Corona Leaderboard")
def get_data(self) -> list[tuple]:
result = requests.get("https://disease.sh/v3/covid-19/countries").json()
result.sort(key=lambda x: int(x["cases"]), reverse=True)
data = []
for country in result:
data.append((country["country"], f"{country['cases']:,}", country["cases"]))
return data
def get_value(self, data: tuple):
return data[2]
@dataclass
class DinksLeaderboard(Leaderboard):
title: str = field(default="Dinks Leaderboard")
def get_data(self) -> list[tuple]:
entries = currency.getAllRows()
platDinks = currency.getAllPlatDinks()
# Take platinum dinks into account
for i, user in enumerate(entries):
if str(user[0]) in platDinks:
# Tuples don't support assignment, cast to list
user = list(user)
user[1] += platDinks[str(user[0])] * Numbers.q.value
entries[i] = user
return entries
def get_value(self, data: tuple):
return float(data[1]) + float(data[3])
@property
def empty_description(self) -> str:
return "Er zijn nog geen personen met Didier Dinks."
@dataclass
class MessageLeaderboard(Leaderboard):
title: str = field(default="Message Leaderboard")
message_count: int = field(init=False)
def get_data(self) -> list[tuple]:
entries = stats.getAllRows()
self.message_count = stats.getTotalMessageCount()
return entries
def get_value(self, data: tuple):
return round(int(data[11]))
def format_entry_data(self, data: tuple) -> str:
perc = round(data[2] * 100 / self.message_count, 2)
return f"{data[2]:,} | {perc}%"
@dataclass
class MuttnLeaderboard(Leaderboard):
title: str = field(default="Muttn Leaderboard")
def get_data(self) -> list[tuple]:
return muttn.getAllRows()
def get_value(self, data: tuple):
return round(float(data[1]), 2)
def format_entry_data(self, data: tuple) -> str:
return f"{data[2]}%"
def empty_description(self) -> str:
return "Der zittn nog geen muttns in de server."
@dataclass
class PokeLeaderboard(Leaderboard):
title: str = field(default="Poke Leaderboard")
def get_data(self) -> list[tuple]:
data = stats.getAllRows()
blacklist = poke.getAllBlacklistedUsers()
return list(filter(lambda x: x[0] not in blacklist, data))
def get_value(self, data: tuple):
return round(int(data[1]))
@property
def empty_description(self) -> str:
return "Er is nog niemand getikt."
@dataclass
class RobLeaderboard(Leaderboard):
title: str = field(default="Rob Leaderboard")
def get_data(self) -> list[tuple]:
return list(stats.getAllRows())
def get_value(self, data: tuple):
return math.floor(float(data[4]))
@property
def empty_description(self) -> str:
return "Er heeft nog niemand Didier Dinks gestolen."
@dataclass
class XPLeaderboard(Leaderboard):
title: str = field(default="XP Leaderboard")
def get_data(self) -> list[tuple]:
return stats.getAllRows()
def get_value(self, data: tuple):
return round(int(data[12]))
def format_entry_data(self, data: tuple) -> str:
entry = data[2]
return f"Level {xp.calculate_level(entry):,} | {entry:,} XP"
class Source(menus.ListPageSource):
def __init__(self, data, name, colour=discord.Colour.blue()):
super().__init__(data, per_page=10)
self.name = name
self.colour = colour
async def format_page(self, menu: menus.MenuPages, entries):
offset = menu.current_page * self.per_page
description = ""
for i, v in enumerate(entries, start=offset):
# Check if the person's name has to be highlighted
if v.startswith("**") and v.endswith("**"):
description += "**"
v = v[2:]
description += "{}: {}\n".format(i + 1, v)
embed = discord.Embed(colour=self.colour)
embed.set_author(name=self.name)
embed.description = description
embed.set_footer(text="{}/{}".format(menu.current_page + 1, self.get_max_pages()))
return embed
class Pages(menus.MenuPages):
def __init__(self, source, clear_reactions_after, timeout=30.0):
super().__init__(source, timeout=timeout, delete_message_after=True, clear_reactions_after=clear_reactions_after)