111 Commits

Author SHA1 Message Date
adator
0a2e3f724b Merge pull request #88 from adator85/v6.2.2
Deleting the admin when quit IRC
2025-08-24 03:44:26 +02:00
adator
1224604460 Deleting the admin when quit IRC 2025-08-24 03:43:39 +02:00
adator
43072acecc Merge pull request #87 from adator85/v6.2.1
Adding sasl authentication
2025-08-24 03:11:33 +02:00
adator
bcf6b6b675 Adding sasl authentication 2025-08-24 03:10:45 +02:00
adator
b50205c766 Merge pull request #86 from adator85/v6.2.0
V6.2.1 Adding main module utils and rehash utils to manager reload/re…
2025-08-23 19:29:06 +02:00
adator
bd95b6b448 V6.2.1 Adding main module utils and rehash utils to manager reload/rehash/restart 2025-08-23 19:26:22 +02:00
adator
ebbad1d9e4 Merge pull request #85 from adator85/v6.2.0
Fix removing a user when he leave a channel
2025-08-21 01:39:11 +02:00
adator
ae1f0ed424 Fix removing a user when he leave a channel 2025-08-21 01:38:23 +02:00
adator
80854aea98 Merge pull request #84 from adator85/v6.2.0
Update jsonrpc lib version
2025-08-21 01:07:15 +02:00
adator
483638dab4 Update jsonrpc lib version 2025-08-21 01:06:49 +02:00
adator
4806de4cca Merge pull request #83 from adator85/v6.2.0
Remove requirements.txt
2025-08-21 01:02:55 +02:00
adator
f4b76eaf09 Remove requirements.txt 2025-08-21 01:02:28 +02:00
adator
37617fc286 Merge pull request #82 from adator85/v6.2.0
V6.2.0
2025-08-21 01:00:12 +02:00
adator
06fc6c4d82 First version to merge 2025-08-21 00:59:13 +02:00
adator
0a4e185fe8 Update module load and reload./update also mod_jsonrpc 2025-08-20 17:17:25 +02:00
adator
4c327940dd Adding new classes, todo: fix jsonrpc module 2025-08-19 03:06:56 +02:00
adator
a15a5b1026 Updating command module 2025-08-17 22:48:35 +02:00
adator
25262d4049 Moving some methods to utils.py, creating new logs class 2025-08-17 22:41:51 +02:00
adator
8d23827e1e Updating new classes into votekick module 2025-08-16 21:27:00 +02:00
adator85
f5212deacf Update Votekick first part comment 2025-08-16 02:27:03 +02:00
adator85
3fc49e9069 Update Votekick module! following the same structure as other modules 2025-08-16 02:11:21 +02:00
adator85
e075b7b8d5 Updating gitignore file 2025-08-15 16:22:33 +02:00
adator85
30a89e7d14 Updating gitignore file 2025-08-15 16:19:56 +02:00
adator85
aee1dca124 Importing the MAdmin directly from definition (admin.py) 2025-08-15 16:17:32 +02:00
adator85
9e255e806d Adding some comments, editing methods names (admin.py) 2025-08-15 16:14:35 +02:00
adator85
6b22d786e3 Adding some comments, editing methods names 2025-08-15 15:47:01 +02:00
adator
21a2619f49 Updating mod_clone by adding action on groups. reloading the module is now using Settings.set_cache and get_cache 2025-08-10 02:31:50 +02:00
adator
1686c4a0b5 updating namings convension to stay coherent 2025-08-09 12:17:45 +02:00
adator
5629dcfde6 updating namings in user.py and channel.py to stay coherent 2025-08-09 03:53:57 +02:00
adator
f3fe3c43cb updating namings in admin.py to stay coherent 2025-08-09 03:48:56 +02:00
adator
c0cd4db3af updating namings to stay coherent 2025-08-09 03:43:44 +02:00
adator
9ea5ae50d5 Updating cmd by handling all functions, Threads and timers and schemas in separate files. code should be clear 2025-08-09 03:35:30 +02:00
adator
0a655b2df0 Moving modules in separate folders 2025-08-08 17:38:45 +02:00
adator
7a50bc9632 Merge pull request #81 from adator85/update-mod-defender
Nickname not connected to the network
2025-08-07 23:38:52 +02:00
adator
275d37ed2d Nickname not connected to the network 2025-08-07 23:38:32 +02:00
adator
9ae6e5bf4c Merge pull request #80 from adator85/update-mod-defender
Updating this output message again
2025-08-07 23:36:19 +02:00
adator
2feed6878f Updating this output message again 2025-08-07 23:35:52 +02:00
adator
0b173e8d18 Merge pull request #79 from adator85/update-mod-defender
Updating response message.
2025-08-07 23:34:49 +02:00
adator
d2b46f2618 Updating response message. 2025-08-07 23:34:16 +02:00
adator
7a9bcff988 Merge pull request #78 from adator85/update-mod-defender
Updating reputation help command
2025-08-07 23:24:09 +02:00
adator
cc80e35482 Updating reputation help command 2025-08-07 23:23:25 +02:00
adator
d1568106f9 Merge pull request #77 from adator85/update-mod-defender
New command in mod_defender (reputation release)
2025-08-07 23:18:23 +02:00
adator
9df18de0b1 mod_defender:
- New command added in reputation (release)
    to release a known user.
base.py:
    - Switch deprecated getName() method
    to name attribute (In threading methods)
2025-08-07 23:15:18 +02:00
adator
64f1490942 Merge pull request #76 from adator85/upgrade-mod-jsonrpc
Moving rpc module to rpc package v2
2025-08-07 16:27:40 +02:00
adator
9ddb2a28b2 Moving rpc module to rpc package v2 2025-08-07 16:26:59 +02:00
adator
eacfe0d087 Merge pull request #75 from adator85/update/admin.py
Update level of the debug for admin.py
2025-08-07 02:00:38 +02:00
adator
680a446c2c Update level of the debug for admin.py 2025-08-07 02:00:04 +02:00
adator
a10aa9b94e Merge pull request #74 from adator85/jsonrpc/update
Update jsonrpc module with new version of unrealircd-rpc-py
2025-08-07 01:39:55 +02:00
adator
56265985f7 Update jsonrpc module with new version of unrealircd-rpc-py 2025-08-07 01:39:21 +02:00
adator
be36c56ceb Merge pull request #73 from adator85/update/protocol
Update/protocol
2025-07-20 19:44:49 +02:00
adator
ac332e6802 Update version number 2025-07-20 19:42:59 +02:00
adator
6063ceba35 Update Protocol Connection 2025-07-20 19:41:10 +02:00
adator
2204c4fdf8 Merge pull request #72 from adator85/V6.X.X
V6.1.1 Updates
2024-12-08 23:03:01 +01:00
adator
befe452df8 V6.1.1 Updates 2024-12-08 23:02:36 +01:00
adator
b98a20ad45 Merge pull request #71 from adator85/V6.X.X
V6.1.0 update the help command
2024-12-08 12:56:18 +01:00
adator
cb042a5411 V6.1.0 update the help command 2024-12-08 12:52:36 +01:00
adator
dc63df08cf Merge pull request #70 from adator85/V6.X.X
Update on_version method
2024-11-22 23:27:15 +01:00
adator
a3edf48120 Update on_version method 2024-11-22 23:26:39 +01:00
adator
a3a61c332f Merge pull request #69 from adator85/V6.X.X
V6.0.4
2024-11-18 23:50:25 +01:00
adator
eeaacddbf2 Merge pull request #68 from adator85/V6.X.X
Update to version 6.0.3
2024-11-17 21:09:51 +01:00
adator
39412fc1c0 Merge pull request #67 from adator85/V6.X.X
V6.0.2
2024-11-15 22:14:55 +01:00
adator
e148659d00 Merge pull request #66 from adator85/V6.X.X
V6.0.1
2024-11-11 23:38:26 +01:00
adator
71a7d29b08 Merge pull request #65 from adator85/V6.X.X
Update the version 6
2024-11-11 15:39:20 +01:00
adator
71170baf1a Merge pull request #64 from adator85/V6.X.X
Fix clone reply
2024-11-07 23:43:02 +01:00
adator
1b20435b83 Merge pull request #63 from adator85/V6.X.X
fix main file name
2024-11-07 23:11:26 +01:00
adator
008dacfde6 Merge pull request #62 from adator85/V6.X.X
Disable console
2024-11-07 23:03:47 +01:00
adator
5347c45579 Merge pull request #61 from adator85/V6.X.X
V6.0.0
2024-11-07 22:50:18 +01:00
adator
63130fbc06 Merge pull request #60 from adator85/V6.X.X
V6.0.0
2024-11-07 22:46:20 +01:00
adator
b27b503d78 Merge pull request #59 from adator85/V5.X.X
V5.3.9
2024-10-13 22:22:00 +02:00
adator
36e3835e6c Merge pull request #58 from adator85/V5.X.X
update websocket connection
2024-10-06 21:58:24 +02:00
adator
2c78025bfb Merge pull request #57 from adator85/V5.X.X
V5.3.8
2024-10-06 21:49:13 +02:00
adator
979ba40c05 Merge pull request #56 from adator85/V5.X.X
V5.x.x
2024-10-04 01:10:33 +02:00
adator
cea69c1580 Merge pull request #54 from adator85/dev
V5.3.6
2024-10-02 23:40:38 +02:00
adator
c404cc3234 Merge pull request #53 from adator85/dev
Dev
2024-10-02 00:06:54 +02:00
adator
80b329dd5d Merge pull request #52 from adator85/dev
You need to have unrealirc_rpc_py v1.0.3
2024-09-30 01:40:36 +02:00
adator
f7b49c151f Merge pull request #51 from adator85/dev
V5.3.4
2024-09-29 22:43:28 +02:00
adator
1ee9b7e3ff Merge pull request #50 from adator85/dev
Dev
2024-09-26 01:00:39 +02:00
adator
4d0087623c Merge pull request #49 from adator85/dev
unsubscribe before unload
2024-09-22 23:54:22 +02:00
adator
dc20f5ec3c Merge pull request #48 from adator85/dev
Dev
2024-09-22 23:20:37 +02:00
adator
110cae3b84 Merge pull request #47 from adator85/dev
Update the way to clean exceptions and bans
2024-09-22 16:45:54 +02:00
adator
857cbfc85d Merge pull request #46 from adator85/dev
Send the ready msg to channel log
2024-09-22 16:33:49 +02:00
adator
3518589e9c Merge pull request #45 from adator85/dev
Fix ConfModel error
2024-09-22 16:31:35 +02:00
adator
e14c97de03 Merge pull request #44 from adator85/dev
V5.3.0
2024-09-22 16:20:45 +02:00
adator
69360be3ad Merge pull request #43 from adator85/dev
Update info command
2024-09-21 20:28:29 +02:00
adator
bfa90c6bd5 Merge pull request #42 from adator85/dev
V5.2.9
2024-09-21 20:22:42 +02:00
adator
5c8378a0e7 Merge pull request #41 from adator85/dev
V5.2.9
2024-09-21 16:43:14 +02:00
adator
e3b212ea88 Merge pull request #40 from adator85/dev
finetune clone connection
2024-09-20 23:13:33 +02:00
adator
0c2a350d38 Merge pull request #39 from adator85/dev
Dev
2024-09-20 21:12:59 +02:00
adator
1cea8d0601 Merge pull request #38 from adator85/dev
V5.2.1
2024-09-15 03:09:22 +02:00
adator
652b400d5e Merge pull request #37 from adator85/dev
update mode clone
2024-09-15 02:50:27 +02:00
adator
2f8b965b59 Merge pull request #36 from adator85/dev
Dev
2024-09-15 02:04:32 +02:00
adator
3c043cefd8 Merge pull request #35 from adator85/dev
V5.1.8
2024-09-08 00:42:57 +02:00
adator
59a75cecd8 Merge pull request #34 from adator85/dev
V5.1.7
2024-09-03 00:21:32 +02:00
adator
71053437a7 Merge pull request #33 from adator85/dev
V5.1.6
2024-09-01 22:15:54 +02:00
adator
7796d05206 Merge pull request #32 from adator85/dev
Update vote kick commands
2024-09-01 18:55:57 +02:00
adator
5f2567f9e5 Merge pull request #31 from adator85/dev
mod_command update
2024-09-01 17:30:05 +02:00
adator
aaa1dd9a1a Merge pull request #30 from adator85/dev
adding Say command for clones
2024-09-01 16:40:25 +02:00
adator
a02f2f9a26 Merge pull request #29 from adator85/dev
update mod_clone module
2024-09-01 15:54:50 +02:00
adator
d73adb6f0b Merge pull request #28 from adator85/dev
update readme
2024-09-01 15:35:59 +02:00
adator
b812e64992 Merge pull request #27 from adator85/dev
Dev
2024-09-01 15:24:18 +02:00
adator
9bd1f68df2 Merge pull request #26 from adator85/dev
Dev
2024-09-01 14:59:38 +02:00
adator
f44b08bf36 Merge pull request #25 from adator85/dev
fix Installation
2024-08-29 01:36:38 +02:00
adator
1a19e1613a Merge pull request #24 from adator85/dev
Fix Bug installation
2024-08-29 01:31:19 +02:00
adator
cdc15b7b47 Merge pull request #23 from adator85/dev
Dev
2024-08-29 01:16:55 +02:00
adator
31fe9f62ec Merge pull request #22 from adator85/dev
Dev
2024-08-24 01:39:11 +02:00
adator
f0853e3afb Merge pull request #21 from adator85/dev
New Installation file created for unix system
2024-08-22 01:02:00 +02:00
adator
6dade09257 Merge pull request #20 from adator85/dev
README Update
2024-08-21 00:50:31 +02:00
adator
9533b010b2 Merge pull request #19 from adator85/dev
V5.0.4 - Delete a user when a user has been kicked
2024-08-20 02:24:37 +02:00
adator
824db73590 Merge pull request #18 from adator85/dev
Delete channel mode information
2024-08-20 02:14:31 +02:00
adator
96bf4b6f80 Merge pull request #17 from adator85/dev
Fix channel update
2024-08-20 02:08:09 +02:00
adator
922336363e Merge pull request #16 from adator85/dev
Dev
2024-08-20 01:56:04 +02:00
44 changed files with 5640 additions and 3850 deletions

2
.gitignore vendored
View File

@@ -1,4 +1,6 @@
.pyenv/ .pyenv/
.venv/
.idea/
db/ db/
logs/ logs/
__pycache__/ __pycache__/

View File

