From 5106e2f3ae5ad5745e4701c0522dea1b63909ac9 Mon Sep 17 00:00:00 2001 From: Dhivakaran V R Date: Fri, 11 Oct 2024 13:18:22 +0530 Subject: [PATCH 1/4] adding basic logging, cog loading commands --- cogs/admin.py | 134 +++++++++++++++++++++++++++++++++++++---- cogs/general.py | 6 +- cogs/inspection.py | 2 +- core/bot.py | 21 +++++-- core/igknite_ascii.txt | 48 +++++++++++++++ 5 files changed, 194 insertions(+), 17 deletions(-) create mode 100644 core/igknite_ascii.txt diff --git a/cogs/admin.py b/cogs/admin.py index e067a85..7016594 100644 --- a/cogs/admin.py +++ b/cogs/admin.py @@ -3,20 +3,48 @@ # Imports. - +import disnake from disnake.ext import commands from disnake.ext.commands import errors +import logging import core +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) class Admin(commands.Cog): - @commands.command(name='reloadext') + + def __init__(self, bot: core.IgKnite) -> None: + logger.info("[+]Loading Admin cog... init") + self.bot = bot + + @commands.slash_command( + name='pingi', description='Shows my current response time.' + ) + async def _ping(self, inter: disnake.CommandInter) -> None: + + await inter.send("no this ") + + + @commands.slash_command( + name='reloadext', + description='Reload an cog extension.', + dm_permission=False + ) async def reload_extension( - self, ctx: commands.Context[core.IgKnite], name: str + self, inter: disnake.CommandInter, name: str ) -> None: + """ Use this command to reload an cog extension. + Args: + name (str): The name of the extension to reload. + Exampls use: + /reloadext admin + /reloadext general + """ try: - ctx.bot.reload_extension(name) + inter.bot.reload_extension(name) + logger.info(f"Reloaded {name} extension.") except Exception as e: if isinstance(e, errors.ExtensionNotLoaded): msg = f'`{name}` extension is not loaded.' @@ -31,31 +59,115 @@ async def reload_extension( else: msg = f'Something went wrong while loading `{name}` extension.' + logger.error(f"Loading extension failed. Reason: {msg}") + embed = core.TypicalEmbed(description=msg, is_error=True) - await ctx.send(embed=embed) + await inter.send(embed=embed) else: embed = core.TypicalEmbed( description=f'Successfully reloaded `{name}` extension.' ) - await ctx.send(embed=embed) + await inter.send(embed=embed) @reload_extension.error async def reload_ext( - self, ctx: commands.Context[core.IgKnite], error: errors.CommandError + self, inter: disnake.CommandInter, error: errors.CommandError ) -> None: if isinstance(error, errors.MissingRequiredArgument): embed = core.TypicalEmbed( description='Please provide an extension name.', is_error=True ) - await ctx.send(embed=embed) + await inter.send(embed=embed) # a check failure would be raised when someone who is not # an owner uses the command but we dont wanna catch it, # since we dont wanna send any messages if someone who # isn't an owner uses the command. + + @commands.slash_command(name='unloadext') + @commands.is_owner() + async def unload_extension( + self, inter: disnake.CommandInter, name: str + ) -> None: + """ Use this command to unload an cog extension. + Args: + name (str): The name of the extension to unload. + Exampls use: + /unloadext admin + /unloadext general + """ + try: + inter.bot.unload_extension(name) + logger.info(f"Unloaded {name} extension.") + except Exception as e: + if isinstance(e, errors.ExtensionNotLoaded): + msg = f'`{name}` extension is not loaded.' + elif isinstance(e, errors.ExtensionNotFound): + msg = f'No extension with name `{name}` exists.' + # getting here shouldnt happen, just making sure msg + # doesnt have an Unbound type. + else: + msg = f'Something went wrong while unloading `{name}` extension.' + + logger.error(f"Unloading extension failed. Reason: {msg}") + + embed = core.TypicalEmbed(description=msg, is_error=True) + await inter.send(embed=embed) + else: + embed = core.TypicalEmbed( + description=f'Successfully unloaded `{name}` extension.' + ) + await inter.send(embed=embed) + + @commands.slash_command(name='loadext') + @commands.is_owner() + async def load_extension( + self, inter: disnake.CommandInter, name: str + ) -> None: + """ Use this command to load an cog extension. + Args: + name (str): The name of the extension to load. + Exampls use: + /loadext admin + /loadext general + """ + try: + inter.bot.load_extension(name) + logger.info(f"Loaded {name} extension.") + except Exception as e: + if isinstance(e, errors.ExtensionAlreadyLoaded): + msg = f'`{name}` extension is already loaded.' + elif isinstance(e, errors.ExtensionNotFound): + msg = f'No extension with name `{name}` exists.' + elif isinstance(e, errors.NoEntryPointError): + msg = f'Setup function is not defined in `{name}` file.' + # getting here shouldnt happen, just making sure msg + # doesnt have an Unbound type. + else: + msg = f'Something went wrong while loading `{name}` extension.' + + logger.error(f"Loading extension failed. Reason: {msg}") + + embed = core.TypicalEmbed(description=msg, is_error=True) + await inter.send(embed=embed) + else: + embed = core.TypicalEmbed( + description=f'Successfully loaded `{name}` extension.' + ) + await inter.send(embed=embed) + + @commands.slash_command(name='shutdown') + @commands.is_owner() + async def shutdown(self, inter: disnake.CommandInter) -> None: + """ Use this command to shutdown the bot. """ + logger.info('[X]Shutting down...') + await inter.send('Shutting down...') + await inter.bot.close() - async def cog_check(self, ctx: commands.Context[core.IgKnite]) -> bool: - return await ctx.bot.is_owner(ctx.author) + async def cog_check(self, inter: disnake.CommandInter) -> bool: + logger.info(f"[-----]Checking if {inter.author} is an owner.") + return await inter.bot.is_owner(inter.author) def setup(bot: core.IgKnite) -> None: - bot.add_cog(Admin()) + logger.info("[+]Loading Admin cog...") + bot.add_cog(Admin(bot)) diff --git a/cogs/general.py b/cogs/general.py index f29af44..88b4402 100644 --- a/cogs/general.py +++ b/cogs/general.py @@ -4,6 +4,7 @@ # Imports. import time from datetime import datetime +import logging import disnake from disnake.ext import commands @@ -11,6 +12,9 @@ import core +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) + # Common backend for ping-labelled commands. # Do not use it within other commands unless really necessary. @@ -140,7 +144,7 @@ async def _ping(self, inter: disnake.CommandInter) -> None: async def help(self, inter: disnake.CommandInter): embed = core.TypicalEmbed( inter=inter, - title="Hey there! I'm IgKnite.", + title="Hey there! I'm IgKnite help.", description="I'm a bot with no text commands (you heard that right) " + "and I'm here to help you manage and moderate your Discord server alongside " + 'having a midnight music party with your friends in a random voice channel. ' diff --git a/cogs/inspection.py b/cogs/inspection.py index 653d73e..d999353 100644 --- a/cogs/inspection.py +++ b/cogs/inspection.py @@ -101,7 +101,7 @@ async def _guildinfo(self, inter: disnake.CommandInter) -> None: name='Birth', value=datetime.strptime( str(inter.guild.created_at), '%Y-%m-%d %H:%M:%S.%f%z' - ).strftime('%b %d, %Y'), + ).strftime('%b %d, %fY'), ) .add_field(name='Owner', value=inter.guild.owner.mention) .add_field(name='Members', value=inter.guild.member_count) diff --git a/core/bot.py b/core/bot.py index 6f13e63..57d9ed8 100644 --- a/core/bot.py +++ b/core/bot.py @@ -8,9 +8,14 @@ import disnake from disnake.ext import commands +import logging + from cogs import EXTENTIONS from core.chain import keychain +#setup logging +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') +logger = logging.getLogger("core") # Set up a custom class for core functionality. class IgKnite(commands.AutoShardedBot): @@ -30,6 +35,7 @@ def __init__( *args, **kwargs, ) + logger.info("[+]=======Starting IgKnite=======") to_load = EXTENTIONS if ignored_extensions is not None: @@ -37,9 +43,16 @@ def __init__( # but not worth it since these are gonna be passed by a # developer to_load -= ignored_extensions + + logger.info(f"Selected cogs to load: {", ".join(to_load)}") for extension in to_load: - self.load_extension(extension) + try: + self.load_extension(extension) + logger.info(f"[+]Loaded {extension} successfully") + except Exception as e: + logger.error(f"[-]Failed to load extension {extension}") + logger.error(e) async def _update_presence(self) -> None: """ @@ -55,11 +68,11 @@ async def _update_presence(self) -> None: ) async def on_connect(self) -> None: - print(f'\nConnected to Discord as {self.user}.') + logger.info(f'[+]Connected to Discord as {self.user}!') async def on_ready(self) -> None: - print( - f'Inside {len(self.guilds)} server(s) with {self.shard_count} shard(s) active.' + logger.info( + f'[+]Inside {len(self.guilds)} server(s) with {self.shard_count} shard(s) active.' ) await self._update_presence() diff --git a/core/igknite_ascii.txt b/core/igknite_ascii.txt new file mode 100644 index 0000000..9a5061c --- /dev/null +++ b/core/igknite_ascii.txt @@ -0,0 +1,48 @@ + .. .. + .... .... + ....... ....... + ........ ........ + ......... ......... + .......... .......... + ............ ............ + ............. ............. + ............... ............... + ................ ................ + ................. ................. + .................. .................. + ................... ................... + ..................... ..................... + ...................... ...................... + ........................ ........................ + ......................... ......................... + ........................... ........................... + ............................. ............................. +................--:............. .............:--................ +.:............:@@@@@%#*+-:........ ........:-+*#%@@@@@:............:. +.::...........=@@@@@@@@@@@@%#*+-:.... ....:-+*#%@@@@@@@@@@@@-...........::. +::::..........=@@@@@@@@@@@@@@@@@@%:....... .......:%@@@@@@@@@@@@@@@@@@-..........:::: +:::::.........=@@@@@@@@@@@@@@@@@@%.............. .............:%@@@@@@@@@@@@@@@@@@-.........::::: +.:::::........-@@@@@@@@@@@@@@@@*-..................................-*@@@@@@@@@@@@@@@@-........:::::. +.:::::::.......+############*-........................................-*############=.......:::::::. +.::::::::..................................................................................::::::::. + ::::::::::..............................................................................:::::::::: + .:::::::::::..........................................................................:::::::::::. + :::::::::::::......................................................................::::::::::::: + .::::::::::::::..................................................................::::::::::::::. + .::::::::::::::::............................................................::::::::::::::::. + :::::::::::::::::::......................................................::::::::::::::::::: + ::::::::::::::::::::::..............................................:::::::::::::::::::::: + :::::::::::::::::::::::::......................................::::::::::::::::::::::::: + :::::::::::::::::::::::::::::::........................::::::::::::::::::::::::::::::: + .::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::. + .::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::. + .::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::. + .::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::. + .::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::. + ..::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::.. + .::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::. + ..::::::::::::::::::::::::::::::::::::::::::::::::::::::.. + ..::::::::::::::::::::::::::::::::::::::::::::::::.. + ..::::::::::::::::::::::::::::::::::::::::.. + ...::::::::::::::::::::::::::::... + .......::::::....... From edb8b3f5d122647577ec5ff83e98bd9261f7389d Mon Sep 17 00:00:00 2001 From: Dhivakaran V R Date: Fri, 11 Oct 2024 20:38:36 +0530 Subject: [PATCH 2/4] added shutdown button. commit before debugging interaction timeout error --- cogs/admin.py | 149 ++++++++++++++++++++++++++------------- cogs/exceptionhandler.py | 24 +++++++ cogs/general.py | 2 +- core/ui.py | 31 ++++++++ 4 files changed, 156 insertions(+), 50 deletions(-) diff --git a/cogs/admin.py b/cogs/admin.py index 7016594..e467d45 100644 --- a/cogs/admin.py +++ b/cogs/admin.py @@ -7,40 +7,75 @@ from disnake.ext import commands from disnake.ext.commands import errors import logging +import time import core logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) +class ShutdownCommandView(disnake.ui.View): + def __init__(self, inter: disnake.CommandInter, *, timeout: float = 30) -> None: + super().__init__(timeout=timeout) + self.inter = inter + + @disnake.ui.button(label='Shutdown', style=disnake.ButtonStyle.red) + async def shutdown(self, button: disnake.ui.Button, inter: disnake.Interaction): + + await inter.response.send_message('You clicket the button...', ephemeral=True) + for child in self.children: + child.disabled = True + await inter.edit_original_message(view=self) + + + # @disnake.ui.button(label='Cancel', style=disnake.ButtonStyle.gray) + # async def cancel(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction): + # await interaction.response.send_message('Shutdown cancelled.', ephemeral=True) + # self.stop() + class Admin(commands.Cog): def __init__(self, bot: core.IgKnite) -> None: - logger.info("[+]Loading Admin cog... init") + self.bot = bot + @commands.slash_command( - name='pingi', description='Shows my current response time.' + name='cog', + description='Manage cog extensions.', ) - async def _ping(self, inter: disnake.CommandInter) -> None: - - await inter.send("no this ") + @commands.cooldown(1, 5, commands.BucketType.user) + @commands.is_owner() + async def _cog(self, inter: disnake.CommandInter) -> None: + """Manage cog extensions. + """ + pass + @_cog.sub_command( + name="list", + description='List all cog extensions.', + ) + async def list_extensions(self, inter: disnake.CommandInter) -> None: + """List all cog extensions. + """ + extensions = self.bot.extensions + embed = core.TypicalEmbed( + title='List of all cog extensions.', + description='\n'.join(extensions.keys()), + ) + await inter.send(embed=embed) - @commands.slash_command( - name='reloadext', + @_cog.sub_command( + name='reload', description='Reload an cog extension.', - dm_permission=False + ) async def reload_extension( - self, inter: disnake.CommandInter, name: str + self, + inter: disnake.CommandInter, + name: str= commands.Param(description='The name of the extension to reload.') ) -> None: - """ Use this command to reload an cog extension. - Args: - name (str): The name of the extension to reload. - Exampls use: - /reloadext admin - /reloadext general + """ Reload an cog extension. """ try: inter.bot.reload_extension(name) @@ -69,31 +104,15 @@ async def reload_extension( ) await inter.send(embed=embed) - @reload_extension.error - async def reload_ext( - self, inter: disnake.CommandInter, error: errors.CommandError - ) -> None: - if isinstance(error, errors.MissingRequiredArgument): - embed = core.TypicalEmbed( - description='Please provide an extension name.', is_error=True - ) - await inter.send(embed=embed) - # a check failure would be raised when someone who is not - # an owner uses the command but we dont wanna catch it, - # since we dont wanna send any messages if someone who - # isn't an owner uses the command. - @commands.slash_command(name='unloadext') - @commands.is_owner() + @_cog.sub_command(name='unload', description='Unload an cog extension.') + async def unload_extension( - self, inter: disnake.CommandInter, name: str + self, inter: disnake.CommandInter, name: str= commands.Param(description='The name of the cog extension to unload.') ) -> None: - """ Use this command to unload an cog extension. + """unload an cog extension. Args: name (str): The name of the extension to unload. - Exampls use: - /unloadext admin - /unloadext general """ try: inter.bot.unload_extension(name) @@ -118,17 +137,17 @@ async def unload_extension( ) await inter.send(embed=embed) - @commands.slash_command(name='loadext') - @commands.is_owner() + @_cog.sub_command( + name='load', + description='Load an cog extension.', + ) async def load_extension( - self, inter: disnake.CommandInter, name: str + self, inter: disnake.CommandInter, name: str= commands.Param(description='The name of the cog extension to load.') ) -> None: - """ Use this command to load an cog extension. + """ Load an cog extension. Args: name (str): The name of the extension to load. - Exampls use: - /loadext admin - /loadext general + """ try: inter.bot.load_extension(name) @@ -145,7 +164,7 @@ async def load_extension( else: msg = f'Something went wrong while loading `{name}` extension.' - logger.error(f"Loading extension failed. Reason: {msg}") + logger.error(f"Loading extension failed. Reason: {msg} Error: {e}") embed = core.TypicalEmbed(description=msg, is_error=True) await inter.send(embed=embed) @@ -155,19 +174,51 @@ async def load_extension( ) await inter.send(embed=embed) - @commands.slash_command(name='shutdown') + + # def get_view(self, inter: disnake.CommandInter) -> core.SmallView: + # view = core.SmallView(inter,timeout=15).add_button( + # label="Shutdown", + # style=disnake.ButtonStyle.red, + # ) + # return view + + # async def disable_button(self,view:core.SmallView,inter: disnake.CommandInter) -> None: + # """disables the button once the user has clicked it""" + # logger.info("I am inside disable_button") + # await view.disable_button(inter) + + + + @commands.slash_command( + name='shutdown', + description='Shutdown the bot.', + # cooldown=disnake.Cooldown(1, 5, disnake.BucketType.user), + ) @commands.is_owner() + @commands.cooldown(1, 30, commands.BucketType.user) async def shutdown(self, inter: disnake.CommandInter) -> None: - """ Use this command to shutdown the bot. """ + """Shutdown the bot. """ + + embed = core.TypicalEmbed( + description='Are you sure you want to shut down the bot?', + ) + + await inter.send(embed=embed,view=ShutdownCommandView(inter)) + + try: + # await inter.bot.wait_for('button_click', check=lambda i: i.author == inter.author, timeout=15) + pass + except TimeoutError: + return await inter.send('You took too long to respond.') + # await self.disable_button(view,inter) logger.info('[X]Shutting down...') + time.sleep(10) await inter.send('Shutting down...') + #sleep for 10 seconds + await inter.bot.close() - async def cog_check(self, inter: disnake.CommandInter) -> bool: - logger.info(f"[-----]Checking if {inter.author} is an owner.") - return await inter.bot.is_owner(inter.author) def setup(bot: core.IgKnite) -> None: - logger.info("[+]Loading Admin cog...") bot.add_cog(Admin(bot)) diff --git a/cogs/exceptionhandler.py b/cogs/exceptionhandler.py index 65a24cf..5f4f811 100644 --- a/cogs/exceptionhandler.py +++ b/cogs/exceptionhandler.py @@ -3,6 +3,8 @@ # Imports. from typing import Any +import logging +import traceback import disnake from disnake import errors @@ -10,6 +12,8 @@ import core +logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) # The actual cog. class ExceptionHandler(commands.Cog): @@ -47,6 +51,10 @@ async def process_error( ): embed.title = "Oops! You're missing a role." + #cooldown + elif isinstance(error, commands.errors.CommandOnCooldown): + embed.title = "Hold your horses!" + # Anything else... else: embed.title = 'Oops! An alien error occured.' @@ -58,20 +66,36 @@ async def process_error( async def on_slash_command_error( self, inter: disnake.CommandInter, error: Any ) -> None: + traceback.print_exc() + logger.error(f"[!]Error in command \"{inter.application_command.qualified_name}\". Error: \"{error}\". User: \"{inter.author}\". Guild: \"{inter.guild}\". Channel: \"{inter.channel if inter.guild else None}\". Cog: \"{inter.application_command.cog_name}\"",exc_info=True) + await self.process_error(inter, error) @commands.Cog.listener() async def on_user_command_error( self, inter: disnake.CommandInter, error: Any ) -> None: + logger.error(f"[!]Error in command \"{inter.application_command.qualified_name}\". Error: \"{error}\". User: \"{inter.author}\". Guild: \"{inter.guild}\". Channel: \"{inter.channel}\". Cog: \"{inter.application_command.cog_name}\"",exc_info=True) + await self.process_error(inter, error) @commands.Cog.listener() async def on_message_command_error( self, inter: disnake.CommandInter, error: Any ) -> None: + logger.error(f"[!]Error in command \"{inter.application_command.qualified_name}\". Error: \"{error}\". User: \"{inter.author}\". Guild: \"{inter.guild}\". Channel: \"{inter.channel}\". Cog: \"{inter.application_command.cog_name}\"",exc_info=True) await self.process_error(inter, error) + @commands.slash_command( + name='logerrors', + ) + @commands.is_owner() + async def error_group(self, inter: disnake.CommandInter) -> None: + """ + A group of commands to test the error handler. + """ + + # The setup() function for the cog. def setup(bot: core.IgKnite) -> None: diff --git a/cogs/general.py b/cogs/general.py index 88b4402..1008f96 100644 --- a/cogs/general.py +++ b/cogs/general.py @@ -117,7 +117,7 @@ async def _avatar_backend( async def _avatar( self, inter: disnake.CommandInter, - member: disnake.Member = Param( + ember: disnake.Member = Param( description='Mention the server member. Defaults to you.', default=lambda inter: inter.author, ), diff --git a/core/ui.py b/core/ui.py index 7cfafd3..8a7f605 100644 --- a/core/ui.py +++ b/core/ui.py @@ -4,10 +4,16 @@ # Imports. import random from typing import Self +import logging import disnake +from disnake.ui.item import Item +# Set up logging. +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) + # Overwrite disnake.Embed class to form custom embeds. class TypicalEmbed(disnake.Embed): """ @@ -76,6 +82,23 @@ def add_button( ) ) return self + + # async def disable_button(self,inter: disnake.CommandInter) -> None: + # """disables the button in the view + # """ + + # #check if interacted user is the author of the message + # logger.info(f"Interacted user: {inter.author}") + + # if self.inter.author == inter.author: + # for child in self.children: + # if child.style != disnake.ButtonStyle.link: + # child.disabled = True + + # await inter.edit_original_message(view=self) + # logger.info("Button disabled") + + async def on_timeout(self) -> None: if self.inter: @@ -84,3 +107,11 @@ async def on_timeout(self) -> None: child.disabled = True await self.inter.edit_original_message(view=self) + + + # async def on_error(self, error: Exception, item: Item, interaction: disnake.MessageInteraction) -> None: + # logger.error(f"Error in button click: {error}") + # return await super().on_error(error, item, interaction) + + + From d9b3a99e0f07275d634f5638fdbbdac02d3391ab Mon Sep 17 00:00:00 2001 From: Dhivakaran V R Date: Sat, 12 Oct 2024 18:05:07 +0530 Subject: [PATCH 3/4] Cog handler & logging errors --- cogs/__init__.py | 36 +++++- cogs/admin.py | 237 +++++++++++++++++++++++++++++-------- cogs/cogs.json | 37 ++++++ cogs/exceptionhandler.json | 6 + cogs/exceptionhandler.py | 94 +++++++++++++-- cogs/general.py | 2 +- core/bot.py | 21 ++-- main.py | 3 +- 8 files changed, 359 insertions(+), 77 deletions(-) create mode 100644 cogs/cogs.json create mode 100644 cogs/exceptionhandler.json diff --git a/cogs/__init__.py b/cogs/__init__.py index 6ee3280..8186ce1 100644 --- a/cogs/__init__.py +++ b/cogs/__init__.py @@ -1,5 +1,33 @@ -from pkgutil import walk_packages +import json +import logging -EXTENTIONS = set( - module.name for module in walk_packages(__path__, f'{__package__}.') -) +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) +cogs=[] +loaded_cogs = [] +all_cogs=[] +def load_cogs(): + global cogs, loaded_cogs, all_cogs + try: + cogs = json.load(open("./cogs/cogs.json")) + except FileNotFoundError: + logger.error("cogs.json not found... please see if the file exists in the cogs directory.") + cogs = [] + except json.JSONDecodeError: + logger.error("cogs.json is not a valid json file...") + cogs = [] + + for cog in cogs: + if cog['is_enabled']: + loaded_cogs.append(cog['name']) + all_cogs.append(cog['name']) +load_cogs() +def update_cogs(loaded): + global cogs + for cog in cogs: + if cog['name'] in loaded: + cog['is_enabled'] = True + else: + cog['is_enabled'] = False + with open("./cogs/cogs.json", "w") as f: + json.dump(cogs, f, indent=4) \ No newline at end of file diff --git a/cogs/admin.py b/cogs/admin.py index e467d45..0b0718e 100644 --- a/cogs/admin.py +++ b/cogs/admin.py @@ -8,37 +8,114 @@ from disnake.ext.commands import errors import logging import time +import json import core logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) +Cog_config_path = "./cogs/cogs.json" + +#todo: add registration of cogs. modify the json when cogs is loaded, unloaded, registered, unregistered. class ShutdownCommandView(disnake.ui.View): - def __init__(self, inter: disnake.CommandInter, *, timeout: float = 30) -> None: - super().__init__(timeout=timeout) + """A view for the shutdown command. + Inherits: + disnake.ui.View + Args: + inter (disnake.CommandInter): The interaction object. + timeout (float): View interaction will be disabled after this time. + """ + + def __init__(self, inter: disnake.CommandInter, *, timeout: float = 15) -> None: + super().__init__(timeout = timeout) self.inter = inter + self.clicked=False @disnake.ui.button(label='Shutdown', style=disnake.ButtonStyle.red) - async def shutdown(self, button: disnake.ui.Button, inter: disnake.Interaction): + async def shutdown(self, button: disnake.ui.Button, inter: disnake.Interaction)-> None: + """disables the button and shuts down the bot.""" + logger.info('Shutdown button clicked') + self.clicked=True + await inter.response.defer() - await inter.response.send_message('You clicket the button...', ephemeral=True) for child in self.children: child.disabled = True + logger.info('[X]Shutting down...') + + await inter.send('Shutting down...', ephemeral=True) await inter.edit_original_message(view=self) + #sleep for 3 seconds before shutting down + time.sleep(3) + + await inter.bot.close() + - # @disnake.ui.button(label='Cancel', style=disnake.ButtonStyle.gray) - # async def cancel(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction): - # await interaction.response.send_message('Shutdown cancelled.', ephemeral=True) - # self.stop() + async def on_timeout(self) -> None: + """Disables the button in the view after timeout.""" + if not self.clicked: + for child in self.children: + child.disabled = True + # try: + await self.inter.send('You took too long to respond. Please try again', ephemeral=True) + await self.inter.edit_original_message(view=self) + class Admin(commands.Cog): + """Contains commands for cog handler and shutdown + Inherits: + commands.Cog + Args: + bot (core.IgKnite): An instance of `core.IgKnite`. + """ def __init__(self, bot: core.IgKnite) -> None: - + self.bot = bot + #wait until bot is ready + + + + self.loaded_cogs=[] #cogs which are loaded + self.all_cogs=[] #cogs which are present in the cogs.json file + self.cog_object=None #cogs.json object + + #read cog configuration from file + + @commands.Cog.listener() + async def on_ready(self): + """Load the cogs when the bot is ready. + This is seperated from __init__ to avoid conflict with bot.py's cog loading, where the cog enabled status changes due to errors in loading the cog.""" + try: + self.cog_object=json.load(open(Cog_config_path)) + except FileNotFoundError: + logger.error("cogs.json not found... please see if the file exists in the cogs directory.") + self.cog_object=[] + except json.JSONDecodeError: + logger.error("cogs.json is not a valid json file...") + self.cog_object=[] + for cog in self.cog_object: + if cog['is_enabled']: + self.loaded_cogs.append(cog['name']) + self.all_cogs.append(cog['name']) + def update_cog_object(self, name:str, is_enabled:bool): + """When a cog status is changed, this function is used to update the cogs.json file. + Args: + name (str): The name of the cog to update. + is_enabled (bool): The status of the cog.""" + if is_enabled: + if name not in self.loaded_cogs: + self.loaded_cogs.append(name) + else: + if name in self.loaded_cogs: + self.loaded_cogs.remove(name) + for cog in self.cog_object: + if cog['name']==name: + cog['is_enabled']=is_enabled + with open(Cog_config_path, 'w') as f: + json.dump(self.cog_object,f,indent=4) @commands.slash_command( name='cog', @@ -50,6 +127,8 @@ async def _cog(self, inter: disnake.CommandInter) -> None: """Manage cog extensions. """ pass + + @_cog.sub_command( name="list", description='List all cog extensions.', @@ -57,11 +136,16 @@ async def _cog(self, inter: disnake.CommandInter) -> None: async def list_extensions(self, inter: disnake.CommandInter) -> None: """List all cog extensions. """ - - extensions = self.bot.extensions + #registerd cogs embed = core.TypicalEmbed( - title='List of all cog extensions.', - description='\n'.join(extensions.keys()), + title='List of registered cogs:', + description='\n'.join(self.all_cogs), + + ) + #loaded cogs + embed.add_field( + name='**Loaded Cogs:**', + value='\n'.join(self.loaded_cogs) if self.loaded_cogs else 'No cogs loaded.' ) await inter.send(embed=embed) @@ -78,7 +162,7 @@ async def reload_extension( """ Reload an cog extension. """ try: - inter.bot.reload_extension(name) + inter.bot.reload_extension(f"cogs.{name}") logger.info(f"Reloaded {name} extension.") except Exception as e: if isinstance(e, errors.ExtensionNotLoaded): @@ -115,13 +199,19 @@ async def unload_extension( name (str): The name of the extension to unload. """ try: - inter.bot.unload_extension(name) - logger.info(f"Unloaded {name} extension.") + if name not in self.all_cogs: + raise errors.ExtensionNotFound(name) + if name == "admin": + return await inter.send("You cannot unload the admin cog.") + inter.bot.unload_extension(f"cogs.{name}") + self.update_cog_object(name,False) + logger.info(f"Unloaded {name} extension. cog object: {self.cog_object}") + except Exception as e: if isinstance(e, errors.ExtensionNotLoaded): - msg = f'`{name}` extension is not loaded.' + msg = f'`{name}` extension is not loaded.\n Use `/cog load {name}` to load it.' elif isinstance(e, errors.ExtensionNotFound): - msg = f'No extension with name `{name}` exists.' + msg = f'No extension with name `{name}` is registered.\n Use `/cog register {name}` to register it.' # getting here shouldnt happen, just making sure msg # doesnt have an Unbound type. else: @@ -147,16 +237,19 @@ async def load_extension( """ Load an cog extension. Args: name (str): The name of the extension to load. - """ try: - inter.bot.load_extension(name) + if name not in self.all_cogs: + raise errors.ExtensionNotFound(name) + inter.bot.load_extension(f"cogs.{name}") + #update the cog object + self.update_cog_object(name,True) logger.info(f"Loaded {name} extension.") except Exception as e: if isinstance(e, errors.ExtensionAlreadyLoaded): msg = f'`{name}` extension is already loaded.' elif isinstance(e, errors.ExtensionNotFound): - msg = f'No extension with name `{name}` exists.' + msg = f'No extension with name `{name}` exists.\n Use `/cog register {name}` to register it.' elif isinstance(e, errors.NoEntryPointError): msg = f'Setup function is not defined in `{name}` file.' # getting here shouldnt happen, just making sure msg @@ -174,20 +267,73 @@ async def load_extension( ) await inter.send(embed=embed) + @_cog.sub_command( + name='register', + description='Register an cog extension.', + ) + async def register_extension( + self, inter: disnake.CommandInter, name: str= commands.Param(description='The name of the extension to register.'), description: str= commands.Param(description='Description of the extension.') + ) -> None: + """ Register an cog extension. + Args: + name (str): The name of the extension to register. + """ + if name in self.all_cogs: + embed = core.TypicalEmbed( + description=f'`{name}` extension is already registered.', + is_error=True + ) + return await inter.send(embed=embed) + #check if cog file is present + try: + open(f"./cogs/{name}.py") + except FileNotFoundError: + embed = core.TypicalEmbed( + description=f'`{name}` extension file is not found. Make sure it is present in the cogs directory.', + is_error=True + ) + return await inter.send(embed=embed) - # def get_view(self, inter: disnake.CommandInter) -> core.SmallView: - # view = core.SmallView(inter,timeout=15).add_button( - # label="Shutdown", - # style=disnake.ButtonStyle.red, - # ) - # return view - - # async def disable_button(self,view:core.SmallView,inter: disnake.CommandInter) -> None: - # """disables the button once the user has clicked it""" - # logger.info("I am inside disable_button") - # await view.disable_button(inter) + self.cog_object.append({'name':name, 'description':description,'is_enabled':False}) + self.all_cogs.append(name) + with open(Cog_config_path, 'w') as f: + json.dump(self.cog_object,f,indent=4) + embed = core.TypicalEmbed( + description=f'Successfully registered `{name}` extension.' + ) + await inter.send(embed=embed) - + @_cog.sub_command( + name='unregister', + description='Unregister an cog extension.', + ) + async def unregister_extension( + self, inter: disnake.CommandInter, name: str= commands.Param(description='The name of the extension to unregister.') + ) -> None: + """ Unregister an cog extension. + Args: + name (str): The name of the extension to unregister. + """ + if name not in self.all_cogs: + embed = core.TypicalEmbed( + description=f'`{name}` extension is not registered.', + is_error=True + ) + return await inter.send(embed=embed) + elif name in self.loaded_cogs: + embed = core.TypicalEmbed( + description=f'`{name}` extension is loaded. Unload it first.', + is_error=True + ) + return await inter.send(embed=embed) + self.cog_object=[cog for cog in self.cog_object if cog['name']!=name] + self.all_cogs.remove(name) + with open(Cog_config_path, 'w') as f: + json.dump(self.cog_object,f,indent=4) + embed = core.TypicalEmbed( + description=f'Successfully unregistered `{name}` extension.' + ) + await inter.send(embed=embed) @commands.slash_command( name='shutdown', @@ -197,27 +343,18 @@ async def load_extension( @commands.is_owner() @commands.cooldown(1, 30, commands.BucketType.user) async def shutdown(self, inter: disnake.CommandInter) -> None: - """Shutdown the bot. """ + """Shutdown the bot.""" embed = core.TypicalEmbed( - description='Are you sure you want to shut down the bot?', - ) + inter=inter, + title='Shutdown the bot?', + description='Are you sure you want to shut down the bot?', + disabled_footer=True + ) - await inter.send(embed=embed,view=ShutdownCommandView(inter)) + view=ShutdownCommandView(inter,timeout=15) + await inter.send(embed=embed,view=view) - try: - # await inter.bot.wait_for('button_click', check=lambda i: i.author == inter.author, timeout=15) - pass - except TimeoutError: - return await inter.send('You took too long to respond.') - # await self.disable_button(view,inter) - logger.info('[X]Shutting down...') - time.sleep(10) - await inter.send('Shutting down...') - #sleep for 10 seconds - - await inter.bot.close() - def setup(bot: core.IgKnite) -> None: diff --git a/cogs/cogs.json b/cogs/cogs.json new file mode 100644 index 0000000..42bc37d --- /dev/null +++ b/cogs/cogs.json @@ -0,0 +1,37 @@ +[ + { + "name": "moderation", + "description": "Moderation commands for the bot.", + "is_enabled": true + }, + { + "name": "admin", + "description": "Cog handler commands for the bot.", + "is_enabled": true + }, + { + "name": "exceptionhandler", + "description": "Exception handler for the bot.", + "is_enabled": true + }, + { + "name": "music", + "description": "Music commands for the bot.", + "is_enabled": false + }, + { + "name": "general", + "description": "General commands for the bot.", + "is_enabled": false + }, + { + "name": "customization", + "description": "Customization commands for the bot.", + "is_enabled": true + }, + { + "name": "inspection", + "description": "Inspection commands for the bot.", + "is_enabled": true + } +] \ No newline at end of file diff --git a/cogs/exceptionhandler.json b/cogs/exceptionhandler.json new file mode 100644 index 0000000..8f75042 --- /dev/null +++ b/cogs/exceptionhandler.json @@ -0,0 +1,6 @@ +{ + "guild_id": 781133714306105394, + "guild_name": "SKCET - AI and DS", + "channel_id": 818839322790854656, + "channel_name": "playlists" +} \ No newline at end of file diff --git a/cogs/exceptionhandler.py b/cogs/exceptionhandler.py index 5f4f811..8312890 100644 --- a/cogs/exceptionhandler.py +++ b/cogs/exceptionhandler.py @@ -4,7 +4,8 @@ # Imports. from typing import Any import logging -import traceback +import os +import json import disnake from disnake import errors @@ -15,10 +16,75 @@ logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) -# The actual cog. +Exception_config_path="./cogs/exceptionhandler.json" + +class LogerrorsCommandView(disnake.ui.View): + """A view for the logerror command. + Inherits: + disnake.ui.View + Args: + inter (disnake.CommandInter): The interaction object. + timeout (float): View interaction will be disabled after this time. + """ + + def __init__(self, inter: disnake.CommandInter, *, timeout: float = 15) -> None: + super().__init__(timeout = timeout) + self.inter = inter + self.clicked = False + + @disnake.ui.button(label='Log Errors here', style=disnake.ButtonStyle.gray) + async def logerrors(self, button: disnake.ui.Button, inter: disnake.Interaction)-> None: + """disables the button in the view and logs the error in the specified channel.""" + self.clicked = True + await inter.response.defer() + + for child in self.children: + child.disabled = True + await self.inter.edit_original_message(view=self) + + self.config = { + "guild_id": inter.guild.id, + "guild_name": inter.guild.name, + "channel_id": inter.channel.id, + "channel_name": inter.channel.name + } + + with open(Exception_config_path, "w") as f: + json.dump(self.config, f, indent=4) + logger.info(f"Error logging enabled to guild. Guild: `{inter.guild.name}`, Channel: `{inter.channel.name}`") + + await inter.send(f"Error logging enabled in this guild. Guild: `{inter.guild.name}`, Channel: `{inter.channel.name}`", ephemeral=True) + + async def on_timeout(self) -> None: + """Disables the button in the view after the timeout.""" + if not self.clicked: + for child in self.children: + child.disabled = True + await self.inter.send("You took too long to respond. Please try again.", ephemeral=True) + await self.inter.edit_original_message(view=self) + + class ExceptionHandler(commands.Cog): + """Contains commands for handling exceptions during command execution. + Inherits: + commands.Cog + Args: + bot (core.IgKnite): An instance of `core.IgKnite`.""" def __init__(self, bot: core.IgKnite) -> None: self.bot = bot + + if not os.path.exists(Exception_config_path): + logger.warning("exceptionhandler.json not found... file will be created when `logerrors` command is used.") + else: + try: + with open(Exception_config_path, "r") as f: + self.config = json.load(f) + logger.info(f"exceptionhandler.json present. Logging errors guild: {self.config.get('guild_name')}, channel: {self.config.get('channel_name')}") + except json.JSONDecodeError: + logger.error("Log errors: exceptionhandler.json is not a valid json file...") + self.config = {} + + def get_view(self, inter: disnake.CommandInter) -> core.SmallView: view = core.SmallView(inter).add_button( @@ -66,8 +132,9 @@ async def process_error( async def on_slash_command_error( self, inter: disnake.CommandInter, error: Any ) -> None: - traceback.print_exc() - logger.error(f"[!]Error in command \"{inter.application_command.qualified_name}\". Error: \"{error}\". User: \"{inter.author}\". Guild: \"{inter.guild}\". Channel: \"{inter.channel if inter.guild else None}\". Cog: \"{inter.application_command.cog_name}\"",exc_info=True) + logger.error(f"[!]Error in command \"{inter.application_command.qualified_name}\". Error: \"{error}\". User: `{inter.author}`. Guild: `{inter.guild}`. Channel: `{inter.channel if inter.guild else None}`. Cog: \"{inter.application_command.cog_name}\"",exc_info=True) + if self.config: + await self.bot.get_channel(self.config['channel_id']).send(f"[!]Error in command \"{inter.application_command.qualified_name}\". \nError: \"{error}\". \nUser: `{inter.author}`. Guild: `{inter.guild}`. Channel: `{inter.channel if inter.guild else None}`. Cog: \"{inter.application_command.cog_name}\"") await self.process_error(inter, error) @@ -76,7 +143,8 @@ async def on_user_command_error( self, inter: disnake.CommandInter, error: Any ) -> None: logger.error(f"[!]Error in command \"{inter.application_command.qualified_name}\". Error: \"{error}\". User: \"{inter.author}\". Guild: \"{inter.guild}\". Channel: \"{inter.channel}\". Cog: \"{inter.application_command.cog_name}\"",exc_info=True) - + if self.config: + await self.bot.get_channel(self.config['channel_id']).send(f"[!]Error in command \"{inter.application_command.qualified_name}\". \nError: \"{error}\". \nUser: `{inter.author}`. Guild: `{inter.guild}`. Channel: `{inter.channel}`. Cog: \"{inter.application_command.cog_name}\"") await self.process_error(inter, error) @commands.Cog.listener() @@ -84,16 +152,26 @@ async def on_message_command_error( self, inter: disnake.CommandInter, error: Any ) -> None: logger.error(f"[!]Error in command \"{inter.application_command.qualified_name}\". Error: \"{error}\". User: \"{inter.author}\". Guild: \"{inter.guild}\". Channel: \"{inter.channel}\". Cog: \"{inter.application_command.cog_name}\"",exc_info=True) + if self.config: + await self.bot.get_channel(self.config['channel_id']).send(f"[!]Error in command \"{inter.application_command.qualified_name}\". \nError: \"{error}\". \nUser: `{inter.author}`. Guild: `{inter.guild}`. Channel: `{inter.channel}`. Cog: \"{inter.application_command.cog_name}\"") await self.process_error(inter, error) @commands.slash_command( name='logerrors', + dm_permission=False, ) @commands.is_owner() - async def error_group(self, inter: disnake.CommandInter) -> None: - """ - A group of commands to test the error handler. + @commands.cooldown(1, 60, commands.BucketType.user) + async def log_errors(self, inter: disnake.CommandInter) -> None: + """Logs errors in the specified channel. """ + msg="" + if self.config: + msg=f"Error logging is already enabled in the channel `{self.config.get('channel_name')}` in the guild `{self.config.get('guild_name')}`." + msg+="Are you sure you want to enable error logging in this channel?" + embed = core.TypicalEmbed(inter=inter, title="Log errors here?", description=msg,disabled_footer=True) + + await inter.send(embed=embed, view=LogerrorsCommandView(inter,timeout=15)) diff --git a/cogs/general.py b/cogs/general.py index 1008f96..88b4402 100644 --- a/cogs/general.py +++ b/cogs/general.py @@ -117,7 +117,7 @@ async def _avatar_backend( async def _avatar( self, inter: disnake.CommandInter, - ember: disnake.Member = Param( + member: disnake.Member = Param( description='Mention the server member. Defaults to you.', default=lambda inter: inter.author, ), diff --git a/core/bot.py b/core/bot.py index 57d9ed8..1f17933 100644 --- a/core/bot.py +++ b/core/bot.py @@ -10,7 +10,7 @@ import logging -from cogs import EXTENTIONS +from cogs import loaded_cogs,update_cogs from core.chain import keychain #setup logging @@ -25,7 +25,7 @@ class IgKnite(commands.AutoShardedBot): """ def __init__( - self, *args, ignored_extensions: Optional[Set[str]] = None, **kwargs + self, *args, **kwargs ) -> None: super().__init__( command_prefix=commands.when_mentioned_or('.'), @@ -37,22 +37,19 @@ def __init__( ) logger.info("[+]=======Starting IgKnite=======") - to_load = EXTENTIONS - if ignored_extensions is not None: - # ignored_extensions need's to be a set, can add a check - # but not worth it since these are gonna be passed by a - # developer - to_load -= ignored_extensions - - logger.info(f"Selected cogs to load: {", ".join(to_load)}") + logger.info(f"Selected cogs to load: {", ".join(loaded_cogs)}") - for extension in to_load: + for extension in loaded_cogs: try: - self.load_extension(extension) + self.load_extension(f"cogs.{extension}") logger.info(f"[+]Loaded {extension} successfully") except Exception as e: + #removing the failed cog from the loaded cogs list + loaded_cogs.remove(extension) logger.error(f"[-]Failed to load extension {extension}") logger.error(e) + #update the failed cogs in the cogs.json file + update_cogs(loaded_cogs) async def _update_presence(self) -> None: """ diff --git a/main.py b/main.py index e6df036..10c8f52 100644 --- a/main.py +++ b/main.py @@ -11,8 +11,7 @@ # Set up an instance of IgKnite. bot = core.IgKnite( - intents=disnake.Intents.all(), - ignored_extensions={'cogs.music'}, # lets say we dont wanna load music.py + intents=disnake.Intents.all() ) From eb1eb3618ae694a23e038a2b5e6bb2c18643c390 Mon Sep 17 00:00:00 2001 From: Dhivakaran V R Date: Sun, 13 Oct 2024 12:12:32 +0530 Subject: [PATCH 4/4] fix typos, repeated code --- cogs/__init__.py | 8 ++-- cogs/admin.py | 90 ++++++++++++++++++++-------------------- cogs/cogs.json | 2 +- cogs/customization.py | 2 +- cogs/exceptionhandler.py | 23 +++++----- cogs/general.py | 2 +- cogs/inspection.py | 2 +- core/ui.py | 20 --------- 8 files changed, 64 insertions(+), 85 deletions(-) diff --git a/cogs/__init__.py b/cogs/__init__.py index 8186ce1..7c42026 100644 --- a/cogs/__init__.py +++ b/cogs/__init__.py @@ -3,19 +3,18 @@ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') logger = logging.getLogger(__name__) -cogs=[] +cogs = [] loaded_cogs = [] -all_cogs=[] +all_cogs = [] def load_cogs(): + """load the cogs from the cogs.json file""" global cogs, loaded_cogs, all_cogs try: cogs = json.load(open("./cogs/cogs.json")) except FileNotFoundError: logger.error("cogs.json not found... please see if the file exists in the cogs directory.") - cogs = [] except json.JSONDecodeError: logger.error("cogs.json is not a valid json file...") - cogs = [] for cog in cogs: if cog['is_enabled']: @@ -23,6 +22,7 @@ def load_cogs(): all_cogs.append(cog['name']) load_cogs() def update_cogs(loaded): + """update the cogs.json file with the loaded cogs""" global cogs for cog in cogs: if cog['name'] in loaded: diff --git a/cogs/admin.py b/cogs/admin.py index 0b0718e..fae7229 100644 --- a/cogs/admin.py +++ b/cogs/admin.py @@ -9,6 +9,7 @@ import logging import time import json +import os import core @@ -79,7 +80,7 @@ def __init__(self, bot: core.IgKnite) -> None: self.loaded_cogs=[] #cogs which are loaded self.all_cogs=[] #cogs which are present in the cogs.json file - self.cog_object=None #cogs.json object + self.cog_object=[] #cogs.json object #read cog configuration from file @@ -91,10 +92,9 @@ async def on_ready(self): self.cog_object=json.load(open(Cog_config_path)) except FileNotFoundError: logger.error("cogs.json not found... please see if the file exists in the cogs directory.") - self.cog_object=[] + except json.JSONDecodeError: logger.error("cogs.json is not a valid json file...") - self.cog_object=[] for cog in self.cog_object: if cog['is_enabled']: self.loaded_cogs.append(cog['name']) @@ -179,14 +179,14 @@ async def reload_extension( msg = f'Something went wrong while loading `{name}` extension.' logger.error(f"Loading extension failed. Reason: {msg}") - - embed = core.TypicalEmbed(description=msg, is_error=True) - await inter.send(embed=embed) + is_error=True else: - embed = core.TypicalEmbed( - description=f'Successfully reloaded `{name}` extension.' - ) - await inter.send(embed=embed) + msg = f'Successfully reloaded `{name}` extension.' + is_error=False + embed = core.TypicalEmbed( + description=f'Successfully reloaded `{name}` extension.',is_error=is_error + ) + await inter.send(embed=embed) @_cog.sub_command(name='unload', description='Unload an cog extension.') @@ -219,13 +219,14 @@ async def unload_extension( logger.error(f"Unloading extension failed. Reason: {msg}") - embed = core.TypicalEmbed(description=msg, is_error=True) - await inter.send(embed=embed) + is_error=True else: - embed = core.TypicalEmbed( - description=f'Successfully unloaded `{name}` extension.' - ) - await inter.send(embed=embed) + msg = f'Successfully unloaded `{name}` extension.' + is_error=False + embed = core.TypicalEmbed( + description=f'Successfully unloaded `{name}` extension.',is_error=is_error + ) + await inter.send(embed=embed) @_cog.sub_command( name='load', @@ -259,13 +260,15 @@ async def load_extension( logger.error(f"Loading extension failed. Reason: {msg} Error: {e}") - embed = core.TypicalEmbed(description=msg, is_error=True) - await inter.send(embed=embed) + is_error=True + else: - embed = core.TypicalEmbed( - description=f'Successfully loaded `{name}` extension.' - ) - await inter.send(embed=embed) + msg = f'Successfully loaded `{name}` extension.' + is_error=False + embed = core.TypicalEmbed( + description=f'Successfully loaded `{name}` extension.',is_error=is_error + ) + await inter.send(embed=embed) @_cog.sub_command( name='register', @@ -283,24 +286,22 @@ async def register_extension( description=f'`{name}` extension is already registered.', is_error=True ) - return await inter.send(embed=embed) + #check if cog file is present - try: - open(f"./cogs/{name}.py") - except FileNotFoundError: + elif not os.path.exists(f"./cogs/{name}.py"): embed = core.TypicalEmbed( description=f'`{name}` extension file is not found. Make sure it is present in the cogs directory.', is_error=True ) - return await inter.send(embed=embed) - - self.cog_object.append({'name':name, 'description':description,'is_enabled':False}) - self.all_cogs.append(name) - with open(Cog_config_path, 'w') as f: - json.dump(self.cog_object,f,indent=4) - embed = core.TypicalEmbed( - description=f'Successfully registered `{name}` extension.' - ) + + else: + self.cog_object.append({'name':name, 'description':description,'is_enabled':False}) + self.all_cogs.append(name) + with open(Cog_config_path, 'w') as f: + json.dump(self.cog_object,f,indent=4) + embed = core.TypicalEmbed( + description=f'Successfully registered `{name}` extension.' + ) await inter.send(embed=embed) @_cog.sub_command( @@ -319,20 +320,21 @@ async def unregister_extension( description=f'`{name}` extension is not registered.', is_error=True ) - return await inter.send(embed=embed) + elif name in self.loaded_cogs: embed = core.TypicalEmbed( description=f'`{name}` extension is loaded. Unload it first.', is_error=True ) - return await inter.send(embed=embed) - self.cog_object=[cog for cog in self.cog_object if cog['name']!=name] - self.all_cogs.remove(name) - with open(Cog_config_path, 'w') as f: - json.dump(self.cog_object,f,indent=4) - embed = core.TypicalEmbed( - description=f'Successfully unregistered `{name}` extension.' - ) + + else: + self.cog_object=[cog for cog in self.cog_object if cog['name']!=name] + self.all_cogs.remove(name) + with open(Cog_config_path, 'w') as f: + json.dump(self.cog_object,f,indent=4) + embed = core.TypicalEmbed( + description=f'Successfully unregistered `{name}` extension.' + ) await inter.send(embed=embed) @commands.slash_command( diff --git a/cogs/cogs.json b/cogs/cogs.json index 42bc37d..4d9567a 100644 --- a/cogs/cogs.json +++ b/cogs/cogs.json @@ -22,7 +22,7 @@ { "name": "general", "description": "General commands for the bot.", - "is_enabled": false + "is_enabled": true }, { "name": "customization", diff --git a/cogs/customization.py b/cogs/customization.py index cc8738f..811912f 100644 --- a/cogs/customization.py +++ b/cogs/customization.py @@ -319,7 +319,7 @@ async def _makestage( max_value=21600, choices=__slowmode_choices__, ), - ): + ) -> None: stage = await inter.guild.create_stage_channel( name=name, category=category, slowmode_delay=slowmode ) diff --git a/cogs/exceptionhandler.py b/cogs/exceptionhandler.py index 8312890..a40c6e8 100644 --- a/cogs/exceptionhandler.py +++ b/cogs/exceptionhandler.py @@ -87,12 +87,12 @@ def __init__(self, bot: core.IgKnite) -> None: def get_view(self, inter: disnake.CommandInter) -> core.SmallView: - view = core.SmallView(inter).add_button( + return core.SmallView(inter).add_button( label="Think it's a bug?", url=core.BotData.repo + '/issues/new?template=bug.yml', style=disnake.ButtonStyle.red, ) - return view + async def process_error( self, inter: disnake.CommandInter, error: Any @@ -102,6 +102,13 @@ async def process_error( accordingly. """ + logger.error(f"[!]Error in command \"{inter.application_command.qualified_name}\". Error: \"{error}\". User: `{inter.author}`. Guild: `{inter.guild}`. Channel: `{inter.channel if inter.guild else None}`. Cog: \"{inter.application_command.cog_name}\"",exc_info=True) + if self.config: + try: + await self.bot.get_channel(self.config['channel_id']).send(f"[!]Error in command \"{inter.application_command.qualified_name}\". \nError: \"{error}\". \nUser: `{inter.author}`. Guild: `{inter.guild}`. Channel: `{inter.channel if inter.guild else None}`. Cog: \"{inter.application_command.cog_name}\"") + except Exception as e: + logger.error(f"Failed to log error in the specified channel. Error: {e}") + error = getattr(error, 'original', error) embed = core.TypicalEmbed(inter=inter, is_error=True) @@ -132,28 +139,18 @@ async def process_error( async def on_slash_command_error( self, inter: disnake.CommandInter, error: Any ) -> None: - logger.error(f"[!]Error in command \"{inter.application_command.qualified_name}\". Error: \"{error}\". User: `{inter.author}`. Guild: `{inter.guild}`. Channel: `{inter.channel if inter.guild else None}`. Cog: \"{inter.application_command.cog_name}\"",exc_info=True) - if self.config: - await self.bot.get_channel(self.config['channel_id']).send(f"[!]Error in command \"{inter.application_command.qualified_name}\". \nError: \"{error}\". \nUser: `{inter.author}`. Guild: `{inter.guild}`. Channel: `{inter.channel if inter.guild else None}`. Cog: \"{inter.application_command.cog_name}\"") - await self.process_error(inter, error) @commands.Cog.listener() async def on_user_command_error( self, inter: disnake.CommandInter, error: Any ) -> None: - logger.error(f"[!]Error in command \"{inter.application_command.qualified_name}\". Error: \"{error}\". User: \"{inter.author}\". Guild: \"{inter.guild}\". Channel: \"{inter.channel}\". Cog: \"{inter.application_command.cog_name}\"",exc_info=True) - if self.config: - await self.bot.get_channel(self.config['channel_id']).send(f"[!]Error in command \"{inter.application_command.qualified_name}\". \nError: \"{error}\". \nUser: `{inter.author}`. Guild: `{inter.guild}`. Channel: `{inter.channel}`. Cog: \"{inter.application_command.cog_name}\"") await self.process_error(inter, error) @commands.Cog.listener() async def on_message_command_error( self, inter: disnake.CommandInter, error: Any ) -> None: - logger.error(f"[!]Error in command \"{inter.application_command.qualified_name}\". Error: \"{error}\". User: \"{inter.author}\". Guild: \"{inter.guild}\". Channel: \"{inter.channel}\". Cog: \"{inter.application_command.cog_name}\"",exc_info=True) - if self.config: - await self.bot.get_channel(self.config['channel_id']).send(f"[!]Error in command \"{inter.application_command.qualified_name}\". \nError: \"{error}\". \nUser: `{inter.author}`. Guild: `{inter.guild}`. Channel: `{inter.channel}`. Cog: \"{inter.application_command.cog_name}\"") await self.process_error(inter, error) @commands.slash_command( @@ -168,7 +165,7 @@ async def log_errors(self, inter: disnake.CommandInter) -> None: msg="" if self.config: msg=f"Error logging is already enabled in the channel `{self.config.get('channel_name')}` in the guild `{self.config.get('guild_name')}`." - msg+="Are you sure you want to enable error logging in this channel?" + msg+=" Are you sure you want to enable error logging in this channel?" embed = core.TypicalEmbed(inter=inter, title="Log errors here?", description=msg,disabled_footer=True) await inter.send(embed=embed, view=LogerrorsCommandView(inter,timeout=15)) diff --git a/cogs/general.py b/cogs/general.py index 88b4402..7444296 100644 --- a/cogs/general.py +++ b/cogs/general.py @@ -144,7 +144,7 @@ async def _ping(self, inter: disnake.CommandInter) -> None: async def help(self, inter: disnake.CommandInter): embed = core.TypicalEmbed( inter=inter, - title="Hey there! I'm IgKnite help.", + title="Hey there! I'm IgKnite.", description="I'm a bot with no text commands (you heard that right) " + "and I'm here to help you manage and moderate your Discord server alongside " + 'having a midnight music party with your friends in a random voice channel. ' diff --git a/cogs/inspection.py b/cogs/inspection.py index d999353..653d73e 100644 --- a/cogs/inspection.py +++ b/cogs/inspection.py @@ -101,7 +101,7 @@ async def _guildinfo(self, inter: disnake.CommandInter) -> None: name='Birth', value=datetime.strptime( str(inter.guild.created_at), '%Y-%m-%d %H:%M:%S.%f%z' - ).strftime('%b %d, %fY'), + ).strftime('%b %d, %Y'), ) .add_field(name='Owner', value=inter.guild.owner.mention) .add_field(name='Members', value=inter.guild.member_count) diff --git a/core/ui.py b/core/ui.py index 8a7f605..46fbd04 100644 --- a/core/ui.py +++ b/core/ui.py @@ -83,22 +83,6 @@ def add_button( ) return self - # async def disable_button(self,inter: disnake.CommandInter) -> None: - # """disables the button in the view - # """ - - # #check if interacted user is the author of the message - # logger.info(f"Interacted user: {inter.author}") - - # if self.inter.author == inter.author: - # for child in self.children: - # if child.style != disnake.ButtonStyle.link: - # child.disabled = True - - # await inter.edit_original_message(view=self) - # logger.info("Button disabled") - - async def on_timeout(self) -> None: if self.inter: @@ -109,9 +93,5 @@ async def on_timeout(self) -> None: await self.inter.edit_original_message(view=self) - # async def on_error(self, error: Exception, item: Item, interaction: disnake.MessageInteraction) -> None: - # logger.error(f"Error in button click: {error}") - # return await super().on_error(error, item, interaction) -