From c24965bae97181f34167a56145021899ae1630d1 Mon Sep 17 00:00:00 2001 From: Caraffa-git Date: Tue, 25 Jun 2024 22:07:30 +0200 Subject: [PATCH 01/18] clean up --- thatkitebot/cogs/info.py | 558 ++++++--------------------------------- 1 file changed, 80 insertions(+), 478 deletions(-) diff --git a/thatkitebot/cogs/info.py b/thatkitebot/cogs/info.py index 1a06d53..fff0f6d 100644 --- a/thatkitebot/cogs/info.py +++ b/thatkitebot/cogs/info.py @@ -1,5 +1,3 @@ -# Copyright (c) 2019-2023 ThatRedKite and contributors - import os import re import json @@ -13,184 +11,100 @@ from discord.ui import Select, View, Button from discord.commands import Option, SlashCommandGroup -from thatkitebot.base.util import PermissonChecks as pc -from thatkitebot.embeds.info import * - +class Config: + def __init__(self, bot, data_dir): + self.bot = bot + self.data_dir = data_dir -# generate random 24 bit number -def gen_random_hex_color(): - def get_int(): - return random.randint(0, 255) + def exists(self, guild): + """Check if guild config file exists.""" + return Path(self.data_dir, f"info/{guild.id}.json").exists(); - return f'0x{get_int():02X}{get_int():02X}{get_int():02X}' + async def get_default(self): + """Retrieve default config.""" + async with aiofiles.open(os.path.join(self.data_dir, "info/info.json"), "r") as f: + return json.loads(await f.read()) + async def load_default(self, guild): + """Load default config for guild.""" + await self.update(guild, await self.get_default()) -# check if given string is valid discord emoji -def check_emoji(emoji): - emoji_regex = r"<\S+:\d+>" - if len(emoji) == 1: - return True - elif re.match(emoji_regex, emoji): - return True - else: - return False + async def get(self, guild): + try: + async with aiofiles.open(os.path.join(self.data_dir, f"info/{guild.id}.json"), "r") as f: + return json.loads(await f.read()) + except FileNotFoundError: + default = await self.get_default() + await self.update(guild, default) + return await self.get(guild) + async def update(self, guild, config): + """Update guild config.""" + async with aiofiles.open(os.path.join(self.data_dir, f"info/{guild.id}.json"), "w") as f: + await f.write(json.dumps(config)) -def check_hex(hex): - hex_regex = r"^0x[0-9a-fA-F]+$" - if re.match(hex_regex, hex): - return True + async def get_sections(self, ctx: discord.AutocompleteContext): + config = await self.get(ctx.interaction.guild) + return [f"{id + 1}. {section['title']}" for id, section in enumerate(config) if section['title'].lower().startswith(ctx.value.lower())] - return False -class FieldModal(discord.ui.Modal): - def __init__(self, bot, config, section_id, field_id, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) +class Navigation(View): + def __init__(self, config_file = None, buttons: bool=False, dropdown: bool=True): + super().__init__(timeout = None) + if dropdown: + self.add_dropdown(config_file) + if buttons: + self.add_buttons() - self.bot = bot - self.config = config - self.section_id = section_id - self.field_id = field_id + def add_buttons(self): + prev = Button(emoji="⬅️", style=discord.ButtonStyle.gray, custom_id="prev") + next = Button(emoji="➡️", style=discord.ButtonStyle.gray, custom_id="next") - name = config[section_id]["fields"][field_id]["name"] - contents = config[section_id]["fields"][field_id]["value"] - inline = config[section_id]["fields"][field_id]["inline"] + prev.callback = self.prev_button_callback + next.callback = self.next_button_callback - _title = discord.ui.InputText( - label="Title", - value=name if name != "" else "Example title 💡", - placeholder="Think of an interesting title", - max_length=256 - ) + self.add_item(prev) + self.add_item(next) - _contents = discord.ui.InputText( - label="Contents", - value=contents if contents != "" else "[Example hyperlink](https://github.com/ThatRedKite/thatkitebot)", - placeholder="Here you can type the contents of this field in your embed", - style=discord.InputTextStyle.long, - max_length=1024 - ) + def add_dropdown(self, config_file): + options = [] + for id, section in enumerate(config_file): + if discord_emoji.to_uni(section["emoji"]): + options.append(discord.SelectOption(label=f'{id + 1}. {section["title"]}', emoji=discord_emoji.to_uni(section["emoji"]))) + else: + options.append(discord.SelectOption(label=f'{id + 1}. {section["title"]}', emoji=section["emoji"])) - _inline = discord.ui.InputText( - label="In-line", - value=inline if inline != "" else "True", - placeholder="True or False", - max_length=5 + select = Select( + placeholder='Select a section...', + min_values=1, + max_values=1, + options=options ) - self.add_item(_title) - self.add_item(_contents) - self.add_item(_inline) + select.callback = self.dropdown_callback - async def callback(self, interaction: discord.Interaction): + self.add_item(select) - self.config[self.section_id]["fields"][self.field_id]["name"] = self.children[0].value # set title of the field - self.config[self.section_id]["fields"][self.field_id]["value"] = self.children[ - 1].value # set value of the field + async def prev_button_callback(self, interaction: discord.Interaction): + await interaction.response.send_message("You selected prev button!") - # check if "inline" input value is correct - if str(self.children[2].value).lower() == "true": - self.config[self.section_id]["fields"][self.field_id]["inline"] = True - elif str(self.children[2].value).lower() == "false": - self.config[self.section_id]["fields"][self.field_id]["inline"] = False - else: - await interaction.response.send_message("Invalid `in-line` value!", ephemeral=True) - return + async def next_button_callback(self, interaction: discord.Interaction): + await interaction.response.send_message("You selected next button!") - # update config - await self.bot.get_cog("Info").update_config(interaction.guild, self.config) + async def dropdown_callback(self, interaction: discord.Interaction): + await interaction.response.send_message("You selected a section!") - await interaction.response.send_message( - f"Field with ID {self.field_id + 1} in section {self.config[self.section_id]['title']} has been changed.") - -class InfoCog(commands.Cog, name="Info"): +class InfoCog(commands.Cog): def __init__(self, bot): - self.bot: discord.Bot = bot - self.current_embed = None - - self.main_view = None - - self.buttons = [ - Button( - emoji="⬅️", - style=discord.ButtonStyle.gray, - custom_id="prev" - ), - Button( - emoji="➡️", - style=discord.ButtonStyle.gray, - custom_id="next", - - ) - ] - - for button in self.buttons: button.callback = self.button_callback - - ###### "Utility" functions ###### - - # section list for autocompletion - async def get_sections(self, ctx: discord.AutocompleteContext): - return [f"{id + 1}. {section['title']}" for id, section in - enumerate(await self.get_config(ctx.interaction.guild)) - if f"{id + 1}. {section['title']}".lower().startswith(ctx.value.lower())] - - # field edit options list for autocompletion - async def get_options(self, ctx: discord.AutocompleteContext): - return [section for section in ["add", "edit", "remove"] if section.lower().startswith(ctx.value.lower())] - - # get config file - async def get_config(self, guild): - try: - async with aiofiles.open(os.path.join(self.bot.data_dir, f"info/{guild.id}.json"), "r") as f: - return json.loads("".join([line async for line in f])) - except FileNotFoundError: - default = await self.get_default_config() - await self.update_config(guild, default) - return await self.get_config(guild=guild) - - # get default config file - async def get_default_config(self): - async with aiofiles.open(os.path.join(self.bot.data_dir, f"info/info.json"), "r") as f: - return json.loads("".join([line async for line in f])) - - # update config file - async def update_config(self, guild, data: dict): - async with aiofiles.open(os.path.join(self.bot.data_dir, f"info/{guild.id}.json"), "w") as f: - await f.write(json.dumps(data)) - - # setting up view for main dropdown menu - async def get_dropdown(self, guild): - option_list = [] - for id, section in enumerate(await self.get_config(guild)): - if discord_emoji.to_uni( - section["emoji"]): # if it's standard emoji (e.g. :smile:) it has to be converted to unicode - option_list.append(discord.SelectOption(label=f'{id + 1}. {section["title"]}', - emoji=discord_emoji.to_uni(section["emoji"]))) - else: - option_list.append(discord.SelectOption(label=f'{id + 1}. {section["title"]}', emoji=section["emoji"])) - - return Select(options=option_list) - - # check if given section name has valid id and returns it if so - async def check_section_id(self, section, guild): - config = await self.get_config(guild) - try: - id = int(section.split(".")[0]) - 1 - if not (id >= 0 and id < len(config)): - return -1 - except: - return -1 - - return id - - ###### Commands ###### + self.bot = bot + self.config = Config(bot, bot.data_dir) @commands.slash_command(name="info") - async def info(self, ctx: discord.ApplicationContext, - section: Option(str, "Pick a section!", required=False, autocomplete=get_sections) = None, - disable_navigation: Option(bool, "True or False", required=False, name="disable-navigation") = None): + async def info(self, ctx: discord.ApplicationContext, + section: Option(str, "Pick a section!", required=False, autocomplete=Config.get_sections) = None, # type: ignore + disable_navigation: Option(bool, "Disable navigation", required=False) = False): # type: ignore """ Sends YT channels, documents, books etc. related to chosen science topic arranged in convenient way. """ @@ -198,335 +112,23 @@ async def info(self, ctx: discord.ApplicationContext, # load default config if not already done await self.load_defaults(ctx.guild) - # check if section is provided if disable_navigation and not section: - await ctx.respond("If you want to disable navigation, you need to specify a section!", ephemeral=True) - return - - # setting up view for embeds - dropdown = await self.get_dropdown(ctx.guild) - dropdown.callback = self.info_dropdown_callback - - dropdown_view = View() - dropdown_view.add_item(dropdown) - - self.main_view = View() - self.main_view.add_item(dropdown) - for button in self.buttons: self.main_view.add_item(button) - - id = await self.check_section_id(section, ctx.guild) - if section is not None: - if id < 0: - await ctx.respond("Incorrect section name!", ephemeral=True) - return - - if disable_navigation: - await ctx.respond(embed=await get_embed(id, await self.get_config(ctx.guild))) - else: - await ctx.respond(embed=await get_embed(id, await self.get_config(ctx.guild)), view=self.main_view) - else: - await ctx.respond("Choose a section!", view=dropdown_view) - - ###### Group commands ###### - - info_settings = SlashCommandGroup( - "info-settings", - "Settings for /info command", - checks=[pc.mods_can_change_settings] - ) - - @info_settings.command(name="add-section") - async def new_section(self, ctx: discord.ApplicationContext, - name: Option(str, "Choose a name!", required=True, max_lenght=256), - emoji: Option(str, "Choose an emoji! (e.g :lightbulb:)", required=True), - color: Option(str, "Choose a color! (hex e.g. 0x123456)", required=False) = None): - """ - Create new section in /info command - """ - config = await self.get_config(ctx.guild) - - # check if given string is valid discord emoji - if not check_emoji(emoji=emoji): - await ctx.respond("Invalid emoji!", ephemeral=True) - return - - # if it's unicode emoji, convert it into string - if discord_emoji.to_discord(emoji): - emoji = f":{discord_emoji.to_discord(emoji, get_all=True)}:" - - # check if color format is valid - if color: - if check_hex(color): - color = int(color, base=16) - else: - await ctx.respond("Invalid color format!", ephemeral=True) - return - else: - color = int(gen_random_hex_color(), base=16) - - dictionary = { - "title": f"{name}", - "emoji": f"{emoji}", - "color": f"{color}", - "fields": [ - { - "name": "", - "value": "", - "inline": True - } - ], - "footer": "false" - } - - config.append(dictionary) - await self.update_config(ctx.guild, config) - - await ctx.respond(f"Section {name} {emoji} has been created, now you can add a new field or edit existing one.") - - @info_settings.command(name="remove-section") - async def remove_section(self, ctx, name: Option(str, "Pick a section!", required=True, autocomplete=get_sections)): - """Remove section in /info command""" - config = await self.get_config(ctx.guild) - - id = await self.check_section_id(name, ctx.guild) - if id < 0: - await ctx.respond("Incorrect section name!", ephemeral=True) - return - - name = config[id]["title"] - emoji = config[id]["emoji"] - - del config[id] - - await self.update_config(ctx.guild, config) - await ctx.respond(f"Section {name} {emoji} has been removed.") - - @info_settings.command(name="edit-field") - async def edit_field(self, ctx: discord.ApplicationContext, - section: Option(str, "Pick a section!", required=True, autocomplete=get_sections), - option: Option(str, "Choose option.", required=True, autocomplete=get_options)): - """Add, edit or remove a field in section""" - - config = await self.get_config(ctx.guild) - - empty_field = { - "name": "", - "value": "", - "inline": True - } - - # check if "option" input is valid - if option != "edit" and option != "remove" and option != "add": - await ctx.respond("Incorrect option!", ephemeral=True) - return - - # check if given section name has valid id - id = await self.check_section_id(section, ctx.guild) - if id < 0: - await ctx.respond("Incorrect section name!", ephemeral=True) - return - - # add empty field to config - if option == "add": - config[id]["fields"].append(empty_field) - - # prepare modal for user to edit field - await self.update_config(ctx.guild, config) - modal = FieldModal(title=f"Editing new field :pencil:", - config=await self.get_config(ctx.guild), section_id=id, - field_id=len(config[id]["fields"]) - 1, bot=self.bot) - - await ctx.send_modal(modal) - return - - # check if there are any fields to edit/remove - if len(config[id]["fields"]) == 0: - await ctx.respond("There is no fields to edit/remove!") - return - - # prepare dropdown list of existing fields to edit/remove - option_list = [] - for field_id, section in enumerate(config[id]["fields"]): - option_list.append(discord.SelectOption(label=f'{id + 1}.{field_id + 1}. {section["name"]}')) - - dropdown = Select(options=option_list) - - # set callback function to dropdown list according to selected option - if option == "edit": - dropdown.callback = self.edit_field_dropdown_callback - elif option == "remove": - dropdown.callback = self.remove_field_dropdown_callback - - dropdown_view = View() - dropdown_view.add_item(dropdown) - - await ctx.respond(view=dropdown_view) - - @info_settings.command(name="edit-section") - async def edit_section(self, ctx: discord.ApplicationContext, - section: Option(str, "Choose a section", required=True, autocomplete=get_sections), - title: Option(str, "Choose a title!", max_lenght=256, required=False) = None, - emoji: Option(str, "Choose an emoji! (e.g :lightbulb:)", required=False) = None, - color: Option(str, "Choose a color! (hex e.g. 0x123456)", required=False) = None): - """Change name, emoji or color of given section""" - - config = await self.get_config(ctx.guild) - - # count how many properties have been changed - counter = 0 - - # check if given section name has valid id - id = await self.check_section_id(section, ctx.guild) - if id < 0: - await ctx.respond("Incorrect section name!", ephemeral=True) - return - - _emoji = config[id]["emoji"] - _title = config[id]["title"] - - # check if every input is valid and if so - change them in config - if title: - config[id]["title"] = title - counter += 1 - if emoji: - # check if given string is valid discord emoji - if not check_emoji(emoji): - await ctx.respond("Invalid emoji!", ephemeral=True) - return - # if it's unicode emoji, convert it into string - if discord_emoji.to_discord(emoji): - emoji = f":{discord_emoji.to_discord(emoji, get_all=True)}:" - - config[id]["emoji"] = emoji - counter += 1 - if color: - try: - color = int(color, base=16) - except Exception as e: - await ctx.respond("Invalid color format!", ephemeral=True) - return - - config[id]["color"] = color - counter += 1 - - await self.update_config(ctx.guild, config) - await ctx.respond(f"{counter} properties has been changed in {_title} {_emoji}") - - @info_settings.command(name="edit-footer") - async def edit_footer(self, ctx, name: Option(str, "Pick a section!", required=True, autocomplete=get_sections), - footer: Option(str, required=True, max_lenght=2048)): - """Edit section footer in /info command""" - config = await self.get_config(ctx.guild) - - # check if given section name has valid id - id = await self.check_section_id(name, ctx.guild) - if id < 0: - await ctx.respond("Incorrect section name!", ephemeral=True) + await ctx.respond("Specify a section to disable navigation!", ephemeral=True) return + + config_file = await self.config.get(ctx.guild) + navigation = Navigation(config_file, buttons=False, dropdown=True) - # edit footer and update config - config[id]["footer"] = footer - await self.update_config(ctx.guild, config) - - await ctx.respond("Footer has been changed.") - - @info_settings.command(name="factory-reset") - async def factory_reset(self, ctx): - view = View() - - buttons = [ - Button( - emoji="✅", - style=discord.ButtonStyle.gray, - custom_id="yes" - ), - Button( - emoji="⛔", - style=discord.ButtonStyle.gray, - custom_id="no" - ) - ] - - for button in buttons: button.callback = self.reset_callback - for button in buttons: view.add_item(button) - - await ctx.respond("Are you sure? It will **replace all of your sections.**", view=view) - - ###### Callbacks ###### - - async def info_dropdown_callback(self, interaction: discord.Interaction): - id = await self.check_section_id(interaction.data["values"][0], interaction.guild) - self.current_embed = id - await interaction.response.edit_message(embed=await get_embed(id, await self.get_config(interaction.guild)), - view=self.main_view, content=None) - - async def edit_field_dropdown_callback(self, interaction: discord.Interaction): - # get id and field id - id = int(interaction.data["values"][0].split(".")[0]) - 1 - field_id = int(interaction.data["values"][0].split(".")[1]) - 1 - - modal = FieldModal(title=f"Field editor :pencil:", - config=await self.get_config(interaction.guild), section_id=id, field_id=field_id, - bot=self.bot) + await ctx.respond("Choose a section!", view=navigation) + + async def cog_command_error(self, ctx: commands.Context, error: commands.CommandError): + await ctx.send(f"{error}") - await interaction.response.send_modal(modal) - - async def remove_field_dropdown_callback(self, interaction: discord.Interaction): - # get id and field id - id = int(interaction.data["values"][0].split(".")[0]) - 1 - field_id = int(interaction.data["values"][0].split(".")[1]) - 1 - - config = await self.get_config(interaction.guild) - name = config[id]["title"] - emoji = config[id]["emoji"] - - del config[id]["fields"][field_id] - - await self.update_config(interaction.guild, config) - await interaction.response.edit_message( - content=f"Field no. {field_id} has been removed from section {name} {emoji}.", view=None) - - async def button_callback(self, interaction: discord.Interaction): - config = await self.get_config(interaction.guild) - max_id = len(config) - 1 - - if interaction.custom_id == "next": - self.current_embed += 1 - if self.current_embed > max_id: - self.current_embed = 0 - else: - self.current_embed -= 1 - if self.current_embed < 0: - self.current_embed = max_id - - await interaction.response.edit_message(embed=await get_embed(self.current_embed, config), view=self.main_view, - content=None) - - async def reset_callback(self, interaction: discord.Interaction): - """Factory reset all the sections in /info """ - - if interaction.custom_id == "no": - await interaction.response.edit_message(content="Nothing has changed.", view=None) - else: - # load default config - default_config = await self.get_default_config() - - # replace the contents of the file with the default values - await self.update_config(interaction.guild, default_config) - - await interaction.response.edit_message(content="Default settings have been restored.", view=None) - - # this initializes default /info content for the guild the bot joins @commands.Cog.listener(name="on_guild_join") async def load_defaults(self, guild): - # check if there already is a config for the guild present - if not Path(os.path.join(self.bot.data_dir, "info", f"{guild.id}.json")).exists(): - # load default config - default_config = await self.get_default_config() - - # make config file for a guild - await self.update_config(guild, default_config) - + """Load default config file when bot joins guild.""" + if not self.config.exists(guild): + await self.config.load_default(guild) def setup(bot): bot.add_cog(InfoCog(bot)) From 1a02973848f123653a1a1ac01bdf0e211cb02246 Mon Sep 17 00:00:00 2001 From: Caraffa-git Date: Tue, 25 Jun 2024 22:12:46 +0200 Subject: [PATCH 02/18] navigation view class --- thatkitebot/cogs/info.py | 1 + 1 file changed, 1 insertion(+) diff --git a/thatkitebot/cogs/info.py b/thatkitebot/cogs/info.py index fff0f6d..7990ade 100644 --- a/thatkitebot/cogs/info.py +++ b/thatkitebot/cogs/info.py @@ -96,6 +96,7 @@ async def dropdown_callback(self, interaction: discord.Interaction): await interaction.response.send_message("You selected a section!") + class InfoCog(commands.Cog): def __init__(self, bot): self.bot = bot From 2a022b2e213e78f495fc6db03ba6aa9d3a39d89f Mon Sep 17 00:00:00 2001 From: Caraffa-git Date: Tue, 25 Jun 2024 22:20:58 +0200 Subject: [PATCH 03/18] comments --- thatkitebot/cogs/info.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/thatkitebot/cogs/info.py b/thatkitebot/cogs/info.py index 7990ade..e79453e 100644 --- a/thatkitebot/cogs/info.py +++ b/thatkitebot/cogs/info.py @@ -30,6 +30,7 @@ async def load_default(self, guild): await self.update(guild, await self.get_default()) async def get(self, guild): + """Get config json file""" try: async with aiofiles.open(os.path.join(self.data_dir, f"info/{guild.id}.json"), "r") as f: return json.loads(await f.read()) @@ -44,12 +45,13 @@ async def update(self, guild, config): await f.write(json.dumps(config)) async def get_sections(self, ctx: discord.AutocompleteContext): + """Autocomplete function for section names.""" config = await self.get(ctx.interaction.guild) return [f"{id + 1}. {section['title']}" for id, section in enumerate(config) if section['title'].lower().startswith(ctx.value.lower())] -class Navigation(View): +class NavigationView(View): def __init__(self, config_file = None, buttons: bool=False, dropdown: bool=True): super().__init__(timeout = None) if dropdown: @@ -58,6 +60,7 @@ def __init__(self, config_file = None, buttons: bool=False, dropdown: bool=True) self.add_buttons() def add_buttons(self): + """Add navigation buttons.""" prev = Button(emoji="⬅️", style=discord.ButtonStyle.gray, custom_id="prev") next = Button(emoji="➡️", style=discord.ButtonStyle.gray, custom_id="next") @@ -68,6 +71,7 @@ def add_buttons(self): self.add_item(next) def add_dropdown(self, config_file): + """Add dropdown with options.""" options = [] for id, section in enumerate(config_file): if discord_emoji.to_uni(section["emoji"]): @@ -87,12 +91,15 @@ def add_dropdown(self, config_file): self.add_item(select) async def prev_button_callback(self, interaction: discord.Interaction): + """Callback function for the previous button.""" await interaction.response.send_message("You selected prev button!") async def next_button_callback(self, interaction: discord.Interaction): + """Callback function for the next button.""" await interaction.response.send_message("You selected next button!") async def dropdown_callback(self, interaction: discord.Interaction): + """Callback function for the dropdown selection.""" await interaction.response.send_message("You selected a section!") @@ -118,11 +125,13 @@ async def info(self, ctx: discord.ApplicationContext, return config_file = await self.config.get(ctx.guild) - navigation = Navigation(config_file, buttons=False, dropdown=True) + navigation = NavigationView(config_file, buttons=False, dropdown=True) await ctx.respond("Choose a section!", view=navigation) + # TODO it's just temporary debug function async def cog_command_error(self, ctx: commands.Context, error: commands.CommandError): + """Error handler for the cog.""" await ctx.send(f"{error}") @commands.Cog.listener(name="on_guild_join") From 778adebd5eae6424fbb68614113873c078782770 Mon Sep 17 00:00:00 2001 From: Caraffa-git Date: Tue, 25 Jun 2024 22:54:35 +0200 Subject: [PATCH 04/18] refactor --- thatkitebot/embeds/info.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/thatkitebot/embeds/info.py b/thatkitebot/embeds/info.py index 19f1bf2..54d7ad3 100644 --- a/thatkitebot/embeds/info.py +++ b/thatkitebot/embeds/info.py @@ -3,8 +3,8 @@ from discord import Embed, Color # prepares embed by given section id -async def get_embed(id, config): - section_config = config[id] +async def get_embed(id, config_file): + section_config = config_file[id] embed = Embed(title = f'{section_config["title"]} {section_config["emoji"]}', color = Color(int(section_config["color"]))) From e5cca0c58bc9afef9bb64710c5e138675d92c72c Mon Sep 17 00:00:00 2001 From: Caraffa-git Date: Tue, 25 Jun 2024 23:05:13 +0200 Subject: [PATCH 05/18] minor changes --- thatkitebot/embeds/info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thatkitebot/embeds/info.py b/thatkitebot/embeds/info.py index 54d7ad3..50e9900 100644 --- a/thatkitebot/embeds/info.py +++ b/thatkitebot/embeds/info.py @@ -3,7 +3,7 @@ from discord import Embed, Color # prepares embed by given section id -async def get_embed(id, config_file): +async def get_embed(id: int, config_file): section_config = config_file[id] embed = Embed(title = f'{section_config["title"]} {section_config["emoji"]}', color = Color(int(section_config["color"]))) From b0733506fdbd974a62324bcf95f6ec0701a4b8b3 Mon Sep 17 00:00:00 2001 From: Caraffa-git Date: Tue, 25 Jun 2024 23:20:50 +0200 Subject: [PATCH 06/18] working dropdown list --- thatkitebot/cogs/info.py | 52 ++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/thatkitebot/cogs/info.py b/thatkitebot/cogs/info.py index e79453e..e24c2a5 100644 --- a/thatkitebot/cogs/info.py +++ b/thatkitebot/cogs/info.py @@ -11,6 +11,8 @@ from discord.ui import Select, View, Button from discord.commands import Option, SlashCommandGroup +from thatkitebot.embeds.info import * + class Config: def __init__(self, bot, data_dir): self.bot = bot @@ -29,6 +31,11 @@ async def load_default(self, guild): """Load default config for guild.""" await self.update(guild, await self.get_default()) + async def update(self, guild, config): + """Update guild config.""" + async with aiofiles.open(os.path.join(self.data_dir, f"info/{guild.id}.json"), "w") as f: + await f.write(json.dumps(config)) + async def get(self, guild): """Get config json file""" try: @@ -39,26 +46,33 @@ async def get(self, guild): await self.update(guild, default) return await self.get(guild) - async def update(self, guild, config): - """Update guild config.""" - async with aiofiles.open(os.path.join(self.data_dir, f"info/{guild.id}.json"), "w") as f: - await f.write(json.dumps(config)) - async def get_sections(self, ctx: discord.AutocompleteContext): """Autocomplete function for section names.""" config = await self.get(ctx.interaction.guild) return [f"{id + 1}. {section['title']}" for id, section in enumerate(config) if section['title'].lower().startswith(ctx.value.lower())] - class NavigationView(View): - def __init__(self, config_file = None, buttons: bool=False, dropdown: bool=True): + def __init__(self, config, config_file, buttons, dropdown, toggle): super().__init__(timeout = None) + + self.config = config + self.config_file = config_file + + # Current states of the view contents + self.toggle = toggle + self.state_buttons = buttons + self.state_dropdown = dropdown + if dropdown: self.add_dropdown(config_file) if buttons: self.add_buttons() + @staticmethod + async def create(guild, config: Config = None, buttons: bool=False, dropdown: bool= True, toggle: bool = False): + return NavigationView(config, await config.get(guild), buttons, dropdown, toggle) + def add_buttons(self): """Add navigation buttons.""" prev = Button(emoji="⬅️", style=discord.ButtonStyle.gray, custom_id="prev") @@ -75,9 +89,9 @@ def add_dropdown(self, config_file): options = [] for id, section in enumerate(config_file): if discord_emoji.to_uni(section["emoji"]): - options.append(discord.SelectOption(label=f'{id + 1}. {section["title"]}', emoji=discord_emoji.to_uni(section["emoji"]))) + options.append(discord.SelectOption(label=f'{id + 1}. {section["title"]}', emoji=discord_emoji.to_uni(section["emoji"]), value = f"{id}")) else: - options.append(discord.SelectOption(label=f'{id + 1}. {section["title"]}', emoji=section["emoji"])) + options.append(discord.SelectOption(label=f'{id + 1}. {section["title"]}', emoji=section["emoji"], value = f"{id}")) select = Select( placeholder='Select a section...', @@ -100,7 +114,22 @@ async def next_button_callback(self, interaction: discord.Interaction): async def dropdown_callback(self, interaction: discord.Interaction): """Callback function for the dropdown selection.""" - await interaction.response.send_message("You selected a section!") + id = int(interaction.data["values"][0]) + config_file = await self.config.get(interaction.guild) + + embed = await get_embed(id, config_file) + + if(self.toggle): + self.disable_all_items + else: + if(not self.state_buttons): + self.add_buttons() + self.state_buttons = True + if(not self.state_dropdown): + self.add_dropdown(config_file) + self.state_dropdown = True + + await interaction.response.edit_message(embed=embed, content=None, view = self) @@ -124,8 +153,7 @@ async def info(self, ctx: discord.ApplicationContext, await ctx.respond("Specify a section to disable navigation!", ephemeral=True) return - config_file = await self.config.get(ctx.guild) - navigation = NavigationView(config_file, buttons=False, dropdown=True) + navigation = await NavigationView.create(ctx.guild, self.config, toggle = disable_navigation) await ctx.respond("Choose a section!", view=navigation) From 79322152bc01afb5108b2c8cfeb776aa2068fc2d Mon Sep 17 00:00:00 2001 From: Caraffa-git Date: Wed, 26 Jun 2024 20:21:02 +0200 Subject: [PATCH 07/18] working base --- thatkitebot/cogs/info.py | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/thatkitebot/cogs/info.py b/thatkitebot/cogs/info.py index e24c2a5..77577e5 100644 --- a/thatkitebot/cogs/info.py +++ b/thatkitebot/cogs/info.py @@ -46,6 +46,11 @@ async def get(self, guild): await self.update(guild, default) return await self.get(guild) + async def size(self, guild): + """Get an amount of entries in config file""" + config_file = await self.get(guild) + return len(config_file) + async def get_sections(self, ctx: discord.AutocompleteContext): """Autocomplete function for section names.""" config = await self.get(ctx.interaction.guild) @@ -63,6 +68,7 @@ def __init__(self, config, config_file, buttons, dropdown, toggle): self.toggle = toggle self.state_buttons = buttons self.state_dropdown = dropdown + self.current_embed_id = None if dropdown: self.add_dropdown(config_file) @@ -78,8 +84,8 @@ def add_buttons(self): prev = Button(emoji="⬅️", style=discord.ButtonStyle.gray, custom_id="prev") next = Button(emoji="➡️", style=discord.ButtonStyle.gray, custom_id="next") - prev.callback = self.prev_button_callback - next.callback = self.next_button_callback + prev.callback = self.button_callback + next.callback = self.button_callback self.add_item(prev) self.add_item(next) @@ -104,20 +110,27 @@ def add_dropdown(self, config_file): self.add_item(select) - async def prev_button_callback(self, interaction: discord.Interaction): - """Callback function for the previous button.""" - await interaction.response.send_message("You selected prev button!") - - async def next_button_callback(self, interaction: discord.Interaction): + async def button_callback(self, interaction: discord.Interaction): """Callback function for the next button.""" - await interaction.response.send_message("You selected next button!") + max_id = await self.config.size(interaction.guild) + + if interaction.custom_id == "next": + self.current_embed_id = (self.current_embed_id + 1) % max_id + else: + self.current_embed_id = (self.current_embed_id - 1) % max_id + + config_file = await self.config.get(interaction.guild) + embed = await get_embed(self.current_embed_id, config_file) + + await interaction.response.edit_message(embed=embed, content=None, view = self) + async def dropdown_callback(self, interaction: discord.Interaction): """Callback function for the dropdown selection.""" - id = int(interaction.data["values"][0]) + self.current_embed_id = int(interaction.data["values"][0]) config_file = await self.config.get(interaction.guild) - embed = await get_embed(id, config_file) + embed = await get_embed(self.current_embed_id, config_file) if(self.toggle): self.disable_all_items From dac4bd186318c16ce95583710b9b45ed2a0f4915 Mon Sep 17 00:00:00 2001 From: Caraffa-git Date: Wed, 26 Jun 2024 21:10:38 +0200 Subject: [PATCH 08/18] working full info base --- thatkitebot/cogs/info.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/thatkitebot/cogs/info.py b/thatkitebot/cogs/info.py index 77577e5..f9350fb 100644 --- a/thatkitebot/cogs/info.py +++ b/thatkitebot/cogs/info.py @@ -53,7 +53,7 @@ async def size(self, guild): async def get_sections(self, ctx: discord.AutocompleteContext): """Autocomplete function for section names.""" - config = await self.get(ctx.interaction.guild) + config = await self.config.get(ctx.interaction.guild) return [f"{id + 1}. {section['title']}" for id, section in enumerate(config) if section['title'].lower().startswith(ctx.value.lower())] @@ -70,9 +70,9 @@ def __init__(self, config, config_file, buttons, dropdown, toggle): self.state_dropdown = dropdown self.current_embed_id = None - if dropdown: + if dropdown and not toggle: self.add_dropdown(config_file) - if buttons: + if buttons and not toggle: self.add_buttons() @staticmethod @@ -162,12 +162,20 @@ async def info(self, ctx: discord.ApplicationContext, # load default config if not already done await self.load_defaults(ctx.guild) + config_file = await self.config.get(ctx.guild) + if disable_navigation and not section: await ctx.respond("Specify a section to disable navigation!", ephemeral=True) return - navigation = await NavigationView.create(ctx.guild, self.config, toggle = disable_navigation) + if section: + navigation = await NavigationView.create(ctx.guild, self.config, toggle = disable_navigation, buttons= True) + section_id = await self.retrive_section_id(section, len(config_file)) + embed = await get_embed(section_id, config_file) + await ctx.respond(embed = embed, content = None, view=navigation) + return + navigation = await NavigationView.create(ctx.guild, self.config, toggle = disable_navigation) await ctx.respond("Choose a section!", view=navigation) # TODO it's just temporary debug function @@ -181,5 +189,16 @@ async def load_defaults(self, guild): if not self.config.exists(guild): await self.config.load_default(guild) + # Utility + async def retrive_section_id(self, section_name, config_len): + try: + id = int(section_name.split(".")[0]) - 1 + if not (id >= 0 and id < config_len): + return -1 + except: + return -1 + + return id + def setup(bot): bot.add_cog(InfoCog(bot)) From 4650f395a56bbde507cadae6620507d40447ed24 Mon Sep 17 00:00:00 2001 From: Caraffa-git Date: Wed, 26 Jun 2024 21:45:19 +0200 Subject: [PATCH 09/18] settings - add and remove --- thatkitebot/cogs/info.py | 120 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 114 insertions(+), 6 deletions(-) diff --git a/thatkitebot/cogs/info.py b/thatkitebot/cogs/info.py index f9350fb..a29b738 100644 --- a/thatkitebot/cogs/info.py +++ b/thatkitebot/cogs/info.py @@ -11,6 +11,7 @@ from discord.ui import Select, View, Button from discord.commands import Option, SlashCommandGroup +from thatkitebot.base.util import PermissonChecks as pc from thatkitebot.embeds.info import * class Config: @@ -170,14 +171,89 @@ async def info(self, ctx: discord.ApplicationContext, if section: navigation = await NavigationView.create(ctx.guild, self.config, toggle = disable_navigation, buttons= True) - section_id = await self.retrive_section_id(section, len(config_file)) + section_id = await Utility.retrive_section_id(section, len(config_file)) embed = await get_embed(section_id, config_file) await ctx.respond(embed = embed, content = None, view=navigation) return navigation = await NavigationView.create(ctx.guild, self.config, toggle = disable_navigation) await ctx.respond("Choose a section!", view=navigation) - + + + ###### Group commands ###### + + info_settings = SlashCommandGroup( + "info-settings", + "Settings for /info command", + checks=[pc.mods_can_change_settings] + ) + + @info_settings.command(name="add-section") + async def new_section(self, ctx: discord.ApplicationContext, + name: Option(str, "Choose a name!", required=True, max_lenght=256), + emoji: Option(str, "Choose an emoji! (e.g :lightbulb:)", required=True), + color: Option(str, "Choose a color! (hex e.g. 0x123456)", required=False) = None): + """ + Create new section in /info command + """ + config_file = await self.config.get(ctx.guild) + + # check if given string is valid discord emoji + if not Utility.check_emoji(emoji=emoji): + await ctx.respond("Invalid emoji!", ephemeral=True) + return + + # if it's unicode emoji, convert it into string + if discord_emoji.to_discord(emoji): + emoji = f":{discord_emoji.to_discord(emoji, get_all=True)}:" + + # check if color format is valid + if color: + if Utility.check_hex(color): + color = int(color, base=16) + else: + await ctx.respond("Invalid color format!", ephemeral=True) + return + else: + color = int(Utility.gen_random_hex_color(), base=16) + + dictionary = { + "title": f"{name}", + "emoji": f"{emoji}", + "color": f"{color}", + "fields": [ + { + "name": "", + "value": "", + "inline": True + } + ], + "footer": "false" + } + + config_file.append(dictionary) + await self.config.update(ctx.guild, config_file) + + await ctx.respond(f"Section {name} {emoji} has been created, now you can add a new field or edit existing one.") + + @info_settings.command(name="remove-section") + async def remove_section(self, ctx, name: Option(str, "Pick a section!", required=True, autocomplete=Config.get_sections)): + """Remove section in /info command""" + config_file = await self.config.get(ctx.guild) + + id = await Utility.retrive_section_id(name, len(config_file)) + if id < 0: + await ctx.respond("Incorrect section name!", ephemeral=True) + return + + name = config_file[id]["title"] + emoji = config_file[id]["emoji"] + + del config_file[id] + + await self.config.update(ctx.guild, config_file) + await ctx.respond(f"Section {name} {emoji} has been removed.") + # TODO it's just temporary debug function async def cog_command_error(self, ctx: commands.Context, error: commands.CommandError): """Error handler for the cog.""" @@ -189,16 +265,48 @@ async def load_defaults(self, guild): if not self.config.exists(guild): await self.config.load_default(guild) - # Utility - async def retrive_section_id(self, section_name, config_len): + +class Utility: + # generate random 24 bit number + @staticmethod + def gen_random_hex_color(): + def get_int(): + return random.randint(0, 255) + + return f'0x{get_int():02X}{get_int():02X}{get_int():02X}' + + + # check if given string is valid discord emoji + @staticmethod + def check_emoji(emoji): + emoji_regex = r"<\S+:\d+>" + if len(emoji) == 1: + return True + elif re.match(emoji_regex, emoji): + return True + else: + return False + + @staticmethod + def check_hex(s): + if s.startswith('#'): + s = s[1:] + try: + int(s, 16) + return True + except ValueError: + return False + + @staticmethod + async def retrive_section_id(section_name, config_len): try: id = int(section_name.split(".")[0]) - 1 - if not (id >= 0 and id < config_len): + if not (id >= 0 and id <= config_len): return -1 except: return -1 - return id + return id def setup(bot): bot.add_cog(InfoCog(bot)) From 5938a9cd91401268385c32a36347d8c9afee5195 Mon Sep 17 00:00:00 2001 From: Caraffa-git Date: Wed, 26 Jun 2024 23:22:14 +0200 Subject: [PATCH 10/18] settings - add, edit, remove fields and factory reset --- thatkitebot/cogs/info.py | 208 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) diff --git a/thatkitebot/cogs/info.py b/thatkitebot/cogs/info.py index a29b738..541677d 100644 --- a/thatkitebot/cogs/info.py +++ b/thatkitebot/cogs/info.py @@ -3,6 +3,7 @@ import json import random from pathlib import Path +from typing import Literal import aiofiles import discord_emoji @@ -145,6 +146,155 @@ async def dropdown_callback(self, interaction: discord.Interaction): await interaction.response.edit_message(embed=embed, content=None, view = self) +class EditFieldsView(View): + def __init__(self, bot, config, config_file, id ,action): + super().__init__(timeout = None) + + self.bot = bot + self.config = config + self.config_file = config_file + + self.add_dropdown(config_file, id, action) + + @staticmethod + async def create(bot, guild, config: Config, id, action: Literal["edit", "remove"]): + return EditFieldsView(bot, config, await config.get(guild), id, action) + + def add_dropdown(self, config_file, id, action): + """Add dropdown with options.""" + options = [] + for field_id, section in enumerate(config_file [id]["fields"]): + options.append(discord.SelectOption(label=f'{id + 1}.{field_id + 1}. {section["name"]}')) + + select = Select( + placeholder='Select a field...', + min_values=1, + max_values=1, + options=options + ) + + if(action == "remove"): + select.callback = self.remove_field_callback + if(action == "edit"): + select.callback = self.edit_field_callback + + self.add_item(select) + + async def edit_field_callback(self, interaction: discord.Interaction): + # get id and field id + id = int(interaction.data["values"][0].split(".")[0]) - 1 + field_id = int(interaction.data["values"][0].split(".")[1]) - 1 + + modal = FieldModal(title=f"Field editor :pencil:", + config_file=await self.config.get(interaction.guild), section_id=id, field_id=field_id, + bot=self.bot) + + await interaction.response.send_modal(modal) + + async def remove_field_callback(self, interaction: discord.Interaction): + # get id and field id + id = int(interaction.data["values"][0].split(".")[0]) - 1 + field_id = int(interaction.data["values"][0].split(".")[1]) - 1 + + config_file = await self.config.get(interaction.guild) + name = config_file[id]["title"] + emoji = config_file[id]["emoji"] + + del config_file[id]["fields"][field_id] + + await self.config.update(interaction.guild, config_file) + await interaction.response.edit_message( + content=f"Field no. {field_id + 1} has been removed from section {name} {emoji}.", view=None) + +class FieldModal(discord.ui.Modal): + def __init__(self, bot, config_file, section_id, field_id, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + + self.bot = bot + self.config_file = config_file + self.section_id = section_id + self.field_id = field_id + + name = config_file[section_id]["fields"][field_id]["name"] + contents = config_file[section_id]["fields"][field_id]["value"] + inline = config_file[section_id]["fields"][field_id]["inline"] + + _title = discord.ui.InputText( + label="Title", + value=name if name != "" else "Example title 💡", + placeholder="Think of an interesting title", + max_length=256 + ) + + _contents = discord.ui.InputText( + label="Contents", + value=contents if contents != "" else "[Example hyperlink](https://github.com/ThatRedKite/thatkitebot)", + placeholder="Here you can type the contents of this field in your embed", + style=discord.InputTextStyle.long, + max_length=1024 + ) + + _inline = discord.ui.InputText( + label="In-line", + value=inline if inline != "" else "True", + placeholder="True or False", + max_length=5 + ) + + self.add_item(_title) + self.add_item(_contents) + self.add_item(_inline) + + async def callback(self, interaction: discord.Interaction): + + self.config_file[self.section_id]["fields"][self.field_id]["name"] = self.children[0].value # set title of the field + self.config_file[self.section_id]["fields"][self.field_id]["value"] = self.children[1].value # set value of the field + + # check if "inline" input value is correct + if str(self.children[2].value).lower() == "true": + self.config_file[self.section_id]["fields"][self.field_id]["inline"] = True + elif str(self.children[2].value).lower() == "false": + self.config_file[self.section_id]["fields"][self.field_id]["inline"] = False + else: + await interaction.response.send_message("Invalid `in-line` value!", ephemeral=True) + return + + # update config + await self.bot.get_cog("InfoCog").config.update(interaction.guild, self.config_file) + + await interaction.response.send_message( + f"Field with ID {self.field_id + 1} in section {self.config_file[self.section_id]['title']} has been changed.") + + + +class FactoryResetModal(discord.ui.Modal): + def __init__(self, config, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + + self.config = config + + _decision = discord.ui.InputText( + label="Are you sure? Type \"YES\" or \"NO\"", + value="", + placeholder="It will **replace all of your sections!", + max_length=256 + ) + + self.add_item(_decision) + + async def callback(self, interaction: discord.Interaction): + """Factory reset all the sections in /info """ + + if self.children[0].value == "YES": + # load default config + default_config = await self.config.get_default() + + # replace the contents of the file with the default values + await self.config.update(interaction.guild, default_config) + + await interaction.response.send_message(content="Default settings have been restored.", view=None) + else: + await interaction.response.send_message(content="Nothing has changed.", view=None) class InfoCog(commands.Cog): @@ -254,6 +404,64 @@ async def remove_section(self, ctx, name: Option(str, "Pick a section!", require await self.config.update(ctx.guild, config_file) await ctx.respond(f"Section {name} {emoji} has been removed.") + @info_settings.command(name="edit-field") + async def edit_field(self, ctx: discord.ApplicationContext, + section: Option(str, "Pick a section!", required=True, autocomplete=Config.get_sections), + option: Option(str, "Choose option.", required=True, choices=["edit", "remove", "add"])): + """Add, edit or remove a field in section""" + + config_file = await self.config.get(ctx.guild) + + empty_field = { + "name": "", + "value": "", + "inline": True + } + + # check if "option" input is valid + if option not in {"edit", "remove", "add"}: + await ctx.respond("Incorrect option!", ephemeral=True) + return + + # check if given section name has valid id + id = await Utility.retrive_section_id(section, len(config_file)) + if id < 0: + await ctx.respond("Incorrect section name!", ephemeral=True) + return + + # add empty field to config + if option == "add": + config_file[id]["fields"].append(empty_field) + new_field_id = len(config_file[id]["fields"]) - 1 + + # prepare modal for user to edit field + await self.config.update(ctx.guild, config_file) + config_file = await self.config.get(ctx.guild) + modal = FieldModal(title=f"Editing new field {discord_emoji.to_uni(":pencil:")}", + config_file=config_file, section_id=id, + field_id= new_field_id, bot=self.bot) + + await ctx.send_modal(modal) + return + + # check if there are any fields to edit/remove + if len(config_file[id]["fields"]) == 0: + await ctx.respond("There is no fields to edit/remove!") + return + + # prepare dropdown list of existing fields to edit/remove + view = await EditFieldsView.create(self.bot, ctx.guild, self.config, id, option) + + await ctx.respond(view=view) + +# TODO edit_section edit_footer (merge it in edit_section) + + @info_settings.command(name="factory-reset") + async def factory_reset(self, ctx): + modal = FactoryResetModal(title="Factory Reset!", config=self.config) + await ctx.send_modal(modal) + + # TODO it's just temporary debug function async def cog_command_error(self, ctx: commands.Context, error: commands.CommandError): """Error handler for the cog.""" From f0f7b96f2d54bc90fd4ac4f276d186377186031d Mon Sep 17 00:00:00 2001 From: Caraffa-git Date: Thu, 27 Jun 2024 10:51:16 +0200 Subject: [PATCH 11/18] refactor --- thatkitebot/cogs/info.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/thatkitebot/cogs/info.py b/thatkitebot/cogs/info.py index 541677d..76f50e4 100644 --- a/thatkitebot/cogs/info.py +++ b/thatkitebot/cogs/info.py @@ -236,8 +236,8 @@ def __init__(self, bot, config_file, section_id, field_id, *args, **kwargs) -> N _inline = discord.ui.InputText( label="In-line", - value=inline if inline != "" else "True", - placeholder="True or False", + value=inline if inline != "" else "true", + placeholder="Type \"true\" or \"false\"", max_length=5 ) @@ -250,11 +250,11 @@ async def callback(self, interaction: discord.Interaction): self.config_file[self.section_id]["fields"][self.field_id]["name"] = self.children[0].value # set title of the field self.config_file[self.section_id]["fields"][self.field_id]["value"] = self.children[1].value # set value of the field - # check if "inline" input value is correct - if str(self.children[2].value).lower() == "true": - self.config_file[self.section_id]["fields"][self.field_id]["inline"] = True - elif str(self.children[2].value).lower() == "false": - self.config_file[self.section_id]["fields"][self.field_id]["inline"] = False + # Check if "inline" input value is correct + inline_value = str(self.children[2].value).strip().lower() + + if inline_value in {"true", "false"}: + self.config_file[self.section_id]["fields"][self.field_id]["inline"] = (inline_value == "true") else: await interaction.response.send_message("Invalid `in-line` value!", ephemeral=True) return @@ -458,6 +458,7 @@ async def edit_field(self, ctx: discord.ApplicationContext, @info_settings.command(name="factory-reset") async def factory_reset(self, ctx): + """Factory reset of all the sections in /info """ modal = FactoryResetModal(title="Factory Reset!", config=self.config) await ctx.send_modal(modal) From 89eea1780ed587752422040f8e4a55df9f53df1d Mon Sep 17 00:00:00 2001 From: Caraffa-git Date: Thu, 27 Jun 2024 11:04:01 +0200 Subject: [PATCH 12/18] refactor --- thatkitebot/cogs/info.py | 364 ++++++++++++++++++++------------------- 1 file changed, 185 insertions(+), 179 deletions(-) diff --git a/thatkitebot/cogs/info.py b/thatkitebot/cogs/info.py index 76f50e4..b53205c 100644 --- a/thatkitebot/cogs/info.py +++ b/thatkitebot/cogs/info.py @@ -59,6 +59,186 @@ async def get_sections(self, ctx: discord.AutocompleteContext): return [f"{id + 1}. {section['title']}" for id, section in enumerate(config) if section['title'].lower().startswith(ctx.value.lower())] +class InfoCog(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.config = Config(bot, bot.data_dir) + + @commands.slash_command(name="info") + async def info(self, ctx: discord.ApplicationContext, + section: Option(str, "Pick a section!", required=False, autocomplete=Config.get_sections) = None, # type: ignore + disable_navigation: Option(bool, "Disable navigation", required=False) = False): # type: ignore + """ + Sends YT channels, documents, books etc. related to chosen science topic arranged in convenient way. + """ + + # load default config if not already done + await self.load_defaults(ctx.guild) + + config_file = await self.config.get(ctx.guild) + + if disable_navigation and not section: + await ctx.respond("Specify a section to disable navigation!", ephemeral=True) + return + + if section: + navigation = await NavigationView.create(ctx.guild, self.config, toggle = disable_navigation, buttons= True) + section_id = await Utility.retrive_section_id(section, len(config_file)) + embed = await get_embed(section_id, config_file) + await ctx.respond(embed = embed, content = None, view=navigation) + return + + navigation = await NavigationView.create(ctx.guild, self.config, toggle = disable_navigation) + await ctx.respond("Choose a section!", view=navigation) + + + ###### Group commands ###### + + info_settings = SlashCommandGroup( + "info-settings", + "Settings for /info command", + checks=[pc.mods_can_change_settings] + ) + + @info_settings.command(name="add-section") + async def new_section(self, ctx: discord.ApplicationContext, + name: Option(str, "Choose a name!", required=True, max_lenght=256), + emoji: Option(str, "Choose an emoji! (e.g :lightbulb:)", required=True), + color: Option(str, "Choose a color! (hex e.g. 0x123456)", required=False) = None): + """ + Create new section in /info command + """ + config_file = await self.config.get(ctx.guild) + + # check if given string is valid discord emoji + if not Utility.check_emoji(emoji=emoji): + await ctx.respond("Invalid emoji!", ephemeral=True) + return + + # if it's unicode emoji, convert it into string + if discord_emoji.to_discord(emoji): + emoji = f":{discord_emoji.to_discord(emoji, get_all=True)}:" + + # check if color format is valid + if color: + if Utility.check_hex(color): + color = int(color, base=16) + else: + await ctx.respond("Invalid color format!", ephemeral=True) + return + else: + color = int(Utility.gen_random_hex_color(), base=16) + + dictionary = { + "title": f"{name}", + "emoji": f"{emoji}", + "color": f"{color}", + "fields": [ + { + "name": "", + "value": "", + "inline": True + } + ], + "footer": "false" + } + + config_file.append(dictionary) + await self.config.update(ctx.guild, config_file) + + await ctx.respond(f"Section {name} {emoji} has been created, now you can add a new field or edit existing one.") + + @info_settings.command(name="remove-section") + async def remove_section(self, ctx, name: Option(str, "Pick a section!", required=True, autocomplete=Config.get_sections)): + """Remove section in /info command""" + config_file = await self.config.get(ctx.guild) + + id = await Utility.retrive_section_id(name, len(config_file)) + if id < 0: + await ctx.respond("Incorrect section name!", ephemeral=True) + return + + name = config_file[id]["title"] + emoji = config_file[id]["emoji"] + + del config_file[id] + + await self.config.update(ctx.guild, config_file) + await ctx.respond(f"Section {name} {emoji} has been removed.") + + @info_settings.command(name="edit-field") + async def edit_field(self, ctx: discord.ApplicationContext, + section: Option(str, "Pick a section!", required=True, autocomplete=Config.get_sections), + option: Option(str, "Choose option.", required=True, choices=["edit", "remove", "add"])): + """Add, edit or remove a field in section""" + + config_file = await self.config.get(ctx.guild) + + empty_field = { + "name": "", + "value": "", + "inline": True + } + + # check if "option" input is valid + if option not in {"edit", "remove", "add"}: + await ctx.respond("Incorrect option!", ephemeral=True) + return + + # check if given section name has valid id + id = await Utility.retrive_section_id(section, len(config_file)) + if id < 0: + await ctx.respond("Incorrect section name!", ephemeral=True) + return + + # add empty field to config + if option == "add": + config_file[id]["fields"].append(empty_field) + new_field_id = len(config_file[id]["fields"]) - 1 + + # prepare modal for user to edit field + await self.config.update(ctx.guild, config_file) + config_file = await self.config.get(ctx.guild) + modal = FieldModal(title=f"Editing new field {discord_emoji.to_uni(":pencil:")}", + config_file=config_file, section_id=id, + field_id= new_field_id, bot=self.bot) + + await ctx.send_modal(modal) + return + + # check if there are any fields to edit/remove + if len(config_file[id]["fields"]) == 0: + await ctx.respond("There is no fields to edit/remove!") + return + + # prepare dropdown list of existing fields to edit/remove + view = await EditFieldsView.create(self.bot, ctx.guild, self.config, id, option) + + await ctx.respond(view=view) + +# TODO edit_section edit_footer (merge it in edit_section) + + @info_settings.command(name="factory-reset") + async def factory_reset(self, ctx): + """Factory reset of all the sections in /info """ + modal = FactoryResetModal(title="Factory Reset!", config=self.config) + await ctx.send_modal(modal) + + + # TODO it's just temporary debug function + async def cog_command_error(self, ctx: commands.Context, error: commands.CommandError): + """Error handler for the cog.""" + await ctx.send(f"{error}") + + @commands.Cog.listener(name="on_guild_join") + async def load_defaults(self, guild): + """Load default config file when bot joins guild.""" + if not self.config.exists(guild): + await self.config.load_default(guild) + + +###### Views ###### + class NavigationView(View): def __init__(self, config, config_file, buttons, dropdown, toggle): super().__init__(timeout = None) @@ -206,6 +386,9 @@ async def remove_field_callback(self, interaction: discord.Interaction): await interaction.response.edit_message( content=f"Field no. {field_id + 1} has been removed from section {name} {emoji}.", view=None) + +###### Modals ###### + class FieldModal(discord.ui.Modal): def __init__(self, bot, config_file, section_id, field_id, *args, **kwargs) -> None: super().__init__(*args, **kwargs) @@ -275,9 +458,8 @@ def __init__(self, config, *args, **kwargs) -> None: _decision = discord.ui.InputText( label="Are you sure? Type \"YES\" or \"NO\"", - value="", placeholder="It will **replace all of your sections!", - max_length=256 + max_length=3 ) self.add_item(_decision) @@ -297,184 +479,8 @@ async def callback(self, interaction: discord.Interaction): await interaction.response.send_message(content="Nothing has changed.", view=None) -class InfoCog(commands.Cog): - def __init__(self, bot): - self.bot = bot - self.config = Config(bot, bot.data_dir) - - @commands.slash_command(name="info") - async def info(self, ctx: discord.ApplicationContext, - section: Option(str, "Pick a section!", required=False, autocomplete=Config.get_sections) = None, # type: ignore - disable_navigation: Option(bool, "Disable navigation", required=False) = False): # type: ignore - """ - Sends YT channels, documents, books etc. related to chosen science topic arranged in convenient way. - """ - - # load default config if not already done - await self.load_defaults(ctx.guild) - - config_file = await self.config.get(ctx.guild) - - if disable_navigation and not section: - await ctx.respond("Specify a section to disable navigation!", ephemeral=True) - return - - if section: - navigation = await NavigationView.create(ctx.guild, self.config, toggle = disable_navigation, buttons= True) - section_id = await Utility.retrive_section_id(section, len(config_file)) - embed = await get_embed(section_id, config_file) - await ctx.respond(embed = embed, content = None, view=navigation) - return - - navigation = await NavigationView.create(ctx.guild, self.config, toggle = disable_navigation) - await ctx.respond("Choose a section!", view=navigation) - - - ###### Group commands ###### - - info_settings = SlashCommandGroup( - "info-settings", - "Settings for /info command", - checks=[pc.mods_can_change_settings] - ) - - @info_settings.command(name="add-section") - async def new_section(self, ctx: discord.ApplicationContext, - name: Option(str, "Choose a name!", required=True, max_lenght=256), - emoji: Option(str, "Choose an emoji! (e.g :lightbulb:)", required=True), - color: Option(str, "Choose a color! (hex e.g. 0x123456)", required=False) = None): - """ - Create new section in /info command - """ - config_file = await self.config.get(ctx.guild) - - # check if given string is valid discord emoji - if not Utility.check_emoji(emoji=emoji): - await ctx.respond("Invalid emoji!", ephemeral=True) - return - - # if it's unicode emoji, convert it into string - if discord_emoji.to_discord(emoji): - emoji = f":{discord_emoji.to_discord(emoji, get_all=True)}:" - - # check if color format is valid - if color: - if Utility.check_hex(color): - color = int(color, base=16) - else: - await ctx.respond("Invalid color format!", ephemeral=True) - return - else: - color = int(Utility.gen_random_hex_color(), base=16) - - dictionary = { - "title": f"{name}", - "emoji": f"{emoji}", - "color": f"{color}", - "fields": [ - { - "name": "", - "value": "", - "inline": True - } - ], - "footer": "false" - } - - config_file.append(dictionary) - await self.config.update(ctx.guild, config_file) - - await ctx.respond(f"Section {name} {emoji} has been created, now you can add a new field or edit existing one.") +###### Utility ###### - @info_settings.command(name="remove-section") - async def remove_section(self, ctx, name: Option(str, "Pick a section!", required=True, autocomplete=Config.get_sections)): - """Remove section in /info command""" - config_file = await self.config.get(ctx.guild) - - id = await Utility.retrive_section_id(name, len(config_file)) - if id < 0: - await ctx.respond("Incorrect section name!", ephemeral=True) - return - - name = config_file[id]["title"] - emoji = config_file[id]["emoji"] - - del config_file[id] - - await self.config.update(ctx.guild, config_file) - await ctx.respond(f"Section {name} {emoji} has been removed.") - - @info_settings.command(name="edit-field") - async def edit_field(self, ctx: discord.ApplicationContext, - section: Option(str, "Pick a section!", required=True, autocomplete=Config.get_sections), - option: Option(str, "Choose option.", required=True, choices=["edit", "remove", "add"])): - """Add, edit or remove a field in section""" - - config_file = await self.config.get(ctx.guild) - - empty_field = { - "name": "", - "value": "", - "inline": True - } - - # check if "option" input is valid - if option not in {"edit", "remove", "add"}: - await ctx.respond("Incorrect option!", ephemeral=True) - return - - # check if given section name has valid id - id = await Utility.retrive_section_id(section, len(config_file)) - if id < 0: - await ctx.respond("Incorrect section name!", ephemeral=True) - return - - # add empty field to config - if option == "add": - config_file[id]["fields"].append(empty_field) - new_field_id = len(config_file[id]["fields"]) - 1 - - # prepare modal for user to edit field - await self.config.update(ctx.guild, config_file) - config_file = await self.config.get(ctx.guild) - modal = FieldModal(title=f"Editing new field {discord_emoji.to_uni(":pencil:")}", - config_file=config_file, section_id=id, - field_id= new_field_id, bot=self.bot) - - await ctx.send_modal(modal) - return - - # check if there are any fields to edit/remove - if len(config_file[id]["fields"]) == 0: - await ctx.respond("There is no fields to edit/remove!") - return - - # prepare dropdown list of existing fields to edit/remove - view = await EditFieldsView.create(self.bot, ctx.guild, self.config, id, option) - - await ctx.respond(view=view) - -# TODO edit_section edit_footer (merge it in edit_section) - - @info_settings.command(name="factory-reset") - async def factory_reset(self, ctx): - """Factory reset of all the sections in /info """ - modal = FactoryResetModal(title="Factory Reset!", config=self.config) - await ctx.send_modal(modal) - - - # TODO it's just temporary debug function - async def cog_command_error(self, ctx: commands.Context, error: commands.CommandError): - """Error handler for the cog.""" - await ctx.send(f"{error}") - - @commands.Cog.listener(name="on_guild_join") - async def load_defaults(self, guild): - """Load default config file when bot joins guild.""" - if not self.config.exists(guild): - await self.config.load_default(guild) - - class Utility: # generate random 24 bit number @staticmethod From 65b3a46fac44d8511a789b8da417158d7964e07d Mon Sep 17 00:00:00 2001 From: Caraffa-git Date: Thu, 27 Jun 2024 11:58:21 +0200 Subject: [PATCH 13/18] editing sections --- thatkitebot/cogs/info.py | 105 ++++++++++++++++++++++++++++++++------- 1 file changed, 87 insertions(+), 18 deletions(-) diff --git a/thatkitebot/cogs/info.py b/thatkitebot/cogs/info.py index b53205c..a226720 100644 --- a/thatkitebot/cogs/info.py +++ b/thatkitebot/cogs/info.py @@ -9,7 +9,7 @@ import discord_emoji import discord from discord.ext import commands -from discord.ui import Select, View, Button +from discord.ui import Select, View, Button, Modal, InputText from discord.commands import Option, SlashCommandGroup from thatkitebot.base.util import PermissonChecks as pc @@ -121,11 +121,11 @@ async def new_section(self, ctx: discord.ApplicationContext, # check if color format is valid if color: - if Utility.check_hex(color): - color = int(color, base=16) - else: + if not Utility.convert_hex(color): await ctx.respond("Invalid color format!", ephemeral=True) return + color = Utility.convert_hex(color) + else: color = int(Utility.gen_random_hex_color(), base=16) @@ -218,6 +218,21 @@ async def edit_field(self, ctx: discord.ApplicationContext, # TODO edit_section edit_footer (merge it in edit_section) + @info_settings.command(name="edit-section") + async def edit_section(self, ctx: discord.ApplicationContext, + section: Option(str, "Pick a section!", required=True, autocomplete=Config.get_sections)): + """Change name, emoji, footer or color of a given section""" + config_file = await self.config.get(ctx.guild) + + section_id = await Utility.retrive_section_id(section, len(config_file)) + + if section_id < 0: + await ctx.respond("Incorrect section name!", ephemeral=True) + return + + modal = EditSectionModal(self.config, config_file, section_id, title = "Edit Section") + await ctx.send_modal(modal) + @info_settings.command(name="factory-reset") async def factory_reset(self, ctx): """Factory reset of all the sections in /info """ @@ -389,7 +404,7 @@ async def remove_field_callback(self, interaction: discord.Interaction): ###### Modals ###### -class FieldModal(discord.ui.Modal): +class FieldModal(Modal): def __init__(self, bot, config_file, section_id, field_id, *args, **kwargs) -> None: super().__init__(*args, **kwargs) @@ -402,14 +417,14 @@ def __init__(self, bot, config_file, section_id, field_id, *args, **kwargs) -> N contents = config_file[section_id]["fields"][field_id]["value"] inline = config_file[section_id]["fields"][field_id]["inline"] - _title = discord.ui.InputText( + _title = InputText( label="Title", value=name if name != "" else "Example title 💡", placeholder="Think of an interesting title", max_length=256 ) - _contents = discord.ui.InputText( + _contents = InputText( label="Contents", value=contents if contents != "" else "[Example hyperlink](https://github.com/ThatRedKite/thatkitebot)", placeholder="Here you can type the contents of this field in your embed", @@ -417,7 +432,7 @@ def __init__(self, bot, config_file, section_id, field_id, *args, **kwargs) -> N max_length=1024 ) - _inline = discord.ui.InputText( + _inline = InputText( label="In-line", value=inline if inline != "" else "true", placeholder="Type \"true\" or \"false\"", @@ -447,16 +462,15 @@ async def callback(self, interaction: discord.Interaction): await interaction.response.send_message( f"Field with ID {self.field_id + 1} in section {self.config_file[self.section_id]['title']} has been changed.") - -class FactoryResetModal(discord.ui.Modal): +class FactoryResetModal(Modal): def __init__(self, config, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.config = config - _decision = discord.ui.InputText( + _decision = InputText( label="Are you sure? Type \"YES\" or \"NO\"", placeholder="It will **replace all of your sections!", max_length=3 @@ -479,6 +493,57 @@ async def callback(self, interaction: discord.Interaction): await interaction.response.send_message(content="Nothing has changed.", view=None) +class EditSectionModal(Modal): + def __init__(self, config, config_file ,section_id,*args, **kwargs) -> None: + self.section_id = section_id + self.config = config + super().__init__(*args, **kwargs) + + title = config_file[self.section_id]["title"] + emoji = config_file[self.section_id]["emoji"] + color = hex(config_file[self.section_id]["color"]) + + self.add_item(InputText(label="Title", value = title, placeholder="Choose a title!", max_length=256, required=False)) + self.add_item(InputText(label="Emoji", value = emoji, placeholder="Choose an emoji! (e.g :lightbulb:)", required=False)) + self.add_item(InputText(label="Color", value = color, placeholder="Choose a color! (hex e.g. 0x123456)", required=False)) + + async def callback(self, interaction: discord.Interaction): + title = self.children[0].value + emoji = self.children[1].value + color = self.children[2].value + + config_file = await self.config.get(interaction.guild) + old_emoji = config_file[self.section_id]["emoji"] + old_section_title = config_file[self.section_id]["title"] + + counter = 0 + + if title: + config_file[self.section_id]["title"] = title + counter += 1 + + if emoji: + if not Utility.check_emoji(emoji): + await interaction.response.send_message("Invalid emoji!", ephemeral=True) + return + + if discord_emoji.to_discord(emoji): + emoji = f":{discord_emoji.to_discord(emoji, get_all=True)}:" + + config_file[self.section_id]["emoji"] = emoji + counter += 1 + + if color: + if not Utility.convert_hex(color): + await interaction.response.send_message("Invalid color format!", ephemeral=True) + return + + config_file[self.section_id]["color"] = Utility.convert_hex(color) + counter += 1 + + await self.config.update(interaction.guild, config_file) + await interaction.response.send_message(f"{counter} properties have been changed in {old_section_title} {old_emoji}", ephemeral=True) + ###### Utility ###### class Utility: @@ -494,23 +559,27 @@ def get_int(): # check if given string is valid discord emoji @staticmethod def check_emoji(emoji): - emoji_regex = r"<\S+:\d+>" - if len(emoji) == 1: + # Check if it's a standard emoji + if len(emoji) == 1 and discord_emoji.to_discord(emoji, get_all=True): + return True + # Check if it's a custom Discord emoji (format: ) + elif re.match(r"<:\w+:\d+>", emoji): return True - elif re.match(emoji_regex, emoji): + # Check if it's a text emoji (format: :name:) + elif discord_emoji.to_uni(emoji): return True else: return False @staticmethod - def check_hex(s): + def convert_hex(s): if s.startswith('#'): s = s[1:] try: - int(s, 16) - return True + val = int(s, 16) + return val except ValueError: - return False + return None @staticmethod async def retrive_section_id(section_name, config_len): From ef173315980830dc84ad4751f8d33143ac7463e3 Mon Sep 17 00:00:00 2001 From: Caraffa-git Date: Thu, 27 Jun 2024 11:59:41 +0200 Subject: [PATCH 14/18] minor changes --- thatkitebot/cogs/info.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/thatkitebot/cogs/info.py b/thatkitebot/cogs/info.py index a226720..3580367 100644 --- a/thatkitebot/cogs/info.py +++ b/thatkitebot/cogs/info.py @@ -520,7 +520,6 @@ async def callback(self, interaction: discord.Interaction): if title: config_file[self.section_id]["title"] = title - counter += 1 if emoji: if not Utility.check_emoji(emoji): @@ -531,7 +530,6 @@ async def callback(self, interaction: discord.Interaction): emoji = f":{discord_emoji.to_discord(emoji, get_all=True)}:" config_file[self.section_id]["emoji"] = emoji - counter += 1 if color: if not Utility.convert_hex(color): @@ -539,10 +537,9 @@ async def callback(self, interaction: discord.Interaction): return config_file[self.section_id]["color"] = Utility.convert_hex(color) - counter += 1 await self.config.update(interaction.guild, config_file) - await interaction.response.send_message(f"{counter} properties have been changed in {old_section_title} {old_emoji}", ephemeral=True) + await interaction.response.send_message(f"Section {old_section_title} {old_emoji} has beed updated", ephemeral=True) ###### Utility ###### From dd1cf8d7833902994622240b1cb602c2e0e17cae Mon Sep 17 00:00:00 2001 From: Caraffa-git Date: Thu, 27 Jun 2024 12:03:11 +0200 Subject: [PATCH 15/18] editing footer --- thatkitebot/cogs/info.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/thatkitebot/cogs/info.py b/thatkitebot/cogs/info.py index 3580367..3412578 100644 --- a/thatkitebot/cogs/info.py +++ b/thatkitebot/cogs/info.py @@ -216,7 +216,6 @@ async def edit_field(self, ctx: discord.ApplicationContext, await ctx.respond(view=view) -# TODO edit_section edit_footer (merge it in edit_section) @info_settings.command(name="edit-section") async def edit_section(self, ctx: discord.ApplicationContext, @@ -502,15 +501,19 @@ def __init__(self, config, config_file ,section_id,*args, **kwargs) -> None: title = config_file[self.section_id]["title"] emoji = config_file[self.section_id]["emoji"] color = hex(config_file[self.section_id]["color"]) + footer = config_file[self.section_id]["footer"] + self.add_item(InputText(label="Title", value = title, placeholder="Choose a title!", max_length=256, required=False)) self.add_item(InputText(label="Emoji", value = emoji, placeholder="Choose an emoji! (e.g :lightbulb:)", required=False)) self.add_item(InputText(label="Color", value = color, placeholder="Choose a color! (hex e.g. 0x123456)", required=False)) + self.add_item(InputText(label="Footer", value = footer, placeholder="Choose a footer!", max_length=2048, required=False)) async def callback(self, interaction: discord.Interaction): title = self.children[0].value emoji = self.children[1].value color = self.children[2].value + footer = self.children[3].value config_file = await self.config.get(interaction.guild) old_emoji = config_file[self.section_id]["emoji"] @@ -518,8 +521,8 @@ async def callback(self, interaction: discord.Interaction): counter = 0 - if title: - config_file[self.section_id]["title"] = title + config_file[self.section_id]["title"] = title + config_file[self.section_id]["footer"] = footer if emoji: if not Utility.check_emoji(emoji): From aac863713cd775a2fbf4752783275063ab79c5b8 Mon Sep 17 00:00:00 2001 From: Caraffa-git Date: Thu, 27 Jun 2024 19:08:39 +0200 Subject: [PATCH 16/18] clean up --- thatkitebot/cogs/info.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/thatkitebot/cogs/info.py b/thatkitebot/cogs/info.py index 3412578..94ad0ea 100644 --- a/thatkitebot/cogs/info.py +++ b/thatkitebot/cogs/info.py @@ -239,11 +239,6 @@ async def factory_reset(self, ctx): await ctx.send_modal(modal) - # TODO it's just temporary debug function - async def cog_command_error(self, ctx: commands.Context, error: commands.CommandError): - """Error handler for the cog.""" - await ctx.send(f"{error}") - @commands.Cog.listener(name="on_guild_join") async def load_defaults(self, guild): """Load default config file when bot joins guild.""" From 5ef2f8fd1bd25c5e4fa87558f00e2fd22cba92ca Mon Sep 17 00:00:00 2001 From: Caraffa-git Date: Thu, 27 Jun 2024 19:39:02 +0200 Subject: [PATCH 17/18] copyrights --- thatkitebot/cogs/info.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/thatkitebot/cogs/info.py b/thatkitebot/cogs/info.py index 94ad0ea..390d8ba 100644 --- a/thatkitebot/cogs/info.py +++ b/thatkitebot/cogs/info.py @@ -1,3 +1,5 @@ +# Copyright (c) 2019-2024 ThatRedKite and contributors + import os import re import json From b9b9d683ee3ab02c3dc8a68c609abec9a7be8db4 Mon Sep 17 00:00:00 2001 From: Caraffa-git Date: Thu, 27 Jun 2024 19:40:25 +0200 Subject: [PATCH 18/18] fix warnings --- thatkitebot/cogs/info.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/thatkitebot/cogs/info.py b/thatkitebot/cogs/info.py index 390d8ba..11a5005 100644 --- a/thatkitebot/cogs/info.py +++ b/thatkitebot/cogs/info.py @@ -104,9 +104,9 @@ async def info(self, ctx: discord.ApplicationContext, @info_settings.command(name="add-section") async def new_section(self, ctx: discord.ApplicationContext, - name: Option(str, "Choose a name!", required=True, max_lenght=256), - emoji: Option(str, "Choose an emoji! (e.g :lightbulb:)", required=True), - color: Option(str, "Choose a color! (hex e.g. 0x123456)", required=False) = None): + name: Option(str, "Choose a name!", required=True, max_lenght=256), # type: ignore + emoji: Option(str, "Choose an emoji! (e.g :lightbulb:)", required=True), # type: ignore + color: Option(str, "Choose a color! (hex e.g. 0x123456)", required=False) = None): # type: ignore """ Create new section in /info command """ @@ -151,7 +151,7 @@ async def new_section(self, ctx: discord.ApplicationContext, await ctx.respond(f"Section {name} {emoji} has been created, now you can add a new field or edit existing one.") @info_settings.command(name="remove-section") - async def remove_section(self, ctx, name: Option(str, "Pick a section!", required=True, autocomplete=Config.get_sections)): + async def remove_section(self, ctx, name: Option(str, "Pick a section!", required=True, autocomplete=Config.get_sections)): # type: ignore """Remove section in /info command""" config_file = await self.config.get(ctx.guild) @@ -170,8 +170,8 @@ async def remove_section(self, ctx, name: Option(str, "Pick a section!", require @info_settings.command(name="edit-field") async def edit_field(self, ctx: discord.ApplicationContext, - section: Option(str, "Pick a section!", required=True, autocomplete=Config.get_sections), - option: Option(str, "Choose option.", required=True, choices=["edit", "remove", "add"])): + section: Option(str, "Pick a section!", required=True, autocomplete=Config.get_sections), # type: ignore + option: Option(str, "Choose option.", required=True, choices=["edit", "remove", "add"])): # type: ignore """Add, edit or remove a field in section""" config_file = await self.config.get(ctx.guild) @@ -221,7 +221,7 @@ async def edit_field(self, ctx: discord.ApplicationContext, @info_settings.command(name="edit-section") async def edit_section(self, ctx: discord.ApplicationContext, - section: Option(str, "Pick a section!", required=True, autocomplete=Config.get_sections)): + section: Option(str, "Pick a section!", required=True, autocomplete=Config.get_sections)): # type: ignore """Change name, emoji, footer or color of a given section""" config_file = await self.config.get(ctx.guild)