@@ -1,35 +1,35 @@
import importlib
import os import os
import re import re
import json import json
import sys
import time import time
import random
import socket import socket
import hashlib
import logging
import threading import threading
import ipaddress import ipaddress
import ast import ast
import requests import requests
from pathlib import Path
from types import ModuleType
from dataclasses import fields from dataclasses import fields
from typing import Union, Literal, TYPE_CHECKING from typing import Any, Optional, TYPE_CHECKING
from base64 import b64decode, b64encode from base64 import b64decode, b64encode
from datetime import datetime, timedelta, timezone
from sqlalchemy import create_engine, Engine, Connection, CursorResult from sqlalchemy import create_engine, Engine, Connection, CursorResult
from sqlalchemy.sql import text from sqlalchemy.sql import text
from core.definition import MConfig
if TYPE_CHECKING: if TYPE_CHECKING:
from core.classes.settings import Settings from core.loader import Loader
class Base: 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.logs = loader.Logs
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 self.check_for_new_version(True) # Verifier si une nouvelle version est disponible
# Liste des timers en cours # Liste des timers en cours
@@ -136,120 +136,26 @@ class Base:
except Exception as err: except Exception as err:
self.logs.error(f'General Error: {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_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:
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())
return all_modules
def create_log(self, log_message: str) -> None: def create_log(self, log_message: str) -> None:
"""Enregiste les logs """Enregiste les logs
Args: Args:
string (str): Le message a enregistrer log_message (str): Le message a enregistrer
Returns: Returns:
None: Aucun retour None: Aucun retour
""" """
sql_insert = f"INSERT INTO {self.Config.TABLE_LOG} (datetime, server_msg) VALUES (:datetime, :server_msg)" 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) self.db_execute_query(sql_insert, mes_donnees)
return None 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
filter: 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:
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: def log_cmd(self, user_cmd: str, cmd: str) -> None:
"""Enregistre les commandes envoyées par les utilisateurs """Enregistre les commandes envoyées par les utilisateurs
Args: Args:
user_cmd (str): The user who performed the command
cmd (str): la commande a enregistrer cmd (str): la commande a enregistrer
""" """
cmd_list = cmd.split() cmd_list = cmd.split()
@@ -260,70 +166,10 @@ class Base:
cmd = ' '.join(cmd_list) cmd = ' '.join(cmd_list)
insert_cmd_query = f"INSERT INTO {self.Config.TABLE_COMMAND} (datetime, user, commande) VALUES (:datetime, :user, :commande)" 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) 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
Args:
module_name (str): le non du module a chercher dans la base de données
Returns:
bool: True si le module existe déja dans la base de données sinon False
"""
query = f"SELECT id FROM {self.Config.TABLE_MODULE} WHERE module_name = :module_name"
mes_donnes = {'module_name': module_name}
results = self.db_execute_query(query, mes_donnes)
if results.fetchall():
return True
else:
return False
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
"""
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}
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
def db_update_module(self, user_cmd: str, module_name: str) -> None:
"""Modifie la date et le user qui a rechargé le module
Args:
user_cmd (str): le user qui a rechargé le module
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}
self.db_execute_query(update_cmd_query, mes_donnees)
return False
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
"""
insert_cmd_query = f"DELETE FROM {self.Config.TABLE_MODULE} WHERE module_name = :module_name"
mes_donnees = {'module_name': module_name}
self.db_execute_query(insert_cmd_query, mes_donnees)
return False
def db_sync_core_config(self, module_name: str, dataclassObj: object) -> bool: def db_sync_core_config(self, module_name: str, dataclassObj: object) -> bool:
"""Sync module local parameters with the database """Sync module local parameters with the database
@@ -341,7 +187,7 @@ class Base:
""" """
try: try:
response = True response = True
current_date = self.get_datetime() current_date = self.Utils.get_sdatetime()
core_table = self.Config.TABLE_CONFIG core_table = self.Config.TABLE_CONFIG
# Add local parameters to DB # Add local parameters to DB
@@ -391,7 +237,7 @@ class Base:
result = response.fetchall() result = response.fetchall()
for param, value in result: for param, value in result:
if type(getattr(dataclassObj, param)) == list: if isinstance(getattr(dataclassObj, param), list):
value = ast.literal_eval(value) value = ast.literal_eval(value)
setattr(dataclassObj, param, self.int_if_possible(value)) setattr(dataclassObj, param, self.int_if_possible(value))
@@ -418,7 +264,7 @@ class Base:
isParamExist = result.fetchone() isParamExist = result.fetchone()
if not isParamExist is None: if not isParamExist is None:
mes_donnees = {'datetime': self.get_datetime(), mes_donnees = {'datetime': self.Utils.get_sdatetime(),
'module_name': module_name, 'module_name': module_name,
'param_key': param_key, 'param_key': param_key,
'param_value': param_value 'param_value': param_value
@@ -443,9 +289,9 @@ class Base:
user = self.db_execute_query(f"SELECT id FROM {self.Config.TABLE_ADMIN}") user = self.db_execute_query(f"SELECT id FROM {self.Config.TABLE_ADMIN}")
if not user.fetchall(): if not user.fetchall():
admin = self.Config.OWNER 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, 'user': admin,
'password': password, 'password': password,
'hostname': '*', 'hostname': '*',
@@ -465,15 +311,18 @@ class Base:
try: try:
t = threading.Timer(interval=time_to_wait, function=func, args=func_args) t = threading.Timer(interval=time_to_wait, function=func, args=func_args)
t.setName(func.__name__) t.name = func.__name__
t.start() t.start()
self.running_timers.append(t) self.running_timers.append(t)
self.logs.debug(f"-- Timer ID : {str(t.ident)} | Running Threads : {len(threading.enumerate())}") self.logs.debug(f"-- Timer ID : {str(t.ident)} | Running Threads : {len(threading.enumerate())}")
return None
except AssertionError as ae: except AssertionError as ae:
self.logs.error(f'Assertion Error -> {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: 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 """Create a new thread and store it into running_threads variable
@@ -488,18 +337,51 @@ class Base:
if run_once: if run_once:
for thread in self.running_threads: for thread in self.running_threads:
if thread.getName() == func_name: if thread.name == func_name:
return None return None
th = threading.Thread(target=func, args=func_args, name=str(func_name), daemon=daemon) th = threading.Thread(target=func, args=func_args, name=str(func_name), daemon=daemon)
th.start() th.start()
self.running_threads.append(th) self.running_threads.append(th)
self.logs.debug(f"-- Thread ID : {str(th.ident)} | Thread name : {th.getName()} | Running Threads : {len(threading.enumerate())}") self.logs.debug(f"-- Thread ID : {str(th.ident)} | Thread name : {th.name} | Running Threads : {len(threading.enumerate())}")
except AssertionError as ae: except AssertionError as ae:
self.logs.error(f'{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: def thread_count(self, thread_name: str) -> int:
"""This method return the number of existing threads """This method return the number of existing threads
currently running or not running currently running or not running
@@ -514,7 +396,7 @@ class Base:
count = 0 count = 0
for thr in self.running_threads: for thr in self.running_threads:
if thread_name == thr.getName(): if thread_name == thr.name:
count += 1 count += 1
return count return count
@@ -523,12 +405,11 @@ class Base:
"""Methode qui supprime les timers qui ont finis leurs job """Methode qui supprime les timers qui ont finis leurs job
""" """
try: try:
for timer in self.running_timers: for timer in self.running_timers:
if not timer.is_alive(): if not timer.is_alive():
timer.cancel() timer.cancel()
self.running_timers.remove(timer) self.running_timers.remove(timer)
self.logs.info(f"-- Timer {str(timer)} removed") self.logs.debug(f"-- Timer {str(timer)} removed")
else: else:
self.logs.debug(f"--* Timer {str(timer)} Still running ...") self.logs.debug(f"--* Timer {str(timer)} Still running ...")
@@ -540,11 +421,10 @@ class Base:
""" """
try: try:
for thread in self.running_threads: for thread in self.running_threads:
if thread.getName() != 'heartbeat': if thread.name != 'heartbeat':
# print(thread.getName(), thread.is_alive(), sep=' / ')
if not thread.is_alive(): if not thread.is_alive():
self.running_threads.remove(thread) self.running_threads.remove(thread)
self.logs.info(f"-- Thread {str(thread.getName())} {str(thread.native_id)} removed") self.logs.info(f"-- Thread {str(thread.name)} {str(thread.native_id)} removed")
# print(threading.enumerate()) # print(threading.enumerate())
except AssertionError as ae: except AssertionError as ae:
@@ -568,19 +448,19 @@ class Base:
self.logs.debug(f"=======> Checking for Timers to stop") self.logs.debug(f"=======> Checking for Timers to stop")
for timer in self.running_timers: for timer in self.running_timers:
while timer.is_alive(): while timer.is_alive():
self.logs.debug(f"> waiting for {timer.getName()} to close") self.logs.debug(f"> waiting for {timer.name} to close")
timer.cancel() timer.cancel()
time.sleep(0.2) time.sleep(0.2)
self.running_timers.remove(timer) self.running_timers.remove(timer)
self.logs.debug(f"> Cancelling {timer.getName()} {timer.native_id}") self.logs.debug(f"> Cancelling {timer.name} {timer.native_id}")
self.logs.debug(f"=======> Checking for Threads to stop") self.logs.debug(f"=======> Checking for Threads to stop")
for thread in self.running_threads: for thread in self.running_threads:
if thread.getName() == 'heartbeat' and thread.is_alive(): if thread.name == 'heartbeat' and thread.is_alive():
self.execute_periodic_action() self.execute_periodic_action()
self.logs.debug(f"> Running the last periodic action") self.logs.debug(f"> Running the last periodic action")
self.running_threads.remove(thread) self.running_threads.remove(thread)
self.logs.debug(f"> Cancelling {thread.getName()} {thread.native_id}") self.logs.debug(f"> Cancelling {thread.name} {thread.native_id}")
self.logs.debug(f"=======> Checking for Sockets to stop") self.logs.debug(f"=======> Checking for Sockets to stop")
for soc in self.running_sockets: for soc in self.running_sockets:
@@ -657,6 +537,21 @@ class Base:
hostname TEXT, hostname TEXT,
vhost TEXT, vhost TEXT,
password TEXT, password TEXT,
fingerprint TEXT,
level INTEGER
)
'''
table_core_client = f'''CREATE TABLE IF NOT EXISTS {self.Config.TABLE_CLIENT} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
createdOn TEXT,
account TEXT,
nickname TEXT,
hostname TEXT,
vhost TEXT,
realname TEXT,
email TEXT,
password TEXT,
level INTEGER level INTEGER
) )
''' '''
@@ -665,12 +560,13 @@ class Base:
self.db_execute_query(table_core_log_command) self.db_execute_query(table_core_log_command)
self.db_execute_query(table_core_module) self.db_execute_query(table_core_module)
self.db_execute_query(table_core_admin) self.db_execute_query(table_core_admin)
self.db_execute_query(table_core_client)
self.db_execute_query(table_core_channel) self.db_execute_query(table_core_channel)
self.db_execute_query(table_core_config) self.db_execute_query(table_core_config)
if self.install: if self.install:
self.db_record_module('sys', 'mod_command', 1) self.Loader.ModuleUtils.db_register_module('mod_command', 'sys', True)
self.db_record_module('sys', 'mod_defender', 1) self.Loader.ModuleUtils.db_register_module('mod_defender', 'sys', True)
self.install = False self.install = False
return None return None
@@ -695,19 +591,6 @@ class Base:
except AttributeError as ae: except AttributeError as ae:
self.logs.error(f"Attribute Error : {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): def int_if_possible(self, value):
"""Convertit la valeur reçue en entier, si possible. """Convertit la valeur reçue en entier, si possible.
Sinon elle retourne la valeur initiale. Sinon elle retourne la valeur initiale.
@@ -726,14 +609,14 @@ class Base:
except TypeError: except TypeError:
return value 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 """Convert a value to int
Args: Args:
value (any): Value to convert to int if possible value (any): Value to convert to int if possible
Returns: Returns:
Union[int, None]: Return the int value or None if not possible int: Return the int value or None if not possible
""" """
try: try:
response = int(value) response = int(value)
@@ -754,7 +637,27 @@ class Base:
except ValueError: except ValueError:
return False return False
def decode_ip(self, ip_b64encoded: str) -> Union[str, None]: def is_valid_email(self, email_to_control: str) -> bool:
"""Check if the email is valid
Args:
email_to_control (str): email to control
Returns:
bool: True is the email is correct
"""
try:
pattern = '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'
if re.match(pattern, email_to_control):
return True
else:
return False
except Exception as err:
self.logs.error(f'General Error: {err}')
return False
def decode_ip(self, ip_b64encoded: str) -> Optional[str]:
binary_ip = b64decode(ip_b64encoded) binary_ip = b64decode(ip_b64encoded)
try: try:
@@ -765,7 +668,7 @@ class Base:
self.logs.critical(f'This remote ip is not valid : {ve}') self.logs.critical(f'This remote ip is not valid : {ve}')
return None 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) binary_ip = socket.inet_aton(remote_ip_address)
try: try:
@@ -779,15 +682,6 @@ class Base:
self.logs.critical(f'General Error: {err}') self.logs.critical(f'General Error: {err}')
return None 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: def execute_periodic_action(self) -> None:
if not self.periodic_func: if not self.periodic_func:
@@ -807,22 +701,23 @@ class Base:
# Vider le dictionnaire de fonction # Vider le dictionnaire de fonction
self.periodic_func.clear() self.periodic_func.clear()
def clean_uid(self, uid:str) -> Union[str, None]: def execute_dynamic_method(self, obj: object, method_name: str, params: list) -> None:
"""Clean UID by removing @ / % / + / ~ / * / : """#### Ajouter les méthodes a éxecuter dans un dictionnaire
Les methodes seront exécuter par heartbeat.
Args: Args:
uid (str): The UID to clean obj (object): Une instance de la classe qui va etre executer
method_name (str): Le nom de la méthode a executer
params (list): les parametres a faire passer
Returns: Returns:
str: Clean UID without any sign None: aucun retour attendu
""" """
try: self.periodic_func[len(self.periodic_func) + 1] = {
if uid is None: 'object': obj,
'method_name': method_name,
'param': params
}
self.logs.debug(f'Method to execute : {str(self.periodic_func)}')
return 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}')

View File

@@ -1,127 +1,155 @@
from typing import Union from typing import TYPE_CHECKING, Optional
import core.definition as df from core.definition import MAdmin
from core.base import Base
if TYPE_CHECKING:
from core.loader import Loader
class Admin: class Admin:
UID_ADMIN_DB: list[df.MAdmin] = [] UID_ADMIN_DB: list[MAdmin] = []
def __init__(self, baseObj: Base) -> None: def __init__(self, loader: 'Loader') -> None:
self.Logs = baseObj.logs self.Logs = loader.Logs
pass
def insert(self, newAdmin: df.MAdmin) -> bool: def insert(self, new_admin: MAdmin) -> bool:
"""Insert a new admin object model
result = False Args:
exist = False new_admin (MAdmin): The new admin object model to insert
Returns:
bool: True if it was inserted
"""
for record in self.UID_ADMIN_DB: 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') self.Logs.debug(f'{record.uid} already exist')
return result return False
if not exist: self.UID_ADMIN_DB.append(new_admin)
self.UID_ADMIN_DB.append(newAdmin) self.Logs.debug(f'A new admin ({new_admin.nickname}) has been created')
result = True return True
self.Logs.debug(f'UID ({newAdmin.uid}) has been created')
if not result: def update_nickname(self, uid: str, new_admin_nickname: str) -> bool:
self.Logs.critical(f'The User Object was not inserted {newAdmin}') """Update nickname of an admin
return result Args:
uid (str): The Admin UID
new_admin_nickname (str): The new nickname of the admin
def update_nickname(self, uid: str, newNickname: str) -> bool: Returns:
bool: True if the nickname has been updated.
result = False """
for record in self.UID_ADMIN_DB: for record in self.UID_ADMIN_DB:
if record.uid == uid: if record.uid == uid:
# If the admin exist, update and do not go further # 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 {new_admin_nickname}')
self.Logs.debug(f'UID ({record.uid}) has been updated with new nickname {newNickname}') return True
return result
if not result:
self.Logs.critical(f'The new nickname {newNickname} was not updated, uid = {uid}')
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, newLevel: int) -> bool: 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: for record in self.UID_ADMIN_DB:
if record.nickname == nickname: if record.nickname == nickname:
# If the admin exist, update and do not go further # 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 {new_admin_level}')
self.Logs.debug(f'Admin ({record.nickname}) has been updated with new level {newLevel}') return True
return result
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.critical(f'The new level {newLevel} was not updated, nickname = {nickname}')
return result return False
def delete(self, uidornickname: str) -> bool: def delete(self, uidornickname: str) -> bool:
"""Delete admin
result = False 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: for record in self.UID_ADMIN_DB:
if record.uid == uidornickname: if record.uid == uidornickname:
# If the admin exist, delete and do not go further # If the admin exist, delete and do not go further
self.UID_ADMIN_DB.remove(record) self.UID_ADMIN_DB.remove(record)
result = True
self.Logs.debug(f'UID ({record.uid}) has been deleted') self.Logs.debug(f'UID ({record.uid}) has been deleted')
return result return True
if record.nickname == uidornickname: if record.nickname.lower() == uidornickname.lower():
# If the admin exist, delete and do not go further # If the admin exist, delete and do not go further
self.UID_ADMIN_DB.remove(record) self.UID_ADMIN_DB.remove(record)
result = True
self.Logs.debug(f'nickname ({record.nickname}) has been deleted') self.Logs.debug(f'nickname ({record.nickname}) has been deleted')
return result return True
if not result: self.Logs.debug(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[MAdmin]:
"""Get the admin object model
Args:
uidornickname (str): UID or Nickname of the admin
Returns:
Optional[MAdmin]: The MAdmin object model if exist
"""
Admin = None
for record in self.UID_ADMIN_DB: for record in self.UID_ADMIN_DB:
if record.uid == uidornickname: if record.uid == uidornickname:
Admin = record return record
elif record.nickname == uidornickname: elif record.nickname.lower() == uidornickname.lower():
Admin = record return record
#self.Logs.debug(f'Search {uidornickname} -- result = {Admin}') return None
return Admin def get_uid(self, uidornickname:str) -> Optional[str]:
"""Get the UID of the admin
def get_uid(self, uidornickname:str) -> Union[str, None]: Args:
uidornickname (str): The UID or nickname of the admin
Returns:
Optional[str]: The UID of the admin
"""
uid = None
for record in self.UID_ADMIN_DB: for record in self.UID_ADMIN_DB:
if record.uid == uidornickname: if record.uid == uidornickname:
uid = record.uid return record.uid
if record.nickname == uidornickname: if record.nickname.lower() == uidornickname.lower():
uid = record.uid return record.uid
self.Logs.debug(f'The UID that you are looking for {uidornickname} has been found {uid}') return None
return uid
def get_nickname(self, uidornickname:str) -> Union[str, 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
"""
nickname = None
for record in self.UID_ADMIN_DB: for record in self.UID_ADMIN_DB:
if record.nickname == uidornickname: if record.nickname.lower() == uidornickname.lower():
nickname = record.nickname return record.nickname
if record.uid == uidornickname: if record.uid == uidornickname:
nickname = record.nickname return record.nickname
self.Logs.debug(f'The value {uidornickname} -- {nickname}')
return nickname return None

View File

@@ -1,12 +1,9 @@
from re import findall from re import findall
from typing import Union, Literal, TYPE_CHECKING from typing import Any, Optional, Literal, TYPE_CHECKING
from dataclasses import asdict
from core.classes import user
if TYPE_CHECKING: if TYPE_CHECKING:
from core.definition import MChannel from core.definition import MChannel
from core.base import Base from core.loader import Loader
class Channel: class Channel:
@@ -14,18 +11,19 @@ class Channel:
"""List that contains all the Channels objects (ChannelModel) """List that contains all the Channels objects (ChannelModel)
""" """
def __init__(self, baseObj: 'Base') -> None: def __init__(self, loader: 'Loader') -> None:
self.Logs = baseObj.logs self.Logs = loader.Logs
self.Base = baseObj self.Base = loader.Base
self.Utils = loader.Utils
return None 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) """This method will insert a new channel and if the channel exist it will update the user list (uids)
Args: Args:
newChan (ChannelModel): The channel model object new_channel (MChannel): The channel model object
Returns: Returns:
bool: True if new channel, False if channel exist (However UID could be updated) bool: True if new channel, False if channel exist (However UID could be updated)
@@ -33,17 +31,17 @@ class Channel:
result = False result = False
exist = False exist = False
if not self.Is_Channel(newChan.name): if not self.is_valid_channel(new_channel.name):
self.Logs.error(f"The channel {newChan.name} is not valid, channel must start with #") self.Logs.error(f"The channel {new_channel.name} is not valid, channel must start with #")
return False return False
for record in self.UID_CHANNEL_DB: 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 # If the channel exist, update the user list and do not go further
exist = True exist = True
# self.Logs.debug(f'{record.name} already exist') # self.Logs.debug(f'{record.name} already exist')
for user in newChan.uids: for user in new_channel.uids:
record.uids.append(user) record.uids.append(user)
# Supprimer les doublons # Supprimer les doublons
@@ -54,41 +52,57 @@ class Channel:
if not exist: if not exist:
# If the channel don't exist, then create it # If the channel don't exist, then create it
newChan.name = newChan.name.lower() new_channel.name = new_channel.name.lower()
self.UID_CHANNEL_DB.append(newChan) self.UID_CHANNEL_DB.append(new_channel)
result = True result = True
# self.Logs.debug(f'New Channel Created: ({newChan})') # self.Logs.debug(f'New Channel Created: ({new_channel})')
if not result: 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() self.clean_channel()
return result return result
def delete(self, channel_name: str) -> bool: def delete(self, channel_name: str) -> bool:
"""Delete channel from the UID_CHANNEL_DB
chanObj = self.get_Channel(channel_name) Args:
channel_name (str): The Channel name
if chanObj is None: Returns:
bool: True if it was deleted
"""
chan_obj = self.get_channel(channel_name)
if chan_obj is None:
return False return False
self.UID_CHANNEL_DB.remove(chanObj) self.UID_CHANNEL_DB.remove(chan_obj)
return True return True
def delete_user_from_channel(self, channel_name: str, uid: str) -> bool: 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: try:
result = False result = False
chan_obj = self.get_channel(channel_name)
chanObj = self.get_Channel(channel_name.lower()) if chan_obj is None:
if chanObj is None:
return result return result
for userid in chanObj.uids: 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):
chanObj.uids.remove(userid) chan_obj.uids.remove(userid)
result = True result = True
self.clean_channel() self.clean_channel()
@@ -98,14 +112,21 @@ class Channel:
self.Logs.error(f'{ve}') self.Logs.error(f'{ve}')
def delete_user_from_all_channel(self, uid:str) -> bool: 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: try:
result = False result = False
for record in self.UID_CHANNEL_DB: for record in self.UID_CHANNEL_DB:
for user_id in record.uids: 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) record.uids.remove(user_id)
# self.Logs.debug(f'The UID {uid} has been removed, here is the new object: {record}')
result = True result = True
self.clean_channel() self.clean_channel()
@@ -115,104 +136,113 @@ class Channel:
self.Logs.error(f'{ve}') self.Logs.error(f'{ve}')
def add_user_to_a_channel(self, channel_name: str, uid: str) -> bool: 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: try:
result = False chan_obj = self.get_channel(channel_name)
chanObj = 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])) # Create a new channel if the channel don't exist
# self.Logs.debug(f"** {__name__} - result: {result}") self.Logs.debug(f"New channel will be created ({channel_name} - {uid})")
# self.Logs.debug(f'New Channel Created: ({chanObj})') return self.insert(MChannel(channel_name, uids=[uid]))
return result
chanObj.uids.append(uid) chan_obj.uids.append(uid)
del_duplicates = list(set(chanObj.uids)) del_duplicates = list(set(chan_obj.uids))
chanObj.uids = del_duplicates chan_obj.uids = del_duplicates
# self.Logs.debug(f'New Channel Created: ({chanObj})')
return True return True
except Exception as err: except Exception as err:
self.Logs.error(f'{err}') self.Logs.error(f'{err}')
return False
def is_user_present_in_channel(self, channel_name: str, uid: str) -> bool: def is_user_present_in_channel(self, channel_name: str, uid: str) -> bool:
"""Check if a user is present in the channel """Check if a user is present in the channel
Args: Args:
channel_name (str): The channel to check channel_name (str): The channel name to check
uid (str): The UID uid (str): The client UID
Returns: Returns:
bool: True if the user is present in the channel bool: True if the user is present in the channel
""" """
user_found = False chan = self.get_channel(channel_name=channel_name)
chan = self.get_Channel(channel_name=channel_name)
if chan is None: if chan is None:
return user_found return False
clean_uid = self.Base.clean_uid(uid=uid) clean_uid = self.Utils.clean_uid(uid=uid)
for chan_uid in chan.uids: for chan_uid in chan.uids:
if self.Base.clean_uid(chan_uid) == clean_uid: if self.Utils.clean_uid(chan_uid) == clean_uid:
user_found = True return True
break
return user_found return False
def clean_channel(self) -> None: def clean_channel(self) -> None:
"""Remove Channels if empty """If channel doesn't contain any client this method will remove the channel
""" """
try: try:
for record in self.UID_CHANNEL_DB: for record in self.UID_CHANNEL_DB:
if not record.uids: if not record.uids:
self.UID_CHANNEL_DB.remove(record) 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 return None
except Exception as err: except Exception as err:
self.Logs.error(f'{err}') self.Logs.error(f'{err}')
def get_Channel(self, channel_name: str) -> Union['MChannel', None]: def get_channel(self, channel_name: str) -> Optional['MChannel']:
"""Get the channel object
Channel = None
for record in self.UID_CHANNEL_DB:
if record.name == channel_name:
Channel = record
return Channel
def get_Channel_AsDict(self, chan_name: str) -> Union[dict[str, any], None]:
chanObj = self.get_Channel(chan_name=chan_name)
if not chanObj is None:
chan_as_dict = asdict(chanObj)
return chan_as_dict
else:
return None
def Is_Channel(self, channelToCheck: str) -> bool:
"""Check if the string has the # caractere and return True if this is a channel
Args: Args:
channelToCheck (str): The string to test if it is a channel or not 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.lower() == channel_name.lower():
return record
return None
def get_channel_asdict(self, channel_name: str) -> Optional[dict[str, Any]]:
channel_obj: Optional['MChannel'] = self.get_channel(channel_name)
if channel_obj is None:
return None
return channel_obj.to_dict()
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
Returns: Returns:
bool: True if the string is a channel / False if this is not a channel bool: True if the string is a channel / False if this is not a channel
""" """
try: try:
if channelToCheck is None: if channel_to_check is None:
return False return False
pattern = fr'^#' pattern = fr'^#'
isChannel = findall(pattern, channelToCheck) isChannel = findall(pattern, channel_to_check)
if not isChannel: if not isChannel:
return False return False
else: else:
return True return True
except TypeError as te: except TypeError as te:
self.Logs.error(f'TypeError: [{channelToCheck}] - {te}') self.Logs.error(f'TypeError: [{channel_to_check}] - {te}')
except Exception as err: except Exception as err:
self.Logs.error(f'Error Not defined: {err}') self.Logs.error(f'Error Not defined: {err}')
@@ -228,7 +258,7 @@ class Channel:
bool: True if action done bool: True if action done
""" """
try: 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 core_table = self.Base.Config.TABLE_CHANNEL
if not channel_name: if not channel_name:
@@ -240,10 +270,10 @@ class Channel:
case 'add': case 'add':
mes_donnees = {'module_name': module_name, 'channel_name': channel_name} 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) 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} 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) 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: if insert.rowcount:
self.Logs.debug(f'New channel added: channel={channel_name} / module_name={module_name}') self.Logs.debug(f'New channel added: channel={channel_name} / module_name={module_name}')
@@ -266,4 +296,3 @@ class Channel:
except Exception as err: except Exception as err:
self.Logs.error(err) self.Logs.error(err)

239
core/classes/client.py Normal file
View File

@@ -0,0 +1,239 @@
from re import sub
from typing import Any, Optional, Union, TYPE_CHECKING
if TYPE_CHECKING:
from core.loader import Loader
from core.definition import MClient
class Client:
CLIENT_DB: list['MClient'] = []
def __init__(self, loader: 'Loader'):
self.Logs = loader.Logs
self.Base = loader.Base
def insert(self, new_client: 'MClient') -> bool:
"""Insert a new User object
Args:
new_client (MClient): New Client object
Returns:
bool: True if inserted
"""
client_obj = self.get_Client(new_client.uid)
if not client_obj is None:
# User already created return False
return False
self.CLIENT_DB.append(new_client)
return True
def update_nickname(self, uid: str, newNickname: str) -> bool:
"""Update the nickname starting from the UID
Args:
uid (str): UID of the user
newNickname (str): New nickname
Returns:
bool: True if updated
"""
user_obj = self.get_Client(uidornickname=uid)
if user_obj is None:
return False
user_obj.nickname = newNickname
return True
def update_mode(self, uidornickname: str, modes: str) -> bool:
"""Updating user mode
Args:
uidornickname (str): The UID or Nickname of the user
modes (str): new modes to update
Returns:
bool: True if user mode has been updaed
"""
response = True
user_obj = self.get_Client(uidornickname=uidornickname)
if user_obj is None:
return False
action = modes[0]
new_modes = modes[1:]
existing_umodes = user_obj.umodes
umodes = user_obj.umodes
if action == '+':
for nm in new_modes:
if nm not in existing_umodes:
umodes += nm
elif action == '-':
for nm in new_modes:
if nm in existing_umodes:
umodes = umodes.replace(nm, '')
else:
return False
liste_umodes = list(umodes)
final_umodes_liste = [x for x in self.Base.Settings.PROTOCTL_USER_MODES if x in liste_umodes]
final_umodes = ''.join(final_umodes_liste)
user_obj.umodes = f"+{final_umodes}"
return response
def delete(self, uid: str) -> bool:
"""Delete the User starting from the UID
Args:
uid (str): UID of the user
Returns:
bool: True if deleted
"""
user_obj = self.get_Client(uidornickname=uid)
if user_obj is None:
return False
self.CLIENT_DB.remove(user_obj)
return True
def get_Client(self, uidornickname: str) -> Optional['MClient']:
"""Get The Client Object model
Args:
uidornickname (str): UID or Nickname
Returns:
UserModel|None: The UserModel Object | None
"""
for record in self.CLIENT_DB:
if record.uid == uidornickname:
return record
elif record.nickname == uidornickname:
return record
return None
def get_uid(self, uidornickname:str) -> Optional[str]:
"""Get the UID of the user starting from the UID or the Nickname
Args:
uidornickname (str): UID or Nickname
Returns:
str|None: Return the UID
"""
client_obj = self.get_Client(uidornickname=uidornickname)
if client_obj is None:
return None
return client_obj.uid
def get_nickname(self, uidornickname:str) -> Union[str, None]:
"""Get the Nickname starting from UID or the nickname
Args:
uidornickname (str): UID or Nickname of the user
Returns:
str|None: the nickname
"""
client_obj = self.get_Client(uidornickname=uidornickname)
if client_obj is None:
return None
return client_obj.nickname
def get_client_asdict(self, uidornickname: str) -> Optional[dict[str, Any]]:
"""Transform User Object to a dictionary
Args:
uidornickname (str): The UID or The nickname
Returns:
Union[dict[str, any], None]: User Object as a dictionary or None
"""
client_obj = self.get_Client(uidornickname=uidornickname)
if client_obj is None:
return None
return client_obj.to_dict()
def is_exist(self, uidornikname: str) -> bool:
"""Check if the UID or the nickname exist in the USER DB
Args:
uidornickname (str): The UID or the NICKNAME
Returns:
bool: True if exist
"""
user_obj = self.get_Client(uidornickname=uidornikname)
if user_obj is None:
return False
return True
def db_is_account_exist(self, account: str) -> bool:
"""Check if the account exist in the database
Args:
account (str): The account to check
Returns:
bool: True if exist
"""
table_client = self.Base.Config.TABLE_CLIENT
account_to_check = {'account': account.lower()}
account_to_check_query = self.Base.db_execute_query(f"""
SELECT id FROM {table_client} WHERE LOWER(account) = :account
""", account_to_check)
account_to_check_result = account_to_check_query.fetchone()
if account_to_check_result:
self.Logs.error(f"Account ({account}) already exist")
return True
return False
def clean_uid(self, uid: str) -> Union[str, None]:
"""Clean UID by removing @ / % / + / ~ / * / :
Args:
uid (str): The UID to clean
Returns:
str: Clean UID without any sign
"""
pattern = fr'[:|@|%|\+|~|\*]*'
parsed_UID = sub(pattern, '', uid)
if not parsed_UID:
return None
return parsed_UID

View File

