4 Commits

Author SHA1 Message Date
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
15 changed files with 723 additions and 2293 deletions

1
.gitignore vendored
View File

@@ -3,5 +3,4 @@ db/
logs/ logs/
__pycache__/ __pycache__/
configuration.json configuration.json
install.log
test.py test.py

140
README.md
View File

@@ -2,104 +2,70 @@
Defender est un service IRC basé sur la sécurité des réseaux IRC ( UnrealIRCD ) Defender est un service IRC basé sur la sécurité des réseaux IRC ( UnrealIRCD )
Il permet d'ajouter une sécurité supplémentaire pour vérifier les users connectés au réseau Il permet d'ajouter une sécurité supplémentaire pour vérifier les users connectés au réseau
en demandant aux user un code de validation. en demandant aux user un code de validation.
Il permet aux opérateurs de gérer efficacement un canal, tout en offrant aux utilisateurs des outils d'interaction et de décision collective.
# Fonctionnalités principales Pré-requis :
Commandes opérateurs complètes:
Kick: Expulser un utilisateur du canal.
Ban: Interdire définitivement l'accès au canal.
Unban: Lever une interdiction.
Op/Deop: Attribuer ou retirer les droits d'opérateur.
Halfop/Dehalfop: Attribuer ou retirer les droits
Voice/Devoice: Attribuer ou retirer les droits de voix.
Système de quarantaine: - Python version >= 3.10
Mise en quarantaine: Isoler temporairement un utilisateur dans un canal privé. - Pip de python installé sur la machine
Libération: Permettre à un utilisateur de quitter la quarantaine en entrant un code spécifique. - Python librairies psutil & sqlalchemy & requests
- IRC Serveur Version >= UnrealIRCd-6.1.2.2
Système de vote: Lancement de Defender :
Kick: Les utilisateurs peuvent voter pour expulser un membre du canal.
Autres actions: Possibilité d'étendre le système de vote à d'autres actions (ban, etc.).
# Installation automatique sur une machine Debian/Ubuntu - Installer les librairies python : psutil & sqlalchemy & requests
- pip3 install psutil sqlalchemy requests ou pip install psutil sqlalchemy requests
- Ne pas lancer Defender en tant que root
- Créer plutot un service qui lancera Defender en tant qu'utilisateur non root
- Un fichier PID sera crée.
Prérequis: # TO DO LIST
- Système d'exploitation Linux (Windows non supporté)
- Droits d'administrateur (root) pour l'exécution du script
- Python version 3.10 ou supérieure
Bash: - Optimiser le systeme de réputation:
$ git clone https://github.com/adator85/IRC_DEFENDER_MODULES.git - lorsque les users ce connectent, Ils entrent dans un salon puis une fraction de seconde le service les bans
- Renommer le fichier exemple_configuration.json en configuration.json
- Configurer le fichier configuration.json
$ sudo python3 install.py
Si votre configuration est bonne, votre service est censé etre connecté a votre réseau IRC # VERSION 1
# Installation manuelle: [02.01.2024]
Bash: - Rajout de l'activation de la commande flood
$ git clone https://github.com/adator85/IRC_DEFENDER_MODULES.git - Les deux variables RESTART et INIT ont été déplacées vers le module Irc
$ cd IRC_DEFENDER_MODULES - Nouvelle class Install:
$ python3 -m venv .pyenv - Le programme va vérifier si les 3 librairies sont installées (SQLAlchemy & requests & psutil)
$ source .pyenv/bin/activate - Une fois la vérification, il va mêtre a jour pip puis installera les dépendances
- Créer un service nommé "Defender.service" pour votre service et placer le dans "/etc/systemd/system/"
$ sudo systemctl start Defender
# Configuration [28.12.2023]
- Changement de méthode pour récuperer la version actuelle de python
- Ajout de la réponse a une PING de la part d'un utilisateur
- Installation automatique des packages sqlalchemy, requests et psutil
SERVEUR (Serveur) # BUG FIX
SERVEUR_IP: Adresse IP du serveur IRC à rejoindre.
SERVEUR_HOSTNAME: Nom d'hôte du serveur IRC à rejoindre (optionnel).
SERVEUR_LINK: Lien vers le serveur IRC (optionnel).
SERVEUR_PORT: Port de connexion au serveur IRC.
SERVEUR_PASSWORD: Mot de passe d'enregistrement du service sur le serveur IRC.
SERVEUR_ID: Identifiant unique du service.
SERVEUR_SSL: Active la connexion SSL sécurisée au serveur IRC (true/false).
SERVICE (Service)
SERVICE_NAME: Nom du service IRC.
SERVICE_NICKNAME: Surnom utilisé par le service sur le serveur IRC.
SERVICE_REALNAME: Nom réel du service affiché sur le serveur IRC.
SERVICE_USERNAME: Nom d'utilisateur utilisé par le service pour se connecter au serveur IRC.
SERVICE_HOST: Nom d'hôte du service affiché sur le serveur IRC (optionnel).
SERVICE_INFO: Description du service.
SERVICE_CHANLOG: Canal utilisé pour la journalisation des actions du service.
SERVICE_SMODES: Modes serveur appliqués aux canaux rejoints par le service.
SERVICE_CMODES: Modes de canal appliqués aux canaux rejoints par le service.
SERVICE_UMODES: Modes utilisateur appliqués au service.
SERVICE_PREFIX: Caractère utilisé comme préfixe des commandes du service.
COMPTE (Compte)
OWNER: Nom d'utilisateur possédant les droits d'administration du service.
PASSWORD: Mot de passe de l'administrateur du service.
CANAUX (Canaux)
SALON_JAIL: Canal utilisé comme prison pour les utilisateurs sanctionnés.
SALON_JAIL_MODES: Modes appliqués au canal de prison.
SALON_LIBERER: Canal utilisé pour la libération des utilisateurs sanctionnés.
API (API)
API_TIMEOUT: Durée maximale d'attente d'une réponse de l'API en secondes.
SCANNER (Scanner)
PORTS_TO_SCAN: Liste des ports à scanner pour détecter des serveurs potentiellement malveillants.
SÉCURITÉ (Sécurité)
WHITELISTED_IP: Liste d'adresses IP autorisées à contourner certaines restrictions.
GLINE_DURATION: Durée de bannissement temporaire d'un utilisateur en minutes.
DEBUG (Debug)
DEBUG_LEVEL: Niveau de verbosité des messages de debug (plus grand est le nombre, plus il y a d'informations).
COULEURS (Couleurs)
CONFIG_COLOR: Dictionnaire contenant des codes de couleurs IRC pour un meilleur affichage des messages.
Modification de la configuration [29.12.2023]
- Correction des messages de receptions trop longs > 4070 caractéres;
- la méthode boucle et incrémente la réponse tant que le nombre de caractére reçu est supérieur a 4072
- Rajout du protocol MTAGS a la connexion du service
- Impact majeur dans la lecture des messages reçu du serveur ( PRIVMSG, SLOGS, UID, QUIT, NICK, PONG, SJOIN)
Vous devez modifier le fichier config.json en remplaçant les valeurs par défaut avec vos propres informations. Assurez-vous de bien lire la description de chaque paramètre pour une configuration optimale du service. # ALREADY IMPLEMENTED
Attention - Connexion en tant que service
- Gestion des messages reçus/envoyés par le serveur
- Gestion des caractéres spéciaux
- Gestion des logs (salon, fichiers et console)
- Mode debug : gestion des logs coté console
- Création du systeme de gestion de commandes
- Defender reconnait les commandes qui commence par le suffix définit dans la configuration
- Defender reconnait aussi reconnaitre les commandes qui viennent de /msg Defender [commande]
- Identifications
- Systéme d'identification [OK]
- Systéme de changement d'information [OK]
- Suppression d'un admin
- Systéme de groupe d'accés [OK]
Le mot de passe de l'administrateur et le mot de passe du service doivent être modifiés pour des raisons de sécurité. Reputation security
Ne partagez pas vos informations de connexion au serveur IRC avec des tiers. - Activation ou désaction du systéme --> OK | .reputation ON/off
- Le user sera en mesure de changer la limite de la réputation --> OK | .reputation set limit 120
#Extension: - Defender devra envoyer l'utilisateur dans un salon définit dans la configuration --> OK
Le code est modulaire et conçu pour être facilement étendu. Vous pouvez ajouter de nouvelles commandes, de nouvelles fonctionnalités (mods/mod_test.py est un exemple pour bien demarrer la création de son module). - Defender bannira l'utilisateur de la totalité des salons, il le bannira aussi lorsqu'il souhaitera accéder a de nouveau salon --> OK
- Defender devra envoyer un message du type "Merci de taper cette comande /msg {nomdudefender} code {un code générer aléatoirement} --> OK
# Contributions: - Defender devra reconnaitre le code --> OK
Les contributions sont les bienvenues ! N'hésitez pas à ouvrir des issues ou des pull requests. - Defender devra liberer l'utilisateur et l'envoyer vers un salon définit dans la configuration --> OK
# Avertissement:
Ce bot est fourni "tel quel" sans aucune garantie. Utilisez-le à vos risques et périls.

View File

@@ -38,10 +38,8 @@ class User:
for record in self.UID_DB: for record in self.UID_DB:
if record.uid == newUser.uid: if record.uid == newUser.uid:
# If the user exist then return False and do not go further
exist = True exist = True
self.log.debug(f'{record.uid} already exist') self.log.debug(f'{record.uid} already exist')
return result
if not exist: if not exist:
self.UID_DB.append(newUser) self.UID_DB.append(newUser)
@@ -67,11 +65,9 @@ class User:
for record in self.UID_DB: for record in self.UID_DB:
if record.uid == uid: if record.uid == uid:
# If the user exist then update and return True and do not go further
record.nickname = newNickname record.nickname = newNickname
result = True result = True
self.log.debug(f'UID ({record.uid}) has been updated with new nickname {newNickname}') self.log.debug(f'UID ({record.uid}) has been updated with new nickname {newNickname}')
return result
if not result: if not result:
self.log.critical(f'The new nickname {newNickname} was not updated, uid = {uid}') self.log.critical(f'The new nickname {newNickname} was not updated, uid = {uid}')
@@ -91,11 +87,9 @@ class User:
for record in self.UID_DB: for record in self.UID_DB:
if record.uid == uid: if record.uid == uid:
# If the user exist then remove and return True and do not go further
self.UID_DB.remove(record) self.UID_DB.remove(record)
result = True result = True
self.log.debug(f'UID ({record.uid}) has been deleted') self.log.debug(f'UID ({record.uid}) has been deleted')
return result
if not result: if not result:
self.log.critical(f'The UID {uid} was not deleted') self.log.critical(f'The UID {uid} was not deleted')
@@ -185,10 +179,8 @@ class Admin:
for record in self.UID_ADMIN_DB: for record in self.UID_ADMIN_DB:
if record.uid == newAdmin.uid: if record.uid == newAdmin.uid:
# If the admin exist then return False and do not go further
exist = True exist = True
self.log.debug(f'{record.uid} already exist') self.log.debug(f'{record.uid} already exist')
return result
if not exist: if not exist:
self.UID_ADMIN_DB.append(newAdmin) self.UID_ADMIN_DB.append(newAdmin)
@@ -199,41 +191,37 @@ class Admin:
self.log.critical(f'The User Object was not inserted {newAdmin}') self.log.critical(f'The User Object was not inserted {newAdmin}')
return result return result
def update(self, uid: str, newNickname: str) -> bool: def update(self, uid: str, newNickname: str) -> bool:
result = False 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
record.nickname = newNickname record.nickname = newNickname
result = True result = True
self.log.debug(f'UID ({record.uid}) has been updated with new nickname {newNickname}') self.log.debug(f'UID ({record.uid}) has been updated with new nickname {newNickname}')
return result
if not result: if not result:
self.log.critical(f'The new nickname {newNickname} was not updated, uid = {uid}') self.log.critical(f'The new nickname {newNickname} was not updated, uid = {uid}')
return result return result
def delete(self, uid: str) -> bool: def delete(self, uid: str) -> bool:
result = False 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, delete and do not go further
self.UID_ADMIN_DB.remove(record) self.UID_ADMIN_DB.remove(record)
result = True result = True
self.log.debug(f'UID ({record.uid}) has been created') self.log.debug(f'UID ({record.uid}) has been created')
return result
if not result: if not result:
self.log.critical(f'The UID {uid} was not deleted') self.log.critical(f'The UID {uid} was not deleted')
return result return result
def get_Admin(self, uidornickname: str) -> Union[AdminModel, None]: def get_Admin(self, uidornickname: str) -> Union[AdminModel, None]:
Admin = None Admin = None
@@ -275,19 +263,9 @@ class Channel:
@dataclass @dataclass
class ChannelModel: class ChannelModel:
name: str name: str
"""### Channel name
It include the #"""
uids: list uids: list
"""### List of UID available in the channel
including their modes ~ @ % + *
Returns:
list: The list of UID's including theirs modes
"""
UID_CHANNEL_DB: list[ChannelModel] = [] UID_CHANNEL_DB: list[ChannelModel] = []
"""List that contains all the Channels objects (ChannelModel)
"""
def __init__(self, Base: Base) -> None: def __init__(self, Base: Base) -> None:
self.log = Base.logs self.log = Base.logs
@@ -295,23 +273,15 @@ class Channel:
pass pass
def insert(self, newChan: ChannelModel) -> bool: def insert(self, newChan: ChannelModel) -> bool:
"""This method will insert a new channel and if the channel exist it will update the user list (uids)
Args:
newChan (ChannelModel): The channel model object
Returns:
bool: True if new channel, False if channel exist (However UID could be updated)
"""
result = False result = False
exist = False exist = False
for record in self.UID_CHANNEL_DB: for record in self.UID_CHANNEL_DB:
if record.name == newChan.name: if record.name == newChan.name:
# If the channel exist, update the user list and do not go further
exist = True exist = True
self.log.debug(f'{record.name} already exist') self.log.debug(f'{record.name} already exist')
for user in newChan.uids: for user in newChan.uids:
record.uids.append(user) record.uids.append(user)
@@ -319,11 +289,9 @@ class Channel:
del_duplicates = list(set(record.uids)) del_duplicates = list(set(record.uids))
record.uids = del_duplicates record.uids = del_duplicates
self.log.debug(f'Updating a new UID to the channel {record}') self.log.debug(f'Updating a new UID to the channel {record}')
return result
if not exist: if not exist:
# If the channel don't exist, then create it
self.UID_CHANNEL_DB.append(newChan) self.UID_CHANNEL_DB.append(newChan)
result = True result = True
self.log.debug(f'New Channel Created: ({newChan})') self.log.debug(f'New Channel Created: ({newChan})')
@@ -339,12 +307,9 @@ class Channel:
for record in self.UID_CHANNEL_DB: for record in self.UID_CHANNEL_DB:
if record.name == name: if record.name == name:
# If the channel exist, then remove it and return True.
# As soon as the channel found, return True and stop the loop
self.UID_CHANNEL_DB.remove(record) self.UID_CHANNEL_DB.remove(record)
result = True result = True
self.log.debug(f'Channel ({record.name}) has been created') self.log.debug(f'Channel ({record.name}) has been created')
return result
if not result: if not result:
self.log.critical(f'The Channel {name} was not deleted') self.log.critical(f'The Channel {name} was not deleted')
@@ -360,33 +325,13 @@ class Channel:
for user_id in record.uids: for user_id in record.uids:
if self.Base.clean_uid(user_id) == uid: if self.Base.clean_uid(user_id) == uid:
record.uids.remove(user_id) record.uids.remove(user_id)
self.log.debug(f'The UID {uid} has been removed, here is the new object: {record}') self.log.debug(f'uid {uid} has been removed, here is the new object: {record}')
result = True result = True
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.log.debug(f'The Channel {record.name} has been removed, here is the new object: {record}') self.log.debug(f'Channel {record.name} has been removed, here is the new object: {record}')
return result
except ValueError as ve:
self.log.error(f'{ve}')
def delete_user_from_all_channel(self, uid:str) -> bool:
try:
result = False
for record in self.UID_CHANNEL_DB:
for user_id in record.uids:
if self.Base.clean_uid(user_id) == self.Base.clean_uid(uid):
record.uids.remove(user_id)
self.log.debug(f'The UID {uid} has been removed, here is the new object: {record}')
result = True
for record in self.UID_CHANNEL_DB:
if not record.uids:
self.UID_CHANNEL_DB.remove(record)
self.log.debug(f'The Channel {record.name} has been removed, here is the new object: {record}')
return result return result
except ValueError as ve: except ValueError as ve:
@@ -402,3 +347,13 @@ class Channel:
self.log.debug(f'Search {name} -- result = {Channel}') self.log.debug(f'Search {name} -- result = {Channel}')
return Channel return Channel
def get_mode(self, name:str) -> Union[str, None]:
mode = None
for record in self.UID_CHANNEL_DB:
if record.name == name:
mode = record.mode
self.log.debug(f'The mode of the channel {name} has been found: {mode}')
return mode

View File

@@ -1,6 +1,5 @@
import time, threading, os, random, socket, hashlib, ipaddress, logging, requests, json, re, ast import time, threading, os, random, socket, hashlib, ipaddress, logging, requests, json, re
from dataclasses import fields from typing import Union
from typing import Union, Literal
from base64 import b64decode from base64 import b64decode
from datetime import datetime from datetime import datetime
from sqlalchemy import create_engine, Engine, Connection, CursorResult from sqlalchemy import create_engine, Engine, Connection, CursorResult
@@ -17,7 +16,7 @@ class Base:
self.Config = Config # Assigner l'objet de configuration self.Config = Config # Assigner l'objet de configuration
self.init_log_system() # Demarrer le systeme de log self.init_log_system() # Demarrer le systeme de log
self.check_for_new_version(True) # Verifier si une nouvelle version est disponible self.check_for_new_version(True) # Verifier si une nouvelle version est disponible
self.running_timers:list[threading.Timer] = [] # Liste des timers en cours self.running_timers:list[threading.Timer] = [] # Liste des timers en cours
self.running_threads:list[threading.Thread] = [] # Liste des threads en cours self.running_threads:list[threading.Thread] = [] # Liste des threads en cours
@@ -48,7 +47,7 @@ class Base:
def __get_latest_defender_version(self) -> None: def __get_latest_defender_version(self) -> None:
try: try:
self.logs.debug(f'Looking for a new version available on Github') self.logs.debug(f'Looking for a new version available on Github')
# print(f'===> Looking for a new version available on Github') print(f'===> Looking for a new version available on Github')
token = '' token = ''
json_url = f'https://raw.githubusercontent.com/adator85/IRC_DEFENDER_MODULES/main/version.json' json_url = f'https://raw.githubusercontent.com/adator85/IRC_DEFENDER_MODULES/main/version.json'
headers = { headers = {
@@ -57,9 +56,9 @@ class Base:
} }
if token == '': if token == '':
response = requests.get(json_url, timeout=self.Config.API_TIMEOUT) response = requests.get(json_url)
else: else:
response = requests.get(json_url, headers=headers, timeout=self.Config.API_TIMEOUT) response = requests.get(json_url, headers=headers)
response.raise_for_status() # Vérifie si la requête a réussi response.raise_for_status() # Vérifie si la requête a réussi
json_response:dict = response.json() json_response:dict = response.json()
@@ -121,16 +120,6 @@ class Base:
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_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
@@ -159,7 +148,7 @@ class Base:
encoding='UTF-8', encoding='UTF-8',
format='%(asctime)s - %(levelname)s - %(filename)s - %(lineno)d - %(funcName)s - %(message)s') format='%(asctime)s - %(levelname)s - %(filename)s - %(lineno)d - %(funcName)s - %(message)s')
self.logs.info('#################### STARTING DEFENDER ####################') self.logs.info('#################### STARTING INTERCEPTOR HQ ####################')
return None return None
@@ -191,8 +180,8 @@ class Base:
Returns: Returns:
bool: True si le module existe déja dans la base de données sinon False 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" query = f"SELECT id FROM {self.Config.table_module} WHERE module = :module"
mes_donnes = {'module_name': module_name} mes_donnes = {'module': module_name}
results = self.db_execute_query(query, mes_donnes) results = self.db_execute_query(query, mes_donnes)
if results.fetchall(): if results.fetchall():
@@ -209,9 +198,10 @@ class Base:
if not self.db_isModuleExist(module_name): if not self.db_isModuleExist(module_name):
self.logs.debug(f"Le module {module_name} n'existe pas alors ont le créer") 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)" insert_cmd_query = f"INSERT INTO {self.Config.table_module} (datetime, user, module) VALUES (:datetime, :user, :module)"
mes_donnees = {'datetime': self.get_datetime(), 'user': user_cmd, 'module_name': module_name, 'isdefault': 0} mes_donnees = {'datetime': self.get_datetime(), 'user': user_cmd, 'module': module_name}
self.db_execute_query(insert_cmd_query, mes_donnees) self.db_execute_query(insert_cmd_query, mes_donnees)
# self.db_close_session(self.session)
else: else:
self.logs.debug(f"Le module {module_name} existe déja dans la base de données") self.logs.debug(f"Le module {module_name} existe déja dans la base de données")
@@ -223,174 +213,12 @@ class Base:
Args: Args:
cmd (str): le module a enregistrer cmd (str): le module a enregistrer
""" """
insert_cmd_query = f"DELETE FROM {self.Config.table_module} WHERE module_name = :module_name" insert_cmd_query = f"DELETE FROM {self.Config.table_module} WHERE module = :module"
mes_donnees = {'module_name': module_name} mes_donnees = {'module': module_name}
self.db_execute_query(insert_cmd_query, mes_donnees) self.db_execute_query(insert_cmd_query, mes_donnees)
return False return False
def db_sync_core_config(self, module_name: str, dataclassObj: object) -> bool:
"""Sync module local parameters with the database
if new module then local param will be stored in the database
if old module then db param will be moved to the local dataclassObj
if new local param it will be stored in the database
if local param was removed then it will also be removed from the database
Args:
module_name (str): The module name ex. mod_defender
dataclassObj (object): The Dataclass object
Returns:
bool: _description_
"""
try:
response = True
current_date = self.get_datetime()
core_table = self.Config.table_config
# Add local parameters to DB
for field in fields(dataclassObj):
param_key = field.name
param_value = str(getattr(dataclassObj, field.name))
param_to_search = {'module_name': module_name, 'param_key': param_key}
search_query = f'''SELECT id FROM {core_table} WHERE module_name = :module_name AND param_key = :param_key'''
excecute_search_query = self.db_execute_query(search_query, param_to_search)
result_search_query = excecute_search_query.fetchone()
if result_search_query is None:
# If param and module_name doesn't exist create the record
param_to_insert = {'datetime': current_date,'module_name': module_name,
'param_key': param_key,'param_value': param_value
}
insert_query = f'''INSERT INTO {core_table} (datetime, module_name, param_key, param_value)
VALUES (:datetime, :module_name, :param_key, :param_value)
'''
execution = self.db_execute_query(insert_query, param_to_insert)
if execution.rowcount > 0:
self.logs.debug(f'New parameter added to the database: {param_key} --> {param_value}')
# Delete from DB unused parameter
query_select = f"SELECT module_name, param_key, param_value FROM {core_table} WHERE module_name = :module_name"
parameter = {'module_name': module_name}
execute_query_select = self.db_execute_query(query_select, parameter)
result_query_select = execute_query_select.fetchall()
for result in result_query_select:
db_mod_name, db_param_key, db_param_value = result
if not hasattr(dataclassObj, db_param_key):
mes_donnees = {'param_key': db_param_key, 'module_name': db_mod_name}
execute_delete = self.db_execute_query(f'DELETE FROM {core_table} WHERE module_name = :module_name and param_key = :param_key', mes_donnees)
row_affected = execute_delete.rowcount
if row_affected > 0:
self.logs.debug(f'A parameter has been deleted from the database: {db_param_key} --> {db_param_value} | Mod: {db_mod_name}')
# Sync local variable with Database
query = f"SELECT param_key, param_value FROM {core_table} WHERE module_name = :module_name"
parameter = {'module_name': module_name}
response = self.db_execute_query(query, parameter)
result = response.fetchall()
for param, value in result:
if type(getattr(dataclassObj, param)) == list:
value = ast.literal_eval(value)
setattr(dataclassObj, param, self.int_if_possible(value))
return response
except AttributeError as attrerr:
self.logs.error(f'Attribute Error: {attrerr}')
except Exception as err:
self.logs.error(err)
return False
def db_update_core_config(self, module_name:str, dataclassObj: object, param_key:str, param_value: str) -> bool:
core_table = 'core_config'
# Check if the param exist
if not hasattr(dataclassObj, param_key):
self.logs.error(f"Le parametre {param_key} n'existe pas dans la variable global")
return False
mes_donnees = {'module_name': module_name, 'param_key': param_key, 'param_value': param_value}
search_param_query = f"SELECT id FROM {core_table} WHERE module_name = :module_name AND param_key = :param_key"
result = self.db_execute_query(search_param_query, mes_donnees)
isParamExist = result.fetchone()
if not isParamExist is None:
mes_donnees = {'datetime': self.get_datetime(),
'module_name': module_name,
'param_key': param_key,
'param_value': param_value
}
query = f'''UPDATE {core_table} SET datetime = :datetime, param_value = :param_value WHERE module_name = :module_name AND param_key = :param_key'''
update = self.db_execute_query(query, mes_donnees)
updated_rows = update.rowcount
if updated_rows > 0:
setattr(dataclassObj, param_key, self.int_if_possible(param_value))
self.logs.debug(f'Parameter updated : {param_key} - {param_value} | Module: {module_name}')
self.logs.debug(dataclassObj)
return True
def db_query_channel(self, action: Literal['add','del'], module_name: str, channel_name: str) -> bool:
"""You can add a channel or delete a channel.
Args:
action (Literal['add','del']): Action on the database
module_name (str): The module name (mod_test)
channel_name (str): The channel name (With #)
Returns:
bool: True if action done
"""
try:
channel_name = channel_name.lower() if self.Is_Channel(channel_name) else None
core_table = 'core_channel'
if not channel_name:
self.logs.warn(f'The channel [{channel_name}] is not correct')
return False
match action:
case 'add':
mes_donnees = {'module_name': module_name, 'channel_name': channel_name}
response = self.db_execute_query(f"SELECT id FROM {core_table} WHERE module_name = :module_name AND channel_name = :channel_name", mes_donnees)
isChannelExist = response.fetchone()
if isChannelExist is None:
mes_donnees = {'datetime': self.get_datetime(), 'channel_name': channel_name, 'module_name': module_name}
insert = self.db_execute_query(f"INSERT INTO {core_table} (datetime, channel_name, module_name) VALUES (:datetime, :channel_name, :module_name)", mes_donnees)
if insert.rowcount:
self.logs.debug(f'New channel added: channel={channel_name} / module_name={module_name}')
return True
else:
return False
pass
case 'del':
mes_donnes = {'channel_name': channel_name, 'module_name': module_name}
response = self.db_execute_query(f"DELETE FROM {core_table} WHERE channel_name = :channel_name AND module_name = :module_name", mes_donnes)
if response.rowcount > 0:
self.logs.debug(f'Channel deleted: channel={channel_name} / module: {module_name}')
return True
else:
return False
case _:
return False
except Exception as err:
self.logs.error(err)
def db_create_first_admin(self) -> None: def db_create_first_admin(self) -> None:
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}")
@@ -429,13 +257,6 @@ class Base:
self.logs.error(f'Assertion Error -> {ae}') self.logs.error(f'Assertion Error -> {ae}')
def create_thread(self, func:object, func_args: tuple = (), run_once:bool = False) -> None: def create_thread(self, func:object, func_args: tuple = (), run_once:bool = False) -> None:
"""Create a new thread and store it into running_threads variable
Args:
func (object): The method/function you want to execute via this thread
func_args (tuple, optional): Arguments of the function/method. Defaults to ().
run_once (bool, optional): If you want to ensure that this method/function run once. Defaults to False.
"""
try: try:
func_name = func.__name__ func_name = func.__name__
@@ -444,6 +265,10 @@ class Base:
if thread.getName() == func_name: if thread.getName() == func_name:
return None return None
# if func_name in self.running_threads:
# print(f"HeartBeat is running")
# return None
th = threading.Thread(target=func, args=func_args, name=str(func_name), daemon=True) th = threading.Thread(target=func, args=func_args, name=str(func_name), daemon=True)
th.start() th.start()
@@ -477,7 +302,7 @@ class Base:
if thread.getName() != 'heartbeat': if thread.getName() != 'heartbeat':
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.debug(f"Thread {str(thread.getName())} {str(thread.native_id)} removed")
# print(threading.enumerate()) # print(threading.enumerate())
except AssertionError as ae: except AssertionError as ae:
@@ -541,23 +366,14 @@ class Base:
def __create_db(self) -> None: def __create_db(self) -> None:
table_core_log = f'''CREATE TABLE IF NOT EXISTS {self.Config.table_log} ( table_logs = f'''CREATE TABLE IF NOT EXISTS {self.Config.table_log} (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
datetime TEXT, datetime TEXT,
server_msg TEXT server_msg TEXT
) )
''' '''
table_core_config = f'''CREATE TABLE IF NOT EXISTS {self.Config.table_config} ( table_cmds = f'''CREATE TABLE IF NOT EXISTS {self.Config.table_commande} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
datetime TEXT,
module_name TEXT,
param_key TEXT,
param_value TEXT
)
'''
table_core_log_command = f'''CREATE TABLE IF NOT EXISTS {self.Config.table_commande} (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
datetime TEXT, datetime TEXT,
user TEXT, user TEXT,
@@ -565,24 +381,15 @@ class Base:
) )
''' '''
table_core_module = f'''CREATE TABLE IF NOT EXISTS {self.Config.table_module} ( table_modules = f'''CREATE TABLE IF NOT EXISTS {self.Config.table_module} (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
datetime TEXT, datetime TEXT,
user TEXT, user TEXT,
module_name TEXT, module TEXT
isdefault INTEGER
)
'''
table_core_channel = '''CREATE TABLE IF NOT EXISTS core_channel (
id INTEGER PRIMARY KEY AUTOINCREMENT,
datetime TEXT,
module_name TEXT,
channel_name TEXT
) )
''' '''
table_core_admin = f'''CREATE TABLE IF NOT EXISTS {self.Config.table_admin} ( table_admins = f'''CREATE TABLE IF NOT EXISTS {self.Config.table_admin} (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
createdOn TEXT, createdOn TEXT,
user TEXT, user TEXT,
@@ -593,12 +400,10 @@ class Base:
) )
''' '''
self.db_execute_query(table_core_log) self.db_execute_query(table_logs)
self.db_execute_query(table_core_log_command) self.db_execute_query(table_cmds)
self.db_execute_query(table_core_module) self.db_execute_query(table_modules)
self.db_execute_query(table_core_admin) self.db_execute_query(table_admins)
self.db_execute_query(table_core_channel)
self.db_execute_query(table_core_config)
return None return None
@@ -704,7 +509,7 @@ class Base:
self.periodic_func.clear() self.periodic_func.clear()
def clean_uid(self, uid:str) -> str: def clean_uid(self, uid:str) -> str:
"""Clean UID by removing @ / % / + / ~ / * / : """Clean UID by removing @ / % / + / Owner / and *
Args: Args:
uid (str): The UID to clean uid (str): The UID to clean
@@ -713,11 +518,11 @@ class Base:
str: Clean UID without any sign str: Clean UID without any sign
""" """
pattern = fr'[:|@|%|\+|~|\*]*' pattern = fr'[@|%|\+|~|\*]*'
parsed_UID = re.sub(pattern, '', uid) parsed_UID = re.sub(pattern, '', uid)
return parsed_UID return parsed_UID
def Is_Channel(self, channelToCheck: str) -> bool: def Is_Channel(self, channelToCheck: str) -> bool:
"""Check if the string has the # caractere and return True if this is a channel """Check if the string has the # caractere and return True if this is a channel
@@ -727,19 +532,11 @@ class Base:
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:
if channelToCheck is None:
return False
pattern = fr'^#' pattern = fr'^#'
isChannel = re.findall(pattern, channelToCheck) isChannel = re.findall(pattern, channelToCheck)
if not isChannel: if not isChannel:
return False return False
else: else:
return True return True
except TypeError as te:
self.logs.error(f'TypeError: [{channelToCheck}] - {te}')
except Exception as err:
self.logs.error(f'TypeError: {err}')

