Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,6 @@ class Greeter(cogs.BaseCog):
@app_commands.command(
name="hello",
description="Says hello to the bot (because they are doing such a great job!)",
extras={"module": "hello"},
)
async def hello_app_command(self: Self, interaction: discord.Interaction) -> None:
await interaction.response.send_message("🇭 🇪 🇾")
Expand Down
93 changes: 30 additions & 63 deletions bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,13 @@ class TechSupportBot(commands.Bot):
CONFIG_PATH (str): The hard coded path to the yaml config file
EXTENSIONS_DIR_NAME (str): The hardcoded folder for commands
EXTENSIONS_DIR (str): The list of all files in the EXTENSIONS_DIR_NAME folder
FUNCTIONS_DIR_NAME (str):The hardcoded folder for functions
FUNCTIONS_DIR (str):The list of all files in the FUNCTIONS_DIR_NAME folder
"""

CONFIG_PATH: str = os.environ.get("CONFIG_YML", "./config.yml")
EXTENSIONS_DIR_NAME: str = "commands"
EXTENSIONS_DIR_NAME: str = "modules"
EXTENSIONS_DIR: str = (
f"{os.path.join(os.path.dirname(__file__))}/{EXTENSIONS_DIR_NAME}"
)
FUNCTIONS_DIR_NAME: str = "functions"
FUNCTIONS_DIR: str = (
f"{os.path.join(os.path.dirname(__file__))}/{FUNCTIONS_DIR_NAME}"
)

def __init__(
self: Self, intents: discord.Intents, allowed_mentions: discord.AllowedMentions
Expand Down Expand Up @@ -482,35 +476,35 @@ async def get_postgres_ref(self: Self) -> gino.GinoEngine:

async def get_potential_extensions(self: Self) -> list[str]:
"""Gets the current list of extensions in the defined directory.
This ONLY gets commands, not functions

Returns:
list[str]: Gets a list of the string names of every python file
in the commands folder
"""

self.logger.console.info(f"Searching {self.EXTENSIONS_DIR} for extensions")
extensions_list = [
os.path.basename(f)[:-3]
for f in glob.glob(f"{self.EXTENSIONS_DIR}/*.py")
if os.path.isfile(f) and not f.endswith("__init__.py")
]
return extensions_list
pattern = os.path.join(self.EXTENSIONS_DIR, "**", "*.py")

async def get_potential_function_extensions(self: Self) -> list[str]:
"""Gets the current list of extensions in the defined directory.
This ONLY gets functions, not commands
extensions_list = []

for file_path in glob.glob(pattern, recursive=True):
if not os.path.isfile(file_path):
continue

if file_path.endswith("__init__.py"):
continue

# strip root dir
rel_path = os.path.relpath(file_path, self.EXTENSIONS_DIR)

# remove .py
rel_path = rel_path[:-3]

# convert folder separators into dots
dotted = rel_path.replace(os.sep, ".")

extensions_list.append(dotted)

Returns:
list[str]: Gets a list of the string names of every python file
in the functions folder
"""
self.logger.console.info(f"Searching {self.FUNCTIONS_DIR} for extensions")
extensions_list = [
os.path.basename(f)[:-3]
for f in glob.glob(f"{self.FUNCTIONS_DIR}/*.py")
if os.path.isfile(f) and not f.endswith("__init__.py")
]
return extensions_list

async def load_extensions(self: Self, graceful: bool = True) -> None:
Expand Down Expand Up @@ -545,27 +539,8 @@ async def load_extensions(self: Self, graceful: bool = True) -> None:
if not graceful:
raise exception

self.logger.console.debug("Retrieving functions")
for extension_name in await self.get_potential_function_extensions():
if extension_name in self.file_config.bot_config.disabled_extensions:
self.logger.console.debug(
f"{extension_name} is disabled on startup - ignoring load"
)
continue

try:
await self.load_extension(f"{self.FUNCTIONS_DIR_NAME}.{extension_name}")
self.extension_name_list.append(extension_name)
except Exception as exception:
self.logger.console.error(
f"Failed to load extension {extension_name}: {exception}"
)
if not graceful:
raise exception

def get_command_extension_name(self: Self, command: commands.Command) -> str:
"""Gets the subname of an extension from a command.
Used only for commands, should never be run for a function
"""Gets the subname of an module from a command.

Args:
command (commands.Command): the command to reference
Expand All @@ -575,7 +550,7 @@ def get_command_extension_name(self: Self, command: commands.Command) -> str:
"""
if not command.module.startswith(f"{self.EXTENSIONS_DIR_NAME}."):
return None
extension_name = command.module.split(".")[1]
extension_name = ".".join(command.module.split(".")[1:]).replace(".py", "")
return extension_name

async def register_file_extension(
Expand Down Expand Up @@ -786,21 +761,13 @@ async def interaction_check(self: Self, interaction: discord.Interaction) -> boo
console_only=True,
)
# Check 1 - Ensure extension is enabled
try:
extension_name = interaction.command.extras["module"]
except KeyError:
# Skip extension enabled check if no extras module has been defined
self.logger.console.warning(
"No module has been defined, skipping extension enabled check"
)
extension_name = None

if extension_name:
# If the extension is disabled, raise an error to show it and block execution
if not self.command_run_extension_disabled_check(
interaction.guild, extension_name
):
raise custom_errors.AppCommandExtensionDisabled
# removes "modules."
extension_name = interaction.command.callback.__module__[8:]
# If the extension is disabled, raise an error to show it and block execution
if not self.command_run_extension_disabled_check(
interaction.guild, extension_name
):
raise custom_errors.AppCommandExtensionDisabled

# Check 2 - Approve if invoker is bot admin
result = await self.command_run_admin_check(interaction.user)
Expand Down
27 changes: 0 additions & 27 deletions commands/__init__.py