@@ -1,161 +0,0 @@
from dataclasses import asdict
from core.definition import MClone
from typing import Union
from core.base import Base
class Clone:
UID_CLONE_DB: list[MClone] = []
def __init__(self, baseObj: Base) -> None:
self.Logs = baseObj.logs
return None
def insert(self, newCloneObject: MClone) -> bool:
"""Create new Clone object
Args:
newCloneObject (CloneModel): New CloneModel object
Returns:
bool: True if inserted
"""
result = False
exist = False
for record in self.UID_CLONE_DB:
if record.nickname == newCloneObject.nickname:
# If the user exist then return False and do not go further
exist = True
self.Logs.warning(f'Nickname {record.nickname} already exist')
return result
if record.uid == newCloneObject.uid:
exist = True
self.Logs.warning(f'UID: {record.uid} already exist')
return result
if not exist:
self.UID_CLONE_DB.append(newCloneObject)
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}')
return result
def delete(self, uidornickname: str) -> bool:
"""Delete the Clone Object starting from the nickname or the UID
Args:
uidornickname (str): UID or nickname of the clone
Returns:
bool: True if deleted
"""
cloneObj = self.get_Clone(uidornickname=uidornickname)
if cloneObj is None:
return False
self.UID_CLONE_DB.remove(cloneObj)
return True
def exists(self, nickname: str) -> bool:
"""Check if the nickname exist
Args:
nickname (str): Nickname of the clone
Returns:
bool: True if the nickname exist
"""
response = False
for cloneObject in self.UID_CLONE_DB:
if cloneObject.nickname == nickname:
response = True
return response
def uid_exists(self, uid: str) -> bool:
"""Check if the nickname exist
Args:
uid (str): uid of the clone
Returns:
bool: True if the nickname exist
"""
response = 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]:
"""Get MClone object or None
Args:
uidornickname (str): The UID or the Nickname
Returns:
Union[MClone, None]: Return MClone object or None
"""
cloneObj = None
for clone in self.UID_CLONE_DB:
if clone.uid == uidornickname:
cloneObj = clone
if clone.nickname == uidornickname:
cloneObj = clone
return cloneObj
def get_uid(self, uidornickname: str) -> Union[str, None]:
"""Get the UID of the clone starting from the UID or the Nickname
Args:
uidornickname (str): UID or Nickname
Returns:
str|None: Return the UID
"""
uid = None
for record in self.UID_CLONE_DB:
if record.uid == uidornickname:
uid = record.uid
if record.nickname == uidornickname:
uid = record.uid
# if not uid is None:
# self.Logs.debug(f'The UID that you are looking for {uidornickname} has been found {uid}')
return uid
def get_Clone_AsDict(self, uidornickname: str) -> Union[dict[str, any], None]:
cloneObj = self.get_Clone(uidornickname=uidornickname)
if not cloneObj is None:
cloneObj_as_dict = asdict(cloneObj)
return cloneObj_as_dict
else:
return None
def kill(self, nickname:str) -> bool:
response = False
for cloneObject in self.UID_CLONE_DB:
if cloneObject.nickname == nickname:
cloneObject.alive = False # Kill the clone
response = True
return response

83
core/classes/commands.py Normal file
View File

@@ -0,0 +1,83 @@
from typing import TYPE_CHECKING, Optional
from core.definition import MCommand
if TYPE_CHECKING:
from core.loader import Loader
class Command:
DB_COMMANDS: list['MCommand'] = []
def __init__(self, loader: 'Loader'):
self.Loader = loader
self.Base = loader.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
def is_client_allowed_to_run_command(self, nickname: str, command_name: str) -> bool:
admin = self.Loader.Admin.get_admin(nickname)
admin_level = admin.level if admin else 0
commands = self.get_commands_by_level(admin_level)
if command_name in [command.command_name for command in commands]:
return True
return False
def is_command_exist(self, command_name: str) -> bool:
"""Check if the command name exist
Args:
command_name (str): The command name you want to check
Returns:
bool: True if the command exist
"""
if command_name.lower() in [command.command_name.lower() for command in self.get_ordered_commands()]:
return True
return False

View File

@@ -1,19 +1,28 @@
from json import load from json import load
from sys import exit from sys import exit
from os import sep from os import sep
from typing import Union from typing import Any, Optional, Union, TYPE_CHECKING
from core.definition import MConfig from core.definition import MConfig
if TYPE_CHECKING:
from core.loader import Loader
class Configuration: class Configuration:
def __init__(self) -> None: def __init__(self, loader: 'Loader') -> None:
self.ConfigObject: MConfig = self.__load_service_configuration() self.Loader = loader
self.Logs = loader.Logs
self._config_model: MConfig = self.__load_service_configuration()
loader.ServiceLogging.set_file_handler_level(self._config_model.DEBUG_LEVEL)
loader.ServiceLogging.set_stdout_handler_level(self._config_model.DEBUG_LEVEL)
loader.ServiceLogging.update_handler_format(self._config_model.DEBUG_HARD)
return None return None
def __load_json_service_configuration(self): def get_config_model(self) -> MConfig:
return self._config_model
def __load_json_service_configuration(self) -> Optional[dict[str, Any]]:
try: try:
conf_filename = f'config{sep}configuration.json' conf_filename = f'config{sep}configuration.json'
with open(conf_filename, 'r') as configuration_data: with open(conf_filename, 'r') as configuration_data:
@@ -22,18 +31,18 @@ class Configuration:
return configuration return configuration
except FileNotFoundError as fe: except FileNotFoundError as fe:
print(f'FileNotFound: {fe}') self.Logs.error(f'FileNotFound: {fe}')
print('Configuration file not found please create config/configuration.json') self.Logs.error('Configuration file not found please create config/configuration.json')
exit(0) exit(0)
except KeyError as ke: except KeyError as ke:
print(f'Key Error: {ke}') self.Logs.error(f'Key Error: {ke}')
print('The key must be defined in core/configuration.json') self.Logs.error('The key must be defined in core/configuration.json')
def __load_service_configuration(self) -> MConfig: def __load_service_configuration(self) -> MConfig:
try: try:
import_config = self.__load_json_service_configuration() import_config = self.__load_json_service_configuration()
Model_keys = MConfig().__dict__ Model_keys = MConfig().to_dict()
model_key_list: list = [] model_key_list: list = []
json_config_key_list: list = [] json_config_key_list: list = []
@@ -46,12 +55,10 @@ class Configuration:
for json_conf in json_config_key_list: for json_conf in json_config_key_list:
if not json_conf in model_key_list: if not json_conf in model_key_list:
import_config.pop(json_conf, None) 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( self.Logs.debug(f"[LOADING CONFIGURATION]: Loading configuration with {len(import_config)} parameters!")
**import_config return MConfig(**import_config)
)
return ConfigObject
except TypeError as te: except TypeError as te:
print(te) self.Logs.error(te)

View File

@@ -14,8 +14,10 @@ class Inspircd:
self.__Irc = ircInstance self.__Irc = ircInstance
self.__Config = ircInstance.Config self.__Config = ircInstance.Config
self.__Base = ircInstance.Base 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: def send2socket(self, message: str, print_log: bool = True) -> None:
"""Envoit les commandes à envoyer au serveur. """Envoit les commandes à envoyer au serveur.
@@ -27,24 +29,24 @@ class Inspircd:
with self.__Base.lock: with self.__Base.lock:
self.__Irc.IrcSocket.send(f"{message}\r\n".encode(self.__Config.SERVEUR_CHARSET[0])) self.__Irc.IrcSocket.send(f"{message}\r\n".encode(self.__Config.SERVEUR_CHARSET[0]))
if print_log: if print_log:
self.__Base.logs.debug(f'<< {message}') self.__Logs.debug(f'<< {message}')
except UnicodeDecodeError as ude: 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')) self.__Irc.IrcSocket.send(f"{message}\r\n".encode(self.__Config.SERVEUR_CHARSET[1],'replace'))
except UnicodeEncodeError as uee: 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')) self.__Irc.IrcSocket.send(f"{message}\r\n".encode(self.__Config.SERVEUR_CHARSET[1],'replace'))
except AssertionError as ae: 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: except SSLEOFError as soe:
self.__Base.logs.error(f"SSLEOFError: {soe} - {message}") self.__Logs.error(f"SSLEOFError: {soe} - {message}")
except SSLError as se: except SSLError as se:
self.__Base.logs.error(f"SSLError: {se} - {message}") self.__Logs.error(f"SSLError: {se} - {message}")
except OSError as oe: except OSError as oe:
self.__Base.logs.error(f"OSError: {oe} - {message}") self.__Logs.error(f"OSError: {oe} - {message}")
except AttributeError as ae: 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): 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 """Sending PRIVMSG to a channel or to a nickname by batches
@@ -57,11 +59,11 @@ class Inspircd:
""" """
try: try:
batch_size = self.__Config.BATCH_SIZE batch_size = self.__Config.BATCH_SIZE
User_from = self.__Irc.User.get_User(nick_from) User_from = self.__Irc.User.get_user(nick_from)
User_to = self.__Irc.User.get_User(nick_to) if nick_to is None else None User_to = self.__Irc.User.get_user(nick_to) if nick_to is None else None
if User_from is 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 return None
if not channel is None: if not channel is None:
@@ -74,7 +76,7 @@ class Inspircd:
batch = str(msg)[i:i+batch_size] batch = str(msg)[i:i+batch_size]
self.send2socket(f":{nick_from} PRIVMSG {User_to.uid} :{batch}") self.send2socket(f":{nick_from} PRIVMSG {User_to.uid} :{batch}")
except Exception as err: 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: def send_notice(self, nick_from: str, nick_to: str, msg: str) -> None:
"""Sending NOTICE by batches """Sending NOTICE by batches
@@ -86,11 +88,11 @@ class Inspircd:
""" """
try: try:
batch_size = self.__Config.BATCH_SIZE batch_size = self.__Config.BATCH_SIZE
User_from = self.__Irc.User.get_User(nick_from) User_from = self.__Irc.User.get_user(nick_from)
User_to = self.__Irc.User.get_User(nick_to) User_to = self.__Irc.User.get_user(nick_to)
if User_from is None or User_to is None: 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 return None
for i in range(0, len(str(msg)), batch_size): for i in range(0, len(str(msg)), batch_size):
@@ -98,9 +100,9 @@ class Inspircd:
self.send2socket(f":{User_from.uid} NOTICE {User_to.uid} :{batch}") self.send2socket(f":{User_from.uid} NOTICE {User_to.uid} :{batch}")
except Exception as err: 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 """Créer le link et envoyer les informations nécessaires pour la
connexion au serveur. connexion au serveur.
""" """
@@ -122,7 +124,7 @@ class Inspircd:
service_id = self.__Config.SERVICE_ID service_id = self.__Config.SERVICE_ID
version = self.__Config.CURRENT_VERSION version = self.__Config.CURRENT_VERSION
unixtime = self.__Base.get_unixtime() unixtime = self.__Utils.get_unixtime()
self.send2socket(f"CAPAB START 1206") self.send2socket(f"CAPAB START 1206")
@@ -132,7 +134,7 @@ class Inspircd:
self.send2socket(f"BURST {unixtime}") self.send2socket(f"BURST {unixtime}")
self.send2socket(f":{server_id} ENDBURST") 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: 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 # TKL + G user host set_by expire_timestamp set_at_timestamp :reason
@@ -141,12 +143,12 @@ class Inspircd:
return None 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}") self.send2socket(f":{self.__Config.SERVICE_NICKNAME} NICK {newnickname}")
return None 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: if not reason:
reason = 'Service Shutdown' reason = 'Service Shutdown'
@@ -154,26 +156,26 @@ class Inspircd:
self.send2socket(f":{server_id} SQUIT {server_link} :{reason}") self.send2socket(f":{server_id} SQUIT {server_link} :{reason}")
return None 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}") self.send2socket(f":{self.__Config.SERVEUR_ID} TKL - G {nickname} {hostname} {self.__Config.SERVICE_NICKNAME}")
return None 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 # 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}") self.send2socket(f":{self.__Config.SERVEUR_ID} TKL + k {nickname} {hostname} {set_by} {expire_timestamp} {set_at_timestamp} :{reason}")
return None return None
def sjoin(self, channel: str) -> None: def send_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") self.__Logs.error(f"The channel [{channel}] is not valid")
return None 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 # Add defender to the channel uids list
self.__Irc.Channel.insert(self.__Irc.Loader.Definition.MChannel(name=channel, uids=[self.__Config.SERVICE_ID])) self.__Irc.Channel.insert(self.__Irc.Loader.Definition.MChannel(name=channel, uids=[self.__Config.SERVICE_ID]))
@@ -186,22 +188,18 @@ class Inspircd:
uidornickname (str): The UID or the Nickname uidornickname (str): The UID or the Nickname
reason (str): The reason for the quit reason (str): The reason for the quit
""" """
userObj = self.__Irc.User.get_User(uidornickname=uid) user_obj = self.__Irc.User.get_user(uidornickname=uid)
cloneObj = self.__Irc.Clone.get_Clone(uidornickname=uid)
reputationObj = self.__Irc.Reputation.get_Reputation(uidornickname=uid) reputationObj = self.__Irc.Reputation.get_Reputation(uidornickname=uid)
if not userObj is None: if not user_obj is None:
self.send2socket(f":{userObj.uid} QUIT :{reason}", print_log=print_log) self.send2socket(f":{user_obj.uid} QUIT :{reason}", print_log=print_log)
self.__Irc.User.delete(userObj.uid) self.__Irc.User.delete(user_obj.uid)
if not cloneObj is None:
self.__Irc.Clone.delete(cloneObj.uid)
if not reputationObj is None: if not reputationObj is None:
self.__Irc.Reputation.delete(reputationObj.uid) self.__Irc.Reputation.delete(reputationObj.uid)
if not self.__Irc.Channel.delete_user_from_all_channel(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 return None
@@ -220,9 +218,9 @@ class Inspircd:
print_log (bool, optional): print logs if true. Defaults to True. print_log (bool, optional): print logs if true. Defaults to True.
""" """
# {self.Config.SERVEUR_ID} UID # {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: try:
unixtime = self.__Base.get_unixtime() unixtime = self.__Utils.get_unixtime()
encoded_ip = self.__Base.encode_ip(remote_ip) encoded_ip = self.__Base.encode_ip(remote_ip)
# Create the user # Create the user
@@ -241,7 +239,7 @@ class Inspircd:
return None return None
except Exception as err: 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: def send_join_chan(self, uidornickname: str, channel: str, password: str = None, print_log: bool = True) -> None:
"""Joining a channel """Joining a channel
@@ -253,14 +251,14 @@ class Inspircd:
print_log (bool, optional): Write logs. Defaults to True. print_log (bool, optional): Write logs. Defaults to True.
""" """
userObj = self.__Irc.User.get_User(uidornickname) userObj = self.__Irc.User.get_user(uidornickname)
passwordChannel = password if not password is None else '' passwordChannel = password if not password is None else ''
if userObj is None: if userObj is None:
return 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") self.__Logs.error(f"The channel [{channel}] is not valid")
return None return None
self.send2socket(f":{userObj.uid} JOIN {channel} {passwordChannel}", print_log=print_log) self.send2socket(f":{userObj.uid} JOIN {channel} {passwordChannel}", print_log=print_log)
@@ -278,14 +276,14 @@ class Inspircd:
print_log (bool, optional): Write logs. Defaults to True. print_log (bool, optional): Write logs. Defaults to True.
""" """
userObj = self.__Irc.User.get_User(uidornickname) userObj = self.__Irc.User.get_user(uidornickname)
if userObj is None: 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 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") self.__Logs.error(f"The channel [{channel}] is not valid")
return None return None
self.send2socket(f":{userObj.uid} PART {channel}", print_log=print_log) self.send2socket(f":{userObj.uid} PART {channel}", print_log=print_log)
@@ -294,7 +292,7 @@ class Inspircd:
self.__Irc.Channel.delete_user_from_channel(channel, userObj.uid) self.__Irc.Channel.delete_user_from_channel(channel, userObj.uid)
return None 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}") self.send2socket(f":{self.__Config.SERVEUR_ID} TKL - K {nickname} {hostname} {self.__Config.SERVICE_NICKNAME}")
@@ -309,7 +307,7 @@ class Inspircd:
try: try:
# [':adator_', 'UMODE2', '-iwx'] # [':adator_', 'UMODE2', '-iwx']
userObj = self.__Irc.User.get_User(str(serverMsg[0]).lstrip(':')) userObj = self.__Irc.User.get_user(str(serverMsg[0]).lstrip(':'))
userMode = serverMsg[2] userMode = serverMsg[2]
if userObj is None: # If user is not created if userObj is None: # If user is not created
@@ -321,14 +319,14 @@ class Inspircd:
# TODO : User object should be able to update user modes # TODO : User object should be able to update user modes
if self.__Irc.User.update_mode(userObj.uid, userMode): if self.__Irc.User.update_mode(userObj.uid, userMode):
return None 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 return None
except IndexError as ie: 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: 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: def on_quit(self, serverMsg: list[str]) -> None:
"""Handle quit coming from a server """Handle quit coming from a server
@@ -344,14 +342,13 @@ class Inspircd:
self.__Irc.Channel.delete_user_from_all_channel(uid_who_quit) self.__Irc.Channel.delete_user_from_all_channel(uid_who_quit)
self.__Irc.User.delete(uid_who_quit) self.__Irc.User.delete(uid_who_quit)
self.__Irc.Reputation.delete(uid_who_quit) self.__Irc.Reputation.delete(uid_who_quit)
self.__Irc.Clone.delete(uid_who_quit)
return None return None
except IndexError as ie: 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: 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: def on_squit(self, serverMsg: list[str]) -> None:
"""Handle squit coming from a server """Handle squit coming from a server
@@ -401,14 +398,16 @@ class Inspircd:
uid = str(serverMsg[1]).lstrip(':') uid = str(serverMsg[1]).lstrip(':')
newnickname = serverMsg[3] newnickname = serverMsg[3]
self.__Irc.User.update(uid, newnickname) self.__Irc.User.update_nickname(uid, newnickname)
self.__Irc.Client.update_nickname(uid, newnickname)
self.__Irc.Admin.update_nickname(uid, newnickname)
return None return None
except IndexError as ie: 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: 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: def on_sjoin(self, serverMsg: list[str]) -> None:
"""Handle sjoin coming from a server """Handle sjoin coming from a server
@@ -441,7 +440,7 @@ class Inspircd:
# Boucle qui va ajouter l'ensemble des users (UID) # Boucle qui va ajouter l'ensemble des users (UID)
for i in range(start_boucle, len(serverMsg)): for i in range(start_boucle, len(serverMsg)):
parsed_UID = str(serverMsg[i]) 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: if not clean_uid is None and len(clean_uid) == 9:
list_users.append(parsed_UID) list_users.append(parsed_UID)
@@ -455,9 +454,9 @@ class Inspircd:
return None return None
except IndexError as ie: 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: 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: def on_part(self, serverMsg: list[str]) -> None:
"""Handle part coming from a server """Handle part coming from a server
@@ -476,9 +475,9 @@ class Inspircd:
return None return None
except IndexError as ie: 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: 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: def on_uid(self, serverMsg: list[str]) -> None:
"""Handle uid message coming from the server """Handle uid message coming from the server
@@ -539,9 +538,9 @@ class Inspircd:
) )
return None return None
except IndexError as ie: 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: 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: def on_server_ping(self, serverMsg: list[str]) -> None:
"""Send a PONG message to the server """Send a PONG message to the server
@@ -559,7 +558,7 @@ class Inspircd:
return None return None
except Exception as err: 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: def on_version(self, serverMsg: list[str]) -> None:
"""Sending Server Version to the server """Sending Server Version to the server
@@ -571,7 +570,7 @@ class Inspircd:
# Réponse a un CTCP VERSION # Réponse a un CTCP VERSION
try: 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 dnickname = self.__Config.SERVICE_NICKNAME
arg = serverMsg[4].replace(':', '') arg = serverMsg[4].replace(':', '')
@@ -583,7 +582,7 @@ class Inspircd:
return None return None
except Exception as err: 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: def on_time(self, serverMsg: list[str]) -> None:
"""Sending TIME answer to a requestor """Sending TIME answer to a requestor
@@ -595,10 +594,10 @@ class Inspircd:
# Réponse a un CTCP VERSION # Réponse a un CTCP VERSION
try: 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 dnickname = self.__Config.SERVICE_NICKNAME
arg = serverMsg[4].replace(':', '') arg = serverMsg[4].replace(':', '')
current_datetime = self.__Base.get_datetime() current_datetime = self.__Utils.get_sdatetime()
if nickname is None: if nickname is None:
return None return None
@@ -608,7 +607,7 @@ class Inspircd:
return None return None
except Exception as err: 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: def on_ping(self, serverMsg: list[str]) -> None:
"""Sending a PING answer to requestor """Sending a PING answer to requestor
@@ -620,7 +619,7 @@ class Inspircd:
# Réponse a un CTCP VERSION # Réponse a un CTCP VERSION
try: 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 dnickname = self.__Config.SERVICE_NICKNAME
arg = serverMsg[4].replace(':', '') arg = serverMsg[4].replace(':', '')
@@ -629,7 +628,7 @@ class Inspircd:
if arg == '\x01PING': if arg == '\x01PING':
recieved_unixtime = int(serverMsg[5].replace('\x01','')) 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 ping_response = current_unixtime - recieved_unixtime
# self.__Irc.send2socket(f':{dnickname} NOTICE {nickname} :\x01PING {ping_response} secs\x01') # self.__Irc.send2socket(f':{dnickname} NOTICE {nickname} :\x01PING {ping_response} secs\x01')
@@ -641,7 +640,7 @@ class Inspircd:
return None return None
except Exception as err: 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: def on_version_msg(self, serverMsg: list[str]) -> None:
"""Handle version coming from the server """Handle version coming from the server
@@ -651,7 +650,7 @@ class Inspircd:
""" """
try: try:
# ['@label=0073', ':0014E7P06', 'VERSION', 'PyDefender'] # ['@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: if getUser is None:
return None return None
@@ -659,11 +658,11 @@ class Inspircd:
response_351 = f"{self.__Config.SERVICE_NAME.capitalize()}-{self.__Config.CURRENT_VERSION} {self.__Config.SERVICE_HOST} {self.name}" response_351 = f"{self.__Config.SERVICE_NAME.capitalize()}-{self.__Config.CURRENT_VERSION} {self.__Config.SERVICE_HOST} {self.name}"
self.send2socket(f':{self.__Config.SERVICE_HOST} 351 {getUser.nickname} {response_351}') self.send2socket(f':{self.__Config.SERVICE_HOST} 351 {getUser.nickname} {response_351}')
modules = self.__Base.get_all_modules() modules = self.__Irc.ModuleUtils.get_all_available_modules()
response_005 = ' | '.join(modules) response_005 = ' | '.join(modules)
self.send2socket(f':{self.__Config.SERVICE_HOST} 005 {getUser.nickname} {response_005} are supported by this server') self.send2socket(f':{self.__Config.SERVICE_HOST} 005 {getUser.nickname} {response_005} are supported by this server')
return None return None
except Exception as err: except Exception as err:
self.__Base.logs.error(f"{__name__} - General Error: {err}") self.__Logs.error(f"{__name__} - General Error: {err}")

File diff suppressed because it is too large Load Diff

115
core/classes/rehash.py Normal file
View File

