From 33b2b71a2c5e2f774749f2b118a33b067c645500 Mon Sep 17 00:00:00 2001 From: ticccco <23436953+LucasPlacentino@users.noreply.github.com> Date: Thu, 17 Nov 2022 00:44:52 +0100 Subject: [PATCH 1/9] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b9038ed..2953111 100644 --- a/README.md +++ b/README.md @@ -191,6 +191,7 @@ docker-compose up -d To see the bot logs when running with docker in detached mode (`-d`), use the [docker logs for the container](https://docs.docker.com/engine/reference/commandline/logs/). ## 💠 Bot usage +> TODO add `/server info` and `/user *` with other parameters to docs ### ULB servers From 46f709e12885b5d8bb38b3d7a3d6f41841b31ee6 Mon Sep 17 00:00:00 2001 From: ticccco <23436953+LucasPlacentino@users.noreply.github.com> Date: Sat, 26 Nov 2022 16:37:25 +0100 Subject: [PATCH 2/9] add memory limit to the container --- docker-compose.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 782d599..801d484 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,3 +12,5 @@ services: volumes: - ./:/usr/src/ulbdiscordbot restart: always # or unless-stopped + mem_limit: 1g # memory limit for the container + # cpus: 0.5 # cpu limit for the container From c4ebc10616fa52189816d9e0b5f24a038de268c0 Mon Sep 17 00:00:00 2001 From: OscarVsp Date: Thu, 16 Feb 2023 13:28:52 +0100 Subject: [PATCH 3/9] added missing information about discord commands --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2953111..62fd378 100644 --- a/README.md +++ b/README.md @@ -191,7 +191,6 @@ docker-compose up -d To see the bot logs when running with docker in detached mode (`-d`), use the [docker logs for the container](https://docs.docker.com/engine/reference/commandline/logs/). ## 💠 Bot usage -> TODO add `/server info` and `/user *` with other parameters to docs ### ULB servers @@ -207,6 +206,10 @@ To see the bot logs when running with docker in detached mode (`-d`), use the [d Once the ULB role is set, when a new user joins the server, either they are already registered (from another of your servers) in which case they will get the `@ULB` role and get renamed, or they are not registered yet and will receive a DM message with the instructions to register themselves using the `/ulb` command. +* `/feedback` + +Send a feedback directly from discord. + ### Admin server * `/user add` @@ -225,6 +228,10 @@ Edit info of a user. Delete a user. +* `/server info` + +Get information about a guild (ULB role, number of registered members, ...) + * `/update` This forces a total update of the database and of all the servers. Since the bot already does this automatically at startup and after each reconnection, the only normal usecase for this would be if you manually add an entry (server or user) to the google sheet instead of using the `/user add` command above, we don't recommend manually editing the google sheet. From 1767f248808fbdebd0ae9cd074ad52de4dc01cae Mon Sep 17 00:00:00 2001 From: OscarVsp Date: Thu, 16 Feb 2023 14:02:05 +0100 Subject: [PATCH 4/9] added member count and percent --- cogs/Ulb.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cogs/Ulb.py b/cogs/Ulb.py index a1a6b53..05a9e1c 100644 --- a/cogs/Ulb.py +++ b/cogs/Ulb.py @@ -162,9 +162,12 @@ async def info(self, inter: disnake.ApplicationCommandInteraction): ) return + n_registered = len(guilddata.role.members) + percent = int(n_registered / inter.guild.member_count * 100) + embed = disnake.Embed( title="Info du serveur", - description=f"ULB role : {guilddata.role.mention}\nRenommer les membres : **{'oui' if guilddata.rename else 'non'}**", + description=f"ULB role : {guilddata.role.mention}\nNombre de membre vérifié : **{n_registered}** *({percent}%)*\nRenommer les membres : **{'oui' if guilddata.rename else 'non'}**", color=disnake.Color.green(), ) From 5de4cc30425161e0b464f09869a9e45cd90895cd Mon Sep 17 00:00:00 2001 From: OscarVsp Date: Thu, 16 Feb 2023 14:02:33 +0100 Subject: [PATCH 5/9] added member count to server info and /stats command --- cogs/Admin.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/cogs/Admin.py b/cogs/Admin.py index b6564b7..10990fc 100644 --- a/cogs/Admin.py +++ b/cogs/Admin.py @@ -401,12 +401,32 @@ async def server_info( number_registered_user = len( [1 for _, member in enumerate(guild.members) if member in Database.ulb_users.keys()] ) + + n_registered = len(Database.ulb_guilds.get(guild).role.members) + percent = int(n_registered / guild.member_count * 100) + guild_data = Database.ulb_guilds.get(guild) await inter.edit_original_response( embed=disnake.Embed( title="Info du server", - description=f"**Nom :** {guild.name}\n**ID :** `{guild.id}`\n**Role :** @{guild_data.role.name}\n**Role ID :** `{guild_data.role.id}`\n**Rename :** `{'Oui' if guild_data.rename else 'Non'}`\n**Nombre de membre vérifié :** `{number_registered_user}`", - color=disnake.Color.green(), + description=f"**Nom :** {guild.name}\n**ID :** `{guild.id}`\n**Role :** @{guild_data.role.name}\n**Role ID :** `{guild_data.role.id}`\n**Rename :** `{'Oui' if guild_data.rename else 'Non'}`\n**Nombre de membre vérifié :** `{n_registered} ({percent}%)`", + color=disnake.Color.blue(), + ) + ) + + @commands.slash_command( + name="stats", + default_member_permissions=disnake.Permissions.all(), + description="Voir les statistiques d'utilisation du bot.", + ) + async def stats(self, inter: disnake.ApplicationCommandInteraction): + await inter.response.defer(ephemeral=True) + + await inter.edit_original_response( + embed=disnake.Embed( + title="Statistique du bot", + description=f"**Servers configurés : **`{len(Database.ulb_guilds)}`\n**Membres vérifiés : **`{len(Database.ulb_users)}`", + color=disnake.Colour.blue(), ) ) From 3c7ae176b05dbebf1c259015a2bab0bf195ec714 Mon Sep 17 00:00:00 2001 From: OscarVsp Date: Thu, 16 Feb 2023 14:02:54 +0100 Subject: [PATCH 6/9] added /stats admin command info --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 62fd378..03c40cd 100644 --- a/README.md +++ b/README.md @@ -232,6 +232,10 @@ Delete a user. Get information about a guild (ULB role, number of registered members, ...) +* `/stats` + +Get statistics about the bot usage (nombre of configured servers, number of registered users, ...) + * `/update` This forces a total update of the database and of all the servers. Since the bot already does this automatically at startup and after each reconnection, the only normal usecase for this would be if you manually add an entry (server or user) to the google sheet instead of using the `/user add` command above, we don't recommend manually editing the google sheet. From a03683db26457359a2bdac3a3c07fa32a370dc72 Mon Sep 17 00:00:00 2001 From: oscarvsp Date: Tue, 5 Dec 2023 12:25:15 +0100 Subject: [PATCH 7/9] change black type to python3 --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 72a22a0..b7cee0d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ repos: hooks: - id: black language: python - types: [python] + types: [python3] args: ["--line-length=120"] - repo: https://github.com/PyCQA/autoflake From 14e34132c6706a9356b176e7e2537e2fe7f58967 Mon Sep 17 00:00:00 2001 From: oscarvsp Date: Tue, 5 Dec 2023 12:26:17 +0100 Subject: [PATCH 8/9] added way to send error log message without cmd context --- bot/bot.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/bot/bot.py b/bot/bot.py index a308b33..0bd8bb0 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -96,11 +96,24 @@ def load_commands(self) -> None: ) self.cog_not_loaded.append(extension) - async def send_error_log(self, interaction: ApplicationCommandInteraction, error: Exception): + async def send_error_log(self, tb: str): + + n = len(tb) // 4050 + + #Logs need to be diveded into multiple embed due to size limitation + # TODO Check if we can use a list of embeds and one message + # TODO Make it dynamic base on the message size from the lib (check library version, maybe need to upgrade) + for i in range(n): + await self.log_channel.send(embed=disnake.Embed(description=f"```python\n{tb[4050*i:4050*(i+1)]}```")) + await self.log_channel.send(embed=disnake.Embed(description=f"```python\n{tb[4050*n:]}```")) + + async def send_cmd_error_log(self, interaction: ApplicationCommandInteraction, error: Exception): tb = self.tracebackEx(error) logging.error( f"{error} raised on command /{interaction.application_command.name} from {interaction.guild.name+'#'+interaction.channel.name if interaction.guild else 'DM'} by {interaction.author.name}.\n{tb}" ) + + #Send error msg to the user await interaction.send( content=self.owner.mention, embed=disnake.Embed( @@ -110,6 +123,8 @@ async def send_error_log(self, interaction: ApplicationCommandInteraction, error ), delete_after=10, ) + + #Send logs to admins await self.log_channel.send( embed=disnake.Embed(title=f":x: __** ERROR**__ :x:", description=f"```{error}```").add_field( name=f"Raised on command :", @@ -117,10 +132,7 @@ async def send_error_log(self, interaction: ApplicationCommandInteraction, error + (f" and target\n``'{interaction.target}``'." if interaction.target else "."), ) ) - n = len(tb) // 4050 - for i in range(n): - await self.log_channel.send(embed=disnake.Embed(description=f"```python\n{tb[4050*i:4050*(i+1)]}```")) - await self.log_channel.send(embed=disnake.Embed(description=f"```python\n{tb[4050*n:]}```")) + await self.send_error_log(tb) async def on_slash_command(self, interaction: disnake.ApplicationCommandInteraction) -> None: logging.trace( @@ -138,13 +150,13 @@ async def on_message_command(self, interaction: disnake.MessageCommandInteractio ) async def on_slash_command_error(self, interaction: ApplicationCommandInteraction, error: Exception) -> None: - await self.send_error_log(interaction, error) + await self.send_cmd_error_log(interaction, error) async def on_user_command_error(self, interaction: disnake.UserCommandInteraction, error: Exception) -> None: - await self.send_error_log(interaction, error) + await self.send_cmd_error_log(interaction, error) async def on_message_command_error(self, interaction: disnake.MessageCommandInteraction, error: Exception) -> None: - await self.send_error_log(interaction, error) + await self.send_cmd_error_log(interaction, error) async def on_slash_command_completion(self, interaction: disnake.ApplicationCommandInteraction) -> None: logging.trace( From a9928597e3671bbe880e8b929bb212d7da90dac7 Mon Sep 17 00:00:00 2001 From: oscarvsp Date: Tue, 5 Dec 2023 12:27:27 +0100 Subject: [PATCH 9/9] added check and error msg for database loading --- classes/database.py | 56 ++++++++++++++++++++++++--------------------- cogs/Admin.py | 35 +++++++--------------------- cogs/Ulb.py | 14 +++++------- 3 files changed, 44 insertions(+), 61 deletions(-) diff --git a/classes/database.py b/classes/database.py index 61d0c39..9313d65 100644 --- a/classes/database.py +++ b/classes/database.py @@ -94,7 +94,7 @@ def loaded(cls) -> bool: return cls._loaded @classmethod - def load(cls, bot: Bot) -> None: + async def load(cls, bot: Bot) -> bool: """Load the data from the google sheet. Returns @@ -104,31 +104,35 @@ def load(cls, bot: Bot) -> None: - Guild: `Dict[disnake.Guild, disnake.Role]` - Users: `Dict[disnake.User, UlbUser]]` """ - # First time this is call, we need to load the credentials and the sheet - if not cls._sheet: - cred_dict = {} - cred_dict["type"] = os.getenv("GS_TYPE") - cred_dict["project_id"] = os.getenv("GS_PROJECT_ID") - cred_dict["auth_uri"] = os.getenv("GS_AUTHOR_URI") - cred_dict["token_uri"] = os.getenv("GS_TOKEN_URI") - cred_dict["auth_provider_x509_cert_url"] = os.getenv("GS_AUTH_PROV") - cred_dict["client_x509_cert_url"] = os.getenv("GS_CLIENT_CERT_URL") - cred_dict["private_key"] = os.getenv("GS_PRIVATE_KEY").replace( - "\\n", "\n" - ) # Python add a '\' before any '\n' when loading a str - cred_dict["private_key_id"] = os.getenv("GS_PRIVATE_KEY_ID") - cred_dict["client_email"] = os.getenv("GS_CLIENT_EMAIL") - cred_dict["client_id"] = int(os.getenv("GS_CLIENT_ID")) - creds = ServiceAccountCredentials.from_json_keyfile_dict(cred_dict, cls._scope) - cls._client = gspread.authorize(creds) - logging.info("[Database] Google sheet credentials loaded.") - - # Open google sheet - cls._sheet = cls._client.open_by_url(os.getenv("GOOGLE_SHEET_URL")) - cls._users_ws = cls._sheet.worksheet("users") - cls._guilds_ws = cls._sheet.worksheet("guilds") - - logging.info("[Database] Spreadsheed loaded") + try: + # First time this is call, we need to load the credentials and the sheet + if not cls._sheet: + cred_dict = {} + cred_dict["type"] = os.getenv("GS_TYPE") + cred_dict["project_id"] = os.getenv("GS_PROJECT_ID") + cred_dict["auth_uri"] = os.getenv("GS_AUTHOR_URI") + cred_dict["token_uri"] = os.getenv("GS_TOKEN_URI") + cred_dict["auth_provider_x509_cert_url"] = os.getenv("GS_AUTH_PROV") + cred_dict["client_x509_cert_url"] = os.getenv("GS_CLIENT_CERT_URL") + cred_dict["private_key"] = os.getenv("GS_PRIVATE_KEY").replace( + "\\n", "\n" + ) # Python add a '\' before any '\n' when loading a str + cred_dict["private_key_id"] = os.getenv("GS_PRIVATE_KEY_ID") + cred_dict["client_email"] = os.getenv("GS_CLIENT_EMAIL") + cred_dict["client_id"] = int(os.getenv("GS_CLIENT_ID")) + creds = ServiceAccountCredentials.from_json_keyfile_dict(cred_dict, cls._scope) + cls._client = gspread.authorize(creds) + logging.info("[Database] Google sheet credentials loaded.") + + # Open google sheet + cls._sheet = cls._client.open_by_url(os.getenv("GOOGLE_SHEET_URL")) + cls._users_ws = cls._sheet.worksheet("users") + cls._guilds_ws = cls._sheet.worksheet("guilds") + + logging.info("[Database] Spreadsheed loaded") + except (ValueError, gspread.exceptions.SpreadsheetNotFound, gspread.exceptions.WorksheetNotFound) as err: + await bot.send_error_log(bot.tracebackEx(err)) + return logging.info("[Database] Loading data...") diff --git a/cogs/Admin.py b/cogs/Admin.py index 10990fc..3ca1882 100644 --- a/cogs/Admin.py +++ b/cogs/Admin.py @@ -28,11 +28,12 @@ def __init__(self, bot: Bot): ) async def update(self, inter: disnake.ApplicationCommandInteraction): await inter.response.defer(ephemeral=True) - Database.load(self.bot) - await utils.update_all_guilds() - await inter.edit_original_response( - embed=disnake.Embed(description="All servers updated !", color=disnake.Color.green()) - ) + await Database.load(self.bot) + if (Database.loaded): + await utils.update_all_guilds() + await inter.edit_original_response( + embed=disnake.Embed(description="All servers updated !", color=disnake.Color.green()) + ) @commands.slash_command( name="yearly-update", @@ -401,32 +402,12 @@ async def server_info( number_registered_user = len( [1 for _, member in enumerate(guild.members) if member in Database.ulb_users.keys()] ) - - n_registered = len(Database.ulb_guilds.get(guild).role.members) - percent = int(n_registered / guild.member_count * 100) - guild_data = Database.ulb_guilds.get(guild) await inter.edit_original_response( embed=disnake.Embed( title="Info du server", - description=f"**Nom :** {guild.name}\n**ID :** `{guild.id}`\n**Role :** @{guild_data.role.name}\n**Role ID :** `{guild_data.role.id}`\n**Rename :** `{'Oui' if guild_data.rename else 'Non'}`\n**Nombre de membre vérifié :** `{n_registered} ({percent}%)`", - color=disnake.Color.blue(), - ) - ) - - @commands.slash_command( - name="stats", - default_member_permissions=disnake.Permissions.all(), - description="Voir les statistiques d'utilisation du bot.", - ) - async def stats(self, inter: disnake.ApplicationCommandInteraction): - await inter.response.defer(ephemeral=True) - - await inter.edit_original_response( - embed=disnake.Embed( - title="Statistique du bot", - description=f"**Servers configurés : **`{len(Database.ulb_guilds)}`\n**Membres vérifiés : **`{len(Database.ulb_users)}`", - color=disnake.Colour.blue(), + description=f"**Nom :** {guild.name}\n**ID :** `{guild.id}`\n**Role :** @{guild_data.role.name}\n**Role ID :** `{guild_data.role.id}`\n**Rename :** `{'Oui' if guild_data.rename else 'Non'}`\n**Nombre de membre vérifié :** `{number_registered_user}`", + color=disnake.Color.green(), ) ) diff --git a/cogs/Ulb.py b/cogs/Ulb.py index 05a9e1c..7e42f46 100644 --- a/cogs/Ulb.py +++ b/cogs/Ulb.py @@ -19,10 +19,11 @@ def __init__(self, bot: Bot): @commands.Cog.listener("on_ready") async def on_ready(self): - Database.load(self.bot) - Registration.setup(self) - logging.info("[Cog:Ulb] Ready !") - await utils.update_all_guilds() + await Database.load(self.bot) + if (Database.loaded): + Registration.setup(self) + logging.info("[Cog:Ulb] Ready !") + await utils.update_all_guilds() async def wait_setup(self, inter: disnake.ApplicationCommandInteraction) -> None: """Async sleep until GoogleSheet is loaded and RegistrationForm is set""" @@ -162,12 +163,9 @@ async def info(self, inter: disnake.ApplicationCommandInteraction): ) return - n_registered = len(guilddata.role.members) - percent = int(n_registered / inter.guild.member_count * 100) - embed = disnake.Embed( title="Info du serveur", - description=f"ULB role : {guilddata.role.mention}\nNombre de membre vérifié : **{n_registered}** *({percent}%)*\nRenommer les membres : **{'oui' if guilddata.rename else 'non'}**", + description=f"ULB role : {guilddata.role.mention}\nRenommer les membres : **{'oui' if guilddata.rename else 'non'}**", color=disnake.Color.green(), )