From 0a655b2df02b04d1907b0d9b95a4032be93e2325 Mon Sep 17 00:00:00 2001 From: adator <85586985+adator85@users.noreply.github.com> Date: Fri, 8 Aug 2025 17:38:45 +0200 Subject: [PATCH 01/20] Moving modules in separate folders --- core/base.py | 2 +- core/classes/protocols/unreal6.py | 15 ++- core/definition.py | 36 ++++-- core/irc.py | 10 +- mods/{ => clone}/mod_clone.py | 0 mods/{ => command}/mod_command.py | 0 mods/{ => defender}/mod_defender.py | 178 +++++++++++----------------- mods/defender/schemas.py | 22 ++++ mods/defender/utils.py | 0 mods/{ => jsonrpc}/mod_jsonrpc.py | 0 mods/{ => test}/mod_test.py | 0 mods/{ => votekick}/mod_votekick.py | 0 version.json | 2 +- 13 files changed, 138 insertions(+), 127 deletions(-) rename mods/{ => clone}/mod_clone.py (100%) rename mods/{ => command}/mod_command.py (100%) rename mods/{ => defender}/mod_defender.py (93%) create mode 100644 mods/defender/schemas.py create mode 100644 mods/defender/utils.py rename mods/{ => jsonrpc}/mod_jsonrpc.py (100%) rename mods/{ => test}/mod_test.py (100%) rename mods/{ => votekick}/mod_votekick.py (100%) diff --git a/core/base.py b/core/base.py index 66b4809..5e15add 100644 --- a/core/base.py +++ b/core/base.py @@ -391,7 +391,7 @@ class Base: result = response.fetchall() for param, value in result: - if type(getattr(dataclassObj, param)) == list: + if isinstance(getattr(dataclassObj, param), list): value = ast.literal_eval(value) setattr(dataclassObj, param, self.int_if_possible(value)) diff --git a/core/classes/protocols/unreal6.py b/core/classes/protocols/unreal6.py index 21c5b9f..d666f32 100644 --- a/core/classes/protocols/unreal6.py +++ b/core/classes/protocols/unreal6.py @@ -1,6 +1,6 @@ from re import match, findall, search from datetime import datetime -from typing import TYPE_CHECKING, Union +from typing import TYPE_CHECKING, Optional, Union from ssl import SSLEOFError, SSLError if TYPE_CHECKING: @@ -17,14 +17,21 @@ class Unrealircd6: self.__Base = ircInstance.Base self.__Settings = ircInstance.Base.Settings - self.known_protocol = ['SJOIN', 'UID', 'MD', 'QUIT', 'SQUIT', + self.known_protocol: set[str] = {'SJOIN', 'UID', 'MD', 'QUIT', 'SQUIT', 'EOS', 'PRIVMSG', 'MODE', 'UMODE2', 'VERSION', 'REPUTATION', 'SVS2MODE', - 'SLOG', 'NICK', 'PART', 'PONG' - ] + 'SLOG', 'NICK', 'PART', 'PONG'} self.__Base.logs.info(f"** Loading protocol [{__name__}]") + def get_ircd_protocol_poisition(self, cmd: list[str]) -> tuple[int, Optional[str]]: + + for index, token in enumerate(cmd): + if token.upper() in self.known_protocol: + return index, token.upper() + + return (-1, None) + def send2socket(self, message: str, print_log: bool = True) -> None: """Envoit les commandes à envoyer au serveur. diff --git a/core/definition.py b/core/definition.py index fea388e..ef71fad 100644 --- a/core/definition.py +++ b/core/definition.py @@ -1,10 +1,26 @@ from datetime import datetime -from dataclasses import dataclass, field -from typing import Literal +from json import dumps +from dataclasses import dataclass, field, asdict, fields +from typing import Literal, Any from os import sep @dataclass -class MClient: +class MainModel: + """Parent Model contains important methods""" + def to_dict(self) -> dict[str, Any]: + """Return the fields of a dataclass instance as a new dictionary mapping field names to field values.""" + return asdict(self) + + def to_json(self) -> str: + """Return the object of a dataclass a json str.""" + return dumps(self.to_dict()) + + def get_attributes(self) -> list[str]: + """Return a list of attributes name""" + return [f.name for f in fields(self)] + +@dataclass +class MClient(MainModel): """Model Client for registred nickname""" uid: str = None account: str = None @@ -22,7 +38,7 @@ class MClient: connexion_datetime: datetime = field(default=datetime.now()) @dataclass -class MUser: +class MUser(MainModel): """Model User""" uid: str = None @@ -40,7 +56,7 @@ class MUser: connexion_datetime: datetime = field(default=datetime.now()) @dataclass -class MAdmin: +class MAdmin(MainModel): """Model Admin""" uid: str = None @@ -59,7 +75,7 @@ class MAdmin: level: int = 0 @dataclass -class MReputation: +class MReputation(MainModel): """Model Reputation""" uid: str = None nickname: str = None @@ -77,7 +93,7 @@ class MReputation: secret_code: str = None @dataclass -class MChannel: +class MChannel(MainModel): """Model Channel""" name: str = None @@ -92,7 +108,7 @@ class MChannel: """ @dataclass -class ColorModel: +class ColorModel(MainModel): white: str = "\x0300" black: str = "\x0301" blue: str = "\x0302" @@ -104,7 +120,7 @@ class ColorModel: underline: str = "\x1F" @dataclass -class MConfig: +class MConfig(MainModel): """Model Configuration""" SERVEUR_IP: str = "127.0.0.1" @@ -305,7 +321,7 @@ class MConfig: """0: utf-8 | 1: iso-8859-1""" @dataclass -class MClone: +class MClone(MainModel): """Model Clone""" connected: bool = False uid: str = None diff --git a/core/irc.py b/core/irc.py index f2ef8ff..35b3a33 100644 --- a/core/irc.py +++ b/core/irc.py @@ -494,7 +494,8 @@ class Irc: try: # module_name : mod_voice module_name = module_name.lower() - class_name = module_name.split('_')[1].capitalize() # ==> Voice + module_folder = module_name.split('_')[1].lower() # ==> voice + class_name = module_name.split('_')[1].capitalize() # ==> Voice # print(self.loaded_classes) @@ -511,7 +512,7 @@ class Irc: ) return False - the_module = sys.modules['mods.' + module_name] + the_module = sys.modules[f'mods.{module_folder}.{module_name}'] importlib.reload(the_module) my_class = getattr(the_module, class_name, None) new_instance = my_class(self.ircObject) @@ -529,7 +530,7 @@ class Irc: return False # Charger le module - loaded_module = importlib.import_module(f"mods.{module_name}") + loaded_module = importlib.import_module(f'mods.{module_folder}.{module_name}') my_class = getattr(loaded_module, class_name, None) # Récuperer le nom de classe create_instance_of_the_class = my_class(self.ircObject) # Créer une nouvelle instance de la classe @@ -612,13 +613,14 @@ class Irc: def reload_module(self, from_user: str, mod_name: str) -> bool: try: module_name = mod_name.lower() # ==> mod_defender + module_folder = module_name.split('_')[1].lower() # ==> defender class_name = module_name.split('_')[1].capitalize() # ==> Defender if 'mods.' + module_name in sys.modules: self.Logs.info('Unload the module ...') self.loaded_classes[class_name].unload() self.Logs.info('Module Already Loaded ... reloading the module ...') - the_module = sys.modules['mods.' + module_name] + the_module = sys.modules[f'mods.{module_folder}.{module_name}'] importlib.reload(the_module) # Supprimer la class déja instancier diff --git a/mods/mod_clone.py b/mods/clone/mod_clone.py similarity index 100% rename from mods/mod_clone.py rename to mods/clone/mod_clone.py diff --git a/mods/mod_command.py b/mods/command/mod_command.py similarity index 100% rename from mods/mod_command.py rename to mods/command/mod_command.py diff --git a/mods/mod_defender.py b/mods/defender/mod_defender.py similarity index 93% rename from mods/mod_defender.py rename to mods/defender/mod_defender.py index e28be2b..d989f11 100644 --- a/mods/mod_defender.py +++ b/mods/defender/mod_defender.py @@ -2,53 +2,19 @@ import socket import json import time import re -from webbrowser import get import psutil import requests -from dataclasses import dataclass from datetime import datetime from typing import Union, TYPE_CHECKING -import core.definition as df -# Le module crée devra réspecter quelques conditions -# 1. Le nom de la classe devra toujours s'appeler comme le module. Exemple => nom de class Defender | nom du module mod_defender -# 2. la methode __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 . Créer vos tables, en utilisant toujours le nom des votre classe en minuscule ==> defender_votre-table -# 3. Methode suivantes: -# cmd(self, data:list) -# hcmds(self, user:str, cmd: list) -# unload(self) +import core.definition as df +import mods.defender.schemas as schemas if TYPE_CHECKING: from core.irc import Irc - class Defender(): - @dataclass - class ModConfModel: - reputation: int - reputation_timer: int - reputation_seuil: int - reputation_score_after_release: int - reputation_ban_all_chan: int - reputation_sg: int - local_scan: int - psutil_scan: int - abuseipdb_scan: int - freeipapi_scan: int - cloudfilt_scan: int - flood: int - flood_message: int - flood_time: int - flood_timer: int - autolimit: int - autolimit_amount: int - autolimit_interval: int - def __init__(self, ircInstance: 'Irc') -> None: # Module name (Mandatory) @@ -81,6 +47,9 @@ class Defender(): # Add Reputation object to the module (Optional) self.Reputation = ircInstance.Reputation + # Add module schemas + self.Schemas = schemas + # Create module commands (Mandatory) self.Irc.build_command(0, self.module_name, 'code', 'Display the code or key for access') self.Irc.build_command(1, self.module_name, 'info', 'Provide information about the channel or server') @@ -97,7 +66,7 @@ class Defender(): self.__init_module() # Log the module - self.Logs.debug(f'-- Module {self.module_name} loaded ...') + self.Logs.debug(f'-- Module {self.module_name} V2 loaded ...') def __init_module(self) -> None: @@ -179,23 +148,13 @@ class Defender(): 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=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, - autolimit=1, autolimit_amount=3, autolimit_interval=3 - ) + # Variable qui va contenir les options de configuration du module Defender + self.ModConfig = self.Schemas.ModConfModel() - # Sync the configuration with core configuration (Mandatory) - self.Base.db_sync_core_config(self.module_name, self.ModConfig) + # 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) + return None def __update_configuration(self, param_key: str, param_value: str): @@ -415,8 +374,6 @@ class Defender(): Args: action (str): _description_ - timer (int): _description_ - nickname (str): _description_ channel (str): _description_ Returns: @@ -973,10 +930,17 @@ class Defender(): def cmd(self, data: list[str]) -> None: try: - service_id = self.Config.SERVICE_ID # Defender serveur id - cmd = list(data).copy() + if not data or len(data) < 2: + return None - match cmd[1]: + service_id = self.Config.SERVICE_ID + p = self.Protocol + cmd = data.copy() if isinstance(data, list) else list(data).copy() + index, command = p.get_ircd_protocol_poisition(cmd) + if index == -1: + return None + + match command: case 'REPUTATION': # :001 REPUTATION 8.8.8.8 118 @@ -994,11 +958,6 @@ class Defender(): except IndexError as ie: self.Logs.error(f'cmd reputation: index error: {ie}') - if len(cmd) < 3: - return None - - match cmd[2]: - case 'MODE': # ['...', ':001XSCU0Q', 'MODE', '#jail', '+b', '~security-group:unknown-users'] # ['@unrealircd.org/...', ':001C0MF01', 'MODE', '#services', '+l', '1'] @@ -1011,19 +970,22 @@ class Defender(): if self.ModConfig.autolimit == 1: if mode == '+l' or mode == '-l': chan = self.Channel.get_Channel(channel) - self.Protocol.send2socket(f":{self.Config.SERVICE_ID} MODE {chan.name} +l {len(chan.uids) + self.ModConfig.autolimit_amount}") + p.send2socket(f":{self.Config.SERVICE_ID} MODE {chan.name} +l {len(chan.uids) + self.ModConfig.autolimit_amount}") if self.Config.SALON_JAIL == channel: if mode == '+b' and group_to_unban in group_to_check: - self.Protocol.send2socket(f":{service_id} MODE {self.Config.SALON_JAIL} -b ~security-group:unknown-users") - self.Protocol.send2socket(f":{service_id} MODE {self.Config.SALON_JAIL} -eee ~security-group:webirc-users ~security-group:known-users ~security-group:websocket-users") + p.send2socket(f":{service_id} MODE {self.Config.SALON_JAIL} -b ~security-group:unknown-users") + p.send2socket(f":{service_id} MODE {self.Config.SALON_JAIL} -eee ~security-group:webirc-users ~security-group:known-users ~security-group:websocket-users") + + return None case 'PRIVMSG': - cmd.pop(0) - user_trigger = str(cmd[0]).replace(':','') - channel = cmd[2] + # ['@mtag....',':python', 'PRIVMSG', '#defender', ':zefzefzregreg', 'regg', 'aerg'] + user_trigger = str(cmd[1]).replace(':','') + channel = cmd[3] find_nickname = self.User.get_nickname(user_trigger) self.flood(find_nickname, channel) + return None case 'UID': # If Init then do nothing @@ -1036,6 +998,10 @@ class Defender(): # Get User information _User = self.User.get_User(str(cmd[7])) + if _User is None: + self.Logs.critical(f'This UID: [{cmd[7]}] is not available please check why') + return None + # If user is not service or IrcOp then scan them if not re.match(r'^.*[S|o?].*$', _User.umodes): self.abuseipdb_UserModel.append(_User) if self.ModConfig.abuseipdb_scan == 1 and _User.remote_ip not in self.Config.WHITELISTED_IP else None @@ -1044,10 +1010,6 @@ class Defender(): self.psutil_UserModel.append(_User) if self.ModConfig.psutil_scan == 1 and _User.remote_ip not in self.Config.WHITELISTED_IP else None self.localscan_UserModel.append(_User) if self.ModConfig.local_scan == 1 and _User.remote_ip not in self.Config.WHITELISTED_IP else None - if _User is None: - self.Logs.critical(f'This UID: [{cmd[7]}] is not available please check why') - return None - reputation_flag = self.ModConfig.reputation reputation_seuil = self.ModConfig.reputation_seuil @@ -1058,12 +1020,8 @@ class Defender(): # currentDateTime = self.Base.get_datetime() self.Reputation.insert( self.Loader.Definition.MReputation( - **_User.__dict__, + **_User.to_dict(), secret_code=self.Base.get_random(8) - # uid=_User.uid, nickname=_User.nickname, username=_User.username, realname=_User.realname, - # hostname=_User.hostname, umodes=_User.umodes, vhost=_User.vhost, ip=_User.remote_ip, score=_User.score_connexion, - # secret_code=self.Base.get_random(8), isWebirc=_User.isWebirc, isWebsocket=_User.isWebsocket, connected_datetime=currentDateTime, - # updated_datetime=currentDateTime ) ) if self.Reputation.is_exist(_User.uid): @@ -1071,44 +1029,48 @@ class Defender(): self.system_reputation(_User.uid) self.Logs.info('Démarrer le systeme de reputation') + return None + case 'SJOIN': - # ['@msgid=F9B7JeHL5pj9nN57cJ5pEr;time=2023-12-28T20:47:24.305Z', ':001', 'SJOIN', '1702138958', '#welcome', ':0015L1AHL'] + # ['@msgid=F9B7JeHL5pj9nN57cJ5pEr..', ':001', 'SJOIN', '1702138958', '#welcome', ':0015L1AHL'] try: - cmd.pop(0) - parsed_chan = cmd[3] if self.Channel.Is_Channel(cmd[3]) else None + parsed_chan = cmd[4] if self.Channel.Is_Channel(cmd[4]) else None + parsed_UID = self.User.clean_uid(cmd[5]) if self.ModConfig.reputation == 1: - parsed_UID = self.User.clean_uid(cmd[4]) get_reputation = self.Reputation.get_Reputation(parsed_UID) if parsed_chan != self.Config.SALON_JAIL: - self.Protocol.send2socket(f":{service_id} MODE {parsed_chan} +b ~security-group:unknown-users") - self.Protocol.send2socket(f":{service_id} MODE {parsed_chan} +eee ~security-group:webirc-users ~security-group:known-users ~security-group:websocket-users") + p.send2socket(f":{service_id} MODE {parsed_chan} +b ~security-group:unknown-users") + p.send2socket(f":{service_id} MODE {parsed_chan} +eee ~security-group:webirc-users ~security-group:known-users ~security-group:websocket-users") if get_reputation is not None: isWebirc = get_reputation.isWebirc if not isWebirc: if parsed_chan != self.Config.SALON_JAIL: - self.Protocol.send_sapart(nick_to_sapart=get_reputation.nickname, channel_name=parsed_chan) + p.send_sapart(nick_to_sapart=get_reputation.nickname, channel_name=parsed_chan) if self.ModConfig.reputation_ban_all_chan == 1 and not isWebirc: if parsed_chan != self.Config.SALON_JAIL: - self.Protocol.send2socket(f":{service_id} MODE {parsed_chan} +b {get_reputation.nickname}!*@*") - self.Protocol.send2socket(f":{service_id} KICK {parsed_chan} {get_reputation.nickname}") + p.send2socket(f":{service_id} MODE {parsed_chan} +b {get_reputation.nickname}!*@*") + p.send2socket(f":{service_id} KICK {parsed_chan} {get_reputation.nickname}") self.Logs.debug(f'SJOIN parsed_uid : {parsed_UID}') + return None + except KeyError as ke: self.Logs.error(f"key error SJOIN : {ke}") case 'SLOG': - # self.Base.scan_ports(cmd[7]) - cmd.pop(0) + ['@unrealircd...', ':001', 'SLOG', 'info', 'blacklist', 'BLACKLIST_HIT', ':[Blacklist]', 'IP', '162.x.x.x', 'matches', 'blacklist', 'dronebl', '(dnsbl.dronebl.org/reply=6)'] - if not self.Base.is_valid_ip(cmd[7]): + if not self.Base.is_valid_ip(cmd[8]): return None + return None + # if self.ModConfig.local_scan == 1 and not cmd[7] in self.Config.WHITELISTED_IP: # self.localscan_remote_ip.append(cmd[7]) @@ -1125,11 +1087,10 @@ class Defender(): # self.cloudfilt_remote_ip.append(cmd[7]) case 'NICK': - # :0010BS24L NICK [NEWNICK] 1697917711 + # ['@unrealircd.org...', ':001MZQ0RB', 'NICK', 'halow', '1754663712'] # Changement de nickname try: - cmd.pop(0) - uid = str(cmd[0]).replace(':','') + uid = self.User.clean_uid(str(cmd[1])) get_Reputation = self.Reputation.get_Reputation(uid) jail_salon = self.Config.SALON_JAIL service_id = self.Config.SERVICE_ID @@ -1140,37 +1101,40 @@ class Defender(): # Update the new nickname oldnick = get_Reputation.nickname - newnickname = cmd[2] + newnickname = cmd[3] get_Reputation.nickname = newnickname # If ban in all channel is ON then unban old nickname an ban the new nickname if self.ModConfig.reputation_ban_all_chan == 1: for chan in self.Channel.UID_CHANNEL_DB: if chan.name != jail_salon: - self.Protocol.send2socket(f":{service_id} MODE {chan.name} -b {oldnick}!*@*") - self.Protocol.send2socket(f":{service_id} MODE {chan.name} +b {newnickname}!*@*") + p.send2socket(f":{service_id} MODE {chan.name} -b {oldnick}!*@*") + p.send2socket(f":{service_id} MODE {chan.name} +b {newnickname}!*@*") + + return None except KeyError as ke: self.Logs.error(f'cmd - NICK - KeyError: {ke}') case 'QUIT': - # :001N1WD7L QUIT :Quit: free_znc_1 - cmd.pop(0) + # ['@unrealircd.org...', ':001MZQ0RB', 'QUIT', ':Quit:', '....'] ban_all_chan = self.Base.int_if_possible(self.ModConfig.reputation_ban_all_chan) - user_id = str(cmd[0]).replace(':','') - final_UID = user_id - + final_UID = self.User.clean_uid(str(cmd[1])) jail_salon = self.Config.SALON_JAIL service_id = self.Config.SERVICE_ID - get_user_reputation = self.Reputation.get_Reputation(final_UID) if get_user_reputation is not None: final_nickname = get_user_reputation.nickname for chan in self.Channel.UID_CHANNEL_DB: if chan.name != jail_salon and ban_all_chan == 1: - self.Protocol.send2socket(f":{service_id} MODE {chan.name} -b {final_nickname}!*@*") + p.send2socket(f":{service_id} MODE {chan.name} -b {final_nickname}!*@*") self.Reputation.delete(final_UID) + + return None + + case _: + return None except KeyError as ke: self.Logs.error(f"{ke} / {cmd} / length {str(len(cmd))}") @@ -1183,8 +1147,7 @@ class Defender(): command = str(cmd[0]).lower() fromuser = user - fromchannel = channel if self.Channel.Is_Channel(channel) else None - channel = fromchannel + channel = fromchannel = channel if self.Channel.Is_Channel(channel) else None dnickname = self.Config.SERVICE_NICKNAME # Defender nickname dchanlog = self.Config.SERVICE_CHANLOG # Defender chan log @@ -1816,16 +1779,17 @@ class Defender(): case 'sentinel': # .sentinel on activation = str(cmd[1]).lower() - service_id = self.Config.SERVICE_ID - channel_to_dont_quit = [self.Config.SALON_JAIL, self.Config.SERVICE_CHANLOG] if activation == 'on': for chan in self.Channel.UID_CHANNEL_DB: if chan.name not in channel_to_dont_quit: self.Protocol.send_join_chan(uidornickname=dnickname, channel=chan.name) + return None + if activation == 'off': for chan in self.Channel.UID_CHANNEL_DB: if chan.name not in channel_to_dont_quit: - self.Protocol.part(uidornickname=dnickname, channel=chan.name) + self.Protocol.send_part_chan(uidornickname=dnickname, channel=chan.name) self.join_saved_channels() + return None diff --git a/mods/defender/schemas.py b/mods/defender/schemas.py new file mode 100644 index 0000000..1a91ae6 --- /dev/null +++ b/mods/defender/schemas.py @@ -0,0 +1,22 @@ +from core.definition import MainModel, dataclass + +@dataclass +class ModConfModel(MainModel): + reputation: int = 0 + reputation_timer: int = 1 + reputation_seuil: int = 26 + reputation_score_after_release: int = 27 + reputation_ban_all_chan: int = 0 + reputation_sg: int = 1 + local_scan: int = 0 + psutil_scan: int = 0 + abuseipdb_scan: int = 0 + freeipapi_scan: int = 0 + cloudfilt_scan: int = 0 + flood: int = 0 + flood_message: int = 5 + flood_time: int = 1 + flood_timer: int = 20 + autolimit: int = 0 + autolimit_amount: int = 3 + autolimit_interval: int = 3 \ No newline at end of file diff --git a/mods/defender/utils.py b/mods/defender/utils.py new file mode 100644 index 0000000..e69de29 diff --git a/mods/mod_jsonrpc.py b/mods/jsonrpc/mod_jsonrpc.py similarity index 100% rename from mods/mod_jsonrpc.py rename to mods/jsonrpc/mod_jsonrpc.py diff --git a/mods/mod_test.py b/mods/test/mod_test.py similarity index 100% rename from mods/mod_test.py rename to mods/test/mod_test.py diff --git a/mods/mod_votekick.py b/mods/votekick/mod_votekick.py similarity index 100% rename from mods/mod_votekick.py rename to mods/votekick/mod_votekick.py diff --git a/version.json b/version.json index 10eeed3..265b584 100644 --- a/version.json +++ b/version.json @@ -3,7 +3,7 @@ "requests": "2.32.3", "psutil": "6.0.0", - "unrealircd_rpc_py": "2.0.0", + "unrealircd_rpc_py": "2.0.1", "sqlalchemy": "2.0.35", "faker": "30.1.0" } \ No newline at end of file From 9ea5ae50d5423e5095fe1a59822e4518c9201fae Mon Sep 17 00:00:00 2001 From: adator <85586985+adator85@users.noreply.github.com> Date: Sat, 9 Aug 2025 03:35:30 +0200 Subject: [PATCH 02/20] Updating cmd by handling all functions, Threads and timers and schemas in separate files. code should be clear --- core/base.py | 48 +- core/classes/admin.py | 3 +- core/classes/channel.py | 23 +- core/classes/client.py | 10 +- core/classes/clone.py | 60 +-- core/classes/user.py | 40 +- core/irc.py | 7 +- core/utils.py | 3 + mods/defender/mod_defender.py | 929 ++-------------------------------- mods/defender/schemas.py | 17 +- mods/defender/threads.py | 167 ++++++ mods/defender/utils.py | 709 ++++++++++++++++++++++++++ 12 files changed, 1053 insertions(+), 963 deletions(-) create mode 100644 mods/defender/threads.py diff --git a/core/base.py b/core/base.py index 5e15add..e62689c 100644 --- a/core/base.py +++ b/core/base.py @@ -1,6 +1,8 @@ +import importlib import os import re import json +import sys import time import random import socket @@ -8,12 +10,12 @@ import hashlib import logging import threading import ipaddress - import ast +from pathlib import Path +from types import ModuleType import requests - from dataclasses import fields -from typing import Union, Literal, TYPE_CHECKING +from typing import Union, TYPE_CHECKING from base64 import b64decode, b64encode from datetime import datetime, timedelta, timezone from sqlalchemy import create_engine, Engine, Connection, CursorResult @@ -155,15 +157,41 @@ class Base: currentdate = datetime.now().strftime('%d-%m-%Y %H:%M:%S') return currentdate - def get_all_modules(self) -> list: + def get_all_modules(self) -> list[str]: + """Get list of all main modules + using this pattern mod_*.py - all_files = os.listdir('mods/') - all_modules: list = [] - for module in all_files: - if module.endswith('.py') and not module == '__init__.py': - all_modules.append(module.replace('.py', '').lower()) + Returns: + list[str]: List of module names. + """ + base_path = Path('mods') + return [file.name.replace('.py', '') for file in base_path.rglob('mod_*.py')] - return all_modules + def reload_modules_with_dependencies(self, prefix: str = 'mods'): + """ + Reload all modules in sys.modules that start with the given prefix. + Useful for reloading a full package during development. + """ + modules_to_reload = [] + + # Collect target modules + for name, module in sys.modules.items(): + if ( + isinstance(module, ModuleType) + and module is not None + and name.startswith(prefix) + ): + modules_to_reload.append((name, module)) + + # Sort to reload submodules before parent modules + for name, module in sorted(modules_to_reload, key=lambda x: x[0], reverse=True): + try: + if 'mod_' not in name: + importlib.reload(module) + self.logs.debug(f'[LOAD_MODULE] Module {module} success') + + except Exception: + self.logs.error(f'[LOAD_MODULE] Module {module} failed [!]') def create_log(self, log_message: str) -> None: """Enregiste les logs diff --git a/core/classes/admin.py b/core/classes/admin.py index 9cf22b2..b1bd63f 100644 --- a/core/classes/admin.py +++ b/core/classes/admin.py @@ -1,7 +1,6 @@ from typing import Union -import core.definition as df from core.base import Base - +import core.definition as df class Admin: diff --git a/core/classes/channel.py b/core/classes/channel.py index 608b051..77b2001 100644 --- a/core/classes/channel.py +++ b/core/classes/channel.py @@ -1,7 +1,5 @@ from re import findall -from typing import Union, Literal, TYPE_CHECKING -from dataclasses import asdict - +from typing import Any, Optional, Literal, TYPE_CHECKING from core.classes import user if TYPE_CHECKING: @@ -170,25 +168,22 @@ class Channel: except Exception as err: self.Logs.error(f'{err}') - def get_Channel(self, channel_name: str) -> Union['MChannel', None]: - - Channel = None + def get_Channel(self, channel_name: str) -> Optional['MChannel']: for record in self.UID_CHANNEL_DB: if record.name == channel_name: - Channel = record + return record - return Channel + return None - def get_Channel_AsDict(self, chan_name: str) -> Union[dict[str, any], None]: + def get_channel_asdict(self, chan_name: str) -> Optional[dict[str, Any]]: - chanObj = self.get_Channel(chan_name=chan_name) + channel_obj: Optional['MChannel'] = self.get_Channel(chan_name=chan_name) - if not chanObj is None: - chan_as_dict = asdict(chanObj) - return chan_as_dict - else: + if channel_obj is None: return None + + return channel_obj.to_dict() def Is_Channel(self, channelToCheck: str) -> bool: """Check if the string has the # caractere and return True if this is a channel diff --git a/core/classes/client.py b/core/classes/client.py index fd54ff0..93e5b76 100644 --- a/core/classes/client.py +++ b/core/classes/client.py @@ -1,5 +1,5 @@ from re import sub -from typing import Union, TYPE_CHECKING +from typing import Any, Optional, Union, TYPE_CHECKING from dataclasses import asdict if TYPE_CHECKING: @@ -169,7 +169,7 @@ class Client: return userObj.nickname - def get_Client_AsDict(self, uidornickname: str) -> Union[dict[str, any], None]: + def get_client_asdict(self, uidornickname: str) -> Optional[dict[str, Any]]: """Transform User Object to a dictionary Args: @@ -178,12 +178,12 @@ class Client: Returns: Union[dict[str, any], None]: User Object as a dictionary or None """ - userObj = self.get_Client(uidornickname=uidornickname) + client_obj = self.get_Client(uidornickname=uidornickname) - if userObj is None: + if client_obj is None: return None - return asdict(userObj) + return client_obj.to_dict() def is_exist(self, uidornikname: str) -> bool: """Check if the UID or the nickname exist in the USER DB diff --git a/core/classes/clone.py b/core/classes/clone.py index 7315822..846750b 100644 --- a/core/classes/clone.py +++ b/core/classes/clone.py @@ -1,6 +1,5 @@ -from dataclasses import asdict from core.definition import MClone -from typing import Union +from typing import Any, Optional from core.base import Base class Clone: @@ -74,13 +73,11 @@ class 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 + clone = self.get_Clone(nickname) + if isinstance(clone, MClone): + return True + + return False def uid_exists(self, uid: str) -> bool: """Check if the nickname exist @@ -91,15 +88,13 @@ class Clone: Returns: bool: True if the nickname exist """ - response = False + clone = self.get_Clone(uid) + if isinstance(clone, MClone): + return True + + return False - for cloneObject in self.UID_CLONE_DB: - if cloneObject.uid == uid: - response = True - - return response - - def get_Clone(self, uidornickname: str) -> Union[MClone, None]: + def get_Clone(self, uidornickname: str) -> Optional[MClone]: """Get MClone object or None Args: @@ -108,17 +103,15 @@ class Clone: Returns: Union[MClone, None]: Return MClone object or None """ - cloneObj = None - for clone in self.UID_CLONE_DB: if clone.uid == uidornickname: - cloneObj = clone + return clone if clone.nickname == uidornickname: - cloneObj = clone + return clone - return cloneObj + return None - def get_uid(self, uidornickname: str) -> Union[str, None]: + def get_uid(self, uidornickname: str) -> Optional[str]: """Get the UID of the clone starting from the UID or the Nickname Args: @@ -127,27 +120,22 @@ class Clone: Returns: str|None: Return the UID """ - uid = None for record in self.UID_CLONE_DB: if record.uid == uidornickname: - uid = record.uid + return record.uid if record.nickname == uidornickname: - uid = record.uid + return record.uid - # if not uid is None: - # self.Logs.debug(f'The UID that you are looking for {uidornickname} has been found {uid}') + return None - return uid + def get_Clone_AsDict(self, uidornickname: str) -> Optional[dict[str, Any]]: - def get_Clone_AsDict(self, uidornickname: str) -> Union[dict[str, any], None]: + clone_obj = self.get_Clone(uidornickname=uidornickname) - cloneObj = self.get_Clone(uidornickname=uidornickname) - - if not cloneObj is None: - cloneObj_as_dict = asdict(cloneObj) - return cloneObj_as_dict - else: + if clone_obj is None: return None + + return clone_obj.to_dict() def kill(self, nickname:str) -> bool: diff --git a/core/classes/user.py b/core/classes/user.py index 17451de..9d2951e 100644 --- a/core/classes/user.py +++ b/core/classes/user.py @@ -1,6 +1,7 @@ from re import sub -from typing import Union, TYPE_CHECKING +from typing import Any, Optional, Union, TYPE_CHECKING from dataclasses import asdict +from datetime import datetime if TYPE_CHECKING: from core.base import Base @@ -169,7 +170,7 @@ class User: return userObj.nickname - def get_User_AsDict(self, uidornickname: str) -> Union[dict[str, any], None]: + def get_user_asdict(self, uidornickname: str) -> Optional[dict[str, Any]]: """Transform User Object to a dictionary Args: @@ -183,7 +184,7 @@ class User: if userObj is None: return None - return asdict(userObj) + return userObj.to_dict() def is_exist(self, uidornikname: str) -> bool: """Check if the UID or the nickname exist in the USER DB @@ -217,4 +218,35 @@ class User: if not parsed_UID: return None - return parsed_UID \ No newline at end of file + return parsed_UID + + def get_user_uptime_in_minutes(self, uidornickname: str) -> float: + """Retourne depuis quand l'utilisateur est connecté (in minutes). + + Args: + uid (str): The uid or le nickname + + Returns: + int: How long in minutes has the user been connected? + """ + + get_user = self.get_User(uidornickname) + if get_user is None: + return 0 + + # Convertir la date enregistrée dans UID_DB en un objet {datetime} + connected_time_string = get_user.connexion_datetime + + if isinstance(connected_time_string, datetime): + connected_time = connected_time_string + else: + connected_time = datetime.strptime(connected_time_string, "%Y-%m-%d %H:%M:%S.%f") + + # What time is it ? + current_datetime = datetime.now() + + uptime = current_datetime - connected_time + convert_to_minutes = uptime.seconds / 60 + uptime_minutes = round(number=convert_to_minutes, ndigits=2) + + return uptime_minutes diff --git a/core/irc.py b/core/irc.py index 35b3a33..7c17e10 100644 --- a/core/irc.py +++ b/core/irc.py @@ -616,10 +616,13 @@ class Irc: module_folder = module_name.split('_')[1].lower() # ==> defender class_name = module_name.split('_')[1].capitalize() # ==> Defender - if 'mods.' + module_name in sys.modules: + if f'mods.{module_folder}.{module_name}' in sys.modules: self.Logs.info('Unload the module ...') self.loaded_classes[class_name].unload() self.Logs.info('Module Already Loaded ... reloading the module ...') + + # Load dependencies + self.Base.reload_modules_with_dependencies(f'mods.{module_folder}') the_module = sys.modules[f'mods.{module_folder}.{module_name}'] importlib.reload(the_module) @@ -683,7 +686,7 @@ class Irc: if self.User.get_User(uid) is None: return None - getUser = self.User.get_User_AsDict(uid) + getUser = self.User.get_user_asdict(uid) level = int(level) diff --git a/core/utils.py b/core/utils.py index 9289f13..10bb365 100644 --- a/core/utils.py +++ b/core/utils.py @@ -1,3 +1,6 @@ +import sys +import importlib +from types import ModuleType from typing import Literal, Union from datetime import datetime from time import time diff --git a/mods/defender/mod_defender.py b/mods/defender/mod_defender.py index d989f11..250864f 100644 --- a/mods/defender/mod_defender.py +++ b/mods/defender/mod_defender.py @@ -1,14 +1,8 @@ -import socket -import json -import time -import re -import psutil -import requests -from datetime import datetime -from typing import Union, TYPE_CHECKING - -import core.definition as df +import traceback import mods.defender.schemas as schemas +import mods.defender.utils as utils +import mods.defender.threads as thds +from typing import TYPE_CHECKING if TYPE_CHECKING: from core.irc import Irc @@ -50,6 +44,9 @@ class Defender(): # Add module schemas self.Schemas = schemas + # Add utils functions + self.Utils = utils + # Create module commands (Mandatory) self.Irc.build_command(0, self.module_name, 'code', 'Display the code or key for access') self.Irc.build_command(1, self.module_name, 'info', 'Provide information about the channel or server') @@ -80,11 +77,11 @@ class Defender(): self.timeout = self.Config.API_TIMEOUT # Listes qui vont contenir les ip a scanner avec les différentes API - self.abuseipdb_UserModel: list[df.MUser] = [] - self.freeipapi_UserModel: list[df.MUser] = [] - self.cloudfilt_UserModel: list[df.MUser] = [] - self.psutil_UserModel: list[df.MUser] = [] - self.localscan_UserModel: list[df.MUser] = [] + self.Schemas.DB_ABUSEIPDB_USERS = [] + self.Schemas.DB_FREEIPAPI_USERS = [] + self.Schemas.DB_CLOUDFILT_USERS = [] + self.Schemas.DB_PSUTIL_USERS = [] + self.Schemas.DB_LOCALSCAN_USERS = [] # Variables qui indique que les threads sont en cours d'éxecutions self.abuseipdb_isRunning:bool = True @@ -106,15 +103,16 @@ class Defender(): 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) + self.Base.create_thread(func=thds.thread_freeipapi_scan, func_args=(self, )) + self.Base.create_thread(func=thds.thread_cloudfilt_scan, func_args=(self, )) + self.Base.create_thread(func=thds.thread_abuseipdb_scan, func_args=(self, )) + self.Base.create_thread(func=thds.thread_local_scan, func_args=(self, )) + self.Base.create_thread(func=thds.thread_psutil_scan, func_args=(self, )) + + self.Base.create_thread(func=thds.thread_apply_reputation_sanctions, func_args=(self, )) if self.ModConfig.autolimit == 1: - self.Base.create_thread(func=self.thread_autolimit) + self.Base.create_thread(func=thds.thread_autolimit, func_args=(self, )) if self.ModConfig.reputation == 1: self.Protocol.sjoin(self.Config.SALON_JAIL) @@ -164,11 +162,11 @@ class Defender(): """Cette methode sera executée a chaque désactivation ou rechargement de module """ - self.abuseipdb_UserModel: list[df.MUser] = [] - self.freeipapi_UserModel: list[df.MUser] = [] - self.cloudfilt_UserModel: list[df.MUser] = [] - self.psutil_UserModel: list[df.MUser] = [] - self.localscan_UserModel: list[df.MUser] = [] + self.Schemas.DB_ABUSEIPDB_USERS = [] + self.Schemas.DB_FREEIPAPI_USERS = [] + self.Schemas.DB_CLOUDFILT_USERS = [] + self.Schemas.DB_PSUTIL_USERS = [] + self.Schemas.DB_LOCALSCAN_USERS = [] self.abuseipdb_isRunning:bool = False self.freeipapi_isRunning:bool = False @@ -177,6 +175,7 @@ class Defender(): self.localscan_isRunning:bool = False self.reputationTimer_isRunning:bool = False self.autolimit_isRunning: bool = False + return None def insert_db_trusted(self, uid: str, nickname:str) -> None: @@ -218,226 +217,6 @@ class Defender(): except Exception as err: self.Logs.error(f"General Error: {err}") - def get_user_uptime_in_minutes(self, uidornickname:str) -> float: - """Retourne depuis quand l'utilisateur est connecté (en secondes ). - - Args: - uid (str): le uid ou le nickname de l'utilisateur - - Returns: - int: Temps de connexion de l'utilisateur en secondes - """ - - get_user = self.User.get_User(uidornickname) - if get_user is None: - return 0 - - # Convertir la date enregistrée dans UID_DB en un objet {datetime} - connected_time_string = get_user.connexion_datetime - - if isinstance(connected_time_string, datetime): - connected_time = connected_time_string - else: - connected_time = datetime.strptime(connected_time_string, "%Y-%m-%d %H:%M:%S.%f") - - # Quelle heure est-il ? - current_datetime = datetime.now() - - uptime = current_datetime - connected_time - convert_to_minutes = uptime.seconds / 60 - uptime_minutes = round(number=convert_to_minutes, ndigits=2) - - return uptime_minutes - - def system_reputation(self, uid: str)-> None: - # Reputation security - # - Activation ou désactivation du système --> OK - # - Le user sera en mesure de changer la limite de la réputation --> OK - # - Defender devra envoyer l'utilisateur sur un salon défini dans la configuration, {jail_chan} - # - Defender devra bloquer cet utilisateur sur le salon qui sera en mode (+m) - # - Defender devra envoyer un message du type "Merci de taper cette comande /msg {nomdudefender} {un code généré aléatoirement} - # - Defender devra reconnaître le code - # - Defender devra libérer l'utilisateur et l'envoyer vers un salon défini dans la configuration {welcome_chan} - # - Defender devra intégrer une liste d'IDs (pseudo/host) exemptés de 'Reputation security' malgré un score de rép. faible et un pseudo non enregistré. - try: - - get_reputation = self.Reputation.get_Reputation(uid) - - if get_reputation is None: - self.Logs.error(f'UID {uid} has not been found') - return False - - salon_logs = self.Config.SERVICE_CHANLOG - salon_jail = self.Config.SALON_JAIL - - code = get_reputation.secret_code - jailed_nickname = get_reputation.nickname - jailed_score = get_reputation.score_connexion - - color_red = self.Config.COLORS.red - color_black = self.Config.COLORS.black - color_bold = self.Config.COLORS.bold - nogc = self.Config.COLORS.nogc - service_id = self.Config.SERVICE_ID - service_prefix = self.Config.SERVICE_PREFIX - reputation_ban_all_chan = self.ModConfig.reputation_ban_all_chan - - if not get_reputation.isWebirc: - # Si le user ne vient pas de webIrc - - self.Protocol.send_sajoin(nick_to_sajoin=jailed_nickname, channel_name=salon_jail) - self.Protocol.send_priv_msg(nick_from=self.Config.SERVICE_NICKNAME, - msg=f" [{color_red} REPUTATION {nogc}] : Connexion de {jailed_nickname} ({jailed_score}) ==> {salon_jail}", - channel=salon_logs - ) - self.Protocol.send_notice( - nick_from=self.Config.SERVICE_NICKNAME, - nick_to=jailed_nickname, - msg=f"[{color_red} {jailed_nickname} {color_black}] : Merci de tapez la commande suivante {color_bold}{service_prefix}code {code}{color_bold}" - ) - if reputation_ban_all_chan == 1: - for chan in self.Channel.UID_CHANNEL_DB: - if chan.name != salon_jail: - self.Protocol.send2socket(f":{service_id} MODE {chan.name} +b {jailed_nickname}!*@*") - self.Protocol.send2socket(f":{service_id} KICK {chan.name} {jailed_nickname}") - - self.Logs.info(f"system_reputation : {jailed_nickname} à été capturé par le système de réputation") - # self.Irc.create_ping_timer(int(self.ModConfig.reputation_timer) * 60, 'Defender', 'system_reputation_timer') - # self.Base.create_timer(int(self.ModConfig.reputation_timer) * 60, self.system_reputation_timer) - else: - self.Logs.info(f"system_reputation : {jailed_nickname} à été supprimé du système de réputation car connecté via WebIrc ou il est dans la 'Trusted list'") - self.Reputation.delete(uid) - - except IndexError as e: - self.Logs.error(f"system_reputation : {str(e)}") - - def system_reputation_timer(self) -> None: - try: - reputation_flag = self.ModConfig.reputation - reputation_timer = self.ModConfig.reputation_timer - reputation_seuil = self.ModConfig.reputation_seuil - ban_all_chan = self.ModConfig.reputation_ban_all_chan - service_id = self.Config.SERVICE_ID - dchanlog = self.Config.SERVICE_CHANLOG - color_red = self.Config.COLORS.red - nogc = self.Config.COLORS.nogc - salon_jail = self.Config.SALON_JAIL - - if reputation_flag == 0: - return None - elif reputation_timer == 0: - return None - - uid_to_clean = [] - - for user in self.Reputation.UID_REPUTATION_DB: - if not user.isWebirc: # Si il ne vient pas de WebIRC - if self.get_user_uptime_in_minutes(user.uid) >= reputation_timer and int(user.score_connexion) <= int(reputation_seuil): - self.Protocol.send_priv_msg( - nick_from=service_id, - msg=f"[{color_red} REPUTATION {nogc}] : Action sur {user.nickname} aprés {str(reputation_timer)} minutes d'inactivité", - channel=dchanlog - ) - self.Protocol.send2socket(f":{service_id} KILL {user.nickname} After {str(reputation_timer)} minutes of inactivity you should reconnect and type the password code") - self.Protocol.send2socket(f":{self.Config.SERVEUR_LINK} REPUTATION {user.remote_ip} 0") - - self.Logs.info(f"Nickname: {user.nickname} KILLED after {str(reputation_timer)} minutes of inactivity") - uid_to_clean.append(user.uid) - - for uid in uid_to_clean: - # Suppression des éléments dans {UID_DB} et {REPUTATION_DB} - for chan in self.Channel.UID_CHANNEL_DB: - if chan.name != salon_jail and ban_all_chan == 1: - get_user_reputation = self.Reputation.get_Reputation(uid) - self.Protocol.send2socket(f":{service_id} MODE {chan.name} -b {get_user_reputation.nickname}!*@*") - - # Lorsqu'un utilisateur quitte, il doit être supprimé de {UID_DB}. - self.Channel.delete_user_from_all_channel(uid) - self.Reputation.delete(uid) - self.User.delete(uid) - - except AssertionError as ae: - self.Logs.error(f'Assertion Error -> {ae}') - - def thread_reputation_timer(self) -> None: - try: - while self.reputationTimer_isRunning: - self.system_reputation_timer() - time.sleep(5) - - return None - except ValueError as ve: - self.Logs.error(f"thread_reputation_timer Error : {ve}") - - def _execute_flood_action(self, action:str, channel:str) -> None: - """DO NOT EXECUTE THIS FUNCTION WITHOUT THREADING - - Args: - action (str): _description_ - channel (str): _description_ - - Returns: - _type_: _description_ - """ - service_id = self.Config.SERVICE_ID - match action: - case 'mode-m': - # Action -m sur le salon - self.Protocol.send2socket(f":{service_id} MODE {channel} -m") - case _: - pass - - return None - - def flood(self, detected_user:str, channel:str) -> None: - - if self.ModConfig.flood == 0: - return None - - if not self.Channel.Is_Channel(channelToCheck=channel): - return None - - flood_time = self.ModConfig.flood_time - flood_message = self.ModConfig.flood_message - flood_timer = self.ModConfig.flood_timer - service_id = self.Config.SERVICE_ID - dnickname = self.Config.SERVICE_NICKNAME - color_red = self.Config.COLORS.red - color_bold = self.Config.COLORS.bold - - get_detected_uid = self.User.get_uid(detected_user) - get_detected_nickname = self.User.get_nickname(detected_user) - - unixtime = self.Base.get_unixtime() - get_diff_secondes = 0 - - if get_detected_uid not in self.flood_system: - self.flood_system[get_detected_uid] = { - 'nbr_msg': 0, - 'first_msg_time': unixtime - } - - self.flood_system[get_detected_uid]['nbr_msg'] += 1 - get_diff_secondes = unixtime - self.flood_system[get_detected_uid]['first_msg_time'] - if get_diff_secondes > flood_time: - self.flood_system[get_detected_uid]['first_msg_time'] = unixtime - self.flood_system[get_detected_uid]['nbr_msg'] = 0 - get_diff_secondes = unixtime - self.flood_system[get_detected_uid]['first_msg_time'] - - elif self.flood_system[get_detected_uid]['nbr_msg'] > flood_message: - self.Irc.Base.logs.info('system de flood detecté') - self.Protocol.send_priv_msg( - nick_from=dnickname, - msg=f"{color_red} {color_bold} Flood detected. Apply the +m mode (Ô_o)", - channel=channel - ) - self.Protocol.send2socket(f":{service_id} MODE {channel} +m") - self.Irc.Base.logs.info(f'FLOOD Détecté sur {get_detected_nickname} mode +m appliqué sur le salon {channel}') - self.flood_system[get_detected_uid]['nbr_msg'] = 0 - self.flood_system[get_detected_uid]['first_msg_time'] = unixtime - - self.Base.create_timer(flood_timer, self._execute_flood_action, ('mode-m', channel)) - def run_db_action_timer(self, wait_for: float = 0) -> None: query = f"SELECT param_key FROM {self.Config.TABLE_CONFIG}" @@ -461,676 +240,49 @@ class Defender(): return None - def scan_ports(self, userModel: df.MUser) -> None: - """local_scan - - Args: - userModel (UserModel): _description_ - """ - User = userModel - remote_ip = User.remote_ip - username = User.username - hostname = User.hostname - nickname = User.nickname - fullname = f'{nickname}!{username}@{hostname}' - - if remote_ip in self.Config.WHITELISTED_IP: - return None - - for port in self.Config.PORTS_TO_SCAN: - try: - newSocket = '' - newSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM or socket.SOCK_NONBLOCK) - newSocket.settimeout(0.5) - - connection = (remote_ip, self.Base.int_if_possible(port)) - newSocket.connect(connection) - - self.Protocol.send_priv_msg( - nick_from=self.Config.SERVICE_NICKNAME, - msg=f"[ {self.Config.COLORS.red}PROXY_SCAN{self.Config.COLORS.nogc} ] {fullname} ({remote_ip}) : Port [{str(port)}] ouvert sur l'adresse ip [{remote_ip}]", - channel=self.Config.SERVICE_CHANLOG - ) - # print(f"=======> Le port {str(port)} est ouvert !!") - self.Base.running_sockets.append(newSocket) - # print(newSocket) - newSocket.shutdown(socket.SHUT_RDWR) - newSocket.close() - - except (socket.timeout, ConnectionRefusedError): - self.Logs.info(f"Le port {remote_ip}:{str(port)} est fermé") - except AttributeError as ae: - self.Logs.warning(f"AttributeError ({remote_ip}): {ae}") - except socket.gaierror as err: - self.Logs.warning(f"Address Info Error ({remote_ip}): {err}") - finally: - # newSocket.shutdown(socket.SHUT_RDWR) - newSocket.close() - self.Logs.info('=======> Fermeture de la socket') - - def thread_local_scan(self) -> None: - try: - while self.localscan_isRunning: - - list_to_remove:list = [] - for user in self.localscan_UserModel: - self.scan_ports(user) - list_to_remove.append(user) - time.sleep(1) - - for user_model in list_to_remove: - self.localscan_UserModel.remove(user_model) - - time.sleep(1) - - return None - except ValueError as ve: - self.Logs.warning(f"thread_local_scan Error : {ve}") - - def get_ports_connexion(self, userModel: df.MUser) -> list[int]: - """psutil_scan for Linux (should be run on the same location as the unrealircd server) - - Args: - userModel (UserModel): The User Model Object - - Returns: - list[int]: list of ports - """ - try: - User = userModel - remote_ip = User.remote_ip - username = User.username - hostname = User.hostname - nickname = User.nickname - - if remote_ip in self.Config.WHITELISTED_IP: - return None - - connections = psutil.net_connections(kind='inet') - fullname = f'{nickname}!{username}@{hostname}' - - matching_ports = [conn.raddr.port for conn in connections if conn.raddr and conn.raddr.ip == remote_ip] - self.Logs.info(f"Connexion of {fullname} ({remote_ip}) using ports : {str(matching_ports)}") - - if matching_ports: - self.Protocol.send_priv_msg( - nick_from=self.Config.SERVICE_NICKNAME, - msg=f"[ {self.Config.COLORS.red}PSUTIL_SCAN{self.Config.COLORS.black} ] {fullname} ({remote_ip}) : is using ports {matching_ports}", - channel=self.Config.SERVICE_CHANLOG - ) - - return matching_ports - - except psutil.AccessDenied as ad: - self.Logs.critical(f'psutil_scan: Permission error: {ad}') - - def thread_psutil_scan(self) -> None: - try: - - while self.psutil_isRunning: - - list_to_remove:list = [] - for user in self.psutil_UserModel: - self.get_ports_connexion(user) - list_to_remove.append(user) - time.sleep(1) - - for user_model in list_to_remove: - self.psutil_UserModel.remove(user_model) - - time.sleep(1) - - return None - except ValueError as ve: - self.Logs.warning(f"thread_psutil_scan Error : {ve}") - - def abuseipdb_scan(self, userModel: df.MUser) -> Union[dict[str, any], None]: - """Analyse l'ip avec AbuseIpDB - Cette methode devra etre lancer toujours via un thread ou un timer. - Args: - userModel (UserModel): l'objet User qui contient l'ip - - Returns: - dict[str, any] | None: les informations du provider - keys : 'score', 'country', 'isTor', 'totalReports' - """ - User = userModel - remote_ip = User.remote_ip - username = User.username - hostname = User.hostname - nickname = User.nickname - - if remote_ip in self.Config.WHITELISTED_IP: - return None - if self.ModConfig.abuseipdb_scan == 0: - return None - - if self.abuseipdb_key == '': - return None - - url = 'https://api.abuseipdb.com/api/v2/check' - querystring = { - 'ipAddress': remote_ip, - 'maxAgeInDays': '90' - } - - headers = { - 'Accept': 'application/json', - 'Key': self.abuseipdb_key - } - - try: - response = requests.request(method='GET', url=url, headers=headers, params=querystring, timeout=self.timeout) - - # Formatted output - decodedResponse = json.loads(response.text) - - if 'data' not in decodedResponse: - return None - - result = { - 'score': decodedResponse['data']['abuseConfidenceScore'], - 'country': decodedResponse['data']['countryCode'], - 'isTor': decodedResponse['data']['isTor'], - 'totalReports': decodedResponse['data']['totalReports'] - } - - service_id = self.Config.SERVICE_ID - service_chanlog = self.Config.SERVICE_CHANLOG - color_red = self.Config.COLORS.red - nogc = self.Config.COLORS.nogc - - # pseudo!ident@host - fullname = f'{nickname}!{username}@{hostname}' - - self.Protocol.send_priv_msg( - nick_from=service_id, - msg=f"[ {color_red}ABUSEIPDB_SCAN{nogc} ] : Connexion de {fullname} ({remote_ip}) ==> Score: {str(result['score'])} | Country : {result['country']} | Tor : {str(result['isTor'])} | Total Reports : {str(result['totalReports'])}", - channel=service_chanlog - ) - - if result['isTor']: - self.Protocol.send2socket(f":{service_id} GLINE +*@{remote_ip} {self.Config.GLINE_DURATION} This server do not allow Tor connexions {str(result['isTor'])} - Detected by Abuseipdb") - elif result['score'] >= 95: - self.Protocol.send2socket(f":{service_id} GLINE +*@{remote_ip} {self.Config.GLINE_DURATION} You were banned from this server because your abuse score is = {str(result['score'])} - Detected by Abuseipdb") - - response.close() - - return result - except KeyError as ke: - self.Logs.error(f"AbuseIpDb KeyError : {ke}") - except requests.ReadTimeout as rt: - self.Logs.error(f"AbuseIpDb Timeout : {rt}") - except requests.ConnectionError as ce: - self.Logs.error(f"AbuseIpDb Connection Error : {ce}") - except Exception as err: - self.Logs.error(f"General Error Abuseipdb : {err}") - - def thread_abuseipdb_scan(self) -> None: - try: - - while self.abuseipdb_isRunning: - - list_to_remove: list = [] - for user in self.abuseipdb_UserModel: - self.abuseipdb_scan(user) - list_to_remove.append(user) - time.sleep(1) - - for user_model in list_to_remove: - self.abuseipdb_UserModel.remove(user_model) - - time.sleep(1) - - return None - except ValueError as ve: - self.Logs.error(f"thread_abuseipdb_scan Error : {ve}") - - def freeipapi_scan(self, userModel: df.MUser) -> Union[dict[str, any], None]: - """Analyse l'ip avec Freeipapi - Cette methode devra etre lancer toujours via un thread ou un timer. - Args: - remote_ip (_type_): l'ip a analyser - - Returns: - dict[str, any] | None: les informations du provider - keys : 'countryCode', 'isProxy' - """ - User = userModel - remote_ip = User.remote_ip - username = User.username - hostname = User.hostname - nickname = User.nickname - - if remote_ip in self.Config.WHITELISTED_IP: - return None - if self.ModConfig.freeipapi_scan == 0: - return None - - service_id = self.Config.SERVICE_ID - service_chanlog = self.Config.SERVICE_CHANLOG - color_red = self.Config.COLORS.red - nogc = self.Config.COLORS.nogc - - url = f'https://freeipapi.com/api/json/{remote_ip}' - - headers = { - 'Accept': 'application/json', - } - - try: - response = requests.request(method='GET', url=url, headers=headers, timeout=self.timeout) - - # Formatted output - decodedResponse = json.loads(response.text) - - status_code = response.status_code - if status_code == 429: - self.Logs.warning('Too Many Requests - The rate limit for the API has been exceeded.') - return None - elif status_code != 200: - self.Logs.warning(f'status code = {str(status_code)}') - return None - - result = { - 'countryCode': decodedResponse['countryCode'] if 'countryCode' in decodedResponse else None, - 'isProxy': decodedResponse['isProxy'] if 'isProxy' in decodedResponse else None - } - - # pseudo!ident@host - fullname = f'{nickname}!{username}@{hostname}' - - self.Protocol.send_priv_msg( - nick_from=service_id, - msg=f"[ {color_red}FREEIPAPI_SCAN{nogc} ] : Connexion de {fullname} ({remote_ip}) ==> Proxy: {str(result['isProxy'])} | Country : {str(result['countryCode'])}", - channel=service_chanlog - ) - - if result['isProxy']: - self.Protocol.send2socket(f":{service_id} GLINE +*@{remote_ip} {self.Config.GLINE_DURATION} This server do not allow proxy connexions {str(result['isProxy'])} - detected by freeipapi") - response.close() - - return result - except KeyError as ke: - self.Logs.error(f"FREEIPAPI_SCAN KeyError : {ke}") - except Exception as err: - self.Logs.error(f"General Error Freeipapi : {err}") - - def thread_freeipapi_scan(self) -> None: - try: - - while self.freeipapi_isRunning: - - list_to_remove: list[df.MUser] = [] - for user in self.freeipapi_UserModel: - self.freeipapi_scan(user) - list_to_remove.append(user) - time.sleep(1) - - for user_model in list_to_remove: - self.freeipapi_UserModel.remove(user_model) - - time.sleep(1) - - return None - except ValueError as ve: - self.Logs.error(f"thread_freeipapi_scan Error : {ve}") - - def cloudfilt_scan(self, userModel: df.MUser) -> Union[dict[str, any], None]: - """Analyse l'ip avec cloudfilt - Cette methode devra etre lancer toujours via un thread ou un timer. - Args: - remote_ip (_type_): l'ip a analyser - - Returns: - dict[str, any] | None: les informations du provider - keys : 'countryCode', 'isProxy' - """ - User = userModel - remote_ip = User.remote_ip - username = User.username - hostname = User.hostname - nickname = User.nickname - - if remote_ip in self.Config.WHITELISTED_IP: - return None - if self.ModConfig.cloudfilt_scan == 0: - return None - if self.cloudfilt_key == '': - return None - - service_id = self.Config.SERVICE_ID - service_chanlog = self.Config.SERVICE_CHANLOG - color_red = self.Config.COLORS.red - nogc = self.Config.COLORS.nogc - - url = "https://developers18334.cloudfilt.com/" - - data = { - 'ip': remote_ip, - 'key': self.cloudfilt_key - } - - try: - response = requests.post(url=url, data=data) - # Formatted output - decodedResponse = json.loads(response.text) - status_code = response.status_code - if status_code != 200: - self.Logs.warning(f'Error connecting to cloudfilt API | Code: {str(status_code)}') - return None - - result = { - 'countryiso': decodedResponse['countryiso'] if 'countryiso' in decodedResponse else None, - 'listed': decodedResponse['listed'] if 'listed' in decodedResponse else None, - 'listed_by': decodedResponse['listed_by'] if 'listed_by' in decodedResponse else None, - 'host': decodedResponse['host'] if 'host' in decodedResponse else None - } - - # pseudo!ident@host - fullname = f'{nickname}!{username}@{hostname}' - - self.Protocol.send_priv_msg( - nick_from=service_id, - msg=f"[ {color_red}CLOUDFILT_SCAN{nogc} ] : Connexion de {fullname} ({remote_ip}) ==> Host: {str(result['host'])} | country: {str(result['countryiso'])} | listed: {str(result['listed'])} | listed by : {str(result['listed_by'])}", - channel=service_chanlog - ) - - if result['listed']: - self.Protocol.send2socket(f":{service_id} GLINE +*@{remote_ip} {self.Config.GLINE_DURATION} You connexion is listed as dangerous {str(result['listed'])} {str(result['listed_by'])} - detected by cloudfilt") - - response.close() - - return result - except KeyError as ke: - self.Logs.error(f"CLOUDFILT_SCAN KeyError : {ke}") - return None - - def thread_cloudfilt_scan(self) -> None: - try: - - while self.cloudfilt_isRunning: - - list_to_remove:list = [] - for user in self.cloudfilt_UserModel: - self.cloudfilt_scan(user) - list_to_remove.append(user) - time.sleep(1) - - for user_model in list_to_remove: - self.cloudfilt_UserModel.remove(user_model) - - time.sleep(1) - - return None - except ValueError as ve: - self.Logs.error(f"Thread_cloudfilt_scan Error : {ve}") - - def thread_autolimit(self) -> None: - - if self.ModConfig.autolimit == 0: - self.Logs.debug("autolimit deactivated ... canceling the thread") - return None - - while self.Irc.autolimit_started: - time.sleep(0.2) - - self.Irc.autolimit_started = True - init_amount = self.ModConfig.autolimit_amount - INIT = 1 - - # Copy Channels to a list of dict - chanObj_copy: list[dict[str, int]] = [{"name": c.name, "uids_count": len(c.uids)} for c in self.Channel.UID_CHANNEL_DB] - chan_list: list[str] = [c.name for c in self.Channel.UID_CHANNEL_DB] - - while self.autolimit_isRunning: - - if self.ModConfig.autolimit == 0: - self.Logs.debug("autolimit deactivated ... stopping the current thread") - break - - for chan in self.Channel.UID_CHANNEL_DB: - for chan_copy in chanObj_copy: - if chan_copy["name"] == chan.name and len(chan.uids) != chan_copy["uids_count"]: - self.Protocol.send2socket(f":{self.Config.SERVICE_ID} MODE {chan.name} +l {len(chan.uids) + self.ModConfig.autolimit_amount}") - chan_copy["uids_count"] = len(chan.uids) - - if chan.name not in chan_list: - chan_list.append(chan.name) - chanObj_copy.append({"name": chan.name, "uids_count": 0}) - - # Verifier si un salon a été vidé - current_chan_in_list = [d.name for d in self.Channel.UID_CHANNEL_DB] - for c in chan_list: - if c not in current_chan_in_list: - chan_list.remove(c) - - # Si c'est la premiere execution - if INIT == 1: - for chan in self.Channel.UID_CHANNEL_DB: - self.Protocol.send2socket(f":{self.Config.SERVICE_ID} MODE {chan.name} +l {len(chan.uids) + self.ModConfig.autolimit_amount}") - - # Si le nouveau amount est différent de l'initial - if init_amount != self.ModConfig.autolimit_amount: - init_amount = self.ModConfig.autolimit_amount - for chan in self.Channel.UID_CHANNEL_DB: - self.Protocol.send2socket(f":{self.Config.SERVICE_ID} MODE {chan.name} +l {len(chan.uids) + self.ModConfig.autolimit_amount}") - - INIT = 0 - - if self.autolimit_isRunning: - time.sleep(self.ModConfig.autolimit_interval) - - for chan in self.Channel.UID_CHANNEL_DB: - self.Protocol.send2socket(f":{self.Config.SERVICE_ID} MODE {chan.name} -l") - - self.Irc.autolimit_started = False - - return None - def cmd(self, data: list[str]) -> None: try: if not data or len(data) < 2: return None - service_id = self.Config.SERVICE_ID - p = self.Protocol cmd = data.copy() if isinstance(data, list) else list(data).copy() - index, command = p.get_ircd_protocol_poisition(cmd) + index, command = self.Irc.Protocol.get_ircd_protocol_poisition(cmd) if index == -1: return None match command: case 'REPUTATION': - # :001 REPUTATION 8.8.8.8 118 - try: - self.reputation_first_connexion['ip'] = cmd[2] - self.reputation_first_connexion['score'] = cmd[3] - if str(cmd[3]).find('*') != -1: - # If the reputation changed, we do not need to scan the IP - return None - - if not self.Base.is_valid_ip(cmd[2]): - return None - - # Possibilité de déclancher les bans a ce niveau. - except IndexError as ie: - self.Logs.error(f'cmd reputation: index error: {ie}') + self.Utils.handle_on_reputation(self, cmd) + return None case 'MODE': - # ['...', ':001XSCU0Q', 'MODE', '#jail', '+b', '~security-group:unknown-users'] - # ['@unrealircd.org/...', ':001C0MF01', 'MODE', '#services', '+l', '1'] - - channel = str(cmd[3]) - mode = str(cmd[4]) - group_to_check = str(cmd[5:]) - group_to_unban = '~security-group:unknown-users' - - if self.ModConfig.autolimit == 1: - if mode == '+l' or mode == '-l': - chan = self.Channel.get_Channel(channel) - p.send2socket(f":{self.Config.SERVICE_ID} MODE {chan.name} +l {len(chan.uids) + self.ModConfig.autolimit_amount}") - - if self.Config.SALON_JAIL == channel: - if mode == '+b' and group_to_unban in group_to_check: - p.send2socket(f":{service_id} MODE {self.Config.SALON_JAIL} -b ~security-group:unknown-users") - p.send2socket(f":{service_id} MODE {self.Config.SALON_JAIL} -eee ~security-group:webirc-users ~security-group:known-users ~security-group:websocket-users") - + self.Utils.handle_on_mode(self, cmd) return None case 'PRIVMSG': - # ['@mtag....',':python', 'PRIVMSG', '#defender', ':zefzefzregreg', 'regg', 'aerg'] - user_trigger = str(cmd[1]).replace(':','') - channel = cmd[3] - find_nickname = self.User.get_nickname(user_trigger) - self.flood(find_nickname, channel) + self.Utils.handle_on_privmsg(self, cmd) return None case 'UID': - # If Init then do nothing - if self.Config.DEFENDER_INIT == 1: - return None - - # Supprimer la premiere valeur et finir le code normalement - cmd.pop(0) - - # Get User information - _User = self.User.get_User(str(cmd[7])) - - if _User is None: - self.Logs.critical(f'This UID: [{cmd[7]}] is not available please check why') - return None - - # If user is not service or IrcOp then scan them - if not re.match(r'^.*[S|o?].*$', _User.umodes): - self.abuseipdb_UserModel.append(_User) if self.ModConfig.abuseipdb_scan == 1 and _User.remote_ip not in self.Config.WHITELISTED_IP else None - self.freeipapi_UserModel.append(_User) if self.ModConfig.freeipapi_scan == 1 and _User.remote_ip not in self.Config.WHITELISTED_IP else None - self.cloudfilt_UserModel.append(_User) if self.ModConfig.cloudfilt_scan == 1 and _User.remote_ip not in self.Config.WHITELISTED_IP else None - self.psutil_UserModel.append(_User) if self.ModConfig.psutil_scan == 1 and _User.remote_ip not in self.Config.WHITELISTED_IP else None - self.localscan_UserModel.append(_User) if self.ModConfig.local_scan == 1 and _User.remote_ip not in self.Config.WHITELISTED_IP else None - - reputation_flag = self.ModConfig.reputation - reputation_seuil = self.ModConfig.reputation_seuil - - if self.Config.DEFENDER_INIT == 0: - # Si le user n'es pas un service ni un IrcOP - if not re.match(r'^.*[S|o?].*$', _User.umodes): - if reputation_flag == 1 and _User.score_connexion <= reputation_seuil: - # currentDateTime = self.Base.get_datetime() - self.Reputation.insert( - self.Loader.Definition.MReputation( - **_User.to_dict(), - secret_code=self.Base.get_random(8) - ) - ) - if self.Reputation.is_exist(_User.uid): - if reputation_flag == 1 and _User.score_connexion <= reputation_seuil: - self.system_reputation(_User.uid) - self.Logs.info('Démarrer le systeme de reputation') - + self.Utils.handle_on_uid(self, cmd) return None case 'SJOIN': - # ['@msgid=F9B7JeHL5pj9nN57cJ5pEr..', ':001', 'SJOIN', '1702138958', '#welcome', ':0015L1AHL'] - try: - parsed_chan = cmd[4] if self.Channel.Is_Channel(cmd[4]) else None - parsed_UID = self.User.clean_uid(cmd[5]) - - if self.ModConfig.reputation == 1: - get_reputation = self.Reputation.get_Reputation(parsed_UID) - - if parsed_chan != self.Config.SALON_JAIL: - p.send2socket(f":{service_id} MODE {parsed_chan} +b ~security-group:unknown-users") - p.send2socket(f":{service_id} MODE {parsed_chan} +eee ~security-group:webirc-users ~security-group:known-users ~security-group:websocket-users") - - if get_reputation is not None: - isWebirc = get_reputation.isWebirc - - if not isWebirc: - if parsed_chan != self.Config.SALON_JAIL: - p.send_sapart(nick_to_sapart=get_reputation.nickname, channel_name=parsed_chan) - - if self.ModConfig.reputation_ban_all_chan == 1 and not isWebirc: - if parsed_chan != self.Config.SALON_JAIL: - p.send2socket(f":{service_id} MODE {parsed_chan} +b {get_reputation.nickname}!*@*") - p.send2socket(f":{service_id} KICK {parsed_chan} {get_reputation.nickname}") - - self.Logs.debug(f'SJOIN parsed_uid : {parsed_UID}') - - return None - - except KeyError as ke: - self.Logs.error(f"key error SJOIN : {ke}") - - case 'SLOG': - ['@unrealircd...', ':001', 'SLOG', 'info', 'blacklist', 'BLACKLIST_HIT', ':[Blacklist]', 'IP', '162.x.x.x', 'matches', 'blacklist', 'dronebl', '(dnsbl.dronebl.org/reply=6)'] - - if not self.Base.is_valid_ip(cmd[8]): - return None + self.Utils.handle_on_sjoin(self, cmd) return None - # if self.ModConfig.local_scan == 1 and not cmd[7] in self.Config.WHITELISTED_IP: - # self.localscan_remote_ip.append(cmd[7]) - - # if self.ModConfig.psutil_scan == 1 and not cmd[7] in self.Config.WHITELISTED_IP: - # self.psutil_remote_ip.append(cmd[7]) - - # if self.ModConfig.abuseipdb_scan == 1 and not cmd[7] in self.Config.WHITELISTED_IP: - # self.abuseipdb_remote_ip.append(cmd[7]) - - # if self.ModConfig.freeipapi_scan == 1 and not cmd[7] in self.Config.WHITELISTED_IP: - # self.freeipapi_remote_ip.append(cmd[7]) - - # if self.ModConfig.cloudfilt_scan == 1 and not cmd[7] in self.Config.WHITELISTED_IP: - # self.cloudfilt_remote_ip.append(cmd[7]) + case 'SLOG': + self.Utils.handle_on_slog(self, cmd) + return None case 'NICK': - # ['@unrealircd.org...', ':001MZQ0RB', 'NICK', 'halow', '1754663712'] - # Changement de nickname - try: - uid = self.User.clean_uid(str(cmd[1])) - get_Reputation = self.Reputation.get_Reputation(uid) - jail_salon = self.Config.SALON_JAIL - service_id = self.Config.SERVICE_ID - - if get_Reputation is None: - self.Logs.debug(f'This UID: {uid} is not listed in the reputation dataclass') - return None - - # Update the new nickname - oldnick = get_Reputation.nickname - newnickname = cmd[3] - get_Reputation.nickname = newnickname - - # If ban in all channel is ON then unban old nickname an ban the new nickname - if self.ModConfig.reputation_ban_all_chan == 1: - for chan in self.Channel.UID_CHANNEL_DB: - if chan.name != jail_salon: - p.send2socket(f":{service_id} MODE {chan.name} -b {oldnick}!*@*") - p.send2socket(f":{service_id} MODE {chan.name} +b {newnickname}!*@*") - - return None - - except KeyError as ke: - self.Logs.error(f'cmd - NICK - KeyError: {ke}') + self.Utils.handle_on_nick(self, cmd) + return None case 'QUIT': - # ['@unrealircd.org...', ':001MZQ0RB', 'QUIT', ':Quit:', '....'] - ban_all_chan = self.Base.int_if_possible(self.ModConfig.reputation_ban_all_chan) - final_UID = self.User.clean_uid(str(cmd[1])) - jail_salon = self.Config.SALON_JAIL - service_id = self.Config.SERVICE_ID - get_user_reputation = self.Reputation.get_Reputation(final_UID) - - if get_user_reputation is not None: - final_nickname = get_user_reputation.nickname - for chan in self.Channel.UID_CHANNEL_DB: - if chan.name != jail_salon and ban_all_chan == 1: - p.send2socket(f":{service_id} MODE {chan.name} -b {final_nickname}!*@*") - self.Reputation.delete(final_UID) - + self.Utils.handle_on_quit(self, cmd) return None case _: @@ -1142,6 +294,7 @@ class Defender(): self.Logs.error(f"{ie} / {cmd} / length {str(len(cmd))}") except Exception as err: self.Logs.error(f"General Error: {err}") + traceback.print_exc() def hcmds(self, user:str, channel: any, cmd: list, fullcmd: list = []) -> None: @@ -1252,7 +405,7 @@ class Defender(): if self.ModConfig.autolimit == 0: self.__update_configuration('autolimit', 1) self.autolimit_isRunning = True - self.Base.create_thread(self.thread_autolimit) + self.Base.create_thread(func=thds.thread_autolimit, func_args=(self, )) self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[{self.Config.COLORS.green}AUTOLIMIT{self.Config.COLORS.nogc}] Activated", channel=self.Config.SERVICE_CHANLOG) else: self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[{self.Config.COLORS.red}AUTOLIMIT{self.Config.COLORS.nogc}] Already activated", channel=self.Config.SERVICE_CHANLOG) diff --git a/mods/defender/schemas.py b/mods/defender/schemas.py index 1a91ae6..94e004f 100644 --- a/mods/defender/schemas.py +++ b/mods/defender/schemas.py @@ -1,4 +1,4 @@ -from core.definition import MainModel, dataclass +from core.definition import MainModel, dataclass, MUser @dataclass class ModConfModel(MainModel): @@ -19,4 +19,17 @@ class ModConfModel(MainModel): flood_timer: int = 20 autolimit: int = 0 autolimit_amount: int = 3 - autolimit_interval: int = 3 \ No newline at end of file + autolimit_interval: int = 3 + +@dataclass +class FloodUser: + uid: str = None + nbr_msg: int = 0 + first_msg_time: int = 0 + +DB_FLOOD_USERS: list[FloodUser] = [] +DB_ABUSEIPDB_USERS: list[MUser] = [] +DB_FREEIPAPI_USERS: list[MUser] = [] +DB_CLOUDFILT_USERS: list[MUser] = [] +DB_PSUTIL_USERS: list[MUser] = [] +DB_LOCALSCAN_USERS: list[MUser] = [] \ No newline at end of file diff --git a/mods/defender/threads.py b/mods/defender/threads.py new file mode 100644 index 0000000..eacaa49 --- /dev/null +++ b/mods/defender/threads.py @@ -0,0 +1,167 @@ +from typing import TYPE_CHECKING +from time import sleep + +if TYPE_CHECKING: + from mods.defender.mod_defender import Defender + +def thread_apply_reputation_sanctions(uplink: 'Defender'): + while uplink.reputationTimer_isRunning: + uplink.Utils.action_apply_reputation_santions(uplink) + sleep(5) + +def thread_cloudfilt_scan(uplink: 'Defender'): + + while uplink.cloudfilt_isRunning: + list_to_remove:list = [] + for user in uplink.Schemas.DB_CLOUDFILT_USERS: + uplink.Utils.action_scan_client_with_cloudfilt(uplink, user) + list_to_remove.append(user) + sleep(1) + + for user_model in list_to_remove: + uplink.Schemas.DB_CLOUDFILT_USERS.remove(user_model) + + sleep(1) + +def thread_freeipapi_scan(uplink: 'Defender'): + + while uplink.freeipapi_isRunning: + + list_to_remove: list = [] + for user in uplink.Schemas.DB_FREEIPAPI_USERS: + uplink.Utils.action_scan_client_with_freeipapi(uplink, user) + list_to_remove.append(user) + sleep(1) + + for user_model in list_to_remove: + uplink.Schemas.DB_FREEIPAPI_USERS.remove(user_model) + + sleep(1) + +def thread_abuseipdb_scan(uplink: 'Defender'): + + while uplink.abuseipdb_isRunning: + + list_to_remove: list = [] + for user in uplink.Schemas.DB_ABUSEIPDB_USERS: + uplink.Utils.action_scan_client_with_abuseipdb(uplink, user) + list_to_remove.append(user) + sleep(1) + + for user_model in list_to_remove: + uplink.Schemas.DB_ABUSEIPDB_USERS.remove(user_model) + + sleep(1) + +def thread_local_scan(uplink: 'Defender'): + + while uplink.localscan_isRunning: + list_to_remove:list = [] + for user in uplink.Schemas.DB_LOCALSCAN_USERS: + uplink.Utils.action_scan_client_with_local_socket(uplink, user) + list_to_remove.append(user) + sleep(1) + + for user_model in list_to_remove: + uplink.Schemas.DB_LOCALSCAN_USERS.remove(user_model) + + sleep(1) + +def thread_psutil_scan(uplink: 'Defender'): + + while uplink.psutil_isRunning: + + list_to_remove:list = [] + for user in uplink.Schemas.DB_PSUTIL_USERS: + uplink.Utils.action_scan_client_with_psutil(uplink, user) + list_to_remove.append(user) + sleep(1) + + for user_model in list_to_remove: + uplink.Schemas.DB_PSUTIL_USERS.remove(user_model) + + sleep(1) + +def thread_autolimit(uplink: 'Defender'): + + if uplink.ModConfig.autolimit == 0: + uplink.Logs.debug("autolimit deactivated ... canceling the thread") + return None + + while uplink.Irc.autolimit_started: + sleep(0.2) + + uplink.Irc.autolimit_started = True + init_amount = uplink.ModConfig.autolimit_amount + p = uplink.Protocol + INIT = 1 + + # Copy Channels to a list of dict + chanObj_copy: list[dict[str, int]] = [{"name": c.name, "uids_count": len(c.uids)} for c in uplink.Channel.UID_CHANNEL_DB] + chan_list: list[str] = [c.name for c in uplink.Channel.UID_CHANNEL_DB] + + while uplink.autolimit_isRunning: + + if uplink.ModConfig.autolimit == 0: + uplink.Logs.debug("autolimit deactivated ... stopping the current thread") + break + + for chan in uplink.Channel.UID_CHANNEL_DB: + for chan_copy in chanObj_copy: + if chan_copy["name"] == chan.name and len(chan.uids) != chan_copy["uids_count"]: + p.send2socket(f":{uplink.Config.SERVICE_ID} MODE {chan.name} +l {len(chan.uids) + uplink.ModConfig.autolimit_amount}") + chan_copy["uids_count"] = len(chan.uids) + + if chan.name not in chan_list: + chan_list.append(chan.name) + chanObj_copy.append({"name": chan.name, "uids_count": 0}) + + # Verifier si un salon a été vidé + current_chan_in_list = [d.name for d in uplink.Channel.UID_CHANNEL_DB] + for c in chan_list: + if c not in current_chan_in_list: + chan_list.remove(c) + + # Si c'est la premiere execution + if INIT == 1: + for chan in uplink.Channel.UID_CHANNEL_DB: + p.send2socket(f":{uplink.Config.SERVICE_ID} MODE {chan.name} +l {len(chan.uids) + uplink.ModConfig.autolimit_amount}") + + # Si le nouveau amount est différent de l'initial + if init_amount != uplink.ModConfig.autolimit_amount: + init_amount = uplink.ModConfig.autolimit_amount + for chan in uplink.Channel.UID_CHANNEL_DB: + p.send2socket(f":{uplink.Config.SERVICE_ID} MODE {chan.name} +l {len(chan.uids) + uplink.ModConfig.autolimit_amount}") + + INIT = 0 + + if uplink.autolimit_isRunning: + sleep(uplink.ModConfig.autolimit_interval) + + for chan in uplink.Channel.UID_CHANNEL_DB: + p.send2socket(f":{uplink.Config.SERVICE_ID} MODE {chan.name} -l") + + uplink.Irc.autolimit_started = False + + return None + +def timer_release_mode_mute(uplink: 'Defender', action: str, channel: str): + """DO NOT EXECUTE THIS FUNCTION WITHOUT THREADING + + Args: + action (str): _description_ + channel (str): The related channel + + """ + service_id = uplink.Config.SERVICE_ID + + if not uplink.Channel.Is_Channel(channel): + uplink.Logs.debug(f"Channel is not valid {channel}") + return + + match action: + case 'mode-m': + # Action -m sur le salon + uplink.Protocol.send2socket(f":{service_id} MODE {channel} -m") + case _: + pass diff --git a/mods/defender/utils.py b/mods/defender/utils.py index e69de29..7b2bf07 100644 --- a/mods/defender/utils.py +++ b/mods/defender/utils.py @@ -0,0 +1,709 @@ +import socket +import psutil +import requests +import mods.defender.threads as dthreads +from json import loads +from re import match +from typing import TYPE_CHECKING, Optional +from mods.defender.schemas import FloodUser + +if TYPE_CHECKING: + from core.definition import MUser + from mods.defender.mod_defender import Defender + +def handle_on_reputation(uplink: 'Defender', srvmsg: list[str]): + """Handle reputation server message + >>> srvmsg = [':001', 'REPUTATION', '128.128.128.128', '0'] + >>> srvmsg = [':001', 'REPUTATION', '128.128.128.128', '*0'] + Args: + irc_instance (Irc): The Irc instance + srvmsg (list[str]): The Server MSG + """ + ip = srvmsg[2] + score = srvmsg[3] + + if str(ip).find('*') != -1: + # If the reputation changed, we do not need to scan the IP + return + + # Possibilité de déclancher les bans a ce niveau. + if not uplink.Base.is_valid_ip(ip): + return + +def handle_on_mode(uplink: 'Defender', srvmsg: list[str]): + """_summary_ + >>> srvmsg = ['@unrealircd.org/...', ':001C0MF01', 'MODE', '#services', '+l', '1'] + >>> srvmsg = ['...', ':001XSCU0Q', 'MODE', '#jail', '+b', '~security-group:unknown-users'] + Args: + irc_instance (Irc): The Irc instance + srvmsg (list[str]): The Server MSG + confmodel (ModConfModel): The Module Configuration + """ + irc = uplink.Irc + gconfig = uplink.Config + p = uplink.Protocol + confmodel = uplink.ModConfig + + channel = str(srvmsg[3]) + mode = str(srvmsg[4]) + group_to_check = str(srvmsg[5:]) + group_to_unban = '~security-group:unknown-users' + + if confmodel.autolimit == 1: + if mode == '+l' or mode == '-l': + chan = irc.Channel.get_Channel(channel) + p.send2socket(f":{gconfig.SERVICE_ID} MODE {chan.name} +l {len(chan.uids) + confmodel.autolimit_amount}") + + if gconfig.SALON_JAIL == channel: + if mode == '+b' and group_to_unban in group_to_check: + p.send2socket(f":{gconfig.SERVICE_ID} MODE {gconfig.SALON_JAIL} -b ~security-group:unknown-users") + p.send2socket(f":{gconfig.SERVICE_ID} MODE {gconfig.SALON_JAIL} -eee ~security-group:webirc-users ~security-group:known-users ~security-group:websocket-users") + +def handle_on_privmsg(uplink: 'Defender', srvmsg: list[str]): + # ['@mtag....',':python', 'PRIVMSG', '#defender', ':zefzefzregreg', 'regg', 'aerg'] + action_on_flood(uplink, srvmsg) + return None + +def handle_on_sjoin(uplink: 'Defender', srvmsg: list[str]): + """If Joining a new channel, it applies group bans. + + >>> srvmsg = ['@msgid..', ':001', 'SJOIN', '1702138958', '#welcome', ':0015L1AHL'] + + Args: + irc_instance (Irc): The Irc instance + srvmsg (list[str]): The Server MSG + confmodel (ModConfModel): The Module Configuration + """ + irc = uplink.Irc + p = irc.Protocol + gconfig = uplink.Config + confmodel = uplink.ModConfig + + parsed_chan = srvmsg[4] if irc.Channel.Is_Channel(srvmsg[4]) else None + parsed_UID = irc.User.clean_uid(srvmsg[5]) + + if parsed_chan is None or parsed_UID is None: + return + + if confmodel.reputation == 1: + get_reputation = irc.Reputation.get_Reputation(parsed_UID) + + if parsed_chan != gconfig.SALON_JAIL: + p.send2socket(f":{gconfig.SERVICE_ID} MODE {parsed_chan} +b ~security-group:unknown-users") + p.send2socket(f":{gconfig.SERVICE_ID} MODE {parsed_chan} +eee ~security-group:webirc-users ~security-group:known-users ~security-group:websocket-users") + + if get_reputation is not None: + isWebirc = get_reputation.isWebirc + + if not isWebirc: + if parsed_chan != gconfig.SALON_JAIL: + p.send_sapart(nick_to_sapart=get_reputation.nickname, channel_name=parsed_chan) + + if confmodel.reputation_ban_all_chan == 1 and not isWebirc: + if parsed_chan != gconfig.SALON_JAIL: + p.send2socket(f":{gconfig.SERVICE_ID} MODE {parsed_chan} +b {get_reputation.nickname}!*@*") + p.send2socket(f":{gconfig.SERVICE_ID} KICK {parsed_chan} {get_reputation.nickname}") + + irc.Logs.debug(f'SJOIN parsed_uid : {parsed_UID}') + +def handle_on_slog(uplink: 'Defender', srvmsg: list[str]): + """Handling SLOG messages + >>> srvmsg = ['@unrealircd...', ':001', 'SLOG', 'info', 'blacklist', 'BLACKLIST_HIT', ':[Blacklist]', 'IP', '162.x.x.x', 'matches', 'blacklist', 'dronebl', '(dnsbl.dronebl.org/reply=6)'] + Args: + irc_instance (Irc): The Irc instance + srvmsg (list[str]): The Server MSG + confmodel (ModConfModel): The Module Configuration + """ + ['@unrealircd...', ':001', 'SLOG', 'info', 'blacklist', 'BLACKLIST_HIT', ':[Blacklist]', 'IP', '162.x.x.x', 'matches', 'blacklist', 'dronebl', '(dnsbl.dronebl.org/reply=6)'] + + if not uplink.Base.is_valid_ip(srvmsg[8]): + return None + + # if self.ModConfig.local_scan == 1 and not cmd[7] in self.Config.WHITELISTED_IP: + # self.localscan_remote_ip.append(cmd[7]) + + # if self.ModConfig.psutil_scan == 1 and not cmd[7] in self.Config.WHITELISTED_IP: + # self.psutil_remote_ip.append(cmd[7]) + + # if self.ModConfig.abuseipdb_scan == 1 and not cmd[7] in self.Config.WHITELISTED_IP: + # self.abuseipdb_remote_ip.append(cmd[7]) + + # if self.ModConfig.freeipapi_scan == 1 and not cmd[7] in self.Config.WHITELISTED_IP: + # self.freeipapi_remote_ip.append(cmd[7]) + + # if self.ModConfig.cloudfilt_scan == 1 and not cmd[7] in self.Config.WHITELISTED_IP: + # self.cloudfilt_remote_ip.append(cmd[7]) + + return None + +def handle_on_nick(uplink: 'Defender', srvmsg: list[str]): + """_summary_ + >>> srvmsg = ['@unrealircd.org...', ':001MZQ0RB', 'NICK', 'newnickname', '1754663712'] + Args: + irc_instance (Irc): The Irc instance + srvmsg (list[str]): The Server MSG + confmodel (ModConfModel): The Module Configuration + """ + uid = uplink.User.clean_uid(str(srvmsg[1])) + p = uplink.Protocol + confmodel = uplink.ModConfig + + get_reputation = uplink.Reputation.get_Reputation(uid) + jail_salon = uplink.Config.SALON_JAIL + service_id = uplink.Config.SERVICE_ID + + if get_reputation is None: + uplink.Logs.debug(f'This UID: {uid} is not listed in the reputation dataclass') + return None + + # Update the new nickname + oldnick = get_reputation.nickname + newnickname = srvmsg[3] + get_reputation.nickname = newnickname + + # If ban in all channel is ON then unban old nickname an ban the new nickname + if confmodel.reputation_ban_all_chan == 1: + for chan in uplink.Channel.UID_CHANNEL_DB: + if chan.name != jail_salon: + p.send2socket(f":{service_id} MODE {chan.name} -b {oldnick}!*@*") + p.send2socket(f":{service_id} MODE {chan.name} +b {newnickname}!*@*") + +def handle_on_quit(uplink: 'Defender', srvmsg: list[str]): + """_summary_ + >>> srvmsg = ['@unrealircd.org...', ':001MZQ0RB', 'QUIT', ':Quit:', 'quit message'] + Args: + uplink (Irc): The Defender Module instance + srvmsg (list[str]): The Server MSG + """ + p = uplink.Protocol + confmodel = uplink.ModConfig + + ban_all_chan = uplink.Base.int_if_possible(confmodel.reputation_ban_all_chan) + final_UID = uplink.User.clean_uid(str(srvmsg[1])) + jail_salon = uplink.Config.SALON_JAIL + service_id = uplink.Config.SERVICE_ID + get_user_reputation = uplink.Reputation.get_Reputation(final_UID) + + if get_user_reputation is not None: + final_nickname = get_user_reputation.nickname + for chan in uplink.Channel.UID_CHANNEL_DB: + if chan.name != jail_salon and ban_all_chan == 1: + p.send2socket(f":{service_id} MODE {chan.name} -b {final_nickname}!*@*") + uplink.Logs.debug(f"Mode -b {final_nickname} on channel {chan.name}") + + uplink.Reputation.delete(final_UID) + uplink.Logs.debug(f"Client {get_user_reputation.nickname} has been removed from Reputation local DB") + +def handle_on_uid(uplink: 'Defender', srvmsg: list[str]): + """_summary_ + >>> ['@s2s-md...', ':001', 'UID', 'nickname', '0', '1754675249', '...', '125-168-141-239.hostname.net', '001BAPN8M', + '0', '+iwx', '*', '32001BBE.25ACEFE7.429FE90D.IP', 'ZA2ic7w==', ':realname'] + + Args: + uplink (Defender): The Defender instance + srvmsg (list[str]): The Server MSG + """ + gconfig = uplink.Config + irc = uplink.Irc + confmodel = uplink.ModConfig + + # If Init then do nothing + if gconfig.DEFENDER_INIT == 1: + return None + + # Get User information + _User = irc.User.get_User(str(srvmsg[8])) + + if _User is None: + irc.Logs.warning(f'This UID: [{srvmsg[8]}] is not available please check why') + return + + # If user is not service or IrcOp then scan them + if not match(r'^.*[S|o?].*$', _User.umodes): + uplink.Schemas.DB_ABUSEIPDB_USERS.append(_User) if confmodel.abuseipdb_scan == 1 and _User.remote_ip not in gconfig.WHITELISTED_IP else None + uplink.Schemas.DB_FREEIPAPI_USERS.append(_User) if confmodel.freeipapi_scan == 1 and _User.remote_ip not in gconfig.WHITELISTED_IP else None + uplink.Schemas.DB_CLOUDFILT_USERS.append(_User) if confmodel.cloudfilt_scan == 1 and _User.remote_ip not in gconfig.WHITELISTED_IP else None + uplink.Schemas.DB_PSUTIL_USERS.append(_User) if confmodel.psutil_scan == 1 and _User.remote_ip not in gconfig.WHITELISTED_IP else None + uplink.Schemas.DB_LOCALSCAN_USERS.append(_User) if confmodel.local_scan == 1 and _User.remote_ip not in gconfig.WHITELISTED_IP else None + + reputation_flag = confmodel.reputation + reputation_seuil = confmodel.reputation_seuil + + if gconfig.DEFENDER_INIT == 0: + # Si le user n'es pas un service ni un IrcOP + if not match(r'^.*[S|o?].*$', _User.umodes): + if reputation_flag == 1 and _User.score_connexion <= reputation_seuil: + # currentDateTime = self.Base.get_datetime() + irc.Reputation.insert( + irc.Loader.Definition.MReputation( + **_User.to_dict(), + secret_code=irc.Base.get_random(8) + ) + ) + if irc.Reputation.is_exist(_User.uid): + if reputation_flag == 1 and _User.score_connexion <= reputation_seuil: + action_add_reputation_sanctions(uplink, _User.uid) + irc.Logs.info(f'[REPUTATION] Reputation system ON (Nickname: {_User.nickname}, uid: {_User.uid})') + +#################### +# ACTION FUNCTIONS # +#################### + +def action_on_flood(uplink: 'Defender', srvmsg: list[str]): + + confmodel = uplink.ModConfig + if confmodel.flood == 0: + return None + + irc = uplink.Irc + gconfig = uplink.Config + p = uplink.Protocol + flood_users = uplink.Schemas.DB_FLOOD_USERS + + user_trigger = str(srvmsg[1]).replace(':','') + channel = srvmsg[3] + User = irc.User.get_User(user_trigger) + + if User is None or not irc.Channel.Is_Channel(channelToCheck=channel): + return + + flood_time = confmodel.flood_time + flood_message = confmodel.flood_message + flood_timer = confmodel.flood_timer + service_id = gconfig.SERVICE_ID + dnickname = gconfig.SERVICE_NICKNAME + color_red = gconfig.COLORS.red + color_bold = gconfig.COLORS.bold + + get_detected_uid = User.uid + get_detected_nickname = User.nickname + unixtime = irc.Base.get_unixtime() + get_diff_secondes = 0 + + def get_flood_user(uid: str) -> Optional[FloodUser]: + for flood_user in flood_users: + if flood_user.uid == uid: + return flood_user + + fu = get_flood_user(get_detected_uid) + if fu is None: + fu = FloodUser(get_detected_uid, 0, unixtime) + flood_users.append(fu) + + fu.nbr_msg += 1 + + get_diff_secondes = unixtime - fu.first_msg_time + if get_diff_secondes > flood_time: + fu.first_msg_time = unixtime + fu.nbr_msg = 0 + get_diff_secondes = unixtime - fu.first_msg_time + elif fu.nbr_msg > flood_message: + irc.Logs.info('system de flood detecté') + p.send_priv_msg( + nick_from=dnickname, + msg=f"{color_red} {color_bold} Flood detected. Apply the +m mode (Ô_o)", + channel=channel + ) + p.send2socket(f":{service_id} MODE {channel} +m") + irc.Logs.info(f'FLOOD Détecté sur {get_detected_nickname} mode +m appliqué sur le salon {channel}') + fu.nbr_msg = 0 + fu.first_msg_time = unixtime + irc.Base.create_timer(flood_timer, dthreads.timer_release_mode_mute, (uplink, 'mode-m', channel)) + +def action_add_reputation_sanctions(uplink: 'Defender', jailed_uid: str ): + + irc = uplink.Irc + gconfig = uplink.Config + p = uplink.Protocol + confmodel = uplink.ModConfig + + get_reputation = irc.Reputation.get_Reputation(jailed_uid) + + if get_reputation is None: + irc.Logs.warning(f'UID {jailed_uid} has not been found') + return + + salon_logs = gconfig.SERVICE_CHANLOG + salon_jail = gconfig.SALON_JAIL + + code = get_reputation.secret_code + jailed_nickname = get_reputation.nickname + jailed_score = get_reputation.score_connexion + + color_red = gconfig.COLORS.red + color_black = gconfig.COLORS.black + color_bold = gconfig.COLORS.bold + nogc = gconfig.COLORS.nogc + service_id = gconfig.SERVICE_ID + service_prefix = gconfig.SERVICE_PREFIX + reputation_ban_all_chan = confmodel.reputation_ban_all_chan + + if not get_reputation.isWebirc: + # Si le user ne vient pas de webIrc + p.send_sajoin(nick_to_sajoin=jailed_nickname, channel_name=salon_jail) + p.send_priv_msg(nick_from=gconfig.SERVICE_NICKNAME, + msg=f" [{color_red} REPUTATION {nogc}] : Connexion de {jailed_nickname} ({jailed_score}) ==> {salon_jail}", + channel=salon_logs + ) + p.send_notice( + nick_from=gconfig.SERVICE_NICKNAME, + nick_to=jailed_nickname, + msg=f"[{color_red} {jailed_nickname} {color_black}] : Merci de tapez la commande suivante {color_bold}{service_prefix}code {code}{color_bold}" + ) + if reputation_ban_all_chan == 1: + for chan in irc.Channel.UID_CHANNEL_DB: + if chan.name != salon_jail: + p.send2socket(f":{service_id} MODE {chan.name} +b {jailed_nickname}!*@*") + p.send2socket(f":{service_id} KICK {chan.name} {jailed_nickname}") + + irc.Logs.info(f"[REPUTATION] {jailed_nickname} jailed (UID: {jailed_uid}, score: {jailed_score})") + else: + irc.Logs.info(f"[REPUTATION] {jailed_nickname} skipped (trusted or WebIRC)") + irc.Reputation.delete(jailed_uid) + +def action_apply_reputation_santions(uplink: 'Defender') -> None: + + irc = uplink.Irc + gconfig = uplink.Config + p = uplink.Protocol + confmodel = uplink.ModConfig + + reputation_flag = confmodel.reputation + reputation_timer = confmodel.reputation_timer + reputation_seuil = confmodel.reputation_seuil + ban_all_chan = confmodel.reputation_ban_all_chan + service_id = gconfig.SERVICE_ID + dchanlog = gconfig.SERVICE_CHANLOG + color_red = gconfig.COLORS.red + nogc = gconfig.COLORS.nogc + salon_jail = gconfig.SALON_JAIL + + if reputation_flag == 0: + return None + elif reputation_timer == 0: + return None + + uid_to_clean = [] + + for user in irc.Reputation.UID_REPUTATION_DB: + if not user.isWebirc: # Si il ne vient pas de WebIRC + if irc.User.get_user_uptime_in_minutes(user.uid) >= reputation_timer and int(user.score_connexion) <= int(reputation_seuil): + p.send_priv_msg( + nick_from=service_id, + msg=f"[{color_red} REPUTATION {nogc}] : Action sur {user.nickname} aprés {str(reputation_timer)} minutes d'inactivité", + channel=dchanlog + ) + p.send2socket(f":{service_id} KILL {user.nickname} After {str(reputation_timer)} minutes of inactivity you should reconnect and type the password code") + p.send2socket(f":{gconfig.SERVEUR_LINK} REPUTATION {user.remote_ip} 0") + + irc.Logs.info(f"Nickname: {user.nickname} KILLED after {str(reputation_timer)} minutes of inactivity") + uid_to_clean.append(user.uid) + + for uid in uid_to_clean: + # Suppression des éléments dans {UID_DB} et {REPUTATION_DB} + for chan in irc.Channel.UID_CHANNEL_DB: + if chan.name != salon_jail and ban_all_chan == 1: + get_user_reputation = irc.Reputation.get_Reputation(uid) + p.send2socket(f":{service_id} MODE {chan.name} -b {get_user_reputation.nickname}!*@*") + + # Lorsqu'un utilisateur quitte, il doit être supprimé de {UID_DB}. + irc.Channel.delete_user_from_all_channel(uid) + irc.Reputation.delete(uid) + irc.User.delete(uid) + +def action_scan_client_with_cloudfilt(uplink: 'Defender', user_model: 'MUser') -> Optional[dict[str, str]]: + """Analyse l'ip avec cloudfilt + Cette methode devra etre lancer toujours via un thread ou un timer. + Args: + uplink (Defender): Defender Instance + + Returns: + dict[str, any] | None: les informations du provider + keys : 'countryCode', 'isProxy' + """ + + remote_ip = user_model.remote_ip + username = user_model.username + hostname = user_model.hostname + nickname = user_model.nickname + p = uplink.Protocol + + if remote_ip in uplink.Config.WHITELISTED_IP: + return None + if uplink.ModConfig.cloudfilt_scan == 0: + return None + if uplink.cloudfilt_key == '': + return None + + service_id = uplink.Config.SERVICE_ID + service_chanlog = uplink.Config.SERVICE_CHANLOG + color_red = uplink.Config.COLORS.red + nogc = uplink.Config.COLORS.nogc + + url = "https://developers18334.cloudfilt.com/" + + data = { + 'ip': remote_ip, + 'key': uplink.cloudfilt_key + } + + response = requests.post(url=url, data=data) + # Formatted output + decoded_response: dict = loads(response.text) + status_code = response.status_code + if status_code != 200: + uplink.Logs.warning(f'Error connecting to cloudfilt API | Code: {str(status_code)}') + return + + result = { + 'countryiso': decoded_response.get('countryiso', None), + 'listed': decoded_response.get('listed', None), + 'listed_by': decoded_response.get('listed_by', None), + 'host': decoded_response.get('host', None) + } + + # pseudo!ident@host + fullname = f'{nickname}!{username}@{hostname}' + + p.send_priv_msg( + nick_from=service_id, + msg=f"[ {color_red}CLOUDFILT_SCAN{nogc} ] : Connexion de {fullname} ({remote_ip}) ==> Host: {str(result['host'])} | country: {str(result['countryiso'])} | listed: {str(result['listed'])} | listed by : {str(result['listed_by'])}", + channel=service_chanlog) + + uplink.Logs.debug(f"[CLOUDFILT SCAN] ({fullname}) connected from ({result['countryiso']}), Listed: {result['listed']}, by: {result['listed_by']}") + + if result['listed']: + p.send2socket(f":{service_id} GLINE +*@{remote_ip} {uplink.Config.GLINE_DURATION} Your connexion is listed as dangerous {str(result['listed'])} {str(result['listed_by'])} - detected by cloudfilt") + uplink.Logs.debug(f"[CLOUDFILT SCAN GLINE] Dangerous connection ({fullname}) from ({result['countryiso']}) Listed: {result['listed']}, by: {result['listed_by']}") + + response.close() + + return result + +def action_scan_client_with_freeipapi(uplink: 'Defender', user_model: 'MUser') -> Optional[dict[str, str]]: + """Analyse l'ip avec Freeipapi + Cette methode devra etre lancer toujours via un thread ou un timer. + Args: + uplink (Defender): The Defender object Instance + + Returns: + dict[str, any] | None: les informations du provider + keys : 'countryCode', 'isProxy' + """ + p = uplink.Protocol + remote_ip = user_model.remote_ip + username = user_model.username + hostname = user_model.hostname + nickname = user_model.nickname + + if remote_ip in uplink.Config.WHITELISTED_IP: + return None + if uplink.ModConfig.freeipapi_scan == 0: + return None + + service_id = uplink.Config.SERVICE_ID + service_chanlog = uplink.Config.SERVICE_CHANLOG + color_red = uplink.Config.COLORS.red + nogc = uplink.Config.COLORS.nogc + + url = f'https://freeipapi.com/api/json/{remote_ip}' + + headers = { + 'Accept': 'application/json', + } + + response = requests.request(method='GET', url=url, headers=headers, timeout=uplink.timeout) + + # Formatted output + decoded_response: dict = loads(response.text) + + status_code = response.status_code + if status_code == 429: + uplink.Logs.warning('Too Many Requests - The rate limit for the API has been exceeded.') + return None + elif status_code != 200: + uplink.Logs.warning(f'status code = {str(status_code)}') + return None + + result = { + 'countryCode': decoded_response.get('countryCode', None), + 'isProxy': decoded_response.get('isProxy', None) + } + + # pseudo!ident@host + fullname = f'{nickname}!{username}@{hostname}' + + p.send_priv_msg( + nick_from=service_id, + msg=f"[ {color_red}FREEIPAPI_SCAN{nogc} ] : Connexion de {fullname} ({remote_ip}) ==> Proxy: {str(result['isProxy'])} | Country : {str(result['countryCode'])}", + channel=service_chanlog) + uplink.Logs.debug(f"[FREEIPAPI SCAN] ({fullname}) connected from ({result['countryCode']}), Proxy: {result['isProxy']}") + + if result['isProxy']: + p.send2socket(f":{service_id} GLINE +*@{remote_ip} {uplink.Config.GLINE_DURATION} This server do not allow proxy connexions {str(result['isProxy'])} - detected by freeipapi") + uplink.Logs.debug(f"[FREEIPAPI SCAN GLINE] Server do not allow proxy connexions {result['isProxy']}") + + response.close() + + return result + +def action_scan_client_with_abuseipdb(uplink: 'Defender', user_model: 'MUser') -> Optional[dict[str, str]]: + """Analyse l'ip avec AbuseIpDB + Cette methode devra etre lancer toujours via un thread ou un timer. + Args: + uplink (Defender): Defender instance object + user_model (MUser): l'objet User qui contient l'ip + + Returns: + dict[str, str] | None: les informations du provider + """ + p = uplink.Protocol + remote_ip = user_model.remote_ip + username = user_model.username + hostname = user_model.hostname + nickname = user_model.nickname + + if remote_ip in uplink.Config.WHITELISTED_IP: + return None + if uplink.ModConfig.abuseipdb_scan == 0: + return None + + if uplink.abuseipdb_key == '': + return None + + url = 'https://api.abuseipdb.com/api/v2/check' + querystring = { + 'ipAddress': remote_ip, + 'maxAgeInDays': '90' + } + + headers = { + 'Accept': 'application/json', + 'Key': uplink.abuseipdb_key + } + + response = requests.request(method='GET', url=url, headers=headers, params=querystring, timeout=uplink.timeout) + + # Formatted output + decoded_response: dict[str, dict] = loads(response.text) + + if 'data' not in decoded_response: + return None + + result = { + 'score': decoded_response.get('data', {}).get('abuseConfidenceScore', 0), + 'country': decoded_response.get('data', {}).get('countryCode', None), + 'isTor': decoded_response.get('data', {}).get('isTor', None), + 'totalReports': decoded_response.get('data', {}).get('totalReports', 0) + } + + service_id = uplink.Config.SERVICE_ID + service_chanlog = uplink.Config.SERVICE_CHANLOG + color_red = uplink.Config.COLORS.red + nogc = uplink.Config.COLORS.nogc + + # pseudo!ident@host + fullname = f'{nickname}!{username}@{hostname}' + + p.send_priv_msg( + nick_from=service_id, + msg=f"[ {color_red}ABUSEIPDB_SCAN{nogc} ] : Connexion de {fullname} ({remote_ip}) ==> Score: {str(result['score'])} | Country : {result['country']} | Tor : {str(result['isTor'])} | Total Reports : {str(result['totalReports'])}", + channel=service_chanlog + ) + uplink.Logs.debug(f"[ABUSEIPDB SCAN] ({fullname}) connected from ({result['country']}), Score: {result['score']}, Tor: {result['isTor']}") + + if result['isTor']: + p.send2socket(f":{service_id} GLINE +*@{remote_ip} {uplink.Config.GLINE_DURATION} This server do not allow Tor connexions {str(result['isTor'])} - Detected by Abuseipdb") + uplink.Logs.debug(f"[ABUSEIPDB SCAN GLINE] Server do not allow Tor connections Tor: {result['isTor']}, Score: {result['score']}") + elif result['score'] >= 95: + p.send2socket(f":{service_id} GLINE +*@{remote_ip} {uplink.Config.GLINE_DURATION} You were banned from this server because your abuse score is = {str(result['score'])} - Detected by Abuseipdb") + uplink.Logs.debug(f"[ABUSEIPDB SCAN GLINE] Server do not high risk connections Country: {result['country']}, Score: {result['score']}") + + response.close() + + return result + +def action_scan_client_with_local_socket(uplink: 'Defender', user_model: 'MUser'): + """local_scan + + Args: + uplink (Defender): Defender instance object + user_model (MUser): l'objet User qui contient l'ip + """ + p = uplink.Protocol + remote_ip = user_model.remote_ip + username = user_model.username + hostname = user_model.hostname + nickname = user_model.nickname + fullname = f'{nickname}!{username}@{hostname}' + + if remote_ip in uplink.Config.WHITELISTED_IP: + return None + + for port in uplink.Config.PORTS_TO_SCAN: + try: + newSocket = '' + newSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM or socket.SOCK_NONBLOCK) + newSocket.settimeout(0.5) + + connection = (remote_ip, uplink.Base.int_if_possible(port)) + newSocket.connect(connection) + + p.send_priv_msg( + nick_from=uplink.Config.SERVICE_NICKNAME, + msg=f"[ {uplink.Config.COLORS.red}PROXY_SCAN{uplink.Config.COLORS.nogc} ] {fullname} ({remote_ip}) : Port [{str(port)}] ouvert sur l'adresse ip [{remote_ip}]", + channel=uplink.Config.SERVICE_CHANLOG + ) + # print(f"=======> Le port {str(port)} est ouvert !!") + uplink.Base.running_sockets.append(newSocket) + # print(newSocket) + newSocket.shutdown(socket.SHUT_RDWR) + newSocket.close() + + except (socket.timeout, ConnectionRefusedError): + uplink.Logs.info(f"Le port {remote_ip}:{str(port)} est fermé") + except AttributeError as ae: + uplink.Logs.warning(f"AttributeError ({remote_ip}): {ae}") + except socket.gaierror as err: + uplink.Logs.warning(f"Address Info Error ({remote_ip}): {err}") + finally: + # newSocket.shutdown(socket.SHUT_RDWR) + newSocket.close() + uplink.Logs.info('=======> Fermeture de la socket') + +def action_scan_client_with_psutil(uplink: 'Defender', user_model: 'MUser') -> list[int]: + """psutil_scan for Linux (should be run on the same location as the unrealircd server) + + Args: + userModel (UserModel): The User Model Object + + Returns: + list[int]: list of ports + """ + p = uplink.Protocol + remote_ip = user_model.remote_ip + username = user_model.username + hostname = user_model.hostname + nickname = user_model.nickname + + if remote_ip in uplink.Config.WHITELISTED_IP: + return None + + try: + connections = psutil.net_connections(kind='inet') + fullname = f'{nickname}!{username}@{hostname}' + + matching_ports = [conn.raddr.port for conn in connections if conn.raddr and conn.raddr.ip == remote_ip] + uplink.Logs.info(f"Connexion of {fullname} ({remote_ip}) using ports : {str(matching_ports)}") + + if matching_ports: + p.send_priv_msg( + nick_from=uplink.Config.SERVICE_NICKNAME, + msg=f"[ {uplink.Config.COLORS.red}PSUTIL_SCAN{uplink.Config.COLORS.black} ] {fullname} ({remote_ip}) : is using ports {matching_ports}", + channel=uplink.Config.SERVICE_CHANLOG + ) + + return matching_ports + + except psutil.AccessDenied as ad: + uplink.Logs.critical(f'psutil_scan: Permission error: {ad}') \ No newline at end of file From c0cd4db3af64ab4f97835fff659b27258692b72e Mon Sep 17 00:00:00 2001 From: adator <85586985+adator85@users.noreply.github.com> Date: Sat, 9 Aug 2025 03:43:44 +0200 Subject: [PATCH 03/20] updating namings to stay coherent --- core/classes/reputation.py | 55 +++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/core/classes/reputation.py b/core/classes/reputation.py index 64a2b27..75f431b 100644 --- a/core/classes/reputation.py +++ b/core/classes/reputation.py @@ -1,4 +1,4 @@ -from typing import Union +from typing import Optional from core.definition import MReputation from core.base import Base @@ -13,11 +13,11 @@ class Reputation: return None - def insert(self, newReputationUser: MReputation) -> bool: + def insert(self, new_reputation_user: MReputation) -> bool: """Insert a new Reputation User object Args: - newReputationUser (MReputation): New Reputation Model object + new_reputation_user (MReputation): New Reputation Model object Returns: bool: True if inserted @@ -26,23 +26,23 @@ class Reputation: exist = False for record in self.UID_REPUTATION_DB: - if record.uid == newReputationUser.uid: + if record.uid == new_reputation_user.uid: # If the user exist then return False and do not go further exist = True self.Logs.debug(f'{record.uid} already exist') return result if not exist: - self.UID_REPUTATION_DB.append(newReputationUser) + self.UID_REPUTATION_DB.append(new_reputation_user) result = True - self.Logs.debug(f'New Reputation User Captured: ({newReputationUser})') + self.Logs.debug(f'New Reputation User Captured: ({new_reputation_user})') if not result: - self.Logs.critical(f'The Reputation User Object was not inserted {newReputationUser}') + self.Logs.critical(f'The Reputation User Object was not inserted {new_reputation_user}') return result - def update(self, uid: str, newNickname: str) -> bool: + def update(self, uid: str, new_nickname: str) -> bool: """Update the nickname starting from the UID Args: @@ -53,12 +53,12 @@ class Reputation: bool: True if updated """ - reputationObj = self.get_Reputation(uid) + reputation_obj = self.get_Reputation(uid) - if reputationObj is None: + if reputation_obj is None: return False - reputationObj.nickname = newNickname + reputation_obj.nickname = new_nickname return True @@ -89,7 +89,7 @@ class Reputation: return result - def get_Reputation(self, uidornickname: str) -> Union[MReputation, None]: + def get_Reputation(self, uidornickname: str) -> Optional[MReputation]: """Get The User Object model Args: @@ -98,16 +98,15 @@ class Reputation: Returns: UserModel|None: The UserModel Object | None """ - User = None for record in self.UID_REPUTATION_DB: if record.uid == uidornickname: - User = record + return record elif record.nickname == uidornickname: - User = record + return record - return User + return None - def get_uid(self, uidornickname:str) -> Union[str, None]: + def get_uid(self, uidornickname: str) -> Optional[str]: """Get the UID of the user starting from the UID or the Nickname Args: @@ -117,14 +116,14 @@ class Reputation: str|None: Return the UID """ - reputationObj = self.get_Reputation(uidornickname) + reputation_obj = self.get_Reputation(uidornickname) - if reputationObj is None: + if reputation_obj is None: return None - return reputationObj.uid + return reputation_obj.uid - def get_nickname(self, uidornickname:str) -> Union[str, None]: + def get_nickname(self, uidornickname: str) -> Optional[str]: """Get the Nickname starting from UID or the nickname Args: @@ -133,12 +132,12 @@ class Reputation: Returns: str|None: the nickname """ - reputationObj = self.get_Reputation(uidornickname) + reputation_obj = self.get_Reputation(uidornickname) - if reputationObj is None: + if reputation_obj is None: return None - return reputationObj.nickname + return reputation_obj.nickname def is_exist(self, uidornickname: str) -> bool: """Check if the UID or the nickname exist in the reputation DB @@ -150,9 +149,9 @@ class Reputation: bool: True if exist """ - reputationObj = self.get_Reputation(uidornickname) + reputation_obj = self.get_Reputation(uidornickname) - if reputationObj is None: - return False - else: + if isinstance(reputation_obj, MReputation): return True + + return False From f3fe3c43cbf5af1842b468ed589fd48132720002 Mon Sep 17 00:00:00 2001 From: adator <85586985+adator85@users.noreply.github.com> Date: Sat, 9 Aug 2025 03:48:56 +0200 Subject: [PATCH 04/20] updating namings in admin.py to stay coherent --- core/classes/admin.py | 78 ++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 45 deletions(-) diff --git a/core/classes/admin.py b/core/classes/admin.py index b1bd63f..96fbfa0 100644 --- a/core/classes/admin.py +++ b/core/classes/admin.py @@ -1,4 +1,4 @@ -from typing import Union +from typing import Optional, Union from core.base import Base import core.definition as df @@ -6,121 +6,109 @@ class Admin: UID_ADMIN_DB: list[df.MAdmin] = [] - def __init__(self, baseObj: Base) -> None: - self.Logs = baseObj.logs - pass + def __init__(self, base: Base) -> None: + self.Logs = base.logs - def insert(self, newAdmin: df.MAdmin) -> bool: + def insert(self, new_admin: df.MAdmin) -> bool: result = False exist = False for record in self.UID_ADMIN_DB: - if record.uid == newAdmin.uid: + if record.uid == new_admin.uid: # If the admin exist then return False and do not go further exist = True self.Logs.debug(f'{record.uid} already exist') return result if not exist: - self.UID_ADMIN_DB.append(newAdmin) + self.UID_ADMIN_DB.append(new_admin) result = True - self.Logs.debug(f'UID ({newAdmin.uid}) has been created') + self.Logs.debug(f'UID ({new_admin.uid}) has been created') if not result: - self.Logs.critical(f'The User Object was not inserted {newAdmin}') + self.Logs.critical(f'The User Object was not inserted {new_admin}') return result - def update_nickname(self, uid: str, newNickname: str) -> bool: + def update_nickname(self, uid: str, new_admin_nickname: str) -> bool: result = False for record in self.UID_ADMIN_DB: if record.uid == uid: # If the admin exist, update and do not go further - record.nickname = newNickname + record.nickname = new_admin_nickname result = True - self.Logs.debug(f'UID ({record.uid}) has been updated with new nickname {newNickname}') + self.Logs.debug(f'UID ({record.uid}) has been updated with new nickname {new_admin_nickname}') return result if not result: - self.Logs.debug(f'The new nickname {newNickname} was not updated, uid = {uid} - The Client is not an admin') + self.Logs.debug(f'The new nickname {new_admin_nickname} was not updated, uid = {uid} - The Client is not an admin') return result - def update_level(self, nickname: str, newLevel: int) -> bool: + def update_level(self, nickname: str, new_admin_level: int) -> bool: result = False for record in self.UID_ADMIN_DB: if record.nickname == nickname: # If the admin exist, update and do not go further - record.level = newLevel + record.level = new_admin_level result = True - self.Logs.debug(f'Admin ({record.nickname}) has been updated with new level {newLevel}') + self.Logs.debug(f'Admin ({record.nickname}) has been updated with new level {new_admin_level}') return result if not result: - self.Logs.debug(f'The new level {newLevel} was not updated, nickname = {nickname} - The Client is not an admin') + self.Logs.debug(f'The new level {new_admin_level} was not updated, nickname = {nickname} - The Client is not an admin') return result def delete(self, uidornickname: str) -> bool: - result = False - for record in self.UID_ADMIN_DB: if record.uid == uidornickname: # If the admin exist, delete and do not go further self.UID_ADMIN_DB.remove(record) - result = True self.Logs.debug(f'UID ({record.uid}) has been deleted') - return result + return True if record.nickname == uidornickname: # If the admin exist, delete and do not go further self.UID_ADMIN_DB.remove(record) - result = True self.Logs.debug(f'nickname ({record.nickname}) has been deleted') - return result + return True - if not result: - self.Logs.critical(f'The UID {uidornickname} was not deleted') + self.Logs.critical(f'The UID {uidornickname} was not deleted') - return result + return False - def get_Admin(self, uidornickname: str) -> Union[df.MAdmin, None]: + def get_Admin(self, uidornickname: str) -> Optional[df.MAdmin]: - Admin = None for record in self.UID_ADMIN_DB: if record.uid == uidornickname: - Admin = record + return record elif record.nickname == uidornickname: - Admin = record + return record - #self.Logs.debug(f'Search {uidornickname} -- result = {Admin}') + return None - return Admin + def get_uid(self, uidornickname:str) -> Optional[str]: - def get_uid(self, uidornickname:str) -> Union[str, None]: - - uid = None for record in self.UID_ADMIN_DB: if record.uid == uidornickname: - uid = record.uid + return record.uid if record.nickname == uidornickname: - uid = record.uid + return record.uid - self.Logs.debug(f'The UID that you are looking for {uidornickname} has been found {uid}') - return uid + return None - def get_nickname(self, uidornickname:str) -> Union[str, None]: + def get_nickname(self, uidornickname:str) -> Optional[str]: - nickname = None for record in self.UID_ADMIN_DB: if record.nickname == uidornickname: - nickname = record.nickname + return record.nickname if record.uid == uidornickname: - nickname = record.nickname - self.Logs.debug(f'The value {uidornickname} -- {nickname}') - return nickname \ No newline at end of file + return record.nickname + + return None \ No newline at end of file From 5629dcfde6f99900d46a5b2b6b208b53347f26b8 Mon Sep 17 00:00:00 2001 From: adator <85586985+adator85@users.noreply.github.com> Date: Sat, 9 Aug 2025 03:53:57 +0200 Subject: [PATCH 05/20] updating namings in user.py and channel.py to stay coherent --- core/classes/channel.py | 10 +++--- core/classes/user.py | 71 ++++++++++++++++++++--------------------- 2 files changed, 40 insertions(+), 41 deletions(-) diff --git a/core/classes/channel.py b/core/classes/channel.py index 77b2001..9e134f1 100644 --- a/core/classes/channel.py +++ b/core/classes/channel.py @@ -185,29 +185,29 @@ class Channel: return channel_obj.to_dict() - def Is_Channel(self, channelToCheck: str) -> bool: + def Is_Channel(self, channel_to_check: str) -> bool: """Check if the string has the # caractere and return True if this is a channel Args: - channelToCheck (str): The string to test if it is a channel or not + channel_to_check (str): The string to test if it is a channel or not Returns: bool: True if the string is a channel / False if this is not a channel """ try: - if channelToCheck is None: + if channel_to_check is None: return False pattern = fr'^#' - isChannel = findall(pattern, channelToCheck) + isChannel = findall(pattern, channel_to_check) if not isChannel: return False else: return True except TypeError as te: - self.Logs.error(f'TypeError: [{channelToCheck}] - {te}') + self.Logs.error(f'TypeError: [{channel_to_check}] - {te}') except Exception as err: self.Logs.error(f'Error Not defined: {err}') diff --git a/core/classes/user.py b/core/classes/user.py index 9d2951e..acce7a4 100644 --- a/core/classes/user.py +++ b/core/classes/user.py @@ -18,7 +18,7 @@ class User: return None - def insert(self, newUser: 'MUser') -> bool: + def insert(self, new_user: 'MUser') -> bool: """Insert a new User object Args: @@ -28,32 +28,32 @@ class User: bool: True if inserted """ - userObj = self.get_User(newUser.uid) + user_obj = self.get_User(new_user.uid) - if not userObj is None: + if not user_obj is None: # User already created return False return False - self.UID_DB.append(newUser) + self.UID_DB.append(new_user) return True - def update_nickname(self, uid: str, newNickname: str) -> bool: + def update_nickname(self, uid: str, new_nickname: str) -> bool: """Update the nickname starting from the UID Args: uid (str): UID of the user - newNickname (str): New nickname + new_nickname (str): New nickname Returns: bool: True if updated """ - userObj = self.get_User(uidornickname=uid) + user_obj = self.get_User(uidornickname=uid) - if userObj is None: + if user_obj is None: return False - userObj.nickname = newNickname + user_obj.nickname = new_nickname return True @@ -68,16 +68,16 @@ class User: bool: True if user mode has been updaed """ response = True - userObj = self.get_User(uidornickname=uidornickname) + user_obj = self.get_User(uidornickname=uidornickname) - if userObj is None: + if user_obj is None: return False action = modes[0] new_modes = modes[1:] - existing_umodes = userObj.umodes - umodes = userObj.umodes + existing_umodes = user_obj.umodes + umodes = user_obj.umodes if action == '+': @@ -96,7 +96,7 @@ class User: final_umodes_liste = [x for x in self.Base.Settings.PROTOCTL_USER_MODES if x in liste_umodes] final_umodes = ''.join(final_umodes_liste) - userObj.umodes = f"+{final_umodes}" + user_obj.umodes = f"+{final_umodes}" return response @@ -110,16 +110,16 @@ class User: bool: True if deleted """ - userObj = self.get_User(uidornickname=uid) + user_obj = self.get_User(uidornickname=uid) - if userObj is None: + if user_obj is None: return False - self.UID_DB.remove(userObj) + self.UID_DB.remove(user_obj) return True - def get_User(self, uidornickname: str) -> Union['MUser', None]: + def get_User(self, uidornickname: str) -> Optional['MUser']: """Get The User Object model Args: @@ -128,16 +128,15 @@ class User: Returns: UserModel|None: The UserModel Object | None """ - User = None for record in self.UID_DB: if record.uid == uidornickname: - User = record + return record elif record.nickname == uidornickname: - User = record + return record - return User + return None - def get_uid(self, uidornickname:str) -> Union[str, None]: + def get_uid(self, uidornickname:str) -> Optional[str]: """Get the UID of the user starting from the UID or the Nickname Args: @@ -147,14 +146,14 @@ class User: str|None: Return the UID """ - userObj = self.get_User(uidornickname=uidornickname) + user_obj = self.get_User(uidornickname=uidornickname) - if userObj is None: + if user_obj is None: return None - return userObj.uid + return user_obj.uid - def get_nickname(self, uidornickname:str) -> Union[str, None]: + def get_nickname(self, uidornickname:str) -> Optional[str]: """Get the Nickname starting from UID or the nickname Args: @@ -163,12 +162,12 @@ class User: Returns: str|None: the nickname """ - userObj = self.get_User(uidornickname=uidornickname) + user_obj = self.get_User(uidornickname=uidornickname) - if userObj is None: + if user_obj is None: return None - return userObj.nickname + return user_obj.nickname def get_user_asdict(self, uidornickname: str) -> Optional[dict[str, Any]]: """Transform User Object to a dictionary @@ -179,12 +178,12 @@ class User: Returns: Union[dict[str, any], None]: User Object as a dictionary or None """ - userObj = self.get_User(uidornickname=uidornickname) + user_obj = self.get_User(uidornickname=uidornickname) - if userObj is None: + if user_obj is None: return None - return userObj.to_dict() + return user_obj.to_dict() def is_exist(self, uidornikname: str) -> bool: """Check if the UID or the nickname exist in the USER DB @@ -195,14 +194,14 @@ class User: Returns: bool: True if exist """ - userObj = self.get_User(uidornickname=uidornikname) + user_obj = self.get_User(uidornickname=uidornikname) - if userObj is None: + if user_obj is None: return False return True - def clean_uid(self, uid: str) -> Union[str, None]: + def clean_uid(self, uid: str) -> Optional[str]: """Clean UID by removing @ / % / + / ~ / * / : Args: From 1686c4a0b5f0e736539eb604d016a2c80f440f26 Mon Sep 17 00:00:00 2001 From: adator <85586985+adator85@users.noreply.github.com> Date: Sat, 9 Aug 2025 12:17:45 +0200 Subject: [PATCH 06/20] updating namings convension to stay coherent --- core/classes/admin.py | 2 +- core/classes/channel.py | 70 +++++++++++++----------------- core/classes/client.py | 68 ++++++++++++++--------------- core/classes/clone.py | 39 ++++++++--------- core/classes/protocols/inspircd.py | 14 +++--- core/classes/protocols/unreal6.py | 16 +++---- core/classes/reputation.py | 6 +-- core/classes/user.py | 11 ++--- mods/clone/mod_clone.py | 14 +++--- mods/defender/schemas.py | 2 +- mods/defender/utils.py | 3 +- 11 files changed, 113 insertions(+), 132 deletions(-) diff --git a/core/classes/admin.py b/core/classes/admin.py index 96fbfa0..67a1672 100644 --- a/core/classes/admin.py +++ b/core/classes/admin.py @@ -1,4 +1,4 @@ -from typing import Optional, Union +from typing import Optional from core.base import Base import core.definition as df diff --git a/core/classes/channel.py b/core/classes/channel.py index 9e134f1..6d4380f 100644 --- a/core/classes/channel.py +++ b/core/classes/channel.py @@ -1,6 +1,5 @@ from re import findall from typing import Any, Optional, Literal, TYPE_CHECKING -from core.classes import user if TYPE_CHECKING: from core.definition import MChannel @@ -12,14 +11,14 @@ class Channel: """List that contains all the Channels objects (ChannelModel) """ - def __init__(self, baseObj: 'Base') -> None: + def __init__(self, base: 'Base') -> None: - self.Logs = baseObj.logs - self.Base = baseObj + self.Logs = base.logs + self.Base = base return None - def insert(self, newChan: 'MChannel') -> bool: + def insert(self, new_channel: 'MChannel') -> bool: """This method will insert a new channel and if the channel exist it will update the user list (uids) Args: @@ -31,17 +30,17 @@ class Channel: result = False exist = False - if not self.Is_Channel(newChan.name): - self.Logs.error(f"The channel {newChan.name} is not valid, channel must start with #") + if not self.Is_Channel(new_channel.name): + self.Logs.error(f"The channel {new_channel.name} is not valid, channel must start with #") return False for record in self.UID_CHANNEL_DB: - if record.name.lower() == newChan.name.lower(): + if record.name.lower() == new_channel.name.lower(): # If the channel exist, update the user list and do not go further exist = True # self.Logs.debug(f'{record.name} already exist') - for user in newChan.uids: + for user in new_channel.uids: record.uids.append(user) # Supprimer les doublons @@ -52,13 +51,13 @@ class Channel: if not exist: # If the channel don't exist, then create it - newChan.name = newChan.name.lower() - self.UID_CHANNEL_DB.append(newChan) + new_channel.name = new_channel.name.lower() + self.UID_CHANNEL_DB.append(new_channel) result = True - # self.Logs.debug(f'New Channel Created: ({newChan})') + # self.Logs.debug(f'New Channel Created: ({new_channel})') if not result: - self.Logs.critical(f'The Channel Object was not inserted {newChan}') + self.Logs.critical(f'The Channel Object was not inserted {new_channel}') self.clean_channel() @@ -66,12 +65,12 @@ class Channel: def delete(self, channel_name: str) -> bool: - chanObj = self.get_Channel(channel_name) + chan_obj = self.get_Channel(channel_name) - if chanObj is None: + if chan_obj is None: return False - self.UID_CHANNEL_DB.remove(chanObj) + self.UID_CHANNEL_DB.remove(chan_obj) return True @@ -79,14 +78,14 @@ class Channel: try: result = False - chanObj = self.get_Channel(channel_name.lower()) + chan_obj = self.get_Channel(channel_name.lower()) - if chanObj is None: + if chan_obj is None: return result - for userid in chanObj.uids: + for userid in chan_obj.uids: if self.Base.clean_uid(userid) == self.Base.clean_uid(uid): - chanObj.uids.remove(userid) + chan_obj.uids.remove(userid) result = True self.clean_channel() @@ -103,7 +102,6 @@ class Channel: for user_id in record.uids: if self.Base.clean_uid(user_id) == self.Base.clean_uid(uid): record.uids.remove(user_id) - # self.Logs.debug(f'The UID {uid} has been removed, here is the new object: {record}') result = True self.clean_channel() @@ -115,19 +113,16 @@ class Channel: def add_user_to_a_channel(self, channel_name: str, uid: str) -> bool: try: result = False - chanObj = self.get_Channel(channel_name) + chan_obj = self.get_Channel(channel_name) self.Logs.debug(f"** {__name__}") - if chanObj is None: + if chan_obj is None: result = self.insert(MChannel(channel_name, uids=[uid])) - # self.Logs.debug(f"** {__name__} - result: {result}") - # self.Logs.debug(f'New Channel Created: ({chanObj})') return result - chanObj.uids.append(uid) - del_duplicates = list(set(chanObj.uids)) - chanObj.uids = del_duplicates - # self.Logs.debug(f'New Channel Created: ({chanObj})') + chan_obj.uids.append(uid) + del_duplicates = list(set(chan_obj.uids)) + chan_obj.uids = del_duplicates return True except Exception as err: @@ -143,18 +138,16 @@ class Channel: Returns: bool: True if the user is present in the channel """ - user_found = False chan = self.get_Channel(channel_name=channel_name) if chan is None: - return user_found + return False clean_uid = self.Base.clean_uid(uid=uid) for chan_uid in chan.uids: if self.Base.clean_uid(chan_uid) == clean_uid: - user_found = True - break + return True - return user_found + return False def clean_channel(self) -> None: """Remove Channels if empty @@ -163,7 +156,7 @@ class Channel: for record in self.UID_CHANNEL_DB: if not record.uids: self.UID_CHANNEL_DB.remove(record) - # self.Logs.debug(f'The Channel {record.name} has been removed, here is the new object: {record}') + return None except Exception as err: self.Logs.error(f'{err}') @@ -178,7 +171,7 @@ class Channel: def get_channel_asdict(self, chan_name: str) -> Optional[dict[str, Any]]: - channel_obj: Optional['MChannel'] = self.get_Channel(chan_name=chan_name) + channel_obj: Optional['MChannel'] = self.get_Channel(chan_name) if channel_obj is None: return None @@ -235,9 +228,9 @@ class Channel: case 'add': mes_donnees = {'module_name': module_name, 'channel_name': channel_name} response = self.Base.db_execute_query(f"SELECT id FROM {core_table} WHERE module_name = :module_name AND channel_name = :channel_name", mes_donnees) - isChannelExist = response.fetchone() + is_channel_exist = response.fetchone() - if isChannelExist is None: + if is_channel_exist is None: mes_donnees = {'datetime': self.Base.get_datetime(), 'channel_name': channel_name, 'module_name': module_name} insert = self.Base.db_execute_query(f"INSERT INTO {core_table} (datetime, channel_name, module_name) VALUES (:datetime, :channel_name, :module_name)", mes_donnees) if insert.rowcount: @@ -261,4 +254,3 @@ class Channel: except Exception as err: self.Logs.error(err) - diff --git a/core/classes/client.py b/core/classes/client.py index 93e5b76..f5e0172 100644 --- a/core/classes/client.py +++ b/core/classes/client.py @@ -1,6 +1,5 @@ from re import sub from typing import Any, Optional, Union, TYPE_CHECKING -from dataclasses import asdict if TYPE_CHECKING: from core.base import Base @@ -10,30 +9,28 @@ class Client: CLIENT_DB: list['MClient'] = [] - def __init__(self, baseObj: 'Base') -> None: + def __init__(self, base: 'Base'): - self.Logs = baseObj.logs - self.Base = baseObj + self.Logs = base.logs + self.Base = base - return None - - def insert(self, newUser: 'MClient') -> bool: + def insert(self, new_client: 'MClient') -> bool: """Insert a new User object Args: - newUser (UserModel): New userModel object + new_client (MClient): New Client object Returns: bool: True if inserted """ - userObj = self.get_Client(newUser.uid) + client_obj = self.get_Client(new_client.uid) - if not userObj is None: + if not client_obj is None: # User already created return False return False - self.CLIENT_DB.append(newUser) + self.CLIENT_DB.append(new_client) return True @@ -47,12 +44,12 @@ class Client: Returns: bool: True if updated """ - userObj = self.get_Client(uidornickname=uid) + user_obj = self.get_Client(uidornickname=uid) - if userObj is None: + if user_obj is None: return False - userObj.nickname = newNickname + user_obj.nickname = newNickname return True @@ -67,16 +64,16 @@ class Client: bool: True if user mode has been updaed """ response = True - userObj = self.get_Client(uidornickname=uidornickname) + user_obj = self.get_Client(uidornickname=uidornickname) - if userObj is None: + if user_obj is None: return False action = modes[0] new_modes = modes[1:] - existing_umodes = userObj.umodes - umodes = userObj.umodes + existing_umodes = user_obj.umodes + umodes = user_obj.umodes if action == '+': @@ -95,7 +92,7 @@ class Client: final_umodes_liste = [x for x in self.Base.Settings.PROTOCTL_USER_MODES if x in liste_umodes] final_umodes = ''.join(final_umodes_liste) - userObj.umodes = f"+{final_umodes}" + user_obj.umodes = f"+{final_umodes}" return response @@ -109,16 +106,16 @@ class Client: bool: True if deleted """ - userObj = self.get_Client(uidornickname=uid) + user_obj = self.get_Client(uidornickname=uid) - if userObj is None: + if user_obj is None: return False - self.CLIENT_DB.remove(userObj) + self.CLIENT_DB.remove(user_obj) return True - def get_Client(self, uidornickname: str) -> Union['MClient', None]: + def get_Client(self, uidornickname: str) -> Optional['MClient']: """Get The Client Object model Args: @@ -127,16 +124,15 @@ class Client: Returns: UserModel|None: The UserModel Object | None """ - User = None for record in self.CLIENT_DB: if record.uid == uidornickname: - User = record + return record elif record.nickname == uidornickname: - User = record + return record - return User + return None - def get_uid(self, uidornickname:str) -> Union[str, None]: + def get_uid(self, uidornickname:str) -> Optional[str]: """Get the UID of the user starting from the UID or the Nickname Args: @@ -146,12 +142,12 @@ class Client: str|None: Return the UID """ - userObj = self.get_Client(uidornickname=uidornickname) + client_obj = self.get_Client(uidornickname=uidornickname) - if userObj is None: + if client_obj is None: return None - return userObj.uid + return client_obj.uid def get_nickname(self, uidornickname:str) -> Union[str, None]: """Get the Nickname starting from UID or the nickname @@ -162,12 +158,12 @@ class Client: Returns: str|None: the nickname """ - userObj = self.get_Client(uidornickname=uidornickname) + client_obj = self.get_Client(uidornickname=uidornickname) - if userObj is None: + if client_obj is None: return None - return userObj.nickname + return client_obj.nickname def get_client_asdict(self, uidornickname: str) -> Optional[dict[str, Any]]: """Transform User Object to a dictionary @@ -194,9 +190,9 @@ class Client: Returns: bool: True if exist """ - userObj = self.get_Client(uidornickname=uidornikname) + user_obj = self.get_Client(uidornickname=uidornikname) - if userObj is None: + if user_obj is None: return False return True diff --git a/core/classes/clone.py b/core/classes/clone.py index 846750b..4689309 100644 --- a/core/classes/clone.py +++ b/core/classes/clone.py @@ -6,13 +6,11 @@ class Clone: UID_CLONE_DB: list[MClone] = [] - def __init__(self, baseObj: Base) -> None: + def __init__(self, base: Base): - self.Logs = baseObj.logs + self.Logs = base.logs - return None - - def insert(self, newCloneObject: MClone) -> bool: + def insert(self, new_clone_object: MClone) -> bool: """Create new Clone object Args: @@ -25,23 +23,22 @@ class Clone: exist = False for record in self.UID_CLONE_DB: - if record.nickname == newCloneObject.nickname: + if record.nickname == new_clone_object.nickname: # If the user exist then return False and do not go further exist = True self.Logs.warning(f'Nickname {record.nickname} already exist') return result - if record.uid == newCloneObject.uid: + if record.uid == new_clone_object.uid: exist = True self.Logs.warning(f'UID: {record.uid} already exist') return result if not exist: - self.UID_CLONE_DB.append(newCloneObject) + self.UID_CLONE_DB.append(new_clone_object) result = True - # self.Logs.debug(f'New Clone Object Created: ({newCloneObject})') if not result: - self.Logs.critical(f'The Clone Object was not inserted {newCloneObject}') + self.Logs.critical(f'The Clone Object was not inserted {new_clone_object}') return result @@ -55,12 +52,12 @@ class Clone: bool: True if deleted """ - cloneObj = self.get_Clone(uidornickname=uidornickname) + clone_obj = self.get_clone(uidornickname=uidornickname) - if cloneObj is None: + if clone_obj is None: return False - self.UID_CLONE_DB.remove(cloneObj) + self.UID_CLONE_DB.remove(clone_obj) return True @@ -73,7 +70,7 @@ class Clone: Returns: bool: True if the nickname exist """ - clone = self.get_Clone(nickname) + clone = self.get_clone(nickname) if isinstance(clone, MClone): return True @@ -88,13 +85,13 @@ class Clone: Returns: bool: True if the nickname exist """ - clone = self.get_Clone(uid) + clone = self.get_clone(uid) if isinstance(clone, MClone): return True return False - def get_Clone(self, uidornickname: str) -> Optional[MClone]: + def get_clone(self, uidornickname: str) -> Optional[MClone]: """Get MClone object or None Args: @@ -128,9 +125,9 @@ class Clone: return None - def get_Clone_AsDict(self, uidornickname: str) -> Optional[dict[str, Any]]: + def get_clone_asdict(self, uidornickname: str) -> Optional[dict[str, Any]]: - clone_obj = self.get_Clone(uidornickname=uidornickname) + clone_obj = self.get_clone(uidornickname=uidornickname) if clone_obj is None: return None @@ -141,9 +138,9 @@ class Clone: response = False - for cloneObject in self.UID_CLONE_DB: - if cloneObject.nickname == nickname: - cloneObject.alive = False # Kill the clone + for clone in self.UID_CLONE_DB: + if clone.nickname == nickname: + clone.alive = False # Kill the clone response = True return response \ No newline at end of file diff --git a/core/classes/protocols/inspircd.py b/core/classes/protocols/inspircd.py index 39da912..c33eaf6 100644 --- a/core/classes/protocols/inspircd.py +++ b/core/classes/protocols/inspircd.py @@ -186,16 +186,16 @@ class Inspircd: uidornickname (str): The UID or the Nickname reason (str): The reason for the quit """ - userObj = self.__Irc.User.get_User(uidornickname=uid) - cloneObj = self.__Irc.Clone.get_Clone(uidornickname=uid) + user_obj = self.__Irc.User.get_User(uidornickname=uid) + clone_obj = self.__Irc.Clone.get_clone(uidornickname=uid) reputationObj = self.__Irc.Reputation.get_Reputation(uidornickname=uid) - if not userObj is None: - self.send2socket(f":{userObj.uid} QUIT :{reason}", print_log=print_log) - self.__Irc.User.delete(userObj.uid) + if not user_obj is None: + self.send2socket(f":{user_obj.uid} QUIT :{reason}", print_log=print_log) + self.__Irc.User.delete(user_obj.uid) - if not cloneObj is None: - self.__Irc.Clone.delete(cloneObj.uid) + if not clone_obj is None: + self.__Irc.Clone.delete(clone_obj.uid) if not reputationObj is None: self.__Irc.Reputation.delete(reputationObj.uid) diff --git a/core/classes/protocols/unreal6.py b/core/classes/protocols/unreal6.py index d666f32..7987adc 100644 --- a/core/classes/protocols/unreal6.py +++ b/core/classes/protocols/unreal6.py @@ -344,16 +344,16 @@ class Unrealircd6: uidornickname (str): The UID or the Nickname reason (str): The reason for the quit """ - userObj = self.__Irc.User.get_User(uidornickname=uid) - cloneObj = self.__Irc.Clone.get_Clone(uidornickname=uid) + user_obj = self.__Irc.User.get_User(uidornickname=uid) + clone_obj = self.__Irc.Clone.get_clone(uidornickname=uid) reputationObj = self.__Irc.Reputation.get_Reputation(uidornickname=uid) - if not userObj is None: - self.send2socket(f":{userObj.uid} QUIT :{reason}", print_log=print_log) - self.__Irc.User.delete(userObj.uid) + if not user_obj is None: + self.send2socket(f":{user_obj.uid} QUIT :{reason}", print_log=print_log) + self.__Irc.User.delete(user_obj.uid) - if not cloneObj is None: - self.__Irc.Clone.delete(cloneObj.uid) + if not clone_obj is None: + self.__Irc.Clone.delete(clone_obj.uid) if not reputationObj is None: self.__Irc.Reputation.delete(reputationObj.uid) @@ -469,7 +469,7 @@ class Unrealircd6: def send_mode_chan(self, channel_name: str, channel_mode: str) -> None: - channel = self.__Irc.Channel.Is_Channel(channelToCheck=channel_name) + channel = self.__Irc.Channel.Is_Channel(channel_name) if not channel: self.__Base.logs.error(f'The channel [{channel_name}] is not correct') return None diff --git a/core/classes/reputation.py b/core/classes/reputation.py index 75f431b..408f95a 100644 --- a/core/classes/reputation.py +++ b/core/classes/reputation.py @@ -6,13 +6,11 @@ class Reputation: UID_REPUTATION_DB: list[MReputation] = [] - def __init__(self, baseObj: Base) -> None: + def __init__(self, base: Base): - self.Logs = baseObj.logs + self.Logs = base.logs self.MReputation: MReputation = MReputation - return None - def insert(self, new_reputation_user: MReputation) -> bool: """Insert a new Reputation User object diff --git a/core/classes/user.py b/core/classes/user.py index acce7a4..a582780 100644 --- a/core/classes/user.py +++ b/core/classes/user.py @@ -1,6 +1,5 @@ from re import sub -from typing import Any, Optional, Union, TYPE_CHECKING -from dataclasses import asdict +from typing import Any, Optional, TYPE_CHECKING from datetime import datetime if TYPE_CHECKING: @@ -11,12 +10,10 @@ class User: UID_DB: list['MUser'] = [] - def __init__(self, baseObj: 'Base') -> None: + def __init__(self, base: 'Base'): - self.Logs = baseObj.logs - self.Base = baseObj - - return None + self.Logs = base.logs + self.Base = base def insert(self, new_user: 'MUser') -> bool: """Insert a new User object diff --git a/mods/clone/mod_clone.py b/mods/clone/mod_clone.py index 0231255..0eabb59 100644 --- a/mods/clone/mod_clone.py +++ b/mods/clone/mod_clone.py @@ -269,15 +269,15 @@ class Clone(): if not senderObj is None: senderMsg = ' '.join(cmd[4:]) - getClone = self.Clone.get_Clone(cmd[3]) + clone_obj = self.Clone.get_clone(cmd[3]) - if getClone is None: + if clone_obj is None: return None - if getClone.uid != self.Config.SERVICE_ID: + if clone_obj.uid != self.Config.SERVICE_ID: final_message = f"{senderObj.nickname}!{senderObj.username}@{senderObj.hostname} > {senderMsg.lstrip(':')}" self.Protocol.send_priv_msg( - nick_from=getClone.uid, + nick_from=clone_obj.uid, msg=final_message, channel=self.Config.CLONE_CHANNEL ) @@ -337,9 +337,9 @@ class Clone(): self.Base.create_thread(func=self.thread_kill_clones, func_args=(fromuser, )) else: - cloneObj = self.Clone.get_Clone(clone_name) - if not cloneObj is None: - self.Protocol.send_quit(cloneObj.uid, 'Goood bye', print_log=False) + clone_obj = self.Clone.get_clone(clone_name) + if not clone_obj is None: + self.Protocol.send_quit(clone_obj.uid, 'Goood bye', print_log=False) except Exception as err: self.Logs.error(f'{err}') diff --git a/mods/defender/schemas.py b/mods/defender/schemas.py index 94e004f..b404685 100644 --- a/mods/defender/schemas.py +++ b/mods/defender/schemas.py @@ -22,7 +22,7 @@ class ModConfModel(MainModel): autolimit_interval: int = 3 @dataclass -class FloodUser: +class FloodUser(MainModel): uid: str = None nbr_msg: int = 0 first_msg_time: int = 0 diff --git a/mods/defender/utils.py b/mods/defender/utils.py index 7b2bf07..c5fc6e7 100644 --- a/mods/defender/utils.py +++ b/mods/defender/utils.py @@ -1,3 +1,4 @@ +from calendar import c import socket import psutil import requests @@ -264,7 +265,7 @@ def action_on_flood(uplink: 'Defender', srvmsg: list[str]): channel = srvmsg[3] User = irc.User.get_User(user_trigger) - if User is None or not irc.Channel.Is_Channel(channelToCheck=channel): + if User is None or not irc.Channel.Is_Channel(channel_to_check=channel): return flood_time = confmodel.flood_time From 21a2619f4934770f1490670e87a225dcd1868a12 Mon Sep 17 00:00:00 2001 From: adator <85586985+adator85@users.noreply.github.com> Date: Sun, 10 Aug 2025 02:31:50 +0200 Subject: [PATCH 07/20] Updating mod_clone by adding action on groups. reloading the module is now using Settings.set_cache and get_cache --- core/base.py | 2 +- core/classes/commands.py | 59 ++++ core/classes/protocols/unreal6.py | 6 - core/classes/settings.py | 33 ++ core/definition.py | 7 + core/irc.py | 85 ++++- core/loader.py | 8 +- .../clone.py => mods/clone/clone_manager.py | 56 +++- mods/clone/mod_clone.py | 303 ++++++------------ mods/clone/schemas.py | 22 ++ mods/clone/threads.py | 39 +++ mods/clone/utils.py | 198 ++++++++++++ version.json | 2 +- 13 files changed, 587 insertions(+), 233 deletions(-) create mode 100644 core/classes/commands.py rename core/classes/clone.py => mods/clone/clone_manager.py (74%) create mode 100644 mods/clone/schemas.py create mode 100644 mods/clone/threads.py create mode 100644 mods/clone/utils.py diff --git a/core/base.py b/core/base.py index e62689c..a39915b 100644 --- a/core/base.py +++ b/core/base.py @@ -186,7 +186,7 @@ class Base: # Sort to reload submodules before parent modules for name, module in sorted(modules_to_reload, key=lambda x: x[0], reverse=True): try: - if 'mod_' not in name: + if 'mod_' not in name and 'schemas' not in name: importlib.reload(module) self.logs.debug(f'[LOAD_MODULE] Module {module} success') diff --git a/core/classes/commands.py b/core/classes/commands.py new file mode 100644 index 0000000..6a47c75 --- /dev/null +++ b/core/classes/commands.py @@ -0,0 +1,59 @@ +from typing import TYPE_CHECKING, Optional +from core.definition import MCommand + +if TYPE_CHECKING: + from core.base import Base + +class Command: + + DB_COMMANDS: list['MCommand'] = [] + + def __init__(self, base: 'Base'): + self.Base = base + + def build(self, new_command_obj: MCommand) -> bool: + + command = self.get_command(new_command_obj.command_name, new_command_obj.module_name) + if command is None: + self.DB_COMMANDS.append(new_command_obj) + return True + + # Update command if it exist + # Removing the object + if self.drop_command(command.command_name, command.module_name): + # Add the new object + self.DB_COMMANDS.append(new_command_obj) + return True + + return False + + def get_command(self, command_name: str, module_name: str) -> Optional[MCommand]: + + for command in self.DB_COMMANDS: + if command.command_name.lower() == command_name and command.module_name == module_name: + return command + + return None + + def drop_command(self, command_name: str, module_name: str) -> bool: + + cmd = self.get_command(command_name, module_name) + if cmd is not None: + self.DB_COMMANDS.remove(cmd) + return True + + return False + + def get_ordered_commands(self) -> list[MCommand]: + return sorted(self.DB_COMMANDS, key=lambda c: (c.command_level, c.module_name)) + + def get_commands_by_level(self, level: int = 0) -> Optional[list[MCommand]]: + + cmd_list = self.get_ordered_commands() + new_list: list[MCommand] = [] + + for cmd in cmd_list: + if cmd.command_level <= level: + new_list.append(cmd) + + return new_list \ No newline at end of file diff --git a/core/classes/protocols/unreal6.py b/core/classes/protocols/unreal6.py index 7987adc..6a47350 100644 --- a/core/classes/protocols/unreal6.py +++ b/core/classes/protocols/unreal6.py @@ -337,7 +337,6 @@ class Unrealircd6: def send_quit(self, uid: str, reason: str, print_log: True) -> None: """Send quit message - Delete uid from User object - - Delete uid from Clone object - Delete uid from Reputation object Args: @@ -345,16 +344,12 @@ class Unrealircd6: reason (str): The reason for the quit """ user_obj = self.__Irc.User.get_User(uidornickname=uid) - clone_obj = self.__Irc.Clone.get_clone(uidornickname=uid) reputationObj = self.__Irc.Reputation.get_Reputation(uidornickname=uid) if not user_obj is None: self.send2socket(f":{user_obj.uid} QUIT :{reason}", print_log=print_log) self.__Irc.User.delete(user_obj.uid) - if not clone_obj is None: - self.__Irc.Clone.delete(clone_obj.uid) - if not reputationObj is None: self.__Irc.Reputation.delete(reputationObj.uid) @@ -569,7 +564,6 @@ class Unrealircd6: self.__Irc.User.delete(uid_who_quit) self.__Irc.Client.delete(uid_who_quit) self.__Irc.Reputation.delete(uid_who_quit) - self.__Irc.Clone.delete(uid_who_quit) return None diff --git a/core/classes/settings.py b/core/classes/settings.py index 5132a45..46350df 100644 --- a/core/classes/settings.py +++ b/core/classes/settings.py @@ -1,7 +1,14 @@ +'''This class should never be reloaded. +''' from threading import Timer, Thread, RLock from socket import socket +from typing import Any class Settings: + """This Class will never be reloaded. + Means that the variables are available during + the whole life of the app + """ RUNNING_TIMERS: list[Timer] = [] RUNNING_THREADS: list[Thread] = [] @@ -13,3 +20,29 @@ class Settings: PROTOCTL_USER_MODES: list[str] = [] PROTOCTL_PREFIX: list[str] = [] + + __CACHE: dict[str, Any] = {} + """Use set_cache or get_cache instead""" + + def set_cache(self, key: str, value_to_cache: Any): + """When you want to store a variable + Ex. + ```python + set_cache('MY_KEY', {'key1': 'value1', 'key2', 'value2'}) + ``` + Args: + key (str): The key you want to add. + value_to_cache (Any): The Value you want to store. + """ + self.__CACHE[key] = value_to_cache + + def get_cache(self, key) -> Any: + """It returns the value associated to the key and finally it removes the entry""" + if self.__CACHE.get(key): + return self.__CACHE.pop(key) + + return None + + def get_cache_size(self) -> int: + return len(self.__CACHE) + \ No newline at end of file diff --git a/core/definition.py b/core/definition.py index ef71fad..af8f262 100644 --- a/core/definition.py +++ b/core/definition.py @@ -334,3 +334,10 @@ class MClone(MainModel): umodes: str = None remote_ip: str = '127.0.0.1' group: str = 'Default' + +@dataclass +class MCommand(MainModel): + module_name: str = None + command_name: str = None + description: str = None + command_level: int = 0 diff --git a/core/irc.py b/core/irc.py index 7c17e10..df33bcf 100644 --- a/core/irc.py +++ b/core/irc.py @@ -8,9 +8,10 @@ import time import traceback from ssl import SSLSocket from datetime import datetime, timedelta -from typing import Union +from typing import Optional, Union from core.loader import Loader from core.classes.protocol import Protocol +from core.classes.commands import Command class Irc: _instance = None @@ -67,9 +68,6 @@ class Irc: # Use Channel Instance self.Channel = self.Loader.Channel - # Use Clones Instance - self.Clone = self.Loader.Clone - # Use Reputation Instance self.Reputation = self.Loader.Reputation @@ -83,7 +81,11 @@ class Irc: self.first_connexion_ip: str = None # Define the dict that will contain all loaded modules - self.loaded_classes:dict[str, 'Irc'] = {} # Definir la variable qui contiendra la liste modules chargés + self.loaded_classes:dict[str, 'Irc'] = {} + + # Load Commands Utils + self.Commands = self.Loader.Commands + """Command utils""" # Global full module commands that contains level, module name, commands and description self.module_commands: dict[int, dict[str, dict[str, str]]] = {} @@ -341,9 +343,71 @@ class Irc: self.module_commands.setdefault(level, {}).setdefault(module_name, {}).update({command_name: command_description}) self.module_commands_list.append(command_name) + # Build Model. + self.Commands.build(self.Loader.Definition.MCommand(module_name, command_name, command_description, level)) + return None - def generate_help_menu(self, nickname: str) -> None: + def generate_help_menu(self, nickname: str, module: Optional[str] = None) -> None: + + # Check if the nickname is an admin + p = self.Protocol + admin_obj = self.Admin.get_Admin(nickname) + dnickname = self.Config.SERVICE_NICKNAME + color_bold = self.Config.COLORS.bold + color_nogc = self.Config.COLORS.nogc + color_blue = self.Config.COLORS.blue + color_black = self.Config.COLORS.black + color_underline = self.Config.COLORS.underline + current_level = 0 + count = 0 + if admin_obj is not None: + current_level = admin_obj.level + + p.send_notice(nick_from=dnickname,nick_to=nickname, msg=f" ***************** LISTE DES COMMANDES *****************") + header = f" {'Level':<8}| {'Command':<25}| {'Module':<15}| {'Description':<35}" + line = "-"*75 + p.send_notice(nick_from=dnickname,nick_to=nickname, msg=header) + p.send_notice(nick_from=dnickname,nick_to=nickname, msg=f" {line}") + for cmd in self.Commands.get_commands_by_level(current_level): + if module is None or cmd.module_name.lower() == module.lower(): + p.send_notice( + nick_from=dnickname, + nick_to=nickname, + msg=f" {color_black}{cmd.command_level:<8}{color_nogc}| {cmd.command_name:<25}| {cmd.module_name:<15}| {cmd.description:<35}" + ) + + return + + for level, modules in self.module_commands.items(): + if level > current_level: + break + + if count > 0: + p.send_notice(nick_from=dnickname, nick_to=nickname, msg=" ") + + p.send_notice( + nick_from=dnickname, + nick_to=nickname, + msg=f"{color_blue}{color_bold}Level {level}:{color_nogc}" + ) + + for module_name, commands in modules.items(): + if module is None or module.lower() == module_name.lower(): + p.send_notice( + nick_from=dnickname, + nick_to=nickname, + msg=f"{color_black} {color_underline}Module: {module_name}{color_nogc}" + ) + for command, description in commands.items(): + p.send_notice(nick_from=dnickname, nick_to=nickname, msg=f" {command:<20}: {description}") + + count += 1 + + p.send_notice(nick_from=dnickname,nick_to=nickname,msg=f" ***************** FIN DES COMMANDES *****************") + return None + + def generate_help_menu_bakcup(self, nickname: str) -> None: # Check if the nickname is an admin admin_obj = self.Admin.get_Admin(nickname) @@ -578,6 +642,7 @@ class Irc: channel=self.Config.SERVICE_CHANLOG ) self.Base.db_delete_module(module_name) + traceback.print_exc() def unload_module(self, mod_name: str) -> bool: """Unload a module @@ -1387,8 +1452,12 @@ class Irc: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {command.upper()} ") case 'help': - - self.generate_help_menu(nickname=fromuser) + # Syntax. !help [module_name] + module_name = str(cmd[1]) if len(cmd) == 2 else None + self.generate_help_menu(nickname=fromuser, module=module_name) + + for com in self.Commands.get_ordered_commands(): + print(com) case 'load': try: diff --git a/core/loader.py b/core/loader.py index d897c04..d9ebdf5 100644 --- a/core/loader.py +++ b/core/loader.py @@ -1,4 +1,4 @@ -from core.classes import user, admin, client, channel, clone, reputation, settings +from core.classes import user, admin, client, channel, reputation, settings, commands import core.definition as df import core.base as baseModule import core.classes.config as confModule @@ -15,7 +15,7 @@ class Loader: self.BaseModule: baseModule = baseModule # Load Classes - self.Settings: settings = settings.Settings() + self.Settings: settings.Settings = settings.Settings() self.Config: df.MConfig = self.ConfModule.Configuration().ConfigObject @@ -29,6 +29,6 @@ class Loader: self.Channel: channel.Channel = channel.Channel(self.Base) - self.Clone: clone.Clone = clone.Clone(self.Base) - self.Reputation: reputation.Reputation = reputation.Reputation(self.Base) + + self.Commands: commands.Command = commands.Command(self.Base) diff --git a/core/classes/clone.py b/mods/clone/clone_manager.py similarity index 74% rename from core/classes/clone.py rename to mods/clone/clone_manager.py index 4689309..9b53a69 100644 --- a/core/classes/clone.py +++ b/mods/clone/clone_manager.py @@ -1,14 +1,16 @@ +from typing import Optional, TYPE_CHECKING from core.definition import MClone -from typing import Any, Optional -from core.base import Base -class Clone: +if TYPE_CHECKING: + from mods.clone.mod_clone import Clone + +class CloneManager: UID_CLONE_DB: list[MClone] = [] - def __init__(self, base: Base): + def __init__(self, uplink: 'Clone'): - self.Logs = base.logs + self.Logs = uplink.Logs def insert(self, new_clone_object: MClone) -> bool: """Create new Clone object @@ -61,7 +63,7 @@ class Clone: return True - def exists(self, nickname: str) -> bool: + def nickname_exists(self, nickname: str) -> bool: """Check if the nickname exist Args: @@ -91,6 +93,21 @@ class Clone: return False + def group_exists(self, groupname: str) -> bool: + """Verify if a group exist + + Args: + groupname (str): The group name + + Returns: + bool: _description_ + """ + for clone in self.UID_CLONE_DB: + if clone.group.strip().lower() == groupname.strip().lower(): + return True + + return False + def get_clone(self, uidornickname: str) -> Optional[MClone]: """Get MClone object or None @@ -108,6 +125,24 @@ class Clone: return None + def get_clones_from_groupname(self, groupname: str) -> list[MClone]: + """Get list of clone objects by group name + + Args: + groupname (str): The group name + + Returns: + list[MClone]: List of clones in the group + """ + group_of_clone: list[MClone] = [] + + if self.group_exists(groupname): + for clone in self.UID_CLONE_DB: + if clone.group.strip().lower() == groupname.strip().lower(): + group_of_clone.append(clone) + + return group_of_clone + def get_uid(self, uidornickname: str) -> Optional[str]: """Get the UID of the clone starting from the UID or the Nickname @@ -125,15 +160,6 @@ class Clone: return None - def get_clone_asdict(self, uidornickname: str) -> Optional[dict[str, Any]]: - - clone_obj = self.get_clone(uidornickname=uidornickname) - - if clone_obj is None: - return None - - return clone_obj.to_dict() - def kill(self, nickname:str) -> bool: response = False diff --git a/mods/clone/mod_clone.py b/mods/clone/mod_clone.py index 0eabb59..d87706e 100644 --- a/mods/clone/mod_clone.py +++ b/mods/clone/mod_clone.py @@ -1,16 +1,16 @@ -from dataclasses import dataclass -import random, faker, time, logging -from typing import TYPE_CHECKING +import time, logging +from typing import TYPE_CHECKING, Optional +from faker import Faker +import mods.clone.utils as utils +import mods.clone.threads as thds +import mods.clone.schemas as schemas +from mods.clone.clone_manager import CloneManager if TYPE_CHECKING: from core.irc import Irc class Clone(): - @dataclass - class ModConfModel: - clone_nicknames: list[str] - def __init__(self, ircInstance: 'Irc') -> None: # Module name (Mandatory) @@ -36,12 +36,29 @@ class Clone(): # Add Channel object to the module (Mandatory) self.Channel = ircInstance.Channel - - # Add clone object to the module (Optionnal) - self.Clone = ircInstance.Clone - + + # Add global definitions self.Definition = ircInstance.Loader.Definition + # The Global Settings + self.Settings = ircInstance.Loader.Settings + + self.Schemas = schemas + + self.Utils = utils + + self.Threads = thds + + self.Faker: Optional['Faker'] = self.Utils.create_faker_object('en_GB') + + self.Clone = CloneManager(self) + + metadata = self.Settings.get_cache('UID_CLONE_DB') + + if metadata is not None: + self.Clone.UID_CLONE_DB = metadata + self.Logs.debug(f"Cache Size = {self.Settings.get_cache_size()}") + # Créer les nouvelles commandes du module self.Irc.build_command(1, self.module_name, 'clone', 'Connect, join, part, kill and say clones') @@ -57,10 +74,6 @@ class Clone(): self.__create_tables() self.stop = False - logging.getLogger('faker').setLevel(logging.CRITICAL) - - self.fakeEN = faker.Faker('en_GB') - self.fakeFR = faker.Faker('fr_FR') # Load module configuration (Mandatory) self.__load_module_configuration() @@ -99,9 +112,7 @@ class Clone(): """ try: # Variable qui va contenir les options de configuration du module Defender - self.ModConfig = self.ModConfModel( - clone_nicknames=[] - ) + self.ModConfig = self.Schemas.ModConfModel() # Sync the configuration with core configuration (Mandatory) # self.Base.db_sync_core_config(self.module_name, self.ModConfig) @@ -115,6 +126,8 @@ class Clone(): """Cette methode sera executée a chaque désactivation ou rechargement de module """ + # Store Clones DB into the global Settings to retrieve it after the reload. + self.Settings.set_cache('UID_CLONE_DB', self.Clone.UID_CLONE_DB) self.Channel.db_query_channel(action='del', module_name=self.module_name, channel_name=self.Config.CLONE_CHANNEL) self.Protocol.send2socket(f":{self.Config.SERVICE_NICKNAME} MODE {self.Config.CLONE_CHANNEL} -nts") @@ -123,175 +136,40 @@ class Clone(): return None - def generate_vhost(self) -> str: - - fake = self.fakeEN - - rand_1 = fake.random_elements(['A','B','C','D','E','F','0','1','2','3','4','5','6','7','8','9'], unique=True, length=8) - rand_2 = fake.random_elements(['A','B','C','D','E','F','0','1','2','3','4','5','6','7','8','9'], unique=True, length=8) - rand_3 = fake.random_elements(['A','B','C','D','E','F','0','1','2','3','4','5','6','7','8','9'], unique=True, length=8) - - vhost = ''.join(rand_1) + '.' + ''.join(rand_2) + '.' + ''.join(rand_3) + '.IP' - return vhost - - def generate_clones(self, group: str = 'Default', auto_remote_ip: bool = False) -> None: + def cmd(self, data:list): try: + if not data or len(data) < 2: + return - fakeEN = self.fakeEN - fakeFR = self.fakeFR - unixtime = self.Base.get_unixtime() + cmd = data.copy() if isinstance(data, list) else list(data).copy() + index, command = self.Irc.Protocol.get_ircd_protocol_poisition(cmd) + if index == -1: + return - chaine = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' - generate_uid = fakeEN.random_sample(chaine, 6) - uid = self.Config.SERVEUR_ID + ''.join(generate_uid) + match command: - umodes = self.Config.CLONE_UMODES - - # Generate Username - chaine = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' - new_username = fakeEN.random_sample(chaine, 9) - username = ''.join(new_username) - - # Create realname XX F|M Department - gender = fakeEN.random_choices(['F','M'], 1) - gender = ''.join(gender) - - if gender == 'F': - nickname = fakeEN.first_name_female() - elif gender == 'M': - nickname = fakeEN.first_name_male() - else: - nickname = fakeEN.first_name() - - age = random.randint(20, 60) - department = fakeFR.department_name() - realname = f'{age} {gender} {department}' - - decoded_ip = fakeEN.ipv4_private() if auto_remote_ip else '127.0.0.1' - hostname = fakeEN.hostname() - - vhost = self.generate_vhost() - - checkNickname = self.Clone.exists(nickname=nickname) - checkUid = self.Clone.uid_exists(uid=uid) - - while checkNickname: - caracteres = '0123456789' - randomize = ''.join(random.choice(caracteres) for _ in range(2)) - nickname = nickname + str(randomize) - checkNickname = self.Clone.exists(nickname=nickname) - - while checkUid: - chaine = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' - generate_uid = fakeEN.random_sample(chaine, 6) - uid = self.Config.SERVEUR_ID + ''.join(generate_uid) - checkUid = self.Clone.uid_exists(uid=uid) - - clone = self.Definition.MClone( - connected=False, - nickname=nickname, - username=username, - realname=realname, - hostname=hostname, - umodes=umodes, - uid=uid, - remote_ip=decoded_ip, - vhost=vhost, - group=group, - channels=[] - ) - - self.Clone.insert(clone) - - return None - - except AttributeError as ae: - self.Logs.error(f'Attribute Error : {ae}') - except Exception as err: - self.Logs.error(f"General Error: {err}") - - def thread_connect_clones(self, number_of_clones:int , group: str = 'Default', auto_remote_ip: bool = False, interval: float = 0.2) -> None: - - for i in range(0, number_of_clones): - self.generate_clones(group=group, auto_remote_ip=auto_remote_ip) - - for clone in self.Clone.UID_CLONE_DB: - - if self.stop: - print(f"Stop creating clones ...") - self.stop = False - break - - if not clone.connected: - self.Protocol.send_uid(clone.nickname, clone.username, clone.hostname, clone.uid, clone.umodes, clone.vhost, clone.remote_ip, clone.realname, print_log=False) - self.Protocol.send_join_chan(uidornickname=clone.uid, channel=self.Config.CLONE_CHANNEL, password=self.Config.CLONE_CHANNEL_PASSWORD, print_log=False) - - time.sleep(interval) - clone.connected = True - - def thread_kill_clones(self, fromuser: str) -> None: - - clone_to_kill: list[str] = [] - for clone in self.Clone.UID_CLONE_DB: - clone_to_kill.append(clone.uid) - - for clone_uid in clone_to_kill: - self.Protocol.send_quit(clone_uid, 'Gooood bye', print_log=False) - - del clone_to_kill - - return None - - def cmd(self, data:list) -> None: - try: - 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 - - if len(cmd) < 3: - return None - - match cmd[2]: case 'PRIVMSG': - # print(cmd) - uid_sender = self.User.clean_uid(cmd[1]) - senderObj = self.User.get_User(uid_sender) + return self.Utils.handle_on_privmsg(self, cmd) - if senderObj.hostname in self.Config.CLONE_LOG_HOST_EXEMPT: - return None + case 'QUIT': + return - if not senderObj is None: - senderMsg = ' '.join(cmd[4:]) - clone_obj = self.Clone.get_clone(cmd[3]) - - if clone_obj is None: - return None - - if clone_obj.uid != self.Config.SERVICE_ID: - final_message = f"{senderObj.nickname}!{senderObj.username}@{senderObj.hostname} > {senderMsg.lstrip(':')}" - self.Protocol.send_priv_msg( - nick_from=clone_obj.uid, - msg=final_message, - channel=self.Config.CLONE_CHANNEL - ) + case _: + return except Exception as err: - self.Logs.error(f'General Error: {err}') + self.Logs.error(f'General Error: {err}', exc_info=True) - def hcmds(self, user:str, channel: any, cmd: list, fullcmd: list = []) -> None: + def hcmds(self, user: str, channel: any, cmd: list, fullcmd: list = []) -> None: try: + + if len(cmd) < 1: + return + command = str(cmd[0]).lower() fromuser = user - - dnickname = self.Config.SERVICE_NICKNAME # Defender nickname + dnickname = self.Config.SERVICE_NICKNAME match command: @@ -303,6 +181,7 @@ class Clone(): self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone join [all | nickname] #channel") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone part [all | nickname] #channel") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone list") + return None option = str(cmd[1]).lower() @@ -317,8 +196,8 @@ class Clone(): connection_interval = int(cmd[4]) if len(cmd) == 5 else 0.2 self.Base.create_thread( - func=self.thread_connect_clones, - func_args=(number_of_clones, group, False, connection_interval) + func=self.Threads.thread_connect_clones, + func_args=(self, number_of_clones, group, False, connection_interval) ) except Exception as err: @@ -328,18 +207,28 @@ class Clone(): case 'kill': try: - # clone kill [all | nickname] + # clone kill [ALL | group name | nickname] self.stop = True - clone_name = str(cmd[2]) - clone_to_kill: list[str] = [] + option = str(cmd[2]) - if clone_name.lower() == 'all': - self.Base.create_thread(func=self.thread_kill_clones, func_args=(fromuser, )) + if option.lower() == 'all': + self.Base.create_thread(func=self.Threads.thread_kill_clones, func_args=(self, )) + + elif self.Clone.group_exists(option): + list_of_clones_in_group = self.Clone.get_clones_from_groupname(option) + + if len(list_of_clones_in_group) > 0: + self.Logs.debug(f"[Clone Kill Group] - Killing {len(list_of_clones_in_group)} clones in the group {option}") + + for clone in list_of_clones_in_group: + self.Protocol.send_quit(clone.uid, "Now i am leaving irc but i'll come back soon ...", print_log=False) + self.Clone.delete(clone.uid) else: - clone_obj = self.Clone.get_clone(clone_name) + clone_obj = self.Clone.get_clone(option) if not clone_obj is None: self.Protocol.send_quit(clone_obj.uid, 'Goood bye', print_log=False) + self.Clone.delete(clone_obj.uid) except Exception as err: self.Logs.error(f'{err}') @@ -348,19 +237,28 @@ class Clone(): case 'join': try: - # clone join [all | nickname] #channel - clone_name = str(cmd[2]) + # clone join [all | group name | nickname] #channel + option = str(cmd[2]) clone_channel_to_join = str(cmd[3]) - if clone_name.lower() == 'all': + if option.lower() == 'all': for clone in self.Clone.UID_CLONE_DB: self.Protocol.send_join_chan(uidornickname=clone.uid, channel=clone_channel_to_join, print_log=False) + elif self.Clone.group_exists(option): + list_of_clones_in_group = self.Clone.get_clones_from_groupname(option) + + if len(list_of_clones_in_group) > 0: + self.Logs.debug(f"[Clone Join Group] - Joining {len(list_of_clones_in_group)} clones from group {option} in the channel {clone_channel_to_join}") + + for clone in list_of_clones_in_group: + self.Protocol.send_join_chan(uidornickname=clone.nickname, channel=clone_channel_to_join, print_log=False) + else: - if self.Clone.exists(clone_name): - if not self.Clone.get_uid(clone_name) is None: - self.Protocol.send_join_chan(uidornickname=clone_name, channel=clone_channel_to_join, print_log=False) + if self.Clone.nickname_exists(option): + clone_uid = self.Clone.get_clone(option).uid + self.Protocol.send_join_chan(uidornickname=clone_uid, channel=clone_channel_to_join, print_log=False) except Exception as err: self.Logs.error(f'{err}') @@ -369,18 +267,27 @@ class Clone(): case 'part': try: - # clone part [all | nickname] #channel - clone_name = str(cmd[2]) + # clone part [all | nickname] #channel + option = str(cmd[2]) clone_channel_to_part = str(cmd[3]) - if clone_name.lower() == 'all': + if option.lower() == 'all': for clone in self.Clone.UID_CLONE_DB: self.Protocol.send_part_chan(uidornickname=clone.uid, channel=clone_channel_to_part, print_log=False) + elif self.Clone.group_exists(option): + list_of_clones_in_group = self.Clone.get_clones_from_groupname(option) + + if len(list_of_clones_in_group) > 0: + self.Logs.debug(f"[Clone Part Group] - Part {len(list_of_clones_in_group)} clones from group {option} from the channel {clone_channel_to_part}") + + for clone in list_of_clones_in_group: + self.Protocol.send_part_chan(uidornickname=clone.uid, channel=clone_channel_to_part, print_log=False) + else: - if self.Clone.exists(clone_name): - clone_uid = self.Clone.get_uid(clone_name) + if self.Clone.nickname_exists(option): + clone_uid = self.Clone.get_uid(option) if not clone_uid is None: self.Protocol.send_part_chan(uidornickname=clone_uid, channel=clone_channel_to_part, print_log=False) @@ -407,7 +314,7 @@ class Clone(): final_message = ' '.join(cmd[4:]) - if clone_channel is None or not self.Clone.exists(clone_name): + if clone_channel is None or not self.Clone.nickname_exists(clone_name): self.Protocol.send_notice( nick_from=dnickname, nick_to=fromuser, @@ -415,7 +322,7 @@ class Clone(): ) return None - if self.Clone.exists(clone_name): + if self.Clone.nickname_exists(clone_name): self.Protocol.send_priv_msg(nick_from=clone_name, msg=final_message, channel=clone_channel) except Exception as err: @@ -428,12 +335,12 @@ class Clone(): case _: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone connect NUMBER GROUP_NAME INTERVAL") - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone kill [all | nickname]") - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone join [all | nickname] #channel") - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone part [all | nickname] #channel") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone kill [all | group name | nickname]") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone join [all | group name | nickname] #channel") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone part [all | group name | nickname] #channel") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone list") except IndexError as ie: self.Logs.error(f'Index Error: {ie}') except Exception as err: - self.Logs.error(f'Index Error: {err}') + self.Logs.error(f'General Error: {err}') diff --git a/mods/clone/schemas.py b/mods/clone/schemas.py new file mode 100644 index 0000000..d45fafe --- /dev/null +++ b/mods/clone/schemas.py @@ -0,0 +1,22 @@ +from core.definition import MainModel, dataclass, field + +@dataclass +class ModConfModel(MainModel): + clone_nicknames: list[str] = field(default_factory=list) + +@dataclass +class MClone(MainModel): + """Model Clone""" + connected: bool = False + uid: str = None + nickname: str = None + username: str = None + realname: str = None + channels: list = field(default_factory=list) + vhost: str = None + hostname: str = 'localhost' + umodes: str = None + remote_ip: str = '127.0.0.1' + group: str = 'Default' + +DB_CLONES: list[MClone] = [] \ No newline at end of file diff --git a/mods/clone/threads.py b/mods/clone/threads.py new file mode 100644 index 0000000..60e40e4 --- /dev/null +++ b/mods/clone/threads.py @@ -0,0 +1,39 @@ +from typing import TYPE_CHECKING +from time import sleep + +if TYPE_CHECKING: + from mods.clone.mod_clone import Clone + +def thread_connect_clones(uplink: 'Clone', + number_of_clones:int , + group: str = 'Default', + auto_remote_ip: bool = False, + interval: float = 0.2 + ): + + for i in range(0, number_of_clones): + uplink.Utils.create_new_clone(uplink, uplink.Faker, group=group, auto_remote_ip=auto_remote_ip) + + for clone in uplink.Clone.UID_CLONE_DB: + + if uplink.stop: + print(f"Stop creating clones ...") + uplink.stop = False + break + + if not clone.connected: + uplink.Protocol.send_uid(clone.nickname, clone.username, clone.hostname, clone.uid, clone.umodes, clone.vhost, clone.remote_ip, clone.realname, print_log=False) + uplink.Protocol.send_join_chan(uidornickname=clone.uid, channel=uplink.Config.CLONE_CHANNEL, password=uplink.Config.CLONE_CHANNEL_PASSWORD, print_log=False) + + sleep(interval) + clone.connected = True + +def thread_kill_clones(uplink: 'Clone'): + + clone_to_kill = uplink.Clone.UID_CLONE_DB.copy() + + for clone in clone_to_kill: + uplink.Protocol.send_quit(clone.uid, 'Gooood bye', print_log=False) + uplink.Clone.delete(clone.uid) + + del clone_to_kill diff --git a/mods/clone/utils.py b/mods/clone/utils.py new file mode 100644 index 0000000..a636b68 --- /dev/null +++ b/mods/clone/utils.py @@ -0,0 +1,198 @@ +import logging +import random +from typing import Optional, TYPE_CHECKING +from faker import Faker + +logging.getLogger('faker').setLevel(logging.CRITICAL) + +if TYPE_CHECKING: + from mods.clone.mod_clone import Clone + +def create_faker_object(faker_local: Optional[str] = 'en_GB') -> Faker: + """Create a new faker object + + Args: + faker_local (Optional[str], optional): _description_. Defaults to 'en_GB'. + + Returns: + Faker: The Faker Object + """ + if faker_local not in ['en_GB', 'fr_FR']: + faker_local = 'en_GB' + + return Faker(faker_local) + +def generate_uid_for_clone(faker_instance: 'Faker', server_id: str) -> str: + chaine = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' + return server_id + ''.join(faker_instance.random_sample(chaine, 6)) + +def generate_vhost_for_clone(faker_instance: 'Faker') -> str: + """Generate new vhost for the clone + + Args: + faker_instance (Faker): The Faker instance + + Returns: + str: _description_ + """ + rand_1 = faker_instance.random_elements(['A','B','C','D','E','F','0','1','2','3','4','5','6','7','8','9'], unique=True, length=8) + rand_2 = faker_instance.random_elements(['A','B','C','D','E','F','0','1','2','3','4','5','6','7','8','9'], unique=True, length=8) + rand_3 = faker_instance.random_elements(['A','B','C','D','E','F','0','1','2','3','4','5','6','7','8','9'], unique=True, length=8) + + vhost = ''.join(rand_1) + '.' + ''.join(rand_2) + '.' + ''.join(rand_3) + '.IP' + return vhost + +def generate_username_for_clone(faker_instance: 'Faker') -> str: + """Generate vhosts for clones + + Returns: + str: The vhost + """ + chaine = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' + return ''.join(faker_instance.random_sample(chaine, 9)) + +def generate_realname_for_clone(faker_instance: 'Faker') -> tuple[int, str, str]: + """Generate realname for clone + Ex: XX F|M Department + Args: + faker_instance (Faker): _description_ + + Returns: + tuple: Age | Gender | Department + """ + # Create realname XX F|M Department + gender = faker_instance.random_choices(['F','M'], 1) + gender = ''.join(gender) + age = random.randint(20, 60) + if faker_instance.locales[0] == 'fr_FR': + department = faker_instance.department_name() + else: + department = faker_instance.city() + + return (age, gender, department) + +def generate_nickname_for_clone(faker_instance: 'Faker', gender: Optional[str] = 'AUTO') -> str: + """Generate nickname for clone + + Args: + faker_instance (Faker): The Faker Instance + gender (str): The Gender.Default F + + Returns: + str: Nickname Based on the Gender + """ + if gender.upper() == 'AUTO' or gender.upper() not in ['F', 'M']: + # Generate new gender + gender = faker_instance.random_choices(['F','M'], 1) + gender = ''.join(gender) + + if gender.upper() == 'F': + return faker_instance.first_name_female() + elif gender.upper() == 'M': + return faker_instance.first_name_male() + +def generate_ipv4_for_clone(faker_instance: 'Faker', auto: bool = True) -> str: + """Generate remote ipv4 for clone + + Args: + faker_instance (Faker): The Faker Instance + auto (bool): Set auto generation of ip or 127.0.0.1 will be returned + + Returns: + str: Remote IPV4 + """ + return faker_instance.ipv4_private() if auto else '127.0.0.1' + +def generate_hostname_for_clone(faker_instance: 'Faker') -> str: + """Generate hostname for clone + + Args: + faker_instance (Faker): The Faker Instance + + Returns: + str: New hostname + """ + return faker_instance.hostname() + +def create_new_clone(uplink: 'Clone', faker_instance: 'Faker', group: str = 'Default', auto_remote_ip: bool = False) -> bool: + """Create a new Clone object in the DB_CLONES. + + Args: + faker_instance (Faker): The Faker instance + + Returns: + bool: True if it was created + """ + faker = faker_instance + + uid = generate_uid_for_clone(faker, uplink.Config.SERVEUR_ID) + umodes = uplink.Config.CLONE_UMODES + + # Generate Username + username = generate_username_for_clone(faker) + + # Generate realname (XX F|M Department) + age, gender, department = generate_realname_for_clone(faker) + realname = f'{age} {gender} {department}' + + # Generate nickname + nickname = generate_nickname_for_clone(faker, gender) + + # Generate decoded ipv4 and hostname + decoded_ip = generate_ipv4_for_clone(faker, auto_remote_ip) + hostname = generate_hostname_for_clone(faker) + vhost = generate_vhost_for_clone(faker) + + checkNickname = uplink.Clone.nickname_exists(nickname) + checkUid = uplink.Clone.uid_exists(uid=uid) + + while checkNickname: + caracteres = '0123456789' + randomize = ''.join(random.choice(caracteres) for _ in range(2)) + nickname = nickname + str(randomize) + checkNickname = uplink.Clone.nickname_exists(nickname) + + while checkUid: + uid = generate_uid_for_clone(faker, uplink.Config.SERVEUR_ID) + checkUid = uplink.Clone.uid_exists(uid=uid) + + clone = uplink.Definition.MClone( + connected=False, + nickname=nickname, + username=username, + realname=realname, + hostname=hostname, + umodes=umodes, + uid=uid, + remote_ip=decoded_ip, + vhost=vhost, + group=group, + channels=[] + ) + + uplink.Clone.insert(clone) + + return True + +def handle_on_privmsg(uplink: 'Clone', srvmsg: list[str]): + + uid_sender = uplink.User.clean_uid(srvmsg[1]) + senderObj = uplink.User.get_User(uid_sender) + + if senderObj.hostname in uplink.Config.CLONE_LOG_HOST_EXEMPT: + return + + if not senderObj is None: + senderMsg = ' '.join(srvmsg[4:]) + clone_obj = uplink.Clone.get_clone(srvmsg[3]) + + if clone_obj is None: + return + + if clone_obj.uid != uplink.Config.SERVICE_ID: + final_message = f"{senderObj.nickname}!{senderObj.username}@{senderObj.hostname} > {senderMsg.lstrip(':')}" + uplink.Protocol.send_priv_msg( + nick_from=clone_obj.uid, + msg=final_message, + channel=uplink.Config.CLONE_CHANNEL + ) diff --git a/version.json b/version.json index 265b584..a4a7194 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { - "version": "6.1.4", + "version": "6.2.0", "requests": "2.32.3", "psutil": "6.0.0", From 6b22d786e399c1df181cac42467a1fcaa1efe66e Mon Sep 17 00:00:00 2001 From: adator85 <> Date: Fri, 15 Aug 2025 15:47:01 +0200 Subject: [PATCH 08/20] Adding some comments, editing methods names --- .idea/.gitignore | 8 ++ .idea/DEFENDER.iml | 14 +++ .../inspectionProfiles/profiles_settings.xml | 7 ++ .idea/misc.xml | 7 ++ .idea/modules.xml | 8 ++ .idea/vcs.xml | 6 ++ core/base.py | 101 ++++++++---------- core/classes/channel.py | 93 +++++++++++----- core/classes/protocols/inspircd.py | 9 +- core/classes/protocols/unreal6.py | 21 ++-- core/classes/settings.py | 3 +- core/definition.py | 15 --- core/installation.py | 16 +-- core/irc.py | 19 ++-- core/loader.py | 17 +-- core/utils.py | 42 +++++--- mods/clone/clone_manager.py | 31 ++---- mods/clone/mod_clone.py | 44 ++++---- mods/clone/threads.py | 7 +- mods/command/mod_command.py | 32 +++--- mods/defender/mod_defender.py | 56 +++++++--- mods/defender/threads.py | 2 +- mods/defender/utils.py | 6 +- mods/jsonrpc/mod_jsonrpc.py | 7 +- mods/votekick/mod_votekick.py | 6 +- 25 files changed, 344 insertions(+), 233 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/DEFENDER.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/DEFENDER.iml b/.idea/DEFENDER.iml new file mode 100644 index 0000000..c299fe3 --- /dev/null +++ b/.idea/DEFENDER.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..dd4c951 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..4ab2f6a --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..12174db --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/core/base.py b/core/base.py index a39915b..fe4d47d 100644 --- a/core/base.py +++ b/core/base.py @@ -11,40 +11,42 @@ import logging import threading import ipaddress import ast +import requests from pathlib import Path from types import ModuleType -import requests from dataclasses import fields -from typing import Union, TYPE_CHECKING +from typing import Any, Optional, TYPE_CHECKING from base64 import b64decode, b64encode from datetime import datetime, timedelta, timezone from sqlalchemy import create_engine, Engine, Connection, CursorResult from sqlalchemy.sql import text -from core.definition import MConfig if TYPE_CHECKING: - from core.classes.settings import Settings + from core.loader import Loader class Base: - def __init__(self, Config: MConfig, settings: 'Settings') -> None: + def __init__(self, loader: 'Loader') -> None: + + self.Loader = loader + self.Config = loader.Config + self.Settings = loader.Settings + self.Utils = loader.Utils - self.Config = Config # Assigner l'objet de configuration - self.Settings: Settings = settings self.init_log_system() # Demarrer le systeme de log self.check_for_new_version(True) # Verifier si une nouvelle version est disponible # Liste des timers en cours - self.running_timers:list[threading.Timer] = self.Settings.RUNNING_TIMERS + self.running_timers: list[threading.Timer] = self.Settings.RUNNING_TIMERS # Liste des threads en cours - self.running_threads:list[threading.Thread] = self.Settings.RUNNING_THREADS + self.running_threads: list[threading.Thread] = self.Settings.RUNNING_THREADS # Les sockets ouvert self.running_sockets: list[socket.socket] = self.Settings.RUNNING_SOCKETS # Liste des fonctions en attentes - self.periodic_func:dict[object] = self.Settings.PERIODIC_FUNC + self.periodic_func: dict[object] = self.Settings.PERIODIC_FUNC # Création du lock self.lock = self.Settings.LOCK @@ -150,13 +152,6 @@ class Base: return unixtime - def get_datetime(self) -> str: - """ - Retourne une date au format string (24-12-2023 20:50:59) - """ - currentdate = datetime.now().strftime('%d-%m-%Y %H:%M:%S') - return currentdate - def get_all_modules(self) -> list[str]: """Get list of all main modules using this pattern mod_*.py @@ -190,20 +185,20 @@ class Base: importlib.reload(module) self.logs.debug(f'[LOAD_MODULE] Module {module} success') - except Exception: - self.logs.error(f'[LOAD_MODULE] Module {module} failed [!]') + except Exception as err: + self.logs.error(f'[LOAD_MODULE] Module {module} failed [!] - {err}') def create_log(self, log_message: str) -> None: """Enregiste les logs Args: - string (str): Le message a enregistrer + log_message (str): Le message a enregistrer Returns: None: Aucun retour """ sql_insert = f"INSERT INTO {self.Config.TABLE_LOG} (datetime, server_msg) VALUES (:datetime, :server_msg)" - mes_donnees = {'datetime': str(self.get_datetime()),'server_msg': f'{log_message}'} + mes_donnees = {'datetime': str(self.Utils.get_sdatetime()),'server_msg': f'{log_message}'} self.db_execute_query(sql_insert, mes_donnees) return None @@ -247,13 +242,14 @@ class Base: def replace_filter(self, record: logging.LogRecord) -> bool: response = True - filter: list[str] = ['PING', f":{self.Config.SERVICE_PREFIX}auth"] + filters: list[str] = ['PING', + f':{self.Config.SERVICE_PREFIX}auth'] # record.msg = record.getMessage().replace("PING", "[REDACTED]") if self.Settings.CONSOLE: print(record.getMessage()) - for f in filter: + for f in filters: if f in record.getMessage(): response = False @@ -274,10 +270,11 @@ class Base: return None - def log_cmd(self, user_cmd:str, cmd:str) -> None: + def log_cmd(self, user_cmd: str, cmd: str) -> None: """Enregistre les commandes envoyées par les utilisateurs Args: + user_cmd (str): The user who performed the command cmd (str): la commande a enregistrer """ cmd_list = cmd.split() @@ -288,10 +285,10 @@ class Base: cmd = ' '.join(cmd_list) insert_cmd_query = f"INSERT INTO {self.Config.TABLE_COMMAND} (datetime, user, commande) VALUES (:datetime, :user, :commande)" - mes_donnees = {'datetime': self.get_datetime(), 'user': user_cmd, 'commande': cmd} + mes_donnees = {'datetime': self.Utils.get_sdatetime(), 'user': user_cmd, 'commande': cmd} self.db_execute_query(insert_cmd_query, mes_donnees) - return False + return None def db_isModuleExist(self, module_name:str) -> bool: """Teste si un module existe déja dans la base de données @@ -311,22 +308,24 @@ class Base: else: return False - def db_record_module(self, user_cmd:str, module_name:str, isdefault:int = 0) -> None: + def db_record_module(self, user_cmd: str, module_name: str, isdefault: int = 0) -> None: """Enregistre les modules dans la base de données Args: - cmd (str): le module a enregistrer + user_cmd (str): The user who performed the command + module_name (str): The module name + isdefault (int): Is this a default module. Default 0 """ 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_name, isdefault) VALUES (:datetime, :user, :module_name, :isdefault)" - mes_donnees = {'datetime': self.get_datetime(), 'user': user_cmd, 'module_name': module_name, 'isdefault': isdefault} + mes_donnees = {'datetime': self.Utils.get_sdatetime(), 'user': user_cmd, 'module_name': module_name, 'isdefault': isdefault} self.db_execute_query(insert_cmd_query, mes_donnees) else: self.logs.debug(f"Le module {module_name} existe déja dans la base de données") - return False + return None def db_update_module(self, user_cmd: str, module_name: str) -> None: """Modifie la date et le user qui a rechargé le module @@ -336,22 +335,22 @@ class Base: module_name (str): le module a rechargé """ update_cmd_query = f"UPDATE {self.Config.TABLE_MODULE} SET datetime = :datetime, user = :user WHERE module_name = :module_name" - mes_donnees = {'datetime': self.get_datetime(), 'user': user_cmd, 'module_name': module_name} + mes_donnees = {'datetime': self.Utils.get_sdatetime(), 'user': user_cmd, 'module_name': module_name} self.db_execute_query(update_cmd_query, mes_donnees) - return False + return None def db_delete_module(self, module_name:str) -> None: """Supprime les modules de la base de données Args: - cmd (str): le module a supprimer + module_name (str): The module name you want to delete """ 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 + return None def db_sync_core_config(self, module_name: str, dataclassObj: object) -> bool: """Sync module local parameters with the database @@ -369,7 +368,7 @@ class Base: """ try: response = True - current_date = self.get_datetime() + current_date = self.Utils.get_sdatetime() core_table = self.Config.TABLE_CONFIG # Add local parameters to DB @@ -446,7 +445,7 @@ class Base: isParamExist = result.fetchone() if not isParamExist is None: - mes_donnees = {'datetime': self.get_datetime(), + mes_donnees = {'datetime': self.Utils.get_sdatetime(), 'module_name': module_name, 'param_key': param_key, 'param_value': param_value @@ -471,9 +470,9 @@ class Base: user = self.db_execute_query(f"SELECT id FROM {self.Config.TABLE_ADMIN}") if not user.fetchall(): admin = self.Config.OWNER - password = self.crypt_password(self.Config.PASSWORD) + password = self.Utils.hash_password(self.Config.PASSWORD) - mes_donnees = {'createdOn': self.get_datetime(), + mes_donnees = {'createdOn': self.Utils.get_sdatetime(), 'user': admin, 'password': password, 'hostname': '*', @@ -500,8 +499,11 @@ class Base: self.logs.debug(f"-- Timer ID : {str(t.ident)} | Running Threads : {len(threading.enumerate())}") + return None + except AssertionError as ae: self.logs.error(f'Assertion Error -> {ae}') + return None def create_thread(self, func:object, func_args: tuple = (), run_once:bool = False, daemon: bool = True) -> None: """Create a new thread and store it into running_threads variable @@ -737,19 +739,6 @@ class Base: except AttributeError as ae: self.logs.error(f"Attribute Error : {ae}") - def crypt_password(self, password:str) -> str: - """Retourne un mot de passe chiffré en MD5 - - Args: - password (str): Le password en clair - - Returns: - str: Le password en MD5 - """ - md5_password = hashlib.md5(password.encode()).hexdigest() - - return md5_password - def int_if_possible(self, value): """Convertit la valeur reçue en entier, si possible. Sinon elle retourne la valeur initiale. @@ -768,14 +757,14 @@ class Base: except TypeError: return value - def convert_to_int(self, value: any) -> Union[int, None]: + def convert_to_int(self, value: Any) -> Optional[int]: """Convert a value to int Args: value (any): Value to convert to int if possible Returns: - Union[int, None]: Return the int value or None if not possible + int: Return the int value or None if not possible """ try: response = int(value) @@ -816,7 +805,7 @@ class Base: self.logs.error(f'General Error: {err}') return False - def decode_ip(self, ip_b64encoded: str) -> Union[str, None]: + def decode_ip(self, ip_b64encoded: str) -> Optional[str]: binary_ip = b64decode(ip_b64encoded) try: @@ -827,7 +816,7 @@ class Base: self.logs.critical(f'This remote ip is not valid : {ve}') return None - def encode_ip(self, remote_ip_address: str) -> Union[str, None]: + def encode_ip(self, remote_ip_address: str) -> Optional[str]: binary_ip = socket.inet_aton(remote_ip_address) try: @@ -890,7 +879,7 @@ class Base: self.logs.debug(f'Method to execute : {str(self.periodic_func)}') return None - def clean_uid(self, uid:str) -> Union[str, None]: + def clean_uid(self, uid:str) -> Optional[str]: """Clean UID by removing @ / % / + / ~ / * / : Args: diff --git a/core/classes/channel.py b/core/classes/channel.py index 6d4380f..f628394 100644 --- a/core/classes/channel.py +++ b/core/classes/channel.py @@ -3,7 +3,7 @@ from typing import Any, Optional, Literal, TYPE_CHECKING if TYPE_CHECKING: from core.definition import MChannel - from core.base import Base + from core.loader import Loader class Channel: @@ -11,10 +11,11 @@ class Channel: """List that contains all the Channels objects (ChannelModel) """ - def __init__(self, base: 'Base') -> None: + def __init__(self, loader: 'Loader') -> None: - self.Logs = base.logs - self.Base = base + self.Logs = loader.Base.logs + self.Base = loader.Base + self.Utils = loader.Utils return None @@ -22,7 +23,7 @@ class Channel: """This method will insert a new channel and if the channel exist it will update the user list (uids) Args: - newChan (ChannelModel): The channel model object + new_channel (MChannel): The channel model object Returns: bool: True if new channel, False if channel exist (However UID could be updated) @@ -30,7 +31,7 @@ class Channel: result = False exist = False - if not self.Is_Channel(new_channel.name): + if not self.is_valid_channel(new_channel.name): self.Logs.error(f"The channel {new_channel.name} is not valid, channel must start with #") return False @@ -64,8 +65,16 @@ class Channel: return result def delete(self, channel_name: str) -> bool: + """Delete channel from the UID_CHANNEL_DB - chan_obj = self.get_Channel(channel_name) + Args: + channel_name (str): The Channel name + + Returns: + bool: True if it was deleted + """ + + chan_obj = self.get_channel(channel_name) if chan_obj is None: return False @@ -75,10 +84,19 @@ class Channel: return True def delete_user_from_channel(self, channel_name: str, uid:str) -> bool: + """Delete a user from a channel + + Args: + channel_name (str): The channel name + uid (str): The Client UID + + Returns: + bool: True if the client has been deleted from the channel + """ try: result = False - chan_obj = self.get_Channel(channel_name.lower()) + chan_obj = self.get_channel(channel_name.lower()) if chan_obj is None: return result @@ -95,6 +113,14 @@ class Channel: self.Logs.error(f'{ve}') def delete_user_from_all_channel(self, uid:str) -> bool: + """Delete a client from all channels + + Args: + uid (str): The client UID + + Returns: + bool: True if the client has been deleted from all channels + """ try: result = False @@ -111,14 +137,22 @@ class Channel: self.Logs.error(f'{ve}') def add_user_to_a_channel(self, channel_name: str, uid: str) -> bool: + """Add a client to a channel + + Args: + channel_name (str): The channel name + uid (str): The client UID + + Returns: + bool: True is the clien has been added + """ try: - result = False - chan_obj = self.get_Channel(channel_name) - self.Logs.debug(f"** {__name__}") + chan_obj = self.get_channel(channel_name) if chan_obj is None: - result = self.insert(MChannel(channel_name, uids=[uid])) - return result + # Create a new channel if the channel don't exist + self.Logs.debug(f"New channel will be created ({channel_name} - {uid})") + return self.insert(MChannel(channel_name, uids=[uid])) chan_obj.uids.append(uid) del_duplicates = list(set(chan_obj.uids)) @@ -127,18 +161,19 @@ class Channel: return True except Exception as err: self.Logs.error(f'{err}') + return False def is_user_present_in_channel(self, channel_name: str, uid: str) -> bool: """Check if a user is present in the channel Args: - channel_name (str): The channel to check - uid (str): The UID + channel_name (str): The channel name to check + uid (str): The client UID Returns: bool: True if the user is present in the channel """ - chan = self.get_Channel(channel_name=channel_name) + chan = self.get_channel(channel_name=channel_name) if chan is None: return False @@ -150,7 +185,7 @@ class Channel: return False def clean_channel(self) -> None: - """Remove Channels if empty + """If channel doesn't contain any client this method will remove the channel """ try: for record in self.UID_CHANNEL_DB: @@ -161,25 +196,33 @@ class Channel: except Exception as err: self.Logs.error(f'{err}') - def get_Channel(self, channel_name: str) -> Optional['MChannel']: + def get_channel(self, channel_name: str) -> Optional['MChannel']: + """Get the channel object + + Args: + channel_name (str): The Channel name + + Returns: + MChannel: The channel object model if exist else None + """ for record in self.UID_CHANNEL_DB: - if record.name == channel_name: + if record.name.lower() == channel_name.lower(): return record return None - def get_channel_asdict(self, chan_name: str) -> Optional[dict[str, Any]]: + def get_channel_asdict(self, channel_name: str) -> Optional[dict[str, Any]]: - channel_obj: Optional['MChannel'] = self.get_Channel(chan_name) + channel_obj: Optional['MChannel'] = self.get_channel(channel_name) if channel_obj is None: return None return channel_obj.to_dict() - def Is_Channel(self, channel_to_check: str) -> bool: - """Check if the string has the # caractere and return True if this is a channel + def is_valid_channel(self, channel_to_check: str) -> bool: + """Check if the string has the # caractere and return True if this is a valid channel Args: channel_to_check (str): The string to test if it is a channel or not @@ -216,7 +259,7 @@ class Channel: bool: True if action done """ try: - channel_name = channel_name.lower() if self.Is_Channel(channel_name) else None + channel_name = channel_name.lower() if self.is_valid_channel(channel_name) else None core_table = self.Base.Config.TABLE_CHANNEL if not channel_name: @@ -231,7 +274,7 @@ class Channel: is_channel_exist = response.fetchone() if is_channel_exist is None: - mes_donnees = {'datetime': self.Base.get_datetime(), 'channel_name': channel_name, 'module_name': module_name} + mes_donnees = {'datetime': self.Utils.get_sdatetime(), 'channel_name': channel_name, 'module_name': module_name} insert = self.Base.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}') diff --git a/core/classes/protocols/inspircd.py b/core/classes/protocols/inspircd.py index c33eaf6..81bc9c8 100644 --- a/core/classes/protocols/inspircd.py +++ b/core/classes/protocols/inspircd.py @@ -14,6 +14,7 @@ class Inspircd: self.__Irc = ircInstance self.__Config = ircInstance.Config self.__Base = ircInstance.Base + self.__Utils = ircInstance.Loader.Utils self.__Base.logs.info(f"** Loading protocol [{__name__}]") @@ -169,7 +170,7 @@ class Inspircd: def sjoin(self, channel: str) -> None: - if not self.__Irc.Channel.Is_Channel(channel): + if not self.__Irc.Channel.is_valid_channel(channel): self.__Base.logs.error(f"The channel [{channel}] is not valid") return None @@ -259,7 +260,7 @@ class Inspircd: if userObj is None: return None - if not self.__Irc.Channel.Is_Channel(channel): + if not self.__Irc.Channel.is_valid_channel(channel): self.__Base.logs.error(f"The channel [{channel}] is not valid") return None @@ -284,7 +285,7 @@ class Inspircd: self.__Base.logs.error(f"The user [{uidornickname}] is not valid") return None - if not self.__Irc.Channel.Is_Channel(channel): + if not self.__Irc.Channel.is_valid_channel(channel): self.__Base.logs.error(f"The channel [{channel}] is not valid") return None @@ -600,7 +601,7 @@ class Inspircd: nickname = self.__Irc.User.get_nickname(self.__Base.clean_uid(serverMsg[1])) dnickname = self.__Config.SERVICE_NICKNAME arg = serverMsg[4].replace(':', '') - current_datetime = self.__Base.get_datetime() + current_datetime = self.__Utils.get_sdatetime() if nickname is None: return None diff --git a/core/classes/protocols/unreal6.py b/core/classes/protocols/unreal6.py index 6a47350..f6166e5 100644 --- a/core/classes/protocols/unreal6.py +++ b/core/classes/protocols/unreal6.py @@ -16,6 +16,7 @@ class Unrealircd6: self.__Config = ircInstance.Config self.__Base = ircInstance.Base self.__Settings = ircInstance.Base.Settings + self.__Utils = ircInstance.Loader.Utils self.known_protocol: set[str] = {'SJOIN', 'UID', 'MD', 'QUIT', 'SQUIT', 'EOS', 'PRIVMSG', 'MODE', 'UMODE2', @@ -242,7 +243,7 @@ class Unrealircd6: Args: channel (str): Channel to join """ - if not self.__Irc.Channel.Is_Channel(channel): + if not self.__Irc.Channel.is_valid_channel(channel): self.__Base.logs.error(f"The channel [{channel}] is not valid") return None @@ -264,7 +265,7 @@ class Unrealircd6: try: userObj = self.__Irc.User.get_User(uidornickname=nick_to_sapart) - chanObj = self.__Irc.Channel.get_Channel(channel_name) + chanObj = self.__Irc.Channel.get_channel(channel_name) service_uid = self.__Config.SERVICE_ID if userObj is None or chanObj is None: @@ -288,7 +289,7 @@ class Unrealircd6: try: userObj = self.__Irc.User.get_User(uidornickname=nick_to_sajoin) - chanObj = self.__Irc.Channel.get_Channel(channel_name) + chanObj = self.__Irc.Channel.get_channel(channel_name) service_uid = self.__Config.SERVICE_ID if userObj is None: @@ -297,7 +298,7 @@ class Unrealircd6: if chanObj is None: # Channel not exist - if not self.__Irc.Channel.Is_Channel(channel_name): + if not self.__Irc.Channel.is_valid_channel(channel_name): # Incorrect channel: leave return None @@ -412,7 +413,7 @@ class Unrealircd6: if userObj is None: return None - if not self.__Irc.Channel.Is_Channel(channel): + if not self.__Irc.Channel.is_valid_channel(channel): self.__Base.logs.error(f"The channel [{channel}] is not valid") return None @@ -452,7 +453,7 @@ class Unrealircd6: self.__Base.logs.error(f"The user [{uidornickname}] is not valid") return None - if not self.__Irc.Channel.Is_Channel(channel): + if not self.__Irc.Channel.is_valid_channel(channel): self.__Base.logs.error(f"The channel [{channel}] is not valid") return None @@ -464,7 +465,7 @@ class Unrealircd6: def send_mode_chan(self, channel_name: str, channel_mode: str) -> None: - channel = self.__Irc.Channel.Is_Channel(channel_name) + channel = self.__Irc.Channel.is_valid_channel(channel_name) if not channel: self.__Base.logs.error(f'The channel [{channel_name}] is not correct') return None @@ -939,7 +940,7 @@ class Unrealircd6: cmd_to_send = convert_to_string.replace(':','') self.__Base.log_cmd(user_trigger, cmd_to_send) - fromchannel = str(cmd[2]).lower() if self.__Irc.Channel.Is_Channel(cmd[2]) else None + fromchannel = str(cmd[2]).lower() if self.__Irc.Channel.is_valid_channel(cmd[2]) else None self.__Irc.hcmds(user_trigger, fromchannel, arg, cmd) if cmd[2] == self.__Config.SERVICE_ID: @@ -975,7 +976,7 @@ class Unrealircd6: fromchannel = None if len(arg) >= 2: - fromchannel = str(arg[1]).lower() if self.__Irc.Channel.Is_Channel(arg[1]) else None + fromchannel = str(arg[1]).lower() if self.__Irc.Channel.is_valid_channel(arg[1]) else None self.__Irc.hcmds(user_trigger, fromchannel, arg, cmd) return None @@ -1039,7 +1040,7 @@ class Unrealircd6: nickname = self.__Irc.User.get_nickname(self.__Base.clean_uid(serverMsg[1])) dnickname = self.__Config.SERVICE_NICKNAME arg = serverMsg[4].replace(':', '') - current_datetime = self.__Base.get_datetime() + current_datetime = self.__Utils.get_sdatetime() if nickname is None: return None diff --git a/core/classes/settings.py b/core/classes/settings.py index 46350df..c37a37b 100644 --- a/core/classes/settings.py +++ b/core/classes/settings.py @@ -26,7 +26,8 @@ class Settings: def set_cache(self, key: str, value_to_cache: Any): """When you want to store a variable - Ex. + + Ex. ```python set_cache('MY_KEY', {'key1': 'value1', 'key2', 'value2'}) ``` diff --git a/core/definition.py b/core/definition.py index af8f262..0398c50 100644 --- a/core/definition.py +++ b/core/definition.py @@ -320,21 +320,6 @@ class MConfig(MainModel): self.SERVEUR_CHARSET: list = ["utf-8", "iso-8859-1"] """0: utf-8 | 1: iso-8859-1""" -@dataclass -class MClone(MainModel): - """Model Clone""" - connected: bool = False - uid: str = None - nickname: str = None - username: str = None - realname: str = None - channels: list = field(default_factory=list) - vhost: str = None - hostname: str = 'localhost' - umodes: str = None - remote_ip: str = '127.0.0.1' - group: str = 'Default' - @dataclass class MCommand(MainModel): module_name: str = None diff --git a/core/installation.py b/core/installation.py index c7ae319..f0c14b2 100644 --- a/core/installation.py +++ b/core/installation.py @@ -261,21 +261,21 @@ class Install: if not do_install: return None - print("===> Vider le cache de pip") + print("===> Clean pip cache") self.run_subprocess([self.config.venv_pip_executable, 'cache', 'purge']) - print("===> Verifier si pip est a jour") + print("===> Check if pip is up to date") self.run_subprocess([self.config.venv_python_executable, '-m', 'pip', 'install', '--upgrade', 'pip']) if not self.check_package('greenlet'): self.run_subprocess([self.config.venv_pip_executable, 'install', '--only-binary', ':all:', 'greenlet']) - print('====> Module Greenlet installé') + print('====> Greenlet installed') for module in self.config.venv_cmd_requirements: if not self.check_package(module): print("### Trying to install missing python packages ###") self.run_subprocess([self.config.venv_pip_executable, 'install', module]) - print(f"====> Module {module} installé") + print(f"====> Module {module} installed!") else: print(f"==> {module} already installed") @@ -307,8 +307,8 @@ WantedBy=default.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 ...') + print('Service file generated with current configuration') + print('Running IRC Service ...') self.run_subprocess(self.config.service_cmd_daemon_reload) self.run_subprocess(self.config.service_cmd_executable) @@ -316,8 +316,8 @@ WantedBy=default.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 ...') + print('Service file generated with current configuration') + print('Running IRC Service ...') self.run_subprocess(self.config.service_cmd_daemon_reload) self.run_subprocess(self.config.service_cmd_executable) diff --git a/core/irc.py b/core/irc.py index df33bcf..f8b88fa 100644 --- a/core/irc.py +++ b/core/irc.py @@ -31,6 +31,9 @@ class Irc: # Load the configuration self.Config = self.Loader.Config + # Load Main utils functions + self.Utils = self.Loader.Utils + # Date et heure de la premiere connexion de Defender self.defender_connexion_datetime = self.Config.DEFENDER_CONNEXION_DATETIME @@ -802,7 +805,7 @@ class Irc: hostname = get_user.hostname vhost = get_user.vhost - spassword = self.Base.crypt_password(password) + spassword = self.Loader.Utils.hash_password(password) mes_donnees = {'admin': nickname} query_search_user = f"SELECT id FROM {self.Config.TABLE_ADMIN} WHERE user=:admin" @@ -811,7 +814,7 @@ class Irc: # On verifie si le user exist dans la base if not exist_user: - mes_donnees = {'datetime': self.Base.get_datetime(), 'user': nickname, 'password': spassword, 'hostname': hostname, 'vhost': vhost, 'level': level} + mes_donnees = {'datetime': self.Utils.get_sdatetime(), 'user': nickname, 'password': spassword, 'hostname': hostname, 'vhost': vhost, 'level': level} self.Base.db_execute_query(f'''INSERT INTO {self.Config.TABLE_ADMIN} (createdOn, user, password, hostname, vhost, level) VALUES (:datetime, :user, :password, :hostname, :vhost, :level) @@ -833,7 +836,7 @@ class Irc: log_msg (str): the message to log """ try: - mes_donnees = {'datetime': self.Base.get_datetime(), 'server_msg': log_msg} + mes_donnees = {'datetime': self.Utils.get_sdatetime(), 'server_msg': log_msg} self.Base.db_execute_query(f'INSERT INTO {self.Config.TABLE_LOG} (datetime, server_msg) VALUES (:datetime, :server_msg)', mes_donnees) return None @@ -1151,7 +1154,7 @@ class Irc: return False if not user_to_log is None: - mes_donnees = {'user': user_to_log, 'password': self.Base.crypt_password(password)} + mes_donnees = {'user': user_to_log, 'password': self.Loader.Utils.hash_password(password)} query = f"SELECT id, level FROM {self.Config.TABLE_ADMIN} WHERE user = :user AND password = :password" result = self.Base.db_execute_query(query, mes_donnees) user_from_db = result.fetchone() @@ -1204,7 +1207,7 @@ class Irc: return None user_to_edit = cmd[1] - user_password = self.Base.crypt_password(cmd[2]) + user_password = self.Loader.Utils.hash_password(cmd[2]) get_admin = self.Admin.get_Admin(fromuser) if get_admin is None: @@ -1343,9 +1346,9 @@ class Irc: # If the account doesn't exist then insert into database data_to_record = { - 'createdOn': self.Base.get_datetime(), 'account': fromuser, + 'createdOn': self.Utils.get_sdatetime(), 'account': fromuser, 'nickname': user_obj.nickname, 'hostname': user_obj.hostname, 'vhost': user_obj.vhost, 'realname': user_obj.realname, 'email': email, - 'password': self.Base.crypt_password(password=password), 'level': 0 + 'password': self.Loader.Utils.hash_password(password=password), 'level': 0 } insert_to_db = self.Base.db_execute_query(f""" @@ -1380,7 +1383,7 @@ class Irc: return None account = str(cmd[1]) # account - encrypted_password = self.Base.crypt_password(cmd[2]) + encrypted_password = self.Loader.Utils.hash_password(cmd[2]) user_obj = self.User.get_User(fromuser) client_obj = self.Client.get_Client(user_obj.uid) diff --git a/core/loader.py b/core/loader.py index d9ebdf5..b45183f 100644 --- a/core/loader.py +++ b/core/loader.py @@ -1,25 +1,28 @@ from core.classes import user, admin, client, channel, reputation, settings, commands import core.definition as df -import core.base as baseModule -import core.classes.config as confModule +import core.utils as utils +import core.base as base_module +import core.classes.config as conf_module class Loader: def __init__(self): - # Load Modules - self.Definition: df = df + # Load Main Modules + self.Definition: df = df - self.ConfModule: confModule = confModule + self.ConfModule: conf_module = conf_module - self.BaseModule: baseModule = baseModule + self.BaseModule: base_module = base_module + + self.Utils: utils = utils # Load Classes self.Settings: settings.Settings = settings.Settings() self.Config: df.MConfig = self.ConfModule.Configuration().ConfigObject - self.Base: baseModule.Base = self.BaseModule.Base(self.Config, self.Settings) + self.Base: base_module.Base = self.BaseModule.Base(self) self.User: user.User = user.User(self.Base) diff --git a/core/utils.py b/core/utils.py index 10bb365..93188b1 100644 --- a/core/utils.py +++ b/core/utils.py @@ -1,28 +1,24 @@ -import sys -import importlib -from types import ModuleType -from typing import Literal, Union +from pathlib import Path +from typing import Literal, Optional, Any from datetime import datetime from time import time from random import choice from hashlib import md5, sha3_512 -def convert_to_int(value: any) -> Union[int, None]: +def convert_to_int(value: Any) -> Optional[int]: """Convert a value to int Args: - value (any): Value to convert to int if possible + value (Any): Value to convert to int if possible Returns: - Union[int, None]: Return the int value or None if not possible + int: Return the int value or None if not possible """ try: value_to_int = int(value) return value_to_int except ValueError: return None - except Exception: - return None def get_unixtime() -> int: """Cette fonction retourne un UNIXTIME de type 12365456 @@ -32,7 +28,7 @@ def get_unixtime() -> int: """ return int(time()) -def get_datetime() -> str: +def get_sdatetime() -> str: """Retourne une date au format string (24-12-2023 20:50:59) Returns: @@ -41,6 +37,12 @@ def get_datetime() -> str: currentdate = datetime.now().strftime('%d-%m-%Y %H:%M:%S') return currentdate +def get_datetime() -> datetime: + """ + Return the current datetime in a datetime object + """ + return datetime.now() + def generate_random_string(lenght: int) -> str: """Retourn une chaîne aléatoire en fonction de la longueur spécifiée. @@ -52,15 +54,15 @@ def generate_random_string(lenght: int) -> str: return randomize -def hash(password: str, algorithm: Literal["md5, sha3_512"] = 'md5') -> str: - """Retourne un mot de passe chiffré en fonction de l'algorithme utilisé +def hash_password(password: str, algorithm: Literal["md5, sha3_512"] = 'md5') -> str: + """Return the crypted password following the selected algorithm Args: - password (str): Le password en clair - algorithm (str): L'algorithm a utilisé + password (str): The plain text password + algorithm (str): The algorithm to use Returns: - str: Le password haché + str: The crypted password, default md5 """ match algorithm: @@ -75,3 +77,13 @@ def hash(password: str, algorithm: Literal["md5, sha3_512"] = 'md5') -> str: case _: password = md5(password.encode()).hexdigest() return password + +def get_all_modules() -> list[str]: + """Get list of all main modules + using this pattern mod_*.py + + Returns: + list[str]: List of module names. + """ + base_path = Path('mods') + return [file.name.replace('.py', '') for file in base_path.rglob('mod_*.py')] \ No newline at end of file diff --git a/mods/clone/clone_manager.py b/mods/clone/clone_manager.py index 9b53a69..486795b 100644 --- a/mods/clone/clone_manager.py +++ b/mods/clone/clone_manager.py @@ -1,5 +1,5 @@ from typing import Optional, TYPE_CHECKING -from core.definition import MClone +from mods.clone.schemas import MClone if TYPE_CHECKING: from mods.clone.mod_clone import Clone @@ -16,33 +16,24 @@ class CloneManager: """Create new Clone object Args: - newCloneObject (CloneModel): New CloneModel object + new_clone_object (MClone): New Clone object Returns: bool: True if inserted """ - result = False - exist = False + if new_clone_object is None: + self.Logs.debug('New Clone object must not be None') + return False for record in self.UID_CLONE_DB: - if record.nickname == new_clone_object.nickname: + if record.nickname == new_clone_object.nickname or record.uid == new_clone_object.uid: # If the user exist then return False and do not go further - exist = True - self.Logs.warning(f'Nickname {record.nickname} already exist') - return result - if record.uid == new_clone_object.uid: - exist = True - self.Logs.warning(f'UID: {record.uid} already exist') - return result + self.Logs.debug(f'Nickname/UID {record.nickname}/{record.uid} already exist') + return False - if not exist: - self.UID_CLONE_DB.append(new_clone_object) - result = True - - if not result: - self.Logs.critical(f'The Clone Object was not inserted {new_clone_object}') - - return result + self.UID_CLONE_DB.append(new_clone_object) + self.Logs.debug(f'New Clone object created: {new_clone_object}') + return True def delete(self, uidornickname: str) -> bool: """Delete the Clone Object starting from the nickname or the UID diff --git a/mods/clone/mod_clone.py b/mods/clone/mod_clone.py index d87706e..40e2ff2 100644 --- a/mods/clone/mod_clone.py +++ b/mods/clone/mod_clone.py @@ -1,6 +1,4 @@ -import time, logging -from typing import TYPE_CHECKING, Optional -from faker import Faker +from typing import TYPE_CHECKING, Optional, Any import mods.clone.utils as utils import mods.clone.threads as thds import mods.clone.schemas as schemas @@ -8,40 +6,41 @@ from mods.clone.clone_manager import CloneManager if TYPE_CHECKING: from core.irc import Irc + from faker import Faker -class Clone(): +class Clone: - def __init__(self, ircInstance: 'Irc') -> None: + def __init__(self, irc_instance: 'Irc') -> None: # Module name (Mandatory) self.module_name = 'mod_' + str(self.__class__.__name__).lower() # Add Irc Object to the module (Mandatory) - self.Irc = ircInstance + self.Irc = irc_instance # Add Irc Protocol Object to the module (Mandatory) - self.Protocol = ircInstance.Protocol + self.Protocol = irc_instance.Protocol # Add Global Configuration to the module (Mandatory) - self.Config = ircInstance.Config + self.Config = irc_instance.Config # Add Base object to the module (Mandatory) - self.Base = ircInstance.Base + self.Base = irc_instance.Base # Add logs object to the module (Mandatory) - self.Logs = ircInstance.Base.logs + self.Logs = irc_instance.Base.logs # Add User object to the module (Mandatory) - self.User = ircInstance.User + self.User = irc_instance.User # Add Channel object to the module (Mandatory) - self.Channel = ircInstance.Channel + self.Channel = irc_instance.Channel # Add global definitions - self.Definition = ircInstance.Loader.Definition + self.Definition = irc_instance.Loader.Definition # The Global Settings - self.Settings = ircInstance.Loader.Settings + self.Settings = irc_instance.Loader.Settings self.Schemas = schemas @@ -88,8 +87,6 @@ class Clone(): 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 @@ -136,15 +133,15 @@ class Clone(): return None - def cmd(self, data:list): + def cmd(self, data:list) -> None: try: if not data or len(data) < 2: - return + return None cmd = data.copy() if isinstance(data, list) else list(data).copy() index, command = self.Irc.Protocol.get_ircd_protocol_poisition(cmd) if index == -1: - return + return None match command: @@ -152,15 +149,16 @@ class Clone(): return self.Utils.handle_on_privmsg(self, cmd) case 'QUIT': - return + return None case _: - return + return None except Exception as err: self.Logs.error(f'General Error: {err}', exc_info=True) + return None - def hcmds(self, user: str, channel: any, cmd: list, fullcmd: list = []) -> None: + def hcmds(self, user: str, channel: Any, cmd: list, fullcmd: list = []) -> None: try: @@ -310,7 +308,7 @@ class Clone(): try: # clone say clone_nickname #channel message clone_name = str(cmd[2]) - clone_channel = str(cmd[3]) if self.Channel.Is_Channel(str(cmd[3])) else None + clone_channel = str(cmd[3]) if self.Channel.is_valid_channel(str(cmd[3])) else None final_message = ' '.join(cmd[4:]) diff --git a/mods/clone/threads.py b/mods/clone/threads.py index 60e40e4..c8c216a 100644 --- a/mods/clone/threads.py +++ b/mods/clone/threads.py @@ -12,7 +12,12 @@ def thread_connect_clones(uplink: 'Clone', ): for i in range(0, number_of_clones): - uplink.Utils.create_new_clone(uplink, uplink.Faker, group=group, auto_remote_ip=auto_remote_ip) + uplink.Utils.create_new_clone( + uplink=uplink, + faker_instance=uplink.Faker, + group=group, + auto_remote_ip=auto_remote_ip + ) for clone in uplink.Clone.UID_CLONE_DB: diff --git a/mods/command/mod_command.py b/mods/command/mod_command.py index f3b8874..3f5d9d3 100644 --- a/mods/command/mod_command.py +++ b/mods/command/mod_command.py @@ -1,4 +1,4 @@ -from typing import Union, TYPE_CHECKING +from typing import Optional, TYPE_CHECKING from dataclasses import dataclass if TYPE_CHECKING: @@ -271,7 +271,7 @@ class Command: user_uid = self.User.clean_uid(cmd[5]) userObj: MUser = self.User.get_User(user_uid) - channel_name = cmd[4] if self.Channel.Is_Channel(cmd[4]) else None + channel_name = cmd[4] if self.Channel.is_valid_channel(cmd[4]) else None client_obj = self.Client.get_Client(user_uid) nickname = userObj.nickname if userObj is not None else None @@ -296,7 +296,7 @@ class Command: except Exception as err: self.Logs.error(f"General Error: {err}") - def hcmds(self, uidornickname: str, channel_name: Union[str, None], cmd: list, fullcmd: list = []) -> None: + def hcmds(self, uidornickname: str, channel_name: Optional[str], cmd: list, fullcmd: list = []) -> None: command = str(cmd[0]).lower() dnickname = self.Config.SERVICE_NICKNAME @@ -324,7 +324,7 @@ class Command: # userObj: MUser = self.User.get_User(str(cmd[2])) nickname = str(cmd[2]) mode = str(cmd[3]) - chan: str = str(cmd[4]).lower() if self.Channel.Is_Channel(cmd[4]) else None + chan: str = str(cmd[4]).lower() if self.Channel.is_valid_channel(cmd[4]) else None sign = mode[0] if mode.startswith( ('+', '-')) else None clean_mode = mode[1:] if len(mode) > 0 else None @@ -422,7 +422,7 @@ class Command: case 'voiceall': try: - chan_info = self.Channel.get_Channel(fromchannel) + chan_info = self.Channel.get_channel(fromchannel) set_mode = 'v' mode:str = '' users:str = '' @@ -444,7 +444,7 @@ class Command: case 'opall': try: - chan_info = self.Channel.get_Channel(fromchannel) + chan_info = self.Channel.get_channel(fromchannel) set_mode = 'o' mode:str = '' users:str = '' @@ -739,7 +739,7 @@ class Command: case 'ban': # .ban #channel nickname try: - sentchannel = str(cmd[1]) if self.Channel.Is_Channel(cmd[1]) else None + sentchannel = str(cmd[1]) if self.Channel.is_valid_channel(cmd[1]) else None if sentchannel is None: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON] [NICKNAME]") return False @@ -757,7 +757,7 @@ class Command: case 'unban': # .unban #channel nickname try: - sentchannel = str(cmd[1]) if self.Channel.Is_Channel(cmd[1]) else None + sentchannel = str(cmd[1]) if self.Channel.is_valid_channel(cmd[1]) else None if sentchannel is None: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} ban [#SALON] [NICKNAME]") return False @@ -775,7 +775,7 @@ class Command: case 'kick': # .kick #channel nickname reason try: - sentchannel = str(cmd[1]) if self.Channel.Is_Channel(cmd[1]) else None + sentchannel = str(cmd[1]) if self.Channel.is_valid_channel(cmd[1]) else None if sentchannel is None: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} ban [#SALON] [NICKNAME]") return False @@ -794,7 +794,7 @@ class Command: case 'kickban': # .kickban #channel nickname reason try: - sentchannel = str(cmd[1]) if self.Channel.Is_Channel(cmd[1]) else None + sentchannel = str(cmd[1]) if self.Channel.is_valid_channel(cmd[1]) else None if sentchannel is None: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} ban [#SALON] [NICKNAME]") return False @@ -814,7 +814,7 @@ class Command: case 'join' | 'assign': try: - sent_channel = str(cmd[1]) if self.Channel.Is_Channel(cmd[1]) else None + sent_channel = str(cmd[1]) if self.Channel.is_valid_channel(cmd[1]) else None if sent_channel is None: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"{self.Config.SERVICE_PREFIX}JOIN #channel") return False @@ -832,7 +832,7 @@ class Command: case 'part' | 'unassign': try: - sent_channel = str(cmd[1]) if self.Channel.Is_Channel(cmd[1]) else None + sent_channel = str(cmd[1]) if self.Channel.is_valid_channel(cmd[1]) else None if sent_channel is None: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"{self.Config.SERVICE_PREFIX}PART #channel") return False @@ -859,7 +859,7 @@ class Command: return None chan = str(cmd[1]) - if not self.Channel.Is_Channel(chan): + if not self.Channel.is_valid_channel(chan): self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="The channel must start with #") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} TOPIC #channel THE_TOPIC_MESSAGE") return None @@ -959,7 +959,7 @@ class Command: chan = str(cmd[1]) - if not self.Channel.Is_Channel(chan): + if not self.Channel.is_valid_channel(chan): self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="The channel must start with #") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {str(cmd[0]).upper()} #channel") return None @@ -980,7 +980,7 @@ class Command: nickname = str(cmd[1]) chan = str(cmd[2]) - if not self.Channel.Is_Channel(chan): + if not self.Channel.is_valid_channel(chan): self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="The channel must start with #") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {str(cmd[0]).upper()} NICKNAME #CHANNEL") return None @@ -1051,7 +1051,7 @@ class Command: if len(cmd) == 2: channel_mode = cmd[1] - if self.Channel.Is_Channel(fromchannel): + if self.Channel.is_valid_channel(fromchannel): self.Protocol.send2socket(f":{dnickname} MODE {fromchannel} {channel_mode}") else: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : Channel [{fromchannel}] is not correct should start with #") diff --git a/mods/defender/mod_defender.py b/mods/defender/mod_defender.py index 250864f..dae03a1 100644 --- a/mods/defender/mod_defender.py +++ b/mods/defender/mod_defender.py @@ -7,39 +7,42 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: from core.irc import Irc -class Defender(): +class Defender: - def __init__(self, ircInstance: 'Irc') -> None: + def __init__(self, irc_instance: 'Irc') -> None: # Module name (Mandatory) self.module_name = 'mod_' + str(self.__class__.__name__).lower() # Add Irc Object to the module (Mandatory) - self.Irc = ircInstance + self.Irc = irc_instance # Add Loader Object to the module (Mandatory) - self.Loader = ircInstance.Loader + self.Loader = irc_instance.Loader # Add server protocol Object to the module (Mandatory) - self.Protocol = ircInstance.Protocol + self.Protocol = irc_instance.Protocol # Add Global Configuration to the module (Mandatory) - self.Config = ircInstance.Config + self.Config = irc_instance.Config # Add Base object to the module (Mandatory) - self.Base = ircInstance.Base + self.Base = irc_instance.Base # Add logs object to the module (Mandatory) - self.Logs = ircInstance.Base.logs + self.Logs = irc_instance.Base.logs # Add User object to the module (Mandatory) - self.User = ircInstance.User + self.User = irc_instance.User # Add Channel object to the module (Mandatory) - self.Channel = ircInstance.Channel + self.Channel = irc_instance.Channel + + # Add Settings object to save objects when reloading modules (Mandatory) + self.Settings = irc_instance.Settings # Add Reputation object to the module (Optional) - self.Reputation = ircInstance.Reputation + self.Reputation = irc_instance.Reputation # Add module schemas self.Schemas = schemas @@ -158,10 +161,39 @@ class Defender(): self.Base.db_update_core_config(self.module_name, self.ModConfig, param_key, param_value) + def __onload(self): + + abuseipdb = self.Settings.get_cache('ABUSEIPDB') + freeipapi = self.Settings.get_cache('FREEIPAPI') + cloudfilt = self.Settings.get_cache('CLOUDFILT') + psutils = self.Settings.get_cache('PSUTIL') + localscan = self.Settings.get_cache('LOCALSCAN') + + if abuseipdb: + self.Schemas.DB_ABUSEIPDB_USERS = abuseipdb + + if freeipapi: + self.Schemas.DB_FREEIPAPI_USERS = freeipapi + + if cloudfilt: + self.Schemas.DB_CLOUD_FILT_USERS = cloudfilt + + if psutils: + self.Schemas.DB_PSUTIL_USERS = psutils + + if localscan: + self.Schemas.DB_LOCALSCAN_USERS = localscan + def unload(self) -> None: """Cette methode sera executée a chaque désactivation ou rechargement de module """ + self.Settings.set_cache('ABUSEIPDB', self.Schemas.DB_ABUSEIPDB_USERS) + self.Settings.set_cache('FREEIPAPI', self.Schemas.DB_FREEIPAPI_USERS) + self.Settings.set_cache('CLOUDFILT', self.Schemas.DB_CLOUDFILT_USERS) + self.Settings.set_cache('PSUTIL', self.Schemas.DB_PSUTIL_USERS) + self.Settings.set_cache('LOCALSCAN', self.Schemas.DB_LOCALSCAN_USERS) + self.Schemas.DB_ABUSEIPDB_USERS = [] self.Schemas.DB_FREEIPAPI_USERS = [] self.Schemas.DB_CLOUDFILT_USERS = [] @@ -300,7 +332,7 @@ class Defender(): command = str(cmd[0]).lower() fromuser = user - channel = fromchannel = channel if self.Channel.Is_Channel(channel) else None + channel = fromchannel = channel if self.Channel.is_valid_channel(channel) else None dnickname = self.Config.SERVICE_NICKNAME # Defender nickname dchanlog = self.Config.SERVICE_CHANLOG # Defender chan log diff --git a/mods/defender/threads.py b/mods/defender/threads.py index eacaa49..9f00077 100644 --- a/mods/defender/threads.py +++ b/mods/defender/threads.py @@ -155,7 +155,7 @@ def timer_release_mode_mute(uplink: 'Defender', action: str, channel: str): """ service_id = uplink.Config.SERVICE_ID - if not uplink.Channel.Is_Channel(channel): + if not uplink.Channel.is_valid_channel(channel): uplink.Logs.debug(f"Channel is not valid {channel}") return diff --git a/mods/defender/utils.py b/mods/defender/utils.py index c5fc6e7..08cc470 100644 --- a/mods/defender/utils.py +++ b/mods/defender/utils.py @@ -52,7 +52,7 @@ def handle_on_mode(uplink: 'Defender', srvmsg: list[str]): if confmodel.autolimit == 1: if mode == '+l' or mode == '-l': - chan = irc.Channel.get_Channel(channel) + chan = irc.Channel.get_channel(channel) p.send2socket(f":{gconfig.SERVICE_ID} MODE {chan.name} +l {len(chan.uids) + confmodel.autolimit_amount}") if gconfig.SALON_JAIL == channel: @@ -80,7 +80,7 @@ def handle_on_sjoin(uplink: 'Defender', srvmsg: list[str]): gconfig = uplink.Config confmodel = uplink.ModConfig - parsed_chan = srvmsg[4] if irc.Channel.Is_Channel(srvmsg[4]) else None + parsed_chan = srvmsg[4] if irc.Channel.is_valid_channel(srvmsg[4]) else None parsed_UID = irc.User.clean_uid(srvmsg[5]) if parsed_chan is None or parsed_UID is None: @@ -265,7 +265,7 @@ def action_on_flood(uplink: 'Defender', srvmsg: list[str]): channel = srvmsg[3] User = irc.User.get_User(user_trigger) - if User is None or not irc.Channel.Is_Channel(channel_to_check=channel): + if User is None or not irc.Channel.is_valid_channel(channel_to_check=channel): return flood_time = confmodel.flood_time diff --git a/mods/jsonrpc/mod_jsonrpc.py b/mods/jsonrpc/mod_jsonrpc.py index 08d13c3..5ed288a 100644 --- a/mods/jsonrpc/mod_jsonrpc.py +++ b/mods/jsonrpc/mod_jsonrpc.py @@ -190,7 +190,7 @@ class Jsonrpc(): self.Base.db_update_core_config(self.module_name, self.ModConfig, param_key, param_value) def unload(self) -> None: - if self.UnrealIrcdRpcLive.Error.code != -1: + if self.UnrealIrcdRpcLive.get_error.code != -1: self.UnrealIrcdRpcLive.unsubscribe() return None @@ -271,16 +271,13 @@ class Jsonrpc(): self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'{rpc.get_error.message}') return None - chan_list = [] - for chan in UserInfo.user.channels: - chan_list.append(chan.name) self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'UID : {UserInfo.id}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'NICKNAME : {UserInfo.name}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'USERNAME : {UserInfo.user.username}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'REALNAME : {UserInfo.user.realname}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'MODES : {UserInfo.user.modes}') - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'CHANNELS : {chan_list}') + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'CHANNELS : {[chan.name for chan in UserInfo.user.channels]}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'SECURITY GROUP : {UserInfo.user.security_groups}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'REPUTATION : {UserInfo.user.reputation}') diff --git a/mods/votekick/mod_votekick.py b/mods/votekick/mod_votekick.py index 54b7d03..4b67727 100644 --- a/mods/votekick/mod_votekick.py +++ b/mods/votekick/mod_votekick.py @@ -291,7 +291,7 @@ class Votekick(): self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' :Your are not allowed to execute this command') return None - sentchannel = str(cmd[2]).lower() if self.Channel.Is_Channel(str(cmd[2]).lower()) else None + sentchannel = str(cmd[2]).lower() if self.Channel.is_valid_channel(str(cmd[2]).lower()) else None if sentchannel is None: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f" The correct command is {self.Config.SERVICE_PREFIX}{command} {option} #CHANNEL") @@ -325,7 +325,7 @@ class Votekick(): self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f" Your are not allowed to execute this command") return None - sentchannel = str(cmd[2]).lower() if self.Channel.Is_Channel(str(cmd[2]).lower()) else None + sentchannel = str(cmd[2]).lower() if self.Channel.is_valid_channel(str(cmd[2]).lower()) else None if sentchannel is None: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f" The correct command is {self.Config.SERVICE_PREFIX}{command} {option} #CHANNEL") @@ -459,7 +459,7 @@ class Votekick(): return False uid_cleaned = self.Base.clean_uid(uid_submitted) - ChannelInfo = self.Channel.get_Channel(channel) + ChannelInfo = self.Channel.get_channel(channel) if ChannelInfo is None: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' This channel [{channel}] do not exist in the Channel Object') return False From 9e255e806d5a105d0ee32cb9f1aebc2bc27793b8 Mon Sep 17 00:00:00 2001 From: adator85 <> Date: Fri, 15 Aug 2025 16:14:35 +0200 Subject: [PATCH 09/20] Adding some comments, editing methods names (admin.py) --- core/classes/admin.py | 102 +++++++++++++++++++++++----------- core/irc.py | 12 ++-- mods/votekick/mod_votekick.py | 10 ++-- 3 files changed, 82 insertions(+), 42 deletions(-) diff --git a/core/classes/admin.py b/core/classes/admin.py index 67a1672..198e9c6 100644 --- a/core/classes/admin.py +++ b/core/classes/admin.py @@ -10,62 +10,78 @@ class Admin: self.Logs = base.logs def insert(self, new_admin: df.MAdmin) -> bool: + """Insert a new admin object model - result = False - exist = False + Args: + new_admin (MAdmin): The new admin object model to insert + + Returns: + bool: True if it was inserted + """ for record in self.UID_ADMIN_DB: if record.uid == new_admin.uid: # If the admin exist then return False and do not go further - exist = True self.Logs.debug(f'{record.uid} already exist') - return result + return False - if not exist: - self.UID_ADMIN_DB.append(new_admin) - result = True - self.Logs.debug(f'UID ({new_admin.uid}) has been created') - - if not result: - self.Logs.critical(f'The User Object was not inserted {new_admin}') - - return result + self.UID_ADMIN_DB.append(new_admin) + self.Logs.debug(f'A new admin ({new_admin.nickname}) has been created') + return True def update_nickname(self, uid: str, new_admin_nickname: str) -> bool: + """Update nickname of an admin - result = False + Args: + uid (str): The Admin UID + new_admin_nickname (str): The new nickname of the admin + + Returns: + bool: True if the nickname has been updated. + """ for record in self.UID_ADMIN_DB: if record.uid == uid: # If the admin exist, update and do not go further record.nickname = new_admin_nickname - result = True self.Logs.debug(f'UID ({record.uid}) has been updated with new nickname {new_admin_nickname}') - return result + return True - if not result: - self.Logs.debug(f'The new nickname {new_admin_nickname} was not updated, uid = {uid} - The Client is not an admin') - return result + self.Logs.debug(f'The new nickname {new_admin_nickname} was not updated, uid = {uid} - The Client is not an admin') + return False def update_level(self, nickname: str, new_admin_level: int) -> bool: + """Update the admin level - result = False + Args: + nickname (str): The admin nickname + new_admin_level (int): The new level of the admin + + Returns: + bool: True if the admin level has been updated + """ for record in self.UID_ADMIN_DB: if record.nickname == nickname: # If the admin exist, update and do not go further record.level = new_admin_level - result = True self.Logs.debug(f'Admin ({record.nickname}) has been updated with new level {new_admin_level}') - return result + return True - if not result: - self.Logs.debug(f'The new level {new_admin_level} was not updated, nickname = {nickname} - The Client is not an admin') + self.Logs.debug(f'The new level {new_admin_level} was not updated, nickname = {nickname} - The Client is not an admin') - return result + return False def delete(self, uidornickname: str) -> bool: + """Delete admin + + Args: + uidornickname (str): The UID or nickname of the admin + + Returns: + bool: True if the admin has been deleted + """ for record in self.UID_ADMIN_DB: if record.uid == uidornickname: @@ -73,40 +89,64 @@ class Admin: self.UID_ADMIN_DB.remove(record) self.Logs.debug(f'UID ({record.uid}) has been deleted') return True - if record.nickname == uidornickname: + if record.nickname.lower() == uidornickname.lower(): # If the admin exist, delete and do not go further self.UID_ADMIN_DB.remove(record) self.Logs.debug(f'nickname ({record.nickname}) has been deleted') return True - self.Logs.critical(f'The UID {uidornickname} was not deleted') + self.Logs.debug(f'The UID {uidornickname} was not deleted') return False - def get_Admin(self, uidornickname: str) -> Optional[df.MAdmin]: + def get_admin(self, uidornickname: str) -> Optional[df.MAdmin]: + """Get the admin object model + + Args: + uidornickname (str): UID or Nickname of the admin + + Returns: + Optional[MAdmin]: The MAdmin object model if exist + """ for record in self.UID_ADMIN_DB: if record.uid == uidornickname: return record - elif record.nickname == uidornickname: + elif record.nickname.lower() == uidornickname.lower(): return record return None def get_uid(self, uidornickname:str) -> Optional[str]: + """Get the UID of the admin + + Args: + uidornickname (str): The UID or nickname of the admin + + Returns: + Optional[str]: The UID of the admin + """ for record in self.UID_ADMIN_DB: if record.uid == uidornickname: return record.uid - if record.nickname == uidornickname: + if record.nickname.lower() == uidornickname.lower(): return record.uid return None def get_nickname(self, uidornickname:str) -> Optional[str]: + """Get the nickname of the admin + + Args: + uidornickname (str): The UID or the nickname of the admin + + Returns: + Optional[str]: The nickname of the admin + """ for record in self.UID_ADMIN_DB: - if record.nickname == uidornickname: + if record.nickname.lower() == uidornickname.lower(): return record.nickname if record.uid == uidornickname: return record.nickname diff --git a/core/irc.py b/core/irc.py index f8b88fa..19c567a 100644 --- a/core/irc.py +++ b/core/irc.py @@ -355,7 +355,7 @@ class Irc: # Check if the nickname is an admin p = self.Protocol - admin_obj = self.Admin.get_Admin(nickname) + admin_obj = self.Admin.get_admin(nickname) dnickname = self.Config.SERVICE_NICKNAME color_bold = self.Config.COLORS.bold color_nogc = self.Config.COLORS.nogc @@ -413,7 +413,7 @@ class Irc: def generate_help_menu_bakcup(self, nickname: str) -> None: # Check if the nickname is an admin - admin_obj = self.Admin.get_Admin(nickname) + admin_obj = self.Admin.get_admin(nickname) dnickname = self.Config.SERVICE_NICKNAME color_bold = self.Config.COLORS.bold color_nogc = self.Config.COLORS.nogc @@ -447,7 +447,7 @@ class Irc: def is_cmd_allowed(self, nickname: str, command_name: str) -> bool: - admin_obj = self.Admin.get_Admin(nickname) + admin_obj = self.Admin.get_admin(nickname) current_level = 0 if admin_obj is not None: @@ -769,7 +769,7 @@ class Irc: def delete_db_admin(self, uid:str) -> None: - if self.Admin.get_Admin(uid) is None: + if self.Admin.get_admin(uid) is None: return None if not self.Admin.delete(uid): @@ -1209,7 +1209,7 @@ class Irc: user_to_edit = cmd[1] user_password = self.Loader.Utils.hash_password(cmd[2]) - get_admin = self.Admin.get_Admin(fromuser) + get_admin = self.Admin.get_admin(fromuser) if get_admin is None: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"This user {fromuser} has no Admin access") return None @@ -1273,7 +1273,7 @@ class Irc: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"{self.Config.SERVICE_PREFIX}delaccess [USER] [CONFIRMUSER]") return None - get_admin = self.Admin.get_Admin(fromuser) + get_admin = self.Admin.get_admin(fromuser) if get_admin is None: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"This user {fromuser} has no admin access") diff --git a/mods/votekick/mod_votekick.py b/mods/votekick/mod_votekick.py index 4b67727..f1b2145 100644 --- a/mods/votekick/mod_votekick.py +++ b/mods/votekick/mod_votekick.py @@ -287,7 +287,7 @@ class Votekick(): case 'activate': try: # vote activate #channel - if self.Admin.get_Admin(fromuser) is None: + if self.Admin.get_admin(fromuser) is None: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' :Your are not allowed to execute this command') return None @@ -321,7 +321,7 @@ class Votekick(): case 'deactivate': try: # vote deactivate #channel - if self.Admin.get_Admin(fromuser) is None: + if self.Admin.get_admin(fromuser) is None: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f" Your are not allowed to execute this command") return None @@ -392,7 +392,7 @@ class Votekick(): case 'cancel': try: # vote cancel - if self.Admin.get_Admin(fromuser) is None: + if self.Admin.get_admin(fromuser) is None: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' Your are not allowed to execute this command') return None @@ -430,7 +430,7 @@ class Votekick(): case 'submit': try: # vote submit nickname - if self.Admin.get_Admin(fromuser) is None: + if self.Admin.get_admin(fromuser) is None: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' Your are not allowed to execute this command') return None @@ -508,7 +508,7 @@ class Votekick(): case 'verdict': try: # vote verdict - if self.Admin.get_Admin(fromuser) is None: + if self.Admin.get_admin(fromuser) is None: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f'Your are not allowed to execute this command') return None From aee1dca124a83502cb83e8430896f4abb2880ffe Mon Sep 17 00:00:00 2001 From: adator85 <> Date: Fri, 15 Aug 2025 16:17:32 +0200 Subject: [PATCH 10/20] Importing the MAdmin directly from definition (admin.py) --- core/classes/admin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/classes/admin.py b/core/classes/admin.py index 198e9c6..95dc76b 100644 --- a/core/classes/admin.py +++ b/core/classes/admin.py @@ -1,15 +1,15 @@ from typing import Optional from core.base import Base -import core.definition as df +from core.definition import MAdmin class Admin: - UID_ADMIN_DB: list[df.MAdmin] = [] + UID_ADMIN_DB: list[MAdmin] = [] def __init__(self, base: Base) -> None: self.Logs = base.logs - def insert(self, new_admin: df.MAdmin) -> bool: + def insert(self, new_admin: MAdmin) -> bool: """Insert a new admin object model Args: @@ -99,7 +99,7 @@ class Admin: return False - def get_admin(self, uidornickname: str) -> Optional[df.MAdmin]: + def get_admin(self, uidornickname: str) -> Optional[MAdmin]: """Get the admin object model Args: From 30a89e7d141515c8ce77ae3e7ef284bc424eec78 Mon Sep 17 00:00:00 2001 From: adator85 <> Date: Fri, 15 Aug 2025 16:19:56 +0200 Subject: [PATCH 11/20] Updating gitignore file --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 9867c4e..2428eb1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .pyenv/ +.venv/ +.idea/ db/ logs/ __pycache__/ From e075b7b8d5e7f449d96d1789a82b45f2b39ce5cd Mon Sep 17 00:00:00 2001 From: adator85 <> Date: Fri, 15 Aug 2025 16:22:33 +0200 Subject: [PATCH 12/20] Updating gitignore file --- .idea/.gitignore | 8 -------- .idea/DEFENDER.iml | 14 -------------- .idea/inspectionProfiles/profiles_settings.xml | 7 ------- .idea/misc.xml | 7 ------- .idea/modules.xml | 8 -------- .idea/vcs.xml | 6 ------ 6 files changed, 50 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/DEFENDER.iml delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/DEFENDER.iml b/.idea/DEFENDER.iml deleted file mode 100644 index c299fe3..0000000 --- a/.idea/DEFENDER.iml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index dd4c951..0000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 4ab2f6a..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 12174db..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From 3fc49e9069e97423a5f8c18bbcfc3729b40cdf4e Mon Sep 17 00:00:00 2001 From: adator85 <> Date: Sat, 16 Aug 2025 02:11:21 +0200 Subject: [PATCH 13/20] Update Votekick module! following the same structure as other modules --- core/irc.py | 4 +- core/utils.py | 3 + mods/votekick/mod_votekick.py | 9 ++- mods/votekick/schemas.py | 11 ++++ mods/votekick/votekick_manager.py | 98 +++++++++++++++++++++++++++++++ 5 files changed, 121 insertions(+), 4 deletions(-) create mode 100644 mods/votekick/schemas.py create mode 100644 mods/votekick/votekick_manager.py diff --git a/core/irc.py b/core/irc.py index 19c567a..17bad7b 100644 --- a/core/irc.py +++ b/core/irc.py @@ -127,7 +127,7 @@ class Irc: # Define the IrcSocket object - self.IrcSocket:Union[socket.socket, SSLSocket] = None + self.IrcSocket: Union[socket.socket, SSLSocket] = None self.__create_table() self.Base.create_thread(func=self.heartbeat, func_args=(self.beat, )) @@ -145,6 +145,7 @@ class Irc: self.init_service_user() self.__create_socket() self.__connect_to_irc(ircInstance) + except AssertionError as ae: self.Logs.critical(f'Assertion error: {ae}') @@ -197,6 +198,7 @@ class Irc: self.Logs.critical(f"AttributeError: {ae} - {soc.fileno()}") def __ssl_context(self) -> ssl.SSLContext: + ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE diff --git a/core/utils.py b/core/utils.py index 93188b1..d5aec4b 100644 --- a/core/utils.py +++ b/core/utils.py @@ -1,3 +1,6 @@ +''' +Main utils library. +''' from pathlib import Path from typing import Literal, Optional, Any from datetime import datetime diff --git a/mods/votekick/mod_votekick.py b/mods/votekick/mod_votekick.py index f1b2145..eb3c355 100644 --- a/mods/votekick/mod_votekick.py +++ b/mods/votekick/mod_votekick.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any import re from dataclasses import dataclass, field @@ -55,6 +55,9 @@ class Votekick(): # Add Channel object to the module self.Channel = ircInstance.Channel + # Add Utils. + self.Utils = ircInstance.Utils + # Créer les nouvelles commandes du module self.Irc.build_command(1, self.module_name, 'vote', 'The kick vote module') @@ -150,7 +153,7 @@ class Votekick(): Args: channel (str): le salon à enregistrer. """ - current_datetime = self.Base.get_datetime() + current_datetime = self.Utils.get_sdatetime() mes_donnees = {'channel': channel} response = self.Base.db_execute_query("SELECT id FROM votekick_channel WHERE channel = :channel", mes_donnees) @@ -256,7 +259,7 @@ class Votekick(): except Exception as err: self.Logs.error(f"General Error: {err}") - def hcmds(self, user:str, channel: any, 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 diff --git a/mods/votekick/schemas.py b/mods/votekick/schemas.py new file mode 100644 index 0000000..7b7c0c6 --- /dev/null +++ b/mods/votekick/schemas.py @@ -0,0 +1,11 @@ +from typing import Optional +from core.definition import MainModel, dataclass, field + + +@dataclass +class VoteChannelModel(MainModel): + channel_name: Optional[str] = None + target_user: Optional[str] = None + voter_users: list = field(default_factory=list) + vote_for: int = 0 + vote_against: int = 0 diff --git a/mods/votekick/votekick_manager.py b/mods/votekick/votekick_manager.py new file mode 100644 index 0000000..8694ffe --- /dev/null +++ b/mods/votekick/votekick_manager.py @@ -0,0 +1,98 @@ +from typing import TYPE_CHECKING, Optional +from mods.votekick.schemas import VoteChannelModel + +if TYPE_CHECKING: + from mods.votekick.mod_votekick import Votekick + +class VotekickManager: + + VOTE_CHANNEL_DB:list[VoteChannelModel] = [] + + def __init__(self, uplink: 'Votekick'): + self.uplink = uplink + self.Logs = uplink.Logs + self.Utils = uplink.Utils + + def activate_new_channel(self, channel_name: str) -> bool: + """Activate a new channel in the votekick systeme + + Args: + channel_name (str): The channel name you want to activate + + Returns: + bool: True if it was activated + """ + votec = self.get_vote_channel_model(channel_name) + + if votec is None: + self.VOTE_CHANNEL_DB.append( + VoteChannelModel( + channel_name=channel_name, + target_user='', + voter_users=[], + vote_for=0, + vote_against=0 + ) + ) + self.Logs.debug(f"[VOTEKICK MANAGER] {channel_name} has been activated.") + return True + + return False + + def get_vote_channel_model(self, channel_name: str) -> Optional[VoteChannelModel]: + """Get Vote Channel Object model + + Args: + channel_name (str): The channel name you want to activate + + Returns: + (VoteChannelModel | None): The VoteChannelModel if exist + """ + for vote in self.VOTE_CHANNEL_DB: + if vote.channel_name.lower() == channel_name.lower(): + self.Logs.debug(f"[VOTEKICK MANAGER] {channel_name} has been found in the VOTE_CHANNEL_DB") + return vote + + return None + + def drop_vote_channel_model(self, channel_name: str) -> bool: + """Drop a channel from the votekick system. + + Args: + channel_name (str): The channel name you want to drop + + Returns: + bool: True if the channel has been droped. + """ + votec = self.get_vote_channel_model(channel_name) + + if votec: + self.VOTE_CHANNEL_DB.remove(votec) + self.Logs.debug(f"[VOTEKICK MANAGER] {channel_name} has been removed from the VOTE_CHANNEL_DB") + return True + + return False + + def is_vote_ongoing(self, channel_name: str) -> bool: + """Check if there is an angoing vote on the channel provided + + Args: + channel_name (str): The channel name to check + + Returns: + bool: True if there is an ongoing vote on the channel provided. + """ + + votec = self.get_vote_channel_model(channel_name) + + if votec is None: + self.Logs.debug(f"[VOTEKICK MANAGER] {channel_name} is not activated!") + return False + + if votec.target_user: + self.Logs.debug(f'[VOTEKICK MANAGER] A vote is ongoing on {channel_name}') + return True + + self.Logs.debug(f'[VOTEKICK MANAGER] {channel_name} is activated but there is no ongoing vote!') + + return False From f5212deacfaf0ba3615f3af3bea9f42b654683eb Mon Sep 17 00:00:00 2001 From: adator85 <> Date: Sat, 16 Aug 2025 02:27:03 +0200 Subject: [PATCH 14/20] Update Votekick first part comment --- core/classes/settings.py | 4 ++-- mods/votekick/mod_votekick.py | 29 ++++++++++++++++++++--------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/core/classes/settings.py b/core/classes/settings.py index c37a37b..57d5d13 100644 --- a/core/classes/settings.py +++ b/core/classes/settings.py @@ -2,7 +2,7 @@ ''' from threading import Timer, Thread, RLock from socket import socket -from typing import Any +from typing import Any, Optional class Settings: """This Class will never be reloaded. @@ -37,7 +37,7 @@ class Settings: """ self.__CACHE[key] = value_to_cache - def get_cache(self, key) -> Any: + def get_cache(self, key) -> Optional[Any]: """It returns the value associated to the key and finally it removes the entry""" if self.__CACHE.get(key): return self.__CACHE.pop(key) diff --git a/mods/votekick/mod_votekick.py b/mods/votekick/mod_votekick.py index eb3c355..b3ac388 100644 --- a/mods/votekick/mod_votekick.py +++ b/mods/votekick/mod_votekick.py @@ -1,20 +1,23 @@ +''' + File : mod_votekick.py + Version : 1.0.0 + Description : Manages votekick sessions for multiple channels. + Handles activation, ongoing vote checks, and cleanup. + Author : adator + Created : 2025-08-16 + Last Updated: 2025-08-16 +----------------------------------------- +''' from typing import TYPE_CHECKING, Any import re from dataclasses import dataclass, field +import mods.votekick.votekick_manager if TYPE_CHECKING: from core.irc import Irc -# Activer le systeme sur un salon (activate #salon) -# Le service devra se connecter au salon -# Le service devra se mettre en op -# Soumettre un nom de user (submit nickname) -# voter pour un ban (vote_for) -# voter contre un ban (vote_against) - - -class Votekick(): +class Votekick: @dataclass class VoteChannelModel: @@ -58,6 +61,11 @@ class Votekick(): # Add Utils. self.Utils = ircInstance.Utils + metadata = ircInstance.Loader.Settings.get_cache('VOTEKICK') + + if metadata is not None: + self.VOTE_CHANNEL_DB = metadata + # Créer les nouvelles commandes du module self.Irc.build_command(1, self.module_name, 'vote', 'The kick vote module') @@ -106,6 +114,9 @@ class Votekick(): def unload(self) -> None: try: + # Cache the local DB with current votes. + self.Loader.Settings.set_cache('VOTEKICK', self.VOTE_CHANNEL_DB) + for chan in self.VOTE_CHANNEL_DB: self.Protocol.send_part_chan(uidornickname=self.Config.SERVICE_ID, channel=chan.channel_name) From 8d23827e1ea1ca7955127fac9b75e882b3d10455 Mon Sep 17 00:00:00 2001 From: adator <85586985+adator85@users.noreply.github.com> Date: Sat, 16 Aug 2025 21:27:00 +0200 Subject: [PATCH 15/20] Updating new classes into votekick module --- mods/votekick/mod_votekick.py | 141 +++++++++++++++++----------------- mods/votekick/schemas.py | 4 +- 2 files changed, 74 insertions(+), 71 deletions(-) diff --git a/mods/votekick/mod_votekick.py b/mods/votekick/mod_votekick.py index b3ac388..5a9aa36 100644 --- a/mods/votekick/mod_votekick.py +++ b/mods/votekick/mod_votekick.py @@ -1,4 +1,4 @@ -''' +""" File : mod_votekick.py Version : 1.0.0 Description : Manages votekick sessions for multiple channels. @@ -7,11 +7,11 @@ Created : 2025-08-16 Last Updated: 2025-08-16 ----------------------------------------- -''' -from typing import TYPE_CHECKING, Any +""" import re -from dataclasses import dataclass, field -import mods.votekick.votekick_manager +import mods.votekick.schemas as schemas +from mods.votekick.votekick_manager import VotekickManager +from typing import TYPE_CHECKING, Any, Optional if TYPE_CHECKING: from core.irc import Irc @@ -19,52 +19,49 @@ if TYPE_CHECKING: class Votekick: - @dataclass - class VoteChannelModel: - channel_name: str - target_user: str - voter_users: list - vote_for: int - vote_against: int - - VOTE_CHANNEL_DB:list[VoteChannelModel] = [] - - def __init__(self, ircInstance: 'Irc') -> None: + def __init__(self, uplink: 'Irc') -> None: # Module name (Mandatory) self.module_name = 'mod_' + str(self.__class__.__name__).lower() # Add Irc Object to the module - self.Irc = ircInstance + self.Irc = uplink # Add Loader Object to the module (Mandatory) - self.Loader = ircInstance.Loader + self.Loader = uplink.Loader # Add server protocol Object to the module (Mandatory) - self.Protocol = ircInstance.Protocol + self.Protocol = uplink.Protocol # Add Global Configuration to the module - self.Config = ircInstance.Config + self.Config = uplink.Config # Add Base object to the module - self.Base = ircInstance.Base + self.Base = uplink.Base # Add logs object to the module - self.Logs = ircInstance.Base.logs + self.Logs = uplink.Base.logs # Add User object to the module - self.User = ircInstance.User + self.User = uplink.User # Add Channel object to the module - self.Channel = ircInstance.Channel + self.Channel = uplink.Channel # Add Utils. - self.Utils = ircInstance.Utils + self.Utils = uplink.Utils - metadata = ircInstance.Loader.Settings.get_cache('VOTEKICK') + # Add Schemas module + self.Schemas = schemas + + # Add VoteKick Manager + self.VoteKickManager = VotekickManager(self) + + metadata = uplink.Loader.Settings.get_cache('VOTEKICK') if metadata is not None: - self.VOTE_CHANNEL_DB = metadata + self.VoteKickManager.VOTE_CHANNEL_DB = metadata + # self.VOTE_CHANNEL_DB = metadata # Créer les nouvelles commandes du module self.Irc.build_command(1, self.module_name, 'vote', 'The kick vote module') @@ -87,8 +84,6 @@ class Votekick: 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 @@ -115,13 +110,13 @@ class Votekick: def unload(self) -> None: try: # Cache the local DB with current votes. - self.Loader.Settings.set_cache('VOTEKICK', self.VOTE_CHANNEL_DB) + self.Loader.Settings.set_cache('VOTEKICK', self.VoteKickManager.VOTE_CHANNEL_DB) - for chan in self.VOTE_CHANNEL_DB: + for chan in self.VoteKickManager.VOTE_CHANNEL_DB: self.Protocol.send_part_chan(uidornickname=self.Config.SERVICE_ID, channel=chan.channel_name) - self.VOTE_CHANNEL_DB = [] - self.Logs.debug(f'Delete memory DB VOTE_CHANNEL_DB: {self.VOTE_CHANNEL_DB}') + self.VoteKickManager.VOTE_CHANNEL_DB = [] + self.Logs.debug(f'Delete memory DB VOTE_CHANNEL_DB: {self.VoteKickManager.VOTE_CHANNEL_DB}') return None except UnboundLocalError as ne: @@ -134,7 +129,7 @@ class Votekick: def init_vote_system(self, channel: str) -> bool: response = False - for chan in self.VOTE_CHANNEL_DB: + for chan in self.VoteKickManager.VOTE_CHANNEL_DB: if chan.channel_name == channel: chan.target_user = '' chan.voter_users = [] @@ -144,21 +139,21 @@ class Votekick: return response - def insert_vote_channel(self, ChannelObject: VoteChannelModel) -> bool: + def insert_vote_channel(self, channel_obj: schemas.VoteChannelModel) -> bool: result = False found = False - for chan in self.VOTE_CHANNEL_DB: - if chan.channel_name == ChannelObject.channel_name: + for chan in self.VoteKickManager.VOTE_CHANNEL_DB: + if chan.channel_name == channel_obj.channel_name: found = True if not found: - self.VOTE_CHANNEL_DB.append(ChannelObject) - self.Logs.debug(f"The channel has been added {ChannelObject}") + self.VoteKickManager.VOTE_CHANNEL_DB.append(channel_obj) + self.Logs.debug(f"The channel has been added {channel_obj}") # self.db_add_vote_channel(ChannelObject.channel_name) return result - def db_add_vote_channel(self, channel:str) -> bool: + def db_add_vote_channel(self, channel: str) -> bool: """Cette fonction ajoute les salons ou seront autoriser les votes Args: @@ -169,9 +164,9 @@ class Votekick: response = self.Base.db_execute_query("SELECT id FROM votekick_channel WHERE channel = :channel", mes_donnees) - isChannelExist = response.fetchone() + is_channel_exist = response.fetchone() - if isChannelExist is None: + if is_channel_exist is None: mes_donnees = {'datetime': current_datetime, 'channel': channel} insert = self.Base.db_execute_query(f"INSERT INTO votekick_channel (datetime, channel) VALUES (:datetime, :channel)", mes_donnees) if insert.rowcount > 0: @@ -203,11 +198,10 @@ class Votekick: result = self.Base.db_execute_query(f"SELECT id, channel_name FROM {self.Config.TABLE_CHANNEL} WHERE module_name = :module_name", param) channels = result.fetchall() - unixtime = self.Base.get_unixtime() for channel in channels: - id, chan = channel - self.insert_vote_channel(self.VoteChannelModel(channel_name=chan, target_user='', voter_users=[], vote_for=0, vote_against=0)) + id_, chan = channel + self.insert_vote_channel(self.Schemas.VoteChannelModel(channel_name=chan, target_user='', voter_users=[], vote_for=0, vote_against=0)) self.Protocol.sjoin(channel=chan) self.Protocol.send2socket(f":{self.Config.SERVICE_NICKNAME} SAMODE {chan} +o {self.Config.SERVICE_NICKNAME}") @@ -216,7 +210,7 @@ class Votekick: def is_vote_ongoing(self, channel: str) -> bool: response = False - for vote in self.VOTE_CHANNEL_DB: + for vote in self.VoteKickManager.VOTE_CHANNEL_DB: if vote.channel_name == channel: if vote.target_user: response = True @@ -230,7 +224,7 @@ class Votekick: if not self.is_vote_ongoing(channel): return None - for chan in self.VOTE_CHANNEL_DB: + for chan in self.VoteKickManager.VOTE_CHANNEL_DB: if chan.channel_name == channel: target_user = self.User.get_nickname(chan.target_user) if chan.vote_for > chan.vote_against: @@ -258,9 +252,12 @@ class Votekick: return None - def cmd(self, data:list) -> None: + def cmd(self, data: list) -> None: + + cmd = list(data).copy() + try: - cmd = list(data).copy() + return None except KeyError as ke: @@ -270,11 +267,12 @@ class Votekick: except Exception as err: self.Logs.error(f"General Error: {err}") - def hcmds(self, user:str, channel: Any, cmd: list, fullcmd: list = []) -> None: + def hcmds(self, user:str, channel: Any, cmd: list, fullcmd: Optional[list] = None) -> None: # cmd is the command starting from the user command # full cmd is sending the entire server response command = str(cmd[0]).lower() + fullcmd = fullcmd dnickname = self.Config.SERVICE_NICKNAME fromuser = user fromchannel = channel @@ -310,7 +308,7 @@ class Votekick: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f" The correct command is {self.Config.SERVICE_PREFIX}{command} {option} #CHANNEL") self.insert_vote_channel( - self.VoteChannelModel( + self.Schemas.VoteChannelModel( channel_name=sentchannel, target_user='', voter_users=[], @@ -346,9 +344,9 @@ class Votekick: self.Protocol.send2socket(f":{dnickname} SAMODE {sentchannel} -o {dnickname}") self.Protocol.send_part_chan(uidornickname=dnickname, channel=sentchannel) - for chan in self.VOTE_CHANNEL_DB: + for chan in self.VoteKickManager.VOTE_CHANNEL_DB: if chan.channel_name == sentchannel: - self.VOTE_CHANNEL_DB.remove(chan) + self.VoteKickManager.VOTE_CHANNEL_DB.remove(chan) self.Channel.db_query_channel('del', self.module_name, chan.channel_name) self.Logs.debug(f"The Channel {sentchannel} has been deactivated from the vote system") @@ -361,7 +359,7 @@ class Votekick: try: # vote + channel = fromchannel - for chan in self.VOTE_CHANNEL_DB: + for chan in self.VoteKickManager.VOTE_CHANNEL_DB: if chan.channel_name == channel: if fromuser in chan.voter_users: self.Protocol.send_priv_msg(nick_from=dnickname, @@ -384,7 +382,7 @@ class Votekick: try: # vote - channel = fromchannel - for chan in self.VOTE_CHANNEL_DB: + for chan in self.VoteKickManager.VOTE_CHANNEL_DB: if chan.channel_name == channel: if fromuser in chan.voter_users: self.Protocol.send_priv_msg(nick_from=dnickname, @@ -414,7 +412,7 @@ class Votekick: self.Logs.error(f"The channel is not known, defender can't cancel the vote") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' You need to specify the channel => /msg {dnickname} vote_cancel #channel') - for vote in self.VOTE_CHANNEL_DB: + for vote in self.VoteKickManager.VOTE_CHANNEL_DB: if vote.channel_name == channel: self.init_vote_system(channel) self.Protocol.send_priv_msg(nick_from=dnickname, @@ -430,7 +428,7 @@ class Votekick: case 'status': try: # vote status - for chan in self.VOTE_CHANNEL_DB: + for chan in self.VoteKickManager.VOTE_CHANNEL_DB: if chan.channel_name == channel: self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"Channel: {chan.channel_name} | Target: {self.User.get_nickname(chan.target_user)} | For: {chan.vote_for} | Against: {chan.vote_against} | Number of voters: {str(len(chan.voter_users))}", @@ -451,10 +449,11 @@ class Votekick: nickname_submitted = cmd[2] uid_submitted = self.User.get_uid(nickname_submitted) user_submitted = self.User.get_User(nickname_submitted) + ongoing_user = None # check if there is an ongoing vote if self.is_vote_ongoing(channel): - for vote in self.VOTE_CHANNEL_DB: + for vote in self.VoteKickManager.VOTE_CHANNEL_DB: if vote.channel_name == channel: ongoing_user = self.User.get_nickname(vote.target_user) @@ -462,7 +461,7 @@ class Votekick: msg=f"There is an ongoing vote on {ongoing_user}", channel=channel ) - return False + return None # check if the user exist if user_submitted is None: @@ -470,16 +469,16 @@ class Votekick: msg=f"This nickname <{nickname_submitted}> do not exist", channel=channel ) - return False + return None uid_cleaned = self.Base.clean_uid(uid_submitted) - ChannelInfo = self.Channel.get_channel(channel) - if ChannelInfo is None: + channel_obj = self.Channel.get_channel(channel) + if channel_obj is None: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' This channel [{channel}] do not exist in the Channel Object') - return False + return None clean_uids_in_channel: list = [] - for uid in ChannelInfo.uids: + for uid in channel_obj.uids: clean_uids_in_channel.append(self.Base.clean_uid(uid)) if not uid_cleaned in clean_uids_in_channel: @@ -487,7 +486,7 @@ class Votekick: msg=f"This nickname <{nickname_submitted}> is not available in this channel", channel=channel ) - return False + return None # check if Ircop or Service or Bot pattern = fr'[o|B|S]' @@ -497,9 +496,9 @@ class Votekick: msg="You cant vote for this user ! he/she is protected", channel=channel ) - return False + return None - for chan in self.VOTE_CHANNEL_DB: + for chan in self.VoteKickManager.VOTE_CHANNEL_DB: if chan.channel_name == channel: chan.target_user = self.User.get_uid(nickname_submitted) @@ -526,7 +525,7 @@ class Votekick: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f'Your are not allowed to execute this command') return None - for chan in self.VOTE_CHANNEL_DB: + for chan in self.VoteKickManager.VOTE_CHANNEL_DB: if chan.channel_name == channel: target_user = self.User.get_nickname(chan.target_user) if chan.vote_for > chan.vote_against: @@ -562,4 +561,8 @@ class Votekick: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote cancel') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote status') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote submit nickname') - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote verdict') \ No newline at end of file + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote verdict') + return None + + case _: + return None \ No newline at end of file diff --git a/mods/votekick/schemas.py b/mods/votekick/schemas.py index 7b7c0c6..6948f1e 100644 --- a/mods/votekick/schemas.py +++ b/mods/votekick/schemas.py @@ -1,6 +1,6 @@ from typing import Optional -from core.definition import MainModel, dataclass, field - +from core.definition import MainModel +from dataclasses import dataclass, field @dataclass class VoteChannelModel(MainModel): From 25262d40495f8b1b624795fca9c6a631813df4d5 Mon Sep 17 00:00:00 2001 From: adator <85586985+adator85@users.noreply.github.com> Date: Sun, 17 Aug 2025 22:41:51 +0200 Subject: [PATCH 16/20] Moving some methods to utils.py, creating new logs class --- core/base.py | 111 +---------- core/classes/admin.py | 9 +- core/classes/channel.py | 10 +- core/classes/client.py | 8 +- core/classes/commands.py | 6 +- core/classes/protocols/inspircd.py | 111 +++++------ core/classes/protocols/unreal6.py | 189 ++++++++++--------- core/classes/reputation.py | 10 +- core/classes/user.py | 8 +- core/irc.py | 80 ++------ core/loader.py | 30 +-- core/logs.py | 108 +++++++++++ core/utils.py | 25 ++- mods/clone/mod_clone.py | 2 +- mods/clone/utils.py | 4 +- mods/command/mod_command.py | 32 ++-- mods/defender/mod_defender.py | 20 +- mods/defender/utils.py | 10 +- mods/jsonrpc/mod_jsonrpc.py | 2 +- mods/test/mod_test.py | 2 +- mods/votekick/mod_votekick.py | 294 ++++++++--------------------- mods/votekick/threads.py | 40 ++++ mods/votekick/utils.py | 74 ++++++++ mods/votekick/votekick_manager.py | 69 ++++++- 24 files changed, 653 insertions(+), 601 deletions(-) create mode 100644 core/logs.py create mode 100644 mods/votekick/threads.py create mode 100644 mods/votekick/utils.py diff --git a/core/base.py b/core/base.py index fe4d47d..e795b1c 100644 --- a/core/base.py +++ b/core/base.py @@ -32,8 +32,9 @@ class Base: self.Config = loader.Config self.Settings = loader.Settings self.Utils = loader.Utils + self.logs = loader.Logs - self.init_log_system() # Demarrer le systeme de log + # self.init_log_system() # Demarrer le systeme de log self.check_for_new_version(True) # Verifier si une nouvelle version est disponible # Liste des timers en cours @@ -140,18 +141,6 @@ class Base: except Exception as err: self.logs.error(f'General Error: {err}') - def get_unixtime(self) -> int: - """ - Cette fonction retourne un UNIXTIME de type 12365456 - Return: Current time in seconds since the Epoch (int) - """ - cet_offset = timezone(timedelta(hours=2)) - now_cet = datetime.now(cet_offset) - unixtime_cet = int(now_cet.timestamp()) - unixtime = int( time.time() ) - - return unixtime - def get_all_modules(self) -> list[str]: """Get list of all main modules using this pattern mod_*.py @@ -203,73 +192,6 @@ class Base: return None - def init_log_system(self) -> None: - # Create folder if not available - logs_directory = f'logs{self.Config.OS_SEP}' - if not os.path.exists(f'{logs_directory}'): - os.makedirs(logs_directory) - - # Init logs object - self.logs = logging.getLogger(self.Config.LOGGING_NAME) - self.logs.setLevel(self.Config.DEBUG_LEVEL) - - # Add Handlers - file_hanlder = logging.FileHandler(f'logs{self.Config.OS_SEP}defender.log',encoding='UTF-8') - file_hanlder.setLevel(self.Config.DEBUG_LEVEL) - - stdout_handler = logging.StreamHandler() - stdout_handler.setLevel(50) - - # Define log format - formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(filename)s - %(lineno)d - %(funcName)s - %(message)s') - - # Apply log format - file_hanlder.setFormatter(formatter) - stdout_handler.setFormatter(formatter) - - # Add handler to logs - self.logs.addHandler(file_hanlder) - self.logs.addHandler(stdout_handler) - - # Apply the filter - self.logs.addFilter(self.replace_filter) - - # self.logs.Logger('defender').addFilter(self.replace_filter) - self.logs.info('#################### STARTING DEFENDER ####################') - - return None - - def replace_filter(self, record: logging.LogRecord) -> bool: - - response = True - filters: list[str] = ['PING', - f':{self.Config.SERVICE_PREFIX}auth'] - - # record.msg = record.getMessage().replace("PING", "[REDACTED]") - if self.Settings.CONSOLE: - print(record.getMessage()) - - for f in filters: - if f in record.getMessage(): - response = False - - return response # Retourne True pour permettre l'affichage du message - - def delete_logger(self, logger_name: str) -> None: - - # Récupérer le logger - logger = logging.getLogger(logger_name) - - # Retirer tous les gestionnaires du logger et les fermer - for handler in logger.handlers[:]: # Utiliser une copie de la liste - logger.removeHandler(handler) - handler.close() - - # Supprimer le logger du dictionnaire global - logging.Logger.manager.loggerDict.pop(logger_name, None) - - return None - def log_cmd(self, user_cmd: str, cmd: str) -> None: """Enregistre les commandes envoyées par les utilisateurs @@ -830,15 +752,6 @@ class Base: self.logs.critical(f'General Error: {err}') return None - def get_random(self, lenght:int) -> str: - """ - Retourn une chaîne aléatoire en fonction de la longueur spécifiée. - """ - caracteres = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' - randomize = ''.join(random.choice(caracteres) for _ in range(lenght)) - - return randomize - def execute_periodic_action(self) -> None: if not self.periodic_func: @@ -878,23 +791,3 @@ class Base: self.logs.debug(f'Method to execute : {str(self.periodic_func)}') return None - - def clean_uid(self, uid:str) -> Optional[str]: - """Clean UID by removing @ / % / + / ~ / * / : - - Args: - uid (str): The UID to clean - - Returns: - str: Clean UID without any sign - """ - try: - if uid is None: - return None - - pattern = fr'[:|@|%|\+|~|\*]*' - parsed_UID = re.sub(pattern, '', uid) - - return parsed_UID - except TypeError as te: - self.logs.error(f'Type Error: {te}') diff --git a/core/classes/admin.py b/core/classes/admin.py index 95dc76b..e7ce183 100644 --- a/core/classes/admin.py +++ b/core/classes/admin.py @@ -1,13 +1,16 @@ -from typing import Optional +from typing import TYPE_CHECKING, Optional from core.base import Base from core.definition import MAdmin +if TYPE_CHECKING: + from core.loader import Loader + class Admin: UID_ADMIN_DB: list[MAdmin] = [] - def __init__(self, base: Base) -> None: - self.Logs = base.logs + def __init__(self, loader: 'Loader') -> None: + self.Logs = loader.Logs def insert(self, new_admin: MAdmin) -> bool: """Insert a new admin object model diff --git a/core/classes/channel.py b/core/classes/channel.py index f628394..ff74229 100644 --- a/core/classes/channel.py +++ b/core/classes/channel.py @@ -13,7 +13,7 @@ class Channel: def __init__(self, loader: 'Loader') -> None: - self.Logs = loader.Base.logs + self.Logs = loader.Logs self.Base = loader.Base self.Utils = loader.Utils @@ -102,7 +102,7 @@ class Channel: return result for userid in chan_obj.uids: - if self.Base.clean_uid(userid) == self.Base.clean_uid(uid): + if self.Utils.clean_uid(userid) == self.Utils.clean_uid(uid): chan_obj.uids.remove(userid) result = True @@ -126,7 +126,7 @@ class Channel: for record in self.UID_CHANNEL_DB: for user_id in record.uids: - if self.Base.clean_uid(user_id) == self.Base.clean_uid(uid): + if self.Utils.clean_uid(user_id) == self.Utils.clean_uid(uid): record.uids.remove(user_id) result = True @@ -177,9 +177,9 @@ class Channel: if chan is None: return False - clean_uid = self.Base.clean_uid(uid=uid) + clean_uid = self.Utils.clean_uid(uid=uid) for chan_uid in chan.uids: - if self.Base.clean_uid(chan_uid) == clean_uid: + if self.Utils.clean_uid(chan_uid) == clean_uid: return True return False diff --git a/core/classes/client.py b/core/classes/client.py index f5e0172..f7afb41 100644 --- a/core/classes/client.py +++ b/core/classes/client.py @@ -2,17 +2,17 @@ from re import sub from typing import Any, Optional, Union, TYPE_CHECKING if TYPE_CHECKING: - from core.base import Base + from core.loader import Loader from core.definition import MClient class Client: CLIENT_DB: list['MClient'] = [] - def __init__(self, base: 'Base'): + def __init__(self, loader: 'Loader'): - self.Logs = base.logs - self.Base = base + self.Logs = loader.Logs + self.Base = loader.Base def insert(self, new_client: 'MClient') -> bool: """Insert a new User object diff --git a/core/classes/commands.py b/core/classes/commands.py index 6a47c75..64e9e93 100644 --- a/core/classes/commands.py +++ b/core/classes/commands.py @@ -2,14 +2,14 @@ from typing import TYPE_CHECKING, Optional from core.definition import MCommand if TYPE_CHECKING: - from core.base import Base + from core.loader import Loader class Command: DB_COMMANDS: list['MCommand'] = [] - def __init__(self, base: 'Base'): - self.Base = base + def __init__(self, loader: 'Loader'): + self.Base = loader.Base def build(self, new_command_obj: MCommand) -> bool: diff --git a/core/classes/protocols/inspircd.py b/core/classes/protocols/inspircd.py index 81bc9c8..67bc57e 100644 --- a/core/classes/protocols/inspircd.py +++ b/core/classes/protocols/inspircd.py @@ -15,8 +15,9 @@ class Inspircd: self.__Config = ircInstance.Config self.__Base = ircInstance.Base self.__Utils = ircInstance.Loader.Utils + self.__Logs = ircInstance.Loader.Logs - self.__Base.logs.info(f"** Loading protocol [{__name__}]") + self.__Logs.info(f"** Loading protocol [{__name__}]") def send2socket(self, message: str, print_log: bool = True) -> None: """Envoit les commandes à envoyer au serveur. @@ -28,24 +29,24 @@ class Inspircd: with self.__Base.lock: self.__Irc.IrcSocket.send(f"{message}\r\n".encode(self.__Config.SERVEUR_CHARSET[0])) if print_log: - self.__Base.logs.debug(f'<< {message}') + self.__Logs.debug(f'<< {message}') except UnicodeDecodeError as ude: - self.__Base.logs.error(f'Decode Error try iso-8859-1 - {ude} - {message}') + self.__Logs.error(f'Decode Error try iso-8859-1 - {ude} - {message}') self.__Irc.IrcSocket.send(f"{message}\r\n".encode(self.__Config.SERVEUR_CHARSET[1],'replace')) except UnicodeEncodeError as uee: - self.__Base.logs.error(f'Encode Error try iso-8859-1 - {uee} - {message}') + self.__Logs.error(f'Encode Error try iso-8859-1 - {uee} - {message}') self.__Irc.IrcSocket.send(f"{message}\r\n".encode(self.__Config.SERVEUR_CHARSET[1],'replace')) except AssertionError as ae: - self.__Base.logs.warning(f'Assertion Error {ae} - message: {message}') + self.__Logs.warning(f'Assertion Error {ae} - message: {message}') except SSLEOFError as soe: - self.__Base.logs.error(f"SSLEOFError: {soe} - {message}") + self.__Logs.error(f"SSLEOFError: {soe} - {message}") except SSLError as se: - self.__Base.logs.error(f"SSLError: {se} - {message}") + self.__Logs.error(f"SSLError: {se} - {message}") except OSError as oe: - self.__Base.logs.error(f"OSError: {oe} - {message}") + self.__Logs.error(f"OSError: {oe} - {message}") except AttributeError as ae: - self.__Base.logs.critical(f"Attribute Error: {ae}") + self.__Logs.critical(f"Attribute Error: {ae}") def send_priv_msg(self, nick_from: str, msg: str, channel: str = None, nick_to: str = None): """Sending PRIVMSG to a channel or to a nickname by batches @@ -62,7 +63,7 @@ class Inspircd: User_to = self.__Irc.User.get_User(nick_to) if nick_to is None else None if User_from is None: - self.__Base.logs.error(f"The sender nickname [{nick_from}] do not exist") + self.__Logs.error(f"The sender nickname [{nick_from}] do not exist") return None if not channel is None: @@ -75,7 +76,7 @@ class Inspircd: batch = str(msg)[i:i+batch_size] self.send2socket(f":{nick_from} PRIVMSG {User_to.uid} :{batch}") except Exception as err: - self.__Base.logs.error(f"General Error: {err}") + self.__Logs.error(f"General Error: {err}") def send_notice(self, nick_from: str, nick_to: str, msg: str) -> None: """Sending NOTICE by batches @@ -91,7 +92,7 @@ class Inspircd: User_to = self.__Irc.User.get_User(nick_to) if User_from is None or User_to is None: - self.__Base.logs.error(f"The sender [{nick_from}] or the Reciever [{nick_to}] do not exist") + self.__Logs.error(f"The sender [{nick_from}] or the Reciever [{nick_to}] do not exist") return None for i in range(0, len(str(msg)), batch_size): @@ -99,9 +100,9 @@ class Inspircd: self.send2socket(f":{User_from.uid} NOTICE {User_to.uid} :{batch}") except Exception as err: - self.__Base.logs.error(f"General Error: {err}") + self.__Logs.error(f"General Error: {err}") - def link(self): + def send_link(self): """Créer le link et envoyer les informations nécessaires pour la connexion au serveur. """ @@ -123,7 +124,7 @@ class Inspircd: service_id = self.__Config.SERVICE_ID version = self.__Config.CURRENT_VERSION - unixtime = self.__Base.get_unixtime() + unixtime = self.__Utils.get_unixtime() self.send2socket(f"CAPAB START 1206") @@ -133,7 +134,7 @@ class Inspircd: self.send2socket(f"BURST {unixtime}") self.send2socket(f":{server_id} ENDBURST") - self.__Base.logs.debug(f'>> {__name__} Link information sent to the server') + self.__Logs.debug(f'>> {__name__} Link information sent to the server') def gline(self, nickname: str, hostname: str, set_by: str, expire_timestamp: int, set_at_timestamp: int, reason: str) -> None: # TKL + G user host set_by expire_timestamp set_at_timestamp :reason @@ -142,12 +143,12 @@ class Inspircd: return None - def set_nick(self, newnickname: str) -> None: + def send_set_nick(self, newnickname: str) -> None: self.send2socket(f":{self.__Config.SERVICE_NICKNAME} NICK {newnickname}") return None - def squit(self, server_id: str, server_link: str, reason: str) -> None: + def send_squit(self, server_id: str, server_link: str, reason: str) -> None: if not reason: reason = 'Service Shutdown' @@ -155,26 +156,26 @@ class Inspircd: self.send2socket(f":{server_id} SQUIT {server_link} :{reason}") return None - def ungline(self, nickname:str, hostname: str) -> None: + def send_ungline(self, nickname:str, hostname: str) -> None: self.send2socket(f":{self.__Config.SERVEUR_ID} TKL - G {nickname} {hostname} {self.__Config.SERVICE_NICKNAME}") return None - def kline(self, nickname: str, hostname: str, set_by: str, expire_timestamp: int, set_at_timestamp: int, reason: str) -> None: + def send_kline(self, nickname: str, hostname: str, set_by: str, expire_timestamp: int, set_at_timestamp: int, reason: str) -> None: # TKL + k user host set_by expire_timestamp set_at_timestamp :reason self.send2socket(f":{self.__Config.SERVEUR_ID} TKL + k {nickname} {hostname} {set_by} {expire_timestamp} {set_at_timestamp} :{reason}") return None - def sjoin(self, channel: str) -> None: + def send_sjoin(self, channel: str) -> None: if not self.__Irc.Channel.is_valid_channel(channel): - self.__Base.logs.error(f"The channel [{channel}] is not valid") + self.__Logs.error(f"The channel [{channel}] is not valid") return None - self.send2socket(f":{self.__Config.SERVEUR_ID} SJOIN {self.__Base.get_unixtime()} {channel} + :{self.__Config.SERVICE_ID}") + self.send2socket(f":{self.__Config.SERVEUR_ID} SJOIN {self.__Utils.get_unixtime()} {channel} + :{self.__Config.SERVICE_ID}") # Add defender to the channel uids list self.__Irc.Channel.insert(self.__Irc.Loader.Definition.MChannel(name=channel, uids=[self.__Config.SERVICE_ID])) @@ -202,7 +203,7 @@ class Inspircd: self.__Irc.Reputation.delete(reputationObj.uid) if not self.__Irc.Channel.delete_user_from_all_channel(uid): - self.__Base.logs.error(f"The UID [{uid}] has not been deleted from all channels") + self.__Logs.error(f"The UID [{uid}] has not been deleted from all channels") return None @@ -221,9 +222,9 @@ class Inspircd: print_log (bool, optional): print logs if true. Defaults to True. """ # {self.Config.SERVEUR_ID} UID - # {clone.nickname} 1 {self.Base.get_unixtime()} {clone.username} {clone.hostname} {clone.uid} * {clone.umodes} {clone.vhost} * {self.Base.encode_ip(clone.remote_ip)} :{clone.realname} + # {clone.nickname} 1 {self.__Utils.get_unixtime()} {clone.username} {clone.hostname} {clone.uid} * {clone.umodes} {clone.vhost} * {self.Base.encode_ip(clone.remote_ip)} :{clone.realname} try: - unixtime = self.__Base.get_unixtime() + unixtime = self.__Utils.get_unixtime() encoded_ip = self.__Base.encode_ip(remote_ip) # Create the user @@ -242,7 +243,7 @@ class Inspircd: return None except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def send_join_chan(self, uidornickname: str, channel: str, password: str = None, print_log: bool = True) -> None: """Joining a channel @@ -261,7 +262,7 @@ class Inspircd: return None if not self.__Irc.Channel.is_valid_channel(channel): - self.__Base.logs.error(f"The channel [{channel}] is not valid") + self.__Logs.error(f"The channel [{channel}] is not valid") return None self.send2socket(f":{userObj.uid} JOIN {channel} {passwordChannel}", print_log=print_log) @@ -282,11 +283,11 @@ class Inspircd: userObj = self.__Irc.User.get_User(uidornickname) if userObj is None: - self.__Base.logs.error(f"The user [{uidornickname}] is not valid") + self.__Logs.error(f"The user [{uidornickname}] is not valid") return None if not self.__Irc.Channel.is_valid_channel(channel): - self.__Base.logs.error(f"The channel [{channel}] is not valid") + self.__Logs.error(f"The channel [{channel}] is not valid") return None self.send2socket(f":{userObj.uid} PART {channel}", print_log=print_log) @@ -295,7 +296,7 @@ class Inspircd: self.__Irc.Channel.delete_user_from_channel(channel, userObj.uid) return None - def unkline(self, nickname:str, hostname: str) -> None: + def send_unkline(self, nickname:str, hostname: str) -> None: self.send2socket(f":{self.__Config.SERVEUR_ID} TKL - K {nickname} {hostname} {self.__Config.SERVICE_NICKNAME}") @@ -322,14 +323,14 @@ class Inspircd: # TODO : User object should be able to update user modes if self.__Irc.User.update_mode(userObj.uid, userMode): return None - # self.__Base.logs.debug(f"Updating user mode for [{userObj.nickname}] [{old_umodes}] => [{userObj.umodes}]") + # self.__Logs.debug(f"Updating user mode for [{userObj.nickname}] [{old_umodes}] => [{userObj.umodes}]") return None except IndexError as ie: - self.__Base.logs.error(f"{__name__} - Index Error: {ie}") + self.__Logs.error(f"{__name__} - Index Error: {ie}") except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def on_quit(self, serverMsg: list[str]) -> None: """Handle quit coming from a server @@ -350,9 +351,9 @@ class Inspircd: return None except IndexError as ie: - self.__Base.logs.error(f"{__name__} - Index Error: {ie}") + self.__Logs.error(f"{__name__} - Index Error: {ie}") except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def on_squit(self, serverMsg: list[str]) -> None: """Handle squit coming from a server @@ -409,9 +410,9 @@ class Inspircd: return None except IndexError as ie: - self.__Base.logs.error(f"{__name__} - Index Error: {ie}") + self.__Logs.error(f"{__name__} - Index Error: {ie}") except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def on_sjoin(self, serverMsg: list[str]) -> None: """Handle sjoin coming from a server @@ -444,7 +445,7 @@ class Inspircd: # Boucle qui va ajouter l'ensemble des users (UID) for i in range(start_boucle, len(serverMsg)): parsed_UID = str(serverMsg[i]) - clean_uid = self.__Irc.User.clean_uid(parsed_UID) + clean_uid = self.__Utils.clean_uid(parsed_UID) if not clean_uid is None and len(clean_uid) == 9: list_users.append(parsed_UID) @@ -458,9 +459,9 @@ class Inspircd: return None except IndexError as ie: - self.__Base.logs.error(f"{__name__} - Index Error: {ie}") + self.__Logs.error(f"{__name__} - Index Error: {ie}") except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def on_part(self, serverMsg: list[str]) -> None: """Handle part coming from a server @@ -479,9 +480,9 @@ class Inspircd: return None except IndexError as ie: - self.__Base.logs.error(f"{__name__} - Index Error: {ie}") + self.__Logs.error(f"{__name__} - Index Error: {ie}") except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def on_uid(self, serverMsg: list[str]) -> None: """Handle uid message coming from the server @@ -542,9 +543,9 @@ class Inspircd: ) return None except IndexError as ie: - self.__Base.logs.error(f"{__name__} - Index Error: {ie}") + self.__Logs.error(f"{__name__} - Index Error: {ie}") except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def on_server_ping(self, serverMsg: list[str]) -> None: """Send a PONG message to the server @@ -562,7 +563,7 @@ class Inspircd: return None except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def on_version(self, serverMsg: list[str]) -> None: """Sending Server Version to the server @@ -574,7 +575,7 @@ class Inspircd: # Réponse a un CTCP VERSION try: - nickname = self.__Irc.User.get_nickname(self.__Base.clean_uid(serverMsg[1])) + nickname = self.__Irc.User.get_nickname(self.__Utils.clean_uid(serverMsg[1])) dnickname = self.__Config.SERVICE_NICKNAME arg = serverMsg[4].replace(':', '') @@ -586,7 +587,7 @@ class Inspircd: return None except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def on_time(self, serverMsg: list[str]) -> None: """Sending TIME answer to a requestor @@ -598,7 +599,7 @@ class Inspircd: # Réponse a un CTCP VERSION try: - nickname = self.__Irc.User.get_nickname(self.__Base.clean_uid(serverMsg[1])) + nickname = self.__Irc.User.get_nickname(self.__Utils.clean_uid(serverMsg[1])) dnickname = self.__Config.SERVICE_NICKNAME arg = serverMsg[4].replace(':', '') current_datetime = self.__Utils.get_sdatetime() @@ -611,7 +612,7 @@ class Inspircd: return None except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def on_ping(self, serverMsg: list[str]) -> None: """Sending a PING answer to requestor @@ -623,7 +624,7 @@ class Inspircd: # Réponse a un CTCP VERSION try: - nickname = self.__Irc.User.get_nickname(self.__Base.clean_uid(serverMsg[1])) + nickname = self.__Irc.User.get_nickname(self.__Utils.clean_uid(serverMsg[1])) dnickname = self.__Config.SERVICE_NICKNAME arg = serverMsg[4].replace(':', '') @@ -632,7 +633,7 @@ class Inspircd: if arg == '\x01PING': recieved_unixtime = int(serverMsg[5].replace('\x01','')) - current_unixtime = self.__Base.get_unixtime() + current_unixtime = self.__Utils.get_unixtime() ping_response = current_unixtime - recieved_unixtime # self.__Irc.send2socket(f':{dnickname} NOTICE {nickname} :\x01PING {ping_response} secs\x01') @@ -644,7 +645,7 @@ class Inspircd: return None except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def on_version_msg(self, serverMsg: list[str]) -> None: """Handle version coming from the server @@ -654,7 +655,7 @@ class Inspircd: """ try: # ['@label=0073', ':0014E7P06', 'VERSION', 'PyDefender'] - getUser = self.__Irc.User.get_User(self.__Irc.User.clean_uid(serverMsg[1])) + getUser = self.__Irc.User.get_User(self.__Utils.clean_uid(serverMsg[1])) if getUser is None: return None @@ -669,4 +670,4 @@ class Inspircd: return None except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") diff --git a/core/classes/protocols/unreal6.py b/core/classes/protocols/unreal6.py index f6166e5..c1c785f 100644 --- a/core/classes/protocols/unreal6.py +++ b/core/classes/protocols/unreal6.py @@ -1,6 +1,6 @@ from re import match, findall, search from datetime import datetime -from typing import TYPE_CHECKING, Optional, Union +from typing import TYPE_CHECKING, Optional from ssl import SSLEOFError, SSLError if TYPE_CHECKING: @@ -17,20 +17,31 @@ class Unrealircd6: self.__Base = ircInstance.Base self.__Settings = ircInstance.Base.Settings self.__Utils = ircInstance.Loader.Utils + self.__Logs = ircInstance.Loader.Logs self.known_protocol: set[str] = {'SJOIN', 'UID', 'MD', 'QUIT', 'SQUIT', 'EOS', 'PRIVMSG', 'MODE', 'UMODE2', 'VERSION', 'REPUTATION', 'SVS2MODE', - 'SLOG', 'NICK', 'PART', 'PONG'} + 'SLOG', 'NICK', 'PART', 'PONG', + 'PROTOCTL', 'SERVER', 'SMOD', 'TKL', 'NETINFO'} - self.__Base.logs.info(f"** Loading protocol [{__name__}]") + self.__Logs.info(f"** Loading protocol [{__name__}]") def get_ircd_protocol_poisition(self, cmd: list[str]) -> tuple[int, Optional[str]]: + """Get the position of known commands + Args: + cmd (list[str]): The server response + + Returns: + tuple[int, Optional[str]]: The position and the command. + """ for index, token in enumerate(cmd): if token.upper() in self.known_protocol: return index, token.upper() + self.__Logs.debug(f"[IRCD LOGS] You need to handle this response: {cmd}") + return (-1, None) def send2socket(self, message: str, print_log: bool = True) -> None: @@ -43,24 +54,24 @@ class Unrealircd6: with self.__Base.lock: self.__Irc.IrcSocket.send(f"{message}\r\n".encode(self.__Config.SERVEUR_CHARSET[0])) if print_log: - self.__Base.logs.debug(f'<< {message}') + self.__Logs.debug(f'<< {message}') except UnicodeDecodeError as ude: - self.__Base.logs.error(f'Decode Error try iso-8859-1 - {ude} - {message}') + self.__Logs.error(f'Decode Error try iso-8859-1 - {ude} - {message}') self.__Irc.IrcSocket.send(f"{message}\r\n".encode(self.__Config.SERVEUR_CHARSET[1],'replace')) except UnicodeEncodeError as uee: - self.__Base.logs.error(f'Encode Error try iso-8859-1 - {uee} - {message}') + self.__Logs.error(f'Encode Error try iso-8859-1 - {uee} - {message}') self.__Irc.IrcSocket.send(f"{message}\r\n".encode(self.__Config.SERVEUR_CHARSET[1],'replace')) except AssertionError as ae: - self.__Base.logs.warning(f'Assertion Error {ae} - message: {message}') + self.__Logs.warning(f'Assertion Error {ae} - message: {message}') except SSLEOFError as soe: - self.__Base.logs.error(f"SSLEOFError: {soe} - {message}") + self.__Logs.error(f"SSLEOFError: {soe} - {message}") except SSLError as se: - self.__Base.logs.error(f"SSLError: {se} - {message}") + self.__Logs.error(f"SSLError: {se} - {message}") except OSError as oe: - self.__Base.logs.error(f"OSError: {oe} - {message}") + self.__Logs.error(f"OSError: {oe} - {message}") except AttributeError as ae: - self.__Base.logs.critical(f"Attribute Error: {ae}") + self.__Logs.critical(f"Attribute Error: {ae}") def send_priv_msg(self, nick_from: str, msg: str, channel: str = None, nick_to: str = None): """Sending PRIVMSG to a channel or to a nickname by batches @@ -77,7 +88,7 @@ class Unrealircd6: User_to = self.__Irc.User.get_User(nick_to) if not nick_to is None else None if User_from is None: - self.__Base.logs.error(f"The sender nickname [{nick_from}] do not exist") + self.__Logs.error(f"The sender nickname [{nick_from}] do not exist") return None if not channel is None: @@ -91,8 +102,8 @@ class Unrealircd6: self.send2socket(f":{nick_from} PRIVMSG {User_to.uid} :{batch}") except Exception as err: - self.__Base.logs.error(f"General Error: {err}") - self.__Base.logs.error(f"General Error: {nick_from} - {channel} - {nick_to}") + self.__Logs.error(f"General Error: {err}") + self.__Logs.error(f"General Error: {nick_from} - {channel} - {nick_to}") def send_notice(self, nick_from: str, nick_to: str, msg: str) -> None: """Sending NOTICE by batches @@ -108,7 +119,7 @@ class Unrealircd6: User_to = self.__Irc.User.get_User(nick_to) if User_from is None or User_to is None: - self.__Base.logs.error(f"The sender [{nick_from}] or the Reciever [{nick_to}] do not exist") + self.__Logs.error(f"The sender [{nick_from}] or the Reciever [{nick_to}] do not exist") return None for i in range(0, len(str(msg)), batch_size): @@ -116,9 +127,9 @@ class Unrealircd6: self.send2socket(f":{User_from.uid} NOTICE {User_to.uid} :{batch}") except Exception as err: - self.__Base.logs.error(f"General Error: {err}") + self.__Logs.error(f"General Error: {err}") - def parse_server_msg(self, server_msg: list[str]) -> Union[str, None]: + def parse_server_msg(self, server_msg: list[str]) -> Optional[str]: """Parse the server message and return the command Args: @@ -152,7 +163,7 @@ class Unrealircd6: return None - def link(self): + def send_link(self): """Créer le link et envoyer les informations nécessaires pour la connexion au serveur. """ @@ -175,7 +186,7 @@ class Unrealircd6: service_id = self.__Config.SERVICE_ID version = self.__Config.CURRENT_VERSION - unixtime = self.__Base.get_unixtime() + unixtime = self.__Utils.get_unixtime() self.send2socket(f":{server_id} PASS :{password}", print_log=False) self.send2socket(f":{server_id} PROTOCTL SID NOQUIT NICKv2 SJOIN SJ3 NICKIP TKLEXT2 NEXTBANS CLK EXTSWHOIS MLOCK MTAGS") @@ -185,20 +196,20 @@ class Unrealircd6: self.send2socket(f":{server_id} SERVER {link} 1 :{info}") self.send2socket(f":{server_id} {nickname} :Reserved for services") self.send2socket(f":{server_id} UID {nickname} 1 {unixtime} {username} {host} {service_id} * {smodes} * * fwAAAQ== :{realname}") - self.sjoin(chan) + self.send_sjoin(chan) self.send2socket(f":{server_id} TKL + Q * {nickname} {host} 0 {unixtime} :Reserved for services") self.send2socket(f":{service_id} MODE {chan} {cmodes}") - self.__Base.logs.debug(f'>> {__name__} Link information sent to the server') + self.__Logs.debug(f'>> {__name__} Link information sent to the server') - def gline(self, nickname: str, hostname: str, set_by: str, expire_timestamp: int, set_at_timestamp: int, reason: str) -> None: + def send_gline(self, nickname: str, hostname: str, set_by: str, expire_timestamp: int, set_at_timestamp: int, reason: str) -> None: # TKL + G user host set_by expire_timestamp set_at_timestamp :reason self.send2socket(f":{self.__Config.SERVEUR_ID} TKL + G {nickname} {hostname} {set_by} {expire_timestamp} {set_at_timestamp} :{reason}") return None - def set_nick(self, newnickname: str) -> None: + def send_set_nick(self, newnickname: str) -> None: """Change nickname of the server \n This method will also update the User object Args: @@ -210,7 +221,7 @@ class Unrealircd6: self.__Irc.User.update_nickname(userObj.uid, newnickname) return None - def squit(self, server_id: str, server_link: str, reason: str) -> None: + def send_squit(self, server_id: str, server_link: str, reason: str) -> None: if not reason: reason = 'Service Shutdown' @@ -218,36 +229,36 @@ class Unrealircd6: self.send2socket(f":{server_id} SQUIT {server_link} :{reason}") return None - def ungline(self, nickname:str, hostname: str) -> None: + def send_ungline(self, nickname:str, hostname: str) -> None: self.send2socket(f":{self.__Config.SERVEUR_ID} TKL - G {nickname} {hostname} {self.__Config.SERVICE_NICKNAME}") return None - def kline(self, nickname: str, hostname: str, set_by: str, expire_timestamp: int, set_at_timestamp: int, reason: str) -> None: + def send_kline(self, nickname: str, hostname: str, set_by: str, expire_timestamp: int, set_at_timestamp: int, reason: str) -> None: # TKL + k user host set_by expire_timestamp set_at_timestamp :reason self.send2socket(f":{self.__Config.SERVEUR_ID} TKL + k {nickname} {hostname} {set_by} {expire_timestamp} {set_at_timestamp} :{reason}") return None - def unkline(self, nickname:str, hostname: str) -> None: + def send_unkline(self, nickname:str, hostname: str) -> None: self.send2socket(f":{self.__Config.SERVEUR_ID} TKL - K {nickname} {hostname} {self.__Config.SERVICE_NICKNAME}") return None - def sjoin(self, channel: str) -> None: + def send_sjoin(self, channel: str) -> None: """Server will join a channel with pre defined umodes Args: channel (str): Channel to join """ if not self.__Irc.Channel.is_valid_channel(channel): - self.__Base.logs.error(f"The channel [{channel}] is not valid") + self.__Logs.error(f"The channel [{channel}] is not valid") return None - self.send2socket(f":{self.__Config.SERVEUR_ID} SJOIN {self.__Base.get_unixtime()} {channel} {self.__Config.SERVICE_UMODES} :{self.__Config.SERVICE_ID}") + self.send2socket(f":{self.__Config.SERVEUR_ID} SJOIN {self.__Utils.get_unixtime()} {channel} {self.__Config.SERVICE_UMODES} :{self.__Config.SERVICE_ID}") self.send2socket(f":{self.__Config.SERVICE_ID} MODE {channel} {self.__Config.SERVICE_UMODES} {self.__Config.SERVICE_ID}") # Add defender to the channel uids list @@ -277,7 +288,7 @@ class Unrealircd6: return None except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def send_sajoin(self, nick_to_sajoin: str, channel_name: str) -> None: """_summary_ @@ -314,7 +325,7 @@ class Unrealircd6: return None except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def send_svs_mode(self, nickname: str, user_mode: str) -> None: try: @@ -333,7 +344,7 @@ class Unrealircd6: return None except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def send_quit(self, uid: str, reason: str, print_log: True) -> None: """Send quit message @@ -355,7 +366,7 @@ class Unrealircd6: self.__Irc.Reputation.delete(reputationObj.uid) if not self.__Irc.Channel.delete_user_from_all_channel(uid): - self.__Base.logs.error(f"The UID [{uid}] has not been deleted from all channels") + self.__Logs.error(f"The UID [{uid}] has not been deleted from all channels") return None @@ -374,9 +385,9 @@ class Unrealircd6: print_log (bool, optional): print logs if true. Defaults to True. """ # {self.Config.SERVEUR_ID} UID - # {clone.nickname} 1 {self.Base.get_unixtime()} {clone.username} {clone.hostname} {clone.uid} * {clone.umodes} {clone.vhost} * {self.Base.encode_ip(clone.remote_ip)} :{clone.realname} + # {clone.nickname} 1 {self.__Utils.get_unixtime()} {clone.username} {clone.hostname} {clone.uid} * {clone.umodes} {clone.vhost} * {self.Base.encode_ip(clone.remote_ip)} :{clone.realname} try: - unixtime = self.__Base.get_unixtime() + unixtime = self.__Utils.get_unixtime() encoded_ip = self.__Base.encode_ip(remote_ip) # Create the user @@ -395,7 +406,7 @@ class Unrealircd6: return None except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def send_join_chan(self, uidornickname: str, channel: str, password: str = None, print_log: bool = True) -> None: """Joining a channel @@ -414,7 +425,7 @@ class Unrealircd6: return None if not self.__Irc.Channel.is_valid_channel(channel): - self.__Base.logs.error(f"The channel [{channel}] is not valid") + self.__Logs.error(f"The channel [{channel}] is not valid") return None self.send2socket(f":{userObj.uid} JOIN {channel} {passwordChannel}", print_log=print_log) @@ -450,11 +461,11 @@ class Unrealircd6: userObj = self.__Irc.User.get_User(uidornickname) if userObj is None: - self.__Base.logs.error(f"The user [{uidornickname}] is not valid") + self.__Logs.error(f"The user [{uidornickname}] is not valid") return None if not self.__Irc.Channel.is_valid_channel(channel): - self.__Base.logs.error(f"The channel [{channel}] is not valid") + self.__Logs.error(f"The channel [{channel}] is not valid") return None self.send2socket(f":{userObj.uid} PART {channel}", print_log=print_log) @@ -467,7 +478,7 @@ class Unrealircd6: channel = self.__Irc.Channel.is_valid_channel(channel_name) if not channel: - self.__Base.logs.error(f'The channel [{channel_name}] is not correct') + self.__Logs.error(f'The channel [{channel_name}] is not correct') return None self.send2socket(f":{self.__Config.SERVICE_NICKNAME} MODE {channel_name} {channel_mode}") @@ -505,9 +516,9 @@ class Unrealircd6: return None except IndexError as ie: - self.__Base.logs.error(f"{__name__} - Index Error: {ie}") + self.__Logs.error(f"{__name__} - Index Error: {ie}") except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def on_mode(self, serverMsg: list[str]) -> None: """Handle mode coming from a server @@ -541,14 +552,14 @@ class Unrealircd6: # TODO : User object should be able to update user modes if self.__Irc.User.update_mode(userObj.uid, userMode): return None - # self.__Base.logs.debug(f"Updating user mode for [{userObj.nickname}] [{old_umodes}] => [{userObj.umodes}]") + # self.__Logs.debug(f"Updating user mode for [{userObj.nickname}] [{old_umodes}] => [{userObj.umodes}]") return None except IndexError as ie: - self.__Base.logs.error(f"{__name__} - Index Error: {ie}") + self.__Logs.error(f"{__name__} - Index Error: {ie}") except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def on_quit(self, serverMsg: list[str]) -> None: """Handle quit coming from a server @@ -569,9 +580,9 @@ class Unrealircd6: return None except IndexError as ie: - self.__Base.logs.error(f"{__name__} - Index Error: {ie}") + self.__Logs.error(f"{__name__} - Index Error: {ie}") except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def on_squit(self, serverMsg: list[str]) -> None: """Handle squit coming from a server @@ -651,9 +662,9 @@ class Unrealircd6: return None except IndexError as ie: - self.__Base.logs.error(f"{__name__} - Index Error: {ie}") + self.__Logs.error(f"{__name__} - Index Error: {ie}") except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def on_sjoin(self, serverMsg: list[str]) -> None: """Handle sjoin coming from a server @@ -690,7 +701,7 @@ class Unrealircd6: # Boucle qui va ajouter l'ensemble des users (UID) for i in range(start_boucle, len(serverMsg_copy)): parsed_UID = str(serverMsg_copy[i]) - clean_uid = self.__Irc.User.clean_uid(parsed_UID) + clean_uid = self.__Utils.clean_uid(parsed_UID) if not clean_uid is None and len(clean_uid) == 9: list_users.append(clean_uid) @@ -704,9 +715,9 @@ class Unrealircd6: return None except IndexError as ie: - self.__Base.logs.error(f"{__name__} - Index Error: {ie}") + self.__Logs.error(f"{__name__} - Index Error: {ie}") except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def on_part(self, serverMsg: list[str]) -> None: """Handle part coming from a server @@ -725,9 +736,9 @@ class Unrealircd6: return None except IndexError as ie: - self.__Base.logs.error(f"{__name__} - Index Error: {ie}") + self.__Logs.error(f"{__name__} - Index Error: {ie}") except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def on_eos(self, serverMsg: list[str]) -> None: """Handle EOS coming from a server @@ -759,16 +770,16 @@ class Unrealircd6: print(f"# VERSION : {version} ") print(f"################################################") - self.__Base.logs.info(f"################### DEFENDER ###################") - self.__Base.logs.info(f"# SERVICE CONNECTE ") - self.__Base.logs.info(f"# SERVEUR : {self.__Config.SERVEUR_IP} ") - self.__Base.logs.info(f"# PORT : {self.__Config.SERVEUR_PORT} ") - self.__Base.logs.info(f"# SSL : {self.__Config.SERVEUR_SSL} ") - self.__Base.logs.info(f"# SSL VER : {self.__Config.SSL_VERSION} ") - self.__Base.logs.info(f"# NICKNAME : {self.__Config.SERVICE_NICKNAME} ") - self.__Base.logs.info(f"# CHANNEL : {self.__Config.SERVICE_CHANLOG} ") - self.__Base.logs.info(f"# VERSION : {version} ") - self.__Base.logs.info(f"################################################") + self.__Logs.info(f"################### DEFENDER ###################") + self.__Logs.info(f"# SERVICE CONNECTE ") + self.__Logs.info(f"# SERVEUR : {self.__Config.SERVEUR_IP} ") + self.__Logs.info(f"# PORT : {self.__Config.SERVEUR_PORT} ") + self.__Logs.info(f"# SSL : {self.__Config.SERVEUR_SSL} ") + self.__Logs.info(f"# SSL VER : {self.__Config.SSL_VERSION} ") + self.__Logs.info(f"# NICKNAME : {self.__Config.SERVICE_NICKNAME} ") + self.__Logs.info(f"# CHANNEL : {self.__Config.SERVICE_CHANLOG} ") + self.__Logs.info(f"# VERSION : {version} ") + self.__Logs.info(f"################################################") if self.__Base.check_for_new_version(False): self.send_priv_msg( @@ -791,11 +802,11 @@ class Unrealircd6: return None except IndexError as ie: - self.__Base.logs.error(f"{__name__} - Key Error: {ie}") + self.__Logs.error(f"{__name__} - Key Error: {ie}") except KeyError as ke: - self.__Base.logs.error(f"{__name__} - Key Error: {ke}") + self.__Logs.error(f"{__name__} - Key Error: {ke}") except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def on_reputation(self, serverMsg: list[str]) -> None: """Handle REPUTATION coming from a server @@ -826,7 +837,7 @@ class Unrealircd6: self.__Irc.first_score = 0 self.Logs.error(f'Value Error {__name__}: {ve}') except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def on_uid(self, serverMsg: list[str]) -> None: """Handle uid message coming from the server @@ -887,9 +898,9 @@ class Unrealircd6: ) return None except IndexError as ie: - self.__Base.logs.error(f"{__name__} - Index Error: {ie}") + self.__Logs.error(f"{__name__} - Index Error: {ie}") except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def on_privmsg(self, serverMsg: list[str]) -> None: """Handle PRIVMSG message coming from the server @@ -910,11 +921,11 @@ class Unrealircd6: if cmd[2] == 'PRIVMSG' and cmd[4] == ':auth': data_copy = cmd.copy() data_copy[6] = '**********' - self.__Base.logs.debug(f">> {data_copy}") + self.__Logs.debug(f">> {data_copy}") else: - self.__Base.logs.debug(f">> {cmd}") + self.__Logs.debug(f">> {cmd}") else: - self.__Base.logs.debug(f">> {cmd}") + self.__Logs.debug(f">> {cmd}") get_uid_or_nickname = str(cmd[0].replace(':','')) user_trigger = self.__Irc.User.get_nickname(get_uid_or_nickname) @@ -929,7 +940,7 @@ class Unrealircd6: arg = convert_to_string.split() arg.remove(f':{self.__Config.SERVICE_PREFIX}') if not arg[0].lower() in self.__Irc.module_commands_list: - self.__Base.logs.debug(f"This command {arg[0]} is not available") + self.__Logs.debug(f"This command {arg[0]} is not available") self.send_notice( nick_from=self.__Config.SERVICE_NICKNAME, nick_to=user_trigger, @@ -968,7 +979,7 @@ class Unrealircd6: return False if not arg[0].lower() in self.__Irc.module_commands_list: - self.__Base.logs.debug(f"This command {arg[0]} sent by {user_trigger} is not available") + self.__Logs.debug(f"This command {arg[0]} sent by {user_trigger} is not available") return False cmd_to_send = convert_to_string.replace(':','') @@ -982,11 +993,11 @@ class Unrealircd6: return None except KeyError as ke: - self.__Base.logs.error(f"Key Error: {ke}") + self.__Logs.error(f"Key Error: {ke}") except AttributeError as ae: - self.__Base.logs.error(f"Attribute Error: {ae}") + self.__Logs.error(f"Attribute Error: {ae}") except Exception as err: - self.__Base.logs.error(f"General Error: {err} - {srv_msg}") + self.__Logs.error(f"General Error: {err} - {srv_msg}") def on_server_ping(self, serverMsg: list[str]) -> None: """Send a PONG message to the server @@ -1001,7 +1012,7 @@ class Unrealircd6: return None except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def on_version(self, serverMsg: list[str]) -> None: """Sending Server Version to the server @@ -1013,7 +1024,7 @@ class Unrealircd6: # Réponse a un CTCP VERSION try: - nickname = self.__Irc.User.get_nickname(self.__Base.clean_uid(serverMsg[1])) + nickname = self.__Irc.User.get_nickname(self.__Utils.clean_uid(serverMsg[1])) dnickname = self.__Config.SERVICE_NICKNAME arg = serverMsg[4].replace(':', '') @@ -1025,7 +1036,7 @@ class Unrealircd6: return None except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def on_time(self, serverMsg: list[str]) -> None: """Sending TIME answer to a requestor @@ -1037,7 +1048,7 @@ class Unrealircd6: # Réponse a un CTCP VERSION try: - nickname = self.__Irc.User.get_nickname(self.__Base.clean_uid(serverMsg[1])) + nickname = self.__Irc.User.get_nickname(self.__Utils.clean_uid(serverMsg[1])) dnickname = self.__Config.SERVICE_NICKNAME arg = serverMsg[4].replace(':', '') current_datetime = self.__Utils.get_sdatetime() @@ -1050,7 +1061,7 @@ class Unrealircd6: return None except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def on_ping(self, serverMsg: list[str]) -> None: """Sending a PING answer to requestor @@ -1062,7 +1073,7 @@ class Unrealircd6: # Réponse a un CTCP VERSION try: - nickname = self.__Irc.User.get_nickname(self.__Base.clean_uid(serverMsg[1])) + nickname = self.__Irc.User.get_nickname(self.__Utils.clean_uid(serverMsg[1])) dnickname = self.__Config.SERVICE_NICKNAME arg = serverMsg[4].replace(':', '') @@ -1071,7 +1082,7 @@ class Unrealircd6: if arg == '\x01PING': recieved_unixtime = int(serverMsg[5].replace('\x01','')) - current_unixtime = self.__Base.get_unixtime() + current_unixtime = self.__Utils.get_unixtime() ping_response = current_unixtime - recieved_unixtime # self.__Irc.send2socket(f':{dnickname} NOTICE {nickname} :\x01PING {ping_response} secs\x01') @@ -1083,7 +1094,7 @@ class Unrealircd6: return None except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") def on_version_msg(self, serverMsg: list[str]) -> None: """Handle version coming from the server @@ -1097,7 +1108,7 @@ class Unrealircd6: if '@' in list(serverMsg_copy[0])[0]: serverMsg_copy.pop(0) - getUser = self.__Irc.User.get_User(self.__Irc.User.clean_uid(serverMsg_copy[0])) + getUser = self.__Irc.User.get_User(self.__Utils.clean_uid(serverMsg_copy[0])) if getUser is None: return None @@ -1115,4 +1126,4 @@ class Unrealircd6: return None except Exception as err: - self.__Base.logs.error(f"{__name__} - General Error: {err}") + self.__Logs.error(f"{__name__} - General Error: {err}") diff --git a/core/classes/reputation.py b/core/classes/reputation.py index 408f95a..859f62c 100644 --- a/core/classes/reputation.py +++ b/core/classes/reputation.py @@ -1,14 +1,16 @@ -from typing import Optional +from typing import TYPE_CHECKING, Optional from core.definition import MReputation -from core.base import Base + +if TYPE_CHECKING: + from core.loader import Loader class Reputation: UID_REPUTATION_DB: list[MReputation] = [] - def __init__(self, base: Base): + def __init__(self, loader: 'Loader'): - self.Logs = base.logs + self.Logs = loader.Logs self.MReputation: MReputation = MReputation def insert(self, new_reputation_user: MReputation) -> bool: diff --git a/core/classes/user.py b/core/classes/user.py index a582780..379a851 100644 --- a/core/classes/user.py +++ b/core/classes/user.py @@ -3,17 +3,17 @@ from typing import Any, Optional, TYPE_CHECKING from datetime import datetime if TYPE_CHECKING: - from core.base import Base + from core.loader import Loader from core.definition import MUser class User: UID_DB: list['MUser'] = [] - def __init__(self, base: 'Base'): + def __init__(self, loader: 'Loader'): - self.Logs = base.logs - self.Base = base + self.Logs = loader.Logs + self.Base = loader.Base def insert(self, new_user: 'MUser') -> bool: """Insert a new User object diff --git a/core/irc.py b/core/irc.py index 17bad7b..6995e25 100644 --- a/core/irc.py +++ b/core/irc.py @@ -54,7 +54,7 @@ class Irc: self.Base = self.Loader.Base # Logger - self.Logs = self.Loader.Base.logs + self.Logs = self.Loader.Logs # Get Settings. self.Settings = self.Base.Settings @@ -216,7 +216,7 @@ class Irc: protocol=self.Config.SERVEUR_PROTOCOL, ircInstance=self.ircObject ).Protocol - self.Protocol.link() # Etablir le link en fonction du protocol choisi + self.Protocol.send_link() # Etablir le link en fonction du protocol choisi self.signal = True # Une variable pour initier la boucle infinie self.__join_saved_channels() # Join existing channels self.load_existing_modules() # Charger les modules existant dans la base de données @@ -240,7 +240,7 @@ class Irc: self.init_service_user() self.__create_socket() - self.Protocol.link() + self.Protocol.send_link() self.__join_saved_channels() self.load_existing_modules() self.Config.DEFENDER_RESTART = 0 @@ -302,7 +302,7 @@ class Irc: if result_query: for chan_name in result_query: chan = chan_name[0] - self.Protocol.sjoin(channel=chan) + self.Protocol.send_sjoin(channel=chan) def send_response(self, responses:list[bytes]) -> None: try: @@ -359,13 +359,10 @@ class Irc: p = self.Protocol admin_obj = self.Admin.get_admin(nickname) dnickname = self.Config.SERVICE_NICKNAME - color_bold = self.Config.COLORS.bold color_nogc = self.Config.COLORS.nogc - color_blue = self.Config.COLORS.blue color_black = self.Config.COLORS.black - color_underline = self.Config.COLORS.underline current_level = 0 - count = 0 + if admin_obj is not None: current_level = admin_obj.level @@ -377,39 +374,11 @@ class Irc: for cmd in self.Commands.get_commands_by_level(current_level): if module is None or cmd.module_name.lower() == module.lower(): p.send_notice( - nick_from=dnickname, - nick_to=nickname, - msg=f" {color_black}{cmd.command_level:<8}{color_nogc}| {cmd.command_name:<25}| {cmd.module_name:<15}| {cmd.description:<35}" - ) - - return - - for level, modules in self.module_commands.items(): - if level > current_level: - break - - if count > 0: - p.send_notice(nick_from=dnickname, nick_to=nickname, msg=" ") - - p.send_notice( - nick_from=dnickname, - nick_to=nickname, - msg=f"{color_blue}{color_bold}Level {level}:{color_nogc}" - ) - - for module_name, commands in modules.items(): - if module is None or module.lower() == module_name.lower(): - p.send_notice( nick_from=dnickname, nick_to=nickname, - msg=f"{color_black} {color_underline}Module: {module_name}{color_nogc}" + msg=f" {color_black}{cmd.command_level:<8}{color_nogc}| {cmd.command_name:<25}| {cmd.module_name:<15}| {cmd.description:<35}" ) - for command, description in commands.items(): - p.send_notice(nick_from=dnickname, nick_to=nickname, msg=f" {command:<20}: {description}") - - count += 1 - - p.send_notice(nick_from=dnickname,nick_to=nickname,msg=f" ***************** FIN DES COMMANDES *****************") + return None def generate_help_menu_bakcup(self, nickname: str) -> None: @@ -888,16 +857,13 @@ class Irc: case 'PING': self.Protocol.on_server_ping(serverMsg=original_response) - self.Logs.debug(f"** handle {parsed_protocol}") return None case 'SJOIN': self.Protocol.on_sjoin(serverMsg=original_response) - self.Logs.debug(f"** handle {parsed_protocol}") case 'EOS': self.Protocol.on_eos(serverMsg=original_response) - self.Logs.debug(f"** handle {parsed_protocol}") case 'UID': try: @@ -906,48 +872,37 @@ class Irc: for classe_name, classe_object in self.loaded_classes.items(): classe_object.cmd(original_response) - self.Logs.debug(f"** handle {parsed_protocol}") - except Exception as err: self.Logs.error(f'General Error: {err}') case 'QUIT': self.Protocol.on_quit(serverMsg=original_response) - self.Logs.debug(f"** handle {parsed_protocol}") case 'PROTOCTL': self.Protocol.on_protoctl(serverMsg=original_response) - self.Logs.debug(f"** handle {parsed_protocol}") case 'SVS2MODE': # >> [':00BAAAAAG', 'SVS2MODE', '001U01R03', '-r'] self.Protocol.on_svs2mode(serverMsg=original_response) - self.Logs.debug(f"** handle {parsed_protocol}") case 'SQUIT': self.Protocol.on_squit(serverMsg=original_response) - self.Logs.debug(f"** handle {parsed_protocol}") case 'PART': self.Protocol.on_part(serverMsg=parsed_protocol) - self.Logs.debug(f"** handle {parsed_protocol}") case 'VERSION': self.Protocol.on_version_msg(serverMsg=original_response) - self.Logs.debug(f"** handle {parsed_protocol}") case 'UMODE2': # [':adator_', 'UMODE2', '-i'] self.Protocol.on_umode2(serverMsg=original_response) - self.Logs.debug(f"** handle {parsed_protocol}") case 'NICK': self.Protocol.on_nick(serverMsg=original_response) - self.Logs.debug(f"** handle {parsed_protocol}") case 'REPUTATION': self.Protocol.on_reputation(serverMsg=original_response) - self.Logs.debug(f"** handle {parsed_protocol}") case 'SLOG': # TODO self.Logs.debug(f"** handle {parsed_protocol}") @@ -957,7 +912,6 @@ class Irc: case 'PRIVMSG': self.Protocol.on_privmsg(serverMsg=original_response) - self.Logs.debug(f"** handle {parsed_protocol}") case 'PONG': # TODO self.Logs.debug(f"** handle {parsed_protocol}") @@ -985,10 +939,9 @@ class Irc: classe_object.cmd(original_response) except IndexError as ie: - self.Logs.error(f"{ie} / {original_response} / length {str(len(original_response))}") + self.Logs.error(f"IndexError: {ie}") except Exception as err: - self.Logs.error(f"General Error: {err}") - self.Logs.error(f"General Error: {traceback.format_exc()}") + self.Logs.error(f"General Error: {err}", exc_info=True) def hcmds(self, user: str, channel: Union[str, None], cmd: list, fullcmd: list = []) -> None: """Create @@ -1461,9 +1414,6 @@ class Irc: module_name = str(cmd[1]) if len(cmd) == 2 else None self.generate_help_menu(nickname=fromuser, module=module_name) - for com in self.Commands.get_ordered_commands(): - print(com) - case 'load': try: # Load a module ex: .load mod_defender @@ -1513,7 +1463,7 @@ class Irc: nick_to=fromuser, msg=f"Arrêt du service {dnickname}" ) - self.Protocol.squit(server_id=self.Config.SERVEUR_ID, server_link=self.Config.SERVEUR_LINK, reason=final_reason) + self.Protocol.send_squit(server_id=self.Config.SERVEUR_ID, server_link=self.Config.SERVEUR_LINK, reason=final_reason) self.Logs.info(f'Arrêt du server {dnickname}') self.Config.DEFENDER_RESTART = 0 self.signal = False @@ -1540,7 +1490,7 @@ class Irc: self.Channel.UID_CHANNEL_DB.clear() # Clear Channel Object self.Base.delete_logger(self.Config.LOGGING_NAME) - self.Protocol.squit(server_id=self.Config.SERVEUR_ID, server_link=self.Config.SERVEUR_LINK, reason=final_reason) + self.Protocol.send_squit(server_id=self.Config.SERVEUR_ID, server_link=self.Config.SERVEUR_LINK, reason=final_reason) self.Logs.info(f'Redémarrage du server {dnickname}') self.loaded_classes.clear() self.Config.DEFENDER_RESTART = 1 # Set restart status to 1 saying that the service will restart @@ -1592,14 +1542,16 @@ class Irc: restart_flag = True if service_nickname != self.Config.SERVICE_NICKNAME: - self.Protocol.set_nick(self.Config.SERVICE_NICKNAME) + self.Protocol.send_set_nick(self.Config.SERVICE_NICKNAME) if restart_flag: self.Config.SERVEUR_ID = serveur_id self.Protocol.send_priv_msg(nick_from=self.Config.SERVICE_NICKNAME, msg='You need to restart defender !', channel=self.Config.SERVICE_CHANLOG) - self.Base.delete_logger(self.Config.LOGGING_NAME) - self.Base = self.Loader.BaseModule.Base(self.Config, self.Settings) + self.Loader.ServiceLogging.remove_logger() + self.Loader.ServiceLogging = self.Loader.LoggingModule.ServiceLogging() + self.Loader.Logs = self.Loader.ServiceLogging.get_logger() + self.Base = self.Loader.BaseModule.Base(self.Loader) importlib.reload(mod_unreal6) importlib.reload(mod_protocol) diff --git a/core/loader.py b/core/loader.py index b45183f..f502417 100644 --- a/core/loader.py +++ b/core/loader.py @@ -1,4 +1,6 @@ +from logging import Logger from core.classes import user, admin, client, channel, reputation, settings, commands +import core.logs as logs import core.definition as df import core.utils as utils import core.base as base_module @@ -9,29 +11,35 @@ class Loader: def __init__(self): # Load Main Modules - self.Definition: df = df + self.Definition: df = df - self.ConfModule: conf_module = conf_module + self.ConfModule: conf_module = conf_module - self.BaseModule: base_module = base_module + self.BaseModule: base_module = base_module - self.Utils: utils = utils + self.Utils: utils = utils + + self.LoggingModule: logs = logs # Load Classes + self.ServiceLogging: logs.ServiceLogging = logs.ServiceLogging() + + self.Logs: Logger = self.ServiceLogging.get_logger() + self.Settings: settings.Settings = settings.Settings() self.Config: df.MConfig = self.ConfModule.Configuration().ConfigObject - self.Base: base_module.Base = self.BaseModule.Base(self) + self.Base: base_module.Base = self.BaseModule.Base(self) - self.User: user.User = user.User(self.Base) + self.User: user.User = user.User(self) - self.Client: client.Client = client.Client(self.Base) + self.Client: client.Client = client.Client(self) - self.Admin: admin.Admin = admin.Admin(self.Base) + self.Admin: admin.Admin = admin.Admin(self) - self.Channel: channel.Channel = channel.Channel(self.Base) + self.Channel: channel.Channel = channel.Channel(self) - self.Reputation: reputation.Reputation = reputation.Reputation(self.Base) + self.Reputation: reputation.Reputation = reputation.Reputation(self) - self.Commands: commands.Command = commands.Command(self.Base) + self.Commands: commands.Command = commands.Command(self) diff --git a/core/logs.py b/core/logs.py new file mode 100644 index 0000000..b100bdc --- /dev/null +++ b/core/logs.py @@ -0,0 +1,108 @@ +import logging +from os import path, makedirs, sep + +class ServiceLogging: + + def __init__(self, loggin_name: str = "defender"): + """Create the Logging object + """ + self.OS_SEP = sep + self.LOGGING_NAME = loggin_name + self.DEBUG_LEVEL, self.DEBUG_FILE_LEVEL, self.DEBUG_STDOUT_LEVEL = (10, 10, 10) + self.SERVER_PREFIX = None + self.LOGGING_CONSOLE = True + + self.LOG_FILTERS: list[str] = ['PING', f":{self.SERVER_PREFIX}auth", "['PASS'"] + + self.file_handler = None + self.stdout_handler = None + + self.logs: logging.Logger = self.start_log_system() + + def get_logger(self) -> logging.Logger: + + logs_obj: logging.Logger = self.logs + + return logs_obj + + def remove_logger(self) -> None: + + # Récupérer le logger + logger = logging.getLogger(self.LOGGING_NAME) + + # Retirer tous les gestionnaires du logger et les fermer + for handler in logger.handlers[:]: # Utiliser une copie de la liste + # print(handler) + logger.removeHandler(handler) + handler.close() + + # Supprimer le logger du dictionnaire global + logging.Logger.manager.loggerDict.pop(self.LOGGING_NAME, None) + + return None + + def start_log_system(self) -> logging.Logger: + + os_sep = self.OS_SEP + logging_name = self.LOGGING_NAME + debug_level = self.DEBUG_LEVEL + debug_file_level = self.DEBUG_FILE_LEVEL + debug_stdout_level = self.DEBUG_STDOUT_LEVEL + + # Create folder if not available + logs_directory = f'logs{os_sep}' + if not path.exists(f'{logs_directory}'): + makedirs(logs_directory) + + # Init logs object + logs = logging.getLogger(logging_name) + logs.setLevel(debug_level) + + # Add Handlers + self.file_handler = logging.FileHandler(f'logs{os_sep}{logging_name}.log',encoding='UTF-8') + self.file_handler.setLevel(debug_file_level) + + self.stdout_handler = logging.StreamHandler() + self.stdout_handler.setLevel(debug_stdout_level) + + # Define log format + formatter = logging.Formatter( + fmt='%(asctime)s - %(levelname)s - %(message)s (%(filename)s:%(funcName)s:%(lineno)d)', + datefmt='%Y-%m-%d %H:%M:%S' + ) + + # Apply log format + self.file_handler.setFormatter(formatter) + self.stdout_handler.setFormatter(formatter) + + # Add handler to logs + logs.addHandler(self.file_handler) + logs.addHandler(self.stdout_handler) + + # Apply the filter + logs.addFilter(self.replace_filter) + + logs.info(f'#################### STARTING {self.LOGGING_NAME} ####################') + + return logs + + def set_stdout_handler_level(self, level: int) -> None: + self.stdout_handler.setLevel(level) + + def set_file_handler_level(self, level: int) -> None: + self.file_handler.setLevel(level) + + def replace_filter(self, record: logging.LogRecord) -> bool: + + response = True + filter: list[str] = ['PING', f":{self.SERVER_PREFIX}auth", "['PASS'"] + + # record.msg = record.getMessage().replace("PING", "[REDACTED]") + # if self.LOGGING_CONSOLE: + # print(record.getMessage()) + + for f in filter: + if f in record.getMessage(): + response = False + + return response # Retourne True pour permettre l'affichage du message diff --git a/core/utils.py b/core/utils.py index d5aec4b..b832335 100644 --- a/core/utils.py +++ b/core/utils.py @@ -2,8 +2,9 @@ Main utils library. ''' from pathlib import Path +from re import sub from typing import Literal, Optional, Any -from datetime import datetime +from datetime import datetime, timedelta, timezone from time import time from random import choice from hashlib import md5, sha3_512 @@ -29,6 +30,9 @@ def get_unixtime() -> int: Returns: int: Current time in seconds since the Epoch (int) """ + cet_offset = timezone(timedelta(hours=2)) + now_cet = datetime.now(cet_offset) + unixtime_cet = int(now_cet.timestamp()) return int(time()) def get_sdatetime() -> str: @@ -89,4 +93,21 @@ def get_all_modules() -> list[str]: list[str]: List of module names. """ base_path = Path('mods') - return [file.name.replace('.py', '') for file in base_path.rglob('mod_*.py')] \ No newline at end of file + return [file.name.replace('.py', '') for file in base_path.rglob('mod_*.py')] + +def clean_uid(uid: str) -> Optional[str]: + """Clean UID by removing @ / % / + / ~ / * / : + + Args: + uid (str): The UID to clean + + Returns: + str: Clean UID without any sign + """ + if uid is None: + return None + + pattern = fr'[:|@|%|\+|~|\*]*' + parsed_UID = sub(pattern, '', uid) + + return parsed_UID diff --git a/mods/clone/mod_clone.py b/mods/clone/mod_clone.py index 40e2ff2..78a4591 100644 --- a/mods/clone/mod_clone.py +++ b/mods/clone/mod_clone.py @@ -28,7 +28,7 @@ class Clone: self.Base = irc_instance.Base # Add logs object to the module (Mandatory) - self.Logs = irc_instance.Base.logs + self.Logs = irc_instance.Loader.Logs # Add User object to the module (Mandatory) self.User = irc_instance.User diff --git a/mods/clone/utils.py b/mods/clone/utils.py index a636b68..ce509a1 100644 --- a/mods/clone/utils.py +++ b/mods/clone/utils.py @@ -156,7 +156,7 @@ def create_new_clone(uplink: 'Clone', faker_instance: 'Faker', group: str = 'Def uid = generate_uid_for_clone(faker, uplink.Config.SERVEUR_ID) checkUid = uplink.Clone.uid_exists(uid=uid) - clone = uplink.Definition.MClone( + clone = uplink.Schemas.MClone( connected=False, nickname=nickname, username=username, @@ -176,7 +176,7 @@ def create_new_clone(uplink: 'Clone', faker_instance: 'Faker', group: str = 'Def def handle_on_privmsg(uplink: 'Clone', srvmsg: list[str]): - uid_sender = uplink.User.clean_uid(srvmsg[1]) + uid_sender = uplink.Irc.Utils.clean_uid(srvmsg[1]) senderObj = uplink.User.get_User(uid_sender) if senderObj.hostname in uplink.Config.CLONE_LOG_HOST_EXEMPT: diff --git a/mods/command/mod_command.py b/mods/command/mod_command.py index 3f5d9d3..daadbbc 100644 --- a/mods/command/mod_command.py +++ b/mods/command/mod_command.py @@ -34,8 +34,11 @@ class Command: # Add Base object to the module (Mandatory) self.Base = ircInstance.Base + # Add main Utils to the module + self.MainUtils = ircInstance.Utils + # Add logs object to the module (Mandatory) - self.Logs = ircInstance.Base.logs + self.Logs = ircInstance.Loader.Logs # Add User object to the module (Mandatory) self.User = ircInstance.User @@ -296,7 +299,7 @@ class Command: except Exception as err: self.Logs.error(f"General Error: {err}") - def hcmds(self, uidornickname: str, channel_name: Optional[str], cmd: list, fullcmd: list = []) -> None: + def hcmds(self, uidornickname: str, channel_name: Optional[str], cmd: list, fullcmd: list = []): command = str(cmd[0]).lower() dnickname = self.Config.SERVICE_NICKNAME @@ -307,7 +310,8 @@ class Command: fromchannel = channel_name match command: - case "automode": + + case 'automode': # automode set nickname [+/-mode] #channel # automode set adator +o #channel try: @@ -346,7 +350,7 @@ class Command: if db_result is not None: if sign == '+': - db_data = {"updated_on": self.Base.get_datetime(), "nickname": nickname, "channel": chan, "mode": mode} + db_data = {"updated_on": self.MainUtils.get_datetime(), "nickname": nickname, "channel": chan, "mode": mode} db_result = self.Base.db_execute_query(query="UPDATE command_automode SET mode = :mode, updated_on = :updated_on WHERE nickname = :nickname and channel = :channel", params=db_data) if db_result.rowcount > 0: @@ -432,7 +436,7 @@ class Command: for uid in uids_split: for i in range(0, len(uid)): mode += set_mode - users += f'{self.User.get_nickname(self.Base.clean_uid(uid[i]))} ' + users += f'{self.User.get_nickname(self.MainUtils.clean_uid(uid[i]))} ' if i == len(uid) - 1: self.Protocol.send2socket(f":{service_id} MODE {fromchannel} +{mode} {users}") mode = '' @@ -454,7 +458,7 @@ class Command: for uid in uids_split: for i in range(0, len(uid)): mode += set_mode - users += f'{self.User.get_nickname(self.Base.clean_uid(uid[i]))} ' + users += f'{self.User.get_nickname(self.MainUtils.clean_uid(uid[i]))} ' if i == len(uid) - 1: self.Protocol.send2socket(f":{service_id} MODE {fromchannel} +{mode} {users}") mode = '' @@ -1146,7 +1150,7 @@ class Command: # .svsnick nickname newnickname nickname = str(cmd[1]) newnickname = str(cmd[2]) - unixtime = self.Base.get_unixtime() + unixtime = self.MainUtils.get_unixtime() if self.User.get_nickname(nickname) is None: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" This nickname do not exist") @@ -1192,7 +1196,7 @@ class Command: nickname = str(cmd[1]) hostname = str(cmd[2]) - set_at_timestamp = self.Base.get_unixtime() + set_at_timestamp = self.MainUtils.get_unixtime() expire_time = (60 * 60 * 24) + set_at_timestamp gline_reason = ' '.join(cmd[3:]) @@ -1201,7 +1205,7 @@ class Command: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname host reason") return None - self.Protocol.gline(nickname=nickname, hostname=hostname, set_by=dnickname, expire_timestamp=expire_time, set_at_timestamp=set_at_timestamp, reason=gline_reason) + self.Protocol.send_gline(nickname=nickname, hostname=hostname, set_by=dnickname, expire_timestamp=expire_time, set_at_timestamp=set_at_timestamp, reason=gline_reason) except KeyError as ke: self.Logs.error(ke) @@ -1222,7 +1226,7 @@ class Command: hostname = str(cmd[2]) # self.Protocol.send2socket(f":{self.Config.SERVEUR_ID} TKL - G {nickname} {hostname} {dnickname}") - self.Protocol.ungline(nickname=nickname, hostname=hostname) + self.Protocol.send_ungline(nickname=nickname, hostname=hostname) except KeyError as ke: self.Logs.error(ke) @@ -1240,7 +1244,7 @@ class Command: nickname = str(cmd[1]) hostname = str(cmd[2]) - set_at_timestamp = self.Base.get_unixtime() + set_at_timestamp = self.MainUtils.get_unixtime() expire_time = (60 * 60 * 24) + set_at_timestamp gline_reason = ' '.join(cmd[3:]) @@ -1249,7 +1253,7 @@ class Command: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname host reason") return None - self.Protocol.kline(nickname=nickname, hostname=hostname, set_by=dnickname, expire_timestamp=expire_time, set_at_timestamp=set_at_timestamp, reason=gline_reason) + self.Protocol.send_kline(nickname=nickname, hostname=hostname, set_by=dnickname, expire_timestamp=expire_time, set_at_timestamp=set_at_timestamp, reason=gline_reason) except KeyError as ke: self.Logs.error(ke) @@ -1269,7 +1273,7 @@ class Command: nickname = str(cmd[1]) hostname = str(cmd[2]) - self.Protocol.unkline(nickname=nickname, hostname=hostname) + self.Protocol.send_unkline(nickname=nickname, hostname=hostname) except KeyError as ke: self.Logs.error(ke) @@ -1288,7 +1292,7 @@ class Command: nickname = str(cmd[1]) hostname = str(cmd[2]) - set_at_timestamp = self.Base.get_unixtime() + set_at_timestamp = self.MainUtils.get_unixtime() expire_time = (60 * 60 * 24) + set_at_timestamp shun_reason = ' '.join(cmd[3:]) diff --git a/mods/defender/mod_defender.py b/mods/defender/mod_defender.py index dae03a1..d12ea8a 100644 --- a/mods/defender/mod_defender.py +++ b/mods/defender/mod_defender.py @@ -30,7 +30,7 @@ class Defender: self.Base = irc_instance.Base # Add logs object to the module (Mandatory) - self.Logs = irc_instance.Base.logs + self.Logs = irc_instance.Loader.Logs # Add User object to the module (Mandatory) self.User = irc_instance.User @@ -118,7 +118,7 @@ class Defender: self.Base.create_thread(func=thds.thread_autolimit, func_args=(self, )) if self.ModConfig.reputation == 1: - self.Protocol.sjoin(self.Config.SALON_JAIL) + self.Protocol.send_sjoin(self.Config.SALON_JAIL) self.Protocol.send2socket(f":{self.Config.SERVICE_NICKNAME} SAMODE {self.Config.SALON_JAIL} +o {self.Config.SERVICE_NICKNAME}") return None @@ -176,7 +176,7 @@ class Defender: self.Schemas.DB_FREEIPAPI_USERS = freeipapi if cloudfilt: - self.Schemas.DB_CLOUD_FILT_USERS = cloudfilt + self.Schemas.DB_CLOUDFILT_USERS = cloudfilt if psutils: self.Schemas.DB_PSUTIL_USERS = psutils @@ -239,7 +239,7 @@ class Defender: for channel in channels: chan = channel[0] - self.Protocol.sjoin(chan) + self.Protocol.send_sjoin(chan) if chan == jail_chan: self.Protocol.send2socket(f":{service_id} SAMODE {jail_chan} +{dumodes} {dnickname}") self.Protocol.send2socket(f":{service_id} MODE {jail_chan} +{jail_chan_mode}") @@ -931,15 +931,15 @@ class Defender: case 'info': try: + if len(cmd) < 2: + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Syntax. /msg {dnickname} INFO [nickname]") + return None + nickoruid = cmd[1] UserObject = self.User.get_User(nickoruid) if UserObject is not None: - channels: list = [] - for chan in self.Channel.UID_CHANNEL_DB: - for uid_in_chan in chan.uids: - if self.Base.clean_uid(uid_in_chan) == UserObject.uid: - channels.append(chan.name) + channels: list = [chan.name for chan in self.Channel.UID_CHANNEL_DB for uid_in_chan in chan.uids if self.Loader.Utils.clean_uid(uid_in_chan) == UserObject.uid] self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' UID : {UserObject.uid}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' NICKNAME : {UserObject.nickname}') @@ -956,7 +956,7 @@ class Defender: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' CHANNELS : {channels}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' CONNECTION TIME : {UserObject.connexion_datetime}') else: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f":{dnickname} NOTICE {fromuser} : This user {nickoruid} doesn't exist") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"This user {nickoruid} doesn't exist") except KeyError as ke: self.Logs.warning(f"Key error info user : {ke}") diff --git a/mods/defender/utils.py b/mods/defender/utils.py index 08cc470..9855791 100644 --- a/mods/defender/utils.py +++ b/mods/defender/utils.py @@ -81,7 +81,7 @@ def handle_on_sjoin(uplink: 'Defender', srvmsg: list[str]): confmodel = uplink.ModConfig parsed_chan = srvmsg[4] if irc.Channel.is_valid_channel(srvmsg[4]) else None - parsed_UID = irc.User.clean_uid(srvmsg[5]) + parsed_UID = uplink.Loader.Utils.clean_uid(srvmsg[5]) if parsed_chan is None or parsed_UID is None: return @@ -145,7 +145,7 @@ def handle_on_nick(uplink: 'Defender', srvmsg: list[str]): srvmsg (list[str]): The Server MSG confmodel (ModConfModel): The Module Configuration """ - uid = uplink.User.clean_uid(str(srvmsg[1])) + uid = uplink.Loader.Utils.clean_uid(str(srvmsg[1])) p = uplink.Protocol confmodel = uplink.ModConfig @@ -180,7 +180,7 @@ def handle_on_quit(uplink: 'Defender', srvmsg: list[str]): confmodel = uplink.ModConfig ban_all_chan = uplink.Base.int_if_possible(confmodel.reputation_ban_all_chan) - final_UID = uplink.User.clean_uid(str(srvmsg[1])) + final_UID = uplink.Loader.Utils.clean_uid(str(srvmsg[1])) jail_salon = uplink.Config.SALON_JAIL service_id = uplink.Config.SERVICE_ID get_user_reputation = uplink.Reputation.get_Reputation(final_UID) @@ -238,7 +238,7 @@ def handle_on_uid(uplink: 'Defender', srvmsg: list[str]): irc.Reputation.insert( irc.Loader.Definition.MReputation( **_User.to_dict(), - secret_code=irc.Base.get_random(8) + secret_code=irc.Utils.generate_random_string(8) ) ) if irc.Reputation.is_exist(_User.uid): @@ -278,7 +278,7 @@ def action_on_flood(uplink: 'Defender', srvmsg: list[str]): get_detected_uid = User.uid get_detected_nickname = User.nickname - unixtime = irc.Base.get_unixtime() + unixtime = irc.Utils.get_unixtime() get_diff_secondes = 0 def get_flood_user(uid: str) -> Optional[FloodUser]: diff --git a/mods/jsonrpc/mod_jsonrpc.py b/mods/jsonrpc/mod_jsonrpc.py index 5ed288a..e7181cd 100644 --- a/mods/jsonrpc/mod_jsonrpc.py +++ b/mods/jsonrpc/mod_jsonrpc.py @@ -33,7 +33,7 @@ class Jsonrpc(): self.Base = ircInstance.Base # Add logs object to the module (Mandatory) - self.Logs = ircInstance.Base.logs + self.Logs = ircInstance.Loader.Logs # Add User object to the module (Mandatory) self.User = ircInstance.User diff --git a/mods/test/mod_test.py b/mods/test/mod_test.py index 275f845..8372e6f 100644 --- a/mods/test/mod_test.py +++ b/mods/test/mod_test.py @@ -34,7 +34,7 @@ class Test(): self.Base = ircInstance.Base # Add logs object to the module (Mandatory) - self.Logs = ircInstance.Base.logs + self.Logs = ircInstance.Loader.Logs # Add User object to the module (Mandatory) self.User = ircInstance.User diff --git a/mods/votekick/mod_votekick.py b/mods/votekick/mod_votekick.py index 5a9aa36..54acd2d 100644 --- a/mods/votekick/mod_votekick.py +++ b/mods/votekick/mod_votekick.py @@ -10,7 +10,9 @@ """ import re import mods.votekick.schemas as schemas +import mods.votekick.utils as utils from mods.votekick.votekick_manager import VotekickManager +import mods.votekick.threads as thds from typing import TYPE_CHECKING, Any, Optional if TYPE_CHECKING: @@ -40,7 +42,7 @@ class Votekick: self.Base = uplink.Base # Add logs object to the module - self.Logs = uplink.Base.logs + self.Logs = uplink.Logs # Add User object to the module self.User = uplink.User @@ -51,9 +53,15 @@ class Votekick: # Add Utils. self.Utils = uplink.Utils + # Add Utils module + self.ModUtils = utils + # Add Schemas module self.Schemas = schemas + # Add Threads module + self.Threads = thds + # Add VoteKick Manager self.VoteKickManager = VotekickManager(self) @@ -77,7 +85,7 @@ class Votekick: # Add admin object to retrieve admin users self.Admin = self.Irc.Admin self.__create_tables() - self.join_saved_channels() + self.ModUtils.join_saved_channels(self) return None @@ -126,139 +134,28 @@ class Votekick: except Exception as err: self.Logs.error(f'General Error: {err}') - def init_vote_system(self, channel: str) -> bool: - - response = False - for chan in self.VoteKickManager.VOTE_CHANNEL_DB: - if chan.channel_name == channel: - chan.target_user = '' - chan.voter_users = [] - chan.vote_against = 0 - chan.vote_for = 0 - response = True - - return response - - def insert_vote_channel(self, channel_obj: schemas.VoteChannelModel) -> bool: - result = False - found = False - for chan in self.VoteKickManager.VOTE_CHANNEL_DB: - if chan.channel_name == channel_obj.channel_name: - found = True - - if not found: - self.VoteKickManager.VOTE_CHANNEL_DB.append(channel_obj) - self.Logs.debug(f"The channel has been added {channel_obj}") - # self.db_add_vote_channel(ChannelObject.channel_name) - - return result - - def db_add_vote_channel(self, channel: str) -> bool: - """Cette fonction ajoute les salons ou seront autoriser les votes - - Args: - channel (str): le salon à enregistrer. - """ - current_datetime = self.Utils.get_sdatetime() - mes_donnees = {'channel': channel} - - response = self.Base.db_execute_query("SELECT id FROM votekick_channel WHERE channel = :channel", mes_donnees) - - is_channel_exist = response.fetchone() - - if is_channel_exist is None: - mes_donnees = {'datetime': current_datetime, 'channel': channel} - insert = self.Base.db_execute_query(f"INSERT INTO votekick_channel (datetime, channel) VALUES (:datetime, :channel)", mes_donnees) - if insert.rowcount > 0: - return True - else: - return False - else: - return False - - def db_delete_vote_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 votekick_channel WHERE channel = :channel", mes_donnes) - - affected_row = response.rowcount - - if affected_row > 0: - return True - else: - return False - - def join_saved_channels(self) -> None: - - param = {'module_name': self.module_name} - result = self.Base.db_execute_query(f"SELECT id, channel_name FROM {self.Config.TABLE_CHANNEL} WHERE module_name = :module_name", param) - - channels = result.fetchall() - - for channel in channels: - id_, chan = channel - self.insert_vote_channel(self.Schemas.VoteChannelModel(channel_name=chan, target_user='', voter_users=[], vote_for=0, vote_against=0)) - self.Protocol.sjoin(channel=chan) - self.Protocol.send2socket(f":{self.Config.SERVICE_NICKNAME} SAMODE {chan} +o {self.Config.SERVICE_NICKNAME}") - - return None - - def is_vote_ongoing(self, channel: str) -> bool: - - response = False - for vote in self.VoteKickManager.VOTE_CHANNEL_DB: - if vote.channel_name == channel: - if vote.target_user: - response = True - - return response - - def timer_vote_verdict(self, channel: str) -> None: - - dnickname = self.Config.SERVICE_NICKNAME - - if not self.is_vote_ongoing(channel): - return None - - for chan in self.VoteKickManager.VOTE_CHANNEL_DB: - if chan.channel_name == channel: - target_user = self.User.get_nickname(chan.target_user) - if chan.vote_for > chan.vote_against: - self.Protocol.send_priv_msg( - nick_from=dnickname, - msg=f"User {self.Config.COLORS.bold}{target_user}{self.Config.COLORS.nogc} has {chan.vote_against} votes against and {chan.vote_for} votes for. For this reason, it'll be kicked from the channel", - channel=channel - ) - self.Protocol.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.Protocol.send_priv_msg( - nick_from=dnickname, - msg=f"User {self.Config.COLORS.bold}{target_user}{self.Config.COLORS.nogc} has {chan.vote_against} votes against and {chan.vote_for} votes for. For this reason, it\'ll remain in the channel", - channel=channel - ) - - # Init the system - if self.init_vote_system(channel): - self.Protocol.send_priv_msg( - nick_from=dnickname, - msg="System vote re initiated", - channel=channel - ) - - return None - def cmd(self, data: list) -> None: - cmd = list(data).copy() + if not data or len(data) < 2: + return None + + cmd = data.copy() if isinstance(data, list) else list(data).copy() + index, command = self.Irc.Protocol.get_ircd_protocol_poisition(cmd) + if index == -1: + return None try: - return None + match command: + + case 'PRIVMSG': + return None + + case 'QUIT': + return None + + case _: + return None except KeyError as ke: self.Logs.error(f"Key Error: {ke}") @@ -307,24 +204,17 @@ class Votekick: if sentchannel is None: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f" The correct command is {self.Config.SERVICE_PREFIX}{command} {option} #CHANNEL") - self.insert_vote_channel( - self.Schemas.VoteChannelModel( - channel_name=sentchannel, - target_user='', - voter_users=[], - vote_for=0, - vote_against=0 - ) - ) + if self.VoteKickManager.activate_new_channel(sentchannel): + self.Channel.db_query_channel('add', self.module_name, sentchannel) + self.Protocol.send_join_chan(uidornickname=dnickname, channel=sentchannel) + self.Protocol.send2socket(f":{dnickname} SAMODE {sentchannel} +o {dnickname}") + self.Protocol.send_priv_msg(nick_from=dnickname, + msg="You can now use !submit to decide if he will stay or not on this channel ", + channel=sentchannel + ) - self.Channel.db_query_channel('add', self.module_name, sentchannel) + return None - self.Protocol.send_join_chan(uidornickname=dnickname, channel=sentchannel) - self.Protocol.send2socket(f":{dnickname} SAMODE {sentchannel} +o {dnickname}") - self.Protocol.send_priv_msg(nick_from=dnickname, - msg="You can now use !submit to decide if he will stay or not on this channel ", - channel=sentchannel - ) except Exception as err: self.Logs.error(f'{err}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option} #channel') @@ -344,12 +234,10 @@ class Votekick: self.Protocol.send2socket(f":{dnickname} SAMODE {sentchannel} -o {dnickname}") self.Protocol.send_part_chan(uidornickname=dnickname, channel=sentchannel) - for chan in self.VoteKickManager.VOTE_CHANNEL_DB: - if chan.channel_name == sentchannel: - self.VoteKickManager.VOTE_CHANNEL_DB.remove(chan) - self.Channel.db_query_channel('del', self.module_name, chan.channel_name) + if self.VoteKickManager.drop_vote_channel_model(sentchannel): + self.Channel.db_query_channel('del', self.module_name, sentchannel) + return None - self.Logs.debug(f"The Channel {sentchannel} has been deactivated from the vote system") except Exception as err: self.Logs.error(f'{err}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f" /msg {dnickname} {command} {option} #channel") @@ -359,20 +247,11 @@ class Votekick: try: # vote + channel = fromchannel - for chan in self.VoteKickManager.VOTE_CHANNEL_DB: - if chan.channel_name == channel: - if fromuser in chan.voter_users: - self.Protocol.send_priv_msg(nick_from=dnickname, - msg="You already submitted a vote", - channel=channel - ) - else: - chan.vote_for += 1 - chan.voter_users.append(fromuser) - self.Protocol.send_priv_msg(nick_from=dnickname, - msg="Vote recorded, thank you", - channel=channel - ) + if self.VoteKickManager.action_vote(channel, fromuser, '+'): + self.Protocol.send_priv_msg(nick_from=dnickname, msg="Vote recorded, thank you",channel=channel) + else: + self.Protocol.send_priv_msg(nick_from=dnickname, msg="You already submitted a vote", channel=channel) + except Exception as err: self.Logs.error(f'{err}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option}') @@ -382,20 +261,11 @@ class Votekick: try: # vote - channel = fromchannel - for chan in self.VoteKickManager.VOTE_CHANNEL_DB: - if chan.channel_name == channel: - if fromuser in chan.voter_users: - self.Protocol.send_priv_msg(nick_from=dnickname, - msg="You already submitted a vote", - channel=channel - ) - else: - chan.vote_against += 1 - chan.voter_users.append(fromuser) - self.Protocol.send_priv_msg(nick_from=dnickname, - msg="Vote recorded, thank you", - channel=channel - ) + if self.VoteKickManager.action_vote(channel, fromuser, '-'): + self.Protocol.send_priv_msg(nick_from=dnickname, msg="Vote recorded, thank you",channel=channel) + else: + self.Protocol.send_priv_msg(nick_from=dnickname, msg="You already submitted a vote", channel=channel) + except Exception as err: self.Logs.error(f'{err}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option}') @@ -414,11 +284,11 @@ class Votekick: for vote in self.VoteKickManager.VOTE_CHANNEL_DB: if vote.channel_name == channel: - self.init_vote_system(channel) - self.Protocol.send_priv_msg(nick_from=dnickname, - msg="Vote system re-initiated", - channel=channel - ) + if self.VoteKickManager.init_vote_system(channel): + self.Protocol.send_priv_msg(nick_from=dnickname, + msg="Vote system re-initiated", + channel=channel + ) except Exception as err: self.Logs.error(f'{err}') @@ -452,16 +322,15 @@ class Votekick: ongoing_user = None # check if there is an ongoing vote - if self.is_vote_ongoing(channel): - for vote in self.VoteKickManager.VOTE_CHANNEL_DB: - if vote.channel_name == channel: - ongoing_user = self.User.get_nickname(vote.target_user) - - self.Protocol.send_priv_msg(nick_from=dnickname, - msg=f"There is an ongoing vote on {ongoing_user}", - channel=channel - ) - return None + if self.VoteKickManager.is_vote_ongoing(channel): + votec = self.VoteKickManager.get_vote_channel_model(channel) + if votec: + ongoing_user = self.User.get_nickname(votec.target_user) + self.Protocol.send_priv_msg(nick_from=dnickname, + msg=f"There is an ongoing vote on {ongoing_user}", + channel=channel + ) + return None # check if the user exist if user_submitted is None: @@ -471,7 +340,7 @@ class Votekick: ) return None - uid_cleaned = self.Base.clean_uid(uid_submitted) + uid_cleaned = self.Loader.Utils.clean_uid(uid_submitted) channel_obj = self.Channel.get_channel(channel) if channel_obj is None: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' This channel [{channel}] do not exist in the Channel Object') @@ -479,7 +348,7 @@ class Votekick: clean_uids_in_channel: list = [] for uid in channel_obj.uids: - clean_uids_in_channel.append(self.Base.clean_uid(uid)) + clean_uids_in_channel.append(self.Loader.Utils.clean_uid(uid)) if not uid_cleaned in clean_uids_in_channel: self.Protocol.send_priv_msg(nick_from=dnickname, @@ -507,7 +376,7 @@ class Votekick: channel=channel ) - self.Base.create_timer(60, self.timer_vote_verdict, (channel, )) + self.Base.create_timer(60, self.Threads.timer_vote_verdict, (self, channel)) self.Protocol.send_priv_msg(nick_from=dnickname, msg="This vote will end after 60 secondes", channel=channel @@ -524,30 +393,31 @@ class Votekick: if self.Admin.get_admin(fromuser) is None: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f'Your are not allowed to execute this command') return None - - for chan in self.VoteKickManager.VOTE_CHANNEL_DB: - if chan.channel_name == channel: - target_user = self.User.get_nickname(chan.target_user) - if chan.vote_for > chan.vote_against: - self.Protocol.send_priv_msg(nick_from=dnickname, - msg=f"User {self.Config.COLORS.bold}{target_user}{self.Config.COLORS.nogc} has {chan.vote_against} votes against and {chan.vote_for} votes for. For this reason, it\'ll be kicked from the channel", + + votec = self.VoteKickManager.get_vote_channel_model(channel) + if votec: + target_user = self.User.get_nickname(votec.target_user) + if votec.vote_for >= votec.vote_against: + self.Protocol.send_priv_msg(nick_from=dnickname, + msg=f"User {self.Config.COLORS.bold}{target_user}{self.Config.COLORS.nogc} has {votec.vote_against} votes against and {votec.vote_for} votes for. For this reason, it\'ll be kicked from the channel", channel=channel ) - self.Protocol.send2socket(f":{dnickname} KICK {channel} {target_user} Following the vote, you are not welcome in {channel}") - elif chan.vote_for <= chan.vote_against: - self.Protocol.send_priv_msg( + self.Protocol.send2socket(f":{dnickname} KICK {channel} {target_user} Following the vote, you are not welcome in {channel}") + else: + self.Protocol.send_priv_msg( nick_from=dnickname, - msg=f"User {self.Config.COLORS.bold}{target_user}{self.Config.COLORS.nogc} has {chan.vote_against} votes against and {chan.vote_for} votes for. For this reason, it\'ll remain in the channel", + msg=f"User {self.Config.COLORS.bold}{target_user}{self.Config.COLORS.nogc} has {votec.vote_against} votes against and {votec.vote_for} votes for. For this reason, it\'ll remain in the channel", channel=channel ) - - # Init the system - if self.init_vote_system(channel): - self.Protocol.send_priv_msg( + + if self.VoteKickManager.init_vote_system(channel): + self.Protocol.send_priv_msg( nick_from=dnickname, msg="System vote re initiated", channel=channel ) + return None + except Exception as err: self.Logs.error(f'{err}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option}') diff --git a/mods/votekick/threads.py b/mods/votekick/threads.py new file mode 100644 index 0000000..66d4edc --- /dev/null +++ b/mods/votekick/threads.py @@ -0,0 +1,40 @@ +from typing import TYPE_CHECKING + + +if TYPE_CHECKING: + from mods.votekick.mod_votekick import Votekick + +def timer_vote_verdict(uplink: 'Votekick', channel: str) -> None: + + dnickname = uplink.Config.SERVICE_NICKNAME + + if not uplink.VoteKickManager.is_vote_ongoing(channel): + return None + + votec = uplink.VoteKickManager.get_vote_channel_model(channel) + if votec: + target_user = uplink.User.get_nickname(votec.target_user) + + if votec.vote_for >= votec.vote_against and votec.vote_for != 0: + uplink.Protocol.send_priv_msg(nick_from=dnickname, + msg=f"User {uplink.Config.COLORS.bold}{target_user}{uplink.Config.COLORS.nogc} has {votec.vote_against} votes against and {votec.vote_for} votes for. For this reason, it\'ll be kicked from the channel", + channel=channel + ) + uplink.Protocol.send2socket(f":{dnickname} KICK {channel} {target_user} Following the vote, you are not welcome in {channel}") + else: + uplink.Protocol.send_priv_msg( + nick_from=dnickname, + msg=f"User {uplink.Config.COLORS.bold}{target_user}{uplink.Config.COLORS.nogc} has {votec.vote_against} votes against and {votec.vote_for} votes for. For this reason, it\'ll remain in the channel", + channel=channel + ) + + if uplink.VoteKickManager.init_vote_system(channel): + uplink.Protocol.send_priv_msg( + nick_from=dnickname, + msg="System vote re initiated", + channel=channel + ) + + return None + + return None \ No newline at end of file diff --git a/mods/votekick/utils.py b/mods/votekick/utils.py new file mode 100644 index 0000000..7f28bc9 --- /dev/null +++ b/mods/votekick/utils.py @@ -0,0 +1,74 @@ +from typing import TYPE_CHECKING + + +if TYPE_CHECKING: + from mods.votekick.mod_votekick import Votekick + +def add_vote_channel_to_database(uplink: 'Votekick', channel: str) -> bool: + """Adds a new channel to the votekick database if it doesn't already exist. + + This function checks if the specified channel is already registered in the + `votekick_channel` table. If not, it inserts a new entry with the current timestamp. + + Args: + uplink (Votekick): The main votekick system instance that provides access to utilities and database operations. + channel (str): The name of the channel to be added to the database. + + Returns: + bool: True if the channel was successfully inserted into the database. + False if the channel already exists or the insertion failed. + """ + current_datetime = uplink.Utils.get_sdatetime() + mes_donnees = {'channel': channel} + + response = uplink.Base.db_execute_query("SELECT id FROM votekick_channel WHERE channel = :channel", mes_donnees) + + is_channel_exist = response.fetchone() + + if is_channel_exist is None: + mes_donnees = {'datetime': current_datetime, 'channel': channel} + insert = uplink.Base.db_execute_query(f"INSERT INTO votekick_channel (datetime, channel) VALUES (:datetime, :channel)", mes_donnees) + if insert.rowcount > 0: + return True + else: + return False + else: + return False + +def delete_vote_channel_from_database(uplink: 'Votekick', channel: str) -> bool: + """Deletes a channel entry from the votekick database. + + This function removes the specified channel from the `votekick_channel` table + if it exists. It returns True if the deletion was successful. + + Args: + uplink (Votekick): The main votekick system instance used to execute the database operation. + channel (str): The name of the channel to be removed from the database. + + Returns: + bool: True if the channel was successfully deleted, False if no rows were affected. + """ + mes_donnes = {'channel': channel} + response = uplink.Base.db_execute_query("DELETE FROM votekick_channel WHERE channel = :channel", mes_donnes) + + affected_row = response.rowcount + + if affected_row > 0: + return True + else: + return False + +def join_saved_channels(uplink: 'Votekick') -> None: + + param = {'module_name': uplink.module_name} + result = uplink.Base.db_execute_query(f"SELECT id, channel_name FROM {uplink.Config.TABLE_CHANNEL} WHERE module_name = :module_name", param) + + channels = result.fetchall() + + for channel in channels: + id_, chan = channel + uplink.VoteKickManager.activate_new_channel(chan) + uplink.Protocol.send_sjoin(channel=chan) + uplink.Protocol.send2socket(f":{uplink.Config.SERVICE_NICKNAME} SAMODE {chan} +o {uplink.Config.SERVICE_NICKNAME}") + + return None \ No newline at end of file diff --git a/mods/votekick/votekick_manager.py b/mods/votekick/votekick_manager.py index 8694ffe..1eb7098 100644 --- a/mods/votekick/votekick_manager.py +++ b/mods/votekick/votekick_manager.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Literal, Optional from mods.votekick.schemas import VoteChannelModel if TYPE_CHECKING: @@ -38,7 +38,32 @@ class VotekickManager: return True return False - + + def init_vote_system(self, channel_name: str) -> bool: + """Initializes or resets the votekick system for a given channel. + + This method clears the current target, voter list, and vote counts + in preparation for a new votekick session. + + Args: + channel_name (str): The name of the channel for which the votekick system should be initialized. + + Returns: + bool: True if the votekick system was successfully initialized, False if the channel is not found. + """ + votec = self.get_vote_channel_model(channel_name) + + if votec is None: + self.Logs.debug(f"[VOTEKICK MANAGER] The channel ({channel_name}) is not active!") + return False + + votec.target_user = '' + votec.voter_users = [] + votec.vote_for = 0 + votec.vote_against = 0 + self.Logs.debug(f"[VOTEKICK MANAGER] The channel ({channel_name}) has been successfully initialized!") + return True + def get_vote_channel_model(self, channel_name: str) -> Optional[VoteChannelModel]: """Get Vote Channel Object model @@ -96,3 +121,43 @@ class VotekickManager: self.Logs.debug(f'[VOTEKICK MANAGER] {channel_name} is activated but there is no ongoing vote!') return False + + def action_vote(self, channel_name: str, nickname: str, action: Literal['+', '-']) -> bool: + """ + Registers a vote (for or against) in an active votekick session on a channel. + + Args: + channel_name (str): The name of the channel where the votekick session is active. + nickname (str): The nickname of the user casting the vote. + action (Literal['+', '-']): The vote action. Use '+' to vote for kicking, '-' to vote against. + + Returns: + bool: True if the vote was successfully registered, False otherwise. + This can fail if: + - The action is invalid (not '+' or '-') + - The user has already voted + - The channel has no active votekick session + """ + if action not in ['+', '-']: + self.Logs.debug(f"[VOTEKICK MANAGER] The action must be + or - while you have provided ({action})") + return False + votec = self.get_vote_channel_model(channel_name) + + if votec: + client_obj = self.uplink.User.get_User(votec.target_user) + client_to_punish = votec.target_user if client_obj is None else client_obj.nickname + if nickname in votec.voter_users: + self.Logs.debug(f"[VOTEKICK MANAGER] This nickname ({nickname}) has already voted for ({client_to_punish})") + return False + else: + if action == '+': + votec.vote_for += 1 + elif action == '-': + votec.vote_against += 1 + + votec.voter_users.append(nickname) + self.Logs.debug(f"[VOTEKICK MANAGER] The ({nickname}) has voted to ban ({client_to_punish})") + return True + else: + self.Logs.debug(f"[VOTEKICK MANAGER] This channel {channel_name} is not active!") + return False From a15a5b1026aec88421fb7baff8838673bea1d28c Mon Sep 17 00:00:00 2001 From: adator <85586985+adator85@users.noreply.github.com> Date: Sun, 17 Aug 2025 22:48:35 +0200 Subject: [PATCH 17/20] Updating command module --- mods/command/mod_command.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mods/command/mod_command.py b/mods/command/mod_command.py index daadbbc..06ff30e 100644 --- a/mods/command/mod_command.py +++ b/mods/command/mod_command.py @@ -316,9 +316,10 @@ class Command: # automode set adator +o #channel try: option: str = str(cmd[1]).lower() + allowed_modes: list[str] = self.Loader.Settings.PROTOCTL_PREFIX # ['q','a','o','h','v'] + match option: case 'set': - allowed_modes: list[str] = self.Base.Settings.PROTOCTL_PREFIX # ['q','a','o','h','v'] if len(cmd) < 5: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {command.upper()} [nickname] [+/-mode] [#channel]") @@ -350,7 +351,7 @@ class Command: if db_result is not None: if sign == '+': - db_data = {"updated_on": self.MainUtils.get_datetime(), "nickname": nickname, "channel": chan, "mode": mode} + db_data = {"updated_on": self.MainUtils.get_sdatetime(), "nickname": nickname, "channel": chan, "mode": mode} db_result = self.Base.db_execute_query(query="UPDATE command_automode SET mode = :mode, updated_on = :updated_on WHERE nickname = :nickname and channel = :channel", params=db_data) if db_result.rowcount > 0: @@ -368,7 +369,7 @@ class Command: # Instert a new automode if sign == '+': - db_data = {"created_on": self.Base.get_datetime(), "updated_on": self.Base.get_datetime(), "nickname": nickname, "channel": chan, "mode": mode} + db_data = {"created_on": self.MainUtils.get_sdatetime(), "updated_on": self.MainUtils.get_sdatetime(), "nickname": nickname, "channel": chan, "mode": mode} db_query = self.Base.db_execute_query( query="INSERT INTO command_automode (created_on, updated_on, nickname, channel, mode) VALUES (:created_on, :updated_on, :nickname, :channel, :mode)", params=db_data @@ -402,7 +403,7 @@ class Command: except IndexError: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {command.upper()} SET [nickname] [+/-mode] [#channel]") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {command.upper()} LIST") - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"[AUTOMODES AVAILABLE] are {' / '.join(self.Base.Settings.PROTOCTL_PREFIX)}") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"[AUTOMODES AVAILABLE] are {' / '.join(self.Loader.Settings.PROTOCTL_PREFIX)}") except Exception as err: self.Logs.error(f"General Error: {err}") From 4c327940ddea1ce45a6fcc3129db74c07188bbd4 Mon Sep 17 00:00:00 2001 From: adator <85586985+adator85@users.noreply.github.com> Date: Tue, 19 Aug 2025 03:06:56 +0200 Subject: [PATCH 18/20] Adding new classes, todo: fix jsonrpc module --- core/classes/config.py | 21 +- core/irc.py | 21 +- core/loader.py | 2 +- core/logs.py | 10 +- defender.py | 3 +- mods/command/mod_command.py | 431 +++--------------------------------- mods/command/utils.py | 237 ++++++++++++++++++++ mods/jsonrpc/mod_jsonrpc.py | 96 +++++--- version.json | 2 +- 9 files changed, 373 insertions(+), 450 deletions(-) create mode 100644 mods/command/utils.py diff --git a/core/classes/config.py b/core/classes/config.py index 1958200..ca2f7ce 100644 --- a/core/classes/config.py +++ b/core/classes/config.py @@ -3,13 +3,13 @@ from sys import exit from os import sep from typing import Union from core.definition import MConfig - +from logging import Logger class Configuration: - def __init__(self) -> None: - + def __init__(self, logs: Logger) -> None: + self.Logs = logs self.ConfigObject: MConfig = self.__load_service_configuration() return None @@ -22,18 +22,18 @@ class Configuration: return configuration except FileNotFoundError as fe: - print(f'FileNotFound: {fe}') - print('Configuration file not found please create config/configuration.json') + self.Logs.error(f'FileNotFound: {fe}') + self.Logs.error('Configuration file not found please create config/configuration.json') exit(0) except KeyError as ke: - print(f'Key Error: {ke}') - print('The key must be defined in core/configuration.json') + self.Logs.error(f'Key Error: {ke}') + self.Logs.error('The key must be defined in core/configuration.json') def __load_service_configuration(self) -> MConfig: try: import_config = self.__load_json_service_configuration() - Model_keys = MConfig().__dict__ + Model_keys = MConfig().to_dict() model_key_list: list = [] json_config_key_list: list = [] @@ -46,12 +46,13 @@ class Configuration: for json_conf in json_config_key_list: if not json_conf in model_key_list: import_config.pop(json_conf, None) - print(f"\!/ The key {json_conf} is not expected, it has been removed from the system ! please remove it from configuration.json file \!/") + self.Logs.warning(f"[!] The key {json_conf} is not expected, it has been removed from the system ! please remove it from configuration.json file [!]") ConfigObject: MConfig = MConfig( **import_config ) return ConfigObject + except TypeError as te: - print(te) \ No newline at end of file + self.Logs.error(te) \ No newline at end of file diff --git a/core/irc.py b/core/irc.py index 6995e25..2ab5a67 100644 --- a/core/irc.py +++ b/core/irc.py @@ -233,9 +233,10 @@ class Irc: self.Logs.warning('--* Waiting for socket to close ...') # Reload configuration + self.Loader.Logs = self.Loader.LoggingModule.ServiceLogging().get_logger() self.Logs.debug('Reloading configuration') - self.Config = self.Loader.ConfModule.Configuration().ConfigObject - self.Base = self.Loader.BaseModule.Base(self.Config, self.Settings) + self.Config = self.Loader.ConfModule.Configuration(self.Logs).ConfigObject + self.Base = self.Loader.BaseModule.Base(self.Loader) self.Protocol = Protocol(self.Config.SERVEUR_PROTOCOL, ircInstance).Protocol self.init_service_user() @@ -1488,18 +1489,23 @@ class Irc: self.User.UID_DB.clear() # Clear User Object self.Channel.UID_CHANNEL_DB.clear() # Clear Channel Object - self.Base.delete_logger(self.Config.LOGGING_NAME) + self.Base.garbage_collector_thread() self.Protocol.send_squit(server_id=self.Config.SERVEUR_ID, server_link=self.Config.SERVEUR_LINK, reason=final_reason) self.Logs.info(f'Redémarrage du server {dnickname}') self.loaded_classes.clear() self.Config.DEFENDER_RESTART = 1 # Set restart status to 1 saying that the service will restart self.Config.DEFENDER_INIT = 1 # set init to 1 saying that the service will be re initiated + self.Loader.ServiceLogging.remove_logger() case 'rehash': + self.Loader.ServiceLogging.remove_logger() + self.Loader.ServiceLogging = self.Loader.LoggingModule.ServiceLogging() + self.Loader.Logs = self.Loader.ServiceLogging.get_logger() + need_a_restart = ["SERVEUR_ID"] restart_flag = False - Config_bakcup = self.Config.__dict__.copy() + Config_bakcup = self.Config.to_dict().copy() serveur_id = self.Config.SERVEUR_ID service_nickname = self.Config.SERVICE_NICKNAME hsid = self.Config.HSID @@ -1519,7 +1525,7 @@ class Irc: importlib.reload(mod_definition) importlib.reload(mod_config) - self.Config = self.Loader.ConfModule.Configuration().ConfigObject + self.Config = self.Loader.ConfModule.Configuration(self.Loader.Logs).ConfigObject self.Config.HSID = hsid self.Config.DEFENDER_INIT = defender_init self.Config.DEFENDER_RESTART = defender_restart @@ -1529,7 +1535,7 @@ class Irc: importlib.reload(mod_base) conf_bkp_dict: dict = Config_bakcup - config_dict: dict = self.Config.__dict__ + config_dict: dict = self.Config.to_dict() for key, value in conf_bkp_dict.items(): if config_dict[key] != value and key != 'COLORS': @@ -1548,9 +1554,6 @@ class Irc: self.Config.SERVEUR_ID = serveur_id self.Protocol.send_priv_msg(nick_from=self.Config.SERVICE_NICKNAME, msg='You need to restart defender !', channel=self.Config.SERVICE_CHANLOG) - self.Loader.ServiceLogging.remove_logger() - self.Loader.ServiceLogging = self.Loader.LoggingModule.ServiceLogging() - self.Loader.Logs = self.Loader.ServiceLogging.get_logger() self.Base = self.Loader.BaseModule.Base(self.Loader) importlib.reload(mod_unreal6) diff --git a/core/loader.py b/core/loader.py index f502417..6a75451 100644 --- a/core/loader.py +++ b/core/loader.py @@ -28,7 +28,7 @@ class Loader: self.Settings: settings.Settings = settings.Settings() - self.Config: df.MConfig = self.ConfModule.Configuration().ConfigObject + self.Config: df.MConfig = self.ConfModule.Configuration(self.Logs).ConfigObject self.Base: base_module.Base = self.BaseModule.Base(self) diff --git a/core/logs.py b/core/logs.py index b100bdc..bef6ab2 100644 --- a/core/logs.py +++ b/core/logs.py @@ -1,5 +1,6 @@ import logging from os import path, makedirs, sep +from typing import Optional class ServiceLogging: @@ -25,10 +26,13 @@ class ServiceLogging: return logs_obj - def remove_logger(self) -> None: + def remove_logger(self, logger_name: Optional[str] = None) -> None: + + if logger_name is None: + logger_name = self.LOGGING_NAME # Récupérer le logger - logger = logging.getLogger(self.LOGGING_NAME) + logger = logging.getLogger(logger_name) # Retirer tous les gestionnaires du logger et les fermer for handler in logger.handlers[:]: # Utiliser une copie de la liste @@ -37,7 +41,7 @@ class ServiceLogging: handler.close() # Supprimer le logger du dictionnaire global - logging.Logger.manager.loggerDict.pop(self.LOGGING_NAME, None) + logging.Logger.manager.loggerDict.pop(logger_name, None) return None diff --git a/defender.py b/defender.py index cbe0869..9e5e200 100644 --- a/defender.py +++ b/defender.py @@ -1,7 +1,7 @@ from core import installation ############################################# -# @Version : 6 # +# @Version : 6.2 # # Requierements : # # Python3.10 or higher # # SQLAlchemy, requests, psutil # @@ -14,7 +14,6 @@ try: from core.loader import Loader from core.irc import Irc - # loader = Loader() ircInstance = Irc(Loader()) ircInstance.init_irc(ircInstance) diff --git a/mods/command/mod_command.py b/mods/command/mod_command.py index 06ff30e..75d77c6 100644 --- a/mods/command/mod_command.py +++ b/mods/command/mod_command.py @@ -1,5 +1,6 @@ from typing import Optional, TYPE_CHECKING from dataclasses import dataclass +import mods.command.utils as utils if TYPE_CHECKING: from core.irc import Irc @@ -49,6 +50,9 @@ class Command: # Add Channel object to the module (Mandatory) self.Channel = ircInstance.Channel + # Module Utils + self.mod_utils = utils + self.Irc.build_command(1, self.module_name, 'join', 'Join a channel') self.Irc.build_command(1, self.module_name, 'assign', 'Assign a user to a role or task') self.Irc.build_command(1, self.module_name, 'part', 'Leave a channel') @@ -312,94 +316,8 @@ class Command: match command: case 'automode': - # automode set nickname [+/-mode] #channel - # automode set adator +o #channel try: - option: str = str(cmd[1]).lower() - allowed_modes: list[str] = self.Loader.Settings.PROTOCTL_PREFIX # ['q','a','o','h','v'] - - match option: - case 'set': - - if len(cmd) < 5: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {command.upper()} [nickname] [+/-mode] [#channel]") - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"AutoModes available: {' / '.join(allowed_modes)}") - return None - - # userObj: MUser = self.User.get_User(str(cmd[2])) - nickname = str(cmd[2]) - mode = str(cmd[3]) - chan: str = str(cmd[4]).lower() if self.Channel.is_valid_channel(cmd[4]) else None - sign = mode[0] if mode.startswith( ('+', '-')) else None - clean_mode = mode[1:] if len(mode) > 0 else None - - if sign is None: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="You must provide the flag mode + or -") - return None - - if clean_mode not in allowed_modes: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"You should use one of those modes {' / '.join(allowed_modes)}") - return None - - if chan is None: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"You should use one of those modes {' / '.join(allowed_modes)}") - return None - - db_data: dict[str, str] = {"nickname": nickname, "channel": chan} - db_query = self.Base.db_execute_query(query="SELECT id FROM command_automode WHERE nickname = :nickname and channel = :channel", params=db_data) - db_result = db_query.fetchone() - - if db_result is not None: - if sign == '+': - db_data = {"updated_on": self.MainUtils.get_sdatetime(), "nickname": nickname, "channel": chan, "mode": mode} - db_result = self.Base.db_execute_query(query="UPDATE command_automode SET mode = :mode, updated_on = :updated_on WHERE nickname = :nickname and channel = :channel", - params=db_data) - if db_result.rowcount > 0: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Automode {mode} edited for {nickname} in {chan}") - elif sign == '-': - db_data = {"nickname": nickname, "channel": chan, "mode": f"+{clean_mode}"} - db_result = self.Base.db_execute_query(query="DELETE FROM command_automode WHERE nickname = :nickname and channel = :channel and mode = :mode", - params=db_data) - if db_result.rowcount > 0: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Automode {mode} deleted for {nickname} in {chan}") - else: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"The mode [{mode}] has not been found for {nickname} in channel {chan}") - - return None - - # Instert a new automode - if sign == '+': - db_data = {"created_on": self.MainUtils.get_sdatetime(), "updated_on": self.MainUtils.get_sdatetime(), "nickname": nickname, "channel": chan, "mode": mode} - db_query = self.Base.db_execute_query( - query="INSERT INTO command_automode (created_on, updated_on, nickname, channel, mode) VALUES (:created_on, :updated_on, :nickname, :channel, :mode)", - params=db_data - ) - - if db_query.rowcount > 0: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Automode {mode} applied to {nickname} in {chan}") - if self.Channel.is_user_present_in_channel(chan, self.User.get_uid(nickname)): - self.Protocol.send2socket(f":{service_id} MODE {chan} {mode} {nickname}") - else: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"AUTOMODE {mode} cannot be added to {nickname} in {chan} because it doesn't exist") - - case 'list': - db_query: CursorResult = self.Base.db_execute_query("SELECT nickname, channel, mode FROM command_automode") - db_results: Sequence[Row] = db_query.fetchall() - - if not db_results: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, - msg="There is no automode to display.") - - for db_result in db_results: - db_nickname, db_channel, db_mode = db_result - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, - msg=f"Nickname: {db_nickname} | Channel: {db_channel} | Mode: {db_mode}") - - case _: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {command.upper()} SET [nickname] [+/-mode] [#channel]") - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {command.upper()} LIST") - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"[AUTOMODES AVAILABLE] are {' / '.join(allowed_modes)}") - + self.mod_utils.set_automode(self, cmd, fromuser) except IndexError: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {command.upper()} SET [nickname] [+/-mode] [#channel]") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {command.upper()} LIST") @@ -409,196 +327,65 @@ class Command: case 'deopall': try: - self.Protocol.send2socket(f":{service_id} SVSMODE {fromchannel} -o") - - except IndexError as ie: - self.Logs.warning(f'_hcmd OP: {str(ie)}') + self.mod_utils.set_deopall(self, fromchannel) except Exception as err: - self.Logs.warning(f'Unknown Error: {str(err)}') + self.Logs.error(f'Unknown Error: {str(err)}') case 'devoiceall': try: - self.Protocol.send2socket(f":{service_id} SVSMODE {fromchannel} -v") - - except IndexError as e: - self.Logs.warning(f'_hcmd OP: {str(e)}') + self.mod_utils.set_devoiceall(self, fromchannel) except Exception as err: - self.Logs.warning(f'Unknown Error: {str(err)}') + self.Logs.error(f'Unknown Error: {str(err)}') case 'voiceall': try: - chan_info = self.Channel.get_channel(fromchannel) - set_mode = 'v' - mode:str = '' - users:str = '' - uids_split = [chan_info.uids[i:i + 6] for i in range(0, len(chan_info.uids), 6)] - - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} +{set_mode} {dnickname}") - for uid in uids_split: - for i in range(0, len(uid)): - mode += set_mode - users += f'{self.User.get_nickname(self.MainUtils.clean_uid(uid[i]))} ' - if i == len(uid) - 1: - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} +{mode} {users}") - mode = '' - users = '' - except IndexError as e: - self.Logs.warning(f'_hcmd OP: {str(e)}') + self.mod_utils.set_mode_to_all(self, fromchannel, '+', 'v') except Exception as err: self.Logs.warning(f'Unknown Error: {str(err)}') case 'opall': try: - chan_info = self.Channel.get_channel(fromchannel) - set_mode = 'o' - mode:str = '' - users:str = '' - uids_split = [chan_info.uids[i:i + 6] for i in range(0, len(chan_info.uids), 6)] - - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} +{set_mode} {dnickname}") - for uid in uids_split: - for i in range(0, len(uid)): - mode += set_mode - users += f'{self.User.get_nickname(self.MainUtils.clean_uid(uid[i]))} ' - if i == len(uid) - 1: - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} +{mode} {users}") - mode = '' - users = '' - except IndexError as e: - self.Logs.warning(f'_hcmd OP: {str(e)}') + self.mod_utils.set_mode_to_all(self, fromchannel, '+', 'o') except Exception as err: self.Logs.warning(f'Unknown Error: {str(err)}') 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.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} op [#SALON] [NICKNAME]") - return False - - if len(cmd) == 1: - self.Protocol.send2socket(f":{dnickname} MODE {fromchannel} +o {fromuser}") - return True - - # deop nickname - if len(cmd) == 2: - nickname = cmd[1] - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} +o {nickname}") - return True - - nickname = cmd[2] - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} +o {nickname}") - + self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '+o') except IndexError as e: - self.Logs.warning(f'_hcmd OP: {str(e)}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} op [#SALON] [NICKNAME]") except Exception as err: self.Logs.warning(f'Unknown Error: {str(err)}') case 'deop': - # /mode #channel -o user - # .deop #channel user try: - if fromchannel is None: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} deop [#SALON] [NICKNAME]") - return False - - if len(cmd) == 1: - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} -o {fromuser}") - return True - - # deop nickname - if len(cmd) == 2: - nickname = cmd[1] - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} -o {nickname}") - return True - - nickname = cmd[2] - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} -o {nickname}") - + self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '-o') except IndexError as e: - self.Logs.warning(f'_hcmd DEOP: {str(e)}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} deop [#SALON] [NICKNAME]") except Exception as err: self.Logs.warning(f'Unknown Error: {str(err)}') case 'owner': - # /mode #channel +q user - # .owner #channel user try: - if fromchannel is None: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} owner [#SALON] [NICKNAME]") - return False - - if len(cmd) == 1: - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} +q {fromuser}") - return True - - # owner nickname - if len(cmd) == 2: - nickname = cmd[1] - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} +q {nickname}") - return True - - nickname = cmd[2] - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} +q {nickname}") + self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '+q') except IndexError as e: - self.Logs.warning(f'_hcmd OWNER: {str(e)}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} owner [#SALON] [NICKNAME]") except Exception as err: self.Logs.warning(f'Unknown Error: {str(err)}') case 'deowner': - # /mode #channel -q user - # .deowner #channel user try: - if fromchannel is None: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} deowner [#SALON] [NICKNAME]") - return False - - if len(cmd) == 1: - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} -q {fromuser}") - return True - - # deowner nickname - if len(cmd) == 2: - nickname = cmd[1] - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} -q {nickname}") - return True - - nickname = cmd[2] - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} -q {nickname}") + self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '-q') except IndexError as e: - self.Logs.warning(f'_hcmd DEOWNER: {str(e)}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} deowner [#SALON] [NICKNAME]") except Exception as err: self.Logs.warning(f'Unknown Error: {str(err)}') case 'protect': - # /mode #channel +a user - # .protect #channel user try: - if fromchannel is None: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON] [NICKNAME]") - return False - - if len(cmd) == 1: - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} +a {fromuser}") - return True - - # deowner nickname - if len(cmd) == 2: - nickname = cmd[1] - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} +a {nickname}") - return True - - nickname = cmd[2] - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} +a {nickname}") + self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '+a') except IndexError as e: self.Logs.warning(f'_hcmd DEOWNER: {str(e)}') @@ -607,25 +394,8 @@ class Command: self.Logs.warning(f'Unknown Error: {str(err)}') case 'deprotect': - # /mode #channel -a user - # .deprotect #channel user try: - if fromchannel is None: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON] [NICKNAME]") - return False - - if len(cmd) == 1: - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} -a {fromuser}") - return True - - # deowner nickname - if len(cmd) == 2: - nickname = cmd[1] - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} -a {nickname}") - return True - - nickname = cmd[2] - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} -a {nickname}") + self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '-a') except IndexError as e: self.Logs.warning(f'_hcmd DEOWNER: {str(e)}') @@ -634,125 +404,42 @@ class Command: self.Logs.warning(f'Unknown Error: {str(err)}') case 'halfop': - # /mode #channel +h user - # .halfop #channel user try: - if fromchannel is None: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} halfop [#SALON] [NICKNAME]") - return False - - if len(cmd) == 1: - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} +h {fromuser}") - return True - - # deop nickname - if len(cmd) == 2: - nickname = cmd[1] - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} +h {nickname}") - return True - - nickname = cmd[2] - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} +h {nickname}") + self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '+h') except IndexError as e: - self.Logs.warning(f'_hcmd halfop: {str(e)}') - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} halfop [#SALON] [NICKNAME]") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} {command} [#SALON] [NICKNAME]") except Exception as err: self.Logs.warning(f'Unknown Error: {str(err)}') case 'dehalfop': - # /mode #channel -h user - # .dehalfop #channel user try: - if fromchannel is None: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} dehalfop [#SALON] [NICKNAME]") - return False - - if len(cmd) == 1: - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} -h {fromuser}") - return True - - # dehalfop nickname - if len(cmd) == 2: - nickname = cmd[1] - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} -h {nickname}") - return True - - nickname = cmd[2] - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} -h {nickname}") + self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '-h') except IndexError as e: - self.Logs.warning(f'_hcmd DEHALFOP: {str(e)}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} dehalfop [#SALON] [NICKNAME]") except Exception as err: self.Logs.warning(f'Unknown Error: {str(err)}') case 'voice': - # /mode #channel +v user - # .voice #channel user try: - if fromchannel is None: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} voice [#SALON] [NICKNAME]") - return False - - if len(cmd) == 1: - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} +v {fromuser}") - return True - - # voice nickname - if len(cmd) == 2: - nickname = cmd[1] - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} +v {nickname}") - return True - - nickname = cmd[2] - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} +v {nickname}") - + self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '+v') except IndexError as e: - self.Logs.warning(f'_hcmd VOICE: {str(e)}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} voice [#SALON] [NICKNAME]") except Exception as err: self.Logs.warning(f'Unknown Error: {str(err)}') case 'devoice': - # /mode #channel -v user - # .devoice #channel user try: - if fromchannel is None: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} devoice [#SALON] [NICKNAME]") - return False - - if len(cmd) == 1: - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} -v {fromuser}") - return True - - # dehalfop nickname - if len(cmd) == 2: - nickname = cmd[1] - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} -v {nickname}") - return True - - nickname = cmd[2] - self.Protocol.send2socket(f":{service_id} MODE {fromchannel} -v {nickname}") - + self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '-v') except IndexError as e: - self.Logs.warning(f'_hcmd DEVOICE: {str(e)}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} devoice [#SALON] [NICKNAME]") except Exception as err: self.Logs.warning(f'Unknown Error: {str(err)}') case 'ban': - # .ban #channel nickname try: - sentchannel = str(cmd[1]) if self.Channel.is_valid_channel(cmd[1]) else None - if sentchannel is None: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON] [NICKNAME]") - return False - - nickname = cmd[2] - - self.Protocol.send2socket(f":{service_id} MODE {sentchannel} +b {nickname}!*@*") - self.Logs.debug(f'{fromuser} has banned {nickname} from {sentchannel}') + self.mod_utils.set_ban(self, cmd, '+', fromuser) except IndexError as e: self.Logs.warning(f'_hcmd BAN: {str(e)}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON] [NICKNAME]") @@ -760,97 +447,43 @@ class Command: self.Logs.warning(f'Unknown Error: {str(err)}') case 'unban': - # .unban #channel nickname try: - sentchannel = str(cmd[1]) if self.Channel.is_valid_channel(cmd[1]) else None - if sentchannel is None: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} ban [#SALON] [NICKNAME]") - return False - nickname = cmd[2] - - self.Protocol.send2socket(f":{service_id} MODE {sentchannel} -b {nickname}!*@*") - self.Logs.debug(f'{fromuser} has unbanned {nickname} from {sentchannel}') - + self.mod_utils.set_ban(self, cmd, '-', fromuser) except IndexError as e: self.Logs.warning(f'_hcmd UNBAN: {str(e)}') - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} unban [#SALON] [NICKNAME]") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON] [NICKNAME]") except Exception as err: self.Logs.warning(f'Unknown Error: {str(err)}') case 'kick': - # .kick #channel nickname reason try: - sentchannel = str(cmd[1]) if self.Channel.is_valid_channel(cmd[1]) else None - if sentchannel is None: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} ban [#SALON] [NICKNAME]") - return False - nickname = cmd[2] - final_reason = ' '.join(cmd[3:]) - - self.Protocol.send2socket(f":{service_id} KICK {sentchannel} {nickname} {final_reason}") - self.Logs.debug(f'{fromuser} has kicked {nickname} from {sentchannel} : {final_reason}') - + self.mod_utils.set_kick(self, cmd, fromuser) except IndexError as e: self.Logs.warning(f'_hcmd KICK: {str(e)}') - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} kick [#SALON] [NICKNAME] [REASON]") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON] [NICKNAME] [REASON]") except Exception as err: self.Logs.warning(f'Unknown Error: {str(err)}') case 'kickban': - # .kickban #channel nickname reason try: - sentchannel = str(cmd[1]) if self.Channel.is_valid_channel(cmd[1]) else None - if sentchannel is None: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} ban [#SALON] [NICKNAME]") - return False - nickname = cmd[2] - final_reason = ' '.join(cmd[3:]) - - self.Protocol.send2socket(f":{service_id} KICK {sentchannel} {nickname} {final_reason}") - self.Protocol.send2socket(f":{service_id} MODE {sentchannel} +b {nickname}!*@*") - self.Logs.debug(f'{fromuser} has kicked and banned {nickname} from {sentchannel} : {final_reason}') - + self.mod_utils.set_kickban(self, cmd, fromuser) except IndexError as e: self.Logs.warning(f'_hcmd KICKBAN: {str(e)}') - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} kickban [#SALON] [NICKNAME] [REASON]") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON] [NICKNAME] [REASON]") except Exception as err: self.Logs.warning(f'Unknown Error: {str(err)}') case 'join' | 'assign': - try: - sent_channel = str(cmd[1]) if self.Channel.is_valid_channel(cmd[1]) else None - if sent_channel is None: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"{self.Config.SERVICE_PREFIX}JOIN #channel") - return False - - # self.Protocol.send2socket(f':{service_id} JOIN {sent_channel}') - self.Protocol.send_join_chan(uidornickname=dnickname,channel=sent_channel) - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" {dnickname} JOINED {sent_channel}") - self.Channel.db_query_channel('add', self.module_name, sent_channel) - + self.mod_utils.set_assign_channel_to_service(self, cmd, fromuser) except IndexError as ie: - self.Logs.error(f'{ie}') + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON]") except Exception as err: self.Logs.warning(f'Unknown Error: {str(err)}') case 'part' | 'unassign': - try: - sent_channel = str(cmd[1]) if self.Channel.is_valid_channel(cmd[1]) else None - if sent_channel is None: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"{self.Config.SERVICE_PREFIX}PART #channel") - return False - - if sent_channel == dchanlog: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" {dnickname} CAN'T LEFT {sent_channel} AS IT IS LOG CHANNEL") - return False - - self.Protocol.send_part_chan(uidornickname=dnickname, channel=sent_channel) - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" {dnickname} LEFT {sent_channel}") - - self.Channel.db_query_channel('del', self.module_name, sent_channel) - + self.mod_utils.set_unassign_channel_to_service(self, cmd, fromuser) except IndexError as ie: self.Logs.error(f'{ie}') except Exception as err: diff --git a/mods/command/utils.py b/mods/command/utils.py new file mode 100644 index 0000000..c01e70b --- /dev/null +++ b/mods/command/utils.py @@ -0,0 +1,237 @@ +from typing import TYPE_CHECKING, Literal, Optional + +if TYPE_CHECKING: + from mods.command.mod_command import Command + + +def set_automode(uplink: 'Command', cmd: list[str], client: str) -> None: + + command: str = str(cmd[0]).lower() + option: str = str(cmd[1]).lower() + allowed_modes: list[str] = uplink.Loader.Settings.PROTOCTL_PREFIX # ['q','a','o','h','v'] + dnickname = uplink.Config.SERVICE_NICKNAME + service_id = uplink.Config.SERVICE_ID + fromuser = client + + match option: + case 'set': + if len(cmd) < 5: + uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {command.upper()} [nickname] [+/-mode] [#channel]") + uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"AutoModes available: {' / '.join(allowed_modes)}") + return None + + nickname = str(cmd[2]) + mode = str(cmd[3]) + chan: str = str(cmd[4]).lower() if uplink.Channel.is_valid_channel(cmd[4]) else None + sign = mode[0] if mode.startswith( ('+', '-')) else None + clean_mode = mode[1:] if len(mode) > 0 else None + + if sign is None: + uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="You must provide the flag mode + or -") + return None + + if clean_mode not in allowed_modes: + uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"You should use one of those modes {' / '.join(allowed_modes)}") + return None + + if chan is None: + uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"You should use one of those modes {' / '.join(allowed_modes)}") + return None + + db_data: dict[str, str] = {"nickname": nickname, "channel": chan} + db_query = uplink.Base.db_execute_query(query="SELECT id FROM command_automode WHERE nickname = :nickname and channel = :channel", params=db_data) + db_result = db_query.fetchone() + + if db_result is not None: + if sign == '+': + db_data = {"updated_on": uplink.MainUtils.get_sdatetime(), "nickname": nickname, "channel": chan, "mode": mode} + db_result = uplink.Base.db_execute_query(query="UPDATE command_automode SET mode = :mode, updated_on = :updated_on WHERE nickname = :nickname and channel = :channel", + params=db_data) + if db_result.rowcount > 0: + uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Automode {mode} edited for {nickname} in {chan}") + elif sign == '-': + db_data = {"nickname": nickname, "channel": chan, "mode": f"+{clean_mode}"} + db_result = uplink.Base.db_execute_query(query="DELETE FROM command_automode WHERE nickname = :nickname and channel = :channel and mode = :mode", + params=db_data) + if db_result.rowcount > 0: + uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Automode {mode} deleted for {nickname} in {chan}") + else: + uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"The mode [{mode}] has not been found for {nickname} in channel {chan}") + + return None + + # Instert a new automode + if sign == '+': + db_data = {"created_on": uplink.MainUtils.get_sdatetime(), "updated_on": uplink.MainUtils.get_sdatetime(), "nickname": nickname, "channel": chan, "mode": mode} + db_query = uplink.Base.db_execute_query( + query="INSERT INTO command_automode (created_on, updated_on, nickname, channel, mode) VALUES (:created_on, :updated_on, :nickname, :channel, :mode)", + params=db_data + ) + + if db_query.rowcount > 0: + uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Automode {mode} applied to {nickname} in {chan}") + if uplink.Channel.is_user_present_in_channel(chan, uplink.User.get_uid(nickname)): + uplink.Protocol.send2socket(f":{service_id} MODE {chan} {mode} {nickname}") + else: + uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"AUTOMODE {mode} cannot be added to {nickname} in {chan} because it doesn't exist") + + case 'list': + db_query = uplink.Base.db_execute_query("SELECT nickname, channel, mode FROM command_automode") + db_results = db_query.fetchall() + + if not db_results: + uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, + msg="There is no automode to display.") + + for db_result in db_results: + db_nickname, db_channel, db_mode = db_result + uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, + msg=f"Nickname: {db_nickname} | Channel: {db_channel} | Mode: {db_mode}") + + case _: + uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {command.upper()} SET [nickname] [+/-mode] [#channel]") + uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {command.upper()} LIST") + uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"[AUTOMODES AVAILABLE] are {' / '.join(allowed_modes)}") + +def set_deopall(uplink: 'Command', channel_name: str) -> None: + + service_id = uplink.Config.SERVICE_ID + uplink.Protocol.send2socket(f":{service_id} SVSMODE {channel_name} -o") + return None + +def set_devoiceall(uplink: 'Command', channel_name: str) -> None: + + service_id = uplink.Config.SERVICE_ID + uplink.Protocol.send2socket(f":{service_id} SVSMODE {channel_name} -v") + return None + +def set_mode_to_all(uplink: 'Command', channel_name: str, action: Literal['+', '-'], pmode: str) -> None: + + chan_info = uplink.Channel.get_channel(channel_name) + service_id = uplink.Config.SERVICE_ID + dnickname = uplink.Config.SERVICE_NICKNAME + set_mode = pmode + mode:str = '' + users:str = '' + uids_split = [chan_info.uids[i:i + 6] for i in range(0, len(chan_info.uids), 6)] + + uplink.Protocol.send2socket(f":{service_id} MODE {channel_name} {action}{set_mode} {dnickname}") + for uid in uids_split: + for i in range(0, len(uid)): + mode += set_mode + users += f'{uplink.User.get_nickname(uplink.MainUtils.clean_uid(uid[i]))} ' + if i == len(uid) - 1: + uplink.Protocol.send2socket(f":{service_id} MODE {channel_name} {action}{mode} {users}") + mode = '' + users = '' + +def set_operation(uplink: 'Command', cmd: list[str], channel_name: Optional[str], client: str, mode: str) -> None: + + dnickname = uplink.Config.SERVICE_NICKNAME + service_id = uplink.Config.SERVICE_ID + if channel_name is None: + uplink.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f" Right command : /msg {dnickname} {mode} [#SALON] [NICKNAME]") + return False + + if len(cmd) == 1: + uplink.Protocol.send2socket(f":{dnickname} MODE {channel_name} {mode} {client}") + return None + + # deop nickname + if len(cmd) == 2: + nickname = cmd[1] + uplink.Protocol.send2socket(f":{service_id} MODE {channel_name} {mode} {nickname}") + return None + + nickname = cmd[2] + uplink.Protocol.send2socket(f":{service_id} MODE {channel_name} {mode} {nickname}") + return None + +def set_ban(uplink: 'Command', cmd: list[str], action: Literal['+', '-'], client: str) -> None: + + command = str(cmd[0]) + dnickname = uplink.Config.SERVICE_NICKNAME + service_id = uplink.Config.SERVICE_ID + sentchannel = str(cmd[1]) if uplink.Channel.is_valid_channel(cmd[1]) else None + + if sentchannel is None: + uplink.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON] [NICKNAME]") + return None + + nickname = cmd[2] + + uplink.Protocol.send2socket(f":{service_id} MODE {sentchannel} {action}b {nickname}!*@*") + uplink.Logs.debug(f'{client} has banned {nickname} from {sentchannel}') + return None + +def set_kick(uplink: 'Command', cmd: list[str], client: str) -> None: + + command = str(cmd[0]) + dnickname = uplink.Config.SERVICE_NICKNAME + service_id = uplink.Config.SERVICE_ID + + sentchannel = str(cmd[1]) if uplink.Channel.is_valid_channel(cmd[1]) else None + if sentchannel is None: + uplink.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f" Right command : /msg {dnickname} {command} [#SALON] [NICKNAME]") + return False + + nickname = cmd[2] + final_reason = ' '.join(cmd[3:]) + + uplink.Protocol.send2socket(f":{service_id} KICK {sentchannel} {nickname} {final_reason}") + uplink.Logs.debug(f'{client} has kicked {nickname} from {sentchannel} : {final_reason}') + return None + +def set_kickban(uplink: 'Command', cmd: list[str], client: str) -> None: + + command = str(cmd[0]) + dnickname = uplink.Config.SERVICE_NICKNAME + service_id = uplink.Config.SERVICE_ID + + sentchannel = str(cmd[1]) if uplink.Channel.is_valid_channel(cmd[1]) else None + if sentchannel is None: + uplink.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f" Right command : /msg {dnickname} {command} [#SALON] [NICKNAME]") + return False + nickname = cmd[2] + final_reason = ' '.join(cmd[3:]) + + uplink.Protocol.send2socket(f":{service_id} KICK {sentchannel} {nickname} {final_reason}") + uplink.Protocol.send2socket(f":{service_id} MODE {sentchannel} +b {nickname}!*@*") + uplink.Logs.debug(f'{client} has kicked and banned {nickname} from {sentchannel} : {final_reason}') + +def set_assign_channel_to_service(uplink: 'Command', cmd: list[str], client: str) -> None: + + command = str(cmd[0]) + dnickname = uplink.Config.SERVICE_NICKNAME + sent_channel = str(cmd[1]) if uplink.Channel.is_valid_channel(cmd[1]) else None + if sent_channel is None: + uplink.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON]") + return None + + # self.Protocol.send2socket(f':{service_id} JOIN {sent_channel}') + uplink.Protocol.send_join_chan(uidornickname=dnickname,channel=sent_channel) + uplink.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f" Has joined {sent_channel}") + uplink.Channel.db_query_channel('add', uplink.module_name, sent_channel) + + return None + +def set_unassign_channel_to_service(uplink: 'Command', cmd: list[str], client: str) -> None: + + command = str(cmd[0]) + dnickname = uplink.Config.SERVICE_NICKNAME + dchanlog = uplink.Config.SERVICE_CHANLOG + + sent_channel = str(cmd[1]) if uplink.Channel.is_valid_channel(cmd[1]) else None + if sent_channel is None: + uplink.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON]") + return None + + if sent_channel == dchanlog: + uplink.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f"[!] CAN'T LEFT {sent_channel} AS IT IS LOG CHANNEL [!]") + return None + + uplink.Protocol.send_part_chan(uidornickname=dnickname, channel=sent_channel) + uplink.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f" Has left {sent_channel}") + + uplink.Channel.db_query_channel('del', uplink.module_name, sent_channel) + return None \ No newline at end of file diff --git a/mods/jsonrpc/mod_jsonrpc.py b/mods/jsonrpc/mod_jsonrpc.py index e7181cd..1f12134 100644 --- a/mods/jsonrpc/mod_jsonrpc.py +++ b/mods/jsonrpc/mod_jsonrpc.py @@ -1,4 +1,6 @@ +import gc import logging +from time import sleep from typing import TYPE_CHECKING from dataclasses import dataclass from unrealircd_rpc_py.Live import LiveWebsocket @@ -44,6 +46,7 @@ class Jsonrpc(): # Create module commands (Mandatory) self.Irc.build_command(1, self.module_name, 'jsonrpc', 'Activate the JSON RPC Live connection [ON|OFF]') self.Irc.build_command(1, self.module_name, 'jruser', 'Get Information about a user using JSON RPC') + self.Irc.build_command(1, self.module_name, 'jrinstances', 'Get number of instances') # Init the module self.__init_module() @@ -51,10 +54,13 @@ class Jsonrpc(): # Log the module self.Logs.debug(f'Module {self.module_name} loaded ...') + def compter_instances(self, cls) -> int: + return sum(1 for obj in gc.get_objects() if isinstance(obj, cls)) + def __init_module(self) -> None: logging.getLogger('websockets').setLevel(logging.WARNING) - logging.getLogger('unrealircd-rpc-py').setLevel(logging.CRITICAL) + logging.getLogger('unrealircd-rpc-py').setLevel(logging.DEBUG) # Create you own tables (Mandatory) # self.__create_tables() @@ -68,11 +74,12 @@ class Jsonrpc(): username=self.Config.JSONRPC_USER, password=self.Config.JSONRPC_PASSWORD, callback_object_instance=self, - callback_method_or_function_name='callback_sent_to_irc' + callback_method_or_function_name='callback_sent_to_irc', + debug_level=10 ) if self.UnrealIrcdRpcLive.get_error.code != 0: - self.Logs.error(self.UnrealIrcdRpcLive.get_error.code, self.UnrealIrcdRpcLive.get_error.message) + self.Logs.error(f"{self.UnrealIrcdRpcLive.get_error.message} ({self.UnrealIrcdRpcLive.get_error.code})") self.Protocol.send_priv_msg( nick_from=self.Config.SERVICE_NICKNAME, msg=f"[{self.Config.COLORS.red}ERROR{self.Config.COLORS.nogc}] {self.UnrealIrcdRpcLive.get_error.message}", @@ -88,7 +95,8 @@ class Jsonrpc(): ) if self.Rpc.get_error.code != 0: - self.Logs.error(self.Rpc.get_error.code, self.Rpc.get_error.message) + + self.Logs.error(f"{self.Rpc.get_error.message} ({self.Rpc.get_error.code})") self.Protocol.send_priv_msg( nick_from=self.Config.SERVICE_NICKNAME, msg=f"[{self.Config.COLORS.red}ERROR{self.Config.COLORS.nogc}] {self.Rpc.get_error.message}", @@ -132,10 +140,14 @@ class Jsonrpc(): red = self.Config.COLORS.red if hasattr(response, 'result'): + if response.method == 'log.unsubscribe': + print(f">>>>>>>>>>>>>>>>> {response}") + return None + if isinstance(response.result, bool) and response.result: self.Protocol.send_priv_msg( nick_from=self.Config.SERVICE_NICKNAME, - msg=f"[{bold}{green}JSONRPC{nogc}{bold}] Event activated", + msg=f"[{bold}{green}JSONRPC{nogc}{bold}] JSONRPC Event activated on {self.Config.JSONRPC_URL}", channel=dchanlog) return None @@ -155,15 +167,37 @@ class Jsonrpc(): def thread_start_jsonrpc(self): + if not hasattr(self, 'UnrealIrcdRpcLive'): + return None + if self.UnrealIrcdRpcLive.get_error.code == 0: - self.UnrealIrcdRpcLive.subscribe(["all"]) self.subscribed = True + self.UnrealIrcdRpcLive.subscribe(["all"]) else: self.Protocol.send_priv_msg( nick_from=self.Config.SERVICE_NICKNAME, msg=f"[{self.Config.COLORS.red}ERROR{self.Config.COLORS.nogc}] {self.UnrealIrcdRpcLive.get_error.message}", channel=self.Config.SERVICE_CHANLOG ) + + if not self.subscribed: + self.Protocol.send_priv_msg( + nick_from=self.Config.SERVICE_NICKNAME, + msg=f"[{self.Config.COLORS.green}JSONRPC{self.Config.COLORS.nogc}] Stream is OFF", + channel=self.Config.SERVICE_CHANLOG + ) + else: + self.Protocol.send_priv_msg( + nick_from=self.Config.SERVICE_NICKNAME, + msg=f"[{self.Config.COLORS.red}JSONRPC{self.Config.COLORS.nogc}] Stream has crashed!", + channel=self.Config.SERVICE_CHANLOG + ) + + def thread_stop_jsonrpc(self) -> None: + self.subscribed = False + self.UnrealIrcdRpcLive.unsubscribe() + self.Logs.debug("[JSONRPC UNLOAD] Unsubscribe from the stream!") + self.__update_configuration('jsonrpc', 0) def __load_module_configuration(self) -> None: """### Load Module Configuration @@ -189,9 +223,31 @@ class Jsonrpc(): """ self.Base.db_update_core_config(self.module_name, self.ModConfig, param_key, param_value) + def restart_jsonrpc(self) -> None: + self.Logs.debug("[JSONRPC THREAD] Rescue the connection of JSONRPC") + you_can_run_jsonrpc = False + limit = 10 + inc = 0 + + for thread in self.Base.running_threads: + if thread.name == 'thread_start_jsonrpc': + while thread.is_alive(): + sleep(2) + inc += 1 + if inc > limit: + break + + if not thread.is_alive(): + you_can_run_jsonrpc = True + break + + if you_can_run_jsonrpc: + self.Logs.debug("[JSONRPC THREAD] Success re run jsonrpc") + self.Base.create_thread(self.thread_start_jsonrpc, run_once=True) + def unload(self) -> None: - if self.UnrealIrcdRpcLive.get_error.code != -1: - self.UnrealIrcdRpcLive.unsubscribe() + self.Base.create_thread(func=self.thread_stop_jsonrpc, run_once=True) + self.Logs.debug(f"Unloading {self.module_name}") return None def cmd(self, data:list) -> None: @@ -243,7 +299,8 @@ class Jsonrpc(): self.__update_configuration('jsonrpc', 1) case 'off': - self.UnrealIrcdRpcLive.unsubscribe() + self.subscribed = False + self.Base.create_thread(func=self.thread_stop_jsonrpc, run_once=True) self.__update_configuration('jsonrpc', 0) except IndexError as ie: @@ -300,22 +357,11 @@ class Jsonrpc(): except IndexError as ie: self.Logs.error(ie) - case 'ia': + case 'jrinstances': try: - - self.Base.create_thread(self.thread_ask_ia, ('',)) - - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" This is a notice to the sender ...") - self.Protocol.send_priv_msg(nick_from=dnickname, msg="This is private message to the sender ...", nick_to=fromuser) - - if not fromchannel is None: - self.Protocol.send_priv_msg(nick_from=dnickname, msg="This is channel message to the sender ...", channel=fromchannel) - - # 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") - + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"GC Collect: {gc.collect()}") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance LiveWebsock: {self.compter_instances(LiveWebsocket)}") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance Loader: {self.compter_instances(Loader)}") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre de toute les instances: {len(gc.get_objects())}") except Exception as err: self.Logs.error(f"Unknown Error: {err}") \ No newline at end of file diff --git a/version.json b/version.json index a4a7194..085f338 100644 --- a/version.json +++ b/version.json @@ -3,7 +3,7 @@ "requests": "2.32.3", "psutil": "6.0.0", - "unrealircd_rpc_py": "2.0.1", + "unrealircd_rpc_py": "2.0.2", "sqlalchemy": "2.0.35", "faker": "30.1.0" } \ No newline at end of file From 0a4e185fe8d4899779964f7b21cbf1bc608f6b2c Mon Sep 17 00:00:00 2001 From: adator <85586985+adator85@users.noreply.github.com> Date: Wed, 20 Aug 2025 17:17:25 +0200 Subject: [PATCH 19/20] Update module load and reload./update also mod_jsonrpc --- core/irc.py | 39 +++++----- mods/jsonrpc/mod_jsonrpc.py | 138 +++++++++++++++++++++--------------- version.json | 2 +- 3 files changed, 103 insertions(+), 76 deletions(-) diff --git a/core/irc.py b/core/irc.py index 2ab5a67..94f5b91 100644 --- a/core/irc.py +++ b/core/irc.py @@ -536,14 +536,12 @@ class Irc: module_folder = module_name.split('_')[1].lower() # ==> voice class_name = module_name.split('_')[1].capitalize() # ==> Voice - # print(self.loaded_classes) - - # Si le module est déja chargé - if 'mods.' + module_name in sys.modules: - self.Logs.info("Module déja chargé ...") - self.Logs.info('module name = ' + module_name) + # Check if the module is already loaded. + if 'mods.' + module_folder + '.' + module_name in sys.modules: + self.Logs.debug(f"Module [{module_folder}.{module_name}] already loaded!") if class_name in self.loaded_classes: # Si le module existe dans la variable globale retourne False + self.Logs.debug(f"Module [{module_folder}.{module_name}] exist in the local variable!") self.Protocol.send_priv_msg( nick_from=self.Config.SERVICE_NICKNAME, msg=f"Le module {module_name} est déja chargé ! si vous souhaiter le recharge tapez {self.Config.SERVICE_PREFIX}reload {module_name}", @@ -566,11 +564,11 @@ class Irc: msg=f"Module {module_name} chargé", channel=self.Config.SERVICE_CHANLOG ) - return False + self.Logs.debug(f"Module [{module_folder}.{module_name}] reloaded!") + return True # Charger le module loaded_module = importlib.import_module(f'mods.{module_folder}.{module_name}') - my_class = getattr(loaded_module, class_name, None) # Récuperer le nom de classe create_instance_of_the_class = my_class(self.ircObject) # Créer une nouvelle instance de la classe @@ -597,7 +595,7 @@ class Irc: channel=self.Config.SERVICE_CHANLOG ) - self.Logs.info(f"Module {class_name} has been loaded") + self.Logs.debug(f"Module {class_name} has been loaded") return True @@ -605,19 +603,21 @@ class Irc: self.Logs.error(f"MODULE_NOT_FOUND: {moduleNotFound}") self.Protocol.send_priv_msg( nick_from=self.Config.SERVICE_NICKNAME, - msg=f"[ {self.Config.COLORS.red}MODULE_NOT_FOUND{self.Config.COLORS.black} ]: {moduleNotFound}", + msg=f"[ {self.Config.COLORS.red}MODULE ERROR{self.Config.COLORS.black} ]: {moduleNotFound}", channel=self.Config.SERVICE_CHANLOG ) self.Base.db_delete_module(module_name) + return False + except Exception as err: - self.Logs.error(f"Something went wrong with a module you want to load : {err}") + self.Logs.error(f"[LOAD MODULE ERROR]: {err}", exc_info=True) self.Protocol.send_priv_msg( nick_from=self.Config.SERVICE_NICKNAME, - msg=f"[ {self.Config.COLORS.red}ERROR{self.Config.COLORS.black} ]: {err}", + msg=f"[ {self.Config.COLORS.red}MODULE ERROR{self.Config.COLORS.black} ]: {err}", channel=self.Config.SERVICE_CHANLOG ) self.Base.db_delete_module(module_name) - traceback.print_exc() + return False def unload_module(self, mod_name: str) -> bool: """Unload a module @@ -629,21 +629,28 @@ class Irc: bool: True if success """ try: - module_name = mod_name.lower() # Le nom du module. exemple: mod_defender - class_name = module_name.split('_')[1].capitalize() # Nom de la class. exemple: Defender + # Le nom du module. exemple: mod_defender + module_name = mod_name.lower() + module_folder = module_name.split('_')[1].lower() # ==> defender + class_name = module_name.split('_')[1].capitalize() # Nom de la class. exemple: Defender if class_name in self.loaded_classes: self.loaded_classes[class_name].unload() del self.loaded_classes[class_name] + # Delete from the sys. + if sys.modules.get(f'{module_folder}.{module_name}'): + del sys.modules[f"{module_folder}.{module_name}"] + # Supprimer le module de la base de données self.Base.db_delete_module(module_name) self.Protocol.send_priv_msg( nick_from=self.Config.SERVICE_NICKNAME, - msg=f"Module {module_name} supprimé", + msg=f"[ MODULE INFO ] Module {module_name} has been deleted!", channel=self.Config.SERVICE_CHANLOG ) + self.Logs.debug(f"[ MODULE ] {module_name} has been deleted!") return True except Exception as err: diff --git a/mods/jsonrpc/mod_jsonrpc.py b/mods/jsonrpc/mod_jsonrpc.py index 1f12134..0504856 100644 --- a/mods/jsonrpc/mod_jsonrpc.py +++ b/mods/jsonrpc/mod_jsonrpc.py @@ -1,9 +1,11 @@ import gc import logging +import asyncio from time import sleep +from types import SimpleNamespace from typing import TYPE_CHECKING from dataclasses import dataclass -from unrealircd_rpc_py.Live import LiveWebsocket +from unrealircd_rpc_py.Live import LiveWebsocket, LiveUnixSocket from unrealircd_rpc_py.Loader import Loader if TYPE_CHECKING: @@ -43,6 +45,12 @@ class Jsonrpc(): # Add Channel object to the module (Mandatory) self.Channel = ircInstance.Channel + # Is RPC Active? + self.is_streaming = False + + # Run Garbage collector. + self.Base.create_timer(10, gc.collect) + # Create module commands (Mandatory) self.Irc.build_command(1, self.module_name, 'jsonrpc', 'Activate the JSON RPC Live connection [ON|OFF]') self.Irc.build_command(1, self.module_name, 'jruser', 'Get Information about a user using JSON RPC') @@ -60,7 +68,8 @@ class Jsonrpc(): def __init_module(self) -> None: logging.getLogger('websockets').setLevel(logging.WARNING) - logging.getLogger('unrealircd-rpc-py').setLevel(logging.DEBUG) + logging.getLogger('unrealircd-rpc-py').setLevel(logging.CRITICAL) + logging.getLogger('unrealircd-liverpc-py').setLevel(logging.CRITICAL) # Create you own tables (Mandatory) # self.__create_tables() @@ -77,7 +86,7 @@ class Jsonrpc(): callback_method_or_function_name='callback_sent_to_irc', debug_level=10 ) - + if self.UnrealIrcdRpcLive.get_error.code != 0: self.Logs.error(f"{self.UnrealIrcdRpcLive.get_error.message} ({self.UnrealIrcdRpcLive.get_error.code})") self.Protocol.send_priv_msg( @@ -85,7 +94,7 @@ class Jsonrpc(): msg=f"[{self.Config.COLORS.red}ERROR{self.Config.COLORS.nogc}] {self.UnrealIrcdRpcLive.get_error.message}", channel=self.Config.SERVICE_CHANLOG ) - return + raise Exception(f"[LIVE-JSONRPC ERROR] {self.UnrealIrcdRpcLive.get_error.message}") self.Rpc: Loader = Loader( req_method=self.Config.JSONRPC_METHOD, @@ -95,19 +104,17 @@ class Jsonrpc(): ) if self.Rpc.get_error.code != 0: - self.Logs.error(f"{self.Rpc.get_error.message} ({self.Rpc.get_error.code})") self.Protocol.send_priv_msg( nick_from=self.Config.SERVICE_NICKNAME, - msg=f"[{self.Config.COLORS.red}ERROR{self.Config.COLORS.nogc}] {self.Rpc.get_error.message}", + msg=f"[{self.Config.COLORS.red}JSONRPC ERROR{self.Config.COLORS.nogc}] {self.Rpc.get_error.message}", channel=self.Config.SERVICE_CHANLOG ) - - self.subscribed = False + raise Exception(f"[JSONRPC ERROR] {self.Rpc.get_error.message}") if self.ModConfig.jsonrpc == 1: self.Base.create_thread(self.thread_start_jsonrpc, run_once=True) - + return None def __create_tables(self) -> None: @@ -130,7 +137,7 @@ class Jsonrpc(): self.Base.db_execute_query(table_logs) return None - def callback_sent_to_irc(self, response): + def callback_sent_to_irc(self, response: SimpleNamespace) -> None: dnickname = self.Config.SERVICE_NICKNAME dchanlog = self.Config.SERVICE_CHANLOG @@ -139,17 +146,29 @@ class Jsonrpc(): bold = self.Config.COLORS.bold red = self.Config.COLORS.red - if hasattr(response, 'result'): - if response.method == 'log.unsubscribe': - print(f">>>>>>>>>>>>>>>>> {response}") - return None + if self.UnrealIrcdRpcLive.get_error.code != 0: + self.Protocol.send_priv_msg(nick_from=dnickname, + msg=f"[{bold}{red}JSONRPC ERROR{nogc}{bold}] {self.UnrealIrcdRpcLive.get_error.message}", + channel=dchanlog) + return None - if isinstance(response.result, bool) and response.result: + if hasattr(response, 'error'): + if response.error.code != 0: self.Protocol.send_priv_msg( - nick_from=self.Config.SERVICE_NICKNAME, - msg=f"[{bold}{green}JSONRPC{nogc}{bold}] JSONRPC Event activated on {self.Config.JSONRPC_URL}", - channel=dchanlog) - return None + nick_from=self.Config.SERVICE_NICKNAME, + msg=f"[{bold}{red}JSONRPC{nogc}{bold}] JSONRPC Event activated on {self.Config.JSONRPC_URL}", + channel=dchanlog) + + return None + + if hasattr(response, 'result'): + if isinstance(response.result, bool): + if response.result: + self.Protocol.send_priv_msg( + nick_from=self.Config.SERVICE_NICKNAME, + msg=f"[{bold}{green}JSONRPC{nogc}{bold}] JSONRPC Event activated on {self.Config.JSONRPC_URL}", + channel=dchanlog) + return None level = response.result.level if hasattr(response.result, 'level') else '' subsystem = response.result.subsystem if hasattr(response.result, 'subsystem') else '' @@ -158,29 +177,30 @@ class Jsonrpc(): msg = response.result.msg if hasattr(response.result, 'msg') else '' build_msg = f"{green}{log_source}{nogc}: [{bold}{level}{bold}] {subsystem}.{event_id} - {msg}" - - # Check if there is an error - if self.UnrealIrcdRpcLive.get_error.code != 0: - self.Logs.error(f"RpcLiveError: {self.UnrealIrcdRpcLive.get_error.message}") - self.Protocol.send_priv_msg(nick_from=dnickname, msg=build_msg, channel=dchanlog) + + return None def thread_start_jsonrpc(self): - - if not hasattr(self, 'UnrealIrcdRpcLive'): - return None + response: dict[str, dict] = {} if self.UnrealIrcdRpcLive.get_error.code == 0: - self.subscribed = True - self.UnrealIrcdRpcLive.subscribe(["all"]) + self.is_streaming = True + response = asyncio.run(self.UnrealIrcdRpcLive.subscribe(["all"])) else: self.Protocol.send_priv_msg( nick_from=self.Config.SERVICE_NICKNAME, msg=f"[{self.Config.COLORS.red}ERROR{self.Config.COLORS.nogc}] {self.UnrealIrcdRpcLive.get_error.message}", channel=self.Config.SERVICE_CHANLOG ) - - if not self.subscribed: + + if response is None: + return + + code = response.get('error', {}).get('code', 0) + message = response.get('error', {}).get('message', None) + + if code == 0: self.Protocol.send_priv_msg( nick_from=self.Config.SERVICE_NICKNAME, msg=f"[{self.Config.COLORS.green}JSONRPC{self.Config.COLORS.nogc}] Stream is OFF", @@ -189,16 +209,31 @@ class Jsonrpc(): else: self.Protocol.send_priv_msg( nick_from=self.Config.SERVICE_NICKNAME, - msg=f"[{self.Config.COLORS.red}JSONRPC{self.Config.COLORS.nogc}] Stream has crashed!", + msg=f"[{self.Config.COLORS.red}JSONRPC{self.Config.COLORS.nogc}] Stream has crashed! {code} - {message}", channel=self.Config.SERVICE_CHANLOG ) - + def thread_stop_jsonrpc(self) -> None: - self.subscribed = False - self.UnrealIrcdRpcLive.unsubscribe() + + response: dict[str, dict] = asyncio.run(self.UnrealIrcdRpcLive.unsubscribe()) self.Logs.debug("[JSONRPC UNLOAD] Unsubscribe from the stream!") + self.is_streaming = False self.__update_configuration('jsonrpc', 0) + if response is None: + print(f"... Response is None ?! {response}") + return None + + code = response.get('error', {}).get('code', 0) + message = response.get('error', {}).get('message', None) + + if code != 0: + self.Protocol.send_priv_msg( + nick_from=self.Config.SERVICE_NICKNAME, + msg=f"[{self.Config.COLORS.red}JSONRPC ERROR{self.Config.COLORS.nogc}] {message} ({code})", + channel=self.Config.SERVICE_CHANLOG + ) + def __load_module_configuration(self) -> None: """### Load Module Configuration """ @@ -223,34 +258,19 @@ class Jsonrpc(): """ self.Base.db_update_core_config(self.module_name, self.ModConfig, param_key, param_value) - def restart_jsonrpc(self) -> None: - self.Logs.debug("[JSONRPC THREAD] Rescue the connection of JSONRPC") - you_can_run_jsonrpc = False - limit = 10 - inc = 0 - - for thread in self.Base.running_threads: - if thread.name == 'thread_start_jsonrpc': - while thread.is_alive(): - sleep(2) - inc += 1 - if inc > limit: - break - - if not thread.is_alive(): - you_can_run_jsonrpc = True - break - - if you_can_run_jsonrpc: - self.Logs.debug("[JSONRPC THREAD] Success re run jsonrpc") - self.Base.create_thread(self.thread_start_jsonrpc, run_once=True) - def unload(self) -> None: + if self.is_streaming: + self.Protocol.send_priv_msg( + nick_from=self.Config.SERVICE_NICKNAME, + msg=f"[{self.Config.COLORS.green}JSONRPC INFO{self.Config.COLORS.nogc}] Shutting down RPC system!", + channel=self.Config.SERVICE_CHANLOG + ) self.Base.create_thread(func=self.thread_stop_jsonrpc, run_once=True) + self.__update_configuration('jsonrpc', 0) self.Logs.debug(f"Unloading {self.module_name}") return None - def cmd(self, data:list) -> None: + def cmd(self, data: list) -> None: return None @@ -299,7 +319,6 @@ class Jsonrpc(): self.__update_configuration('jsonrpc', 1) case 'off': - self.subscribed = False self.Base.create_thread(func=self.thread_stop_jsonrpc, run_once=True) self.__update_configuration('jsonrpc', 0) @@ -361,6 +380,7 @@ class Jsonrpc(): try: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"GC Collect: {gc.collect()}") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance LiveWebsock: {self.compter_instances(LiveWebsocket)}") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance LiveUnixSocket: {self.compter_instances(LiveUnixSocket)}") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance Loader: {self.compter_instances(Loader)}") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre de toute les instances: {len(gc.get_objects())}") except Exception as err: diff --git a/version.json b/version.json index 085f338..b25ce68 100644 --- a/version.json +++ b/version.json @@ -3,7 +3,7 @@ "requests": "2.32.3", "psutil": "6.0.0", - "unrealircd_rpc_py": "2.0.2", + "unrealircd_rpc_py": "2.0.4", "sqlalchemy": "2.0.35", "faker": "30.1.0" } \ No newline at end of file From 06fc6c4d822f85082e64c6ef0957cd9f0855ed83 Mon Sep 17 00:00:00 2001 From: adator <85586985+adator85@users.noreply.github.com> Date: Thu, 21 Aug 2025 00:59:13 +0200 Subject: [PATCH 20/20] First version to merge --- core/base.py | 33 +++++++++ core/utils.py | 20 +++++ defender.py | 1 + mods/clone/mod_clone.py | 36 +++++++-- mods/jsonrpc/mod_jsonrpc.py | 141 +++++++++++------------------------- mods/jsonrpc/threads.py | 60 +++++++++++++++ mods/jsonrpc/utils.py | 0 requirements.txt | 14 ++++ 8 files changed, 197 insertions(+), 108 deletions(-) create mode 100644 mods/jsonrpc/threads.py create mode 100644 mods/jsonrpc/utils.py create mode 100644 requirements.txt diff --git a/core/base.py b/core/base.py index e795b1c..b13d361 100644 --- a/core/base.py +++ b/core/base.py @@ -452,6 +452,39 @@ class Base: except AssertionError as ae: self.logs.error(f'{ae}') + def is_thread_alive(self, thread_name: str) -> bool: + """Check if the thread is still running! using the is_alive method of Threads. + + Args: + thread_name (str): The thread name + + Returns: + bool: True if is alive + """ + for thread in self.running_threads: + if thread.name.lower() == thread_name.lower(): + if thread.is_alive(): + return True + else: + return False + + return False + + def is_thread_exist(self, thread_name: str) -> bool: + """Check if the thread exist in the local var (running_threads) + + Args: + thread_name (str): The thread name + + Returns: + bool: True if the thread exist + """ + for thread in self.running_threads: + if thread.name.lower() == thread_name.lower(): + return True + + return False + def thread_count(self, thread_name: str) -> int: """This method return the number of existing threads currently running or not running diff --git a/core/utils.py b/core/utils.py index b832335..b89551d 100644 --- a/core/utils.py +++ b/core/utils.py @@ -1,6 +1,7 @@ ''' Main utils library. ''' +import gc from pathlib import Path from re import sub from typing import Literal, Optional, Any @@ -50,6 +51,25 @@ def get_datetime() -> datetime: """ return datetime.now() +def run_python_garbage_collector() -> int: + """Run Python garbage collector + + Returns: + int: The number of unreachable objects is returned. + """ + return gc.collect() + +def get_number_gc_objects(your_object_to_count: Optional[Any] = None) -> int: + """Get The number of objects tracked by the collector (excluding the list returned). + + Returns: + int: Number of tracked objects by the collector + """ + if your_object_to_count is None: + return len(gc.get_objects()) + + return sum(1 for obj in gc.get_objects() if isinstance(obj, your_object_to_count)) + def generate_random_string(lenght: int) -> str: """Retourn une chaîne aléatoire en fonction de la longueur spécifiée. diff --git a/defender.py b/defender.py index 9e5e200..2248040 100644 --- a/defender.py +++ b/defender.py @@ -5,6 +5,7 @@ from core import installation # Requierements : # # Python3.10 or higher # # SQLAlchemy, requests, psutil # +# unrealircd-rpc-py # # UnrealIRCD 6.2.2 or higher # ############################################# diff --git a/mods/clone/mod_clone.py b/mods/clone/mod_clone.py index 78a4591..43bde4a 100644 --- a/mods/clone/mod_clone.py +++ b/mods/clone/mod_clone.py @@ -175,9 +175,9 @@ class Clone: if len(cmd) == 1: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone connect NUMBER GROUP_NAME INTERVAL") - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone kill [all | nickname]") - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone join [all | nickname] #channel") - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone part [all | nickname] #channel") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone kill [all | group_name | nickname]") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone join [all | group_name | nickname] #channel") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone part [all | group_name | nickname] #channel") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone list") return None @@ -296,11 +296,31 @@ class Clone: case 'list': try: - clone_count = len(self.Clone.UID_CLONE_DB) - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f">> Number of connected clones: {clone_count}") - for clone_name in self.Clone.UID_CLONE_DB: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, - msg=f">> Nickname: {clone_name.nickname} | Username: {clone_name.username} | Realname: {clone_name.realname} | Vhost: {clone_name.vhost} | UID: {clone_name.uid} | Group: {clone_name.group} | Connected: {clone_name.connected}") + # Syntax. /msg defender clone list + header = f" {'Nickname':<12}| {'Real name':<25}| {'Group name':<15}| {'Connected':<35}" + line = "-"*67 + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=header) + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" {line}") + group_name = cmd[2] if len(cmd) > 2 else None + + if group_name is None: + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Number of connected clones: {len(self.Clone.UID_CLONE_DB)}") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" {line}") + for clone_name in self.Clone.UID_CLONE_DB: + self.Protocol.send_notice( + nick_from=dnickname, + nick_to=fromuser, + msg=f" {clone_name.nickname:<12}| {clone_name.realname:<25}| {clone_name.group:<15}| {clone_name.connected:<35}") + else: + if not self.Clone.group_exists(group_name): + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="This Group name doesn't exist!") + return None + clones = self.Clone.get_clones_from_groupname(group_name) + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Number of connected clones: {len(clones)}") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" {line}") + for clone in clones: + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, + msg=f" {clone.nickname:<12}| {clone.realname:<25}| {clone.group:<15}| {clone.connected:<35}") except Exception as err: self.Logs.error(f'{err}') diff --git a/mods/jsonrpc/mod_jsonrpc.py b/mods/jsonrpc/mod_jsonrpc.py index 0504856..70cb37e 100644 --- a/mods/jsonrpc/mod_jsonrpc.py +++ b/mods/jsonrpc/mod_jsonrpc.py @@ -1,6 +1,7 @@ -import gc import logging import asyncio +import mods.jsonrpc.utils as utils +import mods.jsonrpc.threads as thds from time import sleep from types import SimpleNamespace from typing import TYPE_CHECKING @@ -36,6 +37,9 @@ class Jsonrpc(): # Add Base object to the module (Mandatory) self.Base = ircInstance.Base + # Add Main Utils (Mandatory) + self.MainUtils = ircInstance.Utils + # Add logs object to the module (Mandatory) self.Logs = ircInstance.Loader.Logs @@ -47,9 +51,15 @@ class Jsonrpc(): # Is RPC Active? self.is_streaming = False + + # Module Utils + self.Utils = utils + + # Module threads + self.Threads = thds # Run Garbage collector. - self.Base.create_timer(10, gc.collect) + self.Base.create_timer(10, self.MainUtils.run_python_garbage_collector) # Create module commands (Mandatory) self.Irc.build_command(1, self.module_name, 'jsonrpc', 'Activate the JSON RPC Live connection [ON|OFF]') @@ -62,9 +72,6 @@ class Jsonrpc(): # Log the module self.Logs.debug(f'Module {self.module_name} loaded ...') - def compter_instances(self, cls) -> int: - return sum(1 for obj in gc.get_objects() if isinstance(obj, cls)) - def __init_module(self) -> None: logging.getLogger('websockets').setLevel(logging.WARNING) @@ -83,8 +90,7 @@ class Jsonrpc(): username=self.Config.JSONRPC_USER, password=self.Config.JSONRPC_PASSWORD, callback_object_instance=self, - callback_method_or_function_name='callback_sent_to_irc', - debug_level=10 + callback_method_or_function_name='callback_sent_to_irc' ) if self.UnrealIrcdRpcLive.get_error.code != 0: @@ -113,7 +119,7 @@ class Jsonrpc(): raise Exception(f"[JSONRPC ERROR] {self.Rpc.get_error.message}") if self.ModConfig.jsonrpc == 1: - self.Base.create_thread(self.thread_start_jsonrpc, run_once=True) + self.Base.create_thread(func=self.Threads.thread_subscribe, func_args=(self, ), run_once=True) return None @@ -181,59 +187,6 @@ class Jsonrpc(): return None - def thread_start_jsonrpc(self): - response: dict[str, dict] = {} - - if self.UnrealIrcdRpcLive.get_error.code == 0: - self.is_streaming = True - response = asyncio.run(self.UnrealIrcdRpcLive.subscribe(["all"])) - else: - self.Protocol.send_priv_msg( - nick_from=self.Config.SERVICE_NICKNAME, - msg=f"[{self.Config.COLORS.red}ERROR{self.Config.COLORS.nogc}] {self.UnrealIrcdRpcLive.get_error.message}", - channel=self.Config.SERVICE_CHANLOG - ) - - if response is None: - return - - code = response.get('error', {}).get('code', 0) - message = response.get('error', {}).get('message', None) - - if code == 0: - self.Protocol.send_priv_msg( - nick_from=self.Config.SERVICE_NICKNAME, - msg=f"[{self.Config.COLORS.green}JSONRPC{self.Config.COLORS.nogc}] Stream is OFF", - channel=self.Config.SERVICE_CHANLOG - ) - else: - self.Protocol.send_priv_msg( - nick_from=self.Config.SERVICE_NICKNAME, - msg=f"[{self.Config.COLORS.red}JSONRPC{self.Config.COLORS.nogc}] Stream has crashed! {code} - {message}", - channel=self.Config.SERVICE_CHANLOG - ) - - def thread_stop_jsonrpc(self) -> None: - - response: dict[str, dict] = asyncio.run(self.UnrealIrcdRpcLive.unsubscribe()) - self.Logs.debug("[JSONRPC UNLOAD] Unsubscribe from the stream!") - self.is_streaming = False - self.__update_configuration('jsonrpc', 0) - - if response is None: - print(f"... Response is None ?! {response}") - return None - - code = response.get('error', {}).get('code', 0) - message = response.get('error', {}).get('message', None) - - if code != 0: - self.Protocol.send_priv_msg( - nick_from=self.Config.SERVICE_NICKNAME, - msg=f"[{self.Config.COLORS.red}JSONRPC ERROR{self.Config.COLORS.nogc}] {message} ({code})", - channel=self.Config.SERVICE_CHANLOG - ) - def __load_module_configuration(self) -> None: """### Load Module Configuration """ @@ -249,7 +202,7 @@ class Jsonrpc(): except TypeError as te: self.Logs.critical(te) - def __update_configuration(self, param_key: str, param_value: str): + def update_configuration(self, param_key: str, param_value: str) -> None: """Update the local and core configuration Args: @@ -265,8 +218,8 @@ class Jsonrpc(): msg=f"[{self.Config.COLORS.green}JSONRPC INFO{self.Config.COLORS.nogc}] Shutting down RPC system!", channel=self.Config.SERVICE_CHANLOG ) - self.Base.create_thread(func=self.thread_stop_jsonrpc, run_once=True) - self.__update_configuration('jsonrpc', 0) + self.Base.create_thread(func=self.Threads.thread_unsubscribe, func_args=(self, ), run_once=True) + self.update_configuration('jsonrpc', 0) self.Logs.debug(f"Unloading {self.module_name}") return None @@ -286,54 +239,42 @@ class Jsonrpc(): case 'jsonrpc': try: - option = str(cmd[1]).lower() - - if len(command) == 1: + if len(cmd) < 2: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'/msg {dnickname} jsonrpc on') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'/msg {dnickname} jsonrpc off') + return None + option = str(cmd[1]).lower() match option: case 'on': + thread_name = 'thread_subscribe' + if self.Base.is_thread_alive(thread_name): + self.Protocol.send_priv_msg(nick_from=dnickname, channel=dchannel, msg=f"The Subscription is running") + return None + elif self.Base.is_thread_exist(thread_name): + self.Protocol.send_priv_msg( + nick_from=dnickname, channel=dchannel, + msg=f"The subscription is not running, wait untill the process will be cleaned up" + ) + return None - # for logger_name, logger in logging.root.manager.loggerDict.items(): - # if isinstance(logger, logging.Logger): - # self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"{logger_name} - {logger.level}") - - for thread in self.Base.running_threads: - if thread.name == 'thread_start_jsonrpc': - if thread.is_alive(): - self.Protocol.send_priv_msg( - nick_from=self.Config.SERVICE_NICKNAME, - msg=f"Thread {thread.name} is running", - channel=dchannel - ) - else: - self.Protocol.send_priv_msg( - nick_from=self.Config.SERVICE_NICKNAME, - msg=f"Thread {thread.name} is not running, wait untill the process will be cleaned up", - channel=dchannel - ) - - self.Base.create_thread(self.thread_start_jsonrpc, run_once=True) - self.__update_configuration('jsonrpc', 1) + self.Base.create_thread(func=self.Threads.thread_subscribe, func_args=(self, ), run_once=True) + self.update_configuration('jsonrpc', 1) case 'off': - self.Base.create_thread(func=self.thread_stop_jsonrpc, run_once=True) - self.__update_configuration('jsonrpc', 0) + self.Base.create_thread(func=self.Threads.thread_unsubscribe, func_args=(self, ), run_once=True) + self.update_configuration('jsonrpc', 0) except IndexError as ie: self.Logs.error(ie) case 'jruser': try: - option = str(cmd[1]).lower() - - if len(command) == 1: + if len(cmd) < 2: self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'/msg {dnickname} jruser get nickname') - + option = str(cmd[1]).lower() match option: - case 'get': nickname = str(cmd[2]) uid_to_get = self.User.get_uid(nickname) @@ -378,10 +319,10 @@ class Jsonrpc(): case 'jrinstances': try: - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"GC Collect: {gc.collect()}") - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance LiveWebsock: {self.compter_instances(LiveWebsocket)}") - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance LiveUnixSocket: {self.compter_instances(LiveUnixSocket)}") - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance Loader: {self.compter_instances(Loader)}") - self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre de toute les instances: {len(gc.get_objects())}") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"GC Collect: {self.MainUtils.run_python_garbage_collector()}") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance LiveWebsock: {self.MainUtils.get_number_gc_objects(LiveWebsocket)}") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance LiveUnixSocket: {self.MainUtils.get_number_gc_objects(LiveUnixSocket)}") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance Loader: {self.MainUtils.get_number_gc_objects(Loader)}") + self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre de toute les instances: {self.MainUtils.get_number_gc_objects()}") except Exception as err: self.Logs.error(f"Unknown Error: {err}") \ No newline at end of file diff --git a/mods/jsonrpc/threads.py b/mods/jsonrpc/threads.py new file mode 100644 index 0000000..f35b902 --- /dev/null +++ b/mods/jsonrpc/threads.py @@ -0,0 +1,60 @@ +import asyncio +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from mods.jsonrpc.mod_jsonrpc import Jsonrpc + +def thread_subscribe(uplink: 'Jsonrpc') -> None: + response: dict[str, dict] = {} + snickname = uplink.Config.SERVICE_NICKNAME + schannel = uplink.Config.SERVICE_CHANLOG + + if uplink.UnrealIrcdRpcLive.get_error.code == 0: + uplink.is_streaming = True + response = asyncio.run(uplink.UnrealIrcdRpcLive.subscribe(["all"])) + else: + uplink.Protocol.send_priv_msg(nick_from=snickname, + msg=f"[{uplink.Config.COLORS.red}JSONRPC ERROR{uplink.Config.COLORS.nogc}] {uplink.UnrealIrcdRpcLive.get_error.message}", + channel=schannel + ) + + if response is None: + return + + code = response.get('error', {}).get('code', 0) + message = response.get('error', {}).get('message', None) + + if code == 0: + uplink.Protocol.send_priv_msg( + nick_from=snickname, + msg=f"[{uplink.Config.COLORS.green}JSONRPC{uplink.Config.COLORS.nogc}] Stream is OFF", + channel=schannel + ) + else: + uplink.Protocol.send_priv_msg( + nick_from=snickname, + msg=f"[{uplink.Config.COLORS.red}JSONRPC{uplink.Config.COLORS.nogc}] Stream has crashed! {code} - {message}", + channel=schannel + ) + +def thread_unsubscribe(uplink: 'Jsonrpc') -> None: + + response: dict[str, dict] = asyncio.run(uplink.UnrealIrcdRpcLive.unsubscribe()) + uplink.Logs.debug("[JSONRPC UNLOAD] Unsubscribe from the stream!") + uplink.is_streaming = False + uplink.update_configuration('jsonrpc', 0) + snickname = uplink.Config.SERVICE_NICKNAME + schannel = uplink.Config.SERVICE_CHANLOG + + if response is None: + return None + + code = response.get('error', {}).get('code', 0) + message = response.get('error', {}).get('message', None) + + if code != 0: + uplink.Protocol.send_priv_msg( + nick_from=snickname, + msg=f"[{uplink.Config.COLORS.red}JSONRPC ERROR{uplink.Config.COLORS.nogc}] {message} ({code})", + channel=schannel + ) diff --git a/mods/jsonrpc/utils.py b/mods/jsonrpc/utils.py new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7c5a081 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,14 @@ +certifi==2024.12.14 +charset-normalizer==3.4.1 +Faker==33.1.2 +greenlet==3.1.1 +idna==3.10 +psutil==6.1.1 +python-dateutil==2.9.0.post0 +requests==2.32.3 +six==1.17.0 +SQLAlchemy==2.0.36 +typing_extensions==4.12.2 +unrealircd-rpc-py==2.0.4 +urllib3==2.3.0 +websockets==14.1