View File

@@ -1,205 +0,0 @@
import socket, ssl, time
from ssl import SSLSocket
from core.loadConf import Config
from core.base import Base
from typing import Union
class Connection:
def __init__(self, server_port: int, nickname: str, username: str, channels:list[str], ssl:bool = False) -> None:
self.Config = Config().ConfigObject
self.Base = Base(self.Config)
self.IrcSocket: Union[socket.socket, SSLSocket] = None
self.signal: bool = True
self.nickname = nickname
self.username = username
self.channels:list[str] = channels
self.CHARSET = ['utf-8', 'iso-8859-1']
self.create_socket(self.Config.SERVEUR_IP, self.Config.SERVEUR_HOSTNAME, server_port, ssl)
self.send_connection_information_to_server(self.IrcSocket)
self.connect()
def create_socket(self, server_ip: str, server_hostname: str, server_port: int, ssl: bool = False) -> bool:
try:
soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM or socket.SOCK_NONBLOCK)
connexion_information = (server_ip, server_port)
if ssl:
# Créer un object ssl
ssl_context = self.__ssl_context()
ssl_connexion = ssl_context.wrap_socket(soc, server_hostname=server_hostname)
ssl_connexion.connect(connexion_information)
self.IrcSocket:SSLSocket = ssl_connexion
self.SSL_VERSION = self.IrcSocket.version()
self.Base.logs.debug(f'> Connexion en mode SSL : Version = {self.SSL_VERSION}')
else:
soc.connect(connexion_information)
self.IrcSocket:socket.socket = soc
self.Base.logs.debug(f'> Connexion en mode normal')
return True
except ssl.SSLEOFError as soe:
self.Base.logs.critical(f"SSLEOFError __create_socket: {soe} - {soc.fileno()}")
return False
except ssl.SSLError as se:
self.Base.logs.critical(f"SSLError __create_socket: {se} - {soc.fileno()}")
return False
except OSError as oe:
self.Base.logs.critical(f"OSError __create_socket: {oe} - {soc.fileno()}")
return False
except AttributeError as ae:
self.Base.logs.critical(f"AttributeError __create_socket: {ae} - {soc.fileno()}")
return False
def send2socket(self, send_message:str) -> None:
"""Envoit les commandes à envoyer au serveur.
Args:
string (Str): contient la commande à envoyer au serveur.
"""
try:
with self.Base.lock:
# print(f">{str(send_message)}")
self.IrcSocket.send(f"{send_message}\r\n".encode(self.CHARSET[0]))
self.Base.logs.debug(f'{send_message}')
except UnicodeDecodeError:
self.Base.logs.error(f'Decode Error try iso-8859-1 - message: {send_message}')
self.IrcSocket.send(f"{send_message}\r\n".encode(self.CHARSET[1],'replace'))
except UnicodeEncodeError:
self.Base.logs.error(f'Encode Error try iso-8859-1 - message: {send_message}')
self.IrcSocket.send(f"{send_message}\r\n".encode(self.CHARSET[1],'replace'))
except AssertionError as ae:
self.Base.logs.warning(f'Assertion Error {ae} - message: {send_message}')
except ssl.SSLEOFError as soe:
self.Base.logs.error(f"SSLEOFError: {soe} - {send_message}")
except ssl.SSLError as se:
self.Base.logs.error(f"SSLError: {se} - {send_message}")
except OSError as oe:
self.Base.logs.error(f"OSError: {oe} - {send_message}")
def send_connection_information_to_server(self, writer:Union[socket.socket, SSLSocket]) -> None:
"""Créer le link et envoyer les informations nécessaires pour la
connexion au serveur.
Args:
writer (StreamWriter): permet l'envoi des informations au serveur.
"""
try:
nickname = self.nickname
username = self.username
# Envoyer un message d'identification
writer.send(f"USER {nickname} {username} {username} {nickname} {username} :{username}\r\n".encode('utf-8'))
writer.send(f"USER {username} {username} {username} :{username}\r\n".encode('utf-8'))
writer.send(f"NICK {nickname}\r\n".encode('utf-8'))
self.Base.logs.debug('Link information sent to the server')
return None
except AttributeError as ae:
self.Base.logs.critical(f'{ae}')
def connect(self):
try:
while self.signal:
try:
# 4072 max what the socket can grab
buffer_size = self.IrcSocket.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF)
data_in_bytes = self.IrcSocket.recv(buffer_size)
data = data_in_bytes.splitlines(True)
count_bytes = len(data_in_bytes)
while count_bytes > 4070:
# If the received message is > 4070 then loop and add the value to the variable
new_data = self.IrcSocket.recv(buffer_size)
data_in_bytes += new_data
count_bytes = len(new_data)
data = data_in_bytes.splitlines(True)
if not data:
break
self.parser(data)
except ssl.SSLEOFError as soe:
self.Base.logs.error(f"SSLEOFError __connect_to_irc: {soe} - {data}")
self.signal = False
except ssl.SSLError as se:
self.Base.logs.error(f"SSLError __connect_to_irc: {se} - {data}")
self.signal = False
except OSError as oe:
self.Base.logs.error(f"OSError __connect_to_irc: {oe} - {data}")
self.signal = False
self.IrcSocket.shutdown(socket.SHUT_RDWR)
self.IrcSocket.close()
self.Base.logs.info("--> Clone Disconnected ...")
except AssertionError as ae:
self.Base.logs.error(f'Assertion error : {ae}')
except ValueError as ve:
self.Base.logs.error(f'Value Error : {ve}')
except ssl.SSLEOFError as soe:
self.Base.logs.error(f"OS Error __connect_to_irc: {soe}")
except AttributeError as atte:
self.Base.logs.critical(f"{atte}")
except Exception as e:
self.Base.logs.error(f"Exception: {e}")
def parser(self, cmd:list[bytes]):
try:
for data in cmd:
response = data.decode(self.CHARSET[0]).split()
# print(response)
match response[0]:
case 'PING':
pong = str(response[1]).replace(':','')
self.send2socket(f"PONG :{pong}")
return None
case 'ERROR':
error_value = str(response[1]).replace(':','')
if error_value == 'Closing':
self.signal = False
match response[1]:
case '376':
for channel in self.channels:
self.send2socket(f"JOIN {channel}")
return None
case 'PRIVMSG':
fullname = str(response[0]).replace(':', '')
nickname = fullname.split('!')[0].replace(':','')
if nickname == self.Config.SERVICE_NICKNAME:
command = str(response[3]).replace(':','')
if command == 'KILL':
self.send2socket(f'QUIT :Thanks and goodbye')
self.signal = False
if command == 'JOIN':
channel_to_join = str(response[4])
self.send2socket(f"JOIN {channel_to_join}")
except UnicodeEncodeError:
for data in cmd:
response = data.decode(self.CHARSET[1],'replace').split()
except UnicodeDecodeError:
for data in cmd:
response = data.decode(self.CHARSET[1],'replace').split()
except AssertionError as ae:
self.Base.logs.error(f"Assertion error : {ae}")
pass
def __ssl_context(self) -> ssl.SSLContext:
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
self.Base.logs.debug(f'SSLContext initiated with verified mode {ctx.verify_mode}')
return ctx