This file was deleted.

101 changes: 55 additions & 46 deletions core/cogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ class BaseCog(commands.Cog):
Args:
bot (bot.TechSupportBot): the bot object
no_guild (bool): True if the extension should run globally
extension_name(str): The name of the extension
if it needs to be different than the file name
"""

COG_TYPE: str = "Base"
Expand All @@ -37,11 +35,10 @@ def __init__(
self: Self,
bot: bot.TechSupportBot,
no_guild: bool = False,
extension_name: str = None,
) -> None:
self.bot = bot
self.no_guild = no_guild
self.extension_name = extension_name
self.extension_name = self.get_extension_name()

asyncio.create_task(self._preconfig())

Expand Down Expand Up @@ -91,6 +88,21 @@ def extension_enabled(self: Self, guild: discord.Guild) -> bool:
return True
return False

def get_extension_name(self: Self) -> str | None:
"""Gets the full extension name for this cog.

Returns:
str | None: The dotted extension name without the root
extensions directory, or None if the cog is not
contained within the extensions directory.
"""
module = self.__class__.__module__

if not module.startswith(f"{self.bot.EXTENSIONS_DIR_NAME}."):
return None

return ".".join(module.split(".")[1:]).replace(".py", "")


class MatchCog(BaseCog):
"""
Expand Down Expand Up @@ -338,60 +350,57 @@ async def _loop_execute(
if not self.ON_START:
await self.wait(guild)

for folder_dir in [self.bot.EXTENSIONS_DIR_NAME, self.bot.FUNCTIONS_DIR_NAME]:
while self.bot.extensions.get(f"{folder_dir}.{self.extension_name}"):
if guild and guild not in self.bot.guilds:
break
while self.bot.extensions.get(
f"{self.bot.EXTENSIONS_DIR_NAME}.{self.extension_name}"
):
if guild and guild not in self.bot.guilds:
break

try:
channels_list = configuration.get_config_entry(
guild.id, self.CHANNELS_KEY
)
except AttributeError:
channels_list = []

if target_channel and str(target_channel.id) not in channels_list:
# exit task if the channel is no longer configured
break

if (
guild is None
or self.extension_name
in configuration.get_config_entry(
guild.id, "core_enabled_extensions"
)
):
try:
if target_channel:
await self.execute(guild, target_channel)
else:
await self.execute(guild)
except Exception as exception:
# always try to wait even when execute fails
await self.bot.logger.send_log(
message=f"Loop cog execute error: {self.__class__.__name__}!",
level=LogLevel.ERROR,
channel=configuration.get_config_entry(
guild.id, "core_logging_channel"
),
context=LogContext(guild=guild),
exception=exception,
)
try:
channels_list = configuration.get_config_entry(
guild.id, self.CHANNELS_KEY
)
except AttributeError:
channels_list = []

if target_channel and str(target_channel.id) not in channels_list:
# exit task if the channel is no longer configured
break

if guild is None or self.extension_name in configuration.get_config_entry(
guild.id, "core_enabled_extensions"
):
try:
await self.wait(guild)
if target_channel:
await self.execute(guild, target_channel)
else:
await self.execute(guild)
except Exception as exception:
# always try to wait even when execute fails
await self.bot.logger.send_log(
message=f"Loop wait cog error: {self.__class__.__name__}!",
message=f"Loop cog execute error: {self.__class__.__name__}!",
level=LogLevel.ERROR,
channel=configuration.get_config_entry(
guild.id, "core_logging_channel"
),
context=LogContext(guild=guild),
exception=exception,
)
# avoid spamming
await self._default_wait()

try:
await self.wait(guild)
except Exception as exception:
await self.bot.logger.send_log(
message=f"Loop wait cog error: {self.__class__.__name__}!",
level=LogLevel.ERROR,
channel=configuration.get_config_entry(
guild.id, "core_logging_channel"
),
context=LogContext(guild=guild),
exception=exception,
)
# avoid spamming
await self._default_wait()

async def execute(
self: Self,
Expand Down
5 changes: 0 additions & 5 deletions functions/__init__.py

This file was deleted.

6 changes: 3 additions & 3 deletions ircrelay/relay.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import irc.client
import irc.connection

import commands
import modules.operation
from ircrelay import formatting


Expand All @@ -25,7 +25,7 @@ class IRCBot(irc.bot.SingleServerIRCBot):
The class to start the entire IRC bot

Attributes:
irc_cog (commands.relay.DiscordToIRC): The discord cog for the relay,
irc_cog (modules.operation.relay.DiscordToIRC): The discord cog for the relay,
to allow communication between
loop (asyncio.AbstractEventLoop): The discord bots event loop
console (logging.Logger): The console to print errors to
Expand All @@ -43,7 +43,7 @@ class IRCBot(irc.bot.SingleServerIRCBot):
password (str): The password of the IRC bot account
"""

irc_cog: commands.relay.DiscordToIRC = None
irc_cog: modules.operation.relay.DiscordToIRC = None
loop: asyncio.AbstractEventLoop = None
console: logging.Logger = logging.getLogger("root")
IRC_BOLD: str = ""
Expand Down
7 changes: 7 additions & 0 deletions modules/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""All the customizable modules for the bot"""

from .administration import *
from .fun import *
from .moderation import *
from .operation import *
from .utility import *
3 changes: 0 additions & 3 deletions commands/backup.py → modules/administration/backup.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@ class BackupCommand(cogs.BaseCog):
@app_commands.command(
name="backup",
description="Backs up data into a zip file",
extras={
"module": "backup",
},
)
async def backup(
self: Self, interaction: discord.Interaction, config_file: bool = False
Expand Down
File renamed without changes.
File renamed without changes.
Loading
Loading