diff --git a/core/classes/interfaces/imodule.py b/core/classes/interfaces/imodule.py index c719e3f..9bff628 100644 --- a/core/classes/interfaces/imodule.py +++ b/core/classes/interfaces/imodule.py @@ -1,7 +1,6 @@ from abc import ABC, abstractmethod from typing import TYPE_CHECKING, Optional from dataclasses import dataclass - from mods.clone.schemas import ModConfModel if TYPE_CHECKING: @@ -23,6 +22,9 @@ class IModule(ABC): # Add Irc Object to the module (Mandatory) self.Irc = uplink + # Add Loader object to the module (Mandatory) + self.Loader = uplink.Loader + # Add Protocol to the module (Mandatory) self.Protocol = uplink.Protocol @@ -41,19 +43,25 @@ class IModule(ABC): # Add User object to the module (Mandatory) self.User = uplink.User + # Add Client object to the module (Mandatory) + self.Client = uplink.Client + # Add Channel object to the module (Mandatory) self.Channel = uplink.Channel # Add Reputation object to the module (Optional) self.Reputation = uplink.Reputation - self.ModConfig = ModConfModel() + # Load the child classes + self.load() - self.load_module_configuration() - """Load module configuration""" + # Inspect child classes + self.inspect_class() + + # Init the ModConfig model object. + self.ModConfig:ModConfModel = ModConfModel() self.create_tables() - """Create custom module tables""" # Sync the configuration with core configuration (Mandatory) uplink.Base.db_sync_core_config(self.module_name, self.ModConfig) @@ -70,32 +78,27 @@ class IModule(ABC): """ self.Base.db_update_core_config(self.module_name, self.ModConfig, param_key, param_value) + def inspect_class(self): + if not hasattr(self, 'ModConfig'): + raise AttributeError("The Module must init ModConfig attribute in the load method!") + @abstractmethod def create_tables(self) -> None: - """Methode qui va créer la base de donnée si elle n'existe pas. - Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module + """ + Method that will create the database if it does not exist. + A single Session for this class will be created, which will be used within this class/module. + Args: - database_name (str): Nom de la base de données ( pas d'espace dans le nom ) + database_name (str): Name of the database (no spaces allowed in the name) Returns: - None: Aucun retour n'es attendu + None: No return is expected """ @abstractmethod - def load_module_configuration(self) -> None: - """### Load Module Configuration + def load(self) -> None: + """This method is executed when the module is loaded or reloaded. """ - try: - # Build the default configuration model (Mandatory) - self.ModConfig = self.ModConfModel(jsonrpc=0) - - # Sync the configuration with core configuration (Mandatory) - self.Base.db_sync_core_config(self.module_name, self.ModConfig) - - return None - - except TypeError as te: - self.Logs.critical(te) @abstractmethod def unload(self) -> None: diff --git a/core/module.py b/core/module.py index e4317e4..967a437 100644 --- a/core/module.py +++ b/core/module.py @@ -66,9 +66,19 @@ class Module: return self.reload_one_module(uplink, module_name, nickname) # Charger le module - loaded_module = importlib.import_module(f'mods.{module_folder}.{module_name}') - my_class = getattr(loaded_module, class_name, None) # Récuperer le nom de classe - create_instance_of_the_class = my_class(uplink) # Créer une nouvelle instance de la classe + try: + loaded_module = importlib.import_module(f'mods.{module_folder}.{module_name}') + my_class = getattr(loaded_module, class_name, None) # Récuperer le nom de classe + create_instance_of_the_class = my_class(uplink) # Créer une nouvelle instance de la classe + except AttributeError as attr: + red = uplink.Config.COLORS.red + nogc = uplink.Config.COLORS.nogc + uplink.Protocol.send_priv_msg( + nick_from=self.__Config.SERVICE_NICKNAME, + msg=f"[{red}MODULE ERROR{nogc}] Module {module_name} is facing issues ! {attr}", + channel=self.__Config.SERVICE_CHANLOG + ) + return False if not hasattr(create_instance_of_the_class, 'cmd'): uplink.Protocol.send_priv_msg( diff --git a/mods/command/mod_command.py b/mods/command/mod_command.py index d125914..2d41148 100644 --- a/mods/command/mod_command.py +++ b/mods/command/mod_command.py @@ -1,13 +1,12 @@ from typing import Optional, TYPE_CHECKING from dataclasses import dataclass +from core.classes.interfaces.imodule import IModule import mods.command.utils as utils if TYPE_CHECKING: - from core.irc import Irc from core.definition import MUser - from sqlalchemy import CursorResult, Row, Sequence -class Command: +class Command(IModule): @dataclass class ModConfModel: @@ -15,44 +14,44 @@ class Command: """ pass - def __init__(self, ircInstance: 'Irc') -> None: + def create_tables(self) -> None: + """Methode qui va créer la base de donnée si elle n'existe pas. + Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module + Args: + database_name (str): Nom de la base de données ( pas d'espace dans le nom ) - # Module name (Mandatory) - self.module_name = 'mod_' + str(self.__class__.__name__).lower() + Returns: + None: Aucun retour n'es attendu + """ - # Add Irc Object to the module (Mandatory) - self.Irc = ircInstance + table_automode = '''CREATE TABLE IF NOT EXISTS command_automode ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + created_on TEXT, + updated_on TEXT, + nickname TEXT, + channel TEXT, + mode TEXT + ) + ''' - # Add Loader Object to the module (Mandatory) - self.Loader = ircInstance.Loader - - # Add Protocol object to the module (Mandatory) - self.Protocol = ircInstance.Protocol - - # Add Global Configuration to the module (Mandatory) - self.Config = ircInstance.Config - - # Add Base object to the module (Mandatory) - self.Base = ircInstance.Base - - # Add main Utils to the module - self.MainUtils = ircInstance.Utils - - # Add logs object to the module (Mandatory) - self.Logs = ircInstance.Loader.Logs - - # Add User object to the module (Mandatory) - self.User = ircInstance.User - - # Add Client object to the module (Mandatory) - self.Client = ircInstance.Client - - # Add Channel object to the module (Mandatory) - self.Channel = ircInstance.Channel + self.Base.db_execute_query(table_automode) + return None + def load(self) -> None: # Module Utils self.mod_utils = utils + # Build the default configuration model (Mandatory) + self.ModConfig = self.ModConfModel() + + self.user_to_notice: str = '' + self.show_219: bool = True + + # Register new commands into the protocol + new_cmds = {'403', '401', '006', '018', '219', '223'} + for c in new_cmds: + self.Irc.Protocol.known_protocol.add(c) + self.Irc.build_command(2, self.module_name, 'join', 'Join a channel') self.Irc.build_command(2, self.module_name, 'assign', 'Assign a user to a role or task') self.Irc.build_command(2, self.module_name, 'part', 'Leave a channel') @@ -104,73 +103,6 @@ class Command: self.Irc.build_command(2, self.module_name, 'klinelist', 'List all K-line bans') self.Irc.build_command(3, self.module_name, 'map', 'Show the server network map') - # Init the module - self.__init_module() - - # Log the module - self.Logs.debug(f'-- Module {self.module_name} loaded ...') - - def __init_module(self) -> None: - - # Create you own tables (Mandatory) - self.__create_tables() - - # Load module configuration and sync with core one (Mandatory) - self.__load_module_configuration() - # End of mandatory methods you can start your customization # - - self.user_to_notice: str = '' - self.show_219: bool = True - - return None - - def __create_tables(self) -> None: - """Methode qui va créer la base de donnée si elle n'existe pas. - Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module - Args: - database_name (str): Nom de la base de données ( pas d'espace dans le nom ) - - Returns: - None: Aucun retour n'es attendu - """ - - table_automode = '''CREATE TABLE IF NOT EXISTS command_automode ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - created_on TEXT, - updated_on TEXT, - nickname TEXT, - channel TEXT, - mode TEXT - ) - ''' - - self.Base.db_execute_query(table_automode) - return None - - def __load_module_configuration(self) -> None: - """### Load Module Configuration - """ - try: - # Build the default configuration model (Mandatory) - self.ModConfig = self.ModConfModel() - - # Sync the configuration with core configuration (Mandatory) - self.Base.db_sync_core_config(self.module_name, self.ModConfig) - - return None - - except TypeError as te: - self.Logs.critical(te) - - def __update_configuration(self, param_key: str, param_value: str): - """Update the local and core configuration - - Args: - param_key (str): The parameter key - param_value (str): The parameter value - """ - self.Base.db_update_core_config(self.module_name, self.ModConfig, param_key, param_value) - def unload(self) -> None: self.Irc.Commands.drop_command_by_module(self.module_name) return None @@ -186,10 +118,12 @@ class Command: nogc = self.Config.COLORS.nogc cmd = list(data).copy() - if len(cmd) < 2: + pos, parsed_cmd = self.Irc.Protocol.get_ircd_protocol_poisition(cmd=cmd, log=True) + + if pos == -1: return None - match cmd[1]: + match parsed_cmd: # [':irc.deb.biz.st', '403', 'Dev-PyDefender', '#Z', ':No', 'such', 'channel'] case '403' | '401': try: @@ -260,22 +194,10 @@ class Command: except Exception as err: self.Logs.warning(f'Unknown Error: {str(err)}') - case _: - pass - - if len(cmd) < 3: - return None - - match cmd[2]: - case 'SJOIN': # ['@msgid=yldTlbwAGbzCGUcCIHi3ku;time=2024-11-11T17:56:24.297Z', ':001', 'SJOIN', '1728815963', '#znc', ':001LQ0L0C'] # Check if the user has an automode try: - - if len(cmd) < 6: - return None - user_uid = self.User.clean_uid(cmd[5]) userObj: MUser = self.User.get_user(user_uid) channel_name = cmd[4] if self.Channel.is_valid_channel(cmd[4]) else None @@ -301,6 +223,9 @@ class Command: except KeyError as ke: self.Logs.error(f"Key Error: {err}") + case _: + pass + except Exception as err: self.Logs.error(f"General Error: {err}") diff --git a/mods/test/mod_test.py b/mods/test/mod_test.py index 8f79be5..b59c9d4 100644 --- a/mods/test/mod_test.py +++ b/mods/test/mod_test.py @@ -6,25 +6,12 @@ class Test(IModule): @dataclass class ModConfModel: """The Model containing the module parameters + you can leave it without params. + just use pass | if you leave it empty, in the load() method just init empty object ==> self.ModConfig = ModConfModel() """ param_exemple1: str param_exemple2: int - def load_module_configuration(self) -> None: - """### Load Module Configuration - """ - - # Create module commands (Mandatory) - self.Irc.build_command(0, self.module_name, 'test-command', 'Execute a test command') - self.Irc.build_command(1, self.module_name, 'test_level_1', 'Execute a level 1 test command') - self.Irc.build_command(2, self.module_name, 'test_level_2', 'Execute a level 2 test command') - self.Irc.build_command(3, self.module_name, 'test_level_3', 'Execute a level 3 test command') - - # Build the default configuration model (Mandatory) - self.ModConfig = self.ModConfModel(param_exemple1='str', param_exemple2=1) - - return None - def create_tables(self) -> None: """Methode qui va créer la base de donnée si elle n'existe pas. Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module @@ -45,6 +32,19 @@ class Test(IModule): # self.Base.db_execute_query(table_logs) return None + def load(self) -> None: + """### Load Module Configuration + """ + + # Create module commands (Mandatory) + self.Irc.build_command(0, self.module_name, 'test-command', 'Execute a test command') + self.Irc.build_command(1, self.module_name, 'test_level_1', 'Execute a level 1 test command') + self.Irc.build_command(2, self.module_name, 'test_level_2', 'Execute a level 2 test command') + self.Irc.build_command(3, self.module_name, 'test_level_3', 'Execute a level 3 test command') + + # Build the default configuration model (Mandatory) + self.ModConfig = self.ModConfModel(param_exemple1='str', param_exemple2=1) + def unload(self) -> None: self.Irc.Commands.drop_command_by_module(self.module_name) return None