@@ -0,0 +1,115 @@
import importlib
import sys
import time
from typing import TYPE_CHECKING
import socket
from core.classes.protocol import Protocol
if TYPE_CHECKING:
from core.irc import Irc
# Modules impacted by rehashing!
REHASH_MODULES = [
'core.definition',
'core.utils',
'core.classes.config',
'core.base',
'core.classes.commands',
'core.classes.protocols.unreal6',
'core.classes.protocols.inspircd',
'core.classes.protocol'
]
def restart_service(uplink: 'Irc', reason: str = "Restarting with no reason!") -> None:
# reload modules.
for module in uplink.ModuleUtils.model_get_loaded_modules().copy():
uplink.ModuleUtils.unload_one_module(uplink, module.module_name)
uplink.ModuleUtils.model_clear() # Clear loaded modules.
uplink.User.UID_DB.clear() # Clear User Object
uplink.Channel.UID_CHANNEL_DB.clear() # Clear Channel Object
uplink.Client.CLIENT_DB.clear() # Clear Client object
uplink.Base.garbage_collector_thread()
# Reload configuration
uplink.Config = uplink.Loader.ConfModule.Configuration(uplink.Loader).get_config_model()
uplink.Base = uplink.Loader.BaseModule.Base(uplink.Loader)
uplink.Protocol = Protocol(uplink.Config.SERVEUR_PROTOCOL, uplink.ircObject).Protocol
uplink.Logs.debug(f'[{uplink.Config.SERVICE_NICKNAME} RESTART]: Reloading configuration!')
uplink.Protocol.send_squit(server_id=uplink.Config.SERVEUR_ID, server_link=uplink.Config.SERVEUR_LINK, reason="Defender Power off")
uplink.Logs.debug('Restarting Defender ...')
uplink.IrcSocket.shutdown(socket.SHUT_RDWR)
uplink.IrcSocket.close()
while uplink.IrcSocket.fileno() != -1:
time.sleep(0.5)
uplink.Logs.warning('-- Waiting for socket to close ...')
uplink.init_service_user()
uplink.Utils.create_socket(uplink)
uplink.Protocol.send_link()
uplink.join_saved_channels()
uplink.ModuleUtils.db_load_all_existing_modules(uplink)
uplink.Config.DEFENDER_RESTART = 0
def rehash_service(uplink: 'Irc', nickname: str) -> None:
need_a_restart = ["SERVEUR_ID"]
uplink.Settings.set_cache('db_commands', uplink.Commands.DB_COMMANDS)
restart_flag = False
config_model_bakcup = uplink.Config
mods = REHASH_MODULES
for mod in mods:
importlib.reload(sys.modules[mod])
uplink.Protocol.send_priv_msg(
nick_from=uplink.Config.SERVICE_NICKNAME,
msg=f'[REHASH] Module [{mod}] reloaded',
channel=uplink.Config.SERVICE_CHANLOG
)
uplink.Config = uplink.Loader.ConfModule.Configuration(uplink.Loader).get_config_model()
uplink.Config.HSID = config_model_bakcup.HSID
uplink.Config.DEFENDER_INIT = config_model_bakcup.DEFENDER_INIT
uplink.Config.DEFENDER_RESTART = config_model_bakcup.DEFENDER_RESTART
uplink.Config.SSL_VERSION = config_model_bakcup.SSL_VERSION
uplink.Config.CURRENT_VERSION = config_model_bakcup.CURRENT_VERSION
uplink.Config.LATEST_VERSION = config_model_bakcup.LATEST_VERSION
conf_bkp_dict: dict = config_model_bakcup.to_dict()
config_dict: dict = uplink.Config.to_dict()
for key, value in conf_bkp_dict.items():
if config_dict[key] != value and key != 'COLORS':
uplink.Protocol.send_priv_msg(
nick_from=uplink.Config.SERVICE_NICKNAME,
msg=f'[{key}]: {value} ==> {config_dict[key]}',
channel=uplink.Config.SERVICE_CHANLOG
)
if key in need_a_restart:
restart_flag = True
if config_model_bakcup.SERVICE_NICKNAME != uplink.Config.SERVICE_NICKNAME:
uplink.Protocol.send_set_nick(uplink.Config.SERVICE_NICKNAME)
if restart_flag:
uplink.Config.SERVEUR_ID = config_model_bakcup.SERVEUR_ID
uplink.Protocol.send_priv_msg(
nick_from=uplink.Config.SERVICE_NICKNAME,
channel=uplink.Config.SERVICE_CHANLOG,
msg='You need to restart defender !')
# Reload Main Commands Module
uplink.Commands = uplink.Loader.CommandModule.Command(uplink.Loader)
uplink.Commands.DB_COMMANDS = uplink.Settings.get_cache('db_commands')
uplink.Base = uplink.Loader.BaseModule.Base(uplink.Loader)
uplink.Protocol = Protocol(uplink.Config.SERVEUR_PROTOCOL, uplink.ircObject).Protocol
# Reload Service modules
for module in uplink.ModuleUtils.model_get_loaded_modules().copy():
uplink.ModuleUtils.reload_one_module(uplink, module.module_name, nickname)
return None

View File

@@ -1,23 +1,23 @@
from typing import Union from typing import TYPE_CHECKING, Optional
from core.definition import MReputation from core.definition import MReputation
from core.base import Base
if TYPE_CHECKING:
from core.loader import Loader
class Reputation: class Reputation:
UID_REPUTATION_DB: list[MReputation] = [] UID_REPUTATION_DB: list[MReputation] = []
def __init__(self, baseObj: Base) -> None: def __init__(self, loader: 'Loader'):
self.Logs = baseObj.logs self.Logs = loader.Logs
self.MReputation: MReputation = MReputation self.MReputation: MReputation = MReputation
return None def insert(self, new_reputation_user: MReputation) -> bool:
def insert(self, newReputationUser: MReputation) -> bool:
"""Insert a new Reputation User object """Insert a new Reputation User object
Args: Args:
newReputationUser (MReputation): New Reputation Model object new_reputation_user (MReputation): New Reputation Model object
Returns: Returns:
bool: True if inserted bool: True if inserted
@@ -26,23 +26,23 @@ class Reputation:
exist = False exist = False
for record in self.UID_REPUTATION_DB: 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 # If the user exist then return False and do not go further
exist = True exist = True
self.Logs.debug(f'{record.uid} already exist') self.Logs.debug(f'{record.uid} already exist')
return result return result
if not exist: if not exist:
self.UID_REPUTATION_DB.append(newReputationUser) self.UID_REPUTATION_DB.append(new_reputation_user)
result = True 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: 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 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 """Update the nickname starting from the UID
Args: Args:
@@ -53,12 +53,12 @@ class Reputation:
bool: True if updated 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 return False
reputationObj.nickname = newNickname reputation_obj.nickname = new_nickname
return True return True
@@ -89,7 +89,7 @@ class Reputation:
return result return result
def get_Reputation(self, uidornickname: str) -> Union[MReputation, None]: def get_Reputation(self, uidornickname: str) -> Optional[MReputation]:
"""Get The User Object model """Get The User Object model
Args: Args:
@@ -98,16 +98,15 @@ class Reputation:
Returns: Returns:
UserModel|None: The UserModel Object | None UserModel|None: The UserModel Object | None
""" """
User = None
for record in self.UID_REPUTATION_DB: for record in self.UID_REPUTATION_DB:
if record.uid == uidornickname: if record.uid == uidornickname:
User = record return record
elif record.nickname == uidornickname: 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 """Get the UID of the user starting from the UID or the Nickname
Args: Args:
@@ -117,14 +116,14 @@ class Reputation:
str|None: Return the UID 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 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 """Get the Nickname starting from UID or the nickname
Args: Args:
@@ -133,12 +132,12 @@ class Reputation:
Returns: Returns:
str|None: the nickname 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 None
return reputationObj.nickname return reputation_obj.nickname
def is_exist(self, uidornickname: str) -> bool: def is_exist(self, uidornickname: str) -> bool:
"""Check if the UID or the nickname exist in the reputation DB """Check if the UID or the nickname exist in the reputation DB
@@ -150,9 +149,9 @@ class Reputation:
bool: True if exist bool: True if exist
""" """
reputationObj = self.get_Reputation(uidornickname) reputation_obj = self.get_Reputation(uidornickname)
if reputationObj is None: if isinstance(reputation_obj, MReputation):
return False
else:
return True return True
return False

70
core/classes/sasl.py Normal file
View File

@@ -0,0 +1,70 @@
from typing import Optional, Union, TYPE_CHECKING
if TYPE_CHECKING:
from core.definition import MSasl
from core.loader import Loader
class Sasl:
DB_SASL: list['MSasl'] = []
def __init__(self, loader: 'Loader'):
self.Logs = loader.Logs # logger
def insert_sasl_client(self, psasl: 'MSasl') -> bool:
"""Insert a new Sasl authentication
Args:
new_user (UserModel): New userModel object
Returns:
bool: True if inserted
"""
if psasl is None:
return False
sasl_obj = self.get_sasl_obj(psasl.client_uid)
if sasl_obj is not None:
# User already created return False
return False
self.DB_SASL.append(psasl)
return True
def delete_sasl_client(self, client_uid: str) -> bool:
"""Delete the User starting from the UID
Args:
uid (str): UID of the user
Returns:
bool: True if deleted
"""
sasl_obj = self.get_sasl_obj(client_uid)
if sasl_obj is None:
return False
self.DB_SASL.remove(sasl_obj)
return True
def get_sasl_obj(self, client_uid: str) -> Optional['MSasl']:
"""Get sasl client Object model
Args:
client_uid (str): UID of the client
Returns:
UserModel|None: The SASL Object | None
"""
for record in self.DB_SASL:
if record.client_uid == client_uid:
return record
return None

View File

@@ -1,7 +1,15 @@
'''This class should never be reloaded.
'''
from threading import Timer, Thread, RLock from threading import Timer, Thread, RLock
from socket import socket from socket import socket
from typing import Any, Optional
from core.definition import MSModule
class Settings: 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_TIMERS: list[Timer] = []
RUNNING_THREADS: list[Thread] = [] RUNNING_THREADS: list[Thread] = []
@@ -11,5 +19,41 @@ class Settings:
CONSOLE: bool = False CONSOLE: bool = False
MAIN_SERVER_HOSTNAME: str = None
PROTOCTL_USER_MODES: list[str] = [] PROTOCTL_USER_MODES: list[str] = []
PROTOCTL_PREFIX: list[str] = [] PROTOCTL_PREFIX: list[str] = []
SMOD_MODULES: list[MSModule] = []
"""List contains all Server modules"""
__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) -> Optional[Any]:
"""It returns the value associated to the key and finally it removes the entry"""
if self.__CACHE.get(key, None) is not None:
return self.__CACHE.pop(key)
return None
def get_cache_size(self) -> int:
return len(self.__CACHE)
def clear_cache(self) -> None:
self.__CACHE.clear()
def show_cache(self) -> dict[str, Any]:
return self.__CACHE.copy()

View File

@@ -1,23 +1,21 @@
from re import sub from re import sub
from typing import Union, TYPE_CHECKING from typing import Any, Optional, TYPE_CHECKING
from dataclasses import asdict from datetime import datetime
if TYPE_CHECKING: if TYPE_CHECKING:
from core.base import Base from core.loader import Loader
from core.definition import MUser from core.definition import MUser
class User: class User:
UID_DB: list['MUser'] = [] UID_DB: list['MUser'] = []
def __init__(self, baseObj: 'Base') -> None: def __init__(self, loader: 'Loader'):
self.Logs = baseObj.logs self.Logs = loader.Logs
self.Base = baseObj self.Base = loader.Base
return None def insert(self, new_user: 'MUser') -> bool:
def insert(self, newUser: 'MUser') -> bool:
"""Insert a new User object """Insert a new User object
Args: Args:
@@ -27,32 +25,31 @@ class User:
bool: True if inserted bool: True if inserted
""" """
userObj = self.get_User(newUser.uid) user_obj = self.get_user(new_user.uid)
if not user_obj is None:
if not userObj is None:
# User already created return False # User already created return False
return False return False
self.UID_DB.append(newUser) self.UID_DB.append(new_user)
return True return True
def update(self, uid: str, newNickname: str) -> bool: def update_nickname(self, uid: str, new_nickname: str) -> bool:
"""Update the nickname starting from the UID """Update the nickname starting from the UID
Args: Args:
uid (str): UID of the user uid (str): UID of the user
newNickname (str): New nickname new_nickname (str): New nickname
Returns: Returns:
bool: True if updated 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 return False
userObj.nickname = newNickname user_obj.nickname = new_nickname
return True return True
@@ -67,16 +64,16 @@ class User:
bool: True if user mode has been updaed bool: True if user mode has been updaed
""" """
response = True 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 return False
action = modes[0] action = modes[0]
new_modes = modes[1:] new_modes = modes[1:]
existing_umodes = userObj.umodes existing_umodes = user_obj.umodes
umodes = userObj.umodes umodes = user_obj.umodes
if action == '+': if action == '+':
@@ -95,7 +92,7 @@ class User:
final_umodes_liste = [x for x in self.Base.Settings.PROTOCTL_USER_MODES if x in liste_umodes] final_umodes_liste = [x for x in self.Base.Settings.PROTOCTL_USER_MODES if x in liste_umodes]
final_umodes = ''.join(final_umodes_liste) final_umodes = ''.join(final_umodes_liste)
userObj.umodes = f"+{final_umodes}" user_obj.umodes = f"+{final_umodes}"
return response return response
@@ -109,16 +106,16 @@ class User:
bool: True if deleted 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 return False
self.UID_DB.remove(userObj) self.UID_DB.remove(user_obj)
return True return True
def get_User(self, uidornickname: str) -> Union['MUser', None]: def get_user(self, uidornickname: str) -> Optional['MUser']:
"""Get The User Object model """Get The User Object model
Args: Args:
@@ -127,16 +124,15 @@ class User:
Returns: Returns:
UserModel|None: The UserModel Object | None UserModel|None: The UserModel Object | None
""" """
User = None
for record in self.UID_DB: for record in self.UID_DB:
if record.uid == uidornickname: if record.uid == uidornickname:
User = record return record
elif record.nickname == uidornickname: 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 """Get the UID of the user starting from the UID or the Nickname
Args: Args:
@@ -146,14 +142,14 @@ class User:
str|None: Return the UID 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 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 """Get the Nickname starting from UID or the nickname
Args: Args:
@@ -162,14 +158,14 @@ class User:
Returns: Returns:
str|None: the nickname 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 None
return userObj.nickname return user_obj.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 """Transform User Object to a dictionary
Args: Args:
@@ -178,12 +174,12 @@ class User:
Returns: Returns:
Union[dict[str, any], None]: User Object as a dictionary or None 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 None
return asdict(userObj) return user_obj.to_dict()
def is_exist(self, uidornikname: str) -> bool: def is_exist(self, uidornikname: str) -> bool:
"""Check if the UID or the nickname exist in the USER DB """Check if the UID or the nickname exist in the USER DB
@@ -194,14 +190,14 @@ class User:
Returns: Returns:
bool: True if exist 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 False
return True return True
def clean_uid(self, uid: str) -> Union[str, None]: def clean_uid(self, uid: str) -> Optional[str]:
"""Clean UID by removing @ / % / + / ~ / * / : """Clean UID by removing @ / % / + / ~ / * / :
Args: Args:
@@ -218,3 +214,34 @@ class User:
return None return None
return parsed_UID 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

View File

@@ -1,10 +1,45 @@
from datetime import datetime from datetime import datetime
from dataclasses import dataclass, field from json import dumps
from typing import Literal from dataclasses import dataclass, field, asdict, fields
from typing import Literal, Any, Optional
from os import sep from os import sep
@dataclass @dataclass
class MUser: 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
nickname: str = None
username: str = None
realname: str = None
hostname: str = None
umodes: str = None
vhost: str = None
fingerprint: str = None
isWebirc: bool = False
isWebsocket: bool = False
remote_ip: str = None
score_connexion: int = 0
geoip: str = None
connexion_datetime: datetime = field(default=datetime.now())
@dataclass
class MUser(MainModel):
"""Model User""" """Model User"""
uid: str = None uid: str = None
@@ -14,6 +49,7 @@ class MUser:
hostname: str = None hostname: str = None
umodes: str = None umodes: str = None
vhost: str = None vhost: str = None
fingerprint: str = None
isWebirc: bool = False isWebirc: bool = False
isWebsocket: bool = False isWebsocket: bool = False
remote_ip: str = None remote_ip: str = None
@@ -22,16 +58,18 @@ class MUser:
connexion_datetime: datetime = field(default=datetime.now()) connexion_datetime: datetime = field(default=datetime.now())
@dataclass @dataclass
class MAdmin: class MAdmin(MainModel):
"""Model Admin""" """Model Admin"""
uid: str = None uid: str = None
account: str = None
nickname: str = None nickname: str = None
username: str = None username: str = None
realname: str = None realname: str = None
hostname: str = None hostname: str = None
umodes: str = None umodes: str = None
vhost: str = None vhost: str = None
fingerprint: str = None
isWebirc: bool = False isWebirc: bool = False
isWebsocket: bool = False isWebsocket: bool = False
remote_ip: str = None remote_ip: str = None
@@ -41,7 +79,7 @@ class MAdmin:
level: int = 0 level: int = 0
@dataclass @dataclass
class MReputation: class MReputation(MainModel):
"""Model Reputation""" """Model Reputation"""
uid: str = None uid: str = None
nickname: str = None nickname: str = None
@@ -50,6 +88,7 @@ class MReputation:
hostname: str = None hostname: str = None
umodes: str = None umodes: str = None
vhost: str = None vhost: str = None
fingerprint: str = None
isWebirc: bool = False isWebirc: bool = False
isWebsocket: bool = False isWebsocket: bool = False
remote_ip: str = None remote_ip: str = None
@@ -59,7 +98,7 @@ class MReputation:
secret_code: str = None secret_code: str = None
@dataclass @dataclass
class MChannel: class MChannel(MainModel):
"""Model Channel""" """Model Channel"""
name: str = None name: str = None
@@ -74,7 +113,7 @@ class MChannel:
""" """
@dataclass @dataclass
class ColorModel: class ColorModel(MainModel):
white: str = "\x0300" white: str = "\x0300"
black: str = "\x0301" black: str = "\x0301"
blue: str = "\x0302" blue: str = "\x0302"
@@ -83,9 +122,10 @@ class ColorModel:
yellow: str = "\x0306" yellow: str = "\x0306"
bold: str = "\x02" bold: str = "\x02"
nogc: str = "\x03" nogc: str = "\x03"
underline: str = "\x1F"
@dataclass @dataclass
class MConfig: class MConfig(MainModel):
"""Model Configuration""" """Model Configuration"""
SERVEUR_IP: str = "127.0.0.1" SERVEUR_IP: str = "127.0.0.1"
@@ -211,11 +251,17 @@ class MConfig:
DEBUG_LEVEL:Literal[10, 20, 30, 40, 50] = 20 DEBUG_LEVEL:Literal[10, 20, 30, 40, 50] = 20
"""Logs level: DEBUG 10 | INFO 20 | WARNING 30 | ERROR 40 | CRITICAL 50. (default: 20)""" """Logs level: DEBUG 10 | INFO 20 | WARNING 30 | ERROR 40 | CRITICAL 50. (default: 20)"""
DEBUG_HARD: bool = False
"""Adding filename, function name and the line number to the logs. Default False"""
LOGGING_NAME: str = "defender" LOGGING_NAME: str = "defender"
"""The name of the Logging instance""" """The name of the Logging instance"""
TABLE_CLIENT: str = "core_client"
"""Core Client table"""
TABLE_ADMIN: str = "core_admin" TABLE_ADMIN: str = "core_admin"
"""Admin table""" """Core Admin table"""
TABLE_COMMAND: str = "core_command" TABLE_COMMAND: str = "core_command"
"""Core command table""" """Core command table"""
@@ -283,16 +329,35 @@ class MConfig:
"""0: utf-8 | 1: iso-8859-1""" """0: utf-8 | 1: iso-8859-1"""
@dataclass @dataclass
class MClone: class MCommand(MainModel):
"""Model Clone""" module_name: str = None
connected: bool = False command_name: str = None
uid: str = None description: str = None
nickname: str = None command_level: int = 0
username: str = None
realname: str = None @dataclass
channels: list = field(default_factory=list) class MModule(MainModel):
vhost: str = None module_name: str = None
hostname: str = 'localhost' class_name: str = None
umodes: str = None class_instance: Optional[Any] = None
remote_ip: str = '127.0.0.1'
group: str = 'Default' @dataclass
class MSModule:
"""Server Modules model"""
name: str = None
version: str = None
type: str = None
@dataclass
class MSasl(MainModel):
"""Sasl model"""
remote_ip: Optional[str] = None
mechanisme: Optional[str] = None
message_type: Optional[str] = None
client_uid: Optional[str] = None
username: Optional[str] = None
password: Optional[str] = None
fingerprint: Optional[str] = None
auth_success: bool = False
level: int = 0

View File

