diff --git a/cogs/events.py b/cogs/events.py index 67abe79..6696d4c 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -85,6 +85,7 @@ class Events(commands.Cog): Logs commands in your terminal. :param ctx: Discord Context """ + print("a") print(stringFormatters.format_command_usage(ctx)) command_stats.invoked(command_stats.InvocationType.TextCommand) diff --git a/cogs/fun.py b/cogs/fun.py index cb938bb..affd2c9 100644 --- a/cogs/fun.py +++ b/cogs/fun.py @@ -1,5 +1,5 @@ from data.embeds.xkcd import XKCDEmbed -from data.menus import leaderboards +from data.menus import paginated_leaderboard from decorators import help import discord from discord.ext import commands diff --git a/cogs/leaderboards.py b/cogs/leaderboards.py index 9f41c5b..6eb9edf 100644 --- a/cogs/leaderboards.py +++ b/cogs/leaderboards.py @@ -1,12 +1,16 @@ +from data.menus import paginated_leaderboard +from decorators import help import discord from discord.ext import commands - -from data.menus import leaderboards -from decorators import help from enums.help_categories import Category -from functions import checks +from enums.numbers import Numbers +from functions import checks, xp +from functions.database import currency, stats, poke, muttn +import math +import requests +# TODO some sort of general leaderboard because all of them are the same class Leaderboards(commands.Cog): def __init__(self, client): @@ -14,7 +18,7 @@ class Leaderboards(commands.Cog): self.utilsCog = self.client.get_cog("Utils") # Don't allow any commands to work when locked - def cog_check(self, _): + def cog_check(self, ctx): return not self.client.locked @commands.group(name="Leaderboard", aliases=["Lb", "Leaderboards"], case_insensitive=True, usage="[Categorie]*", @@ -30,48 +34,179 @@ class Leaderboards(commands.Cog): @leaderboard.command(name="Dinks", aliases=["Cash"], hidden=True) async def dinks(self, ctx): - lb = leaderboards.DinksLeaderboard(ctx=ctx) - await lb.send() + 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 + + data = [] + for i, user in enumerate(sorted(entries, key=lambda x: (float(x[1]) + float(x[3])), reverse=True)): + if i == 0 and float(user[1]) + float(user[3]) == 0.0: + return await self.empty_leaderboard(ctx, "Dinks Leaderboard", + "Er zijn nog geen personen met Didier Dinks.") + elif float(user[1]) + float(user[3]) > 0.0: + total_dinks = math.floor(float(user[1]) + float(user[3])) + data.append((user[0], total_dinks,)) + + lb = paginated_leaderboard.Leaderboard( + ctx=ctx, title="Dinks Leaderboard", data=data, fetch_names=True + ) + + await lb.send(ctx) @leaderboard.command(name="Corona", hidden=True) async def corona(self, ctx): - lb = leaderboards.CoronaLeaderboard(ctx=ctx) - await lb.send() + 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']:,}",)) + + lb = paginated_leaderboard.Leaderboard( + ctx=ctx, title="Corona Leaderboard", data=data, highlight="Belgium", + colour=discord.Colour.red() + ) + + await lb.send(ctx) @leaderboard.command(name="Bitcoin", aliases=["Bc"], hidden=True) async def bitcoin(self, ctx): - lb = leaderboards.BitcoinLeaderboard(ctx=ctx) - await lb.send() + users = currency.getAllRows() + boardTop = [] + for i, user in enumerate(sorted(users, key=lambda x: x[8], reverse=True)): + # Don't create an empty leaderboard + if i == 0 and float(user[8]) == 0.0: + return await self.empty_leaderboard(ctx, "Bitcoin Leaderboard", + "Er zijn nog geen personen met Bitcoins.") + elif float(user[8]) > 0.0: + # Only add people with more than 0 + # Get the username in this guild + name = self.utilsCog.getDisplayName(ctx, user[0]) + if int(user[0]) == int(ctx.author.id): + boardTop.append("**{} ({:,})**".format(name, round(user[8], 8))) + else: + boardTop.append("{} ({:,})".format(name, round(user[8], 8))) + + await self.startPaginated(ctx, boardTop, "Bitcoin Leaderboard") @leaderboard.command(name="Rob", hidden=True) async def rob(self, ctx): - lb = leaderboards.RobLeaderboard(ctx=ctx) - await lb.send() + users = list(stats.getAllRows()) + boardTop = [] + for i, user in enumerate(sorted(users, key=lambda x: x[4], reverse=True)): + # Don't create an empty leaderboard + if i == 0 and float(user[4]) == 0.0: + return await self.empty_leaderboard(ctx, "Rob Leaderboard", + "Er heeft nog niemand Didier Dinks gestolen.") + elif float(user[4]) > 0.0: + # Only add people with more than 0 + # Get the username in this guild + name = self.utilsCog.getDisplayName(ctx, user[0]) + if int(user[0]) == int(ctx.author.id): + boardTop.append("**{} ({:,})**".format(name, math.floor(float(user[4])))) + else: + boardTop.append("{} ({:,})".format(name, math.floor(float(user[4])))) + await self.startPaginated(ctx, boardTop, "Rob Leaderboard") @leaderboard.command(name="Poke", hidden=True) async def poke(self, ctx): - lb = leaderboards.PokeLeaderboard(ctx=ctx) - await lb.send() + s = stats.getAllRows() + blacklist = poke.getAllBlacklistedUsers() + boardTop = [] + for i, user in enumerate(sorted(s, key=lambda x: x[1], reverse=True)): + if i == 0 and int(user[1]) == 0: + return await self.empty_leaderboard(ctx, "Poke Leaderboard", "Er is nog niemand getikt.") + + elif int(user[1]) == 0: + break + # Don't include blacklisted users + elif str(user[0]) not in blacklist: + name = self.utilsCog.getDisplayName(ctx, user[0]) + if int(user[0]) == int(ctx.author.id): + boardTop.append("**{} ({:,})**".format(name, round(int(user[1])))) + else: + boardTop.append("{} ({:,})".format(name, round(int(user[1])))) + await self.startPaginated(ctx, boardTop, "Poke Leaderboard") @leaderboard.command(name="Xp", aliases=["Level"], hidden=True) async def xp(self, ctx): - lb = leaderboards.XPLeaderboard(ctx=ctx) - await lb.send() + s = stats.getAllRows() + boardTop = [] + for i, user in enumerate(sorted(s, key=lambda x: x[12], reverse=True)): + if int(user[12]) == 0: + break + + name = self.utilsCog.getDisplayName(ctx, user[0]) + if int(user[0]) == int(ctx.author.id): + boardTop.append("**{} (Level {:,} | {:,} XP)**".format(name, + xp.calculate_level(round(int(user[12]))), + round(int(user[12])))) + else: + boardTop.append("{} (Level {:,} | {:,} XP)".format(name, + xp.calculate_level(round(int(user[12]))), + round(int(user[12])))) + await self.startPaginated(ctx, boardTop, "XP Leaderboard") @leaderboard.command(name="Messages", aliases=["Mc", "Mess"], hidden=True) async def messages(self, ctx): - lb = leaderboards.MessageLeaderboard(ctx=ctx) - await lb.send() + s = stats.getAllRows() + boardTop = [] + + message_count = stats.getTotalMessageCount() + + for i, user in enumerate(sorted(s, key=lambda x: x[11], reverse=True)): + if int(user[11]) == 0: + break + + perc = round(int(user[11]) * 100 / message_count, 2) + + name = self.utilsCog.getDisplayName(ctx, user[0]) + if int(user[0]) == int(ctx.author.id): + boardTop.append("**{} ({:,} | {}%)**".format(name, round(int(user[11])), perc)) + else: + boardTop.append("{} ({:,} | {}%)".format(name, round(int(user[11])), perc)) + await self.startPaginated(ctx, boardTop, "Messages Leaderboard") @leaderboard.command(name="Muttn", aliases=["M", "Mutn", "Mutten"], hidden=True) async def muttn(self, ctx): - lb = leaderboards.MuttnLeaderboard(ctx=ctx) - await lb.send() + users = muttn.getAllRows() + boardTop = [] + for i, user in enumerate(sorted(users, key=lambda x: x[1], reverse=True)): + if i == 0 and int(user[1]) == 0: + return await self.empty_leaderboard(ctx, "Muttn Leaderboard", "Der zittn nog geen muttns in de server.") + + if float(user[1]) == 0: + break + + name = self.utilsCog.getDisplayName(ctx, user[0]) + if int(user[0]) == int(ctx.author.id): + boardTop.append("**{} ({})%**".format(name, round(float(user[1]), 2))) + else: + boardTop.append("{} ({}%)".format(name, round(float(user[1]), 2))) + await self.startPaginated(ctx, boardTop, "Muttn Leaderboard") async def callLeaderboard(self, name, ctx): command = [command for command in self.leaderboard.commands if command.name.lower() == name.lower()][0] await command(ctx) + async def startPaginated(self, ctx, source, name, colour=discord.Colour.blue()): + pages = paginated_leaderboard.Pages(source=paginated_leaderboard.Source(source, name, colour), + clear_reactions_after=True) + await pages.start(ctx) + + async def empty_leaderboard(self, ctx, name, message, colour=discord.Colour.blue()): + embed = discord.Embed(colour=colour) + embed.set_author(name=name) + embed.description = message + await ctx.send(embed=embed) + def setup(client): client.add_cog(Leaderboards(client)) diff --git a/cogs/other.py b/cogs/other.py index 859c55d..0c62b8d 100644 --- a/cogs/other.py +++ b/cogs/other.py @@ -5,6 +5,8 @@ from data.menus import custom_commands from data.snipe import Action, Snipe from decorators import help from enums.help_categories import Category +from functions.database.custom_commands import get_all +from functions.stringFormatters import capitalize from startup.didier import Didier @@ -12,9 +14,10 @@ class Other(commands.Cog): def __init__(self, client: Didier): self.client: Didier = client - # Don't allow any commands to work when locked - def cog_check(self, _): - return not self.client.locked + # TODO add locked field to Didier instead of client + # # Don't allow any commands to work when locked + # def cog_check(self, ctx): + # return not self.client.locked @commands.command(name="Custom") @help.Category(category=Category.Didier) @@ -22,7 +25,10 @@ class Other(commands.Cog): """ Get a list of all custom commands """ - await custom_commands.CommandsList(ctx).send() + all_commands = get_all() + formatted = list(sorted(map(lambda x: capitalize(x["name"]), all_commands))) + src = custom_commands.CommandsList(formatted) + await custom_commands.Pages(source=src, clear_reactions_after=True).start(ctx) @commands.command(name="Snipe") @help.Category(category=Category.Other) diff --git a/cogs/train.py b/cogs/train.py new file mode 100644 index 0000000..5865bf8 --- /dev/null +++ b/cogs/train.py @@ -0,0 +1,127 @@ +from data.menus import paginated_leaderboard +from decorators import help +import discord +from discord.ext import commands, menus +from enums.help_categories import Category +from functions import checks, timeFormatters +import requests + + +class Train(commands.Cog): + + def __init__(self, client): + self.client = client + + # Don't allow any commands to work when locked + def cog_check(self, ctx): + return not self.client.locked + + @commands.command(name="Train", aliases=["Trein"], usage="[Vertrek]* [Bestemming]") + @help.Category(category=Category.School) + async def train(self, ctx, *args): + if not args or len(args) > 2: + await ctx.send("Controleer je argumenten.") + return + destination = args[-1] + departure = args[0] if len(args) > 1 else "Gent Sint-Pieters" + + req = requests.get( + "http://api.irail.be/connections/?from={}&to={}&alerts=true&lang=nl&format=json".format(departure, + destination)).json() + if "error" in req: + embed = discord.Embed(colour=discord.Colour.red()) + embed.set_author(name="Treinen van {} naar {}".format( + self.formatCity(departure), self.formatCity(destination))) + embed.add_field(name="Error", value="Er ging iets fout, probeer het later opnieuw.", inline=False) + await self.sendEmbed(ctx, embed) + return + + pages = paginated_leaderboard.Pages(source=TrainPagination(self.formatConnections(req["connection"]), + self.formatCity(departure), + self.formatCity(destination)), + clear_reactions_after=True) + await pages.start(ctx) + + def formatConnections(self, connections): + response = [] + for connection in sorted(connections, key=lambda con: con["departure"]["time"]): + conn = {} + if connection["departure"]["canceled"] != "0" or connection["arrival"]["canceled"] != "0": + conn = {"Canceled": "Afgeschaft"} + dep = connection["departure"] + arr = connection["arrival"] + conn["depStation"] = self.formatCity(dep["station"]) + conn["depTime"] = self.formatTime(dep["time"]) + conn["delay"] = self.formatDelay(dep["delay"]) + conn["track"] = dep["platform"] + conn["arrStation"] = self.formatCity(arr["station"]) + conn["direction"] = self.formatCity(dep["direction"]["name"]) + conn["arrTime"] = self.formatTime(arr["time"]) + conn["duration"] = self.formatTime(connection["duration"]) + response.append(conn) + return response + + def formatTime(self, timestamp): + if int(timestamp) <= 86400: + minutes = int(timestamp) // 60 + if minutes < 60: + return str(minutes) + "m" + return "{}h{:02}m".format(minutes // 60, minutes % 60) + else: + return timeFormatters.epochToDate(int(timestamp), "%H:%M")["date"] + + def formatDelay(self, seconds): + seconds = int(seconds) + return self.sign(seconds) + self.formatTime(abs(seconds)) if seconds != 0 else "" + + def sign(self, number): + return "-" if int(number) < 0 else "+" + + def formatCity(self, city): + city = city[0].upper() + city[1:] + arr = [] + for i, letter in enumerate(city): + if (i > 0 and (city[i - 1] == " " or city[i - 1] == "-")) or i == 0: + arr.append(letter.upper()) + else: + arr.append(letter.lower()) + return "".join(arr) + + async def sendEmbed(self, ctx, embed): + if await checks.allowedChannels(ctx): + await ctx.send(embed=embed) + else: + await ctx.author.send(embed=embed) + + +class TrainPagination(menus.ListPageSource): + def __init__(self, data, departure, destination): + super().__init__(data, per_page=3) + self.departure = departure + self.destination = destination + + async def format_page(self, menu: menus.MenuPages, entries): + offset = menu.current_page * self.per_page + embed = discord.Embed(colour=discord.Colour.blue()) + embed.set_author(name="Treinen van {} naar {}".format(self.departure, self.destination)) + embed.set_footer(text="{}/{}".format(menu.current_page + 1, self.get_max_pages())) + + for i, connection in enumerate(entries, start=offset): + afgeschaft = "Canceled" in connection + embed.add_field(name="Van", value=str(connection["depStation"]), inline=True) + embed.add_field(name="Om", value=str(connection["depTime"]), inline=True) + embed.add_field(name="Spoor", value=str(connection["track"]), inline=True) + embed.add_field(name="Richting", value=str(connection["direction"]), inline=True) + embed.add_field(name="Aankomst", value=(str(connection["arrTime"]) + if not afgeschaft else "**AFGESCHAFT**"), inline=True) + embed.add_field(name="Vertraging", value=str(connection["delay"]) if connection["delay"] != "" else "0", + inline=True) + + # White space + if i - offset < 2: + embed.add_field(name="\u200b", value="\u200b", inline=False) + return embed + + +def setup(client): + client.add_cog(Train(client)) diff --git a/data/menus/custom_commands.py b/data/menus/custom_commands.py index cb5261b..68c79c9 100644 --- a/data/menus/custom_commands.py +++ b/data/menus/custom_commands.py @@ -1,18 +1,21 @@ -from typing import Union - -from discord import ApplicationContext -from discord.ext.commands import Context - -from data.menus.paginated import Paginated -from functions.database.custom_commands import get_all -from functions.stringFormatters import capitalize +import discord +from discord.ext import menus -class CommandsList(Paginated): - def __init__(self, ctx: Union[ApplicationContext, Context]): - all_commands = get_all() - commands_sorted = list(sorted(map(lambda x: (capitalize(x["name"]),), all_commands))) - super().__init__(ctx=ctx, title="Custom Commands", data=commands_sorted, per_page=15) +class CommandsList(menus.ListPageSource): + def __init__(self, data, colour=discord.Colour.blue()): + super().__init__(data, per_page=15) + self.colour = colour - def format_entry(self, index: int, value: tuple) -> str: - return value[0] + async def format_page(self, menu: menus.MenuPages, entries): + embed = discord.Embed(colour=self.colour) + embed.set_author(name="Custom Commands") + embed.description = "\n".join(entries) + 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) diff --git a/data/menus/leaderboards.py b/data/menus/leaderboards.py deleted file mode 100644 index f35652f..0000000 --- a/data/menus/leaderboards.py +++ /dev/null @@ -1,246 +0,0 @@ -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.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], **kwargs): - 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, **kwargs) - - async def respond(self, **kwargs) -> discord.Message: - if self.data is None: - return await self.empty_leaderboard(self.ctx, **kwargs) - - return await super().respond(**kwargs) - - async def send(self, **kwargs) -> discord.Message: - if self.data is None: - return await self.empty_leaderboard(self.ctx, mention_author=False, **kwargs) - - return await super().send(mention_author=False, **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" diff --git a/data/menus/paginated.py b/data/menus/paginated.py deleted file mode 100644 index 7a06cc3..0000000 --- a/data/menus/paginated.py +++ /dev/null @@ -1,67 +0,0 @@ -from abc import ABC, abstractmethod -from dataclasses import dataclass -from typing import Union - -import discord -from discord import ApplicationContext -from discord.ext import pages -from discord.ext.commands import Context - - -@dataclass -class Paginated(ABC): - """Abstract class to support paginated menus easily""" - ctx: Union[ApplicationContext, Context] - title: str - data: list[tuple] = None - per_page: int = 10 - colour: discord.Colour = discord.Colour.blue() - - def create_embed(self, description: str) -> discord.Embed: - embed = discord.Embed(colour=self.colour) - embed.set_author(name=self.title) - embed.description = description - - return embed - - @abstractmethod - def format_entry(self, index: int, value: tuple) -> str: - pass - - def create_pages(self, data: list[tuple]) -> list[discord.Embed]: - # Amount of entries added to this page - added = 0 - page_list = [] - - description = "" - for i, v in enumerate(data): - s = self.format_entry(i, v) - - description += s + "\n" - added += 1 - - # Page full, create an embed & change counters - if added == self.per_page: - embed = self.create_embed(description) - - description = "" - added = 0 - page_list.append(embed) - - # Add final embed if necessary - if added != 0: - embed = self.create_embed(description) - page_list.append(embed) - - return page_list - - def create_paginator(self) -> pages.Paginator: - return pages.Paginator(pages=self.create_pages(self.data), show_disabled=False, disable_on_timeout=True, timeout=30) - - async def respond(self, **kwargs) -> discord.Message: - paginator = self.create_paginator() - return await paginator.respond(self.ctx.interaction, **kwargs) - - async def send(self, **kwargs) -> discord.Message: - paginator = self.create_paginator() - return await paginator.send(self.ctx, **kwargs) diff --git a/data/menus/paginated_leaderboard.py b/data/menus/paginated_leaderboard.py new file mode 100644 index 0000000..dd9bec7 --- /dev/null +++ b/data/menus/paginated_leaderboard.py @@ -0,0 +1,127 @@ +from typing import Callable + +import discord +from discord import ApplicationContext +from discord.ext import menus, pages +from dataclasses import dataclass + +from discord.ext.commands import Context + +from functions.utils import get_display_name + + +@dataclass +class Leaderboard: + ctx: Context + title: str + data: list + highlight: str = None + format_f: Callable = None + per_page: int = 10 + colour: discord.Colour = discord.Colour.blue() + fetch_names: bool = False + + def __post_init__(self): + if self.format_f is None: + self.format_f = self._format + + 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(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} ({data[1]})" + + return s + + def _get_page_count(self) -> int: + """Get the amount of pages required to represent this data""" + count = len(self.data) // self.per_page + if len(self.data) % self.per_page != 0: + count += 1 + + return count + + def _create_embed(self, description: str) -> discord.Embed: + embed = discord.Embed(colour=self.colour) + embed.set_author(name=self.title) + embed.description = description + + return embed + + def create_pages(self) -> list[discord.Embed]: + # Amount of entries added to this page + added = 0 + page_list = [] + + description = "" + for i, v in enumerate(self.data): + s = self.format_f(i, v) + + if self._should_highlight(v[0]): + s = f"**{s}**" + + description += s + "\n" + added += 1 + + # Page full, create an embed & change counters + if added == self.per_page: + embed = self._create_embed(description) + + description = "" + added = 0 + page_list.append(embed) + + # Add final embed + if added != 0: + embed = self._create_embed(description) + page_list.append(embed) + + return page_list + + def create_paginator(self) -> pages.Paginator: + return pages.Paginator(pages=self.create_pages(), show_disabled=False, disable_on_timeout=True, timeout=30) + + async def respond(self, ctx: ApplicationContext, **kwargs) -> discord.Message: + paginator = self.create_paginator() + return await paginator.respond(ctx.interaction, **kwargs) + + async def send(self, ctx: Context, **kwargs) -> discord.Message: + paginator = self.create_paginator() + return await paginator.send(ctx, **kwargs) + + +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)