View File

@@ -1,132 +1,44 @@
from importlib.util import find_spec from importlib.util import find_spec
from dataclasses import dataclass from subprocess import check_call, run
from pathlib import Path from platform import python_version
from subprocess import check_call, run, CalledProcessError, PIPE
from platform import python_version, python_version_tuple
from sys import exit from sys import exit
import os
class Install: class Install:
@dataclass
class CoreConfig:
unix_systemd_folder: str
service_file_name: str
service_cmd_executable: list
defender_main_executable: str
python_min_version: str
python_current_version_tuple: tuple[str, str, str]
python_current_version: str
defender_install_folder: str
venv_folder: str
venv_cmd_installation: list
venv_cmd_requirements: list
venv_pip_executable: str
venv_python_executable: str
def __init__(self) -> None: def __init__(self) -> None:
self.PYTHON_MIN_VERSION = '3.10'
self.module_to_install = ['sqlalchemy','psutil','requests']
self.set_configuration() if not self.checkPythonVersion():
if not self.check_python_version():
# Tester si c'est la bonne version de python # Tester si c'est la bonne version de python
exit("Python Version Error") exit("Python Version Error")
else: else:
# Sinon tester les dependances python et les installer avec pip # Sinon tester les dependances python et les installer avec pip
if self.do_install(): self.checkDependencies()
self.install_dependencies()
self.create_service_file()
self.print_final_message()
return None return None
def set_configuration(self): def checkPythonVersion(self) -> bool:
defender_install_folder = os.getcwd()
venv_folder = '.pyenv'
unix_user_home_directory = os.path.expanduser("~")
unix_systemd_folder = os.path.join(unix_user_home_directory, '.config', 'systemd', 'user')
defender_main_executable = os.path.join(defender_install_folder, 'main.py')
self.config = self.CoreConfig(
unix_systemd_folder=unix_systemd_folder,
service_file_name='defender.service',
service_cmd_executable=['systemctl', '--user', 'start', 'defender'],
defender_main_executable=defender_main_executable,
python_min_version='3.10',
python_current_version_tuple=python_version_tuple(),
python_current_version=python_version(),
defender_install_folder=defender_install_folder,
venv_folder=venv_folder,
venv_cmd_installation=['python3', '-m', 'venv', venv_folder],
venv_cmd_requirements=['sqlalchemy','psutil','requests','faker'],
venv_pip_executable=f'{os.path.join(defender_install_folder, venv_folder, "bin")}{os.sep}pip',
venv_python_executable=f'{os.path.join(defender_install_folder, venv_folder, "bin")}{os.sep}python'
)
def do_install(self) -> bool:
full_service_file_path = os.path.join(self.config.unix_systemd_folder, self.config.service_file_name)
if not os.path.exists(full_service_file_path):
print(f'/!\\ Service file does not exist /!\\')
return True
# Check if virtual env exist
if not os.path.exists(f'{os.path.join(self.config.defender_install_folder, self.config.venv_folder)}'):
self.run_subprocess(self.config.venv_cmd_installation)
print(f'/!\\ Virtual env does not exist run the install /!\\')
return True
def run_subprocess(self, command:list) -> None:
print(command)
try:
check_call(command)
print("La commande s'est terminée avec succès.")
except CalledProcessError as e:
print(f"La commande a échoué avec le code de retour :{e.returncode}")
print(f"Try to install dependencies ...")
exit(5)
def check_python_version(self) -> bool:
"""Test si la version de python est autorisée ou non """Test si la version de python est autorisée ou non
Returns: Returns:
bool: True si la version de python est autorisé sinon False bool: True si la version de python est autorisé sinon False
""" """
# Current system version python_required_version = self.PYTHON_MIN_VERSION.split('.')
sys_major, sys_minor, sys_patch = self.config.python_current_version_tuple python_current_version = python_version().split('.')
# min python version required if int(python_current_version[0]) < int(python_required_version[0]):
python_required_version = self.config.python_min_version.split('.') print(f"## Your python version must be greather than or equal to {self.PYTHON_MIN_VERSION} ##")
min_major, min_minor = tuple((python_required_version[0], python_required_version[1])) return False
elif int(python_current_version[1]) < int(python_required_version[1]):
if int(sys_major) < int(min_major): print(f"### Your python version must be greather than or equal to {self.PYTHON_MIN_VERSION} ###")
print(f"## Your python version must be greather than or equal to {self.config.python_current_version} ##")
return False return False
elif (int(sys_major) <= int(min_major)) and (int(sys_minor) < int(min_minor)): print(f"===> Version of python : {python_version()} ==> OK")
print(f"## Your python version must be greather than or equal to {self.config.python_current_version} ##")
return False
print(f"===> Version of python : {self.config.python_current_version} ==> OK")
return True return True
def check_package(self, package_name) -> bool: def checkDependencies(self) -> None:
try:
# Run a command in the virtual environment's Python to check if the package is installed
run([self.config.venv_python_executable, '-c', f'import {package_name}'], check=True, stdout=PIPE, stderr=PIPE)
return True
except CalledProcessError:
return False
def install_dependencies(self) -> None:
"""### Verifie les dépendances si elles sont installées """### Verifie les dépendances si elles sont installées
- Test si les modules sont installés - Test si les modules sont installés
- Met a jour pip - Met a jour pip
@@ -134,72 +46,27 @@ class Install:
""" """
do_install = False do_install = False
# Check if virtual env exist for module in self.module_to_install:
if not os.path.exists(f'{os.path.join(self.config.defender_install_folder, self.config.venv_folder)}'): if find_spec(module) is None:
self.run_subprocess(self.config.venv_cmd_installation)
do_install = True
for module in self.config.venv_cmd_requirements:
if not self.check_package(module):
do_install = True do_install = True
if not do_install: if not do_install:
return None return None
print("===> Vider le cache de pip") print("===> Vider le cache de pip")
self.run_subprocess([self.config.venv_pip_executable, 'cache', 'purge']) check_call(['pip','cache','purge'])
print("===> Verifier si pip est a jour") print("===> Verifier si pip est a jour")
self.run_subprocess([self.config.venv_python_executable, '-m', 'pip', 'install', '--upgrade', 'pip']) check_call(['python', '-m', 'pip', 'install', '--upgrade', 'pip'])
if find_spec('greenlet') is None: if find_spec('greenlet') is None:
self.run_subprocess([self.config.venv_pip_executable, 'install', '--only-binary', ':all:', 'greenlet']) check_call(['pip','install', '--only-binary', ':all:', 'greenlet'])
print('====> Module Greenlet installé') print('====> Module Greenlet installé')
for module in self.config.venv_cmd_requirements: for module in self.module_to_install:
if not self.check_package(module): if find_spec(module) is None:
print("### Trying to install missing python packages ###") print("### Trying to install missing python packages ###")
self.run_subprocess([self.config.venv_pip_executable, 'install', module]) check_call(['pip','install', module])
print(f"====> Module {module} installé") print(f"====> Module {module} installé")
else: else:
print(f"==> {module} already installed") print(f"==> {module} already installed")
def create_service_file(self) -> None:
full_service_file_path = os.path.join(self.config.unix_systemd_folder, self.config.service_file_name)
if os.path.exists(full_service_file_path):
print(f'/!\\ Service file already exist /!\\')
return None
# Check if user systemd is available (.config/systemd/user/)
if not os.path.exists(self.config.unix_systemd_folder):
self.run_subprocess(['mkdir', '-p', self.config.unix_systemd_folder])
contain = f'''[Unit]
Description=Defender IRC Service
[Service]
ExecStart={self.config.venv_python_executable} {self.config.defender_main_executable}
WorkingDirectory={self.config.defender_install_folder}
SyslogIdentifier=Defender
Restart=on-failure
[Install]
WantedBy=multi-user.target
'''
with open(full_service_file_path, 'w+') as servicefile:
servicefile.write(contain)
servicefile.close()
print(f'Service file generated with current configuration')
print(f'Running Defender IRC Service ...')
self.run_subprocess(self.config.service_cmd_executable)
def print_final_message(self) -> None:
print(f"#"*24)
print("Installation complete ...")
print("You must change environment using the command below")
print(f"source {self.config.defender_install_folder}{os.sep}{self.config.venv_folder}{os.sep}bin{os.sep}activate")
print(f"#"*24)
exit(1)

