From 062d54722b73206ba9c2cd45f4c4b164e36f2523 Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Sat, 5 Feb 2022 22:55:09 +0100 Subject: [PATCH 1/4] Rework bc, rob & poke leaderboards --- cogs/events.py | 1 - cogs/leaderboards.py | 115 +++++++++++++++------------- data/menus/custom_commands.py | 1 + data/menus/paginated_leaderboard.py | 6 +- 4 files changed, 65 insertions(+), 58 deletions(-) diff --git a/cogs/events.py b/cogs/events.py index 6696d4c..67abe79 100644 --- a/cogs/events.py +++ b/cogs/events.py @@ -85,7 +85,6 @@ 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/leaderboards.py b/cogs/leaderboards.py index 6eb9edf..b974a0c 100644 --- a/cogs/leaderboards.py +++ b/cogs/leaderboards.py @@ -1,3 +1,5 @@ +from typing import Callable, Optional + from data.menus import paginated_leaderboard from decorators import help import discord @@ -10,7 +12,7 @@ import math import requests -# TODO some sort of general leaderboard because all of them are the same +# TODO some sort of general leaderboard generation because all of them are the same class Leaderboards(commands.Cog): def __init__(self, client): @@ -21,6 +23,26 @@ class Leaderboards(commands.Cog): def cog_check(self, ctx): return not self.client.locked + def _generate_embed_data(self, entries: list, + key_f: Callable = lambda x: x[0], + data_f: Callable = lambda x: x[1], + ignore_non_pos: bool = True) -> Optional[list[tuple]]: + data = [] + for i, v in enumerate(sorted(entries, key=data_f, reverse=True)): + entry_data = data_f(v) + + # Leaderboard is empty + if i == 0 and entry_data == 0 and ignore_non_pos: + return None + + # Ignore entries with no data + if ignore_non_pos and entry_data <= 0: + continue + + data.append((key_f(v), f"{entry_data:,}",)) + + return data + @commands.group(name="Leaderboard", aliases=["Lb", "Leaderboards"], case_insensitive=True, usage="[Categorie]*", invoke_without_command=True) @commands.check(checks.allowedChannels) @@ -45,14 +67,11 @@ class Leaderboards(commands.Cog): 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,)) + data = self._generate_embed_data(entries, key_f=lambda x: x[0], data_f=lambda x: (float(x[1]) + float(x[3]))) + + if data is None: + return await self.empty_leaderboard(ctx, "Dinks Leaderboard", + "Er zijn nog geen personen met Didier Dinks.") lb = paginated_leaderboard.Leaderboard( ctx=ctx, title="Dinks Leaderboard", data=data, fetch_names=True @@ -79,61 +98,49 @@ class Leaderboards(commands.Cog): @leaderboard.command(name="Bitcoin", aliases=["Bc"], hidden=True) async def bitcoin(self, ctx): 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))) + data = self._generate_embed_data(users, data_f=lambda x: round(float(x[8]), 8)) - await self.startPaginated(ctx, boardTop, "Bitcoin Leaderboard") + if data is None: + return await self.empty_leaderboard(ctx, "Bitcoin Leaderboard", + "Er zijn nog geen personen met Bitcoins.") + + lb = paginated_leaderboard.Leaderboard( + ctx=ctx, title="Bitcoin Leaderboard", data=data, fetch_names=True + ) + + await lb.send(ctx) @leaderboard.command(name="Rob", hidden=True) async def rob(self, ctx): 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") + data = self._generate_embed_data(users, data_f=lambda x: math.floor(float(x[4]))) + + if data is None: + return await self.empty_leaderboard(ctx, "Rob Leaderboard", + "Er heeft nog niemand Didier Dinks gestolen.") + + lb = paginated_leaderboard.Leaderboard( + ctx=ctx, title="Rob Leaderboard", data=data, fetch_names=True + ) + + await lb.send(ctx) @leaderboard.command(name="Poke", hidden=True) async def poke(self, ctx): - s = stats.getAllRows() + entries = 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.") + # Remove blacklisted users + entries = list(filter(lambda x: x[0] not in blacklist, entries)) - 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") + data = self._generate_embed_data(entries, data_f=lambda x: round(int(x[1]))) + if data is None: + return await self.empty_leaderboard(ctx, "Poke Leaderboard", "Er is nog niemand getikt.") + + lb = paginated_leaderboard.Leaderboard( + ctx=ctx, title="Poke Leaderboard", data=data, fetch_names=True + ) + + await lb.send(ctx) @leaderboard.command(name="Xp", aliases=["Level"], hidden=True) async def xp(self, ctx): diff --git a/data/menus/custom_commands.py b/data/menus/custom_commands.py index 68c79c9..6e7c215 100644 --- a/data/menus/custom_commands.py +++ b/data/menus/custom_commands.py @@ -2,6 +2,7 @@ import discord from discord.ext import menus +# TODO rework pagination class CommandsList(menus.ListPageSource): def __init__(self, data, colour=discord.Colour.blue()): super().__init__(data, per_page=15) diff --git a/data/menus/paginated_leaderboard.py b/data/menus/paginated_leaderboard.py index dd9bec7..a4b94eb 100644 --- a/data/menus/paginated_leaderboard.py +++ b/data/menus/paginated_leaderboard.py @@ -23,7 +23,7 @@ class Leaderboard: def __post_init__(self): if self.format_f is None: - self.format_f = self._format + self .format_f = lambda x: x def _should_highlight(self, data) -> bool: """Check if an entry should be highlighted""" @@ -38,7 +38,7 @@ class Leaderboard: if self.fetch_names: name = get_display_name(self.ctx, int(data[0])) - s = f"{index + 1}: {name} ({data[1]})" + s = f"{index + 1}: {name} ({self.format_f(data[1])})" return s @@ -64,7 +64,7 @@ class Leaderboard: description = "" for i, v in enumerate(self.data): - s = self.format_f(i, v) + s = self._format(i, v) if self._should_highlight(v[0]): s = f"**{s}**" From ca687956f6c77f5e4e1c66665f09d948ce24f613 Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Sat, 5 Feb 2022 23:21:47 +0100 Subject: [PATCH 2/4] Rework last leaderboards --- cogs/leaderboards.py | 74 +++++++++++++---------------- data/menus/paginated_leaderboard.py | 8 ++-- 2 files changed, 35 insertions(+), 47 deletions(-) diff --git a/cogs/leaderboards.py b/cogs/leaderboards.py index b974a0c..f830307 100644 --- a/cogs/leaderboards.py +++ b/cogs/leaderboards.py @@ -39,7 +39,7 @@ class Leaderboards(commands.Cog): if ignore_non_pos and entry_data <= 0: continue - data.append((key_f(v), f"{entry_data:,}",)) + data.append((key_f(v), f"{entry_data:,}", entry_data,)) return data @@ -144,60 +144,50 @@ class Leaderboards(commands.Cog): @leaderboard.command(name="Xp", aliases=["Level"], hidden=True) async def xp(self, ctx): - s = stats.getAllRows() - boardTop = [] - for i, user in enumerate(sorted(s, key=lambda x: x[12], reverse=True)): - if int(user[12]) == 0: - break + entries = stats.getAllRows() + data = self._generate_embed_data(entries, data_f=lambda x: round(int(x[12]))) - 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") + def _format_entry(entry: int) -> str: + return f"Level {xp.calculate_level(entry):,} | {entry:,} XP" + + lb = paginated_leaderboard.Leaderboard( + ctx=ctx, title="XP Leaderboard", data=data, fetch_names=True, format_f=_format_entry + ) + + await lb.send(ctx) @leaderboard.command(name="Messages", aliases=["Mc", "Mess"], hidden=True) async def messages(self, ctx): - s = stats.getAllRows() - boardTop = [] - + entries = stats.getAllRows() message_count = stats.getTotalMessageCount() - for i, user in enumerate(sorted(s, key=lambda x: x[11], reverse=True)): - if int(user[11]) == 0: - break + data = self._generate_embed_data(entries, data_f=lambda x: round(int(x[11]))) - perc = round(int(user[11]) * 100 / message_count, 2) + def _format_entry(entry: int) -> str: + perc = round(entry * 100 / message_count, 2) + return f"{entry:,} | {perc}%" - 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") + lb = paginated_leaderboard.Leaderboard( + ctx=ctx, title="Messages Leaderboard", data=data, fetch_names=True, format_f=_format_entry + ) + + await lb.send(ctx) @leaderboard.command(name="Muttn", aliases=["M", "Mutn", "Mutten"], hidden=True) async def muttn(self, ctx): - 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.") + entries = muttn.getAllRows() + data = self._generate_embed_data(entries, data_f=lambda x: round(float(x[1]), 2)) + if data is None: + return await self.empty_leaderboard(ctx, "Muttn Leaderboard", "Der zittn nog geen muttns in de server.") - if float(user[1]) == 0: - break + def _format_entry(entry: float) -> str: + return f"{entry}%" - 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") + lb = paginated_leaderboard.Leaderboard( + ctx=ctx, title="Muttn Leaderboard", data=data, fetch_names=True, format_f=_format_entry + ) + + await lb.send(ctx) async def callLeaderboard(self, name, ctx): command = [command for command in self.leaderboard.commands if command.name.lower() == name.lower()][0] diff --git a/data/menus/paginated_leaderboard.py b/data/menus/paginated_leaderboard.py index a4b94eb..5017e4f 100644 --- a/data/menus/paginated_leaderboard.py +++ b/data/menus/paginated_leaderboard.py @@ -21,10 +21,6 @@ class Leaderboard: colour: discord.Colour = discord.Colour.blue() fetch_names: bool = False - def __post_init__(self): - if self.format_f is None: - self .format_f = lambda x: x - def _should_highlight(self, data) -> bool: """Check if an entry should be highlighted""" if self.fetch_names: @@ -38,7 +34,9 @@ class Leaderboard: if self.fetch_names: name = get_display_name(self.ctx, int(data[0])) - s = f"{index + 1}: {name} ({self.format_f(data[1])})" + formatted_data = self.format_f(data[2]) if self.format_f is not None else data[1] + + s = f"{index + 1}: {name} ({formatted_data})" return s From 81a0d90a12615efadf2654a296b49d1e3589c518 Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Sun, 6 Feb 2022 01:11:40 +0100 Subject: [PATCH 3/4] Leaderboard cleanup --- cogs/fun.py | 2 +- cogs/leaderboards.py | 174 +++--------------- cogs/train.py | 2 +- data/menus/leaderboards.py | 275 ++++++++++++++++++++++++++++ data/menus/paginated.py | 67 +++++++ data/menus/paginated_leaderboard.py | 125 ------------- 6 files changed, 365 insertions(+), 280 deletions(-) create mode 100644 data/menus/leaderboards.py create mode 100644 data/menus/paginated.py delete mode 100644 data/menus/paginated_leaderboard.py diff --git a/cogs/fun.py b/cogs/fun.py index affd2c9..cb938bb 100644 --- a/cogs/fun.py +++ b/cogs/fun.py @@ -1,5 +1,5 @@ from data.embeds.xkcd import XKCDEmbed -from data.menus import paginated_leaderboard +from data.menus import leaderboards from decorators import help import discord from discord.ext import commands diff --git a/cogs/leaderboards.py b/cogs/leaderboards.py index f830307..9f41c5b 100644 --- a/cogs/leaderboards.py +++ b/cogs/leaderboards.py @@ -1,18 +1,12 @@ -from typing import Callable, Optional - -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 enums.numbers import Numbers -from functions import checks, xp -from functions.database import currency, stats, poke, muttn -import math -import requests +from functions import checks -# TODO some sort of general leaderboard generation because all of them are the same class Leaderboards(commands.Cog): def __init__(self, client): @@ -20,29 +14,9 @@ class Leaderboards(commands.Cog): self.utilsCog = self.client.get_cog("Utils") # Don't allow any commands to work when locked - def cog_check(self, ctx): + def cog_check(self, _): return not self.client.locked - def _generate_embed_data(self, entries: list, - key_f: Callable = lambda x: x[0], - data_f: Callable = lambda x: x[1], - ignore_non_pos: bool = True) -> Optional[list[tuple]]: - data = [] - for i, v in enumerate(sorted(entries, key=data_f, reverse=True)): - entry_data = data_f(v) - - # Leaderboard is empty - if i == 0 and entry_data == 0 and ignore_non_pos: - return None - - # Ignore entries with no data - if ignore_non_pos and entry_data <= 0: - continue - - data.append((key_f(v), f"{entry_data:,}", entry_data,)) - - return data - @commands.group(name="Leaderboard", aliases=["Lb", "Leaderboards"], case_insensitive=True, usage="[Categorie]*", invoke_without_command=True) @commands.check(checks.allowedChannels) @@ -56,154 +30,48 @@ class Leaderboards(commands.Cog): @leaderboard.command(name="Dinks", aliases=["Cash"], hidden=True) async def dinks(self, ctx): - 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 = self._generate_embed_data(entries, key_f=lambda x: x[0], data_f=lambda x: (float(x[1]) + float(x[3]))) - - if data is None: - return await self.empty_leaderboard(ctx, "Dinks Leaderboard", - "Er zijn nog geen personen met Didier Dinks.") - - lb = paginated_leaderboard.Leaderboard( - ctx=ctx, title="Dinks Leaderboard", data=data, fetch_names=True - ) - - await lb.send(ctx) + lb = leaderboards.DinksLeaderboard(ctx=ctx) + await lb.send() @leaderboard.command(name="Corona", hidden=True) async def corona(self, ctx): - 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) + lb = leaderboards.CoronaLeaderboard(ctx=ctx) + await lb.send() @leaderboard.command(name="Bitcoin", aliases=["Bc"], hidden=True) async def bitcoin(self, ctx): - users = currency.getAllRows() - data = self._generate_embed_data(users, data_f=lambda x: round(float(x[8]), 8)) - - if data is None: - return await self.empty_leaderboard(ctx, "Bitcoin Leaderboard", - "Er zijn nog geen personen met Bitcoins.") - - lb = paginated_leaderboard.Leaderboard( - ctx=ctx, title="Bitcoin Leaderboard", data=data, fetch_names=True - ) - - await lb.send(ctx) + lb = leaderboards.BitcoinLeaderboard(ctx=ctx) + await lb.send() @leaderboard.command(name="Rob", hidden=True) async def rob(self, ctx): - users = list(stats.getAllRows()) - data = self._generate_embed_data(users, data_f=lambda x: math.floor(float(x[4]))) - - if data is None: - return await self.empty_leaderboard(ctx, "Rob Leaderboard", - "Er heeft nog niemand Didier Dinks gestolen.") - - lb = paginated_leaderboard.Leaderboard( - ctx=ctx, title="Rob Leaderboard", data=data, fetch_names=True - ) - - await lb.send(ctx) + lb = leaderboards.RobLeaderboard(ctx=ctx) + await lb.send() @leaderboard.command(name="Poke", hidden=True) async def poke(self, ctx): - entries = stats.getAllRows() - blacklist = poke.getAllBlacklistedUsers() - # Remove blacklisted users - entries = list(filter(lambda x: x[0] not in blacklist, entries)) - - data = self._generate_embed_data(entries, data_f=lambda x: round(int(x[1]))) - if data is None: - return await self.empty_leaderboard(ctx, "Poke Leaderboard", "Er is nog niemand getikt.") - - lb = paginated_leaderboard.Leaderboard( - ctx=ctx, title="Poke Leaderboard", data=data, fetch_names=True - ) - - await lb.send(ctx) + lb = leaderboards.PokeLeaderboard(ctx=ctx) + await lb.send() @leaderboard.command(name="Xp", aliases=["Level"], hidden=True) async def xp(self, ctx): - entries = stats.getAllRows() - data = self._generate_embed_data(entries, data_f=lambda x: round(int(x[12]))) - - def _format_entry(entry: int) -> str: - return f"Level {xp.calculate_level(entry):,} | {entry:,} XP" - - lb = paginated_leaderboard.Leaderboard( - ctx=ctx, title="XP Leaderboard", data=data, fetch_names=True, format_f=_format_entry - ) - - await lb.send(ctx) + lb = leaderboards.XPLeaderboard(ctx=ctx) + await lb.send() @leaderboard.command(name="Messages", aliases=["Mc", "Mess"], hidden=True) async def messages(self, ctx): - entries = stats.getAllRows() - message_count = stats.getTotalMessageCount() - - data = self._generate_embed_data(entries, data_f=lambda x: round(int(x[11]))) - - def _format_entry(entry: int) -> str: - perc = round(entry * 100 / message_count, 2) - return f"{entry:,} | {perc}%" - - lb = paginated_leaderboard.Leaderboard( - ctx=ctx, title="Messages Leaderboard", data=data, fetch_names=True, format_f=_format_entry - ) - - await lb.send(ctx) + lb = leaderboards.MessageLeaderboard(ctx=ctx) + await lb.send() @leaderboard.command(name="Muttn", aliases=["M", "Mutn", "Mutten"], hidden=True) async def muttn(self, ctx): - entries = muttn.getAllRows() - data = self._generate_embed_data(entries, data_f=lambda x: round(float(x[1]), 2)) - if data is None: - return await self.empty_leaderboard(ctx, "Muttn Leaderboard", "Der zittn nog geen muttns in de server.") - - def _format_entry(entry: float) -> str: - return f"{entry}%" - - lb = paginated_leaderboard.Leaderboard( - ctx=ctx, title="Muttn Leaderboard", data=data, fetch_names=True, format_f=_format_entry - ) - - await lb.send(ctx) + lb = leaderboards.MuttnLeaderboard(ctx=ctx) + await lb.send() 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/train.py b/cogs/train.py index 5865bf8..4f643ff 100644 --- a/cogs/train.py +++ b/cogs/train.py @@ -1,4 +1,4 @@ -from data.menus import paginated_leaderboard +from data.menus import leaderboards from decorators import help import discord from discord.ext import commands, menus diff --git a/data/menus/leaderboards.py b/data/menus/leaderboards.py new file mode 100644 index 0000000..bd6e342 --- /dev/null +++ b/data/menus/leaderboards.py @@ -0,0 +1,275 @@ +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) diff --git a/data/menus/paginated.py b/data/menus/paginated.py new file mode 100644 index 0000000..7a06cc3 --- /dev/null +++ b/data/menus/paginated.py @@ -0,0 +1,67 @@ +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 deleted file mode 100644 index 5017e4f..0000000 --- a/data/menus/paginated_leaderboard.py +++ /dev/null @@ -1,125 +0,0 @@ -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 _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])) - - formatted_data = self.format_f(data[2]) if self.format_f is not None else data[1] - - s = f"{index + 1}: {name} ({formatted_data})" - - 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(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) From a71232e292c39d72b89ba81b1e87b77e9e76d1a9 Mon Sep 17 00:00:00 2001 From: Stijn De Clercq Date: Sun, 6 Feb 2022 01:34:00 +0100 Subject: [PATCH 4/4] Custom commands cleanup, remove train for my own sanity --- cogs/other.py | 14 ++-- cogs/train.py | 127 ---------------------------------- data/menus/custom_commands.py | 34 ++++----- data/menus/leaderboards.py | 39 ++--------- 4 files changed, 24 insertions(+), 190 deletions(-) delete mode 100644 cogs/train.py diff --git a/cogs/other.py b/cogs/other.py index 0c62b8d..859c55d 100644 --- a/cogs/other.py +++ b/cogs/other.py @@ -5,8 +5,6 @@ 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 @@ -14,10 +12,9 @@ class Other(commands.Cog): def __init__(self, client: Didier): self.client: Didier = client - # 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 + # Don't allow any commands to work when locked + def cog_check(self, _): + return not self.client.locked @commands.command(name="Custom") @help.Category(category=Category.Didier) @@ -25,10 +22,7 @@ class Other(commands.Cog): """ Get a list of all custom commands """ - 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) + await custom_commands.CommandsList(ctx).send() @commands.command(name="Snipe") @help.Category(category=Category.Other) diff --git a/cogs/train.py b/cogs/train.py deleted file mode 100644 index 4f643ff..0000000 --- a/cogs/train.py +++ /dev/null @@ -1,127 +0,0 @@ -from data.menus import leaderboards -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 6e7c215..cb5261b 100644 --- a/data/menus/custom_commands.py +++ b/data/menus/custom_commands.py @@ -1,22 +1,18 @@ -import discord -from discord.ext import menus +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 -# TODO rework pagination -class CommandsList(menus.ListPageSource): - def __init__(self, data, colour=discord.Colour.blue()): - super().__init__(data, per_page=15) - self.colour = colour +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) - 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) + def format_entry(self, index: int, value: tuple) -> str: + return value[0] diff --git a/data/menus/leaderboards.py b/data/menus/leaderboards.py index bd6e342..f35652f 100644 --- a/data/menus/leaderboards.py +++ b/data/menus/leaderboards.py @@ -6,7 +6,6 @@ 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 @@ -80,7 +79,7 @@ class Leaderboard(Paginated, ABC): def empty_description(self) -> str: return "" - async def empty_leaderboard(self, ctx: Union[ApplicationContext, Context]): + 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 @@ -88,19 +87,19 @@ class Leaderboard(Paginated, ABC): if isinstance(ctx, ApplicationContext): return await ctx.respond(embed=embed) - return await ctx.reply(embed=embed, mention_author=False) + 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) + 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) + return await self.empty_leaderboard(self.ctx, mention_author=False, **kwargs) - return await super().send(**kwargs) + return await super().send(mention_author=False, **kwargs) @dataclass @@ -245,31 +244,3 @@ class XPLeaderboard(Leaderboard): 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)