@@ -261,21 +261,21 @@ class Install:
if not do_install: if not do_install:
return None return None
print("===> Vider le cache de pip") print("===> Clean pip cache")
self.run_subprocess([self.config.venv_pip_executable, 'cache', 'purge']) 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']) self.run_subprocess([self.config.venv_python_executable, '-m', 'pip', 'install', '--upgrade', 'pip'])
if not self.check_package('greenlet'): if not self.check_package('greenlet'):
self.run_subprocess([self.config.venv_pip_executable, 'install', '--only-binary', ':all:', '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: for module in self.config.venv_cmd_requirements:
if not self.check_package(module): if not self.check_package(module):
print("### Trying to install missing python packages ###") print("### Trying to install missing python packages ###")
self.run_subprocess([self.config.venv_pip_executable, 'install', module]) self.run_subprocess([self.config.venv_pip_executable, 'install', module])
print(f"====> Module {module} installé") print(f"====> Module {module} installed!")
else: else:
print(f"==> {module} already installed") print(f"==> {module} already installed")
@@ -307,8 +307,8 @@ WantedBy=default.target
with open(full_service_file_path, 'w+') as servicefile: with open(full_service_file_path, 'w+') as servicefile:
servicefile.write(contain) servicefile.write(contain)
servicefile.close() servicefile.close()
print(f'Service file generated with current configuration') print('Service file generated with current configuration')
print(f'Running Defender IRC Service ...') print('Running IRC Service ...')
self.run_subprocess(self.config.service_cmd_daemon_reload) self.run_subprocess(self.config.service_cmd_daemon_reload)
self.run_subprocess(self.config.service_cmd_executable) self.run_subprocess(self.config.service_cmd_executable)
@@ -316,8 +316,8 @@ WantedBy=default.target
with open(full_service_file_path, 'w+') as servicefile: with open(full_service_file_path, 'w+') as servicefile:
servicefile.write(contain) servicefile.write(contain)
servicefile.close() servicefile.close()
print(f'Service file generated with current configuration') print('Service file generated with current configuration')
print(f'Running Defender IRC Service ...') print('Running IRC Service ...')
self.run_subprocess(self.config.service_cmd_daemon_reload) self.run_subprocess(self.config.service_cmd_daemon_reload)
self.run_subprocess(self.config.service_cmd_executable) self.run_subprocess(self.config.service_cmd_executable)

File diff suppressed because it is too large Load Diff

View File

@@ -1,32 +1,55 @@
from core.classes import user, admin, channel, clone, reputation, settings from logging import Logger
from core.classes import user, admin, client, channel, reputation, settings, sasl
import core.logs as logs
import core.definition as df import core.definition as df
import core.base as baseModule import core.utils as utils
import core.classes.config as confModule import core.base as base_mod
import core.module as module_mod
import core.classes.commands as commands_mod
import core.classes.config as conf_mod
class Loader: class Loader:
def __init__(self): def __init__(self):
# Load Modules # Load Main Modules
self.Definition: df = df self.Definition: df = df
self.ConfModule: confModule = confModule self.ConfModule: conf_mod = conf_mod
self.BaseModule: baseModule = baseModule self.BaseModule: base_mod = base_mod
self.CommandModule: commands_mod = commands_mod
self.LoggingModule: logs = logs
self.Utils: utils = utils
# Load Classes # Load Classes
self.Settings: settings = settings.Settings() self.ServiceLogging: logs.ServiceLogging = self.LoggingModule.ServiceLogging()
self.Config: df.MConfig = self.ConfModule.Configuration().ConfigObject self.Logs: Logger = self.ServiceLogging.get_logger()
self.Base: baseModule.Base = self.BaseModule.Base(self.Config, self.Settings) self.Settings: settings.Settings = settings.Settings()
self.User: user.User = user.User(self.Base) self.Config: df.MConfig = self.ConfModule.Configuration(self).get_config_model()
self.Admin: admin.Admin = admin.Admin(self.Base) self.Base: base_mod.Base = self.BaseModule.Base(self)
self.Channel: channel.Channel = channel.Channel(self.Base) self.User: user.User = user.User(self)
self.Clone: clone.Clone = clone.Clone(self.Base) self.Client: client.Client = client.Client(self)
self.Reputation: reputation.Reputation = reputation.Reputation(self.Base) self.Admin: admin.Admin = admin.Admin(self)
self.Channel: channel.Channel = channel.Channel(self)
self.Reputation: reputation.Reputation = reputation.Reputation(self)
self.Commands: commands_mod.Command = commands_mod.Command(self)
self.ModuleUtils: module_mod.Module = module_mod.Module(self)
self.Sasl: sasl.Sasl = sasl.Sasl(self)
self.Logs.debug("LOADER Success!")

170
core/logs.py Normal file
View File

@@ -0,0 +1,170 @@
import logging
from os import path, makedirs, sep
from typing import Optional
class ServiceLogging:
def __init__(self, loggin_name: str = "defender"):
"""Create the Logging object
"""
self.OS_SEP = sep
self.LOGGING_NAME = loggin_name
self.remove_logger(loggin_name) # Remove logger if exists
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, logger_name: Optional[str] = None) -> None:
if logger_name is None:
logger_name = self.LOGGING_NAME
# 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
# print(handler)
logger.removeHandler(handler)
handler.close()
# Supprimer le logger du dictionnaire global
logging.Logger.manager.loggerDict.pop(logger_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.logs.debug(f"[STDOUT LEVEL] New level {level}")
self.stdout_handler.setLevel(level)
def set_file_handler_level(self, level: int) -> None:
self.logs.debug(f"[LOG FILE LEVEL] new level {level}")
self.file_handler.setLevel(level)
def update_handler_format(self, debug_hard: bool = False) -> None:
"""Updating logging formatter format!
Args:
debug_hard (bool, optional): If true you will have filename,
function name and the line number. Defaults to False.
"""
# Updating logging formatter
if debug_hard:
new_formatter = logging.Formatter(
fmt='%(asctime)s - %(levelname)s - %(message)s (%(filename)s:%(funcName)s:%(lineno)d)',
datefmt='%Y-%m-%d %H:%M:%S'
)
else:
new_formatter = logging.Formatter(
fmt='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
for handler in self.logs.handlers:
handler.setFormatter(new_formatter)
def regenerate_handlers(self, logger: logging.Logger) -> logging.Logger:
os_sep = self.OS_SEP
logging_name = self.LOGGING_NAME
debug_file_level = self.DEBUG_FILE_LEVEL
debug_stdout_level = self.DEBUG_STDOUT_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
logger.addHandler(self.file_handler)
logger.addHandler(self.stdout_handler)
# Apply the filter
logger.addFilter(self.replace_filter)
logger.info(f'REGENRATING LOGGER {self.LOGGING_NAME}')
return logger
def replace_filter(self, record: logging.LogRecord) -> bool:
response = True
filter: list[str] = self.LOG_FILTERS
# 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 to write the log!

424
core/module.py Normal file
View File

@@ -0,0 +1,424 @@
'''
This is the main operational file to handle modules
'''
from pathlib import Path
import sys
import importlib
from types import ModuleType
from typing import TYPE_CHECKING, Optional
from core.definition import MModule
if TYPE_CHECKING:
from core.loader import Loader
from core.irc import Irc
class Module:
DB_MODULES: list[MModule] = []
def __init__(self, loader: 'Loader') -> None:
self.__Loader = loader
self.__Base = loader.Base
self.__Logs = loader.Logs
self.__Utils = loader.Utils
self.__Config = loader.Config
def get_all_available_modules(self) -> list[str]:
"""Get list of all main modules
using this pattern mod_*.py
all files starting with mod_
Returns:
list[str]: List of all module names.
"""
base_path = Path('mods')
return [file.name.replace('.py', '') for file in base_path.rglob('mod_*.py')]
def get_module_information(self, module_name: str) -> tuple[str, str, str]:
# module_name : mod_defender
if not module_name.lower().startswith('mod_'):
return None, None, None
module_name = module_name.lower()
module_folder = module_name.split('_')[1].lower() # --> defender
class_name = module_name.split('_')[1].capitalize() # --> Defender
return module_folder, module_name, class_name
def load_one_module(self, uplink: 'Irc', module_name: str, nickname: str, is_default: bool = False) -> bool:
module_folder, module_name, class_name = self.get_module_information(module_name)
if module_folder is None or module_name is None or class_name is None:
self.__Logs.error(f"There is an error with the module name! {module_folder}, {module_name}, {class_name}")
return False
if self.is_module_exist_in_sys_module(module_name):
self.__Logs.debug(f"Module [{module_folder}.{module_name}] already loaded!")
if self.model_is_module_exist(module_name):
# Si le module existe dans la variable globale retourne False
self.__Logs.debug(f"Module [{module_folder}.{module_name}] exist in the local variable!")
uplink.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}",
channel=self.__Config.SERVICE_CHANLOG
)
return False
return self.reload_one_module(uplink, module_name, nickname)
# Charger le module
loaded_module = importlib.import_module(f'mods.{module_folder}.{module_name}')
my_class = getattr(loaded_module, class_name, None) # Récuperer le nom de classe
create_instance_of_the_class = my_class(uplink) # Créer une nouvelle instance de la classe
if not hasattr(create_instance_of_the_class, 'cmd'):
uplink.Protocol.send_priv_msg(
nick_from=self.__Config.SERVICE_NICKNAME,
msg=f"Module {module_name} ne contient pas de méthode cmd",
channel=self.__Config.SERVICE_CHANLOG
)
self.__Logs.critical(f"The Module {module_name} has not been loaded because cmd method is not available")
self.db_delete_module(module_name)
return False
# Charger la nouvelle class dans la variable globale
if self.model_insert_module(MModule(module_name, class_name, create_instance_of_the_class)):
# Enregistrer le module dans la base de données
self.db_register_module(module_name, nickname, is_default)
uplink.Protocol.send_priv_msg(
nick_from=self.__Config.SERVICE_NICKNAME,
msg=f"Module {module_name} chargé",
channel=self.__Config.SERVICE_CHANLOG
)
self.__Logs.debug(f"Module {class_name} has been loaded")
def load_all_modules(self) -> bool:
...
def reload_one_module(self, uplink: 'Irc', module_name: str, nickname: str) -> bool:
"""Reloading one module and insert it into the model as well as the database
Args:
uplink (Irc): The Irc service instance
module_name (str): The module name
nickname (str): The nickname
Returns:
bool: True if the module has been reloaded
"""
module_folder, module_name, class_name = self.get_module_information(module_name)
red = self.__Config.COLORS.red
nogc = self.__Config.COLORS.nogc
try:
if self.is_module_exist_in_sys_module(module_name):
module_model = self.model_get_module(module_name)
if module_model:
module_model.class_instance.unload()
else:
uplink.Protocol.send_priv_msg(
nick_from=self.__Config.SERVICE_NICKNAME,
msg=f"[ {red}RELOAD MODULE ERROR{nogc} ] Module [{module_folder}.{module_name}] hasn't been reloaded! You must use {self.__Config.SERVICE_PREFIX}load {module_name}",
channel=self.__Config.SERVICE_CHANLOG
)
self.__Logs.debug(f"Module [{module_folder}.{module_name}] not found! Please use {self.__Config.SERVICE_PREFIX}load {module_name}")
return False
# reload module dependencies
self.reload_all_modules_with_all_dependencies(f'mods.{module_folder}')
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(uplink)
module_model.class_instance = new_instance
# Créer le module dans la base de données
self.db_register_module(module_name, nickname)
uplink.Protocol.send_priv_msg(
nick_from=self.__Config.SERVICE_NICKNAME,
msg=f"Module [{module_folder}.{module_name}] has been reloaded!",
channel=self.__Config.SERVICE_CHANLOG
)
self.__Logs.debug(f"Module [{module_folder}.{module_name}] reloaded!")
return True
else:
# Module is not loaded! Nothing to reload
self.__Logs.debug(f"[RELOAD MODULE ERROR] [{module_folder}.{module_name}] is not loaded! You must use {self.__Config.SERVICE_PREFIX}load {module_name}")
uplink.Protocol.send_priv_msg(
nick_from=self.__Config.SERVICE_NICKNAME,
msg=f"[ {red}RELOAD MODULE ERROR{nogc} ] Module [{module_folder}.{module_name}] is not loaded! You must use {self.__Config.SERVICE_PREFIX}load {module_name}",
channel=self.__Config.SERVICE_CHANLOG
)
return False
except (TypeError, AttributeError, KeyError, Exception) as err:
self.__Logs.error(f"[RELOAD MODULE ERROR]: {err}")
uplink.Protocol.send_priv_msg(
nick_from=self.__Config.SERVICE_NICKNAME,
msg=f"[RELOAD MODULE ERROR]: {err}",
channel=self.__Config.SERVICE_CHANLOG
)
self.db_delete_module(module_name)
def reload_all_modules(self) -> bool:
...
def reload_all_modules_with_all_dependencies(self, prefix: str = 'mods') -> bool:
"""
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 and 'schemas' not in name:
importlib.reload(module)
self.__Logs.debug(f'[LOAD_MODULE] Module {module} success')
except Exception as err:
self.__Logs.error(f'[LOAD_MODULE] Module {module} failed [!] - {err}')
def unload_one_module(self, uplink: 'Irc', module_name: str, keep_in_db: bool = True) -> bool:
"""Unload a module
Args:
mod_name (str): Module name ex mod_defender
Returns:
bool: True if success
"""
try:
# Le nom du module. exemple: mod_defender
red = self.__Config.COLORS.red
nogc = self.__Config.COLORS.nogc
module_folder, module_name, class_name = self.get_module_information(module_name)
module = self.model_get_module(module_name)
if module is None:
self.__Logs.debug(f"[ UNLOAD MODULE ERROR ] This module {module_name} is not loaded!")
uplink.Protocol.send_priv_msg(
nick_from=self.__Config.SERVICE_NICKNAME,
msg=f"[ {red}UNLOAD MODULE ERROR{nogc} ] This module {module_name} is not loaded!",
channel=self.__Config.SERVICE_CHANLOG
)
return False
if module:
module.class_instance.unload()
self.DB_MODULES.remove(module)
# Delete from the sys.modules.
if sys.modules.get(f'mods.{module_folder}.{module_name}'):
del sys.modules[f"mods.{module_folder}.{module_name}"]
if sys.modules.get(f'mods.{module_folder}.{module_name}'):
self.__Logs.debug(f"Module mods.{module_folder}.{module_name} still in the sys.modules")
# Supprimer le module de la base de données
if not keep_in_db:
self.db_delete_module(module_name)
uplink.Protocol.send_priv_msg(
nick_from=self.__Config.SERVICE_NICKNAME,
msg=f"[ UNLOAD MODULE INFO ] Module {module_name} has been unloaded!",
channel=self.__Config.SERVICE_CHANLOG
)
self.__Logs.debug(f"[ UNLOAD MODULE ] {module_name} has been unloaded!")
return True
self.__Logs.debug(f"[UNLOAD MODULE]: Module {module_name} not found in DB_MODULES variable!")
return False
except Exception as err:
self.__Logs.error(f"General Error: {err}")
return False
def unload_all_modules(self) -> bool:
...
def is_module_exist_in_sys_module(self, module_name: str) -> bool:
"""Check if the module exist in the sys.modules
This will check only in the folder mods/
Args:
module_name (str): The module name
Returns:
bool: True if the module exist
"""
module_folder, module_name, class_name = self.get_module_information(module_name)
if "mods." + module_folder + "." + module_name in sys.modules:
return True
return False
'''
ALL METHODS RELATED TO THE MModule MODEL DATACLASS
'''
def model_get_module(self, module_name: str) -> Optional[MModule]:
"""Get The module model object if exist otherwise it returns None
Args:
module_name (str): The module name you want to fetch
Returns:
Optional[MModule]: The Module Model Object
"""
for module in self.DB_MODULES:
if module.module_name.lower() == module_name.lower():
self.__Logs.debug(f"[MODEL MODULE GET] The module {module_name} has been found in the model DB_MODULES")
return module
self.__Logs.debug(f"[MODEL MODULE GET] The module {module_name} not found in the model DB_MODULES")
return None
def model_get_loaded_modules(self) -> list[MModule]:
"""Get the instance of DB_MODULES.
Warning: You should use a copy if you want to loop through the list!
Returns:
list[MModule]: A list of module model object
"""
# self.__Logs.debug(f"[MODEL MODULE LOADED MODULES] {len(self.DB_MODULES)} modules found!")
return self.DB_MODULES
def model_insert_module(self, module_model: MModule) -> bool:
"""Insert a new module model object
Args:
module_model (MModule): The module model object
Returns:
bool: True if the model has been inserted
"""
module = self.model_get_module(module_model.module_name)
if module is None:
self.DB_MODULES.append(module_model)
self.__Logs.debug(f"[MODEL MODULE INSERT] The module {module_model.module_name} has been inserted in the local variable model DB_MODULES")
return True
self.__Logs.debug(f"[MODEL MODULE INSERT] The module {module_model.module_name} already exist in the local variable model DB_MODULES")
return False
def model_clear(self) -> None:
"""Clear DB_MODULES list!
"""
self.DB_MODULES.clear()
self.__Logs.debug("[MODEL MODULE CLEAR] The local variable model DB_MODULES has been cleared")
return None
def model_is_module_exist(self, module_name: str) -> bool:
"""Check if the module exist in the module model object
Args:
module_name (str): The module name
Returns:
bool: True if the module_name exist
"""
if self.model_get_module(module_name):
self.__Logs.debug(f"[MODEL MODULE EXIST] The module {module_name} exist in the local model DB_MODULES!")
return True
self.__Logs.debug(f"[MODEL MODULE EXIST] The module {module_name} is not available in the local model DB_MODULES!")
return False
'''
OPERATION DEDICATED TO DATABASE MANAGEMENT
'''
def db_load_all_existing_modules(self, uplink: 'Irc') -> bool:
"""Charge les modules qui existe déja dans la base de données
Returns:
None: Aucun retour requis, elle charge puis c'est tout
"""
self.__Logs.debug("[DB LOAD MODULE] Loading modules from the database!")
result = self.__Base.db_execute_query(f"SELECT module_name FROM {self.__Config.TABLE_MODULE}")
for r in result.fetchall():
self.load_one_module(uplink, r[0], 'sys', True)
return True
def db_is_module_exist(self, module_name: str) -> bool:
"""Check if the module exist in the database
Args:
module_name (str): The module name you want to check
Returns:
bool: True if the module exist in the database
"""
query = f"SELECT id FROM {self.__Config.TABLE_MODULE} WHERE module_name = :module_name"
mes_donnes = {'module_name': module_name.lower()}
results = self.__Base.db_execute_query(query, mes_donnes)
if results.fetchall():
self.__Logs.debug(f"[DB MODULE EXIST] The module {module_name} exist in the database!")
return True
else:
self.__Logs.debug(f"[DB MODULE EXIST] The module {module_name} is not available in the database!")
return False
def db_register_module(self, module_name: str, nickname: str, is_default: bool = False) -> bool:
"""Insert a new module in the database
Args:
module_name (str): The module name
nickname (str): The user who loaded the module
isdefault (int): Is this a default module. Default 0
"""
if not self.db_is_module_exist(module_name):
insert_cmd_query = f"INSERT INTO {self.__Config.TABLE_MODULE} (datetime, user, module_name, isdefault) VALUES (:datetime, :user, :module_name, :isdefault)"
mes_donnees = {'datetime': self.__Utils.get_sdatetime(), 'user': nickname, 'module_name': module_name.lower(), 'isdefault': is_default}
insert = self.__Base.db_execute_query(insert_cmd_query, mes_donnees)
if insert.rowcount > 0:
self.__Logs.debug(f"[DB REGISTER MODULE] Module {module_name} has been inserted to the database!")
return True
else:
self.__Logs.debug(f"[DB REGISTER MODULE] Module {module_name} not inserted to the database!")
return False
self.__Logs.debug(f"[DB REGISTER MODULE] Module {module_name} already exist in the database! Nothing to insert!")
return False
def db_update_module(self, module_name: str, nickname: str) -> None:
"""Update the datetime and the user that updated the module
Args:
module_name (str): The module name to update
nickname (str): The nickname who updated the module
"""
update_cmd_query = f"UPDATE {self.__Config.TABLE_MODULE} SET datetime = :datetime, LOWER(user) = :user WHERE LOWER(module_name) = :module_name"
mes_donnees = {'datetime': self.__Utils.get_sdatetime(), 'user': nickname.lower(), 'module_name': module_name.lower()}
result = self.__Base.db_execute_query(update_cmd_query, mes_donnees)
if result.rowcount > 0:
self.__Logs.debug(f"[DB UPDATE MODULE] Module {module_name} has been updated!")
return True
else:
self.__Logs.debug(f"[DB UPDATE MODULE] Module {module_name} not found! Nothing to update!")
return False
def db_delete_module(self, module_name:str) -> None:
"""Delete a module from the database
Args:
module_name (str): The module name you want to delete
"""
insert_cmd_query = f"DELETE FROM {self.__Config.TABLE_MODULE} WHERE LOWER(module_name) = :module_name"
mes_donnees = {'module_name': module_name.lower()}
delete = self.__Base.db_execute_query(insert_cmd_query, mes_donnees)
if delete.rowcount > 0:
self.__Logs.debug(f"[DB MODULE DELETE] The module {module_name} has been deleted from the dabatase!")
return True
self.__Logs.debug(f"[DB MODULE DELETE] The module {module_name} is not available in the database! Nothing to delete!")
return False

View File

@@ -1,25 +1,36 @@
from typing import Literal, Union '''
from datetime import datetime Main utils library.
'''
import gc
import ssl
import socket
import sys
from pathlib import Path
from re import match, sub
from base64 import b64decode
from typing import Literal, Optional, Any, TYPE_CHECKING
from datetime import datetime, timedelta, timezone
from time import time from time import time
from random import choice from random import choice
from hashlib import md5, sha3_512 from hashlib import md5, sha3_512
def convert_to_int(value: any) -> Union[int, None]: if TYPE_CHECKING:
from core.irc import Irc
def convert_to_int(value: Any) -> Optional[int]:
"""Convert a value to int """Convert a value to int
Args: Args:
value (any): Value to convert to int if possible value (Any): Value to convert to int if possible
Returns: Returns:
Union[int, None]: Return the int value or None if not possible int: Return the int value or None if not possible
""" """
try: try:
value_to_int = int(value) value_to_int = int(value)
return value_to_int return value_to_int
except ValueError: except ValueError:
return None return None
except Exception:
return None
def get_unixtime() -> int: def get_unixtime() -> int:
"""Cette fonction retourne un UNIXTIME de type 12365456 """Cette fonction retourne un UNIXTIME de type 12365456
@@ -27,9 +38,12 @@ def get_unixtime() -> int:
Returns: Returns:
int: Current time in seconds since the Epoch (int) 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()) return int(time())
def get_datetime() -> str: def get_sdatetime() -> str:
"""Retourne une date au format string (24-12-2023 20:50:59) """Retourne une date au format string (24-12-2023 20:50:59)
Returns: Returns:
@@ -38,6 +52,73 @@ def get_datetime() -> str:
currentdate = datetime.now().strftime('%d-%m-%Y %H:%M:%S') currentdate = datetime.now().strftime('%d-%m-%Y %H:%M:%S')
return currentdate return currentdate
def get_datetime() -> datetime:
"""
Return the current datetime in a datetime object
"""
return datetime.now()
def get_ssl_context() -> ssl.SSLContext:
"""Generate the ssl context
Returns:
SSLContext: The SSL Context
"""
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
return ctx
def create_socket(uplink: 'Irc') -> None:
"""Create a socket to connect SSL or Normal connection
"""
try:
soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM or socket.SOCK_NONBLOCK)
connexion_information = (uplink.Config.SERVEUR_IP, uplink.Config.SERVEUR_PORT)
if uplink.Config.SERVEUR_SSL:
# Create SSL Context object
ssl_context = get_ssl_context()
ssl_connexion = ssl_context.wrap_socket(soc, server_hostname=uplink.Config.SERVEUR_HOSTNAME)
ssl_connexion.connect(connexion_information)
uplink.IrcSocket = ssl_connexion
uplink.Config.SSL_VERSION = uplink.IrcSocket.version()
uplink.Logs.info(f"-- Connected using SSL : Version = {uplink.Config.SSL_VERSION}")
else:
soc.connect(connexion_information)
uplink.IrcSocket = soc
uplink.Logs.info("-- Connected in a normal mode!")
return None
except (ssl.SSLEOFError, ssl.SSLError) as soe:
uplink.Logs.critical(f"[SSL ERROR]: {soe}")
except OSError as oe:
uplink.Logs.critical(f"[OS Error]: {oe}")
if 'connection refused' in str(oe).lower():
sys.exit(oe)
except AttributeError as ae:
uplink.Logs.critical(f"AttributeError: {ae}")
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: def generate_random_string(lenght: int) -> str:
"""Retourn une chaîne aléatoire en fonction de la longueur spécifiée. """Retourn une chaîne aléatoire en fonction de la longueur spécifiée.
@@ -49,15 +130,15 @@ def generate_random_string(lenght: int) -> str:
return randomize return randomize
def hash(password: str, algorithm: Literal["md5, sha3_512"] = 'md5') -> str: def hash_password(password: str, algorithm: Literal["md5, sha3_512"] = 'md5') -> str:
"""Retourne un mot de passe chiffré en fonction de l'algorithme utilisé """Return the crypted password following the selected algorithm
Args: Args:
password (str): Le password en clair password (str): The plain text password
algorithm (str): L'algorithm a utilisé algorithm (str): The algorithm to use
Returns: Returns:
str: Le password haché str: The crypted password, default md5
""" """
match algorithm: match algorithm:
@@ -72,3 +153,46 @@ def hash(password: str, algorithm: Literal["md5, sha3_512"] = 'md5') -> str:
case _: case _:
password = md5(password.encode()).hexdigest() password = md5(password.encode()).hexdigest()
return password 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')]
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
def hide_sensitive_data(srvmsg: list[str]) -> list[str]:
try:
srv_msg = srvmsg.copy()
privmsg_index = srv_msg.index('PRIVMSG')
auth_index = privmsg_index + 2
if match(r'^:{1}\W?(auth)$', srv_msg[auth_index]) is None:
return srv_msg
for l in range(auth_index + 1, len(srv_msg)):
srv_msg[l] = '*' * len(srv_msg[l])
return srv_msg
except ValueError:
return srvmsg

View File

@@ -1,30 +1,21 @@
from core import installation from core import installation
############################################# #############################################
# @Version : 1 # # @Version : 6.2 #
# Requierements : # # Requierements : #
# Python3.10 or higher # # Python3.10 or higher #
# SQLAlchemy, requests, psutil # # SQLAlchemy, requests, psutil #
# unrealircd-rpc-py #
# UnrealIRCD 6.2.2 or higher # # UnrealIRCD 6.2.2 or higher #
############################################# #############################################
#########################
# LANCEMENT DE DEFENDER #
#########################
# 1. Chargement de la configuration
# 2. Chargement de l'ensemble des classes
# 3.
#
try: try:
installation.Install() installation.Install()
from core.loader import Loader from core.loader import Loader
from core.irc import Irc from core.irc import Irc
loader = Loader() ircInstance = Irc(Loader())
ircInstance = Irc(loader)
ircInstance.init_irc(ircInstance) ircInstance.init_irc(ircInstance)
except AssertionError as ae: except AssertionError as ae:

163
mods/clone/clone_manager.py Normal file
View File

@@ -0,0 +1,163 @@
from typing import Optional, TYPE_CHECKING
from mods.clone.schemas import MClone
if TYPE_CHECKING:
from mods.clone.mod_clone import Clone
class CloneManager:
UID_CLONE_DB: list[MClone] = []
def __init__(self, uplink: 'Clone'):
self.Logs = uplink.Logs
def insert(self, new_clone_object: MClone) -> bool:
"""Create new Clone object
Args:
new_clone_object (MClone): New Clone object
Returns:
bool: True if inserted
"""
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 or record.uid == new_clone_object.uid:
# If the user exist then return False and do not go further
self.Logs.debug(f'Nickname/UID {record.nickname}/{record.uid} already exist')
return False
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
Args:
uidornickname (str): UID or nickname of the clone
Returns:
bool: True if deleted
"""
clone_obj = self.get_clone(uidornickname=uidornickname)
if clone_obj is None:
return False
self.UID_CLONE_DB.remove(clone_obj)
return True
def nickname_exists(self, nickname: str) -> bool:
"""Check if the nickname exist
Args:
nickname (str): Nickname of the clone
Returns:
bool: True if the nickname exist
"""
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
Args:
uid (str): uid of the clone
Returns:
bool: True if the nickname exist
"""
clone = self.get_clone(uid)
if isinstance(clone, MClone):
return True
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
Args:
uidornickname (str): The UID or the Nickname
Returns:
Union[MClone, None]: Return MClone object or None
"""
for clone in self.UID_CLONE_DB:
if clone.uid == uidornickname:
return clone
if clone.nickname == uidornickname:
return 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
Args:
uidornickname (str): UID or Nickname
Returns:
str|None: Return the UID
"""
for record in self.UID_CLONE_DB:
if record.uid == uidornickname:
return record.uid
if record.nickname == uidornickname:
return record.uid
return None
def kill(self, nickname:str) -> bool:
response = False
for clone in self.UID_CLONE_DB:
if clone.nickname == nickname:
clone.alive = False # Kill the clone
response = True
return response

View File

@@ -1,53 +1,65 @@
from dataclasses import dataclass, fields, field from typing import TYPE_CHECKING, Optional, Any
import copy import mods.clone.utils as utils
import random, faker, time, logging import mods.clone.threads as thds
from datetime import datetime import mods.clone.schemas as schemas
from typing import TYPE_CHECKING from mods.clone.clone_manager import CloneManager
if TYPE_CHECKING: if TYPE_CHECKING:
from core.irc import Irc from core.irc import Irc
from faker import Faker
class Clone(): class Clone:
@dataclass def __init__(self, irc_instance: 'Irc') -> None:
class ModConfModel:
clone_nicknames: list[str]
def __init__(self, ircInstance: 'Irc') -> None:
# Module name (Mandatory) # Module name (Mandatory)
self.module_name = 'mod_' + str(self.__class__.__name__).lower() self.module_name = 'mod_' + str(self.__class__.__name__).lower()
# Add Irc Object to the module (Mandatory) # Add Irc Object to the module (Mandatory)
self.Irc = ircInstance self.Irc = irc_instance
# Add Irc Protocol Object to the module (Mandatory) # Add Irc Protocol Object to the module (Mandatory)
self.Protocol = ircInstance.Protocol self.Protocol = irc_instance.Protocol
# Add Global Configuration to the module (Mandatory) # Add Global Configuration to the module (Mandatory)
self.Config = ircInstance.Config self.Config = irc_instance.Config
# Add Base object to the module (Mandatory) # Add Base object to the module (Mandatory)
self.Base = ircInstance.Base self.Base = irc_instance.Base
# Add logs object to the module (Mandatory) # Add logs object to the module (Mandatory)
self.Logs = ircInstance.Base.logs self.Logs = irc_instance.Loader.Logs
# Add User object to the module (Mandatory) # Add User object to the module (Mandatory)
self.User = ircInstance.User self.User = irc_instance.User
# Add Channel object to the module (Mandatory) # Add Channel object to the module (Mandatory)
self.Channel = ircInstance.Channel self.Channel = irc_instance.Channel
# Add clone object to the module (Optionnal) # Add global definitions
self.Clone = ircInstance.Clone self.Definition = irc_instance.Loader.Definition
self.Definition = ircInstance.Loader.Definition # The Global Settings
self.Settings = irc_instance.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 # Créer les nouvelles commandes du module
self.commands_level = { self.Irc.build_command(1, self.module_name, 'clone', 'Connect, join, part, kill and say clones')
1: ['clone']
}
# Init the module (Mandatory) # Init the module (Mandatory)
self.__init_module() self.__init_module()
@@ -57,17 +69,10 @@ class Clone():
def __init_module(self) -> None: def __init_module(self) -> None:
# Enrigstrer les nouvelles commandes dans le code
self.__set_commands(self.commands_level)
# Créer les tables necessaire a votre module (ce n'es pas obligatoire) # Créer les tables necessaire a votre module (ce n'es pas obligatoire)
self.__create_tables() self.__create_tables()
self.stop = False 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) # Load module configuration (Mandatory)
self.__load_module_configuration() self.__load_module_configuration()
@@ -79,25 +84,9 @@ class Clone():
self.Protocol.send2socket(f":{self.Config.SERVICE_NICKNAME} MODE {self.Config.CLONE_CHANNEL} +nts") self.Protocol.send2socket(f":{self.Config.SERVICE_NICKNAME} MODE {self.Config.CLONE_CHANNEL} +nts")
self.Protocol.send2socket(f":{self.Config.SERVICE_NICKNAME} MODE {self.Config.CLONE_CHANNEL} +k {self.Config.CLONE_CHANNEL_PASSWORD}") self.Protocol.send2socket(f":{self.Config.SERVICE_NICKNAME} MODE {self.Config.CLONE_CHANNEL} +k {self.Config.CLONE_CHANNEL_PASSWORD}")
def __set_commands(self, commands:dict[int, list[str]]) -> None:
"""### Rajoute les commandes du module au programme principal
Args:
commands (list): Liste des commandes du module
"""
for level, com in commands.items():
for c in commands[level]:
if not c in self.Irc.commands:
self.Irc.commands_level[level].append(c)
self.Irc.commands.append(c)
return None
def __create_tables(self) -> None: def __create_tables(self) -> None:
"""Methode qui va créer la base de donnée si elle n'existe pas. """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 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: Returns:
None: Aucun retour n'es attendu None: Aucun retour n'es attendu
@@ -120,9 +109,7 @@ class Clone():
""" """
try: try:
# Variable qui va contenir les options de configuration du module Defender # Variable qui va contenir les options de configuration du module Defender
self.ModConfig = self.ModConfModel( self.ModConfig = self.Schemas.ModConfModel()
clone_nicknames=[]
)
# Sync the configuration with core configuration (Mandatory) # Sync the configuration with core configuration (Mandatory)
# self.Base.db_sync_core_config(self.module_name, self.ModConfig) # self.Base.db_sync_core_config(self.module_name, self.ModConfig)
@@ -136,6 +123,8 @@ class Clone():
"""Cette methode sera executée a chaque désactivation ou """Cette methode sera executée a chaque désactivation ou
rechargement de module 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.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") self.Protocol.send2socket(f":{self.Config.SERVICE_NICKNAME} MODE {self.Config.CLONE_CHANNEL} -nts")
@@ -144,175 +133,41 @@ class Clone():
return None 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') -> None:
try:
fakeEN = self.fakeEN
fakeFR = self.fakeFR
unixtime = self.Base.get_unixtime()
chaine = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
generate_uid = fakeEN.random_sample(chaine, 6)
uid = self.Config.SERVEUR_ID + ''.join(generate_uid)
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()
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, interval: float = 0.2) -> None:
for i in range(0, number_of_clones):
self.generate_clones(group=group)
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: def cmd(self, data:list) -> None:
try: try:
service_id = self.Config.SERVICE_ID # Defender serveur id if not data or len(data) < 2:
cmd = list(data).copy()
if len(cmd) < 2:
return None return None
match cmd[1]: cmd = data.copy() if isinstance(data, list) else list(data).copy()
index, command = self.Irc.Protocol.get_ircd_protocol_poisition(cmd)
case 'REPUTATION': if index == -1:
pass
if len(cmd) < 3:
return None return None
match cmd[2]: match command:
case 'PRIVMSG': case 'PRIVMSG':
# print(cmd) return self.Utils.handle_on_privmsg(self, cmd)
uid_sender = self.User.clean_uid(cmd[1])
senderObj = self.User.get_User(uid_sender)
if senderObj.hostname in self.Config.CLONE_LOG_HOST_EXEMPT: case 'QUIT':
return None return None
if not senderObj is None: case _:
senderMsg = ' '.join(cmd[4:])
getClone = self.Clone.get_Clone(cmd[3])
if getClone is None:
return None return None
if getClone.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,
msg=final_message,
channel=self.Config.CLONE_CHANNEL
)
except Exception as err: except Exception as err:
self.Logs.error(f'General Error: {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: try:
if len(cmd) < 1:
return
command = str(cmd[0]).lower() command = str(cmd[0]).lower()
fromuser = user fromuser = user
dnickname = self.Config.SERVICE_NICKNAME
dnickname = self.Config.SERVICE_NICKNAME # Defender nickname
match command: match command:
@@ -320,10 +175,11 @@ class Clone():
if len(cmd) == 1: 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 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 kill [all | group_name | 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 join [all | group_name | 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 part [all | group_name | nickname] #channel")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone list") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone list [group name]")
return None
option = str(cmd[1]).lower() option = str(cmd[1]).lower()
@@ -331,15 +187,15 @@ class Clone():
case 'connect': case 'connect':
try: try:
# clone connect 5 Group 3 # clone connect 5 GroupName 3
self.stop = False self.stop = False
number_of_clones = int(cmd[2]) number_of_clones = int(cmd[2])
group = str(cmd[3]).lower() group = str(cmd[3]).lower()
connection_interval = int(cmd[4]) if len(cmd) == 5 else 0.5 connection_interval = int(cmd[4]) if len(cmd) == 5 else 0.2
self.Base.create_thread( self.Base.create_thread(
func=self.thread_connect_clones, func=self.Threads.thread_connect_clones,
func_args=(number_of_clones, group, connection_interval) func_args=(self, number_of_clones, group, False, connection_interval)
) )
except Exception as err: except Exception as err:
@@ -349,18 +205,28 @@ class Clone():
case 'kill': case 'kill':
try: try:
# clone kill [all | nickname] # clone kill [ALL | group name | nickname]
self.stop = True self.stop = True
clone_name = str(cmd[2]) option = str(cmd[2])
clone_to_kill: list[str] = []
if clone_name.lower() == 'all': if option.lower() == 'all':
self.Base.create_thread(func=self.thread_kill_clones, func_args=(fromuser, )) 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: else:
cloneObj = self.Clone.get_Clone(clone_name) clone_obj = self.Clone.get_clone(option)
if not cloneObj is None: if not clone_obj is None:
self.Protocol.send_quit(cloneObj.uid, 'Goood bye', print_log=False) self.Protocol.send_quit(clone_obj.uid, 'Goood bye', print_log=False)
self.Clone.delete(clone_obj.uid)
except Exception as err: except Exception as err:
self.Logs.error(f'{err}') self.Logs.error(f'{err}')
@@ -369,19 +235,28 @@ class Clone():
case 'join': case 'join':
try: try:
# clone join [all | nickname] #channel # clone join [all | group name | nickname] #channel
clone_name = str(cmd[2]) option = str(cmd[2])
clone_channel_to_join = str(cmd[3]) clone_channel_to_join = str(cmd[3])
if clone_name.lower() == 'all': if option.lower() == 'all':
for clone in self.Clone.UID_CLONE_DB: for clone in self.Clone.UID_CLONE_DB:
self.Protocol.send_join_chan(uidornickname=clone.uid, channel=clone_channel_to_join, print_log=False) 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: else:
if self.Clone.exists(clone_name): if self.Clone.nickname_exists(option):
if not self.Clone.get_uid(clone_name) is None: clone_uid = self.Clone.get_clone(option).uid
self.Protocol.send_join_chan(uidornickname=clone_name, channel=clone_channel_to_join, print_log=False) self.Protocol.send_join_chan(uidornickname=clone_uid, channel=clone_channel_to_join, print_log=False)
except Exception as err: except Exception as err:
self.Logs.error(f'{err}') self.Logs.error(f'{err}')
@@ -391,17 +266,26 @@ class Clone():
case 'part': case 'part':
try: try:
# clone part [all | nickname] #channel # clone part [all | nickname] #channel
clone_name = str(cmd[2]) option = str(cmd[2])
clone_channel_to_part = str(cmd[3]) clone_channel_to_part = str(cmd[3])
if clone_name.lower() == 'all': if option.lower() == 'all':
for clone in self.Clone.UID_CLONE_DB: for clone in self.Clone.UID_CLONE_DB:
self.Protocol.send_part_chan(uidornickname=clone.uid, channel=clone_channel_to_part, print_log=False) 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: else:
if self.Clone.exists(clone_name): if self.Clone.nickname_exists(option):
clone_uid = self.Clone.get_uid(clone_name) clone_uid = self.Clone.get_uid(option)
if not clone_uid is None: if not clone_uid is None:
self.Protocol.send_part_chan(uidornickname=clone_uid, channel=clone_channel_to_part, print_log=False) self.Protocol.send_part_chan(uidornickname=clone_uid, channel=clone_channel_to_part, print_log=False)
@@ -412,11 +296,31 @@ class Clone():
case 'list': case 'list':
try: try:
clone_count = len(self.Clone.UID_CLONE_DB) # Syntax. /msg defender clone list <group_name>
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f">> Number of connected clones: {clone_count}") 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: 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, 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}") msg=f" {clone.nickname:<12}| {clone.realname:<25}| {clone.group:<15}| {clone.connected:<35}")
except Exception as err: except Exception as err:
self.Logs.error(f'{err}') self.Logs.error(f'{err}')
@@ -424,11 +328,11 @@ class Clone():
try: try:
# clone say clone_nickname #channel message # clone say clone_nickname #channel message
clone_name = str(cmd[2]) 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:]) 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( self.Protocol.send_notice(
nick_from=dnickname, nick_from=dnickname,
nick_to=fromuser, nick_to=fromuser,
@@ -436,7 +340,7 @@ class Clone():
) )
return None 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) self.Protocol.send_priv_msg(nick_from=clone_name, msg=final_message, channel=clone_channel)
except Exception as err: except Exception as err:
@@ -449,12 +353,12 @@ class Clone():
case _: 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 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 kill [all | group name | 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 join [all | group name | 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 part [all | group name | nickname] #channel")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone list") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone list [group name]")
except IndexError as ie: except IndexError as ie:
self.Logs.error(f'Index Error: {ie}') self.Logs.error(f'Index Error: {ie}')
except Exception as err: except Exception as err:
self.Logs.error(f'Index Error: {err}') self.Logs.error(f'General Error: {err}')

22
mods/clone/schemas.py Normal file
View File

@@ -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] = []

44
mods/clone/threads.py Normal file
View File

@@ -0,0 +1,44 @@
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_instance=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

198
mods/clone/utils.py Normal file
View File

@@ -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.Schemas.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.Irc.Utils.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
)