View File

@@ -12,6 +12,7 @@ class Irc:
self.defender_connexion_datetime = datetime.now() # Date et heure de la premiere connexion de Defender self.defender_connexion_datetime = datetime.now() # Date et heure de la premiere connexion de Defender
self.first_score: int = 100 self.first_score: int = 100
self.db_chan = [] # Definir la variable qui contiendra la liste des salons
self.loaded_classes:dict[str, 'Irc'] = {} # Definir la variable qui contiendra la liste modules chargés self.loaded_classes:dict[str, 'Irc'] = {} # Definir la variable qui contiendra la liste modules chargés
self.beat = 30 # Lancer toutes les 30 secondes des actions de nettoyages self.beat = 30 # Lancer toutes les 30 secondes des actions de nettoyages
self.hb_active = True # Heartbeat active self.hb_active = True # Heartbeat active
@@ -27,10 +28,10 @@ class Irc:
# Liste des commandes internes du bot # Liste des commandes internes du bot
self.commands_level = { self.commands_level = {
0: ['help', 'auth', 'copyright', 'uptime'], 0: ['help', 'auth', 'copyright'],
1: ['load','reload','unload', 'deauth', 'checkversion'], 1: ['load','reload','unload', 'deauth', 'uptime', 'checkversion'],
2: ['show_modules', 'show_timers', 'show_threads', 'show_channels', 'show_users', 'show_admins'], 2: ['show_modules', 'show_timers', 'show_threads', 'show_channels'],
3: ['quit', 'restart','addaccess','editaccess', 'delaccess','umode'] 3: ['quit', 'restart','addaccess','editaccess', 'delaccess']
} }
# l'ensemble des commandes. # l'ensemble des commandes.
@@ -43,8 +44,6 @@ class Irc:
self.User = User(self.Base) self.User = User(self.Base)
self.Admin = Admin(self.Base) self.Admin = Admin(self.Base)
self.Channel = Channel(self.Base) self.Channel = Channel(self.Base)
self.__create_table()
self.Base.create_thread(func=self.heartbeat, func_args=(self.beat, )) self.Base.create_thread(func=self.heartbeat, func_args=(self.beat, ))
############################################## ##############################################
@@ -91,7 +90,7 @@ class Irc:
except OSError as oe: except OSError as oe:
self.Base.logs.critical(f"OSError __create_socket: {oe} - {soc.fileno()}") self.Base.logs.critical(f"OSError __create_socket: {oe} - {soc.fileno()}")
except AttributeError as ae: except AttributeError as ae:
self.Base.logs.critical(f"AttributeError __create_socket: {ae} - {soc.fileno()}") self.Base.logs.critical(f"OSError __create_socket: {oe} - {soc.fileno()}")
def __ssl_context(self) -> ssl.SSLContext: def __ssl_context(self) -> ssl.SSLContext:
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
@@ -108,7 +107,6 @@ class Irc:
self.__link(self.IrcSocket) # établir la connexion au serveur IRC self.__link(self.IrcSocket) # établir la connexion au serveur IRC
self.signal = True # Une variable pour initier la boucle infinie self.signal = True # Une variable pour initier la boucle infinie
self.load_existing_modules() # Charger les modules existant dans la base de données self.load_existing_modules() # Charger les modules existant dans la base de données
self.__join_saved_channels() # Join existing channels
while self.signal: while self.signal:
try: try:
@@ -160,18 +158,17 @@ class Irc:
self.IrcSocket.shutdown(socket.SHUT_RDWR) self.IrcSocket.shutdown(socket.SHUT_RDWR)
self.IrcSocket.close() self.IrcSocket.close()
self.Base.logs.info("--> Fermeture de Defender ...") self.Base.logs.info("--> Fermeture de Defender ...")
sys.exit(0)
except AssertionError as ae: except AssertionError as ae:
self.Base.logs.error(f'AssertionError: {ae}') self.Base.logs.error(f'Assertion error : {ae}')
except ValueError as ve: except ValueError as ve:
self.Base.logs.error(f'ValueError: {ve}') self.Base.logs.error(f'Value Error : {ve}')
except ssl.SSLEOFError as soe: except ssl.SSLEOFError as soe:
self.Base.logs.error(f"SSLEOFError: {soe}") self.Base.logs.error(f"OS Error __connect_to_irc: {soe}")
except AttributeError as atte: except AttributeError as atte:
self.Base.logs.critical(f"AttributeError: {atte}") self.Base.logs.critical(f"{atte}")
except Exception as e: # except Exception as e:
self.Base.logs.critical(f"Exception: {e}") # self.debug(f"Exception: {e}")
def __link(self, writer:Union[socket.socket, SSLSocket]) -> None: def __link(self, writer:Union[socket.socket, SSLSocket]) -> None:
"""Créer le link et envoyer les informations nécessaires pour la """Créer le link et envoyer les informations nécessaires pour la
@@ -202,8 +199,7 @@ class Irc:
# Envoyer un message d'identification # Envoyer un message d'identification
writer.send(f":{sid} PASS :{password}\r\n".encode('utf-8')) writer.send(f":{sid} PASS :{password}\r\n".encode('utf-8'))
writer.send(f":{sid} PROTOCTL SID NOQUIT NICKv2 SJOIN SJ3 NICKIP TKLEXT2 NEXTBANS CLK EXTSWHOIS MLOCK MTAGS\r\n".encode('utf-8')) writer.send(f":{sid} PROTOCTL NICKv2 VHP UMODE2 NICKIP SJOIN SJOIN2 SJ3 NOQUIT TKLEXT MLOCK SID MTAGS\r\n".encode('utf-8'))
# writer.send(f":{sid} PROTOCTL NICKv2 VHP UMODE2 NICKIP SJOIN SJOIN2 SJ3 NOQUIT TKLEXT MLOCK SID MTAGS\r\n".encode('utf-8'))
writer.send(f":{sid} PROTOCTL EAUTH={link},,,{service_name}-v{version}\r\n".encode('utf-8')) writer.send(f":{sid} PROTOCTL EAUTH={link},,,{service_name}-v{version}\r\n".encode('utf-8'))
writer.send(f":{sid} PROTOCTL SID={sid}\r\n".encode('utf-8')) writer.send(f":{sid} PROTOCTL SID={sid}\r\n".encode('utf-8'))
writer.send(f":{sid} SERVER {link} 1 :{info}\r\n".encode('utf-8')) writer.send(f":{sid} SERVER {link} 1 :{info}\r\n".encode('utf-8'))
@@ -211,7 +207,7 @@ class Irc:
writer.send(f":{sid} UID {nickname} 1 {unixtime} {username} {host} {service_id} * {smodes} * * * :{realname}\r\n".encode('utf-8')) writer.send(f":{sid} UID {nickname} 1 {unixtime} {username} {host} {service_id} * {smodes} * * * :{realname}\r\n".encode('utf-8'))
writer.send(f":{sid} SJOIN {unixtime} {chan} + :{service_id}\r\n".encode('utf-8')) writer.send(f":{sid} SJOIN {unixtime} {chan} + :{service_id}\r\n".encode('utf-8'))
writer.send(f":{sid} MODE {chan} +{cmodes}\r\n".encode('utf-8')) writer.send(f":{sid} MODE {chan} +{cmodes}\r\n".encode('utf-8'))
writer.send(f":{sid} SAMODE {chan} +{umodes} {nickname}\r\n".encode('utf-8')) writer.send(f":{service_id} SAMODE {chan} +{umodes} {nickname}\r\n".encode('utf-8'))
self.Base.logs.debug('Link information sent to the server') self.Base.logs.debug('Link information sent to the server')
@@ -219,19 +215,6 @@ class Irc:
except AttributeError as ae: except AttributeError as ae:
self.Base.logs.critical(f'{ae}') self.Base.logs.critical(f'{ae}')
def __join_saved_channels(self) -> None:
core_table = 'core_channel'
query = f'''SELECT distinct channel_name FROM {core_table}'''
exec_query = self.Base.db_execute_query(query)
result_query = exec_query.fetchall()
if result_query:
for chan_name in result_query:
chan = chan_name[0]
self.send2socket(f":{self.Config.SERVEUR_ID} SJOIN {self.Base.get_unixtime()} {chan} + :{self.Config.SERVICE_ID}")
def send2socket(self, send_message:str) -> None: def send2socket(self, send_message:str) -> None:
"""Envoit les commandes à envoyer au serveur. """Envoit les commandes à envoyer au serveur.
@@ -285,18 +268,13 @@ class Irc:
# FIN CONNEXION IRC # # FIN CONNEXION IRC #
############################################## ##############################################
def __create_table(self):
"""## Create core tables
"""
pass
def load_existing_modules(self) -> None: def load_existing_modules(self) -> None:
"""Charge les modules qui existe déja dans la base de données """Charge les modules qui existe déja dans la base de données
Returns: Returns:
None: Aucun retour requis, elle charge puis c'est tout None: Aucun retour requis, elle charge puis c'est tout
""" """
result = self.Base.db_execute_query(f"SELECT module_name FROM {self.Config.table_module}") result = self.Base.db_execute_query(f"SELECT module FROM {self.Config.table_module}")
for r in result.fetchall(): for r in result.fetchall():
self.load_module('sys', r[0], True) self.load_module('sys', r[0], True)
@@ -440,7 +418,6 @@ class Irc:
self.send2socket(f":{self.Config.SERVICE_NICKNAME} PRIVMSG {self.Config.SERVICE_CHANLOG} :[ {self.Config.CONFIG_COLOR['rouge']}MODULE_NOT_FOUND{self.Config.CONFIG_COLOR['noire']} ]: {moduleNotFound}") self.send2socket(f":{self.Config.SERVICE_NICKNAME} PRIVMSG {self.Config.SERVICE_CHANLOG} :[ {self.Config.CONFIG_COLOR['rouge']}MODULE_NOT_FOUND{self.Config.CONFIG_COLOR['noire']} ]: {moduleNotFound}")
except Exception as e: except Exception as e:
self.Base.logs.error(f"Something went wrong with a module you want to load : {e}") self.Base.logs.error(f"Something went wrong with a module you want to load : {e}")
self.send2socket(f":{self.Config.SERVICE_NICKNAME} PRIVMSG {self.Config.SERVICE_CHANLOG} :[ {self.Config.CONFIG_COLOR['rouge']}ERROR{self.Config.CONFIG_COLOR['noire']} ]: {e}")
def insert_db_admin(self, uid:str, level:int) -> None: def insert_db_admin(self, uid:str, level:int) -> None:
@@ -642,18 +619,8 @@ class Irc:
# if self.Config.ABUSEIPDB == 1: # if self.Config.ABUSEIPDB == 1:
# self.Base.create_thread(self.abuseipdb_scan, (cmd[2], )) # self.Base.create_thread(self.abuseipdb_scan, (cmd[2], ))
self.first_connexion_ip = cmd[2] self.first_connexion_ip = cmd[2]
self.first_score = int(cmd[3])
self.first_score = 0 pass
if str(cmd[3]).find('*') != -1:
# If * available, it means that an ircop changed the repurtation score
# means also that the user exist will try to update all users with same IP
self.first_score = int(str(cmd[3]).replace('*',''))
for user in self.User.UID_DB:
if user.remote_ip == self.first_connexion_ip:
user.score_connexion = self.first_score
else:
self.first_score = int(cmd[3])
# Possibilité de déclancher les bans a ce niveau. # Possibilité de déclancher les bans a ce niveau.
except IndexError as ie: except IndexError as ie:
self.Base.logs.error(f'{ie}') self.Base.logs.error(f'{ie}')
@@ -685,6 +652,7 @@ class Irc:
else: else:
version = f'{current_version}' version = f'{current_version}'
self.send2socket(f"MODE {self.Config.SERVICE_NICKNAME} +B")
self.send2socket(f"JOIN {self.Config.SERVICE_CHANLOG}") self.send2socket(f"JOIN {self.Config.SERVICE_CHANLOG}")
print(f"################### DEFENDER ###################") print(f"################### DEFENDER ###################")
print(f"# SERVICE CONNECTE ") print(f"# SERVICE CONNECTE ")
@@ -727,7 +695,6 @@ class Irc:
cmd.pop(0) cmd.pop(0)
uid_who_quit = str(cmd[0]).replace(':', '') uid_who_quit = str(cmd[0]).replace(':', '')
self.User.delete(uid_who_quit) self.User.delete(uid_who_quit)
self.Channel.delete_user_from_all_channel(uid_who_quit)
case 'PONG': case 'PONG':
# ['@msgid=aTNJhp17kcPboF5diQqkUL;time=2023-12-28T20:35:58.411Z', ':irc.deb.biz.st', 'PONG', 'irc.deb.biz.st', ':Dev-PyDefender'] # ['@msgid=aTNJhp17kcPboF5diQqkUL;time=2023-12-28T20:35:58.411Z', ':irc.deb.biz.st', 'PONG', 'irc.deb.biz.st', ':Dev-PyDefender']
@@ -751,34 +718,27 @@ class Irc:
case 'SJOIN': case 'SJOIN':
# ['@msgid=5sTwGdj349D82L96p749SY;time=2024-08-15T09:50:23.528Z', ':001', 'SJOIN', '1721564574', '#welcome', ':001JD94QH'] # ['@msgid=5sTwGdj349D82L96p749SY;time=2024-08-15T09:50:23.528Z', ':001', 'SJOIN', '1721564574', '#welcome', ':001JD94QH']
# ['@msgid=bvceb6HthbLJapgGLXn1b0;time=2024-08-15T09:50:11.464Z', ':001', 'SJOIN', '1721564574', '#welcome', '+lnrt', '13', ':001CIVLQF', '+11ZAAAAAB', '001QGR10C', '*@0014UE10B', '001NL1O07', '001SWZR05', '001HB8G04', '@00BAAAAAJ', '0019M7101'] # ['@msgid=bvceb6HthbLJapgGLXn1b0;time=2024-08-15T09:50:11.464Z', ':001', 'SJOIN', '1721564574', '#welcome', '+lnrt', '13', ':001CIVLQF', '+11ZAAAAAB', '001QGR10C', '*@0014UE10B', '001NL1O07', '001SWZR05', '001HB8G04', '@00BAAAAAJ', '0019M7101']
# ['@msgid=SKUeuVzOrTShRDduq8VerX;time=2024-08-23T19:37:04.266Z', ':001', 'SJOIN', '1723993047', '#welcome', '+lnrt', '13',
# ':001T6VU3F', '001JGWB2K', '@11ZAAAAAB',
# '001F16WGR', '001X9YMGQ', '*+001DYPFGP', '@00BAAAAAJ', '001AAGOG9', '001FMFVG8', '001DAEEG7',
# '&~G:unknown-users', '"~G:websocket-users', '"~G:known-users', '"~G:webirc-users']
cmd.pop(0) cmd.pop(0)
channel = str(cmd[3]).lower() channel = str(cmd[3]).lower()
mode = cmd[4]
len_cmd = len(cmd) len_cmd = len(cmd)
list_users:list = [] list_users:list = []
occurence = 0
start_boucle = 0 start_boucle = 0
# Trouver le premier user # Trouver le premier user
for i in range(len_cmd): for i in range(len_cmd):
s: list = re.findall(fr':', cmd[i]) s: list = re.findall(fr':', cmd[i])
if s: if s:
occurence += 1 start_boucle = i
if occurence == 2:
start_boucle = i
# 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(cmd)): for i in range(start_boucle, len(cmd)):
parsed_UID = str(cmd[i]) parsed_UID = str(cmd[i])
# pattern = fr'[:|@|%|\+|~|\*]*' pattern = fr'[:|@|%|\+|~|\*]*'
pattern = fr':' pattern = fr':'
parsed_UID = re.sub(pattern, '', parsed_UID) parsed_UID = re.sub(pattern, '', parsed_UID)
clean_uid = self.Base.clean_uid(parsed_UID) list_users.append(parsed_UID)
if len(clean_uid) == 9:
list_users.append(parsed_UID)
self.Channel.insert( self.Channel.insert(
self.Channel.ChannelModel( self.Channel.ChannelModel(
@@ -840,19 +800,21 @@ class Irc:
try: try:
# Supprimer la premiere valeur # Supprimer la premiere valeur
cmd.pop(0) cmd.pop(0)
get_uid_or_nickname = str(cmd[0].replace(':',''))
user_trigger = self.User.get_nickname(get_uid_or_nickname)
dnickname = self.Config.SERVICE_NICKNAME
get_uid_or_nickname = str(cmd[0].replace(':',''))
if len(cmd) == 6: if len(cmd) == 6:
if cmd[1] == 'PRIVMSG' and str(cmd[3]).replace('.','') == ':auth': if cmd[1] == 'PRIVMSG' and cmd[3] == ':auth':
cmd_copy = cmd.copy() cmd_copy = cmd.copy()
cmd_copy[5] = '**********' cmd_copy[5] = '**********'
self.Base.logs.info(cmd_copy) self.Base.logs.debug(cmd_copy)
else: else:
self.Base.logs.info(cmd) self.Base.logs.info(cmd)
else: else:
self.Base.logs.info(f'{cmd}') self.Base.logs.info(f'{cmd}')
# user_trigger = get_user.split('!')[0]
# user_trigger = self.get_nickname(get_uid_or_nickname)
user_trigger = self.User.get_nickname(get_uid_or_nickname)
dnickname = self.Config.SERVICE_NICKNAME
pattern = fr'(:\{self.Config.SERVICE_PREFIX})(.*)$' pattern = fr'(:\{self.Config.SERVICE_PREFIX})(.*)$'
hcmds = re.search(pattern, ' '.join(cmd)) # va matcher avec tout les caractéres aprés le . hcmds = re.search(pattern, ' '.join(cmd)) # va matcher avec tout les caractéres aprés le .
@@ -869,8 +831,7 @@ class Irc:
cmd_to_send = convert_to_string.replace(':','') cmd_to_send = convert_to_string.replace(':','')
self.Base.log_cmd(user_trigger, cmd_to_send) self.Base.log_cmd(user_trigger, cmd_to_send)
fromchannel = str(cmd[2]).lower() if self.Base.Is_Channel(cmd[2]) else None self._hcmds(user_trigger, arg, cmd)
self._hcmds(user_trigger, fromchannel, arg, cmd)
if cmd[2] == self.Config.SERVICE_ID: if cmd[2] == self.Config.SERVICE_ID:
pattern = fr'^:.*?:(.*)$' pattern = fr'^:.*?:(.*)$'
@@ -908,11 +869,7 @@ class Irc:
cmd_to_send = convert_to_string.replace(':','') cmd_to_send = convert_to_string.replace(':','')
self.Base.log_cmd(self.User.get_nickname(user_trigger), cmd_to_send) self.Base.log_cmd(self.User.get_nickname(user_trigger), cmd_to_send)
fromchannel = None self._hcmds(user_trigger, arg, cmd)
if len(arg) >= 2:
fromchannel = str(arg[1]).lower() if self.Base.Is_Channel(arg[1]) else None
self._hcmds(user_trigger, fromchannel, arg, cmd)
except IndexError as io: except IndexError as io:
self.Base.logs.error(f'{io}') self.Base.logs.error(f'{io}')
@@ -928,10 +885,10 @@ class Irc:
except IndexError as ie: except IndexError as ie:
self.Base.logs.error(f"{ie} / {cmd} / length {str(len(cmd))}") self.Base.logs.error(f"{ie} / {cmd} / length {str(len(cmd))}")
def _hcmds(self, user: str, channel: Union[str, None], cmd:list, fullcmd: list = []) -> None: def _hcmds(self, user: str, cmd:list, fullcmd: list = []) -> None:
fromuser = self.User.get_nickname(user) # Nickname qui a lancé la commande fromuser = self.User.get_nickname(user) # Nickname qui a lancé la commande
uid = self.User.get_uid(fromuser) # Récuperer le uid de l'utilisateur uid = self.User.get_uid(fromuser) # Récuperer le uid de l'utilisateur
# Defender information # Defender information
dnickname = self.Config.SERVICE_NICKNAME # Defender nickname dnickname = self.Config.SERVICE_NICKNAME # Defender nickname
@@ -949,7 +906,7 @@ class Irc:
# Envoyer la commande aux classes dynamiquement chargées # Envoyer la commande aux classes dynamiquement chargées
if command != 'notallowed': if command != 'notallowed':
for classe_name, classe_object in self.loaded_classes.items(): for classe_name, classe_object in self.loaded_classes.items():
classe_object._hcmds(user, channel, cmd, fullcmd) classe_object._hcmds(user, cmd, fullcmd)
match command: match command:
@@ -974,11 +931,6 @@ class Irc:
user_to_log = self.User.get_nickname(cmd[1]) user_to_log = self.User.get_nickname(cmd[1])
password = cmd[2] password = cmd[2]
if fromuser != user_to_log:
# If the current nickname is different from the nickname you want to log in with
self.send2socket(f":{self.Config.SERVICE_NICKNAME} NOTICE {fromuser} :Your current nickname is different from the nickname you want to log in with")
return False
if not user_to_log is None: if not user_to_log is None:
mes_donnees = {'user': user_to_log, 'password': self.Base.crypt_password(password)} mes_donnees = {'user': user_to_log, 'password': self.Base.crypt_password(password)}
query = f"SELECT id, level FROM {self.Config.table_admin} WHERE user = :user AND password = :password" query = f"SELECT id, level FROM {self.Config.table_admin} WHERE user = :user AND password = :password"
@@ -1182,12 +1134,11 @@ class Irc:
class_name = module_name.split('_')[1].capitalize() # ==> Defender class_name = module_name.split('_')[1].capitalize() # ==> Defender
if 'mods.' + module_name in sys.modules: if 'mods.' + module_name in sys.modules:
self.Base.logs.info('Unload the module ...')
self.loaded_classes[class_name].unload() self.loaded_classes[class_name].unload()
self.Base.logs.info('Module Already Loaded ... reloading the module ...') self.Base.logs.info('Module Already Loaded ... reloading the module ...')
the_module = sys.modules['mods.' + module_name] the_module = sys.modules['mods.' + module_name]
importlib.reload(the_module) importlib.reload(the_module)
# Supprimer la class déja instancier # Supprimer la class déja instancier
if class_name in self.loaded_classes: if class_name in self.loaded_classes:
# Supprimer les commandes déclarer dans la classe # Supprimer les commandes déclarer dans la classe
@@ -1207,23 +1158,8 @@ class Irc:
return False return False
else: else:
self.send2socket(f":{self.Config.SERVICE_NICKNAME} PRIVMSG {self.Config.SERVICE_CHANLOG} :Module {module_name} n'est pas chargé !") self.send2socket(f":{self.Config.SERVICE_NICKNAME} PRIVMSG {self.Config.SERVICE_CHANLOG} :Module {module_name} n'est pas chargé !")
except:
except TypeError as te: self.Base.logs.error(f"Something went wrong with a module you want to reload")
self.Base.logs.error(f"A TypeError raised: {te}")
self.send2socket(f":{self.Config.SERVICE_NICKNAME} PRIVMSG {self.Config.SERVICE_CHANLOG} :A TypeError raised: {te}")
self.Base.db_delete_module(module_name)
except AttributeError as ae:
self.Base.logs.error(f"Missing Attribute: {ae}")
self.send2socket(f":{self.Config.SERVICE_NICKNAME} PRIVMSG {self.Config.SERVICE_CHANLOG} :Missing Attribute: {ae}")
self.Base.db_delete_module(module_name)
except KeyError as ke:
self.Base.logs.error(f"Key Error: {ke}")
self.send2socket(f":{self.Config.SERVICE_NICKNAME} PRIVMSG {self.Config.SERVICE_CHANLOG} :Key Error: {ke}")
self.Base.db_delete_module(module_name)
except Exception as e:
self.Base.logs.error(f"Something went wrong with a module you want to reload: {e}")
self.send2socket(f":{self.Config.SERVICE_NICKNAME} PRIVMSG {self.Config.SERVICE_CHANLOG} :Something went wrong with the module: {e}")
self.Base.db_delete_module(module_name)
case 'quit': case 'quit':
try: try:
@@ -1253,6 +1189,9 @@ class Irc:
reason.append(cmd[i]) reason.append(cmd[i])
final_reason = ' '.join(reason) final_reason = ' '.join(reason)
# self.db_uid.clear() #Vider UID_DB
# self.db_chan = [] #Vider les salons
self.User.UID_DB.clear() # Clear User Object self.User.UID_DB.clear() # Clear User Object
self.Channel.UID_CHANNEL_DB.clear() # Clear Channel Object self.Channel.UID_CHANNEL_DB.clear() # Clear Channel Object
@@ -1269,44 +1208,31 @@ class Irc:
case 'show_modules': case 'show_modules':
self.Base.logs.debug(self.loaded_classes) self.Base.logs.debug(self.loaded_classes)
all_modules = self.Base.get_all_modules()
results = self.Base.db_execute_query(f'SELECT module_name FROM {self.Config.table_module}') results = self.Base.db_execute_query(f'SELECT module FROM {self.Config.table_module}')
results = results.fetchall() results = results.fetchall()
# if len(results) == 0: if len(results) == 0:
# self.send2socket(f":{dnickname} NOTICE {fromuser} :There is no module loaded") self.send2socket(f":{dnickname} PRIVMSG {dchanlog} :Aucun module chargé")
# return False return False
found = False for r in results:
self.send2socket(f":{dnickname} PRIVMSG {dchanlog} :Le module {r[0]} chargé")
for module in all_modules:
for loaded_mod in results:
if module == loaded_mod[0]:
found = True
if found:
self.send2socket(f":{dnickname} NOTICE {fromuser} :{module} - {self.Config.CONFIG_COLOR['verte']}Loaded{self.Config.CONFIG_COLOR['nogc']}")
else:
self.send2socket(f":{dnickname} NOTICE {fromuser} :{module} - {self.Config.CONFIG_COLOR['rouge']}Not Loaded{self.Config.CONFIG_COLOR['nogc']}")
found = False
# for r in results:
# self.send2socket(f":{dnickname} NOTICE {fromuser} :{r[0]} - {self.Config.CONFIG_COLOR['verte']}Loaded{self.Config.CONFIG_COLOR['nogc']}")
case 'show_timers': case 'show_timers':
if self.Base.running_timers: if self.Base.running_timers:
for the_timer in self.Base.running_timers: self.send2socket(f":{dnickname} PRIVMSG {dchanlog} :{self.Base.running_timers}")
self.send2socket(f":{dnickname} NOTICE {fromuser} :>> {the_timer.getName()} - {the_timer.is_alive()}")
else: else:
self.send2socket(f":{dnickname} NOTICE {fromuser} :Aucun timers en cours d'execution") self.send2socket(f":{dnickname} PRIVMSG {dchanlog} :Aucun timers en cours d'execution")
case 'show_threads': case 'show_threads':
running_thread_name:list = []
for thread in self.Base.running_threads: for thread in self.Base.running_threads:
self.send2socket(f":{dnickname} NOTICE {fromuser} :>> {thread.getName()} ({thread.is_alive()})") running_thread_name.append(f"{thread.getName()} ({thread.is_alive()})")
self.send2socket(f":{dnickname} PRIVMSG {dchanlog} :{str(running_thread_name)}")
case 'show_channels': case 'show_channels':
@@ -1319,14 +1245,6 @@ class Irc:
self.send2socket(f":{dnickname} NOTICE {fromuser} : Channel: {chan.name} - Users: {list_nicknames}") self.send2socket(f":{dnickname} NOTICE {fromuser} : Channel: {chan.name} - Users: {list_nicknames}")
case 'show_users':
for db_user in self.User.UID_DB:
self.send2socket(f":{dnickname} NOTICE {fromuser} :UID : {db_user.uid} - isWebirc: {db_user.isWebirc} - Nickname: {db_user.nickname} - Connection: {db_user.connexion_datetime}")
case 'show_admins':
for db_admin in self.Admin.UID_ADMIN_DB:
self.send2socket(f":{dnickname} NOTICE {fromuser} :UID : {db_admin.uid} - Nickname: {db_admin.nickname} - Level: {db_admin.level} - Connection: {db_admin.connexion_datetime}")
case 'uptime': case 'uptime':
uptime = self.get_defender_uptime() uptime = self.get_defender_uptime()
self.send2socket(f':{dnickname} NOTICE {fromuser} : {uptime}') self.send2socket(f':{dnickname} NOTICE {fromuser} : {uptime}')
@@ -1341,15 +1259,5 @@ class Irc:
(fromuser, ) (fromuser, )
) )
case 'umode':
try:
# .umode nickname +mode
nickname = str(cmd[1])
umode = str(cmd[2])
self.send2socket(f':{dnickname} SVSMODE {nickname} {umode}')
except KeyError as ke:
self.Base.logs.error(ke)
case _: case _:
pass pass

View File

@@ -1,4 +1,4 @@
import json, sys import json
from os import sep from os import sep
from typing import Union from typing import Union
from dataclasses import dataclass, field from dataclasses import dataclass, field
@@ -52,8 +52,6 @@ class ConfigDataModel:
table_commande: str table_commande: str
table_log: str table_log: str
table_module: str table_module: str
table_config: str
table_channel: str
current_version: str current_version: str
latest_version: str latest_version: str
@@ -72,21 +70,16 @@ class Config:
return None return None
def __load_json_service_configuration(self): def __load_json_service_configuration(self):
try:
conf_filename = f'core{sep}configuration.json'
with open(conf_filename, 'r') as configuration_data:
configuration:dict[str, Union[str, int, list, dict]] = json.load(configuration_data)
for key, value in configuration['CONFIG_COLOR'].items(): conf_filename = f'core{sep}configuration.json'
configuration['CONFIG_COLOR'][key] = str(value).encode('utf-8').decode('unicode_escape') with open(conf_filename, 'r') as configuration_data:
configuration:dict[str, Union[str, int, list, dict]] = json.load(configuration_data)
return configuration
except FileNotFoundError as fe:
print(f'FileNotFound: {fe}')
print('Configuration file not found please create core/configuration.json')
sys.exit(0)
for key, value in configuration['CONFIG_COLOR'].items():
configuration['CONFIG_COLOR'][key] = str(value).encode('utf-8').decode('unicode_escape')
return configuration
def __load_service_configuration(self) -> ConfigDataModel: def __load_service_configuration(self) -> ConfigDataModel:
import_config = self.__load_json_service_configuration() import_config = self.__load_json_service_configuration()
@@ -120,12 +113,10 @@ class Config:
GLINE_DURATION=import_config["GLINE_DURATION"], GLINE_DURATION=import_config["GLINE_DURATION"],
DEBUG_LEVEL=import_config["DEBUG_LEVEL"], DEBUG_LEVEL=import_config["DEBUG_LEVEL"],
CONFIG_COLOR=import_config["CONFIG_COLOR"], CONFIG_COLOR=import_config["CONFIG_COLOR"],
table_admin='core_admin', table_admin='sys_admins',
table_commande='core_command', table_commande='sys_commandes',
table_log='core_log', table_log='sys_logs',
table_module='core_module', table_module='sys_modules',
table_config='core_config',
table_channel='core_channel',
current_version='', current_version='',
latest_version='', latest_version='',
db_name='defender', db_name='defender',

View File

@@ -1,275 +0,0 @@
from subprocess import check_call, run, CalledProcessError, PIPE
from platform import python_version, python_version_tuple, system
from sys import exit
import os, logging, shutil
try:
import pwd
except ModuleNotFoundError as err:
print(err)
class Install:
def __init__(self) -> None:
# Python required version
self.python_min_version = '3.10'
self.log_file = 'install.log'
self.ServiceName = 'Defender'
self.venv_name = '.pyenv'
self.venv_dependencies: list[str] = ['sqlalchemy','psutil','requests']
self.install_folder = os.getcwd()
self.osname = os.name
self.system_name = system()
self.cmd_linux_requirements: list[str] = ['apt', 'install', '-y', 'python3', 'python3-pip', 'python3-venv']
self.venv_pip_full_path = os.path.join(self.venv_name, f'bin{os.sep}pip')
self.venv_python_full_path = os.path.join(self.venv_name, f'bin{os.sep}python')
self.systemd_folder = '/etc/systemd/system/'
# Init log system
self.init_log_system()
# Exclude Windows OS
if self.osname == 'nt':
print('/!\\ Windows OS is not supported by this automatic installation /!\\')
self.Logs.critical('/!\\ Windows OS is not supported by this automatic install /!\\')
print(self.system_name)
exit(5)
if not self.is_root():
exit(5)
# Get the current user
self.system_username: str = input(f'What is the user ro run defender with ? [{os.getlogin()}] : ')
if str(self.system_username).strip() == '':
self.system_username = os.getlogin()
self.get_user_information(self.system_username)
self.Logs.debug(f'The user selected is: {self.system_username}')
self.Logs.debug(f'Operating system: {self.osname}')
# Install linux dependencies
self.install_linux_dependencies()
# Check python version
self.check_python_version()
# Create systemd service file
self.create_service_file()
# Check if Env Exist | install environment | Install python dependencies
self.check_venv()
# Create and start service
if self.osname != 'nt':
self.run_subprocess(['systemctl','daemon-reload'])
self.run_subprocess(['systemctl','start', self.ServiceName])
self.run_subprocess(['systemctl','status', self.ServiceName])
# Clean the Installation
self.clean_installation()
return None
def is_installed(self) -> bool:
is_installed = False
# Check logs folder
if os.path.exists('logs'):
is_installed = True
# Check db folder
if os.path.exists('db'):
is_installed = True
return is_installed
def is_root(self) -> bool:
if os.geteuid() != 0:
print('/!\\ user must run install.py as root /!\\')
self.Logs.critical('/!\\ user must run install.py as root /!\\')
return False
elif os.geteuid() == 0:
return True
def get_user_information(self, system_user: str) -> None:
try:
username: tuple = pwd.getpwnam(system_user)
self.system_uid = username.pw_uid
self.system_gid = username.pw_gid
return None
except KeyError as ke:
self.Logs.critical(f"This user [{system_user}] doesn't exist: {ke}")
print(f"This user [{system_user}] doesn't exist: {ke}")
exit(5)
def init_log_system(self) -> None:
# Init logs object
self.Logs = logging
self.Logs.basicConfig(level=logging.DEBUG,
filename=self.log_file,
encoding='UTF-8',
format='%(asctime)s - %(levelname)s - %(filename)s - %(lineno)d - %(funcName)s - %(message)s')
self.Logs.debug('#################### STARTING INSTALLATION ####################')
return None
def clean_installation(self) -> None:
# Chown the Python Env to non user privilege
self.run_subprocess(['chown','-R', f'{self.system_username}:{self.system_username}',
f'{os.path.join(self.install_folder, self.venv_name)}'
]
)
# Chown the installation log file
self.run_subprocess(['chown','-R', f'{self.system_username}:{self.system_username}',
f'{os.path.join(self.install_folder, self.log_file)}'
]
)
return None
def run_subprocess(self, command:list) -> None:
try:
run_command = check_call(command)
self.Logs.debug(f'{command} - {run_command}')
print(f'{command} - {run_command}')
except CalledProcessError as e:
print(f"Command failed :{e.returncode}")
self.Logs.critical(f"Command failed :{e.returncode}")
exit(5)
def check_python_version(self) -> bool:
"""Test si la version de python est autorisée ou non
Returns:
bool: True si la version de python est autorisé sinon False
"""
self.Logs.debug(f'The current python version is: {python_version()}')
# Current system version
sys_major, sys_minor, sys_patch = python_version_tuple()
# min python version required
python_required_version = self.PYTHON_MIN_VERSION.split('.')
min_major, min_minor = tuple((python_required_version[0], python_required_version[1]))
if int(sys_major) < int(min_major):
print(f"## Your python version must be greather than or equal to {self.PYTHON_MIN_VERSION} ##")
self.Logs.critical(f'Your python version must be greather than or equal to {self.python_min_version}')
return False
elif (int(sys_major) <= int(min_major)) and (int(sys_minor) < int(min_minor)):
print(f"## Your python version must be greather than or equal to {self.PYTHON_MIN_VERSION} ##")
self.Logs.critical(f'Your python version must be greather than or equal to {self.python_min_version}')
return False
print(f"===> Version of python : {python_version()} ==> OK")
self.Logs.debug(f'Version of python : {python_version()} ==> OK')
return True
def check_packages(self, package_name) -> bool:
try:
# Run a command in the virtual environment's Python to check if the package is installed
run([self.venv_python_full_path, '-c', f'import {package_name}'], check=True, stdout=PIPE, stderr=PIPE)
return True
except CalledProcessError:
return False
def check_venv(self) -> bool:
if os.path.exists(self.venv_name):
# Installer les dependances
self.install_dependencies()
return True
else:
self.run_subprocess(['python3', '-m', 'venv', self.venv_name])
self.Logs.debug(f'Python Virtual env installed {self.venv_name}')
print(f'Python Virtual env installed {self.venv_name}')
self.install_dependencies()
return False
def create_service_file(self) -> None:
if self.systemd_folder is None:
# If Windows, do not install systemd
return None
if os.path.exists(f'{self.systemd_folder}{os.sep}{self.ServiceName}.service'):
print(f'/!\\ Service already created in the system /!\\')
self.Logs.warning('/!\\ Service already created in the system /!\\')
print(f'The service file will be regenerated')
self.Logs.warning('The service file will be regenerated')
contain = f'''[Unit]
Description={self.ServiceName} IRC Service
[Service]
User={self.system_username}
ExecStart={os.path.join(self.install_folder, self.venv_python_full_path)} {os.path.join(self.install_folder, 'main.py')}
WorkingDirectory={self.install_folder}
SyslogIdentifier={self.ServiceName}
Restart=on-failure
[Install]
WantedBy=multi-user.target
'''
with open(f'{self.ServiceName}.service.generated', 'w+') as servicefile:
servicefile.write(contain)
servicefile.close()
print('Service file generated with current configuration')
self.Logs.debug('Service file generated with current configuration')
source = f'{self.install_folder}{os.sep}{self.ServiceName}.service.generated'
self.run_subprocess(['chown','-R', f'{self.system_username}:{self.system_username}', source])
destination = f'{self.systemd_folder}'
shutil.copy(source, destination)
os.rename(f'{self.systemd_folder}{os.sep}{self.ServiceName}.service.generated', f'{self.systemd_folder}{os.sep}{self.ServiceName}.service')
print(f'Service file moved to systemd folder {self.systemd_folder}')
self.Logs.debug(f'Service file moved to systemd folder {self.systemd_folder}')
def install_linux_dependencies(self) -> None:
self.run_subprocess(self.cmd_linux_requirements)
return None
def install_dependencies(self) -> None:
try:
self.run_subprocess([self.venv_pip_full_path, 'cache', 'purge'])
self.run_subprocess([self.venv_python_full_path, '-m', 'pip', 'install', '--upgrade', 'pip'])
if self.check_packages('greenlet') is None:
self.run_subprocess(
[self.venv_pip_full_path, 'install', '--only-binary', ':all:', 'greenlet']
)
for module in self.venv_dependencies:
if not self.check_packages(module):
### Trying to install missing python packages ###
self.run_subprocess([self.venv_pip_full_path, 'install', module])
else:
self.Logs.debug(f'{module} already installed')
print(f"==> {module} already installed")
except CalledProcessError as cpe:
self.Logs.critical(f'{cpe}')
Install()

View File

@@ -1,242 +0,0 @@
from dataclasses import dataclass, fields, field
import random, faker, time
from datetime import datetime
from typing import Union
from core.irc import Irc
from core.connection import Connection
class Clone():
@dataclass
class ModConfModel:
clone_count: int
clone_nicknames: list[str]
def __init__(self, ircInstance:Irc) -> None:
# Module name (Mandatory)
self.module_name = 'mod_' + str(self.__class__.__name__).lower()
# Add Irc Object to the module (Mandatory)
self.Irc = ircInstance
# Add Global Configuration to the module (Mandatory)
self.Config = ircInstance.Config
# Add Base object to the module (Mandatory)
self.Base = ircInstance.Base
# Add logs object to the module (Mandatory)
self.Logs = ircInstance.Base.logs
# Add User object to the module (Mandatory)
self.User = ircInstance.User
# Add Channel object to the module (Mandatory)
self.Channel = ircInstance.Channel
# Créer les nouvelles commandes du module
self.commands_level = {
1: ['clone_connect', 'clone_join', 'clone_kill', 'clone_list']
}
# Init the module (Mandatory)
self.__init_module()
# Log the module
self.Logs.debug(f'Module {self.module_name} loaded ...')
def __init_module(self) -> None:
# Enrigstrer les nouvelles commandes dans le code
self.__set_commands(self.commands_level)
# Créer les tables necessaire a votre module (ce n'es pas obligatoire)
self.__create_tables()
# Load module configuration (Mandatory)
self.__load_module_configuration()
def __set_commands(self, commands:dict[int, list[str]]) -> None:
"""### Rajoute les commandes du module au programme principal
Args:
commands (list): Liste des commandes du module
"""
for level, com in commands.items():
for c in commands[level]:
if not c in self.Irc.commands:
self.Irc.commands_level[level].append(c)
self.Irc.commands.append(c)
return None
def __create_tables(self) -> None:
"""Methode qui va créer la base de donnée si elle n'existe pas.
Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module
Args:
database_name (str): Nom de la base de données ( pas d'espace dans le nom )
Returns:
None: Aucun retour n'es attendu
"""
table_channel = '''CREATE TABLE IF NOT EXISTS clone_list (
id INTEGER PRIMARY KEY AUTOINCREMENT,
datetime TEXT,
nickname TEXT,
username TEXT
)
'''
self.Base.db_execute_query(table_channel)
return None
def __load_module_configuration(self) -> None:
"""### Load Module Configuration
"""
try:
# Variable qui va contenir les options de configuration du module Defender
# Variable qui va contenir les options de configuration du module Defender
self.ModConfig = self.ModConfModel(
clone_count=0,
clone_nicknames=[]
)
# Sync the configuration with core configuration (Mandatory)
self.Base.db_sync_core_config(self.module_name, self.ModConfig)
return None
except TypeError as te:
self.Logs.critical(te)
def unload(self) -> None:
"""Cette methode sera executée a chaque désactivation ou
rechargement de module
"""
# kill all clones before unload
for clone in self.ModConfig.clone_nicknames:
self.Irc.send2socket(f':{self.Config.SERVICE_NICKNAME} PRIVMSG {clone} :KILL')
return None
def thread_create_clones(self, nickname: str, username: str, channels:list, server_port:int, ssl:bool) -> None:
Connection(server_port=server_port, nickname=nickname, username=username, channels=channels, ssl=ssl)
return None
def thread_join_channels(self, channel_name: str, wait: float, clone_name:str = None):
if clone_name is None:
for clone in self.ModConfig.clone_nicknames:
self.Irc.send2socket(f':{self.Config.SERVICE_NICKNAME} PRIVMSG {clone} :JOIN {channel_name}')
time.sleep(wait)
else:
for clone in self.ModConfig.clone_nicknames:
if clone_name == clone:
self.Irc.send2socket(f':{self.Config.SERVICE_NICKNAME} PRIVMSG {clone} :JOIN {channel_name}')
def generate_names(self) -> tuple[str, str]:
try:
fake = faker.Faker('en_GB')
nickname = fake.first_name()
username = fake.last_name()
if not nickname in self.ModConfig.clone_nicknames:
self.ModConfig.clone_nicknames.append(nickname)
else:
caracteres = '0123456789'
randomize = ''.join(random.choice(caracteres) for _ in range(2))
nickname = nickname + str(randomize)
self.ModConfig.clone_nicknames.append(nickname)
return (nickname, username)
except AttributeError as ae:
self.Logs.error(f'Attribute Error : {ae}')
except Exception as err:
self.Logs.error(err)
def cmd(self, data:list) -> None:
service_id = self.Config.SERVICE_ID # Defender serveur id
cmd = list(data).copy()
if len(cmd) < 2:
return None
match cmd[1]:
case 'REPUTATION':
pass
def _hcmds(self, user:str, channel: any, cmd: list, fullcmd: list = []) -> None:
command = str(cmd[0]).lower()
fromuser = user
dnickname = self.Config.SERVICE_NICKNAME # Defender nickname
match command:
case 'clone_connect':
# clone_connect 25
try:
number_of_clones = int(cmd[1])
for i in range(number_of_clones):
nickname, username = self.generate_names()
self.Base.create_thread(
self.thread_create_clones,
(nickname, username, [], 6697, True)
)
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} :{str(number_of_clones)} clones joined the network')
except Exception as err:
self.Logs.error(f'{err}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} :/msg {dnickname} clone_connect [number of clone you want to connect]')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} :Exemple /msg {dnickname} clone_kill 6')
case 'clone_kill':
try:
clone_name = str(cmd[1])
if clone_name.lower() == 'all':
for clone in self.ModConfig.clone_nicknames:
self.Irc.send2socket(f':{dnickname} PRIVMSG {clone} :KILL')
self.ModConfig.clone_nicknames.remove(clone)
else:
for clone in self.ModConfig.clone_nicknames:
if clone_name == clone:
self.Irc.send2socket(f':{dnickname} PRIVMSG {clone} :KILL')
self.ModConfig.clone_nicknames.remove(clone)
except Exception as err:
self.Logs.error(f'{err}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} :/msg {dnickname} clone_kill all')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} :/msg {dnickname} clone_kill [clone_name]')
case 'clone_join':
try:
# clone_join nickname #channel
clone_name = str(cmd[1])
clone_channel_to_join = cmd[2]
if clone_name.lower() == 'all':
self.Base.create_thread(self.thread_join_channels, (clone_channel_to_join, 4))
else:
self.Base.create_thread(self.thread_join_channels, (clone_channel_to_join, 4, clone_name))
except Exception as err:
self.Logs.error(f'{err}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} :/msg {dnickname} clone_join all #channel')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} :/msg {dnickname} clone_join clone_nickname #channel')
case 'clone_list':
for clone_name in self.ModConfig.clone_nicknames:
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} :>> {clone_name}')

