diff --git a/core/Model.py b/core/Model.py index 00a2cef..946435c 100644 --- a/core/Model.py +++ b/core/Model.py @@ -315,7 +315,7 @@ class Channel: for user in newChan.uids: record.uids.append(user) - # Supprimer les doublons + # Supprimer les doublons del_duplicates = list(set(record.uids)) record.uids = del_duplicates self.log.debug(f'Updating a new UID to the channel {record}') @@ -402,3 +402,97 @@ class Channel: self.log.debug(f'Search {name} -- result = {Channel}') return Channel + +class Clones: + + @dataclass + class CloneModel: + alive: bool + nickname: str + username: str + + UID_CLONE_DB: list[CloneModel] = [] + + def __init__(self, Base: Base) -> None: + self.log = Base.logs + + def insert(self, newCloneObject: CloneModel) -> bool: + """Create new Clone object + + Args: + newCloneObject (CloneModel): New CloneModel object + + Returns: + bool: True if inserted + """ + result = False + exist = False + + for record in self.UID_CLONE_DB: + if record.nickname == newCloneObject.nickname: + # If the user exist then return False and do not go further + exist = True + self.log.debug(f'{record.nickname} already exist') + return result + + if not exist: + self.UID_CLONE_DB.append(newCloneObject) + result = True + self.log.debug(f'New Clone Object Created: ({newCloneObject})') + + if not result: + self.log.critical(f'The Clone Object was not inserted {newCloneObject}') + + return result + + def delete(self, nickname: str) -> bool: + """Delete the Clone Object starting from the nickname + + Args: + nickname (str): nickname of the clone + + Returns: + bool: True if deleted + """ + result = False + + for record in self.UID_CLONE_DB: + if record.nickname == nickname: + # If the user exist then remove and return True and do not go further + self.UID_CLONE_DB.remove(record) + result = True + self.log.debug(f'The clone ({record.nickname}) has been deleted') + return result + + if not result: + self.log.critical(f'The UID {nickname} was not deleted') + + return result + + def exists(self, nickname: str) -> bool: + """Check if the nickname exist + + Args: + nickname (str): Nickname of the clone + + Returns: + bool: True if the nickname exist + """ + response = False + + for cloneObject in self.UID_CLONE_DB: + if cloneObject.nickname == nickname: + response = True + + return response + + def kill(self, nickname:str) -> bool: + + response = False + + for cloneObject in self.UID_CLONE_DB: + if cloneObject.nickname == nickname: + cloneObject.alive = False # Kill the clone + response = True + + return response diff --git a/core/base.py b/core/base.py index c3b4245..18a28c7 100644 --- a/core/base.py +++ b/core/base.py @@ -1,5 +1,6 @@ -import time, threading, os, random, socket, hashlib, ipaddress, logging, requests, json, re -from typing import Union +import time, threading, os, random, socket, hashlib, ipaddress, logging, requests, json, re, ast +from dataclasses import fields +from typing import Union, Literal from base64 import b64decode from datetime import datetime from sqlalchemy import create_engine, Engine, Connection, CursorResult @@ -16,7 +17,7 @@ class Base: self.Config = Config # Assigner l'objet de configuration self.init_log_system() # Demarrer le systeme de log - self.check_for_new_version(True) # Verifier si une nouvelle version est disponible + self.check_for_new_version(True) # Verifier si une nouvelle version est disponible self.running_timers:list[threading.Timer] = [] # Liste des timers en cours self.running_threads:list[threading.Thread] = [] # Liste des threads en cours @@ -47,7 +48,7 @@ class Base: def __get_latest_defender_version(self) -> None: try: self.logs.debug(f'Looking for a new version available on Github') - print(f'===> Looking for a new version available on Github') + # print(f'===> Looking for a new version available on Github') token = '' json_url = f'https://raw.githubusercontent.com/adator85/IRC_DEFENDER_MODULES/main/version.json' headers = { @@ -158,7 +159,7 @@ class Base: encoding='UTF-8', format='%(asctime)s - %(levelname)s - %(filename)s - %(lineno)d - %(funcName)s - %(message)s') - self.logs.info('#################### STARTING INTERCEPTOR HQ ####################') + self.logs.info('#################### STARTING DEFENDER ####################') return None @@ -190,8 +191,8 @@ class Base: Returns: bool: True si le module existe déja dans la base de données sinon False """ - query = f"SELECT id FROM {self.Config.table_module} WHERE module = :module" - mes_donnes = {'module': module_name} + query = f"SELECT id FROM {self.Config.table_module} WHERE module_name = :module_name" + mes_donnes = {'module_name': module_name} results = self.db_execute_query(query, mes_donnes) if results.fetchall(): @@ -208,10 +209,9 @@ class Base: if not self.db_isModuleExist(module_name): self.logs.debug(f"Le module {module_name} n'existe pas alors ont le créer") - insert_cmd_query = f"INSERT INTO {self.Config.table_module} (datetime, user, module) VALUES (:datetime, :user, :module)" - mes_donnees = {'datetime': self.get_datetime(), 'user': user_cmd, 'module': module_name} + insert_cmd_query = f"INSERT INTO {self.Config.table_module} (datetime, user, module_name, isdefault) VALUES (:datetime, :user, :module_name, :isdefault)" + mes_donnees = {'datetime': self.get_datetime(), 'user': user_cmd, 'module_name': module_name, 'isdefault': 0} self.db_execute_query(insert_cmd_query, mes_donnees) - # self.db_close_session(self.session) else: self.logs.debug(f"Le module {module_name} existe déja dans la base de données") @@ -223,12 +223,174 @@ class Base: Args: cmd (str): le module a enregistrer """ - insert_cmd_query = f"DELETE FROM {self.Config.table_module} WHERE module = :module" - mes_donnees = {'module': module_name} + insert_cmd_query = f"DELETE FROM {self.Config.table_module} WHERE module_name = :module_name" + mes_donnees = {'module_name': module_name} self.db_execute_query(insert_cmd_query, mes_donnees) return False + def db_sync_core_config(self, module_name: str, dataclassObj: object) -> bool: + """Sync module local parameters with the database + if new module then local param will be stored in the database + if old module then db param will be moved to the local dataclassObj + if new local param it will be stored in the database + if local param was removed then it will also be removed from the database + + Args: + module_name (str): The module name ex. mod_defender + dataclassObj (object): The Dataclass object + + Returns: + bool: _description_ + """ + try: + response = True + current_date = self.get_datetime() + core_table = self.Config.table_config + + # Add local parameters to DB + for field in fields(dataclassObj): + param_key = field.name + param_value = str(getattr(dataclassObj, field.name)) + + param_to_search = {'module_name': module_name, 'param_key': param_key} + + search_query = f'''SELECT id FROM {core_table} WHERE module_name = :module_name AND param_key = :param_key''' + excecute_search_query = self.db_execute_query(search_query, param_to_search) + result_search_query = excecute_search_query.fetchone() + + if result_search_query is None: + # If param and module_name doesn't exist create the record + param_to_insert = {'datetime': current_date,'module_name': module_name, + 'param_key': param_key,'param_value': param_value + } + + insert_query = f'''INSERT INTO {core_table} (datetime, module_name, param_key, param_value) + VALUES (:datetime, :module_name, :param_key, :param_value) + ''' + execution = self.db_execute_query(insert_query, param_to_insert) + + if execution.rowcount > 0: + self.logs.debug(f'New parameter added to the database: {param_key} --> {param_value}') + + # Delete from DB unused parameter + query_select = f"SELECT module_name, param_key, param_value FROM {core_table} WHERE module_name = :module_name" + parameter = {'module_name': module_name} + execute_query_select = self.db_execute_query(query_select, parameter) + result_query_select = execute_query_select.fetchall() + + for result in result_query_select: + db_mod_name, db_param_key, db_param_value = result + if not hasattr(dataclassObj, db_param_key): + mes_donnees = {'param_key': db_param_key, 'module_name': db_mod_name} + execute_delete = self.db_execute_query(f'DELETE FROM {core_table} WHERE module_name = :module_name and param_key = :param_key', mes_donnees) + row_affected = execute_delete.rowcount + if row_affected > 0: + self.logs.debug(f'A parameter has been deleted from the database: {db_param_key} --> {db_param_value} | Mod: {db_mod_name}') + + # Sync local variable with Database + query = f"SELECT param_key, param_value FROM {core_table} WHERE module_name = :module_name" + parameter = {'module_name': module_name} + response = self.db_execute_query(query, parameter) + result = response.fetchall() + + for param, value in result: + if type(getattr(dataclassObj, param)) == list: + value = ast.literal_eval(value) + + setattr(dataclassObj, param, self.int_if_possible(value)) + + return response + + except AttributeError as attrerr: + self.logs.error(f'Attribute Error: {attrerr}') + except Exception as err: + self.logs.error(err) + return False + + def db_update_core_config(self, module_name:str, dataclassObj: object, param_key:str, param_value: str) -> bool: + + core_table = 'core_config' + # Check if the param exist + if not hasattr(dataclassObj, param_key): + self.logs.error(f"Le parametre {param_key} n'existe pas dans la variable global") + return False + + mes_donnees = {'module_name': module_name, 'param_key': param_key, 'param_value': param_value} + search_param_query = f"SELECT id FROM {core_table} WHERE module_name = :module_name AND param_key = :param_key" + result = self.db_execute_query(search_param_query, mes_donnees) + isParamExist = result.fetchone() + + if not isParamExist is None: + mes_donnees = {'datetime': self.get_datetime(), + 'module_name': module_name, + 'param_key': param_key, + 'param_value': param_value + } + query = f'''UPDATE {core_table} SET datetime = :datetime, param_value = :param_value WHERE module_name = :module_name AND param_key = :param_key''' + update = self.db_execute_query(query, mes_donnees) + updated_rows = update.rowcount + if updated_rows > 0: + setattr(dataclassObj, param_key, self.int_if_possible(param_value)) + self.logs.debug(f'Parameter updated : {param_key} - {param_value} | Module: {module_name}') + + self.logs.debug(dataclassObj) + + return True + + def db_query_channel(self, action: Literal['add','del'], module_name: str, channel_name: str) -> bool: + """You can add a channel or delete a channel. + + Args: + action (Literal['add','del']): Action on the database + module_name (str): The module name (mod_test) + channel_name (str): The channel name (With #) + + Returns: + bool: True if action done + """ + try: + channel_name = channel_name.lower() if self.Is_Channel(channel_name) else None + core_table = 'core_channel' + + if not channel_name: + self.logs.warn(f'The channel [{channel_name}] is not correct') + return False + + match action: + + case 'add': + mes_donnees = {'module_name': module_name, 'channel_name': channel_name} + response = self.db_execute_query(f"SELECT id FROM {core_table} WHERE module_name = :module_name AND channel_name = :channel_name", mes_donnees) + isChannelExist = response.fetchone() + + if isChannelExist is None: + mes_donnees = {'datetime': self.get_datetime(), 'channel_name': channel_name, 'module_name': module_name} + insert = self.db_execute_query(f"INSERT INTO {core_table} (datetime, channel_name, module_name) VALUES (:datetime, :channel_name, :module_name)", mes_donnees) + if insert.rowcount: + self.logs.debug(f'New channel added: channel={channel_name} / module_name={module_name}') + return True + else: + return False + pass + + case 'del': + mes_donnes = {'channel_name': channel_name, 'module_name': module_name} + response = self.db_execute_query(f"DELETE FROM {core_table} WHERE channel_name = :channel_name AND module_name = :module_name", mes_donnes) + + if response.rowcount > 0: + self.logs.debug(f'Channel deleted: channel={channel_name} / module: {module_name}') + return True + else: + + return False + + case _: + return False + + except Exception as err: + self.logs.error(err) + def db_create_first_admin(self) -> None: user = self.db_execute_query(f"SELECT id FROM {self.Config.table_admin}") @@ -267,6 +429,13 @@ class Base: self.logs.error(f'Assertion Error -> {ae}') def create_thread(self, func:object, func_args: tuple = (), run_once:bool = False) -> None: + """Create a new thread and store it into running_threads variable + + Args: + func (object): The method/function you want to execute via this thread + func_args (tuple, optional): Arguments of the function/method. Defaults to (). + run_once (bool, optional): If you want to ensure that this method/function run once. Defaults to False. + """ try: func_name = func.__name__ @@ -275,10 +444,6 @@ class Base: if thread.getName() == func_name: return None - # if func_name in self.running_threads: - # print(f"HeartBeat is running") - # return None - th = threading.Thread(target=func, args=func_args, name=str(func_name), daemon=True) th.start() @@ -376,14 +541,23 @@ class Base: def __create_db(self) -> None: - table_logs = f'''CREATE TABLE IF NOT EXISTS {self.Config.table_log} ( + table_core_log = f'''CREATE TABLE IF NOT EXISTS {self.Config.table_log} ( id INTEGER PRIMARY KEY AUTOINCREMENT, datetime TEXT, server_msg TEXT ) ''' - table_cmds = f'''CREATE TABLE IF NOT EXISTS {self.Config.table_commande} ( + table_core_config = f'''CREATE TABLE IF NOT EXISTS {self.Config.table_config} ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + datetime TEXT, + module_name TEXT, + param_key TEXT, + param_value TEXT + ) + ''' + + table_core_log_command = f'''CREATE TABLE IF NOT EXISTS {self.Config.table_commande} ( id INTEGER PRIMARY KEY AUTOINCREMENT, datetime TEXT, user TEXT, @@ -391,15 +565,24 @@ class Base: ) ''' - table_modules = f'''CREATE TABLE IF NOT EXISTS {self.Config.table_module} ( + table_core_module = f'''CREATE TABLE IF NOT EXISTS {self.Config.table_module} ( id INTEGER PRIMARY KEY AUTOINCREMENT, datetime TEXT, user TEXT, - module TEXT + module_name TEXT, + isdefault INTEGER + ) + ''' + + table_core_channel = '''CREATE TABLE IF NOT EXISTS core_channel ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + datetime TEXT, + module_name TEXT, + channel_name TEXT ) ''' - table_admins = f'''CREATE TABLE IF NOT EXISTS {self.Config.table_admin} ( + table_core_admin = f'''CREATE TABLE IF NOT EXISTS {self.Config.table_admin} ( id INTEGER PRIMARY KEY AUTOINCREMENT, createdOn TEXT, user TEXT, @@ -410,10 +593,12 @@ class Base: ) ''' - self.db_execute_query(table_logs) - self.db_execute_query(table_cmds) - self.db_execute_query(table_modules) - self.db_execute_query(table_admins) + self.db_execute_query(table_core_log) + self.db_execute_query(table_core_log_command) + self.db_execute_query(table_core_module) + self.db_execute_query(table_core_admin) + self.db_execute_query(table_core_channel) + self.db_execute_query(table_core_config) return None @@ -519,7 +704,7 @@ class Base: self.periodic_func.clear() def clean_uid(self, uid:str) -> str: - """Clean UID by removing @ / % / + / Owner / and * + """Clean UID by removing @ / % / + / ~ / * / : Args: uid (str): The UID to clean @@ -528,7 +713,7 @@ class Base: str: Clean UID without any sign """ - pattern = fr'[@|%|\+|~|\*]*' + pattern = fr'[:|@|%|\+|~|\*]*' parsed_UID = re.sub(pattern, '', uid) return parsed_UID @@ -542,11 +727,19 @@ class Base: Returns: bool: True if the string is a channel / False if this is not a channel """ + try: + + if channelToCheck is None: + return False - pattern = fr'^#' - isChannel = re.findall(pattern, channelToCheck) + pattern = fr'^#' + isChannel = re.findall(pattern, channelToCheck) - if not isChannel: - return False - else: - return True \ No newline at end of file + if not isChannel: + return False + else: + return True + except TypeError as te: + self.logs.error(f'TypeError: [{channelToCheck}] - {te}') + except Exception as err: + self.logs.error(f'TypeError: {err}') diff --git a/core/connection.py b/core/connection.py new file mode 100644 index 0000000..d865db1 --- /dev/null +++ b/core/connection.py @@ -0,0 +1,212 @@ +import socket, ssl, time +from ssl import SSLSocket +from core.loadConf import Config +from core.Model import Clones +from core.base import Base +from typing import Union + +class Connection: + + def __init__(self, server_port: int, nickname: str, username: str, channels:list[str], CloneObject: Clones, ssl:bool = False) -> None: + + self.Config = Config().ConfigObject + self.Base = Base(self.Config) + self.IrcSocket: Union[socket.socket, SSLSocket] = None + self.nickname = nickname + self.username = username + self.channels:list[str] = channels + self.CHARSET = ['utf-8', 'iso-8859-1'] + self.Clones = CloneObject + self.signal: bool = True + for clone in self.Clones.UID_CLONE_DB: + if clone.nickname == nickname: + self.currentCloneObject = clone + + self.create_socket(self.Config.SERVEUR_IP, self.Config.SERVEUR_HOSTNAME, server_port, ssl) + self.send_connection_information_to_server(self.IrcSocket) + self.connect() + + def create_socket(self, server_ip: str, server_hostname: str, server_port: int, ssl: bool = False) -> bool: + + try: + soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM or socket.SOCK_NONBLOCK) + connexion_information = (server_ip, server_port) + + if ssl: + # Créer un object ssl + ssl_context = self.__ssl_context() + ssl_connexion = ssl_context.wrap_socket(soc, server_hostname=server_hostname) + ssl_connexion.connect(connexion_information) + self.IrcSocket:SSLSocket = ssl_connexion + self.SSL_VERSION = self.IrcSocket.version() + self.Base.logs.debug(f'> Connexion en mode SSL : Version = {self.SSL_VERSION}') + else: + soc.connect(connexion_information) + self.IrcSocket:socket.socket = soc + self.Base.logs.debug(f'> Connexion en mode normal') + + return True + + except ssl.SSLEOFError as soe: + self.Base.logs.critical(f"SSLEOFError __create_socket: {soe} - {soc.fileno()}") + return False + except ssl.SSLError as se: + self.Base.logs.critical(f"SSLError __create_socket: {se} - {soc.fileno()}") + return False + except OSError as oe: + self.Base.logs.critical(f"OSError __create_socket: {oe} - {soc.fileno()}") + return False + except AttributeError as ae: + self.Base.logs.critical(f"AttributeError __create_socket: {ae} - {soc.fileno()}") + return False + + def send2socket(self, send_message:str) -> None: + """Envoit les commandes à envoyer au serveur. + + Args: + string (Str): contient la commande à envoyer au serveur. + """ + try: + with self.Base.lock: + # print(f">{str(send_message)}") + self.IrcSocket.send(f"{send_message}\r\n".encode(self.CHARSET[0])) + self.Base.logs.debug(f'{send_message}') + + except UnicodeDecodeError: + self.Base.logs.error(f'Decode Error try iso-8859-1 - message: {send_message}') + self.IrcSocket.send(f"{send_message}\r\n".encode(self.CHARSET[1],'replace')) + except UnicodeEncodeError: + self.Base.logs.error(f'Encode Error try iso-8859-1 - message: {send_message}') + self.IrcSocket.send(f"{send_message}\r\n".encode(self.CHARSET[1],'replace')) + except AssertionError as ae: + self.Base.logs.warning(f'Assertion Error {ae} - message: {send_message}') + except ssl.SSLEOFError as soe: + self.Base.logs.error(f"SSLEOFError: {soe} - {send_message}") + except ssl.SSLError as se: + self.Base.logs.error(f"SSLError: {se} - {send_message}") + except OSError as oe: + self.Base.logs.error(f"OSError: {oe} - {send_message}") + + def send_connection_information_to_server(self, writer:Union[socket.socket, SSLSocket]) -> None: + """Créer le link et envoyer les informations nécessaires pour la + connexion au serveur. + + Args: + writer (StreamWriter): permet l'envoi des informations au serveur. + """ + try: + nickname = self.nickname + username = self.username + + # Envoyer un message d'identification + writer.send(f"USER {nickname} {username} {username} {nickname} {username} :{username}\r\n".encode('utf-8')) + writer.send(f"USER {username} {username} {username} :{username}\r\n".encode('utf-8')) + writer.send(f"NICK {nickname}\r\n".encode('utf-8')) + + self.Base.logs.debug('Link information sent to the server') + + return None + except AttributeError as ae: + self.Base.logs.critical(f'{ae}') + + def connect(self): + try: + + while self.signal: + try: + # 4072 max what the socket can grab + buffer_size = self.IrcSocket.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF) + data_in_bytes = self.IrcSocket.recv(buffer_size) + data = data_in_bytes.splitlines(True) + count_bytes = len(data_in_bytes) + + while count_bytes > 4070: + # If the received message is > 4070 then loop and add the value to the variable + new_data = self.IrcSocket.recv(buffer_size) + data_in_bytes += new_data + count_bytes = len(new_data) + + data = data_in_bytes.splitlines(True) + + if not data: + break + + self.parser(data) + except ssl.SSLEOFError as soe: + self.Base.logs.error(f"SSLEOFError __connect_to_irc: {soe} - {data}") + self.signal = False + except ssl.SSLError as se: + self.Base.logs.error(f"SSLError __connect_to_irc: {se} - {data}") + self.signal = False + except OSError as oe: + self.Base.logs.error(f"OSError __connect_to_irc: {oe} - {data}") + self.signal = False + + self.IrcSocket.shutdown(socket.SHUT_RDWR) + self.IrcSocket.close() + self.Base.logs.info("--> Clone Disconnected ...") + + except AssertionError as ae: + self.Base.logs.error(f'Assertion error : {ae}') + except ValueError as ve: + self.Base.logs.error(f'Value Error : {ve}') + except ssl.SSLEOFError as soe: + self.Base.logs.error(f"OS Error __connect_to_irc: {soe}") + except AttributeError as atte: + self.Base.logs.critical(f"{atte}") + except Exception as e: + self.Base.logs.error(f"Exception: {e}") + + def parser(self, cmd:list[bytes]): + try: + for data in cmd: + response = data.decode(self.CHARSET[0]).split() + self.signal = self.currentCloneObject.alive + # print(response) + + match response[0]: + case 'PING': + pong = str(response[1]).replace(':','') + self.send2socket(f"PONG :{pong}") + return None + case 'ERROR': + error_value = str(response[1]).replace(':','') + if error_value == 'Closing': + self.signal = False + + match response[1]: + case '376': + for channel in self.channels: + self.send2socket(f"JOIN {channel}") + return None + case 'PRIVMSG': + self.Base.logs.debug(response) + self.Base.logs.debug(f'{self.currentCloneObject.nickname} - {self.currentCloneObject.alive}') + fullname = str(response[0]).replace(':', '') + nickname = fullname.split('!')[0].replace(':','') + if nickname == self.Config.SERVICE_NICKNAME: + command = str(response[3]).replace(':','') + if command == 'KILL': + self.send2socket(f'QUIT :Thanks and goodbye') + self.signal = self.currentCloneObject.alive + if command == 'JOIN': + channel_to_join = str(response[4]) + self.send2socket(f"JOIN {channel_to_join}") + + except UnicodeEncodeError: + for data in cmd: + response = data.decode(self.CHARSET[1],'replace').split() + except UnicodeDecodeError: + for data in cmd: + response = data.decode(self.CHARSET[1],'replace').split() + except AssertionError as ae: + self.Base.logs.error(f"Assertion error : {ae}") + + def __ssl_context(self) -> ssl.SSLContext: + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + ctx.check_hostname = False + ctx.verify_mode = ssl.CERT_NONE + + self.Base.logs.debug(f'SSLContext initiated with verified mode {ctx.verify_mode}') + + return ctx \ No newline at end of file diff --git a/core/installation.py b/core/installation.py index 24e5076..90e48c6 100644 --- a/core/installation.py +++ b/core/installation.py @@ -1,51 +1,95 @@ from importlib.util import find_spec -from subprocess import check_call, run, CalledProcessError +from dataclasses import dataclass +from pathlib import Path +from subprocess import check_call, run, CalledProcessError, PIPE from platform import python_version, python_version_tuple from sys import exit import os class Install: + @dataclass + class CoreConfig: + unix_systemd_folder: str + service_file_name: str + service_cmd_executable: list + defender_main_executable: str + python_min_version: str + python_current_version_tuple: tuple[str, str, str] + python_current_version: str + defender_install_folder: str + venv_folder: str + venv_cmd_installation: list + venv_cmd_requirements: list + venv_pip_executable: str + venv_python_executable: str + def __init__(self) -> None: - self.PYTHON_MIN_VERSION = '3.10' - self.venv_folder_name = '.pyenv' - self.cmd_venv_command = ['python3', '-m', 'venv', self.venv_folder_name] - self.module_to_install = ['sqlalchemy','psutil','requests'] + self.set_configuration() + + if self.skip_install: + return None - if not self.checkPythonVersion(): + if not self.check_python_version(): # Tester si c'est la bonne version de python exit("Python Version Error") else: # Sinon tester les dependances python et les installer avec pip - self.checkDependencies() + if self.do_install(): + + self.install_dependencies() + + self.create_service_file() + + self.print_final_message() return None - def checkPythonVersion(self) -> bool: - """Test si la version de python est autorisée ou non + def set_configuration(self): - Returns: - bool: True si la version de python est autorisé sinon False - """ - # Current system version - sys_major, sys_minor, sys_patch = python_version_tuple() - - # min python version required - python_required_version = self.PYTHON_MIN_VERSION.split('.') - min_major, min_minor = tuple((python_required_version[0], python_required_version[1])) - - if int(sys_major) < int(min_major): - print(f"## Your python version must be greather than or equal to {self.PYTHON_MIN_VERSION} ##") + self.skip_install = False + # Exclude Windows OS + if os.name == 'nt': + #print('/!\\ Skip installation /!\\') + self.skip_install = True return False - elif (int(sys_major) <= int(min_major)) and (int(sys_minor) < int(min_minor)): - print(f"## Your python version must be greather than or equal to {self.PYTHON_MIN_VERSION} ##") - return False + defender_install_folder = os.getcwd() + venv_folder = '.pyenv' + unix_user_home_directory = os.path.expanduser("~") + unix_systemd_folder = os.path.join(unix_user_home_directory, '.config', 'systemd', 'user') + defender_main_executable = os.path.join(defender_install_folder, 'main.py') - print(f"===> Version of python : {python_version()} ==> OK") + self.config = self.CoreConfig( + unix_systemd_folder=unix_systemd_folder, + service_file_name='defender.service', + service_cmd_executable=['systemctl', '--user', 'start', 'defender'], + defender_main_executable=defender_main_executable, + python_min_version='3.10', + python_current_version_tuple=python_version_tuple(), + python_current_version=python_version(), + defender_install_folder=defender_install_folder, + venv_folder=venv_folder, + venv_cmd_installation=['python3', '-m', 'venv', venv_folder], + venv_cmd_requirements=['sqlalchemy','psutil','requests','faker'], + venv_pip_executable=f'{os.path.join(defender_install_folder, venv_folder, "bin")}{os.sep}pip', + venv_python_executable=f'{os.path.join(defender_install_folder, venv_folder, "bin")}{os.sep}python' + ) - return True + def do_install(self) -> bool: + + full_service_file_path = os.path.join(self.config.unix_systemd_folder, self.config.service_file_name) + + if not os.path.exists(full_service_file_path): + print(f'/!\\ Service file does not exist /!\\') + return True + + # Check if virtual env exist + if not os.path.exists(f'{os.path.join(self.config.defender_install_folder, self.config.venv_folder)}'): + self.run_subprocess(self.config.venv_cmd_installation) + print(f'/!\\ Virtual env does not exist run the install /!\\') + return True def run_subprocess(self, command:list) -> None: @@ -58,7 +102,41 @@ class Install: print(f"Try to install dependencies ...") exit(5) - def checkDependencies(self) -> None: + def check_python_version(self) -> bool: + """Test si la version de python est autorisée ou non + + Returns: + bool: True si la version de python est autorisé sinon False + """ + # Current system version + sys_major, sys_minor, sys_patch = self.config.python_current_version_tuple + + # min python version required + python_required_version = self.config.python_min_version.split('.') + min_major, min_minor = tuple((python_required_version[0], python_required_version[1])) + + if int(sys_major) < int(min_major): + print(f"## Your python version must be greather than or equal to {self.config.python_current_version} ##") + return False + + elif (int(sys_major) <= int(min_major)) and (int(sys_minor) < int(min_minor)): + print(f"## Your python version must be greather than or equal to {self.config.python_current_version} ##") + return False + + print(f"===> Version of python : {self.config.python_current_version} ==> OK") + + return True + + def check_package(self, package_name) -> bool: + + try: + # Run a command in the virtual environment's Python to check if the package is installed + run([self.config.venv_python_executable, '-c', f'import {package_name}'], check=True, stdout=PIPE, stderr=PIPE) + return True + except CalledProcessError: + return False + + def install_dependencies(self) -> None: """### Verifie les dépendances si elles sont installées - Test si les modules sont installés - Met a jour pip @@ -67,38 +145,71 @@ class Install: do_install = False # Check if virtual env exist - if not os.path.exists(f'{self.venv_folder_name}'): - self.run_subprocess(self.cmd_venv_command) + if not os.path.exists(f'{os.path.join(self.config.defender_install_folder, self.config.venv_folder)}'): + self.run_subprocess(self.config.venv_cmd_installation) do_install = True - for module in self.module_to_install: - if find_spec(module) is None: + for module in self.config.venv_cmd_requirements: + if not self.check_package(module): do_install = True if not do_install: return None print("===> Vider le cache de pip") - check_call(['pip','cache','purge']) + self.run_subprocess([self.config.venv_pip_executable, 'cache', 'purge']) print("===> Verifier si pip est a jour") - check_call(['python', '-m', 'pip', 'install', '--upgrade', 'pip']) + self.run_subprocess([self.config.venv_python_executable, '-m', 'pip', 'install', '--upgrade', 'pip']) if find_spec('greenlet') is None: - check_call(['pip','install', '--only-binary', ':all:', 'greenlet']) + self.run_subprocess([self.config.venv_pip_executable, 'install', '--only-binary', ':all:', 'greenlet']) print('====> Module Greenlet installé') - for module in self.module_to_install: - if find_spec(module) is None: + for module in self.config.venv_cmd_requirements: + if not self.check_package(module): print("### Trying to install missing python packages ###") - check_call(['pip','install', module]) + self.run_subprocess([self.config.venv_pip_executable, 'install', module]) print(f"====> Module {module} installé") else: print(f"==> {module} already installed") - print(f"#"*12) + def create_service_file(self) -> None: + + full_service_file_path = os.path.join(self.config.unix_systemd_folder, self.config.service_file_name) + + if os.path.exists(full_service_file_path): + print(f'/!\\ Service file already exist /!\\') + return None + + # Check if user systemd is available (.config/systemd/user/) + if not os.path.exists(self.config.unix_systemd_folder): + self.run_subprocess(['mkdir', '-p', self.config.unix_systemd_folder]) + + contain = f'''[Unit] +Description=Defender IRC Service + +[Service] +ExecStart={self.config.venv_python_executable} {self.config.defender_main_executable} +WorkingDirectory={self.config.defender_install_folder} +SyslogIdentifier=Defender +Restart=on-failure + +[Install] +WantedBy=multi-user.target + ''' + with open(full_service_file_path, 'w+') as servicefile: + servicefile.write(contain) + servicefile.close() + print(f'Service file generated with current configuration') + print(f'Running Defender IRC Service ...') + self.run_subprocess(self.config.service_cmd_executable) + + def print_final_message(self) -> None: + + print(f"#"*24) print("Installation complete ...") print("You must change environment using the command below") - print(f"source {self.venv_folder_name}{os.sep}bin{os.sep}activate") - print(f"#"*12) - exit(1) \ No newline at end of file + print(f"source {self.config.defender_install_folder}{os.sep}{self.config.venv_folder}{os.sep}bin{os.sep}activate") + print(f"#"*24) + exit(1) diff --git a/core/irc.py b/core/irc.py index ae43358..794e3ca 100644 --- a/core/irc.py +++ b/core/irc.py @@ -3,7 +3,7 @@ from ssl import SSLSocket from datetime import datetime, timedelta from typing import Union from core.loadConf import Config -from core.Model import User, Admin, Channel +from core.Model import User, Admin, Channel, Clones from core.base import Base class Irc: @@ -12,7 +12,6 @@ class Irc: self.defender_connexion_datetime = datetime.now() # Date et heure de la premiere connexion de Defender self.first_score: int = 100 - self.db_chan = [] # Definir la variable qui contiendra la liste des salons self.loaded_classes:dict[str, 'Irc'] = {} # Definir la variable qui contiendra la liste modules chargés self.beat = 30 # Lancer toutes les 30 secondes des actions de nettoyages self.hb_active = True # Heartbeat active @@ -44,6 +43,9 @@ class Irc: self.User = User(self.Base) self.Admin = Admin(self.Base) self.Channel = Channel(self.Base) + self.Clones = Clones(self.Base) + + self.__create_table() self.Base.create_thread(func=self.heartbeat, func_args=(self.beat, )) ############################################## @@ -90,7 +92,7 @@ class Irc: except OSError as oe: self.Base.logs.critical(f"OSError __create_socket: {oe} - {soc.fileno()}") except AttributeError as ae: - self.Base.logs.critical(f"OSError __create_socket: {oe} - {soc.fileno()}") + self.Base.logs.critical(f"AttributeError __create_socket: {ae} - {soc.fileno()}") def __ssl_context(self) -> ssl.SSLContext: ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) @@ -107,6 +109,7 @@ class Irc: self.__link(self.IrcSocket) # établir la connexion au serveur IRC self.signal = True # Une variable pour initier la boucle infinie self.load_existing_modules() # Charger les modules existant dans la base de données + self.__join_saved_channels() # Join existing channels while self.signal: try: @@ -158,17 +161,18 @@ class Irc: self.IrcSocket.shutdown(socket.SHUT_RDWR) self.IrcSocket.close() self.Base.logs.info("--> Fermeture de Defender ...") + sys.exit(0) except AssertionError as ae: - self.Base.logs.error(f'Assertion error : {ae}') + self.Base.logs.error(f'AssertionError: {ae}') except ValueError as ve: - self.Base.logs.error(f'Value Error : {ve}') + self.Base.logs.error(f'ValueError: {ve}') except ssl.SSLEOFError as soe: - self.Base.logs.error(f"OS Error __connect_to_irc: {soe}") + self.Base.logs.error(f"SSLEOFError: {soe}") except AttributeError as atte: - self.Base.logs.critical(f"{atte}") - # except Exception as e: - # self.debug(f"Exception: {e}") + self.Base.logs.critical(f"AttributeError: {atte}") + except Exception as e: + self.Base.logs.critical(f"Exception: {e}") def __link(self, writer:Union[socket.socket, SSLSocket]) -> None: """Créer le link et envoyer les informations nécessaires pour la @@ -199,7 +203,8 @@ class Irc: # Envoyer un message d'identification writer.send(f":{sid} PASS :{password}\r\n".encode('utf-8')) - writer.send(f":{sid} PROTOCTL NICKv2 VHP UMODE2 NICKIP SJOIN SJOIN2 SJ3 NOQUIT TKLEXT MLOCK SID MTAGS\r\n".encode('utf-8')) + writer.send(f":{sid} PROTOCTL SID NOQUIT NICKv2 SJOIN SJ3 NICKIP TKLEXT2 NEXTBANS CLK EXTSWHOIS MLOCK MTAGS\r\n".encode('utf-8')) + # writer.send(f":{sid} PROTOCTL NICKv2 VHP UMODE2 NICKIP SJOIN SJOIN2 SJ3 NOQUIT TKLEXT MLOCK SID MTAGS\r\n".encode('utf-8')) writer.send(f":{sid} PROTOCTL EAUTH={link},,,{service_name}-v{version}\r\n".encode('utf-8')) writer.send(f":{sid} PROTOCTL SID={sid}\r\n".encode('utf-8')) writer.send(f":{sid} SERVER {link} 1 :{info}\r\n".encode('utf-8')) @@ -207,7 +212,7 @@ class Irc: writer.send(f":{sid} UID {nickname} 1 {unixtime} {username} {host} {service_id} * {smodes} * * * :{realname}\r\n".encode('utf-8')) writer.send(f":{sid} SJOIN {unixtime} {chan} + :{service_id}\r\n".encode('utf-8')) writer.send(f":{sid} MODE {chan} +{cmodes}\r\n".encode('utf-8')) - writer.send(f":{service_id} SAMODE {chan} +{umodes} {nickname}\r\n".encode('utf-8')) + writer.send(f":{sid} SAMODE {chan} +{umodes} {nickname}\r\n".encode('utf-8')) self.Base.logs.debug('Link information sent to the server') @@ -215,6 +220,19 @@ class Irc: except AttributeError as ae: self.Base.logs.critical(f'{ae}') + def __join_saved_channels(self) -> None: + + core_table = 'core_channel' + + query = f'''SELECT distinct channel_name FROM {core_table}''' + exec_query = self.Base.db_execute_query(query) + result_query = exec_query.fetchall() + + if result_query: + for chan_name in result_query: + chan = chan_name[0] + self.send2socket(f":{self.Config.SERVEUR_ID} SJOIN {self.Base.get_unixtime()} {chan} + :{self.Config.SERVICE_ID}") + def send2socket(self, send_message:str) -> None: """Envoit les commandes à envoyer au serveur. @@ -268,13 +286,18 @@ class Irc: # FIN CONNEXION IRC # ############################################## + def __create_table(self): + """## Create core tables + """ + pass + def load_existing_modules(self) -> None: """Charge les modules qui existe déja dans la base de données Returns: None: Aucun retour requis, elle charge puis c'est tout """ - result = self.Base.db_execute_query(f"SELECT module FROM {self.Config.table_module}") + result = self.Base.db_execute_query(f"SELECT module_name FROM {self.Config.table_module}") for r in result.fetchall(): self.load_module('sys', r[0], True) @@ -663,7 +686,6 @@ class Irc: else: version = f'{current_version}' - self.send2socket(f"MODE {self.Config.SERVICE_NICKNAME} +B") self.send2socket(f"JOIN {self.Config.SERVICE_CHANLOG}") print(f"################### DEFENDER ###################") print(f"# SERVICE CONNECTE ") @@ -819,8 +841,10 @@ class Irc: try: # Supprimer la premiere valeur cmd.pop(0) - get_uid_or_nickname = str(cmd[0].replace(':','')) + user_trigger = self.User.get_nickname(get_uid_or_nickname) + dnickname = self.Config.SERVICE_NICKNAME + if len(cmd) == 6: if cmd[1] == 'PRIVMSG' and str(cmd[3]).replace('.','') == ':auth': cmd_copy = cmd.copy() @@ -830,10 +854,6 @@ class Irc: self.Base.logs.info(cmd) else: self.Base.logs.info(f'{cmd}') - # user_trigger = get_user.split('!')[0] - # user_trigger = self.get_nickname(get_uid_or_nickname) - user_trigger = self.User.get_nickname(get_uid_or_nickname) - dnickname = self.Config.SERVICE_NICKNAME pattern = fr'(:\{self.Config.SERVICE_PREFIX})(.*)$' hcmds = re.search(pattern, ' '.join(cmd)) # va matcher avec tout les caractéres aprés le . @@ -850,7 +870,8 @@ class Irc: cmd_to_send = convert_to_string.replace(':','') self.Base.log_cmd(user_trigger, cmd_to_send) - self._hcmds(user_trigger, arg, cmd) + fromchannel = str(cmd[2]).lower() if self.Base.Is_Channel(cmd[2]) else None + self._hcmds(user_trigger, fromchannel, arg, cmd) if cmd[2] == self.Config.SERVICE_ID: pattern = fr'^:.*?:(.*)$' @@ -888,7 +909,11 @@ class Irc: cmd_to_send = convert_to_string.replace(':','') self.Base.log_cmd(self.User.get_nickname(user_trigger), cmd_to_send) - self._hcmds(user_trigger, arg, cmd) + fromchannel = None + if len(arg) >= 2: + fromchannel = str(arg[1]).lower() if self.Base.Is_Channel(arg[1]) else None + + self._hcmds(user_trigger, fromchannel, arg, cmd) except IndexError as io: self.Base.logs.error(f'{io}') @@ -904,7 +929,7 @@ class Irc: except IndexError as ie: self.Base.logs.error(f"{ie} / {cmd} / length {str(len(cmd))}") - def _hcmds(self, user: str, cmd:list, fullcmd: list = []) -> None: + def _hcmds(self, user: str, channel: Union[str, None], cmd:list, fullcmd: list = []) -> None: fromuser = self.User.get_nickname(user) # Nickname qui a lancé la commande uid = self.User.get_uid(fromuser) # Récuperer le uid de l'utilisateur @@ -925,7 +950,7 @@ class Irc: # Envoyer la commande aux classes dynamiquement chargées if command != 'notallowed': for classe_name, classe_object in self.loaded_classes.items(): - classe_object._hcmds(user, cmd, fullcmd) + classe_object._hcmds(user, channel, cmd, fullcmd) match command: @@ -1229,9 +1254,6 @@ class Irc: reason.append(cmd[i]) final_reason = ' '.join(reason) - # self.db_uid.clear() #Vider UID_DB - # self.db_chan = [] #Vider les salons - self.User.UID_DB.clear() # Clear User Object self.Channel.UID_CHANNEL_DB.clear() # Clear Channel Object @@ -1250,7 +1272,7 @@ class Irc: self.Base.logs.debug(self.loaded_classes) all_modules = self.Base.get_all_modules() - results = self.Base.db_execute_query(f'SELECT module FROM {self.Config.table_module}') + results = self.Base.db_execute_query(f'SELECT module_name FROM {self.Config.table_module}') results = results.fetchall() # if len(results) == 0: @@ -1277,17 +1299,15 @@ class Irc: case 'show_timers': if self.Base.running_timers: - self.send2socket(f":{dnickname} PRIVMSG {dchanlog} :{self.Base.running_timers}") + for the_timer in self.Base.running_timers: + self.send2socket(f":{dnickname} NOTICE {fromuser} :>> {the_timer.getName()} - {the_timer.is_alive()}") else: - self.send2socket(f":{dnickname} PRIVMSG {dchanlog} :Aucun timers en cours d'execution") + self.send2socket(f":{dnickname} NOTICE {fromuser} :Aucun timers en cours d'execution") case 'show_threads': - running_thread_name:list = [] for thread in self.Base.running_threads: - running_thread_name.append(f"{thread.getName()} ({thread.is_alive()})") - - self.send2socket(f":{dnickname} PRIVMSG {dchanlog} :{str(running_thread_name)}") + self.send2socket(f":{dnickname} NOTICE {fromuser} :>> {thread.getName()} ({thread.is_alive()})") case 'show_channels': diff --git a/core/loadConf.py b/core/loadConf.py index c078939..79557b7 100644 --- a/core/loadConf.py +++ b/core/loadConf.py @@ -1,4 +1,4 @@ -import json +import json, sys from os import sep from typing import Union from dataclasses import dataclass, field @@ -52,6 +52,8 @@ class ConfigDataModel: table_commande: str table_log: str table_module: str + table_config: str + table_channel: str current_version: str latest_version: str @@ -70,16 +72,21 @@ class Config: return None def __load_json_service_configuration(self): + try: + conf_filename = f'core{sep}configuration.json' + with open(conf_filename, 'r') as configuration_data: + configuration:dict[str, Union[str, int, list, dict]] = json.load(configuration_data) - conf_filename = f'core{sep}configuration.json' - with open(conf_filename, 'r') as configuration_data: - configuration:dict[str, Union[str, int, list, dict]] = json.load(configuration_data) + for key, value in configuration['CONFIG_COLOR'].items(): + configuration['CONFIG_COLOR'][key] = str(value).encode('utf-8').decode('unicode_escape') + + return configuration + + except FileNotFoundError as fe: + print(f'FileNotFound: {fe}') + print('Configuration file not found please create core/configuration.json') + sys.exit(0) - for key, value in configuration['CONFIG_COLOR'].items(): - configuration['CONFIG_COLOR'][key] = str(value).encode('utf-8').decode('unicode_escape') - - return configuration - def __load_service_configuration(self) -> ConfigDataModel: import_config = self.__load_json_service_configuration() @@ -113,10 +120,12 @@ class Config: GLINE_DURATION=import_config["GLINE_DURATION"], DEBUG_LEVEL=import_config["DEBUG_LEVEL"], CONFIG_COLOR=import_config["CONFIG_COLOR"], - table_admin='sys_admins', - table_commande='sys_commandes', - table_log='sys_logs', - table_module='sys_modules', + table_admin='core_admin', + table_commande='core_command', + table_log='core_log', + table_module='core_module', + table_config='core_config', + table_channel='core_channel', current_version='', latest_version='', db_name='defender', diff --git a/mods/mod_clone.py b/mods/mod_clone.py new file mode 100644 index 0000000..0446285 --- /dev/null +++ b/mods/mod_clone.py @@ -0,0 +1,262 @@ +from dataclasses import dataclass, fields, field +import random, faker, time +from datetime import datetime +from typing import Union +from core.irc import Irc +from core.connection import Connection + +class Clone(): + + @dataclass + class ModConfModel: + clone_nicknames: list[str] + + def __init__(self, ircInstance:Irc) -> None: + + # Module name (Mandatory) + self.module_name = 'mod_' + str(self.__class__.__name__).lower() + + # Add Irc Object to the module (Mandatory) + self.Irc = ircInstance + + # Add Global Configuration to the module (Mandatory) + self.Config = ircInstance.Config + + # Add Base object to the module (Mandatory) + self.Base = ircInstance.Base + + # Add logs object to the module (Mandatory) + self.Logs = ircInstance.Base.logs + + # Add User object to the module (Mandatory) + self.User = ircInstance.User + + # Add Channel object to the module (Mandatory) + self.Channel = ircInstance.Channel + + self.Clone = ircInstance.Clones + + # Créer les nouvelles commandes du module + self.commands_level = { + 1: ['clone_connect', 'clone_join', 'clone_kill', 'clone_list'] + } + + # Init the module (Mandatory) + self.__init_module() + + # Log the module + self.Logs.debug(f'Module {self.module_name} loaded ...') + + def __init_module(self) -> None: + + # Enrigstrer les nouvelles commandes dans le code + self.__set_commands(self.commands_level) + + # Créer les tables necessaire a votre module (ce n'es pas obligatoire) + self.__create_tables() + + # Load module configuration (Mandatory) + self.__load_module_configuration() + + def __set_commands(self, commands:dict[int, list[str]]) -> None: + """### Rajoute les commandes du module au programme principal + + Args: + commands (list): Liste des commandes du module + """ + for level, com in commands.items(): + for c in commands[level]: + if not c in self.Irc.commands: + self.Irc.commands_level[level].append(c) + self.Irc.commands.append(c) + + 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_channel = '''CREATE TABLE IF NOT EXISTS clone_list ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + datetime TEXT, + nickname TEXT, + username TEXT + ) + ''' + + self.Base.db_execute_query(table_channel) + + return None + + def __load_module_configuration(self) -> None: + """### Load Module Configuration + """ + try: + # Variable qui va contenir les options de configuration du module Defender + self.ModConfig = self.ModConfModel( + clone_nicknames=[] + ) + + # 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 unload(self) -> None: + """Cette methode sera executée a chaque désactivation ou + rechargement de module + """ + + # kill all clones before unload + for clone in self.ModConfig.clone_nicknames: + self.Irc.send2socket(f':{self.Config.SERVICE_NICKNAME} PRIVMSG {clone} :KILL') + + return None + + def thread_create_clones(self, nickname: str, username: str, channels: list, server_port: int, ssl: bool) -> None: + + Connection(server_port=server_port, nickname=nickname, username=username, channels=channels, CloneObject=self.Clone, ssl=ssl) + + return None + + def thread_join_channels(self, channel_name: str, wait: float, clone_name:str = None): + + if clone_name is None: + for clone in self.Clone.UID_CLONE_DB: + self.Irc.send2socket(f':{self.Config.SERVICE_NICKNAME} PRIVMSG {clone.nickname} :JOIN {channel_name}') + time.sleep(wait) + else: + for clone in self.Clone.UID_CLONE_DB: + if clone_name == clone.nickname: + self.Irc.send2socket(f':{self.Config.SERVICE_NICKNAME} PRIVMSG {clone.nickname} :JOIN {channel_name}') + time.sleep(wait) + + def generate_names(self) -> tuple[str, str]: + try: + fake = faker.Faker('en_GB') + nickname = fake.first_name() + username = fake.last_name() + + if self.Clone.exists(nickname=nickname): + caracteres = '0123456789' + randomize = ''.join(random.choice(caracteres) for _ in range(2)) + nickname = nickname + str(randomize) + self.Clone.insert( + self.Clone.CloneModel(alive=True, nickname=nickname, username=username) + ) + else: + self.Clone.insert( + self.Clone.CloneModel(alive=True, nickname=nickname, username=username) + ) + + # if not nickname in self.ModConfig.clone_nicknames: + # self.ModConfig.clone_nicknames.append(nickname) + # else: + # caracteres = '0123456789' + # randomize = ''.join(random.choice(caracteres) for _ in range(2)) + # nickname = nickname + str(randomize) + # self.ModConfig.clone_nicknames.append(nickname) + + return (nickname, username) + + except AttributeError as ae: + self.Logs.error(f'Attribute Error : {ae}') + except Exception as err: + self.Logs.error(err) + + def cmd(self, data:list) -> None: + + service_id = self.Config.SERVICE_ID # Defender serveur id + cmd = list(data).copy() + + if len(cmd) < 2: + return None + + match cmd[1]: + + case 'REPUTATION': + pass + + def _hcmds(self, user:str, channel: any, cmd: list, fullcmd: list = []) -> None: + + command = str(cmd[0]).lower() + fromuser = user + + dnickname = self.Config.SERVICE_NICKNAME # Defender nickname + + match command: + + case 'clone_connect': + # clone_connect 25 + try: + number_of_clones = int(cmd[1]) + for i in range(number_of_clones): + nickname, username = self.generate_names() + self.Base.create_thread( + self.thread_create_clones, + (nickname, username, [], 6697, True) + ) + + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} :{str(number_of_clones)} clones joined the network') + + except Exception as err: + self.Logs.error(f'{err}') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} :/msg {dnickname} clone_connect [number of clone you want to connect]') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} :Exemple /msg {dnickname} clone_kill 6') + + case 'clone_kill': + try: + clone_name = str(cmd[1]) + clone_to_kill: list[str] = [] + + if clone_name.lower() == 'all': + for clone in self.Clone.UID_CLONE_DB: + self.Irc.send2socket(f':{dnickname} PRIVMSG {clone.nickname} :KILL') + clone_to_kill.append(clone.nickname) + clone.alive = False + + for clone_nickname in clone_to_kill: + self.Clone.delete(clone_nickname) + + del clone_to_kill + + else: + if self.Clone.exists(clone_name): + self.Irc.send2socket(f':{dnickname} PRIVMSG {clone_name} :KILL') + self.Clone.kill(clone_name) + self.Clone.delete(clone_name) + + except Exception as err: + self.Logs.error(f'{err}') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} :/msg {dnickname} clone_kill all') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} :/msg {dnickname} clone_kill [clone_name]') + + case 'clone_join': + try: + # clone_join nickname #channel + clone_name = str(cmd[1]) + clone_channel_to_join = cmd[2] + + if clone_name.lower() == 'all': + self.Base.create_thread(self.thread_join_channels, (clone_channel_to_join, 2)) + else: + self.Base.create_thread(self.thread_join_channels, (clone_channel_to_join, 2, clone_name)) + + except Exception as err: + self.Logs.error(f'{err}') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} :/msg {dnickname} clone_join all #channel') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} :/msg {dnickname} clone_join clone_nickname #channel') + + case 'clone_list': + + for clone_name in self.Clone.UID_CLONE_DB: + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} :>> {clone_name.nickname} | {clone_name.username}') diff --git a/mods/mod_command.py b/mods/mod_command.py new file mode 100644 index 0000000..cba38ef --- /dev/null +++ b/mods/mod_command.py @@ -0,0 +1,493 @@ +from dataclasses import dataclass, fields +from core.irc import Irc + +class Command(): + + @dataclass + class ModConfModel: + """The Model containing the module parameters + """ + pass + + def __init__(self, ircInstance:Irc) -> None: + + # Module name (Mandatory) + self.module_name = 'mod_' + str(self.__class__.__name__).lower() + + # Add Irc Object to the module (Mandatory) + self.Irc = ircInstance + + # Add Global Configuration to the module (Mandatory) + self.Config = ircInstance.Config + + # Add Base object to the module (Mandatory) + self.Base = ircInstance.Base + + # Add logs object to the module (Mandatory) + self.Logs = ircInstance.Base.logs + + # Add User object to the module (Mandatory) + self.User = ircInstance.User + + # Add Channel object to the module (Mandatory) + self.Channel = ircInstance.Channel + + # Create module commands (Mandatory) + self.commands_level = { + 1: ['join', 'part'], + 2: ['owner', 'deowner', 'op', 'deop', 'halfop', 'dehalfop', 'voice', 'devoice', 'ban', 'unban','kick', 'kickban', 'umode'] + } + + # Init the module + self.__init_module() + + # Log the module + self.Logs.debug(f'Module {self.module_name} loaded ...') + + def __init_module(self) -> None: + + # Insert module commands into the core one (Mandatory) + self.__set_commands(self.commands_level) + + # 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 # + + return None + + def __set_commands(self, commands:dict[int, list[str]]) -> None: + """### Rajoute les commandes du module au programme principal + + Args: + commands (list): Liste des commandes du module + """ + for level, com in commands.items(): + for c in commands[level]: + if not c in self.Irc.commands: + self.Irc.commands_level[level].append(c) + self.Irc.commands.append(c) + + 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_logs = '''CREATE TABLE IF NOT EXISTS test_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + datetime TEXT, + server_msg TEXT + ) + ''' + + self.Base.db_execute_query(table_logs) + return None + + def __load_module_configuration(self) -> None: + """### Load Module Configuration + """ + try: + # Build the default configuration model (Mandatory) + self.ModConfig = self.ModConfModel(param_exemple1='param value 1', param_exemple2=1) + + # 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: + + return None + + def add_defender_channel(self, channel:str) -> bool: + """Cette fonction ajoute les salons de join de Defender + + Args: + channel (str): le salon à enregistrer. + """ + mes_donnees = {'channel': channel} + response = self.Base.db_execute_query("SELECT id FROM def_channels WHERE channel = :channel", mes_donnees) + + isChannelExist = response.fetchone() + + if isChannelExist is None: + mes_donnees = {'datetime': self.Base.get_datetime(), 'channel': channel} + insert = self.Base.db_execute_query(f"INSERT INTO def_channels (datetime, channel) VALUES (:datetime, :channel)", mes_donnees) + if insert.rowcount >=0: + return True + else: + return False + else: + return False + + def delete_defender_channel(self, channel:str) -> bool: + """Cette fonction supprime les salons de join de Defender + + Args: + channel (str): le salon à enregistrer. + """ + mes_donnes = {'channel': channel} + response = self.Base.db_execute_query("DELETE FROM def_channels WHERE channel = :channel", mes_donnes) + + affected_row = response.rowcount + + if affected_row > 0: + return True + else: + return False + + def cmd(self, data:list) -> None: + + return None + + def _hcmds(self, user:str, channel: any, cmd: list, fullcmd: list = []) -> None: + + command = str(cmd[0]).lower() + dnickname = self.Config.SERVICE_NICKNAME + service_id = self.Config.SERVICE_ID + dchanlog = self.Config.SERVICE_CHANLOG + fromuser = user + fromchannel = channel + + match command: + + case 'op': + # /mode #channel +o user + # .op #channel user + # /msg dnickname op #channel user + # [':adator', 'PRIVMSG', '#services', ':.o', '#services', 'dktmb'] + try: + if fromchannel is None: + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} op [#SALON] [NICKNAME]') + return False + + if len(cmd) == 1: + self.Irc.send2socket(f":{dnickname} MODE {fromchannel} +o {fromuser}") + return True + + # deop nickname + if len(cmd) == 2: + nickname = cmd[1] + self.Irc.send2socket(f":{service_id} MODE {fromchannel} +o {nickname}") + return True + + nickname = cmd[2] + self.Irc.send2socket(f":{service_id} MODE {fromchannel} +o {nickname}") + + except IndexError as e: + self.Logs.warning(f'_hcmd OP: {str(e)}') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} op [#SALON] [NICKNAME]') + + case 'deop': + # /mode #channel -o user + # .deop #channel user + try: + if fromchannel is None: + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} deop [#SALON] [NICKNAME]') + return False + + if len(cmd) == 1: + self.Irc.send2socket(f":{service_id} MODE {fromchannel} -o {fromuser}") + return True + + # deop nickname + if len(cmd) == 2: + nickname = cmd[1] + self.Irc.send2socket(f":{service_id} MODE {fromchannel} -o {nickname}") + return True + + nickname = cmd[2] + self.Irc.send2socket(f":{service_id} MODE {fromchannel} -o {nickname}") + + except IndexError as e: + self.Logs.warning(f'_hcmd DEOP: {str(e)}') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} deop [#SALON] [NICKNAME]') + + case 'owner': + # /mode #channel +q user + # .owner #channel user + try: + if fromchannel is None: + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} owner [#SALON] [NICKNAME]') + return False + + if len(cmd) == 1: + self.Irc.send2socket(f":{service_id} MODE {fromchannel} +q {fromuser}") + return True + + # owner nickname + if len(cmd) == 2: + nickname = cmd[1] + self.Irc.send2socket(f":{service_id} MODE {fromchannel} +q {nickname}") + return True + + nickname = cmd[2] + self.Irc.send2socket(f":{service_id} MODE {fromchannel} +q {nickname}") + + except IndexError as e: + self.Logs.warning(f'_hcmd OWNER: {str(e)}') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} owner [#SALON] [NICKNAME]') + + case 'deowner': + # /mode #channel -q user + # .deowner #channel user + try: + if fromchannel is None: + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} deowner [#SALON] [NICKNAME]') + return False + + if len(cmd) == 1: + self.Irc.send2socket(f":{service_id} MODE {fromchannel} -q {fromuser}") + return True + + # deowner nickname + if len(cmd) == 2: + nickname = cmd[1] + self.Irc.send2socket(f":{service_id} MODE {fromchannel} -q {nickname}") + return True + + nickname = cmd[2] + self.Irc.send2socket(f":{service_id} MODE {fromchannel} -q {nickname}") + + except IndexError as e: + self.Logs.warning(f'_hcmd DEOWNER: {str(e)}') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} deowner [#SALON] [NICKNAME]') + + case 'halfop': + # /mode #channel +h user + # .halfop #channel user + try: + if fromchannel is None: + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} halfop [#SALON] [NICKNAME]') + return False + + if len(cmd) == 1: + self.Irc.send2socket(f":{service_id} MODE {fromchannel} +h {fromuser}") + return True + + # deop nickname + if len(cmd) == 2: + nickname = cmd[1] + self.Irc.send2socket(f":{service_id} MODE {fromchannel} +h {nickname}") + return True + + nickname = cmd[2] + self.Irc.send2socket(f":{service_id} MODE {fromchannel} +h {nickname}") + + except IndexError as e: + self.Logs.warning(f'_hcmd halfop: {str(e)}') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} halfop [#SALON] [NICKNAME]') + + case 'dehalfop': + # /mode #channel -h user + # .dehalfop #channel user + try: + if fromchannel is None: + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} dehalfop [#SALON] [NICKNAME]') + return False + + if len(cmd) == 1: + self.Irc.send2socket(f":{service_id} MODE {fromchannel} -h {fromuser}") + return True + + # dehalfop nickname + if len(cmd) == 2: + nickname = cmd[1] + self.Irc.send2socket(f":{service_id} MODE {fromchannel} -h {nickname}") + return True + + nickname = cmd[2] + self.Irc.send2socket(f":{service_id} MODE {fromchannel} -h {nickname}") + + except IndexError as e: + self.Logs.warning(f'_hcmd DEHALFOP: {str(e)}') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} dehalfop [#SALON] [NICKNAME]') + + case 'voice': + # /mode #channel +v user + # .voice #channel user + try: + if fromchannel is None: + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} voice [#SALON] [NICKNAME]') + return False + + if len(cmd) == 1: + self.Irc.send2socket(f":{service_id} MODE {fromchannel} +v {fromuser}") + return True + + # voice nickname + if len(cmd) == 2: + nickname = cmd[1] + self.Irc.send2socket(f":{service_id} MODE {fromchannel} +v {nickname}") + return True + + nickname = cmd[2] + self.Irc.send2socket(f":{service_id} MODE {fromchannel} +v {nickname}") + + except IndexError as e: + self.Logs.warning(f'_hcmd VOICE: {str(e)}') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} voice [#SALON] [NICKNAME]') + + case 'devoice': + # /mode #channel -v user + # .devoice #channel user + try: + if fromchannel is None: + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} devoice [#SALON] [NICKNAME]') + return False + + if len(cmd) == 1: + self.Irc.send2socket(f":{service_id} MODE {fromchannel} -v {fromuser}") + return True + + # dehalfop nickname + if len(cmd) == 2: + nickname = cmd[1] + self.Irc.send2socket(f":{service_id} MODE {fromchannel} -v {nickname}") + return True + + nickname = cmd[2] + self.Irc.send2socket(f":{service_id} MODE {fromchannel} -v {nickname}") + + except IndexError as e: + self.Logs.warning(f'_hcmd DEVOICE: {str(e)}') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} devoice [#SALON] [NICKNAME]') + + case 'ban': + # .ban #channel nickname + try: + sentchannel = str(cmd[1]) if self.Base.Is_Channel(cmd[1]) else None + if sentchannel is None: + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} ban [#SALON] [NICKNAME]') + return False + + nickname = cmd[2] + + self.Irc.send2socket(f":{service_id} MODE {sentchannel} +b {nickname}!*@*") + self.Logs.debug(f'{fromuser} has banned {nickname} from {sentchannel}') + except IndexError as e: + self.Logs.warning(f'_hcmd BAN: {str(e)}') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} ban [#SALON] [NICKNAME]') + + case 'unban': + # .unban #channel nickname + try: + sentchannel = str(cmd[1]) if self.Base.Is_Channel(cmd[1]) else None + if sentchannel is None: + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} ban [#SALON] [NICKNAME]') + return False + nickname = cmd[2] + + self.Irc.send2socket(f":{service_id} MODE {sentchannel} -b {nickname}!*@*") + self.Logs.debug(f'{fromuser} has unbanned {nickname} from {sentchannel}') + except IndexError as e: + self.Logs.warning(f'_hcmd UNBAN: {str(e)}') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} unban [#SALON] [NICKNAME]') + + case 'kick': + # .kick #channel nickname reason + try: + sentchannel = str(cmd[1]) if self.Base.Is_Channel(cmd[1]) else None + if sentchannel is None: + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} ban [#SALON] [NICKNAME]') + return False + nickname = cmd[2] + reason = [] + + for i in range(3, len(cmd)): + reason.append(cmd[i]) + + final_reason = ' '.join(reason) + + self.Irc.send2socket(f":{service_id} KICK {sentchannel} {nickname} {final_reason}") + self.Logs.debug(f'{fromuser} has kicked {nickname} from {sentchannel} : {final_reason}') + except IndexError as e: + self.Logs.warning(f'_hcmd KICK: {str(e)}') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} kick [#SALON] [NICKNAME] [REASON]') + + case 'kickban': + # .kickban #channel nickname reason + try: + sentchannel = str(cmd[1]) if self.Base.Is_Channel(cmd[1]) else None + if sentchannel is None: + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} ban [#SALON] [NICKNAME]') + return False + nickname = cmd[2] + reason = [] + + for i in range(3, len(cmd)): + reason.append(cmd[i]) + + final_reason = ' '.join(reason) + + self.Irc.send2socket(f":{service_id} KICK {sentchannel} {nickname} {final_reason}") + self.Irc.send2socket(f":{service_id} MODE {sentchannel} +b {nickname}!*@*") + self.Logs.debug(f'{fromuser} has kicked and banned {nickname} from {sentchannel} : {final_reason}') + except IndexError as e: + self.Logs.warning(f'_hcmd KICKBAN: {str(e)}') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} kickban [#SALON] [NICKNAME] [REASON]') + + case 'join': + + try: + sent_channel = str(cmd[1]) if self.Base.Is_Channel(cmd[1]) else None + if sent_channel is None: + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} :{self.Config.SERVICE_PREFIX}JOIN #channel') + return False + + self.Irc.send2socket(f':{service_id} JOIN {sent_channel}') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : {dnickname} JOINED {sent_channel}') + self.Base.db_query_channel('add', self.module_name, sent_channel) + + except IndexError as ie: + self.Logs.error(f'{ie}') + + case 'part': + + try: + sent_channel = str(cmd[1]) if self.Base.Is_Channel(cmd[1]) else None + if sent_channel is None: + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} :{self.Config.SERVICE_PREFIX}PART #channel') + return False + + if sent_channel == dchanlog: + self.Irc.send2socket(f":{dnickname} NOTICE {fromuser} : {dnickname} CAN'T LEFT {sent_channel} AS IT IS LOG CHANNEL") + return False + + self.Irc.send2socket(f':{service_id} PART {sent_channel}') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : {dnickname} LEFT {sent_channel}') + self.Base.db_query_channel('del', self.module_name, sent_channel) + + except IndexError as ie: + self.Logs.error(f'{ie}') + + case 'umode': + try: + # .umode nickname +mode + nickname = str(cmd[1]) + umode = str(cmd[2]) + + self.send2socket(f':{dnickname} SVSMODE {nickname} {umode}') + except KeyError as ke: + self.Base.logs.error(ke) \ No newline at end of file diff --git a/mods/mod_defender.py b/mods/mod_defender.py index 747c602..a442ac4 100644 --- a/mods/mod_defender.py +++ b/mods/mod_defender.py @@ -2,6 +2,7 @@ from dataclasses import dataclass, fields, field from datetime import datetime from typing import Union import re, socket, psutil, requests, json, time +from sys import exit from core.irc import Irc # Le module crée devra réspecter quelques conditions @@ -55,41 +56,91 @@ class Defender(): def __init__(self, ircInstance:Irc) -> None: - self.Irc = ircInstance # Ajouter l'object mod_irc a la classe ( Obligatoire ) - self.Config = ircInstance.Config # Ajouter la configuration a la classe ( Obligatoire ) - self.User = ircInstance.User # Importer les liste des User connectés ( Obligatoire ) - self.Channel = ircInstance.Channel # Ajouter la liste des salons ( Obligatoire ) - self.Base = ircInstance.Base # Ajouter l'objet Base au module ( Obligatoire ) - self.Logs = ircInstance.Base.logs # Ajouter l'objet log ( Obligatoire ) - self.timeout = self.Config.API_TIMEOUT # API Timeout + # Module name (Mandatory) + self.module_name = 'mod_' + str(self.__class__.__name__).lower() - self.freeipapi_remote_ip:list = [] # Liste qui va contenir les adresses ip a scanner avec freeipapi - self.cloudfilt_remote_ip:list = [] # Liste qui va contenir les adresses ip a scanner avec cloudfilt - self.abuseipdb_remote_ip:list = [] # Liste qui va contenir les adresses ip a scanner avec abuseipdb - self.psutil_remote_ip:list = [] # Liste qui va contenir les adresses ip a scanner avec psutil_scan - self.localscan_remote_ip:list = [] # Liste qui va contenir les adresses ip a scanner avec local_scan + # Add Irc Object to the module (Mandatory) + self.Irc = ircInstance - self.abuseipdb_isRunning:bool = True - self.freeipapi_isRunning:bool = True - self.cloudfilt_isRunning:bool = True - self.psutil_isRunning:bool = True - self.localscan_isRunning:bool = True - self.reputationTimer_isRunning:bool = True + # Add Global Configuration to the module (Mandatory) + self.Config = ircInstance.Config - self.Logs.info(f'Module {self.__class__.__name__} loaded ...') + # Add Base object to the module (Mandatory) + self.Base = ircInstance.Base - # Créer les nouvelles commandes du module + # Add logs object to the module (Mandatory) + self.Logs = ircInstance.Base.logs + + # Add User object to the module (Mandatory) + self.User = ircInstance.User + + # Add Channel object to the module (Mandatory) + self.Channel = ircInstance.Channel + + # Create module commands (Mandatory) self.commands_level = { 0: ['code'], - 1: ['join','part', 'info'], + 1: ['info'], 2: ['owner', 'deowner', 'op', 'deop', 'halfop', 'dehalfop', 'voice', 'devoice', 'ban', 'unban','kick', 'kickban'], 3: ['reputation','proxy_scan', 'flood', 'status', 'timer','show_reputation', 'sentinel'] } - self.__set_commands(self.commands_level) # Enrigstrer les nouvelles commandes dans le code - self.__create_tables() # Créer les tables necessaire a votre module (ce n'es pas obligatoire) + # Init the module (Mandatory) + self.__init_module() - self.init_defender() # Créer une methode init ( ce n'es pas obligatoire ) + # Log the module + self.Logs.debug(f'Module {self.module_name} loaded ...') + + def __init_module(self) -> None: + + # Insert module commands into the core one (Mandatory) + self.__set_commands(self.commands_level) + + # Create you own tables if needed (Mandatory) + self.__create_tables() + + # Load module configuration (Mandatory) + self.__load_module_configuration() + # End of mandatory methods you can start your customization # + + # # Rejoindre les salons + # self.join_saved_channels() + + self.timeout = self.Config.API_TIMEOUT + # Listes qui vont contenir les ip a scanner avec les différentes API + self.freeipapi_remote_ip:list = [] + self.cloudfilt_remote_ip:list = [] + self.abuseipdb_remote_ip:list = [] + self.psutil_remote_ip:list = [] + self.localscan_remote_ip:list = [] + + # Variables qui indique que les threads sont en cours d'éxecutions + self.abuseipdb_isRunning:bool = True + self.freeipapi_isRunning:bool = True + self.cloudfilt_isRunning:bool = True + self.psutil_isRunning:bool = True + self.localscan_isRunning:bool = True + self.reputationTimer_isRunning:bool = True + + # Variable qui va contenir les users + self.flood_system = {} + + # Contient les premieres informations de connexion + self.reputation_first_connexion = {'ip': '', 'score': -1} + + # Laisser vide si aucune clé + self.abuseipdb_key = '13c34603fee4d2941a2c443cc5c77fd750757ca2a2c1b304bd0f418aff80c24be12651d1a3cfe674' + self.cloudfilt_key = 'r1gEtjtfgRQjtNBDMxsg' + + # Démarrer les threads pour démarrer les api + self.Base.create_thread(func=self.thread_freeipapi_scan) + self.Base.create_thread(func=self.thread_cloudfilt_scan) + self.Base.create_thread(func=self.thread_abuseipdb_scan) + self.Base.create_thread(func=self.thread_local_scan) + self.Base.create_thread(func=self.thread_psutil_scan) + self.Base.create_thread(func=self.thread_reputation_timer) + + return None def __set_commands(self, commands:dict[int, list[str]]) -> None: """### Rajoute les commandes du module au programme principal @@ -105,24 +156,6 @@ class Defender(): return None - def unload(self) -> None: - """Cette methode sera executée a chaque désactivation ou - rechargement de module - """ - self.abuseipdb_remote_ip:list = [] # Liste qui va contenir les adresses ip a scanner avec abuseipdb - self.freeipapi_remote_ip:list = [] # Liste qui va contenir les adresses ip a scanner avec freeipapi - self.cloudfilt_remote_ip:list = [] # Liste qui va contenir les adresses ip a scanner avec cloudfilt - self.psutil_remote_ip:list = [] # Liste qui va contenir les adresses ip a scanner avec psutil_scan - self.localscan_remote_ip:list = [] # Liste qui va contenir les adresses ip a scanner avec local_scan - - self.abuseipdb_isRunning:bool = False - self.freeipapi_isRunning:bool = False - self.cloudfilt_isRunning:bool = False - self.psutil_isRunning:bool = False - self.localscan_isRunning:bool = False - self.reputationTimer_isRunning:bool = False - 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 @@ -157,141 +190,52 @@ class Defender(): ) ''' - self.Base.db_execute_query(table_channel) - self.Base.db_execute_query(table_config) - self.Base.db_execute_query(table_trusted) + # self.Base.db_execute_query(table_channel) + # self.Base.db_execute_query(table_config) + # self.Base.db_execute_query(table_trusted) return None - def init_defender(self) -> bool: - - # self.db_reputation = {} # Definir la variable qui contiendra la liste des user concerné par la réputation - self.flood_system = {} # Variable qui va contenir les users - self.reputation_first_connexion = {'ip': '', 'score': -1} # Contient les premieres informations de connexion - # 13c34603fee4d2941a2c443cc5c77fd750757ca2a2c1b304bd0f418aff80c24be12651d1a3cfe674 - self.abuseipdb_key = '13c34603fee4d2941a2c443cc5c77fd750757ca2a2c1b304bd0f418aff80c24be12651d1a3cfe674' # Laisser vide si aucune clé - # r1gEtjtfgRQjtNBDMxsg - self.cloudfilt_key = 'r1gEtjtfgRQjtNBDMxsg' # Laisser vide si aucune clé - - # Rejoindre les salons - self.join_saved_channels() - + def __load_module_configuration(self) -> None: + """### Load Module Configuration + """ try: # Variable qui va contenir les options de configuration du module Defender self.ModConfig = self.ModConfModel( - reputation=0, reputation_timer=0, reputation_seuil=26, reputation_score_after_release=27, + reputation=0, reputation_timer=1, reputation_seuil=26, reputation_score_after_release=27, reputation_ban_all_chan=0,reputation_sg=1, local_scan=0, psutil_scan=0, abuseipdb_scan=0, freeipapi_scan=0, cloudfilt_scan=0, flood=0, flood_message=5, flood_time=1, flood_timer=20 - ) + ) + + # 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) - # Logger en debug la variable de configuration - self.Logs.debug(self.ModConfig) + def __update_configuration(self, param_key: str, param_value: str): - # Syncrhoniser l'objet ModConfig avec la configuration de la base de données. - self.sync_db_configuration() + self.Base.db_update_core_config(self.module_name, self.ModConfig, param_key, param_value) - # Démarrer les threads pour démarrer les api - self.Base.create_thread(func=self.thread_freeipapi_scan) - self.Base.create_thread(func=self.thread_cloudfilt_scan) - self.Base.create_thread(func=self.thread_abuseipdb_scan) - self.Base.create_thread(func=self.thread_local_scan) - self.Base.create_thread(func=self.thread_psutil_scan) - self.Base.create_thread(func=self.thread_reputation_timer) - - return True - - def sync_db_configuration(self) -> None: - - query = "SELECT parameter, value FROM def_config" - response = self.Base.db_execute_query(query) - - result = response.fetchall() - - # Si le resultat ne contient aucune valeur - if not result: - # Base de données vide Inserer la premiere configuration - - for field in fields(self.ModConfig): - mes_donnees = {'datetime': self.Base.get_datetime(), 'parameter': field.name, 'value': getattr(self.ModConfig, field.name)} - insert = self.Base.db_execute_query('INSERT INTO def_config (datetime, parameter, value) VALUES (:datetime, :parameter, :value)', mes_donnees) - insert_rows = insert.rowcount - if insert_rows > 0: - self.Logs.debug(f'Row affected into def_config : {insert_rows}') - - # Inserer une nouvelle configuration - for field in fields(self.ModConfig): - mes_donnees = {'parameter': field.name} - search_param_query = "SELECT parameter, value FROM def_config WHERE parameter = :parameter" - result = self.Base.db_execute_query(search_param_query, mes_donnees) - isParamExist = result.fetchone() - - if isParamExist is None: - mes_donnees = {'datetime': self.Base.get_datetime(), 'parameter': field.name, 'value': getattr(self.ModConfig, field.name)} - insert = self.Base.db_execute_query('INSERT INTO def_config (datetime, parameter, value) VALUES (:datetime, :parameter, :value)', mes_donnees) - insert_rows = insert.rowcount - if insert_rows > 0: - self.Logs.debug(f'DB_Def_config - new param included : {insert_rows}') - - # Supprimer un parameter si il n'existe plus dans la variable global - query = "SELECT parameter FROM def_config" - response = self.Base.db_execute_query(query) - dbresult = response.fetchall() - - for dbparam in dbresult: - if not hasattr(self.ModConfig, dbparam[0]): - mes_donnees = {'parameter': dbparam[0]} - delete = self.Base.db_execute_query('DELETE FROM def_config WHERE parameter = :parameter', mes_donnees) - row_affected = delete.rowcount - if row_affected > 0: - self.Logs.debug(f'DB_Def_config - param [{dbparam[0]}] has been deleted') - - # Synchroniser la base de données avec la variable global - query = "SELECT parameter, value FROM def_config" - response = self.Base.db_execute_query(query) - result = response.fetchall() - - for param, value in result: - setattr(self.ModConfig, param, self.Base.int_if_possible(value)) - - self.Logs.debug(self.ModConfig) - return None - - def update_db_configuration(self, param:str, value:str) -> bool: - """Check the parameter if it exist and return True if success - - Args: - param (str): The parameter name - value (str): The value - - Returns: - bool: True if success or False + def unload(self) -> None: + """Cette methode sera executée a chaque désactivation ou + rechargement de module """ - response = False + self.abuseipdb_remote_ip:list = [] # Liste qui va contenir les adresses ip a scanner avec abuseipdb + self.freeipapi_remote_ip:list = [] # Liste qui va contenir les adresses ip a scanner avec freeipapi + self.cloudfilt_remote_ip:list = [] # Liste qui va contenir les adresses ip a scanner avec cloudfilt + self.psutil_remote_ip:list = [] # Liste qui va contenir les adresses ip a scanner avec psutil_scan + self.localscan_remote_ip:list = [] # Liste qui va contenir les adresses ip a scanner avec local_scan - # Check if the param exist - if not hasattr(self.ModConfig, param): - self.Logs.error(f"Le parametre {param} n'existe pas dans la variable global") - return response - - mes_donnees = {'parameter': param} - search_param_query = "SELECT parameter, value FROM def_config WHERE parameter = :parameter" - result = self.Base.db_execute_query(search_param_query, mes_donnees) - isParamExist = result.fetchone() - - if not isParamExist is None: - mes_donnees = {'datetime': self.Base.get_datetime(), 'parameter': param, 'value': value} - update = self.Base.db_execute_query('UPDATE def_config SET datetime = :datetime, value = :value WHERE parameter = :parameter', mes_donnees) - updated_rows = update.rowcount - if updated_rows > 0: - setattr(self.ModConfig, param, self.Base.int_if_possible(value)) - self.Logs.debug(f'DB_Def_config - new param updated : {param} {value}') - response = True - - self.Logs.debug(self.ModConfig) - - return response + self.abuseipdb_isRunning:bool = False + self.freeipapi_isRunning:bool = False + self.cloudfilt_isRunning:bool = False + self.psutil_isRunning:bool = False + self.localscan_isRunning:bool = False + self.reputationTimer_isRunning:bool = False + return None def add_defender_channel(self, channel:str) -> bool: """Cette fonction ajoute les salons de join de Defender @@ -328,14 +272,14 @@ class Defender(): return False def reputation_insert(self, reputationModel: ReputationModel) -> bool: - + response = False # Check if the user already exist for reputation in self.UID_REPUTATION_DB: if reputation.uid == reputationModel.uid: return response - + self.UID_REPUTATION_DB.append(reputationModel) self.Logs.debug(f'Reputation inserted: {reputationModel}') response = True @@ -1178,10 +1122,12 @@ class Defender(): self.Irc.send2socket(f":{service_id} MODE {chan.name} -b {final_nickname}!*@*") self.reputation_delete(final_UID) - def _hcmds(self, user:str, cmd: list, fullcmd: list = []) -> None: + def _hcmds(self, user:str, channel: any, cmd: list, fullcmd: list = []) -> None: command = str(cmd[0]).lower() fromuser = user + fromchannel = channel if self.Base.Is_Channel(channel) else None + channel = fromchannel dnickname = self.Config.SERVICE_NICKNAME # Defender nickname dchanlog = self.Config.SERVICE_CHANLOG # Defender chan log @@ -1190,23 +1136,6 @@ class Defender(): jail_chan = self.Config.SALON_JAIL # Salon pot de miel jail_chan_mode = self.Config.SALON_JAIL_MODES # Mode du salon "pot de miel" - if len(fullcmd) >= 3: - fromchannel = str(fullcmd[2]).lower() if self.Base.Is_Channel(str(fullcmd[2]).lower()) else None - else: - fromchannel = None - - if len(cmd) >= 2: - sentchannel = str(cmd[1]).lower() if self.Base.Is_Channel(str(cmd[1]).lower()) else None - else: - sentchannel = None - - if not fromchannel is None: - channel = fromchannel - elif not sentchannel is None: - channel = sentchannel - else: - channel = None - match command: case 'timer': @@ -1223,10 +1152,10 @@ class Defender(): case 'show_reputation': if not self.UID_REPUTATION_DB: - self.Irc.send2socket(f':{dnickname} PRIVMSG {dchanlog} : No one is suspected') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : No one is suspected') for suspect in self.UID_REPUTATION_DB: - self.Irc.send2socket(f':{dnickname} PRIVMSG {dchanlog} : Uid: {suspect.uid} | Nickname: {suspect.nickname} | Reputation: {suspect.score} | Secret code: {suspect.secret_code} | Connected on: {suspect.connected_datetime}') + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Uid: {suspect.uid} | Nickname: {suspect.nickname} | Reputation: {suspect.score} | Secret code: {suspect.secret_code} | Connected on: {suspect.connected_datetime}') case 'code': try: @@ -1294,7 +1223,9 @@ class Defender(): self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {self.Config.CONFIG_COLOR['verte']}REPUTATION{self.Config.CONFIG_COLOR['noire']} ] : Already activated") return False - self.update_db_configuration('reputation', 1) + # self.update_db_configuration('reputation', 1) + self.__update_configuration(key, 1) + self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {self.Config.CONFIG_COLOR['verte']}REPUTATION{self.Config.CONFIG_COLOR['noire']} ] : Activated by {fromuser}") self.Irc.send2socket(f":{service_id} JOIN {jail_chan}") self.Irc.send2socket(f":{service_id} SAMODE {jail_chan} +{dumodes} {dnickname}") @@ -1308,7 +1239,7 @@ class Defender(): self.Irc.send2socket(f":{service_id} MODE {chan.name} +e ~security-group:known-users") self.Irc.send2socket(f":{service_id} MODE {chan.name} +e ~security-group:websocket-users") - self.add_defender_channel(jail_chan) + self.Base.db_query_channel('add', self.module_name, jail_chan) if activation == 'off': @@ -1316,7 +1247,8 @@ class Defender(): self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {self.Config.CONFIG_COLOR['verte']}REPUTATION{self.Config.CONFIG_COLOR['noire']} ] : Already deactivated") return False - self.update_db_configuration('reputation', 0) + self.__update_configuration(key, 0) + self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {self.Config.CONFIG_COLOR['rouge']}REPUTATION{self.Config.CONFIG_COLOR['noire']} ] : Deactivated by {fromuser}") self.Irc.send2socket(f":{service_id} SAMODE {jail_chan} -{dumodes} {dnickname}") self.Irc.send2socket(f":{service_id} MODE {jail_chan} -sS") @@ -1329,7 +1261,7 @@ class Defender(): self.Irc.send2socket(f":{service_id} MODE {chan.name} -e ~security-group:known-users") self.Irc.send2socket(f":{service_id} MODE {chan.name} -e ~security-group:websocket-users") - self.delete_defender_channel(jail_chan) + self.Base.db_query_channel('del', self.module_name, jail_chan) if len_cmd == 4: get_set = str(cmd[1]).lower() @@ -1350,7 +1282,9 @@ class Defender(): self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {self.Config.CONFIG_COLOR['rouge']}BAN ON ALL CHANS{self.Config.CONFIG_COLOR['noire']} ] : Already activated") return False - self.update_db_configuration(key, 1) + # self.update_db_configuration(key, 1) + self.__update_configuration(key, 1) + self.Irc.send2socket(f':{dnickname} PRIVMSG {dchanlog} :[ {self.Config.CONFIG_COLOR["verte"]}BAN ON ALL CHANS{self.Config.CONFIG_COLOR["noire"]} ] : Activated by {fromuser}') elif get_value == 'off': @@ -1358,13 +1292,17 @@ class Defender(): self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {self.Config.CONFIG_COLOR['rouge']}BAN ON ALL CHANS{self.Config.CONFIG_COLOR['noire']} ] : Already deactivated") return False - self.update_db_configuration(key, 0) + # self.update_db_configuration(key, 0) + self.__update_configuration(key, 0) + self.Irc.send2socket(f':{dnickname} PRIVMSG {dchanlog} :[ {self.Config.CONFIG_COLOR["verte"]}BAN ON ALL CHANS{self.Config.CONFIG_COLOR["noire"]} ] : Deactivated by {fromuser}') case 'limit': reputation_seuil = int(cmd[3]) key = 'reputation_seuil' - self.update_db_configuration(key, reputation_seuil) + + # self.update_db_configuration(key, reputation_seuil) + self.__update_configuration(key, reputation_seuil) self.Irc.send2socket(f':{dnickname} PRIVMSG {dchanlog} :[ {self.Config.CONFIG_COLOR["verte"]}REPUTATION SEUIL{self.Config.CONFIG_COLOR["noire"]} ] : Limit set to {str(reputation_seuil)} by {fromuser}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Reputation set to {reputation_seuil}') @@ -1372,21 +1310,24 @@ class Defender(): case 'timer': reputation_timer = int(cmd[3]) key = 'reputation_timer' - self.update_db_configuration(key, reputation_timer) + self.__update_configuration(key, reputation_timer) + self.Irc.send2socket(f':{dnickname} PRIVMSG {dchanlog} :[ {self.Config.CONFIG_COLOR["verte"]}REPUTATION TIMER{self.Config.CONFIG_COLOR["noire"]} ] : Timer set to {str(reputation_timer)} minute(s) by {fromuser}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Reputation set to {reputation_timer}') case 'score_after_release': reputation_score_after_release = int(cmd[3]) key = 'reputation_score_after_release' - self.update_db_configuration(key, reputation_score_after_release) + self.__update_configuration(key, reputation_score_after_release) + self.Irc.send2socket(f':{dnickname} PRIVMSG {dchanlog} :[ {self.Config.CONFIG_COLOR["verte"]}REPUTATION SCORE AFTER RELEASE{self.Config.CONFIG_COLOR["noire"]} ] : Reputation score after release set to {str(reputation_score_after_release)} by {fromuser}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Reputation score after release set to {reputation_score_after_release}') case 'security_group': reputation_sg = int(cmd[3]) key = 'reputation_sg' - self.update_db_configuration(key, reputation_sg) + self.__update_configuration(key, reputation_sg) + self.Irc.send2socket(f':{dnickname} PRIVMSG {dchanlog} :[ {self.Config.CONFIG_COLOR["verte"]}REPUTATION SECURITY-GROUP{self.Config.CONFIG_COLOR["noire"]} ] : Reputation Security-group set to {str(reputation_sg)} by {fromuser}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Reputation score after release set to {reputation_sg}') @@ -1408,7 +1349,7 @@ class Defender(): self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} reputation set action [kill|None]') except ValueError as ve: - self.Logs.warning(f'{ie}') + self.Logs.warning(f'{ve}') self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : La valeur devrait etre un entier >= 0') case 'proxy_scan': @@ -1440,13 +1381,17 @@ class Defender(): if self.ModConfig.local_scan == 1: self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Already activated") return None - self.update_db_configuration(option, 1) + + self.__update_configuration(option, 1) + self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Activated by {fromuser}") elif action == 'off': if self.ModConfig.local_scan == 0: self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Already Deactivated") return None - self.update_db_configuration(option, 0) + + self.__update_configuration(option, 0) + self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Deactivated by {fromuser}") case 'psutil_scan': @@ -1454,13 +1399,17 @@ class Defender(): if self.ModConfig.psutil_scan == 1: self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Already activated") return None - self.update_db_configuration(option, 1) + + self.__update_configuration(option, 1) + self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Activated by {fromuser}") elif action == 'off': if self.ModConfig.psutil_scan == 0: self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Already Deactivated") return None - self.update_db_configuration(option, 0) + + self.__update_configuration(option, 0) + self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Deactivated by {fromuser}") case 'abuseipdb_scan': @@ -1468,13 +1417,17 @@ class Defender(): if self.ModConfig.abuseipdb_scan == 1: self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Already activated") return None - self.update_db_configuration(option, 1) + + self.__update_configuration(option, 1) + self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Activated by {fromuser}") elif action == 'off': if self.ModConfig.abuseipdb_scan == 0: self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Already Deactivated") return None - self.update_db_configuration(option, 0) + + self.__update_configuration(option, 0) + self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Deactivated by {fromuser}") case 'freeipapi_scan': @@ -1482,7 +1435,8 @@ class Defender(): if self.ModConfig.freeipapi_scan == 1: self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Already activated") return None - self.update_db_configuration(option, 1) + + self.__update_configuration(option, 1) self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Activated by {fromuser}") elif action == 'off': @@ -1490,7 +1444,7 @@ class Defender(): self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Already Deactivated") return None - self.update_db_configuration(option, 0) + self.__update_configuration(option, 0) self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Deactivated by {fromuser}") @@ -1499,13 +1453,17 @@ class Defender(): if self.ModConfig.cloudfilt_scan == 1: self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Already activated") return None - self.update_db_configuration(option, 1) + + self.__update_configuration(option, 1) + self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Activated by {fromuser}") elif action == 'off': if self.ModConfig.cloudfilt_scan == 0: self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Already Deactivated") return None - self.update_db_configuration(option, 0) + + self.__update_configuration(option, 0) + self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Deactivated by {fromuser}") case _: @@ -1537,7 +1495,8 @@ class Defender(): self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {self.Config.CONFIG_COLOR['verte']}FLOOD{self.Config.CONFIG_COLOR['noire']} ] : Already activated") return False - self.update_db_configuration(key, 1) + self.__update_configuration(key, 1) + self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {self.Config.CONFIG_COLOR['verte']}FLOOD{self.Config.CONFIG_COLOR['noire']} ] : Activated by {fromuser}") if activation == 'off': @@ -1545,7 +1504,8 @@ class Defender(): self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {self.Config.CONFIG_COLOR['rouge']}FLOOD{self.Config.CONFIG_COLOR['noire']} ] : Already Deactivated") return False - self.update_db_configuration(key, 0) + self.__update_configuration(key, 0) + self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {self.Config.CONFIG_COLOR['verte']}FLOOD{self.Config.CONFIG_COLOR['noire']} ] : Deactivated by {fromuser}") if len_cmd == 4: @@ -1556,20 +1516,22 @@ class Defender(): case 'flood_message': key = 'flood_message' set_value = int(cmd[3]) - print(f"{str(set_value)} - {set_key}") - self.update_db_configuration(key, set_value) + self.__update_configuration(key, set_value) + self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {self.Config.CONFIG_COLOR['verte']}FLOOD{self.Config.CONFIG_COLOR['noire']} ] : Flood message set to {set_value} by {fromuser}") case 'flood_time': key = 'flood_time' set_value = int(cmd[3]) - self.update_db_configuration(key, set_value) + self.__update_configuration(key, set_value) + self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {self.Config.CONFIG_COLOR['verte']}FLOOD{self.Config.CONFIG_COLOR['noire']} ] : Flood time set to {set_value} by {fromuser}") case 'flood_timer': key = 'flood_timer' set_value = int(cmd[3]) - self.update_db_configuration(key, set_value) + self.__update_configuration(key, set_value) + self.Irc.send2socket(f":{dnickname} PRIVMSG {dchanlog} :[ {self.Config.CONFIG_COLOR['verte']}FLOOD{self.Config.CONFIG_COLOR['noire']} ] : Flood timer set to {set_value} by {fromuser}") case _: @@ -1602,293 +1564,6 @@ class Defender(): except KeyError as ke: self.Logs.error(f"Key Error : {ke}") - case 'join': - - try: - channel = cmd[1] - self.Irc.send2socket(f':{service_id} JOIN {channel}') - self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : {dnickname} JOINED {channel}') - self.add_defender_channel(channel) - except IndexError as ie: - self.Logs.error(f'{ie}') - - case 'part': - - try: - channel = cmd[1] - if channel == dchanlog: - self.Irc.send2socket(f":{dnickname} NOTICE {fromuser} : {dnickname} CAN'T LEFT {channel} AS IT IS LOG CHANNEL") - return False - - self.Irc.send2socket(f':{service_id} PART {channel}') - self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : {dnickname} LEFT {channel}') - self.delete_defender_channel(channel) - except IndexError as ie: - self.Logs.error(f'{ie}') - - case 'op': - # /mode #channel +o user - # .op #channel user - # /msg dnickname op #channel user - # [':adator', 'PRIVMSG', '#services', ':.o', '#services', 'dktmb'] - try: - if channel is None: - self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} op [#SALON] [NICKNAME]') - return False - - if len(cmd) == 1: - self.Irc.send2socket(f":{service_id} MODE {channel} +o {fromuser}") - return True - - # deop nickname - if len(cmd) == 2: - nickname = cmd[1] - self.Irc.send2socket(f":{service_id} MODE {channel} +o {nickname}") - return True - - nickname = cmd[2] - self.Irc.send2socket(f":{service_id} MODE {channel} +o {nickname}") - - except IndexError as e: - self.Logs.warning(f'_hcmd OP: {str(e)}') - self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} op [#SALON] [NICKNAME]') - - case 'deop': - # /mode #channel -o user - # .deop #channel user - try: - if channel is None: - self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} deop [#SALON] [NICKNAME]') - return False - - if len(cmd) == 1: - self.Irc.send2socket(f":{service_id} MODE {channel} -o {fromuser}") - return True - - # deop nickname - if len(cmd) == 2: - nickname = cmd[1] - self.Irc.send2socket(f":{service_id} MODE {channel} -o {nickname}") - return True - - nickname = cmd[2] - self.Irc.send2socket(f":{service_id} MODE {channel} -o {nickname}") - - except IndexError as e: - self.Logs.warning(f'_hcmd DEOP: {str(e)}') - self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} deop [#SALON] [NICKNAME]') - - case 'owner': - # /mode #channel +q user - # .owner #channel user - try: - if channel is None: - self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} owner [#SALON] [NICKNAME]') - return False - - if len(cmd) == 1: - self.Irc.send2socket(f":{service_id} MODE {channel} +q {fromuser}") - return True - - # owner nickname - if len(cmd) == 2: - nickname = cmd[1] - self.Irc.send2socket(f":{service_id} MODE {channel} +q {nickname}") - return True - - nickname = cmd[2] - self.Irc.send2socket(f":{service_id} MODE {channel} +q {nickname}") - - except IndexError as e: - self.Logs.warning(f'_hcmd OWNER: {str(e)}') - self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} owner [#SALON] [NICKNAME]') - - case 'deowner': - # /mode #channel -q user - # .deowner #channel user - try: - if channel is None: - self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} deowner [#SALON] [NICKNAME]') - return False - - if len(cmd) == 1: - self.Irc.send2socket(f":{service_id} MODE {channel} -q {fromuser}") - return True - - # deowner nickname - if len(cmd) == 2: - nickname = cmd[1] - self.Irc.send2socket(f":{service_id} MODE {channel} -q {nickname}") - return True - channel = cmd[1] - nickname = cmd[2] - self.Irc.send2socket(f":{service_id} MODE {channel} -q {nickname}") - - except IndexError as e: - self.Logs.warning(f'_hcmd DEOWNER: {str(e)}') - self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} deowner [#SALON] [NICKNAME]') - - case 'halfop': - # /mode #channel +h user - # .halfop #channel user - try: - if channel is None: - self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} halfop [#SALON] [NICKNAME]') - return False - - if len(cmd) == 1: - self.Irc.send2socket(f":{service_id} MODE {channel} +h {fromuser}") - return True - - # deop nickname - if len(cmd) == 2: - nickname = cmd[1] - self.Irc.send2socket(f":{service_id} MODE {channel} +h {nickname}") - return True - - nickname = cmd[2] - self.Irc.send2socket(f":{service_id} MODE {channel} +h {nickname}") - - except IndexError as e: - self.Logs.warning(f'_hcmd halfop: {str(e)}') - self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} halfop [#SALON] [NICKNAME]') - - case 'dehalfop': - # /mode #channel -h user - # .dehalfop #channel user - try: - if channel is None: - self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} dehalfop [#SALON] [NICKNAME]') - return False - - if len(cmd) == 1: - self.Irc.send2socket(f":{service_id} MODE {channel} -h {fromuser}") - return True - - # dehalfop nickname - if len(cmd) == 2: - nickname = cmd[1] - self.Irc.send2socket(f":{service_id} MODE {channel} -h {nickname}") - return True - - nickname = cmd[2] - self.Irc.send2socket(f":{service_id} MODE {channel} -h {nickname}") - - except IndexError as e: - self.Logs.warning(f'_hcmd DEHALFOP: {str(e)}') - self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} dehalfop [#SALON] [NICKNAME]') - - case 'voice': - # /mode #channel +v user - # .voice #channel user - try: - if channel is None: - self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} voice [#SALON] [NICKNAME]') - return False - - if len(cmd) == 1: - self.Irc.send2socket(f":{service_id} MODE {channel} +v {fromuser}") - return True - - # voice nickname - if len(cmd) == 2: - nickname = cmd[1] - self.Irc.send2socket(f":{service_id} MODE {channel} +v {nickname}") - return True - - nickname = cmd[2] - self.Irc.send2socket(f":{service_id} MODE {channel} +v {nickname}") - - except IndexError as e: - self.Logs.warning(f'_hcmd VOICE: {str(e)}') - self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} voice [#SALON] [NICKNAME]') - - case 'devoice': - # /mode #channel -v user - # .devoice #channel user - try: - if channel is None: - self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} devoice [#SALON] [NICKNAME]') - return False - - if len(cmd) == 1: - self.Irc.send2socket(f":{service_id} MODE {channel} -v {fromuser}") - return True - - # dehalfop nickname - if len(cmd) == 2: - nickname = cmd[1] - self.Irc.send2socket(f":{service_id} MODE {channel} -v {nickname}") - return True - - nickname = cmd[2] - self.Irc.send2socket(f":{service_id} MODE {channel} -v {nickname}") - - except IndexError as e: - self.Logs.warning(f'_hcmd DEVOICE: {str(e)}') - self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} devoice [#SALON] [NICKNAME]') - - case 'ban': - # .ban #channel nickname - try: - channel = cmd[1] - nickname = cmd[2] - - self.Irc.send2socket(f":{service_id} MODE {channel} +b {nickname}!*@*") - self.Logs.debug(f'{fromuser} has banned {nickname} from {channel}') - except IndexError as e: - self.Logs.warning(f'_hcmd BAN: {str(e)}') - self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} ban [#SALON] [NICKNAME]') - - case 'unban': - # .unban #channel nickname - try: - channel = cmd[1] - nickname = cmd[2] - - self.Irc.send2socket(f":{service_id} MODE {channel} -b {nickname}!*@*") - self.Logs.debug(f'{fromuser} has unbanned {nickname} from {channel}') - except IndexError as e: - self.Logs.warning(f'_hcmd UNBAN: {str(e)}') - self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} unban [#SALON] [NICKNAME]') - - case 'kick': - # .kick #channel nickname reason - try: - channel = cmd[1] - nickname = cmd[2] - reason = [] - - for i in range(3, len(cmd)): - reason.append(cmd[i]) - - final_reason = ' '.join(reason) - - self.Irc.send2socket(f":{service_id} KICK {channel} {nickname} {final_reason}") - self.Logs.debug(f'{fromuser} has kicked {nickname} from {channel} : {final_reason}') - except IndexError as e: - self.Logs.warning(f'_hcmd KICK: {str(e)}') - self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} kick [#SALON] [NICKNAME] [REASON]') - - case 'kickban': - # .kickban #channel nickname reason - try: - channel = cmd[1] - nickname = cmd[2] - reason = [] - - for i in range(3, len(cmd)): - reason.append(cmd[i]) - - final_reason = ' '.join(reason) - - self.Irc.send2socket(f":{service_id} KICK {channel} {nickname} {final_reason}") - self.Irc.send2socket(f":{service_id} MODE {channel} +b {nickname}!*@*") - self.Logs.debug(f'{fromuser} has kicked and banned {nickname} from {channel} : {final_reason}') - except IndexError as e: - self.Logs.warning(f'_hcmd KICKBAN: {str(e)}') - self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} kickban [#SALON] [NICKNAME] [REASON]') - case 'info': try: nickoruid = cmd[1] diff --git a/mods/mod_test.py b/mods/mod_test.py index 5fa85b9..6942bc0 100644 --- a/mods/mod_test.py +++ b/mods/mod_test.py @@ -1,53 +1,64 @@ +from dataclasses import dataclass, fields from core.irc import Irc -# Le module crée devra réspecter quelques conditions -# 1. Importer le module de configuration -# 2. Le nom de class devra toujours s'appeler comme le module exemple => nom de class Dktmb | nom du module mod_dktmb -# 3. la fonction __init__ devra toujours avoir les parametres suivant (self, irc:object) -# 1 . Créer la variable Irc dans le module -# 2 . Récuperer la configuration dans une variable -# 3 . Définir et enregistrer les nouvelles commandes -# 4. une fonction _hcmds(self, user:str, cmd: list) devra toujours etre crée. - class Test(): + @dataclass + class ModConfModel: + """The Model containing the module parameters + """ + param_exemple1: str + param_exemple2: int + def __init__(self, ircInstance:Irc) -> None: - # Add Irc Object to the module + # Module name (Mandatory) + self.module_name = 'mod_' + str(self.__class__.__name__).lower() + + # Add Irc Object to the module (Mandatory) self.Irc = ircInstance - # Add Global Configuration to the module + # Add Global Configuration to the module (Mandatory) self.Config = ircInstance.Config - # Add Base object to the module + # Add Base object to the module (Mandatory) self.Base = ircInstance.Base - # Add logs object to the module + # Add logs object to the module (Mandatory) self.Logs = ircInstance.Base.logs - # Add User object to the module + # Add User object to the module (Mandatory) self.User = ircInstance.User - # Add Channel object to the module + # Add Channel object to the module (Mandatory) self.Channel = ircInstance.Channel - # Créer les nouvelles commandes du module + # Create module commands (Mandatory) self.commands_level = { - 0: ['test'], - 1: ['test_level_1'] + 0: ['test-command'], + 1: ['test_level_1'], + 2: ['test_level_2'], + 3: ['test_level_3'] } # Init the module self.__init_module() # Log the module - self.Logs.debug(f'Module {self.__class__.__name__} loaded ...') + self.Logs.debug(f'Module {self.module_name} loaded ...') def __init_module(self) -> None: + # Insert module commands into the core one (Mandatory) self.__set_commands(self.commands_level) + + # 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 # + return None def __set_commands(self, commands:dict[int, list[str]]) -> None: @@ -78,31 +89,67 @@ class Test(): id INTEGER PRIMARY KEY AUTOINCREMENT, datetime TEXT, server_msg TEXT - ) + ) ''' self.Base.db_execute_query(table_logs) return None + def __load_module_configuration(self) -> None: + """### Load Module Configuration + """ + try: + # Build the default configuration model (Mandatory) + self.ModConfig = self.ModConfModel(param_exemple1='param value 1', param_exemple2=1) + + # 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: return None def cmd(self, data:list) -> None: + return None - def _hcmds(self, user:str, cmd: list, fullcmd: list = []) -> None: + def _hcmds(self, user:str, channel: any, cmd: list, fullcmd: list = []) -> None: command = str(cmd[0]).lower() dnickname = self.Config.SERVICE_NICKNAME fromuser = user + fromchannel = str(channel) if not channel is None else None match command: - case 'test': + case 'test-command': try: - - self.Irc.send2socket(f":{dnickname} NOTICE {fromuser} : test command ready ...") + + self.Irc.send2socket(f":{dnickname} NOTICE {fromuser} : This is a notice to the sender ...") + self.Irc.send2socket(f":{dnickname} PRIVMSG {fromuser} : This is private message to the sender ...") + + if not fromchannel is None: + self.Irc.send2socket(f":{dnickname} PRIVMSG {fromchannel} : This is channel message to the sender ...") + + # How to update your module configuration + self.__update_configuration('param_exemple2', 7) + + # Log if you want the result self.Logs.debug(f"Test logs ready") - except KeyError as ke: - self.Logs.error(f"Key Error : {ke}") \ No newline at end of file + + except Exception as err: + self.Logs.error(f"{err}") \ No newline at end of file diff --git a/mods/mod_votekick.py b/mods/mod_votekick.py index e43ad48..d6b8bab 100644 --- a/mods/mod_votekick.py +++ b/mods/mod_votekick.py @@ -20,10 +20,14 @@ class Votekick(): voter_users: list vote_for: int vote_against: int - + VOTE_CHANNEL_DB:list[VoteChannelModel] = [] def __init__(self, ircInstance:Irc) -> None: + + # Module name (Mandatory) + self.module_name = 'mod_' + str(self.__class__.__name__).lower() + # Add Irc Object to the module self.Irc = ircInstance @@ -52,7 +56,7 @@ class Votekick(): self.__init_module() # Log the module - self.Logs.debug(f'Module {self.__class__.__name__} loaded ...') + self.Logs.debug(f'Module {self.module_name} loaded ...') def __init_module(self) -> None: @@ -143,7 +147,7 @@ class Votekick(): if not found: self.VOTE_CHANNEL_DB.append(ChannelObject) self.Logs.debug(f"The channel has been added {ChannelObject}") - self.db_add_vote_channel(ChannelObject.channel_name) + # self.db_add_vote_channel(ChannelObject.channel_name) return result @@ -222,7 +226,7 @@ class Votekick(): self.Irc.send2socket(f":{dnickname} KICK {channel} {target_user} Following the vote, you are not welcome in {channel}") self.Channel.delete_user_from_channel(channel, self.User.get_uid(target_user)) elif chan.vote_for <= chan.vote_against: - self.Irc.send2socket(f':{dnickname} PRIVMSG {channel} :This user will stay on this channel') + self.Irc.send2socket(f':{dnickname} PRIVMSG {channel} :This user [{target_user}] will stay on this channel') # Init the system if self.init_vote_system(channel): @@ -241,30 +245,14 @@ class Votekick(): return None - def _hcmds(self, user:str, cmd: list, fullcmd: list = []) -> None: + def _hcmds(self, user:str, channel: any, cmd: list, fullcmd: list = []) -> None: # cmd is the command starting from the user command # full cmd is sending the entire server response command = str(cmd[0]).lower() dnickname = self.Config.SERVICE_NICKNAME fromuser = user - - if len(fullcmd) >= 3: - fromchannel = str(fullcmd[2]).lower() if self.Base.Is_Channel(str(fullcmd[2]).lower()) else None - else: - fromchannel = None - - if len(cmd) >= 2: - sentchannel = str(cmd[1]).lower() if self.Base.Is_Channel(str(cmd[1]).lower()) else None - else: - sentchannel = None - - if not fromchannel is None: - channel = fromchannel - elif not sentchannel is None: - channel = sentchannel - else: - channel = None + fromchannel = channel match command: @@ -286,7 +274,7 @@ class Votekick(): case 'vote_for': try: # vote_for - channel = str(fullcmd[2]).lower() + channel = fromchannel for chan in self.VOTE_CHANNEL_DB: if chan.channel_name == channel: if fromuser in chan.voter_users: @@ -305,7 +293,7 @@ class Votekick(): case 'vote_against': try: # vote_against - channel = str(fullcmd[2]).lower() + channel = fromchannel for chan in self.VOTE_CHANNEL_DB: if chan.channel_name == channel: if fromuser in chan.voter_users: @@ -371,6 +359,9 @@ class Votekick(): uid_cleaned = self.Base.clean_uid(uid_submitted) ChannelInfo = self.Channel.get_Channel(channel) + if ChannelInfo is None: + self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} :This channel [{channel}] do not exist in the Channel Object') + return False clean_uids_in_channel: list = [] for uid in ChannelInfo.uids: @@ -404,11 +395,13 @@ class Votekick(): case 'activate': try: # activate #channel - # channel = str(cmd[1]).lower() + sentchannel = str(cmd[1]).lower() if self.Base.Is_Channel(str(cmd[1]).lower()) else None + if sentchannel is None: + self.Irc.send2socket(f":{dnickname} NOTICE {fromuser} :The correct command is {self.Config.SERVICE_PREFIX}ACTIVATE #CHANNEL") self.insert_vote_channel( self.VoteChannelModel( - channel_name=channel, + channel_name=sentchannel, target_user='', voter_users=[], vote_for=0, @@ -416,9 +409,11 @@ class Votekick(): ) ) - self.Irc.send2socket(f":{dnickname} JOIN {channel}") - self.Irc.send2socket(f":{dnickname} SAMODE {channel} +o {dnickname}") - self.Irc.send2socket(f":{dnickname} PRIVMSG {channel} :You can now use !submit to decide if he will stay or not on this channel ") + self.Base.db_query_channel('add', self.module_name, sentchannel) + + self.Irc.send2socket(f":{dnickname} JOIN {sentchannel}") + self.Irc.send2socket(f":{dnickname} SAMODE {sentchannel} +o {dnickname}") + self.Irc.send2socket(f":{dnickname} PRIVMSG {sentchannel} :You can now use !submit to decide if he will stay or not on this channel ") except KeyError as ke: self.Logs.error(f"Key Error : {ke}") @@ -426,16 +421,20 @@ class Votekick(): case 'deactivate': try: # deactivate #channel - # channel = str(cmd[1]).lower() + sentchannel = str(cmd[1]).lower() if self.Base.Is_Channel(str(cmd[1]).lower()) else None + if sentchannel is None: + self.Irc.send2socket(f":{dnickname} NOTICE {fromuser} :The correct command is {self.Config.SERVICE_PREFIX}DEACTIVATE #CHANNEL") - self.Irc.send2socket(f":{dnickname} SAMODE {channel} -o {dnickname}") - self.Irc.send2socket(f":{dnickname} PART {channel}") + self.Irc.send2socket(f":{dnickname} SAMODE {sentchannel} -o {dnickname}") + self.Irc.send2socket(f":{dnickname} PART {sentchannel}") for chan in self.VOTE_CHANNEL_DB: - if chan.channel_name == channel: + if chan.channel_name == sentchannel: self.VOTE_CHANNEL_DB.remove(chan) - self.db_delete_vote_channel(chan.channel_name) + self.Base.db_query_channel('del', self.module_name, chan.channel_name) + # self.db_delete_vote_channel(chan.channel_name) + + self.Logs.debug(f"The Channel {sentchannel} has been deactivated from the vote system") - self.Logs.debug(f"Test logs ready") except KeyError as ke: self.Logs.error(f"Key Error : {ke}") \ No newline at end of file diff --git a/version.json b/version.json index 0efa03c..52c9599 100644 --- a/version.json +++ b/version.json @@ -1,3 +1,3 @@ { - "version": "5.0.7" + "version": "5.1.0" } \ No newline at end of file