View File

@@ -1,5 +1,6 @@
from typing import Union, TYPE_CHECKING from typing import Optional, TYPE_CHECKING
from dataclasses import dataclass from dataclasses import dataclass
import mods.command.utils as utils
if TYPE_CHECKING: if TYPE_CHECKING:
from core.irc import Irc from core.irc import Irc
@@ -34,27 +35,74 @@ class Command:
# Add Base object to the module (Mandatory) # Add Base object to the module (Mandatory)
self.Base = ircInstance.Base self.Base = ircInstance.Base
# Add main Utils to the module
self.MainUtils = ircInstance.Utils
# Add logs object to the module (Mandatory) # Add logs object to the module (Mandatory)
self.Logs = ircInstance.Base.logs self.Logs = ircInstance.Loader.Logs
# Add User object to the module (Mandatory) # Add User object to the module (Mandatory)
self.User = ircInstance.User self.User = ircInstance.User
# Add Client object to the module (Mandatory)
self.Client = ircInstance.Client
# Add Channel object to the module (Mandatory) # Add Channel object to the module (Mandatory)
self.Channel = ircInstance.Channel self.Channel = ircInstance.Channel
# Create module commands (Mandatory) # Module Utils
self.commands_level = { self.mod_utils = utils
1: ['join', 'part','owner', 'deowner', 'protect', 'deprotect', 'op',
'deop', 'halfop', 'dehalfop', 'voice','devoice', 'topic'], self.Irc.build_command(1, self.module_name, 'join', 'Join a channel')
2: ['opall', 'deopall', 'devoiceall', 'voiceall', 'ban', 'automode', self.Irc.build_command(1, self.module_name, 'assign', 'Assign a user to a role or task')
'unban', 'kick', 'kickban', 'umode', self.Irc.build_command(1, self.module_name, 'part', 'Leave a channel')
'mode', 'get_mode', 'svsjoin', 'svspart', 'svsnick', self.Irc.build_command(1, self.module_name, 'unassign', 'Remove a user from a role or task')
'wallops', 'globops','gnotice','whois', 'names', 'invite', 'inviteme', self.Irc.build_command(1, self.module_name, 'owner', 'Give channel ownership to a user')
'sajoin', 'sapart', 'kill', 'gline', 'ungline', 'kline', self.Irc.build_command(1, self.module_name, 'deowner', 'Remove channel ownership from a user')
'unkline', 'shun', 'unshun', 'glinelist', 'shunlist', 'klinelist'], self.Irc.build_command(1, self.module_name, 'protect', 'Protect a user from being kicked')
3: ['map'] self.Irc.build_command(1, self.module_name, 'deprotect', 'Remove protection from a user')
} self.Irc.build_command(1, self.module_name, 'op', 'Grant operator privileges to a user')
self.Irc.build_command(1, self.module_name, 'deop', 'Remove operator privileges from a user')
self.Irc.build_command(1, self.module_name, 'halfop', 'Grant half-operator privileges to a user')
self.Irc.build_command(1, self.module_name, 'dehalfop', 'Remove half-operator privileges from a user')
self.Irc.build_command(1, self.module_name, 'voice', 'Grant voice privileges to a user')
self.Irc.build_command(1, self.module_name, 'devoice', 'Remove voice privileges from a user')
self.Irc.build_command(1, self.module_name, 'topic', 'Change the topic of a channel')
self.Irc.build_command(2, self.module_name, 'opall', 'Grant operator privileges to all users')
self.Irc.build_command(2, self.module_name, 'deopall', 'Remove operator privileges from all users')
self.Irc.build_command(2, self.module_name, 'devoiceall', 'Remove voice privileges from all users')
self.Irc.build_command(2, self.module_name, 'voiceall', 'Grant voice privileges to all users')
self.Irc.build_command(2, self.module_name, 'ban', 'Ban a user from a channel')
self.Irc.build_command(2, self.module_name, 'automode', 'Automatically set user modes upon join')
self.Irc.build_command(2, self.module_name, 'unban', 'Remove a ban from a user')
self.Irc.build_command(2, self.module_name, 'kick', 'Kick a user from a channel')
self.Irc.build_command(2, self.module_name, 'kickban', 'Kick and ban a user from a channel')
self.Irc.build_command(2, self.module_name, 'umode', 'Set user mode')
self.Irc.build_command(2, self.module_name, 'mode', 'Set channel mode')
self.Irc.build_command(2, self.module_name, 'get_mode', 'Retrieve current channel mode')
self.Irc.build_command(2, self.module_name, 'svsjoin', 'Force a user to join a channel')
self.Irc.build_command(2, self.module_name, 'svspart', 'Force a user to leave a channel')
self.Irc.build_command(2, self.module_name, 'svsnick', 'Force a user to change their nickname')
self.Irc.build_command(2, self.module_name, 'wallops', 'Send a message to all operators')
self.Irc.build_command(2, self.module_name, 'globops', 'Send a global operator message')
self.Irc.build_command(2, self.module_name, 'gnotice', 'Send a global notice')
self.Irc.build_command(2, self.module_name, 'whois', 'Get information about a user')
self.Irc.build_command(2, self.module_name, 'names', 'List users in a channel')
self.Irc.build_command(2, self.module_name, 'invite', 'Invite a user to a channel')
self.Irc.build_command(2, self.module_name, 'inviteme', 'Invite yourself to a channel')
self.Irc.build_command(2, self.module_name, 'sajoin', 'Force yourself into a channel')
self.Irc.build_command(2, self.module_name, 'sapart', 'Force yourself to leave a channel')
self.Irc.build_command(2, self.module_name, 'kill', 'Disconnect a user from the server')
self.Irc.build_command(2, self.module_name, 'gline', 'Ban a user from the entire server')
self.Irc.build_command(2, self.module_name, 'ungline', 'Remove a global server ban')
self.Irc.build_command(2, self.module_name, 'kline', 'Ban a user based on their hostname')
self.Irc.build_command(2, self.module_name, 'unkline', 'Remove a K-line ban')
self.Irc.build_command(2, self.module_name, 'shun', 'Prevent a user from sending messages')
self.Irc.build_command(2, self.module_name, 'unshun', 'Remove a shun from a user')
self.Irc.build_command(2, self.module_name, 'glinelist', 'List all global bans')
self.Irc.build_command(2, self.module_name, 'shunlist', 'List all shunned users')
self.Irc.build_command(2, self.module_name, 'klinelist', 'List all K-line bans')
self.Irc.build_command(3, self.module_name, 'map', 'Show the server network map')
# Init the module # Init the module
self.__init_module() self.__init_module()
@@ -64,9 +112,6 @@ class Command:
def __init_module(self) -> None: def __init_module(self) -> None:
# Insert module commands into the core one (Mandatory)
self.__set_commands(self.commands_level)
# Create you own tables (Mandatory) # Create you own tables (Mandatory)
self.__create_tables() self.__create_tables()
@@ -79,20 +124,6 @@ class Command:
return None return None
def __set_commands(self, commands: dict[int, list[str]]) -> None:
"""### Rajoute les commandes du module au programme principal
Args:
commands (list): Liste des commandes du module
"""
for level, com in commands.items():
for c in commands[level]:
if c not in self.Irc.commands:
self.Irc.commands_level[level].append(c)
self.Irc.commands.append(c)
return None
def __create_tables(self) -> None: def __create_tables(self) -> None:
"""Methode qui va créer la base de donnée si elle n'existe pas. """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 Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module
@@ -246,28 +277,34 @@ class Command:
return None return None
user_uid = self.User.clean_uid(cmd[5]) user_uid = self.User.clean_uid(cmd[5])
userObj: MUser = self.User.get_User(user_uid) 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
if client_obj is not None:
nickname = client_obj.account
if userObj is None: if userObj is None:
return None return None
if 'r' not in userObj.umodes and 'o' not in userObj.umodes: if 'r' not in userObj.umodes and 'o' not in userObj.umodes and not self.Client.is_exist(userObj.uid):
return None return None
db_data: dict[str, str] = {"nickname": userObj.nickname, "channel": channel_name} db_data: dict[str, str] = {"nickname": nickname.lower(), "channel": channel_name.lower()}
db_query = self.Base.db_execute_query("SELECT id, mode FROM command_automode WHERE nickname = :nickname AND channel = :channel", db_data) db_query = self.Base.db_execute_query("SELECT id, mode FROM command_automode WHERE LOWER(nickname) = :nickname AND LOWER(channel) = :channel", db_data)
db_result = db_query.fetchone() db_result = db_query.fetchone()
if db_result is not None: if db_result:
id, mode = db_result id, mode = db_result
self.Protocol.send2socket(f":{self.Config.SERVICE_ID} MODE {channel_name} {mode} {userObj.nickname}") self.Protocol.send2socket(f":{self.Config.SERVICE_ID} MODE {channel_name} {mode} {userObj.nickname}")
except KeyError as ke: except KeyError as ke:
self.Logs.error(f"Key Error: {err}") self.Logs.error(f"Key Error: {err}")
except Exception as err: except Exception as err:
self.Logs.error(f"General Error: {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 = []):
command = str(cmd[0]).lower() command = str(cmd[0]).lower()
dnickname = self.Config.SERVICE_NICKNAME dnickname = self.Config.SERVICE_NICKNAME
@@ -278,293 +315,78 @@ class Command:
fromchannel = channel_name fromchannel = channel_name
match command: match command:
case "automode":
# automode set nickname [+/-mode] #channel case 'automode':
# automode set adator +o #channel
try: try:
option: str = str(cmd[1]).lower() self.mod_utils.set_automode(self, cmd, fromuser)
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]")
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_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.Base.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:
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.Base.get_datetime(), "updated_on": self.Base.get_datetime(), "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)}")
except IndexError: 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()} 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"/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: except Exception as err:
self.Logs.error(f"General Error: {err}") self.Logs.error(f"General Error: {err}")
case 'deopall': case 'deopall':
try: try:
self.Protocol.send2socket(f":{service_id} SVSMODE {fromchannel} -o") self.mod_utils.set_deopall(self, fromchannel)
except IndexError as ie:
self.Logs.warning(f'_hcmd OP: {str(ie)}')
except Exception as err: except Exception as err:
self.Logs.warning(f'Unknown Error: {str(err)}') self.Logs.error(f'Unknown Error: {str(err)}')
case 'devoiceall': case 'devoiceall':
try: try:
self.Protocol.send2socket(f":{service_id} SVSMODE {fromchannel} -v") self.mod_utils.set_devoiceall(self, fromchannel)
except IndexError as e:
self.Logs.warning(f'_hcmd OP: {str(e)}')
except Exception as err: except Exception as err:
self.Logs.warning(f'Unknown Error: {str(err)}') self.Logs.error(f'Unknown Error: {str(err)}')
case 'voiceall': case 'voiceall':
try: try:
chan_info = self.Channel.get_Channel(fromchannel) self.mod_utils.set_mode_to_all(self, fromchannel, '+', 'v')
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.Base.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)}')
except Exception as err: except Exception as err:
self.Logs.warning(f'Unknown Error: {str(err)}') self.Logs.warning(f'Unknown Error: {str(err)}')
case 'opall': case 'opall':
try: try:
chan_info = self.Channel.get_Channel(fromchannel) self.mod_utils.set_mode_to_all(self, fromchannel, '+', 'o')
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.Base.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)}')
except Exception as err: except Exception as err:
self.Logs.warning(f'Unknown Error: {str(err)}') self.Logs.warning(f'Unknown Error: {str(err)}')
case 'op': case 'op':
# /mode #channel +o user
# .op #channel user
# /msg dnickname op #channel user
# [':adator', 'PRIVMSG', '#services', ':.o', '#services', 'dktmb']
try: try:
if fromchannel is None: self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '+o')
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}")
except IndexError as e: 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]") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} op [#SALON] [NICKNAME]")
except Exception as err: except Exception as err:
self.Logs.warning(f'Unknown Error: {str(err)}') self.Logs.warning(f'Unknown Error: {str(err)}')
case 'deop': case 'deop':
# /mode #channel -o user
# .deop #channel user
try: try:
if fromchannel is None: self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '-o')
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}")
except IndexError as e: 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]") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} deop [#SALON] [NICKNAME]")
except Exception as err: except Exception as err:
self.Logs.warning(f'Unknown Error: {str(err)}') self.Logs.warning(f'Unknown Error: {str(err)}')
case 'owner': case 'owner':
# /mode #channel +q user
# .owner #channel user
try: try:
if fromchannel is None: self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '+q')
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}")
except IndexError as e: 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]") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} owner [#SALON] [NICKNAME]")
except Exception as err: except Exception as err:
self.Logs.warning(f'Unknown Error: {str(err)}') self.Logs.warning(f'Unknown Error: {str(err)}')
case 'deowner': case 'deowner':
# /mode #channel -q user
# .deowner #channel user
try: try:
if fromchannel is None: self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '-q')
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}")
except IndexError as e: 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]") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} deowner [#SALON] [NICKNAME]")
except Exception as err: except Exception as err:
self.Logs.warning(f'Unknown Error: {str(err)}') self.Logs.warning(f'Unknown Error: {str(err)}')
case 'protect': case 'protect':
# /mode #channel +a user
# .protect #channel user
try: try:
if fromchannel is None: self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '+a')
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}")
except IndexError as e: except IndexError as e:
self.Logs.warning(f'_hcmd DEOWNER: {str(e)}') self.Logs.warning(f'_hcmd DEOWNER: {str(e)}')
@@ -573,25 +395,8 @@ class Command:
self.Logs.warning(f'Unknown Error: {str(err)}') self.Logs.warning(f'Unknown Error: {str(err)}')
case 'deprotect': case 'deprotect':
# /mode #channel -a user
# .deprotect #channel user
try: try:
if fromchannel is None: self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '-a')
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}")
except IndexError as e: except IndexError as e:
self.Logs.warning(f'_hcmd DEOWNER: {str(e)}') self.Logs.warning(f'_hcmd DEOWNER: {str(e)}')
@@ -600,125 +405,42 @@ class Command:
self.Logs.warning(f'Unknown Error: {str(err)}') self.Logs.warning(f'Unknown Error: {str(err)}')
case 'halfop': case 'halfop':
# /mode #channel +h user
# .halfop #channel user
try: try:
if fromchannel is None: self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '+h')
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}")
except IndexError as e: 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} {command} [#SALON] [NICKNAME]")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} halfop [#SALON] [NICKNAME]")
except Exception as err: except Exception as err:
self.Logs.warning(f'Unknown Error: {str(err)}') self.Logs.warning(f'Unknown Error: {str(err)}')
case 'dehalfop': case 'dehalfop':
# /mode #channel -h user
# .dehalfop #channel user
try: try:
if fromchannel is None: self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '-h')
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}")
except IndexError as e: 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]") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} dehalfop [#SALON] [NICKNAME]")
except Exception as err: except Exception as err:
self.Logs.warning(f'Unknown Error: {str(err)}') self.Logs.warning(f'Unknown Error: {str(err)}')
case 'voice': case 'voice':
# /mode #channel +v user
# .voice #channel user
try: try:
if fromchannel is None: self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '+v')
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}")
except IndexError as e: 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]") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} voice [#SALON] [NICKNAME]")
except Exception as err: except Exception as err:
self.Logs.warning(f'Unknown Error: {str(err)}') self.Logs.warning(f'Unknown Error: {str(err)}')
case 'devoice': case 'devoice':
# /mode #channel -v user
# .devoice #channel user
try: try:
if fromchannel is None: self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '-v')
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}")
except IndexError as e: 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]") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} devoice [#SALON] [NICKNAME]")
except Exception as err: except Exception as err:
self.Logs.warning(f'Unknown Error: {str(err)}') self.Logs.warning(f'Unknown Error: {str(err)}')
case 'ban': case 'ban':
# .ban #channel nickname
try: try:
sentchannel = str(cmd[1]) if self.Channel.Is_Channel(cmd[1]) else None self.mod_utils.set_ban(self, cmd, '+', fromuser)
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}')
except IndexError as e: except IndexError as e:
self.Logs.warning(f'_hcmd BAN: {str(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]") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON] [NICKNAME]")
@@ -726,99 +448,48 @@ class Command:
self.Logs.warning(f'Unknown Error: {str(err)}') self.Logs.warning(f'Unknown Error: {str(err)}')
case 'unban': case 'unban':
# .unban #channel nickname
try: try:
sentchannel = str(cmd[1]) if self.Channel.Is_Channel(cmd[1]) else None self.mod_utils.set_ban(self, cmd, '-', fromuser)
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}')
except IndexError as e: except IndexError as e:
self.Logs.warning(f'_hcmd UNBAN: {str(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: except Exception as err:
self.Logs.warning(f'Unknown Error: {str(err)}') self.Logs.warning(f'Unknown Error: {str(err)}')
case 'kick': case 'kick':
# .kick #channel nickname reason
try: try:
sentchannel = str(cmd[1]) if self.Channel.Is_Channel(cmd[1]) else None self.mod_utils.set_kick(self, cmd, fromuser)
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}')
except IndexError as e: except IndexError as e:
self.Logs.warning(f'_hcmd KICK: {str(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: except Exception as err:
self.Logs.warning(f'Unknown Error: {str(err)}') self.Logs.warning(f'Unknown Error: {str(err)}')
case 'kickban': case 'kickban':
# .kickban #channel nickname reason
try: try:
sentchannel = str(cmd[1]) if self.Channel.Is_Channel(cmd[1]) else None self.mod_utils.set_kickban(self, cmd, fromuser)
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}')
except IndexError as e: except IndexError as e:
self.Logs.warning(f'_hcmd KICKBAN: {str(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: except Exception as err:
self.Logs.warning(f'Unknown Error: {str(err)}') self.Logs.warning(f'Unknown Error: {str(err)}')
case 'join': case 'join' | 'assign':
try: try:
sent_channel = str(cmd[1]) if self.Channel.Is_Channel(cmd[1]) else None self.mod_utils.set_assign_channel_to_service(self, cmd, fromuser)
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)
except IndexError as ie: except IndexError as ie:
self.Logs.error(f'{ie}') self.Logs.debug(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: except Exception as err:
self.Logs.warning(f'Unknown Error: {str(err)}') self.Logs.warning(f'Unknown Error: {str(err)}')
case 'part': case 'part' | 'unassign':
try: try:
sent_channel = str(cmd[1]) if self.Channel.Is_Channel(cmd[1]) else None # Syntax. !part #channel
if sent_channel is None: self.mod_utils.set_unassign_channel_to_service(self, cmd, fromuser)
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)
except IndexError as ie: except IndexError as ie:
self.Logs.error(f'{ie}') self.Logs.debug(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: except Exception as err:
self.Logs.warning(f'Unknown Error: {str(err)}') self.Logs.warning(f'Unknown Error: {str(err)}')
@@ -830,7 +501,7 @@ class Command:
return None return None
chan = str(cmd[1]) 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="The channel must start with #")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} TOPIC #channel THE_TOPIC_MESSAGE") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} TOPIC #channel THE_TOPIC_MESSAGE")
return None return None
@@ -930,7 +601,7 @@ class Command:
chan = str(cmd[1]) 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="The channel must start with #")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {str(cmd[0]).upper()} #channel") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {str(cmd[0]).upper()} #channel")
return None return None
@@ -951,7 +622,7 @@ class Command:
nickname = str(cmd[1]) nickname = str(cmd[1])
chan = str(cmd[2]) 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="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") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {str(cmd[0]).upper()} NICKNAME #CHANNEL")
return None return None
@@ -1001,7 +672,7 @@ class Command:
nickname = str(cmd[1]) nickname = str(cmd[1])
umode = str(cmd[2]) umode = str(cmd[2])
self.Protocol.send_svs_mode(nickname=nickname, user_mode=umode) self.Protocol.send_svsmode(nickname=nickname, user_mode=umode)
except KeyError as ke: except KeyError as ke:
self.Logs.error(ke) self.Logs.error(ke)
except Exception as err: except Exception as err:
@@ -1022,7 +693,7 @@ class Command:
if len(cmd) == 2: if len(cmd) == 2:
channel_mode = cmd[1] 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}") self.Protocol.send2socket(f":{dnickname} MODE {fromchannel} {channel_mode}")
else: else:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : Channel [{fromchannel}] is not correct should start with #") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : Channel [{fromchannel}] is not correct should start with #")
@@ -1048,34 +719,64 @@ class Command:
case 'svsjoin': case 'svsjoin':
try: try:
# .svsjoin nickname #channel # SVSJOIN <nick> <channel>[,<channel2>..] [key1[,key2[..]]]
nickname = str(cmd[1]) if len(cmd) < 4:
channel = str(cmd[2]) self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSJOIN <nick> <channel>[,<channel2>..] [key1[,key2[..]]]")
if len(cmd) != 3:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSJOIN nickname #channel")
return None return None
self.Protocol.send2socket(f':{self.Config.SERVEUR_ID} SVSJOIN {nickname} {channel}') nickname = str(cmd[1])
except KeyError as ke: channels = str(cmd[2]).split(',')
keys = str(cmd[3]).split(',')
self.Protocol.send_svsjoin(nickname, channels, keys)
except IndexError as ke:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSJOIN <nick> <channel>[,<channel2>..] [key1[,key2[..]]]")
self.Logs.error(ke) self.Logs.error(ke)
except Exception as err: except Exception as err:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSJOIN nickname #channel") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSJOIN <nick> <channel>[,<channel2>..] [key1[,key2[..]]]")
self.Logs.warning(f'Unknown Error: {str(err)}') self.Logs.warning(f'Unknown Error: {str(err)}')
case 'svspart': case 'svspart':
try: try:
# svspart nickname #channel # SVSPART <nick> <channel>[,<channel2>..] [<comment>]
nickname = str(cmd[1]) if len(cmd) < 4:
channel = str(cmd[2]) self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSPART <nick> <channel>[,<channel2>..] [<comment>]")
if len(cmd) != 3:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSPART nickname #channel")
return None return None
self.Protocol.send2socket(f':{self.Config.SERVEUR_ID} SVSPART {nickname} {channel}') nickname = str(cmd[1])
except KeyError as ke: channels = str(cmd[2]).split(',')
reason = ' '.join(cmd[3:])
self.Protocol.send_svspart(nickname, channels, reason)
except IndexError as ke:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSPART <nick> <channel>[,<channel2>..] [<comment>]")
self.Logs.error(ke) self.Logs.error(ke)
except Exception as err: except Exception as err:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSPART nickname #channel") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSPART <nick> <channel>[,<channel2>..] [<comment>]")
self.Logs.warning(f'Unknown Error: {str(err)}')
case 'svsnick':
try:
# .svsnick nickname newnickname
nickname = str(cmd[1])
newnickname = str(cmd[2])
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")
return None
if len(cmd) != 3:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname newnickname")
return None
self.Protocol.send2socket(f':{self.Config.SERVEUR_ID} SVSNICK {nickname} {newnickname} {unixtime}')
except IndexError as ke:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname newnickname")
self.Logs.error(ke)
except Exception as err:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname newnickname")
self.Logs.warning(f'Unknown Error: {str(err)}') self.Logs.warning(f'Unknown Error: {str(err)}')
case 'sajoin': case 'sajoin':
@@ -1098,41 +799,19 @@ class Command:
case 'sapart': case 'sapart':
try: try:
# .sapart nickname #channel # .sapart nickname #channel
nickname = str(cmd[1])
channel = str(cmd[2])
if len(cmd) < 3: if len(cmd) < 3:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname #channel") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname #channel")
return None return None
nickname = str(cmd[1])
channel = str(cmd[2])
self.Protocol.send_sapart(nick_to_sapart=nickname, channel_name=channel) self.Protocol.send_sapart(nick_to_sapart=nickname, channel_name=channel)
except KeyError as ke: except KeyError as ke:
self.Logs.error(ke) self.Logs.error(ke)
except Exception as err: except Exception as err:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname #channel") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname #channel")
self.Logs.warning(f'Unknown Error: {str(err)}') self.Logs.error(f'Unknown Error: {str(err)}')
case 'svsnick':
try:
# .svsnick nickname newnickname
nickname = str(cmd[1])
newnickname = str(cmd[2])
unixtime = self.Base.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")
return None
if len(cmd) != 3:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname newnickname")
return None
self.Protocol.send2socket(f':{self.Config.SERVEUR_ID} SVSNICK {nickname} {newnickname} {unixtime}')
except KeyError as ke:
self.Logs.error(ke)
except Exception as err:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname newnickname")
self.Logs.warning(f'Unknown Error: {str(err)}')
case 'kill': case 'kill':
try: try:
@@ -1162,7 +841,7 @@ class Command:
nickname = str(cmd[1]) nickname = str(cmd[1])
hostname = str(cmd[2]) 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 expire_time = (60 * 60 * 24) + set_at_timestamp
gline_reason = ' '.join(cmd[3:]) gline_reason = ' '.join(cmd[3:])
@@ -1171,7 +850,7 @@ class Command:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname host reason") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname host reason")
return None 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: except KeyError as ke:
self.Logs.error(ke) self.Logs.error(ke)
@@ -1192,7 +871,7 @@ class Command:
hostname = str(cmd[2]) hostname = str(cmd[2])
# self.Protocol.send2socket(f":{self.Config.SERVEUR_ID} TKL - G {nickname} {hostname} {dnickname}") # 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: except KeyError as ke:
self.Logs.error(ke) self.Logs.error(ke)
@@ -1210,7 +889,7 @@ class Command:
nickname = str(cmd[1]) nickname = str(cmd[1])
hostname = str(cmd[2]) 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 expire_time = (60 * 60 * 24) + set_at_timestamp
gline_reason = ' '.join(cmd[3:]) gline_reason = ' '.join(cmd[3:])
@@ -1219,7 +898,7 @@ class Command:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname host reason") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname host reason")
return None 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: except KeyError as ke:
self.Logs.error(ke) self.Logs.error(ke)
@@ -1239,7 +918,7 @@ class Command:
nickname = str(cmd[1]) nickname = str(cmd[1])
hostname = str(cmd[2]) hostname = str(cmd[2])
self.Protocol.unkline(nickname=nickname, hostname=hostname) self.Protocol.send_unkline(nickname=nickname, hostname=hostname)
except KeyError as ke: except KeyError as ke:
self.Logs.error(ke) self.Logs.error(ke)
@@ -1258,7 +937,7 @@ class Command:
nickname = str(cmd[1]) nickname = str(cmd[1])
hostname = str(cmd[2]) 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 expire_time = (60 * 60 * 24) + set_at_timestamp
shun_reason = ' '.join(cmd[3:]) shun_reason = ' '.join(cmd[3:])