View File

@@ -1,483 +0,0 @@
from dataclasses import dataclass, fields
from core.irc import Irc
class Command():
@dataclass
class ModConfModel:
"""The Model containing the module parameters
"""
pass
def __init__(self, ircInstance:Irc) -> None:
# Module name (Mandatory)
self.module_name = 'mod_' + str(self.__class__.__name__).lower()
# Add Irc Object to the module (Mandatory)
self.Irc = ircInstance
# Add Global Configuration to the module (Mandatory)
self.Config = ircInstance.Config
# Add Base object to the module (Mandatory)
self.Base = ircInstance.Base
# Add logs object to the module (Mandatory)
self.Logs = ircInstance.Base.logs
# Add User object to the module (Mandatory)
self.User = ircInstance.User
# Add Channel object to the module (Mandatory)
self.Channel = ircInstance.Channel
# Create module commands (Mandatory)
self.commands_level = {
1: ['join', 'part'],
2: ['owner', 'deowner', 'op', 'deop', 'halfop', 'dehalfop', 'voice', 'devoice', 'ban', 'unban','kick', 'kickban']
}
# Init the module
self.__init_module()
# Log the module
self.Logs.debug(f'Module {self.module_name} loaded ...')
def __init_module(self) -> None:
# Insert module commands into the core one (Mandatory)
self.__set_commands(self.commands_level)
# Create you own tables (Mandatory)
self.__create_tables()
# Load module configuration and sync with core one (Mandatory)
self.__load_module_configuration()
# End of mandatory methods you can start your customization #
return None
def __set_commands(self, commands:dict[int, list[str]]) -> None:
"""### Rajoute les commandes du module au programme principal
Args:
commands (list): Liste des commandes du module
"""
for level, com in commands.items():
for c in commands[level]:
if not c in self.Irc.commands:
self.Irc.commands_level[level].append(c)
self.Irc.commands.append(c)
return None
def __create_tables(self) -> None:
"""Methode qui va créer la base de donnée si elle n'existe pas.
Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module
Args:
database_name (str): Nom de la base de données ( pas d'espace dans le nom )
Returns:
None: Aucun retour n'es attendu
"""
table_logs = '''CREATE TABLE IF NOT EXISTS test_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
datetime TEXT,
server_msg TEXT
)
'''
self.Base.db_execute_query(table_logs)
return None
def __load_module_configuration(self) -> None:
"""### Load Module Configuration
"""
try:
# Build the default configuration model (Mandatory)
self.ModConfig = self.ModConfModel(param_exemple1='param value 1', param_exemple2=1)
# Sync the configuration with core configuration (Mandatory)
self.Base.db_sync_core_config(self.module_name, self.ModConfig)
return None
except TypeError as te:
self.Logs.critical(te)
def __update_configuration(self, param_key: str, param_value: str):
"""Update the local and core configuration
Args:
param_key (str): The parameter key
param_value (str): The parameter value
"""
self.Base.db_update_core_config(self.module_name, self.ModConfig, param_key, param_value)
def unload(self) -> None:
return None
def add_defender_channel(self, channel:str) -> bool:
"""Cette fonction ajoute les salons de join de Defender
Args:
channel (str): le salon à enregistrer.
"""
mes_donnees = {'channel': channel}
response = self.Base.db_execute_query("SELECT id FROM def_channels WHERE channel = :channel", mes_donnees)
isChannelExist = response.fetchone()
if isChannelExist is None:
mes_donnees = {'datetime': self.Base.get_datetime(), 'channel': channel}
insert = self.Base.db_execute_query(f"INSERT INTO def_channels (datetime, channel) VALUES (:datetime, :channel)", mes_donnees)
if insert.rowcount >=0:
return True
else:
return False
else:
return False
def delete_defender_channel(self, channel:str) -> bool:
"""Cette fonction supprime les salons de join de Defender
Args:
channel (str): le salon à enregistrer.
"""
mes_donnes = {'channel': channel}
response = self.Base.db_execute_query("DELETE FROM def_channels WHERE channel = :channel", mes_donnes)
affected_row = response.rowcount
if affected_row > 0:
return True
else:
return False
def cmd(self, data:list) -> None:
return None
def _hcmds(self, user:str, channel: any, cmd: list, fullcmd: list = []) -> None:
command = str(cmd[0]).lower()
dnickname = self.Config.SERVICE_NICKNAME
service_id = self.Config.SERVICE_ID
dchanlog = self.Config.SERVICE_CHANLOG
fromuser = user
fromchannel = channel
match command:
case 'op':
# /mode #channel +o user
# .op #channel user
# /msg dnickname op #channel user
# [':adator', 'PRIVMSG', '#services', ':.o', '#services', 'dktmb']
try:
if fromchannel is None:
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} op [#SALON] [NICKNAME]')
return False
if len(cmd) == 1:
self.Irc.send2socket(f":{dnickname} MODE {fromchannel} +o {fromuser}")
return True
# deop nickname
if len(cmd) == 2:
nickname = cmd[1]
self.Irc.send2socket(f":{service_id} MODE {fromchannel} +o {nickname}")
return True
nickname = cmd[2]
self.Irc.send2socket(f":{service_id} MODE {fromchannel} +o {nickname}")
except IndexError as e:
self.Logs.warning(f'_hcmd OP: {str(e)}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} op [#SALON] [NICKNAME]')
case 'deop':
# /mode #channel -o user
# .deop #channel user
try:
if fromchannel is None:
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} deop [#SALON] [NICKNAME]')
return False
if len(cmd) == 1:
self.Irc.send2socket(f":{service_id} MODE {fromchannel} -o {fromuser}")
return True
# deop nickname
if len(cmd) == 2:
nickname = cmd[1]
self.Irc.send2socket(f":{service_id} MODE {fromchannel} -o {nickname}")
return True
nickname = cmd[2]
self.Irc.send2socket(f":{service_id} MODE {fromchannel} -o {nickname}")
except IndexError as e:
self.Logs.warning(f'_hcmd DEOP: {str(e)}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} deop [#SALON] [NICKNAME]')
case 'owner':
# /mode #channel +q user
# .owner #channel user
try:
if fromchannel is None:
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} owner [#SALON] [NICKNAME]')
return False
if len(cmd) == 1:
self.Irc.send2socket(f":{service_id} MODE {fromchannel} +q {fromuser}")
return True
# owner nickname
if len(cmd) == 2:
nickname = cmd[1]
self.Irc.send2socket(f":{service_id} MODE {fromchannel} +q {nickname}")
return True
nickname = cmd[2]
self.Irc.send2socket(f":{service_id} MODE {fromchannel} +q {nickname}")
except IndexError as e:
self.Logs.warning(f'_hcmd OWNER: {str(e)}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} owner [#SALON] [NICKNAME]')
case 'deowner':
# /mode #channel -q user
# .deowner #channel user
try:
if fromchannel is None:
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} deowner [#SALON] [NICKNAME]')
return False
if len(cmd) == 1:
self.Irc.send2socket(f":{service_id} MODE {fromchannel} -q {fromuser}")
return True
# deowner nickname
if len(cmd) == 2:
nickname = cmd[1]
self.Irc.send2socket(f":{service_id} MODE {fromchannel} -q {nickname}")
return True
nickname = cmd[2]
self.Irc.send2socket(f":{service_id} MODE {fromchannel} -q {nickname}")
except IndexError as e:
self.Logs.warning(f'_hcmd DEOWNER: {str(e)}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} deowner [#SALON] [NICKNAME]')
case 'halfop':
# /mode #channel +h user
# .halfop #channel user
try:
if fromchannel is None:
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} halfop [#SALON] [NICKNAME]')
return False
if len(cmd) == 1:
self.Irc.send2socket(f":{service_id} MODE {fromchannel} +h {fromuser}")
return True
# deop nickname
if len(cmd) == 2:
nickname = cmd[1]
self.Irc.send2socket(f":{service_id} MODE {fromchannel} +h {nickname}")
return True
nickname = cmd[2]
self.Irc.send2socket(f":{service_id} MODE {fromchannel} +h {nickname}")
except IndexError as e:
self.Logs.warning(f'_hcmd halfop: {str(e)}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} halfop [#SALON] [NICKNAME]')
case 'dehalfop':
# /mode #channel -h user
# .dehalfop #channel user
try:
if fromchannel is None:
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} dehalfop [#SALON] [NICKNAME]')
return False
if len(cmd) == 1:
self.Irc.send2socket(f":{service_id} MODE {fromchannel} -h {fromuser}")
return True
# dehalfop nickname
if len(cmd) == 2:
nickname = cmd[1]
self.Irc.send2socket(f":{service_id} MODE {fromchannel} -h {nickname}")
return True
nickname = cmd[2]
self.Irc.send2socket(f":{service_id} MODE {fromchannel} -h {nickname}")
except IndexError as e:
self.Logs.warning(f'_hcmd DEHALFOP: {str(e)}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} dehalfop [#SALON] [NICKNAME]')
case 'voice':
# /mode #channel +v user
# .voice #channel user
try:
if fromchannel is None:
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} voice [#SALON] [NICKNAME]')
return False
if len(cmd) == 1:
self.Irc.send2socket(f":{service_id} MODE {fromchannel} +v {fromuser}")
return True
# voice nickname
if len(cmd) == 2:
nickname = cmd[1]
self.Irc.send2socket(f":{service_id} MODE {fromchannel} +v {nickname}")
return True
nickname = cmd[2]
self.Irc.send2socket(f":{service_id} MODE {fromchannel} +v {nickname}")
except IndexError as e:
self.Logs.warning(f'_hcmd VOICE: {str(e)}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} voice [#SALON] [NICKNAME]')
case 'devoice':
# /mode #channel -v user
# .devoice #channel user
try:
if fromchannel is None:
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} devoice [#SALON] [NICKNAME]')
return False
if len(cmd) == 1:
self.Irc.send2socket(f":{service_id} MODE {fromchannel} -v {fromuser}")
return True
# dehalfop nickname
if len(cmd) == 2:
nickname = cmd[1]
self.Irc.send2socket(f":{service_id} MODE {fromchannel} -v {nickname}")
return True
nickname = cmd[2]
self.Irc.send2socket(f":{service_id} MODE {fromchannel} -v {nickname}")
except IndexError as e:
self.Logs.warning(f'_hcmd DEVOICE: {str(e)}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} devoice [#SALON] [NICKNAME]')
case 'ban':
# .ban #channel nickname
try:
sentchannel = str(cmd[1]) if self.Base.Is_Channel(cmd[1]) else None
if sentchannel is None:
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} ban [#SALON] [NICKNAME]')
return False
nickname = cmd[2]
self.Irc.send2socket(f":{service_id} MODE {sentchannel} +b {nickname}!*@*")
self.Logs.debug(f'{fromuser} has banned {nickname} from {sentchannel}')
except IndexError as e:
self.Logs.warning(f'_hcmd BAN: {str(e)}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} ban [#SALON] [NICKNAME]')
case 'unban':
# .unban #channel nickname
try:
sentchannel = str(cmd[1]) if self.Base.Is_Channel(cmd[1]) else None
if sentchannel is None:
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} ban [#SALON] [NICKNAME]')
return False
nickname = cmd[2]
self.Irc.send2socket(f":{service_id} MODE {sentchannel} -b {nickname}!*@*")
self.Logs.debug(f'{fromuser} has unbanned {nickname} from {sentchannel}')
except IndexError as e:
self.Logs.warning(f'_hcmd UNBAN: {str(e)}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} unban [#SALON] [NICKNAME]')
case 'kick':
# .kick #channel nickname reason
try:
sentchannel = str(cmd[1]) if self.Base.Is_Channel(cmd[1]) else None
if sentchannel is None:
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} ban [#SALON] [NICKNAME]')
return False
nickname = cmd[2]
reason = []
for i in range(3, len(cmd)):
reason.append(cmd[i])
final_reason = ' '.join(reason)
self.Irc.send2socket(f":{service_id} KICK {sentchannel} {nickname} {final_reason}")
self.Logs.debug(f'{fromuser} has kicked {nickname} from {sentchannel} : {final_reason}')
except IndexError as e:
self.Logs.warning(f'_hcmd KICK: {str(e)}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} kick [#SALON] [NICKNAME] [REASON]')
case 'kickban':
# .kickban #channel nickname reason
try:
sentchannel = str(cmd[1]) if self.Base.Is_Channel(cmd[1]) else None
if sentchannel is None:
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} ban [#SALON] [NICKNAME]')
return False
nickname = cmd[2]
reason = []
for i in range(3, len(cmd)):
reason.append(cmd[i])
final_reason = ' '.join(reason)
self.Irc.send2socket(f":{service_id} KICK {sentchannel} {nickname} {final_reason}")
self.Irc.send2socket(f":{service_id} MODE {sentchannel} +b {nickname}!*@*")
self.Logs.debug(f'{fromuser} has kicked and banned {nickname} from {sentchannel} : {final_reason}')
except IndexError as e:
self.Logs.warning(f'_hcmd KICKBAN: {str(e)}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : Right command : /msg {dnickname} kickban [#SALON] [NICKNAME] [REASON]')
case 'join':
try:
sent_channel = str(cmd[1]) if self.Base.Is_Channel(cmd[1]) else None
if sent_channel is None:
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} :{self.Config.SERVICE_PREFIX}JOIN #channel')
return False
self.Irc.send2socket(f':{service_id} JOIN {sent_channel}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : {dnickname} JOINED {sent_channel}')
self.Base.db_query_channel('add', self.module_name, sent_channel)
except IndexError as ie:
self.Logs.error(f'{ie}')
case 'part':
try:
sent_channel = str(cmd[1]) if self.Base.Is_Channel(cmd[1]) else None
if sent_channel is None:
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} :{self.Config.SERVICE_PREFIX}PART #channel')
return False
if sent_channel == dchanlog:
self.Irc.send2socket(f":{dnickname} NOTICE {fromuser} : {dnickname} CAN'T LEFT {sent_channel} AS IT IS LOG CHANNEL")
return False
self.Irc.send2socket(f':{service_id} PART {sent_channel}')
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} : {dnickname} LEFT {sent_channel}')
self.Base.db_query_channel('del', self.module_name, sent_channel)
except IndexError as ie:
self.Logs.error(f'{ie}')

