From 2aac5ce66e9f6d8481d5e32490f92f450daddba3 Mon Sep 17 00:00:00 2001 From: adam757521 <73548219+adam757521@users.noreply.github.com> Date: Thu, 28 Oct 2021 16:59:32 +0300 Subject: [PATCH 01/13] Added page_format to generate_embeds --- discordSuperUtils/paginator.py | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/discordSuperUtils/paginator.py b/discordSuperUtils/paginator.py index e042b3f..ee21bcf 100644 --- a/discordSuperUtils/paginator.py +++ b/discordSuperUtils/paginator.py @@ -22,35 +22,23 @@ def generate_embeds( footer: str = "", display_page_in_footer=False, timestamp: datetime = discord.Embed.Empty, + page_format: str = "(Page {}/{})" ): num_of_embeds = ceil((len(list_to_generate) + 1) / fields) - embeds = [ - discord.Embed( - title=title - if display_page_in_footer - else f"{title} (Page 1/{num_of_embeds})", - description=description, - color=color, - timestamp=timestamp, - ).set_footer( - text=f"{footer} (Page 1/{num_of_embeds})" - if display_page_in_footer - else footer - ) - ] + embeds = [] - for i in range(2, num_of_embeds + 1): + for i in range(1, num_of_embeds + 1): embeds.append( discord.Embed( title=title if display_page_in_footer - else f"{title} (Page {i}/{num_of_embeds})", + else f"{title} {page_format.format(i, num_of_embeds)}", description=description, color=color, timestamp=timestamp, ).set_footer( - text=f"{footer} (Page {i}/{num_of_embeds})" + text=f"{footer} {page_format.format(i, num_of_embeds)}" if display_page_in_footer else footer ) From 98d4504f943f37103303d0f9f56961bcf6338929 Mon Sep 17 00:00:00 2001 From: adam757521 <73548219+adam757521@users.noreply.github.com> Date: Thu, 28 Oct 2021 16:59:51 +0300 Subject: [PATCH 02/13] Black formatted --- discordSuperUtils/paginator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discordSuperUtils/paginator.py b/discordSuperUtils/paginator.py index ee21bcf..414477b 100644 --- a/discordSuperUtils/paginator.py +++ b/discordSuperUtils/paginator.py @@ -22,7 +22,7 @@ def generate_embeds( footer: str = "", display_page_in_footer=False, timestamp: datetime = discord.Embed.Empty, - page_format: str = "(Page {}/{})" + page_format: str = "(Page {}/{})", ): num_of_embeds = ceil((len(list_to_generate) + 1) / fields) From 2fea2eaded06c84d1bb9c4599896e24606e3d372 Mon Sep 17 00:00:00 2001 From: adam757521 Date: Thu, 28 Oct 2021 20:51:35 +0300 Subject: [PATCH 03/13] Renamed QueueError to RemoveIndexInvalid --- discordSuperUtils/music/exceptions.py | 6 +++--- discordSuperUtils/music/music.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/discordSuperUtils/music/exceptions.py b/discordSuperUtils/music/exceptions.py index 4b844cb..2ec27f5 100644 --- a/discordSuperUtils/music/exceptions.py +++ b/discordSuperUtils/music/exceptions.py @@ -5,7 +5,7 @@ "QueueEmpty", "AlreadyConnected", "AlreadyPaused", - "QueueError", + "RemoveIndexInvalid", "SkipError", "UserNotConnected", "InvalidSkipIndex", @@ -41,8 +41,8 @@ class AlreadyPaused(Exception): """Raises error when player is already paused.""" -class QueueError(Exception): - """Raises error when something is wrong with the queue""" +class RemoveIndexInvalid(Exception): + """Raises error when the queue player remove index is invalid""" class SkipError(Exception): diff --git a/discordSuperUtils/music/music.py b/discordSuperUtils/music/music.py index cb014b8..9f9ab19 100644 --- a/discordSuperUtils/music/music.py +++ b/discordSuperUtils/music/music.py @@ -15,7 +15,7 @@ QueueEmpty, NotPlaying, NotConnected, - QueueError, + RemoveIndexInvalid, AlreadyPaused, NotPaused, InvalidSkipIndex, @@ -510,7 +510,7 @@ async def queue_remove(self, ctx: commands.Context, index: int) -> Optional[Play |coro| Removes a player from the queue in ctx at the specified index. - Calls on_music_error with QueueError if index is invalid. + Calls on_music_error with RemoveIndexInvalid if index is invalid. :param ctx: The context. :type ctx: commands.Context @@ -528,7 +528,7 @@ async def queue_remove(self, ctx: commands.Context, index: int) -> Optional[Play await self.call_event( "on_music_error", ctx, - QueueError("Failure when removing player from queue"), + RemoveIndexInvalid("Failure when removing player from queue"), ) async def lyrics( From 403b749d60c2fe533f03f90b94dff5aae28d4d19 Mon Sep 17 00:00:00 2001 From: adam757521 <73548219+adam757521@users.noreply.github.com> Date: Tue, 2 Nov 2021 16:12:15 +0200 Subject: [PATCH 04/13] Black formatted Changed timezone attr to tzinfo Added hash to BirthdayMember Fixed age method --- discordSuperUtils/birthday.py | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/discordSuperUtils/birthday.py b/discordSuperUtils/birthday.py index d79d766..a24542b 100644 --- a/discordSuperUtils/birthday.py +++ b/discordSuperUtils/birthday.py @@ -2,7 +2,7 @@ import asyncio from dataclasses import dataclass -from datetime import datetime, timedelta +from datetime import datetime, timedelta, tzinfo from typing import Dict, List, Optional, Any import discord @@ -23,7 +23,7 @@ class PartialBirthdayMember: member: discord.Member birthday_date: datetime - timezone: str + timezone: tzinfo @dataclass @@ -38,6 +38,9 @@ class BirthdayMember: def __post_init__(self): self.table = self.birthday_manager.tables["birthdays"] + def __hash__(self): + return id(self) + @property def __checks(self) -> Dict[str, int]: return {"guild": self.member.guild.id, "member": self.member.id} @@ -55,7 +58,9 @@ async def birthday_date(self) -> datetime: birthday_data = await self.birthday_manager.database.select( self.table, ["utc_birthday"], self.__checks ) - return datetime.utcfromtimestamp(birthday_data["utc_birthday"]) + return datetime.fromtimestamp( + birthday_data["utc_birthday"], await self.timezone() + ) async def next_birthday(self) -> datetime: """ @@ -67,7 +72,7 @@ async def next_birthday(self) -> datetime: :rtype: datetime """ - current_datetime = datetime.utcnow() + current_datetime = datetime.now(await self.timezone()) new_date = (await self.birthday_date()).replace(year=current_datetime.year) if new_date.timestamp() - current_datetime.timestamp() < 0: @@ -75,7 +80,7 @@ async def next_birthday(self) -> datetime: return new_date - async def timezone(self) -> str: + async def timezone(self) -> tzinfo: """ |coro| @@ -88,7 +93,7 @@ async def timezone(self) -> str: timezone_data = await self.birthday_manager.database.select( self.table, ["timezone"], self.__checks ) - return timezone_data["timezone"] + return pytz.timezone(timezone_data["timezone"]) async def delete(self) -> PartialBirthdayMember: """ @@ -146,20 +151,11 @@ async def age(self) -> int: :rtype: int """ - current_birthday = await self.birthday_date() - current_datetime = datetime.utcnow() + born = await self.birthday_date() + today = datetime.now(await self.timezone()) return ( - current_datetime.year - - current_birthday.year - - ( - datetime( - year=current_datetime.year, - month=current_birthday.month, - day=current_birthday.day, - ) - >= current_datetime - ) + today.year - born.year - ((today.month, today.day) < (born.month, born.day)) ) From 426174a7e02e919e0867c370692d7c6d3678afa2 Mon Sep 17 00:00:00 2001 From: adam757521 <73548219+adam757521@users.noreply.github.com> Date: Tue, 2 Nov 2021 20:39:50 +0200 Subject: [PATCH 05/13] Made commandhinter not raise error --- discordSuperUtils/commandhinter.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/discordSuperUtils/commandhinter.py b/discordSuperUtils/commandhinter.py index 2ff9b0c..6e369e0 100644 --- a/discordSuperUtils/commandhinter.py +++ b/discordSuperUtils/commandhinter.py @@ -129,7 +129,4 @@ async def __handle_hinter(self, ctx: commands.Context, error) -> None: else: raise TypeError( "The generated message must be of type 'discord.Embed' or 'str'." - ) - - else: - raise error + ) \ No newline at end of file From 59b5deec39040f0521911b14ab41865bae8cd8e0 Mon Sep 17 00:00:00 2001 From: adam757521 Date: Sat, 13 Nov 2021 12:33:05 +0200 Subject: [PATCH 06/13] Rewrote economy system Made economy example use all intents --- discordSuperUtils/economy.py | 62 ++++++++++++++++++------------------ examples/economy.py | 3 +- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/discordSuperUtils/economy.py b/discordSuperUtils/economy.py index 1d10fec..1772e1c 100644 --- a/discordSuperUtils/economy.py +++ b/discordSuperUtils/economy.py @@ -1,10 +1,11 @@ +from __future__ import annotations + from dataclasses import dataclass import discord from typing import List, Optional from .base import DatabaseChecker -from .database import Database @dataclass @@ -13,23 +14,24 @@ class EconomyAccount: Represents an EconomyAccount. """ - guild: int - member: int - database: Database - table: str + economy_manager: EconomyManager + member: discord.Member + + def __post_init__(self): + self.table = self.economy_manager.tables["economy"] @property def __checks(self): - return EconomyManager.generate_checks(self.guild, self.member) + return EconomyManager.generate_checks(self.member) async def currency(self): - currency_data = await self.database.select( + currency_data = await self.economy_manager.database.select( self.table, ["currency"], self.__checks ) return currency_data["currency"] async def bank(self): - bank_data = await self.database.select(self.table, ["bank"], self.__checks) + bank_data = await self.economy_manager.database.select(self.table, ["bank"], self.__checks) return bank_data["bank"] async def net(self): @@ -37,13 +39,13 @@ async def net(self): async def change_currency(self, amount: int): currency = await self.currency() - await self.database.update( + await self.economy_manager.database.update( self.table, {"currency": currency + amount}, self.__checks ) async def change_bank(self, amount: int): bank_amount = await self.bank() - await self.database.update( + await self.economy_manager.database.update( self.table, {"bank": bank_amount + amount}, self.__checks ) @@ -64,8 +66,8 @@ def __init__(self, bot): self.bot = bot @staticmethod - def generate_checks(guild: int, member: int): - return {"guild": guild, "member": member} + def generate_checks(member: discord.Member): + return {"guild": member.guild.id, "member": member.id} async def create_account(self, member: discord.Member) -> None: self._check_database() @@ -73,7 +75,7 @@ async def create_account(self, member: discord.Member) -> None: await self.database.insertifnotexists( self.tables["economy"], {"guild": member.guild.id, "member": member.id, "currency": 0, "bank": 0}, - self.generate_checks(member.guild.id, member.id), + self.generate_checks(member), ) async def get_account(self, member: discord.Member) -> Optional[EconomyAccount]: @@ -82,33 +84,31 @@ async def get_account(self, member: discord.Member) -> Optional[EconomyAccount]: member_data = await self.database.select( self.tables["economy"], [], - self.generate_checks(member.guild.id, member.id), + self.generate_checks(member), True, ) if member_data: return EconomyAccount( - member.guild.id, member.id, self.database, self.tables["economy"] + self, member ) return None - async def get_leaderboard(self, guild) -> List[EconomyAccount]: - self._check_database() - - guild_info = await self.database.select( - self.tables["economy"], [], {"guild": guild.id}, True + @DatabaseChecker.uses_database + async def get_leaderboard(self, guild: discord.Guild) -> List[EconomyAccount]: + guild_info = sorted( + await self.database.select( + self.tables["economy"], [], {"guild": guild.id}, True + ), + key=lambda x: x["bank"] + x["currency"], + reverse=True, ) - members = [ - EconomyAccount( - member_info["guild"], - member_info["member"], - database=self.database, - table=self.tables["economy"], - ) - for member_info in sorted( - guild_info, key=lambda x: x["bank"] + x["currency"], reverse=True - ) - ] + + members = [] + for member_info in guild_info: + member = guild.get_member(member_info["member"]) + if member: + members.append(EconomyAccount(self, member)) return members diff --git a/examples/economy.py b/examples/economy.py index 6612de5..5f6d09b 100644 --- a/examples/economy.py +++ b/examples/economy.py @@ -1,8 +1,9 @@ +import discord from discord.ext import commands import discordSuperUtils -bot = commands.Bot(command_prefix="-") +bot = commands.Bot(command_prefix="-", intents=discord.Intents.all()) EconomyManager = discordSuperUtils.EconomyManager(bot) From 30649ee0a4a072f51483fe9f2cbd617296d4b250 Mon Sep 17 00:00:00 2001 From: adam757521 Date: Sat, 13 Nov 2021 12:47:53 +0200 Subject: [PATCH 07/13] Made database decorators staticmethods Moved stuff in requirements.txt --- discordSuperUtils/database.py | 2 ++ requirements.txt | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/discordSuperUtils/database.py b/discordSuperUtils/database.py index d16fb35..2fc21f1 100644 --- a/discordSuperUtils/database.py +++ b/discordSuperUtils/database.py @@ -190,6 +190,7 @@ class _SqlDatabase(Database): def __str__(self): return f"<{self.__class__.__name__}>" + @staticmethod def with_commit(func): async def inner(self, *args, **kwargs): resp = await func(self, *args, **kwargs) @@ -200,6 +201,7 @@ async def inner(self, *args, **kwargs): return inner + @staticmethod def with_cursor(func): async def inner(self, *args, **kwargs): database = await self.database.acquire() if self.pool else self.database diff --git a/requirements.txt b/requirements.txt index e449ef4..0bd7639 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,8 +5,9 @@ requests~=2.25.1 spotipy~=2.16.1 aiosqlite>=0.17.0 motor>=2.5.0 -aiopg>=1.3.1 aiomysql~=0.0.21 discord_components pytz>=2021.1 wavelink==1.0.0b30 +setuptools~=57.0.0 +aiopg~=1.3.1 \ No newline at end of file From f7bc13ed893ae5112af159d0cc50f0c6f552e9e4 Mon Sep 17 00:00:00 2001 From: adam757521 Date: Sat, 13 Nov 2021 13:14:50 +0200 Subject: [PATCH 08/13] Fixed database --- discordSuperUtils/database.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/discordSuperUtils/database.py b/discordSuperUtils/database.py index 2fc21f1..d16fb35 100644 --- a/discordSuperUtils/database.py +++ b/discordSuperUtils/database.py @@ -190,7 +190,6 @@ class _SqlDatabase(Database): def __str__(self): return f"<{self.__class__.__name__}>" - @staticmethod def with_commit(func): async def inner(self, *args, **kwargs): resp = await func(self, *args, **kwargs) @@ -201,7 +200,6 @@ async def inner(self, *args, **kwargs): return inner - @staticmethod def with_cursor(func): async def inner(self, *args, **kwargs): database = await self.database.acquire() if self.pool else self.database From f636e727ffd2425e58ae8b9eaadb4c03bc079808 Mon Sep 17 00:00:00 2001 From: adam757521 Date: Sat, 13 Nov 2021 14:55:22 +0200 Subject: [PATCH 09/13] Added goto method Added complete_queue command and goto command to music Black reformat --- discordSuperUtils/commandhinter.py | 2 +- discordSuperUtils/economy.py | 8 +++--- discordSuperUtils/music/music.py | 7 ++++++ examples/music.py | 39 ++++++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 5 deletions(-) diff --git a/discordSuperUtils/commandhinter.py b/discordSuperUtils/commandhinter.py index 6e369e0..a15f39e 100644 --- a/discordSuperUtils/commandhinter.py +++ b/discordSuperUtils/commandhinter.py @@ -129,4 +129,4 @@ async def __handle_hinter(self, ctx: commands.Context, error) -> None: else: raise TypeError( "The generated message must be of type 'discord.Embed' or 'str'." - ) \ No newline at end of file + ) diff --git a/discordSuperUtils/economy.py b/discordSuperUtils/economy.py index 1772e1c..e8fd1ef 100644 --- a/discordSuperUtils/economy.py +++ b/discordSuperUtils/economy.py @@ -31,7 +31,9 @@ async def currency(self): return currency_data["currency"] async def bank(self): - bank_data = await self.economy_manager.database.select(self.table, ["bank"], self.__checks) + bank_data = await self.economy_manager.database.select( + self.table, ["bank"], self.__checks + ) return bank_data["bank"] async def net(self): @@ -89,9 +91,7 @@ async def get_account(self, member: discord.Member) -> Optional[EconomyAccount]: ) if member_data: - return EconomyAccount( - self, member - ) + return EconomyAccount(self, member) return None diff --git a/discordSuperUtils/music/music.py b/discordSuperUtils/music/music.py index 9f9ab19..21c5960 100644 --- a/discordSuperUtils/music/music.py +++ b/discordSuperUtils/music/music.py @@ -685,6 +685,13 @@ async def previous( await maybe_coroutine(ctx.voice_client.stop) return previous_players + @ensure_connection(check_playing=True, check_queue=True) + async def goto(self, ctx: commands.Context, index: int = 0) -> None: + queue = self.queue[ctx.guild.id] + + queue.pos = index + await maybe_coroutine(ctx.voice_client.stop) + @ensure_connection(check_playing=True, check_queue=True) async def skip(self, ctx: commands.Context, index: int = None) -> Optional[Player]: """ diff --git a/examples/music.py b/examples/music.py index c37762f..b935ef4 100644 --- a/examples/music.py +++ b/examples/music.py @@ -1,3 +1,5 @@ +from math import floor + from discord.ext import commands import discordSuperUtils @@ -252,6 +254,43 @@ async def queueloop(ctx): await ctx.send(f"Queue looping toggled to {is_loop}") +@bot.command() +async def complete_queue(ctx): + if ctx_queue := await MusicManager.get_queue(ctx): + formatted_queue = [ + f"Title: '{x.title}'\nRequester: {x.requester and x.requester.mention}\n" + f"Position: {i - ctx_queue.pos}" + for i, x in enumerate(ctx_queue.queue) + ] + + num_of_fields = 25 + + embeds = discordSuperUtils.generate_embeds( + formatted_queue, + "Complete Song Queue", + "Shows the complete song queue.", + num_of_fields, + string_format="{}", + ) + + page_manager = discordSuperUtils.PageManager( + ctx, embeds, public=True, index=floor(ctx_queue.pos / 25) + ) + await page_manager.run() + + +@bot.command() +async def goto(ctx, position: int): + if ctx_queue := await MusicManager.get_queue(ctx): + new_pos = ctx_queue.pos + position + if not 0 <= new_pos < len(ctx_queue.queue): + await ctx.send("Position is out of bounds.") + return + + await MusicManager.goto(ctx, new_pos) + await ctx.send(f"Moved to position {position}") + + @bot.command() async def history(ctx): if ctx_queue := await MusicManager.get_queue(ctx): From bda78acd81ba663da194caad23b4c9b253badf1f Mon Sep 17 00:00:00 2001 From: adam757521 Date: Sun, 14 Nov 2021 21:14:25 +0200 Subject: [PATCH 10/13] Added ExtendedClient, DatabaseClient, ManagerClient Made connect_to_database accept an empty list of tables Made DatabaseManager.connect return Optional[Database] and updated the docstring Updated moderation.py to use the new ManagerClient --- discordSuperUtils/__init__.py | 1 + discordSuperUtils/base.py | 5 ++++- discordSuperUtils/database.py | 6 +++--- examples/moderation.py | 12 +++++------- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/discordSuperUtils/__init__.py b/discordSuperUtils/__init__.py index d86e6ea..cff178c 100644 --- a/discordSuperUtils/__init__.py +++ b/discordSuperUtils/__init__.py @@ -30,6 +30,7 @@ from .spotify import SpotifyClient from .template import TemplateManager from .youtube import YoutubeClient +from .client import DatabaseClient, ExtendedClient, ManagerClient __title__ = "discordSuperUtils" __version__ = "0.2.9" diff --git a/discordSuperUtils/base.py b/discordSuperUtils/base.py index feec0f8..e61120f 100644 --- a/discordSuperUtils/base.py +++ b/discordSuperUtils/base.py @@ -456,7 +456,7 @@ def _check_database(self, raise_error: bool = True) -> bool: return True - async def connect_to_database(self, database: Database, tables: List[str]) -> None: + async def connect_to_database(self, database: Database, tables: List[str] = None) -> None: """ Connects to the database. Calls on_database_connect when connected. @@ -469,6 +469,9 @@ async def connect_to_database(self, database: Database, tables: List[str]) -> No :return: None """ + if not tables or len(tables) != len(self.table_identifiers): + tables = self.table_identifiers + for table, table_data, identifier in zip( tables, self.tables_column_data, self.table_identifiers ): diff --git a/discordSuperUtils/database.py b/discordSuperUtils/database.py index d16fb35..0a61aa3 100644 --- a/discordSuperUtils/database.py +++ b/discordSuperUtils/database.py @@ -388,13 +388,13 @@ class DatabaseManager: """ @staticmethod - def connect(database: Any) -> Any: + def connect(database: Any) -> Optional[Database]: """ Connects to a database. :param Any database: The database. - :return: The database. - :rtype: Any + :return: The database, if applicable. + :rtype: Optional[Database] """ if type(database) not in DATABASE_TYPES: diff --git a/examples/moderation.py b/examples/moderation.py index f09a78e..6c1c8e6 100644 --- a/examples/moderation.py +++ b/examples/moderation.py @@ -1,17 +1,18 @@ from datetime import datetime import discord -from discord.ext import commands import discordSuperUtils -bot = commands.Bot(command_prefix="-", intents=discord.Intents.all()) +bot = discordSuperUtils.ManagerClient("token", command_prefix="-", intents=discord.Intents.all()) InfractionManager = discordSuperUtils.InfractionManager(bot) BanManager = discordSuperUtils.BanManager(bot) KickManager = discordSuperUtils.KickManager(bot) MuteManager = discordSuperUtils.MuteManager(bot) +bot.managers = [InfractionManager, BanManager, MuteManager] + InfractionManager.add_punishments( [ discordSuperUtils.Punishment(KickManager, punish_after=3), @@ -66,10 +67,7 @@ async def on_punishment(ctx, member, punishment): @bot.event async def on_ready(): - database = discordSuperUtils.DatabaseManager.connect(...) - await InfractionManager.connect_to_database(database, ["infractions"]) - await BanManager.connect_to_database(database, ["bans"]) - await MuteManager.connect_to_database(database, ["mutes"]) + bot.database = discordSuperUtils.DatabaseManager.connect(...) print("Infraction manager is ready.", bot.user) @@ -248,4 +246,4 @@ async def remove_before( ).run() -bot.run("token") +bot.run(cogs_directory=None) From a184a2d165f11173911c98159ef576febc338d12 Mon Sep 17 00:00:00 2001 From: adam757521 Date: Sun, 14 Nov 2021 21:17:06 +0200 Subject: [PATCH 11/13] Forgot to push client.py + Black reformat --- discordSuperUtils/base.py | 4 +- discordSuperUtils/client.py | 116 ++++++++++++++++++++++++++++++++++++ examples/moderation.py | 4 +- 3 files changed, 122 insertions(+), 2 deletions(-) create mode 100644 discordSuperUtils/client.py diff --git a/discordSuperUtils/base.py b/discordSuperUtils/base.py index e61120f..e944c5a 100644 --- a/discordSuperUtils/base.py +++ b/discordSuperUtils/base.py @@ -456,7 +456,9 @@ def _check_database(self, raise_error: bool = True) -> bool: return True - async def connect_to_database(self, database: Database, tables: List[str] = None) -> None: + async def connect_to_database( + self, database: Database, tables: List[str] = None + ) -> None: """ Connects to the database. Calls on_database_connect when connected. diff --git a/discordSuperUtils/client.py b/discordSuperUtils/client.py new file mode 100644 index 0000000..8045285 --- /dev/null +++ b/discordSuperUtils/client.py @@ -0,0 +1,116 @@ +from __future__ import annotations + +import asyncio +import logging +import os +from typing import Optional, TYPE_CHECKING, List, Tuple + +from discord.ext import commands + +if TYPE_CHECKING: + from .base import DatabaseChecker + from .database import Database + +__all__ = ("ExtendedClient", "DatabaseClient", "ManagerClient") + + +class ExtendedClient(commands.Bot): + """ + Represents an extended commands,Bot client. + Adds a token attribute, replaces methods, loads cogs, etc. + """ + + __slots__ = ("token",) + + def __init__(self, token: str, *args, **kwargs): + super().__init__(*args, **kwargs) + self.token = token + + def load_cogs(self, directory: str, ignore_prefix: str = "__") -> None: + """ + Loads all the cog extensions in the directory. + + :param str ignore_prefix: The prefix to ignore, files that start with that prefix will not be loaded. + :param str directory: The directory. + :return: None + :rtype: None + """ + + extension_directory = directory.replace("/", ".") + if extension_directory: + extension_directory += "." + + working_directory = os.getcwd() + + slash = "/" if "/" in working_directory else "\\" + + for file in os.listdir(working_directory + f"{slash}{directory}"): + if not file.endswith(".py") or file.startswith(ignore_prefix): + continue + + try: + self.load_extension(f"{extension_directory}{file.replace('.py', '')}") + logging.info(f"Loaded cog {file}") + except Exception as e: + logging.error(f"Failed to load cog {file}") + raise e + + def run(self, cogs_directory: Optional[str] = "cogs") -> None: + """ + Runs the bot and loads the cogs automatically. + + :param Optional[str] cogs_directory: The directory to load the cogs from. + :return: None + :rtype: None + """ + + if cogs_directory is not None: # Might be an empty string. + self.load_cogs(cogs_directory) + + super().run(self.token) + + +class DatabaseClient(ExtendedClient): + """ + Represents an extended client that has a database. + """ + + __slots__ = ("database",) + + def __init__(self, token: str, *args, **kwargs): + super().__init__(token, *args, **kwargs) + self.database: Optional[Database] = None + + async def wait_until_database_connection(self) -> None: + """ + Waits until the database is connected. + + :return: None + :rtype: None + """ + + while not self.database: + await asyncio.sleep(0.1) + + +class ManagerClient(DatabaseClient): + """ + Represents an extended database client that has managers. + """ + + __slots__ = ("managers",) + + def __init__(self, token: str, *args, **kwargs): + super().__init__(token, *args, **kwargs) + self.managers: List[Tuple[DatabaseChecker, Optional[List[str]]]] = [] + + self.add_listener(self.on_ready) # Doesnt do this automatically. + + def add_manager(self, manager: DatabaseChecker, tables: List[str] = None) -> None: + self.managers.append((manager, tables)) + + async def on_ready(self): + await self.wait_until_database_connection() + + for manager, tables in self.managers: + await manager.connect_to_database(self.database, tables or []) diff --git a/examples/moderation.py b/examples/moderation.py index 6c1c8e6..f066cf8 100644 --- a/examples/moderation.py +++ b/examples/moderation.py @@ -4,7 +4,9 @@ import discordSuperUtils -bot = discordSuperUtils.ManagerClient("token", command_prefix="-", intents=discord.Intents.all()) +bot = discordSuperUtils.ManagerClient( + "token", command_prefix="-", intents=discord.Intents.all() +) InfractionManager = discordSuperUtils.InfractionManager(bot) BanManager = discordSuperUtils.BanManager(bot) From 1b09f94971656fa1b00ad1847fdd4ef2de021f66 Mon Sep 17 00:00:00 2001 From: adam757521 Date: Sun, 14 Nov 2021 21:22:08 +0200 Subject: [PATCH 12/13] Bumped version --- discordSuperUtils/__init__.py | 2 +- setup.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/discordSuperUtils/__init__.py b/discordSuperUtils/__init__.py index cff178c..1b20efc 100644 --- a/discordSuperUtils/__init__.py +++ b/discordSuperUtils/__init__.py @@ -33,6 +33,6 @@ from .client import DatabaseClient, ExtendedClient, ManagerClient __title__ = "discordSuperUtils" -__version__ = "0.2.9" +__version__ = "0.3.0" __author__ = "Koyashie07 & Adam7100" __license__ = "MIT" diff --git a/setup.py b/setup.py index e18e6c6..0ac18ba 100644 --- a/setup.py +++ b/setup.py @@ -13,14 +13,14 @@ "discordSuperUtils.music.lavalink": ["*"], }, include_package_data=True, - version="0.2.9", + version="0.3.0", license="MIT", description="Discord Bot Development made easy!", long_description=README, long_description_content_type="text/markdown", author="koyashie07 and adam7100", url="https://github.com/discordsuperutils/discord-super-utils", - download_url="https://github.com/discordsuperutils/discord-super-utils/archive/refs/tags/v0.2.9.tar.gz", + download_url="https://github.com/discordsuperutils/discord-super-utils/archive/refs/tags/v0.3.0.tar.gz", keywords=[ "discord", "easy", From 9d12f1c020818f46cff28b0ac39e16a91fd770d4 Mon Sep 17 00:00:00 2001 From: adam757521 Date: Sun, 14 Nov 2021 21:24:41 +0200 Subject: [PATCH 13/13] Updated examples in README.md --- README.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5437de8..3d52249 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,8 @@ bot.run("token") ### Playing Example ### ```py +from math import floor + from discord.ext import commands import discordSuperUtils @@ -157,12 +159,14 @@ import discord client_id = "" client_secret = "" -bot = commands.Bot(command_prefix="-") -MusicManager = MusicManager(bot, spotify_support=False) +bot = commands.Bot(command_prefix="-", intents=discord.Intents.all()) +# MusicManager = MusicManager(bot, spotify_support=False) + +MusicManager = MusicManager( + bot, client_id=client_id, client_secret=client_secret, spotify_support=True +) -# MusicManager = MusicManager(bot, client_id=client_id, -# client_secret=client_secret, spotify_support=True) # if using spotify support use this instead ^^^ @@ -190,8 +194,8 @@ async def on_play(ctx, player): @bot.event async def on_ready(): - database = discordSuperUtils.DatabaseManager.connect(...) - await MusicManager.connect_to_database(database, ["playlists"]) + # database = discordSuperUtils.DatabaseManager.connect(...) + # await MusicManager.connect_to_database(database, ["playlists"]) print("Music manager is ready.", bot.user) @@ -400,6 +404,43 @@ async def queueloop(ctx): await ctx.send(f"Queue looping toggled to {is_loop}") +@bot.command() +async def complete_queue(ctx): + if ctx_queue := await MusicManager.get_queue(ctx): + formatted_queue = [ + f"Title: '{x.title}'\nRequester: {x.requester and x.requester.mention}\n" + f"Position: {i - ctx_queue.pos}" + for i, x in enumerate(ctx_queue.queue) + ] + + num_of_fields = 25 + + embeds = discordSuperUtils.generate_embeds( + formatted_queue, + "Complete Song Queue", + "Shows the complete song queue.", + num_of_fields, + string_format="{}", + ) + + page_manager = discordSuperUtils.PageManager( + ctx, embeds, public=True, index=floor(ctx_queue.pos / 25) + ) + await page_manager.run() + + +@bot.command() +async def goto(ctx, position: int): + if ctx_queue := await MusicManager.get_queue(ctx): + new_pos = ctx_queue.pos + position + if not 0 <= new_pos < len(ctx_queue.queue): + await ctx.send("Position is out of bounds.") + return + + await MusicManager.goto(ctx, new_pos) + await ctx.send(f"Moved to position {position}") + + @bot.command() async def history(ctx): if ctx_queue := await MusicManager.get_queue(ctx): @@ -469,6 +510,11 @@ async def ls(ctx): await ctx.send(loop_status) +@bot.command() +async def move(ctx, player_index: int, index: int): + await MusicManager.move(ctx, player_index, index) + + bot.run("token") ``` @@ -480,7 +526,7 @@ Known Issues -------------- - Removing an animated emoji wont be recognized as a reaction role, as it shows up as not animated for some reason, breaking the reaction matcher. (Discord API Related) -- Leveling might call the on_level_up event multiple times, resulting in duplicate messages, caused by duplicate records in the leveling table. +- Leveling might call the on_level_up event multiple times, resulting in duplicate messages, caused by duplicate records in the leveling table. (Fixed) Support --------------