243
mods/command/utils.py Normal file
View File

@@ -0,0 +1,243 @@
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:
if len(cmd) < 2:
raise IndexError(f"{cmd[0].upper()} is expecting the channel parameter")
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:
if len(cmd) < 2:
raise IndexError(f"{cmd[0].upper()} is expecting the channel parameter")
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

File diff suppressed because it is too large Load Diff

35
mods/defender/schemas.py Normal file
View File

@@ -0,0 +1,35 @@
from core.definition import MainModel, dataclass, MUser
@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
@dataclass
class FloodUser(MainModel):
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] = []

167
mods/defender/threads.py Normal file
View File

@@ -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_valid_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

710
mods/defender/utils.py Normal file
View File

@@ -0,0 +1,710 @@
from calendar import c
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_valid_channel(srvmsg[4]) else None
parsed_UID = uplink.Loader.Utils.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.Loader.Utils.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.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)
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.Utils.generate_random_string(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_valid_channel(channel_to_check=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.Utils.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}')

View File

@@ -1,7 +1,12 @@
import logging 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 from typing import TYPE_CHECKING
from dataclasses import dataclass from dataclasses import dataclass
from unrealircd_rpc_py.Live import Live from unrealircd_rpc_py.Live import LiveWebsocket, LiveUnixSocket
from unrealircd_rpc_py.Loader import Loader from unrealircd_rpc_py.Loader import Loader
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -32,8 +37,11 @@ class Jsonrpc():
# Add Base object to the module (Mandatory) # Add Base object to the module (Mandatory)
self.Base = ircInstance.Base self.Base = ircInstance.Base
# Add Main Utils (Mandatory)
self.MainUtils = ircInstance.Utils
# Add logs object to the module (Mandatory) # Add logs object to the module (Mandatory)
self.Logs = ircInstance.Base.logs self.Logs = ircInstance.Loader.Logs
# Add User object to the module (Mandatory) # Add User object to the module (Mandatory)
self.User = ircInstance.User self.User = ircInstance.User
@@ -41,10 +49,22 @@ class Jsonrpc():
# Add Channel object to the module (Mandatory) # Add Channel object to the module (Mandatory)
self.Channel = ircInstance.Channel self.Channel = ircInstance.Channel
# 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, self.MainUtils.run_python_garbage_collector)
# Create module commands (Mandatory) # Create module commands (Mandatory)
self.commands_level = { self.Irc.build_command(1, self.module_name, 'jsonrpc', 'Activate the JSON RPC Live connection [ON|OFF]')
1: ['jsonrpc', 'jruser'] 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 # Init the module
self.__init_module() self.__init_module()
@@ -54,10 +74,9 @@ class Jsonrpc():
def __init_module(self) -> None: def __init_module(self) -> None:
# Insert module commands into the core one (Mandatory)
self.__set_commands(self.commands_level)
logging.getLogger('websockets').setLevel(logging.WARNING) logging.getLogger('websockets').setLevel(logging.WARNING)
logging.getLogger('unrealircd-rpc-py').setLevel(logging.CRITICAL) logging.getLogger('unrealircd-rpc-py').setLevel(logging.CRITICAL)
logging.getLogger('unrealircd-liverpc-py').setLevel(logging.CRITICAL)
# Create you own tables (Mandatory) # Create you own tables (Mandatory)
# self.__create_tables() # self.__create_tables()
@@ -66,15 +85,23 @@ class Jsonrpc():
self.__load_module_configuration() self.__load_module_configuration()
# End of mandatory methods you can start your customization # # End of mandatory methods you can start your customization #
self.UnrealIrcdRpcLive: Live = Live( self.UnrealIrcdRpcLive: LiveWebsocket = LiveWebsocket(
req_method='websocket',
url=self.Config.JSONRPC_URL, url=self.Config.JSONRPC_URL,
username=self.Config.JSONRPC_USER, username=self.Config.JSONRPC_USER,
password=self.Config.JSONRPC_PASSWORD, password=self.Config.JSONRPC_PASSWORD,
callback_object_instance=self, callback_object_instance=self,
callback_method_name='callback_sent_to_irc' callback_method_or_function_name='callback_sent_to_irc'
) )
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(
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
)
raise Exception(f"[LIVE-JSONRPC ERROR] {self.UnrealIrcdRpcLive.get_error.message}")
self.Rpc: Loader = Loader( self.Rpc: Loader = Loader(
req_method=self.Config.JSONRPC_METHOD, req_method=self.Config.JSONRPC_METHOD,
url=self.Config.JSONRPC_URL, url=self.Config.JSONRPC_URL,
@@ -82,38 +109,17 @@ class Jsonrpc():
password=self.Config.JSONRPC_PASSWORD password=self.Config.JSONRPC_PASSWORD
) )
self.subscribed = False if self.Rpc.get_error.code != 0:
self.Logs.error(f"{self.Rpc.get_error.message} ({self.Rpc.get_error.code})")
if self.Rpc.Error.code != 0:
self.Protocol.send_priv_msg( self.Protocol.send_priv_msg(
nick_from=self.Config.SERVICE_NICKNAME, nick_from=self.Config.SERVICE_NICKNAME,
msg=f"[{self.Config.COLORS.red}ERROR{self.Config.COLORS.nogc}] {self.Rpc.Error.message}", msg=f"[{self.Config.COLORS.red}JSONRPC ERROR{self.Config.COLORS.nogc}] {self.Rpc.get_error.message}",
channel=self.Config.SERVICE_CHANLOG
)
if self.UnrealIrcdRpcLive.Error.code != 0:
self.Protocol.send_priv_msg(
nick_from=self.Config.SERVICE_NICKNAME,
msg=f"[{self.Config.COLORS.red}ERROR{self.Config.COLORS.nogc}] {self.UnrealIrcdRpcLive.Error.message}",
channel=self.Config.SERVICE_CHANLOG channel=self.Config.SERVICE_CHANLOG
) )
raise Exception(f"[JSONRPC ERROR] {self.Rpc.get_error.message}")
if self.ModConfig.jsonrpc == 1: 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
def __set_commands(self, commands:dict[int, list[str]]) -> None:
"""### Rajoute les commandes du module au programme principal
Args:
commands (list): Liste des commandes du module
"""
for level, com in commands.items():
for c in commands[level]:
if not c in self.Irc.commands:
self.Irc.commands_level[level].append(c)
self.Irc.commands.append(c)
return None return None
@@ -137,7 +143,7 @@ class Jsonrpc():
self.Base.db_execute_query(table_logs) self.Base.db_execute_query(table_logs)
return None return None
def callback_sent_to_irc(self, json_response: str): def callback_sent_to_irc(self, response: SimpleNamespace) -> None:
dnickname = self.Config.SERVICE_NICKNAME dnickname = self.Config.SERVICE_NICKNAME
dchanlog = self.Config.SERVICE_CHANLOG dchanlog = self.Config.SERVICE_CHANLOG
@@ -146,34 +152,40 @@ class Jsonrpc():
bold = self.Config.COLORS.bold bold = self.Config.COLORS.bold
red = self.Config.COLORS.red red = self.Config.COLORS.red
if json_response.result == True: if self.UnrealIrcdRpcLive.get_error.code != 0:
self.Protocol.send_priv_msg( self.Protocol.send_priv_msg(nick_from=dnickname,
nick_from=self.Config.SERVICE_NICKNAME, msg=f"[{bold}{red}JSONRPC ERROR{nogc}{bold}] {self.UnrealIrcdRpcLive.get_error.message}",
msg=f"[{bold}{green}JSONRPC{nogc}{bold}] Event activated",
channel=dchanlog) channel=dchanlog)
return None return None
level = json_response.result.level if hasattr(response, 'error'):
subsystem = json_response.result.subsystem if response.error.code != 0:
event_id = json_response.result.event_id
log_source = json_response.result.log_source
msg = json_response.result.msg
build_msg = f"{green}{log_source}{nogc}: [{bold}{level}{bold}] {subsystem}.{event_id} - {msg}"
self.Protocol.send_priv_msg(nick_from=dnickname, msg=build_msg, channel=dchanlog)
def thread_start_jsonrpc(self):
if self.UnrealIrcdRpcLive.Error.code == 0:
self.UnrealIrcdRpcLive.subscribe(["all"])
self.subscribed = True
else:
self.Protocol.send_priv_msg( self.Protocol.send_priv_msg(
nick_from=self.Config.SERVICE_NICKNAME, nick_from=self.Config.SERVICE_NICKNAME,
msg=f"[{self.Config.COLORS.red}ERROR{self.Config.COLORS.nogc}] {self.UnrealIrcdRpcLive.Error.message}", msg=f"[{bold}{red}JSONRPC{nogc}{bold}] JSONRPC Event activated on {self.Config.JSONRPC_URL}",
channel=self.Config.SERVICE_CHANLOG 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 ''
event_id = response.result.event_id if hasattr(response.result, 'event_id') else ''
log_source = response.result.log_source if hasattr(response.result, 'log_source') else ''
msg = response.result.msg if hasattr(response.result, 'msg') else ''
build_msg = f"{green}{log_source}{nogc}: [{bold}{level}{bold}] {subsystem}.{event_id} - {msg}"
self.Protocol.send_priv_msg(nick_from=dnickname, msg=build_msg, channel=dchanlog)
return None
def __load_module_configuration(self) -> None: def __load_module_configuration(self) -> None:
"""### Load Module Configuration """### Load Module Configuration
@@ -190,7 +202,7 @@ class Jsonrpc():
except TypeError as te: except TypeError as te:
self.Logs.critical(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 """Update the local and core configuration
Args: Args:
@@ -200,8 +212,15 @@ class Jsonrpc():
self.Base.db_update_core_config(self.module_name, self.ModConfig, param_key, param_value) self.Base.db_update_core_config(self.module_name, self.ModConfig, param_key, param_value)
def unload(self) -> None: def unload(self) -> None:
if self.UnrealIrcdRpcLive.Error.code != -1: if self.is_streaming:
self.UnrealIrcdRpcLive.unsubscribe() 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.Threads.thread_unsubscribe, func_args=(self, ), run_once=True)
self.update_configuration('jsonrpc', 0)
self.Logs.debug(f"Unloading {self.module_name}")
return None return None
def cmd(self, data: list) -> None: def cmd(self, data: list) -> None:
@@ -220,54 +239,42 @@ class Jsonrpc():
case 'jsonrpc': case 'jsonrpc':
try: try:
option = str(cmd[1]).lower() if len(cmd) < 2:
if len(command) == 1:
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 on')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'/msg {dnickname} jsonrpc off') 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: match option:
case 'on': case 'on':
thread_name = 'thread_subscribe'
# for logger_name, logger in logging.root.manager.loggerDict.items(): if self.Base.is_thread_alive(thread_name):
# if isinstance(logger, logging.Logger): self.Protocol.send_priv_msg(nick_from=dnickname, channel=dchannel, msg=f"The Subscription is running")
# self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"{logger_name} - {logger.level}") return None
elif self.Base.is_thread_exist(thread_name):
for thread in self.Base.running_threads:
if thread.getName() == 'thread_start_jsonrpc':
if thread.is_alive():
self.Protocol.send_priv_msg( self.Protocol.send_priv_msg(
nick_from=self.Config.SERVICE_NICKNAME, nick_from=dnickname, channel=dchannel,
msg=f"Thread {thread.getName()} is running", msg=f"The subscription is not running, wait untill the process will be cleaned up"
channel=dchannel
)
else:
self.Protocol.send_priv_msg(
nick_from=self.Config.SERVICE_NICKNAME,
msg=f"Thread {thread.getName()} is not running, wait untill the process will be cleaned up",
channel=dchannel
) )
return None
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)
self.__update_configuration('jsonrpc', 1) self.update_configuration('jsonrpc', 1)
case 'off': case 'off':
self.UnrealIrcdRpcLive.unsubscribe() self.Base.create_thread(func=self.Threads.thread_unsubscribe, func_args=(self, ), run_once=True)
self.__update_configuration('jsonrpc', 0) self.update_configuration('jsonrpc', 0)
except IndexError as ie: except IndexError as ie:
self.Logs.error(ie) self.Logs.error(ie)
case 'jruser': case 'jruser':
try: try:
option = str(cmd[1]).lower() if len(cmd) < 2:
if len(command) == 1:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'/msg {dnickname} jruser get nickname') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'/msg {dnickname} jruser get nickname')
option = str(cmd[1]).lower()
match option: match option:
case 'get': case 'get':
nickname = str(cmd[2]) nickname = str(cmd[2])
uid_to_get = self.User.get_uid(nickname) uid_to_get = self.User.get_uid(nickname)
@@ -277,20 +284,17 @@ class Jsonrpc():
rpc = self.Rpc rpc = self.Rpc
UserInfo = rpc.User.get(uid_to_get) UserInfo = rpc.User.get(uid_to_get)
if rpc.Error.code != 0: if rpc.get_error.code != 0:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'{rpc.Error.message}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'{rpc.get_error.message}')
return None 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'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'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'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'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'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'SECURITY GROUP : {UserInfo.user.security_groups}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'REPUTATION : {UserInfo.user.reputation}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'REPUTATION : {UserInfo.user.reputation}')
@@ -313,22 +317,12 @@ class Jsonrpc():
except IndexError as ie: except IndexError as ie:
self.Logs.error(ie) self.Logs.error(ie)
case 'ia': case 'jrinstances':
try: try:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"GC Collect: {self.MainUtils.run_python_garbage_collector()}")
self.Base.create_thread(self.thread_ask_ia, ('',)) 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" This is a notice to the sender ...") 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_priv_msg(nick_from=dnickname, msg="This is private message to the sender ...", nick_to=fromuser) self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre de toute les instances: {self.MainUtils.get_number_gc_objects()}")
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")
except Exception as err: except Exception as err:
self.Logs.error(f"Unknown Error: {err}") self.Logs.error(f"Unknown Error: {err}")

60
mods/jsonrpc/threads.py Normal file
View File

@@ -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
)

0
mods/jsonrpc/utils.py Normal file
View File

View File

@@ -34,7 +34,7 @@ class Test():
self.Base = ircInstance.Base self.Base = ircInstance.Base
# Add logs object to the module (Mandatory) # Add logs object to the module (Mandatory)
self.Logs = ircInstance.Base.logs self.Logs = ircInstance.Loader.Logs
# Add User object to the module (Mandatory) # Add User object to the module (Mandatory)
self.User = ircInstance.User self.User = ircInstance.User
@@ -46,12 +46,11 @@ class Test():
self.Reputation = ircInstance.Reputation self.Reputation = ircInstance.Reputation
# Create module commands (Mandatory) # Create module commands (Mandatory)
self.commands_level = { self.Irc.build_command(0, self.module_name, 'test-command', 'Execute a test command')
0: ['test-command'], self.Irc.build_command(1, self.module_name, 'test_level_1', 'Execute a level 1 test command')
1: ['test_level_1'], self.Irc.build_command(2, self.module_name, 'test_level_2', 'Execute a level 2 test command')
2: ['test_level_2'], self.Irc.build_command(3, self.module_name, 'test_level_3', 'Execute a level 3 test command')
3: ['test_level_3']
}
# Init the module # Init the module
self.__init_module() self.__init_module()
@@ -61,9 +60,6 @@ class Test():
def __init_module(self) -> None: def __init_module(self) -> None:
# Insert module commands into the core one (Mandatory)
self.__set_commands(self.commands_level)
# Create you own tables (Mandatory) # Create you own tables (Mandatory)
self.__create_tables() self.__create_tables()
@@ -73,20 +69,6 @@ class Test():
return None return None
def __set_commands(self, commands:dict[int, list[str]]) -> None:
"""### Rajoute les commandes du module au programme principal
Args:
commands (list): Liste des commandes du module
"""
for level, com in commands.items():
for c in commands[level]:
if not c in self.Irc.commands:
self.Irc.commands_level[level].append(c)
self.Irc.commands.append(c)
return None
def __create_tables(self) -> None: def __create_tables(self) -> None:
"""Methode qui va créer la base de donnée si elle n'existe pas. """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 Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module

View File

@@ -1,64 +1,78 @@
from typing import TYPE_CHECKING """
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
-----------------------------------------
"""
import re import re
from dataclasses import dataclass, field 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: if TYPE_CHECKING:
from core.irc import Irc 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:
def __init__(self, uplink: 'Irc') -> None:
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:
# Module name (Mandatory) # Module name (Mandatory)
self.module_name = 'mod_' + str(self.__class__.__name__).lower() self.module_name = 'mod_' + str(self.__class__.__name__).lower()
# Add Irc Object to the module # Add Irc Object to the module
self.Irc = ircInstance self.Irc = uplink
# Add Loader Object to the module (Mandatory) # Add Loader Object to the module (Mandatory)
self.Loader = ircInstance.Loader self.Loader = uplink.Loader
# Add server protocol Object to the module (Mandatory) # Add server protocol Object to the module (Mandatory)
self.Protocol = ircInstance.Protocol self.Protocol = uplink.Protocol
# Add Global Configuration to the module # Add Global Configuration to the module
self.Config = ircInstance.Config self.Config = uplink.Config
# Add Base object to the module # Add Base object to the module
self.Base = ircInstance.Base self.Base = uplink.Base
# Add logs object to the module # Add logs object to the module
self.Logs = ircInstance.Base.logs self.Logs = uplink.Logs
# Add User object to the module # Add User object to the module
self.User = ircInstance.User self.User = uplink.User
# Add Channel object to the module # Add Channel object to the module
self.Channel = ircInstance.Channel self.Channel = uplink.Channel
# 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)
metadata = uplink.Loader.Settings.get_cache('VOTEKICK')
if metadata is not None:
self.VoteKickManager.VOTE_CHANNEL_DB = metadata
# self.VOTE_CHANNEL_DB = metadata
# Créer les nouvelles commandes du module # Créer les nouvelles commandes du module
self.commands_level = { self.Irc.build_command(1, self.module_name, 'vote', 'The kick vote module')
0: ['vote']
}
# Init the module # Init the module
self.__init_module() self.__init_module()
@@ -70,32 +84,14 @@ class Votekick():
# Add admin object to retrieve admin users # Add admin object to retrieve admin users
self.Admin = self.Irc.Admin self.Admin = self.Irc.Admin
self.__set_commands(self.commands_level)
self.__create_tables() self.__create_tables()
self.join_saved_channels() self.ModUtils.join_saved_channels(self)
return None
def __set_commands(self, commands:dict[int, list[str]]) -> None:
"""### Rajoute les commandes du module au programme principal
Args:
commands (list): Liste des commandes du module
"""
for level, com in commands.items():
for c in commands[level]:
if not c in self.Irc.commands:
self.Irc.commands_level[level].append(c)
self.Irc.commands.append(c)
return None return None
def __create_tables(self) -> None: def __create_tables(self) -> None:
"""Methode qui va créer la base de donnée si elle n'existe pas. """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 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: Returns:
None: Aucun retour n'es attendu None: Aucun retour n'es attendu
@@ -121,11 +117,14 @@ class Votekick():
def unload(self) -> None: def unload(self) -> None:
try: try:
for chan in self.VOTE_CHANNEL_DB: # Cache the local DB with current votes.
self.Loader.Settings.set_cache('VOTEKICK', self.VoteKickManager.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.Protocol.send_part_chan(uidornickname=self.Config.SERVICE_ID, channel=chan.channel_name)
self.VOTE_CHANNEL_DB = [] self.VoteKickManager.VOTE_CHANNEL_DB = []
self.Logs.debug(f'Delete memory DB VOTE_CHANNEL_DB: {self.VOTE_CHANNEL_DB}') self.Logs.debug(f'Delete memory DB VOTE_CHANNEL_DB: {self.VoteKickManager.VOTE_CHANNEL_DB}')
return None return None
except UnboundLocalError as ne: except UnboundLocalError as ne:
@@ -135,136 +134,27 @@ class Votekick():
except Exception as err: except Exception as err:
self.Logs.error(f'General Error: {err}') self.Logs.error(f'General Error: {err}')
def init_vote_system(self, channel: str) -> bool:
response = False
for chan in self.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, ChannelObject: VoteChannelModel) -> bool:
result = False
found = False
for chan in self.VOTE_CHANNEL_DB:
if chan.channel_name == ChannelObject.channel_name:
found = True
if not found:
self.VOTE_CHANNEL_DB.append(ChannelObject)
self.Logs.debug(f"The channel has been added {ChannelObject}")
# self.db_add_vote_channel(ChannelObject.channel_name)
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.Base.get_datetime()
mes_donnees = {'channel': channel}
response = self.Base.db_execute_query("SELECT id FROM votekick_channel WHERE channel = :channel", mes_donnees)
isChannelExist = response.fetchone()
if isChannelExist 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()
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))
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.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.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: def cmd(self, data: list) -> None:
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: try:
cmd = list(data).copy()
match command:
case 'PRIVMSG':
return None
case 'QUIT':
return None
case _:
return None return None
except KeyError as ke: except KeyError as ke:
@@ -274,11 +164,12 @@ class Votekick():
except Exception as err: except Exception as err:
self.Logs.error(f"General Error: {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 # cmd is the command starting from the user command
# full cmd is sending the entire server response # full cmd is sending the entire server response
command = str(cmd[0]).lower() command = str(cmd[0]).lower()
fullcmd = fullcmd
dnickname = self.Config.SERVICE_NICKNAME dnickname = self.Config.SERVICE_NICKNAME
fromuser = user fromuser = user
fromchannel = channel fromchannel = channel
@@ -305,32 +196,25 @@ class Votekick():
case 'activate': case 'activate':
try: try:
# vote activate #channel # 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') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' :Your are not allowed to execute this command')
return None 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: 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.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( if self.VoteKickManager.activate_new_channel(sentchannel):
self.VoteChannelModel(
channel_name=sentchannel,
target_user='',
voter_users=[],
vote_for=0,
vote_against=0
)
)
self.Channel.db_query_channel('add', self.module_name, sentchannel) self.Channel.db_query_channel('add', self.module_name, sentchannel)
self.Protocol.send_join_chan(uidornickname=dnickname, channel=sentchannel) self.Protocol.send_join_chan(uidornickname=dnickname, channel=sentchannel)
self.Protocol.send2socket(f":{dnickname} SAMODE {sentchannel} +o {dnickname}") self.Protocol.send2socket(f":{dnickname} SAMODE {sentchannel} +o {dnickname}")
self.Protocol.send_priv_msg(nick_from=dnickname, self.Protocol.send_priv_msg(nick_from=dnickname,
msg="You can now use !submit <nickname> to decide if he will stay or not on this channel ", msg="You can now use !submit <nickname> to decide if he will stay or not on this channel ",
channel=sentchannel channel=sentchannel
) )
return None
except Exception as err: except Exception as err:
self.Logs.error(f'{err}') self.Logs.error(f'{err}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option} #channel') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option} #channel')
@@ -339,23 +223,21 @@ class Votekick():
case 'deactivate': case 'deactivate':
try: try:
# vote deactivate #channel # 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") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f" Your are not allowed to execute this command")
return None 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: 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.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f" The correct command is {self.Config.SERVICE_PREFIX}{command} {option} #CHANNEL")
self.Protocol.send2socket(f":{dnickname} SAMODE {sentchannel} -o {dnickname}") self.Protocol.send2socket(f":{dnickname} SAMODE {sentchannel} -o {dnickname}")
self.Protocol.send_part_chan(uidornickname=dnickname, channel=sentchannel) self.Protocol.send_part_chan(uidornickname=dnickname, channel=sentchannel)
for chan in self.VOTE_CHANNEL_DB: if self.VoteKickManager.drop_vote_channel_model(sentchannel):
if chan.channel_name == sentchannel: self.Channel.db_query_channel('del', self.module_name, sentchannel)
self.VOTE_CHANNEL_DB.remove(chan) return None
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")
except Exception as err: except Exception as err:
self.Logs.error(f'{err}') self.Logs.error(f'{err}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f" /msg {dnickname} {command} {option} #channel") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f" /msg {dnickname} {command} {option} #channel")
@@ -365,20 +247,11 @@ class Votekick():
try: try:
# vote + # vote +
channel = fromchannel channel = fromchannel
for chan in self.VOTE_CHANNEL_DB: if self.VoteKickManager.action_vote(channel, fromuser, '+'):
if chan.channel_name == channel: self.Protocol.send_priv_msg(nick_from=dnickname, msg="Vote recorded, thank you",channel=channel)
if fromuser in chan.voter_users:
self.Protocol.send_priv_msg(nick_from=dnickname,
msg="You already submitted a vote",
channel=channel
)
else: else:
chan.vote_for += 1 self.Protocol.send_priv_msg(nick_from=dnickname, msg="You already submitted a vote", channel=channel)
chan.voter_users.append(fromuser)
self.Protocol.send_priv_msg(nick_from=dnickname,
msg="Vote recorded, thank you",
channel=channel
)
except Exception as err: except Exception as err:
self.Logs.error(f'{err}') self.Logs.error(f'{err}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option}')
@@ -388,20 +261,11 @@ class Votekick():
try: try:
# vote - # vote -
channel = fromchannel channel = fromchannel
for chan in self.VOTE_CHANNEL_DB: if self.VoteKickManager.action_vote(channel, fromuser, '-'):
if chan.channel_name == channel: self.Protocol.send_priv_msg(nick_from=dnickname, msg="Vote recorded, thank you",channel=channel)
if fromuser in chan.voter_users:
self.Protocol.send_priv_msg(nick_from=dnickname,
msg="You already submitted a vote",
channel=channel
)
else: else:
chan.vote_against += 1 self.Protocol.send_priv_msg(nick_from=dnickname, msg="You already submitted a vote", channel=channel)
chan.voter_users.append(fromuser)
self.Protocol.send_priv_msg(nick_from=dnickname,
msg="Vote recorded, thank you",
channel=channel
)
except Exception as err: except Exception as err:
self.Logs.error(f'{err}') self.Logs.error(f'{err}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option}')
@@ -410,7 +274,7 @@ class Votekick():
case 'cancel': case 'cancel':
try: try:
# vote cancel # 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') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' Your are not allowed to execute this command')
return None return None
@@ -418,9 +282,9 @@ class Votekick():
self.Logs.error(f"The channel is not known, defender can't cancel the vote") 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') 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: if vote.channel_name == channel:
self.init_vote_system(channel) if self.VoteKickManager.init_vote_system(channel):
self.Protocol.send_priv_msg(nick_from=dnickname, self.Protocol.send_priv_msg(nick_from=dnickname,
msg="Vote system re-initiated", msg="Vote system re-initiated",
channel=channel channel=channel
@@ -434,7 +298,7 @@ class Votekick():
case 'status': case 'status':
try: try:
# vote status # vote status
for chan in self.VOTE_CHANNEL_DB: for chan in self.VoteKickManager.VOTE_CHANNEL_DB:
if chan.channel_name == channel: if chan.channel_name == channel:
self.Protocol.send_priv_msg(nick_from=dnickname, 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))}", 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))}",
@@ -448,25 +312,25 @@ class Votekick():
case 'submit': case 'submit':
try: try:
# vote submit nickname # 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') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' Your are not allowed to execute this command')
return None return None
nickname_submitted = cmd[2] nickname_submitted = cmd[2]
uid_submitted = self.User.get_uid(nickname_submitted) uid_submitted = self.User.get_uid(nickname_submitted)
user_submitted = self.User.get_User(nickname_submitted) user_submitted = self.User.get_user(nickname_submitted)
ongoing_user = None
# check if there is an ongoing vote # check if there is an ongoing vote
if self.is_vote_ongoing(channel): if self.VoteKickManager.is_vote_ongoing(channel):
for vote in self.VOTE_CHANNEL_DB: votec = self.VoteKickManager.get_vote_channel_model(channel)
if vote.channel_name == channel: if votec:
ongoing_user = self.User.get_nickname(vote.target_user) ongoing_user = self.User.get_nickname(votec.target_user)
self.Protocol.send_priv_msg(nick_from=dnickname, self.Protocol.send_priv_msg(nick_from=dnickname,
msg=f"There is an ongoing vote on {ongoing_user}", msg=f"There is an ongoing vote on {ongoing_user}",
channel=channel channel=channel
) )
return False return None
# check if the user exist # check if the user exist
if user_submitted is None: if user_submitted is None:
@@ -474,24 +338,24 @@ class Votekick():
msg=f"This nickname <{nickname_submitted}> do not exist", msg=f"This nickname <{nickname_submitted}> do not exist",
channel=channel channel=channel
) )
return False return None
uid_cleaned = self.Base.clean_uid(uid_submitted) uid_cleaned = self.Loader.Utils.clean_uid(uid_submitted)
ChannelInfo = self.Channel.get_Channel(channel) channel_obj = self.Channel.get_channel(channel)
if ChannelInfo is None: 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') 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 = [] 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)) clean_uids_in_channel.append(self.Loader.Utils.clean_uid(uid))
if not uid_cleaned in clean_uids_in_channel: if not uid_cleaned in clean_uids_in_channel:
self.Protocol.send_priv_msg(nick_from=dnickname, self.Protocol.send_priv_msg(nick_from=dnickname,
msg=f"This nickname <{nickname_submitted}> is not available in this channel", msg=f"This nickname <{nickname_submitted}> is not available in this channel",
channel=channel channel=channel
) )
return False return None
# check if Ircop or Service or Bot # check if Ircop or Service or Bot
pattern = fr'[o|B|S]' pattern = fr'[o|B|S]'
@@ -501,9 +365,9 @@ class Votekick():
msg="You cant vote for this user ! he/she is protected", msg="You cant vote for this user ! he/she is protected",
channel=channel 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: if chan.channel_name == channel:
chan.target_user = self.User.get_uid(nickname_submitted) chan.target_user = self.User.get_uid(nickname_submitted)
@@ -512,7 +376,7 @@ class Votekick():
channel=channel 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, self.Protocol.send_priv_msg(nick_from=dnickname,
msg="This vote will end after 60 secondes", msg="This vote will end after 60 secondes",
channel=channel channel=channel
@@ -526,33 +390,34 @@ class Votekick():
case 'verdict': case 'verdict':
try: try:
# vote verdict # 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') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f'Your are not allowed to execute this command')
return None return None
for chan in self.VOTE_CHANNEL_DB: votec = self.VoteKickManager.get_vote_channel_model(channel)
if chan.channel_name == channel: if votec:
target_user = self.User.get_nickname(chan.target_user) target_user = self.User.get_nickname(votec.target_user)
if chan.vote_for > chan.vote_against: if votec.vote_for >= votec.vote_against:
self.Protocol.send_priv_msg(nick_from=dnickname, 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", 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 channel=channel
) )
self.Protocol.send2socket(f":{dnickname} KICK {channel} {target_user} Following the vote, you are not welcome in {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: else:
self.Protocol.send_priv_msg( self.Protocol.send_priv_msg(
nick_from=dnickname, 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 channel=channel
) )
# Init the system if self.VoteKickManager.init_vote_system(channel):
if self.init_vote_system(channel):
self.Protocol.send_priv_msg( self.Protocol.send_priv_msg(
nick_from=dnickname, nick_from=dnickname,
msg="System vote re initiated", msg="System vote re initiated",
channel=channel channel=channel
) )
return None
except Exception as err: except Exception as err:
self.Logs.error(f'{err}') self.Logs.error(f'{err}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option}') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option}')
@@ -567,3 +432,7 @@ class Votekick():
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 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 submit nickname')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote verdict') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote verdict')
return None
case _:
return None

11
mods/votekick/schemas.py Normal file
View File

@@ -0,0 +1,11 @@
from typing import Optional
from core.definition import MainModel
from dataclasses import 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

40
mods/votekick/threads.py Normal file
View File

@@ -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

74
mods/votekick/utils.py Normal file
View File

@@ -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

View File

@@ -0,0 +1,163 @@
from typing import TYPE_CHECKING, Literal, 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 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
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
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

View File

@@ -1,9 +1,9 @@
{ {
"version": "6.0.4", "version": "6.2.2",
"requests": "2.32.3", "requests": "2.32.3",
"psutil": "6.0.0", "psutil": "6.0.0",
"unrealircd_rpc_py": "1.0.7", "unrealircd_rpc_py": "2.0.5",
"sqlalchemy": "2.0.35", "sqlalchemy": "2.0.35",
"faker": "30.1.0" "faker": "30.1.0"
} }