File diff suppressed because it is too large Load Diff

View File

@@ -1,64 +1,53 @@
from dataclasses import dataclass, fields
from core.irc import Irc from core.irc import Irc
# Le module crée devra réspecter quelques conditions
# 1. Importer le module de configuration
# 2. Le nom de class devra toujours s'appeler comme le module exemple => nom de class Dktmb | nom du module mod_dktmb
# 3. la fonction __init__ devra toujours avoir les parametres suivant (self, irc:object)
# 1 . Créer la variable Irc dans le module
# 2 . Récuperer la configuration dans une variable
# 3 . Définir et enregistrer les nouvelles commandes
# 4. une fonction _hcmds(self, user:str, cmd: list) devra toujours etre crée.
class Test(): class Test():
@dataclass
class ModConfModel:
"""The Model containing the module parameters
"""
param_exemple1: str
param_exemple2: int
def __init__(self, ircInstance:Irc) -> None: def __init__(self, ircInstance:Irc) -> None:
# Module name (Mandatory) # Add Irc Object to the module
self.module_name = 'mod_' + str(self.__class__.__name__).lower()
# Add Irc Object to the module (Mandatory)
self.Irc = ircInstance self.Irc = ircInstance
# Add Global Configuration to the module (Mandatory) # Add Global Configuration to the module
self.Config = ircInstance.Config self.Config = ircInstance.Config
# Add Base object to the module (Mandatory) # Add Base object to the module
self.Base = ircInstance.Base self.Base = ircInstance.Base
# Add logs object to the module (Mandatory) # Add logs object to the module
self.Logs = ircInstance.Base.logs self.Logs = ircInstance.Base.logs
# Add User object to the module (Mandatory) # Add User object to the module
self.User = ircInstance.User self.User = ircInstance.User
# Add Channel object to the module (Mandatory) # Add Channel object to the module
self.Channel = ircInstance.Channel self.Channel = ircInstance.Channel
# Create module commands (Mandatory) # Créer les nouvelles commandes du module
self.commands_level = { self.commands_level = {
0: ['test-command'], 0: ['test'],
1: ['test_level_1'], 1: ['test_level_1']
2: ['test_level_2'],
3: ['test_level_3']
} }
# Init the module # Init the module
self.__init_module() self.__init_module()
# Log the module # Log the module
self.Logs.debug(f'Module {self.module_name} loaded ...') self.Logs.debug(f'Module {self.__class__.__name__} loaded ...')
def __init_module(self) -> None: def __init_module(self) -> None:
# Insert module commands into the core one (Mandatory)
self.__set_commands(self.commands_level) self.__set_commands(self.commands_level)
# Create you own tables (Mandatory)
self.__create_tables() self.__create_tables()
# Load module configuration and sync with core one (Mandatory)
self.__load_module_configuration()
# End of mandatory methods you can start your customization #
return None return None
def __set_commands(self, commands:dict[int, list[str]]) -> None: def __set_commands(self, commands:dict[int, list[str]]) -> None:
@@ -89,67 +78,31 @@ class Test():
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
datetime TEXT, datetime TEXT,
server_msg TEXT server_msg TEXT
) )
''' '''
self.Base.db_execute_query(table_logs) self.Base.db_execute_query(table_logs)
return None return None
def __load_module_configuration(self) -> None:
"""### Load Module Configuration
"""
try:
# Build the default configuration model (Mandatory)
self.ModConfig = self.ModConfModel(param_exemple1='param value 1', param_exemple2=1)
# Sync the configuration with core configuration (Mandatory)
self.Base.db_sync_core_config(self.module_name, self.ModConfig)
return None
except TypeError as te:
self.Logs.critical(te)
def __update_configuration(self, param_key: str, param_value: str):
"""Update the local and core configuration
Args:
param_key (str): The parameter key
param_value (str): The parameter value
"""
self.Base.db_update_core_config(self.module_name, self.ModConfig, param_key, param_value)
def unload(self) -> None: def unload(self) -> None:
return None return None
def cmd(self, data:list) -> None: def cmd(self, data:list) -> None:
return None return None
def _hcmds(self, user:str, channel: any, cmd: list, fullcmd: list = []) -> None: def _hcmds(self, user:str, cmd: list, fullcmd: list = []) -> None:
command = str(cmd[0]).lower() command = str(cmd[0]).lower()
dnickname = self.Config.SERVICE_NICKNAME dnickname = self.Config.SERVICE_NICKNAME
fromuser = user fromuser = user
fromchannel = str(channel) if not channel is None else None
match command: match command:
case 'test-command': case 'test':
try: try:
self.Irc.send2socket(f":{dnickname} NOTICE {fromuser} : This is a notice to the sender ...") self.Irc.send2socket(f":{dnickname} NOTICE {fromuser} : test command ready ...")
self.Irc.send2socket(f":{dnickname} PRIVMSG {fromuser} : This is private message to the sender ...")
if not fromchannel is None:
self.Irc.send2socket(f":{dnickname} PRIVMSG {fromchannel} : This is channel message to the sender ...")
# How to update your module configuration
self.__update_configuration('param_exemple2', 7)
# Log if you want the result
self.Logs.debug(f"Test logs ready") self.Logs.debug(f"Test logs ready")
except KeyError as ke:
except Exception as err: self.Logs.error(f"Key Error : {ke}")
self.Logs.error(f"{err}")

