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)