View File

@@ -20,14 +20,10 @@ class Votekick():
voter_users: list voter_users: list
vote_for: int vote_for: int
vote_against: int vote_against: int
VOTE_CHANNEL_DB:list[VoteChannelModel] = [] VOTE_CHANNEL_DB:list[VoteChannelModel] = []
def __init__(self, ircInstance:Irc) -> None: def __init__(self, ircInstance:Irc) -> None:
# Module name (Mandatory)
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 = ircInstance
@@ -56,7 +52,7 @@ class Votekick():
self.__init_module() self.__init_module()
# Log the module # Log the module
self.Logs.debug(f'Module {self.module_name} loaded ...') self.Logs.debug(f'Module {self.__class__.__name__} loaded ...')
def __init_module(self) -> None: def __init_module(self) -> None:
@@ -147,7 +143,7 @@ class Votekick():
if not found: if not found:
self.VOTE_CHANNEL_DB.append(ChannelObject) self.VOTE_CHANNEL_DB.append(ChannelObject)
self.Logs.debug(f"The channel has been added {ChannelObject}") self.Logs.debug(f"The channel has been added {ChannelObject}")
# self.db_add_vote_channel(ChannelObject.channel_name) self.db_add_vote_channel(ChannelObject.channel_name)
return result return result
@@ -226,7 +222,7 @@ class Votekick():
self.Irc.send2socket(f":{dnickname} KICK {channel} {target_user} Following the vote, you are not welcome in {channel}") self.Irc.send2socket(f":{dnickname} KICK {channel} {target_user} Following the vote, you are not welcome in {channel}")
self.Channel.delete_user_from_channel(channel, self.User.get_uid(target_user)) self.Channel.delete_user_from_channel(channel, self.User.get_uid(target_user))
elif chan.vote_for <= chan.vote_against: elif chan.vote_for <= chan.vote_against:
self.Irc.send2socket(f':{dnickname} PRIVMSG {channel} :This user [{target_user}] will stay on this channel') self.Irc.send2socket(f':{dnickname} PRIVMSG {channel} :This user will stay on this channel')
# Init the system # Init the system
if self.init_vote_system(channel): if self.init_vote_system(channel):
@@ -245,14 +241,30 @@ class Votekick():
return None return None
def _hcmds(self, user:str, channel: any, cmd: list, fullcmd: list = []) -> None: def _hcmds(self, user:str, cmd: list, fullcmd: list = []) -> 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()
dnickname = self.Config.SERVICE_NICKNAME dnickname = self.Config.SERVICE_NICKNAME
fromuser = user fromuser = user
fromchannel = channel
if len(fullcmd) >= 3:
fromchannel = str(fullcmd[2]).lower() if self.Base.Is_Channel(str(fullcmd[2]).lower()) else None
else:
fromchannel = None
if len(cmd) >= 2:
sentchannel = str(cmd[1]).lower() if self.Base.Is_Channel(str(cmd[1]).lower()) else None
else:
sentchannel = None
if not fromchannel is None:
channel = fromchannel
elif not sentchannel is None:
channel = sentchannel
else:
channel = None
match command: match command:
@@ -274,7 +286,7 @@ class Votekick():
case 'vote_for': case 'vote_for':
try: try:
# vote_for # vote_for
channel = fromchannel channel = str(fullcmd[2]).lower()
for chan in self.VOTE_CHANNEL_DB: for chan in self.VOTE_CHANNEL_DB:
if chan.channel_name == channel: if chan.channel_name == channel:
if fromuser in chan.voter_users: if fromuser in chan.voter_users:
@@ -293,7 +305,7 @@ class Votekick():
case 'vote_against': case 'vote_against':
try: try:
# vote_against # vote_against
channel = fromchannel channel = str(fullcmd[2]).lower()
for chan in self.VOTE_CHANNEL_DB: for chan in self.VOTE_CHANNEL_DB:
if chan.channel_name == channel: if chan.channel_name == channel:
if fromuser in chan.voter_users: if fromuser in chan.voter_users:
@@ -359,9 +371,6 @@ class Votekick():
uid_cleaned = self.Base.clean_uid(uid_submitted) uid_cleaned = self.Base.clean_uid(uid_submitted)
ChannelInfo = self.Channel.get_Channel(channel) ChannelInfo = self.Channel.get_Channel(channel)
if ChannelInfo is None:
self.Irc.send2socket(f':{dnickname} NOTICE {fromuser} :This channel [{channel}] do not exist in the Channel Object')
return False
clean_uids_in_channel: list = [] clean_uids_in_channel: list = []
for uid in ChannelInfo.uids: for uid in ChannelInfo.uids:
@@ -395,13 +404,11 @@ class Votekick():
case 'activate': case 'activate':
try: try:
# activate #channel # activate #channel
sentchannel = str(cmd[1]).lower() if self.Base.Is_Channel(str(cmd[1]).lower()) else None # channel = str(cmd[1]).lower()
if sentchannel is None:
self.Irc.send2socket(f":{dnickname} NOTICE {fromuser} :The correct command is {self.Config.SERVICE_PREFIX}ACTIVATE #CHANNEL")
self.insert_vote_channel( self.insert_vote_channel(
self.VoteChannelModel( self.VoteChannelModel(
channel_name=sentchannel, channel_name=channel,
target_user='', target_user='',
voter_users=[], voter_users=[],
vote_for=0, vote_for=0,
@@ -409,11 +416,9 @@ class Votekick():
) )
) )
self.Base.db_query_channel('add', self.module_name, sentchannel) self.Irc.send2socket(f":{dnickname} JOIN {channel}")
self.Irc.send2socket(f":{dnickname} SAMODE {channel} +o {dnickname}")
self.Irc.send2socket(f":{dnickname} JOIN {sentchannel}") self.Irc.send2socket(f":{dnickname} PRIVMSG {channel} :You can now use !submit <nickname> to decide if he will stay or not on this channel ")
self.Irc.send2socket(f":{dnickname} SAMODE {sentchannel} +o {dnickname}")
self.Irc.send2socket(f":{dnickname} PRIVMSG {sentchannel} :You can now use !submit <nickname> to decide if he will stay or not on this channel ")
except KeyError as ke: except KeyError as ke:
self.Logs.error(f"Key Error : {ke}") self.Logs.error(f"Key Error : {ke}")
@@ -421,20 +426,16 @@ class Votekick():
case 'deactivate': case 'deactivate':
try: try:
# deactivate #channel # deactivate #channel
sentchannel = str(cmd[1]).lower() if self.Base.Is_Channel(str(cmd[1]).lower()) else None # channel = str(cmd[1]).lower()
if sentchannel is None:
self.Irc.send2socket(f":{dnickname} NOTICE {fromuser} :The correct command is {self.Config.SERVICE_PREFIX}DEACTIVATE #CHANNEL")
self.Irc.send2socket(f":{dnickname} SAMODE {sentchannel} -o {dnickname}") self.Irc.send2socket(f":{dnickname} SAMODE {channel} -o {dnickname}")
self.Irc.send2socket(f":{dnickname} PART {sentchannel}") self.Irc.send2socket(f":{dnickname} PART {channel}")
for chan in self.VOTE_CHANNEL_DB: for chan in self.VOTE_CHANNEL_DB:
if chan.channel_name == sentchannel: if chan.channel_name == channel:
self.VOTE_CHANNEL_DB.remove(chan) self.VOTE_CHANNEL_DB.remove(chan)
self.Base.db_query_channel('del', self.module_name, chan.channel_name) self.db_delete_vote_channel(chan.channel_name)
# self.db_delete_vote_channel(chan.channel_name)
self.Logs.debug(f"The Channel {sentchannel} has been deactivated from the vote system")
self.Logs.debug(f"Test logs ready")
except KeyError as ke: except KeyError as ke:
self.Logs.error(f"Key Error : {ke}") self.Logs.error(f"Key Error : {ke}")

View File

@@ -1,3 +1,3 @@
{ {
"version": "5.1.0" "version": "5.0.4"
} }