94 Commits

Author SHA1 Message Date
adator
2694d68cdc Merge pull request #98 from adator85/asyncio
Fix Asyncio update
2025-11-23 18:24:33 +01:00
adator
cbe527d7d9 Code Refactoring 2025-11-23 18:22:54 +01:00
adator
5938a1511b Fix asyncio unwaitable methods 2025-11-23 18:22:11 +01:00
adator
eb7c6ef8d0 Code refactoring 2025-11-23 18:20:56 +01:00
adator
4c93f85008 Code refactoring on system modules. 2025-11-23 18:19:29 +01:00
adator
226340e1aa Replace build_command location, code refactoring on irc 2025-11-23 16:15:10 +01:00
adator
d66d297a33 Fix await issue by Adding await to asyncio.sleep. replace build_command location 2025-11-23 16:12:58 +01:00
adator
9e688f7964 error 204 crash the RPC server. changing the 204 error to 404 when data not found 2025-11-23 00:53:03 +01:00
adator
18da187e2e Merge pull request #97 from adator85/asyncio
Fix some asyncio issues!
2025-11-21 00:56:24 +01:00
adator
b527282bf2 Fix some asyncio issues! 2025-11-21 00:55:49 +01:00
adator
2eb0fa5f5d Merge pull request #96 from adator85/asyncio
Asyncio
2025-11-20 16:18:17 +01:00
adator
6af1377823 Moving votekick to use asyncio, fix when shutdown defender 2025-11-20 16:17:27 +01:00
adator
fe4b68e115 Fix blocking Coroutines when shutdown! 2025-11-20 15:13:36 +01:00
adator
e9af3f9155 Unloading modules when stopping defender! 2025-11-20 14:07:20 +01:00
adator
3a8c7f0632 Updating unrealircd_rpc_py version 2025-11-20 14:06:40 +01:00
adator
1e72906f7b Handling the exception when IRCd is down! 2025-11-20 14:05:42 +01:00
adator
5b7c2e83d1 Adapt mod_jsonrpc to the new asyncio approach. 2025-11-20 14:04:19 +01:00
adator
51f709e4a1 Final main file 2025-11-20 14:02:45 +01:00
adator
aa15aea749 Introduce full asyncio version (still some module to migrate). Defender JSONRPC Server ready and using with uvcorn 2025-11-20 00:29:32 +01:00
adator85
1b30b1ff4e Update module interface... 2025-11-18 16:27:42 +01:00
adator
af992f7721 last changes for asyncio 2025-11-18 13:34:03 +01:00
adator
3926d7270d First commit asyncio 2025-11-16 22:09:40 +01:00
adator
c371910066 Creating JSONRPC Server 2025-11-16 19:13:26 +01:00
adator
2e422c93e5 Base.py refactoring. Nothing fancy. 2025-11-16 13:35:09 +01:00
adator
ba989b7f26 Update debug message. 2025-11-16 13:22:33 +01:00
adator
fc01de34b2 Adding new debug messages when module exist or not 2025-11-16 13:21:57 +01:00
adator
a3dcc20a06 Handle SETHOST response to update the vhost of the user 2025-11-16 13:20:11 +01:00
adator
7ffc58d4ff Trigger a thread clean-up before running a new thread 2025-11-16 13:15:49 +01:00
adator
6a0d4e2286 Updating some translation, refactoring the code! 2025-11-11 03:49:16 +01:00
adator
7dd15f2dac Updating translation! 2025-11-11 03:48:37 +01:00
adator
10cad7cda6 Refactoring code! 2025-11-11 03:48:20 +01:00
adator
999072a88a Refactoring unreal6 code! 2025-11-11 03:47:02 +01:00
adator
8932e1441a Update docstring of the test module. 2025-11-10 23:38:19 +01:00
adator
9cee758b6f Remove unused imported library! 2025-11-10 23:13:17 +01:00
adator
511e0c0715 Introduce MOD_HEADER in all modules. impact modules.py, definition.py, unreal6 protocol. 2025-11-10 23:09:13 +01:00
adator
371c8fb5f1 Exclude modules.txt files from the commit 2025-11-10 23:08:18 +01:00
adator
401e785383 Remove deprecated class (abstractproperty). 2025-11-10 00:15:53 +01:00
adator
a7efede75e Introduce MOD_HEADER constante in all modules as mandatory constante. 2025-11-10 00:13:35 +01:00
adator
a1254c7a39 Refactoring the Code, Dispatch server responses to all modules including UID, avoid multi calls to get_user, get_nickname... 2025-11-09 23:39:19 +01:00
adator
c05990f862 Refactoring the Code! 2025-11-09 23:37:42 +01:00
adator
de2b5fa8e2 Refactoring the Code, comment the code to dispatch the server response to all modules 2025-11-09 23:36:58 +01:00
adator
371645149d Refactoring the Code, clean unsused methods. avoid multi calls to get_user, get_nickname ... 2025-11-09 23:35:27 +01:00
adator
a6cf11ae2a Update the log level when userobj is not found! 2025-11-09 23:33:17 +01:00
adator
445cbc27b0 exclude users.txt file 2025-11-09 23:32:21 +01:00
adator
f9eb374798 When fp is None, return False. log when login via fingerprint. 2025-11-09 20:53:30 +01:00
adator
17cb2ada5f Fix server response, fix ircd parser aswell, update first setup on base.py 2025-11-08 21:21:38 +01:00
adator
b52a57f95a Fix library path in settings.py 2025-11-07 22:46:09 +01:00
adator
1bfd95c291 refactoring code 2025-11-06 20:12:28 +01:00
adator
0c6c3cd6ac Refactoring the code, create new folder modules. 2025-11-02 22:52:27 +01:00
adator
0e6384c26c modify and move protocol interface to interfaces folder. refactoring all dependencies 2025-11-02 22:21:55 +01:00
adator
79c1b94a92 Update parse_quit, now it returns MUser object and the reason. 2025-11-02 21:28:44 +01:00
adator
5a1432c1e6 Update parse_uid, now it returns MUser object. 2025-11-02 21:17:15 +01:00
adator
34b5b4204e Update parse_privmsg, now it returns sender, reciever, channel objects and the message 2025-11-02 20:58:56 +01:00
adator
ff58cbb022 Adding serveur_protocol to the configuration exemple. 2025-11-02 00:16:42 +01:00
adator
6450418859 Update docstring 2025-11-02 00:16:04 +01:00
adator
9f2da13f88 Unload the module when the protocol is not unreal6 2025-11-02 00:15:43 +01:00
adator
0117e1dd3a Update the protocol inspircd to work with the protocol interfaces. 2025-11-02 00:14:36 +01:00
adator
deb76baf30 Update the version of defender 6.3.2>>6.3.3 2025-11-01 22:24:57 +01:00
adator
29f049b3c3 Update the protocol interface, no more __init__ constructor needed in the child class! 2025-11-01 22:20:49 +01:00
adator
fb41a13d0a Move mod_clone to use the module interface. 2025-11-01 22:11:15 +01:00
adator
769ab8b632 update rest of modules to fit requirements 2025-11-01 22:00:08 +01:00
adator
2fbe75b83e update module management 2025-11-01 17:39:16 +01:00
adator
8abae5df3e fix db_patch, the table name is no more hard coded! 2025-11-01 15:57:46 +01:00
adator
1a71a6eb4d 1st version of module interface! 2025-10-30 00:35:50 +01:00
adator
b182aa8bcb Fix mod_jsonrpc module! Selecting the correct parameter based on the JSONRPC_METHOD value in the configruation.yaml 2025-10-29 00:02:57 +01:00
adator
e5a5f01603 Adding 'make update' to update from git repository. remove previous installation.py file, the update of packages is done via install.py file. 2025-10-28 23:33:43 +01:00
adator
99f8949681 Create Makefile installation; update copyright core command. TODO replace installation.py script. 2025-10-28 01:02:27 +01:00
adator
05b15f2f18 Merge pull request #95 from adator85/fix-install
Fix the previous configuration.json check in the installation file
2025-10-26 21:16:38 +01:00
adator
35c3faf68c Fix the previous configuration.json check in the installation file 2025-10-26 21:15:05 +01:00
adator
2e9bfd2c3b Merge pull request #94 from adator85/v6.3.2
V6.3.2
2025-10-26 21:10:39 +01:00
adator
80131b7b7a Update version.json 2025-10-26 21:10:19 +01:00
adator
ffb30f12ec Remove json configuration and replace it by yaml configuration files. 2025-10-26 21:00:50 +01:00
adator
b7b61081be Fix get_datetime call and update some docstring. 2025-10-25 00:10:32 +02:00
adator
030b706b65 Update the private message when fingerprint auth is used 2025-10-19 20:42:35 +02:00
adator
c428ea2b41 If no fingerprint available, cert command will trigger an error. 2025-10-19 12:33:30 +02:00
adator
9036e4f626 Merge pull request #93 from adator85/v6.3.0
update mod_jsonrpc and configuration file
2025-10-18 22:48:20 +02:00
adator
fd79ada13d update mod_jsonrpc and configuration file 2025-10-18 22:46:38 +02:00
adator
8323f6cc9b Merge pull request #92 from adator85/v6.3.0
V6.3.0
2025-10-18 20:54:35 +02:00
adator
6fcd553481 Merge branch 'main' into v6.3.0 2025-10-18 20:54:26 +02:00
adator
5cd82a174d remove .vscode/ folder 2025-10-18 20:53:35 +02:00
adator
beec16f39d Update to the 3.0.0 V 2025-10-18 20:49:39 +02:00
adator
a043a58f45 Update to the 3.0.0 V 2025-10-18 20:49:21 +02:00
adator
fd9643eddc Add command handler system. Starting adapt the modules to fit other protocls. 2025-09-09 22:37:41 +02:00
adator
ed1a048603 Merge pull request #91 from adator85/v6.2.5-fix
Fix reputation issue by adding tls_cipher in the datamodel
2025-09-03 22:10:21 +02:00
adator
3dfde9b1aa Fix reputation issue by adding tls_cipher in the datamodel 2025-09-03 22:09:29 +02:00
adator
5e35a10193 Merge pull request #90 from adator85/v6.2.5-fix
Fix reputation issue by adding tls_cipher in the datamodel
2025-09-03 22:06:15 +02:00
adator
ff776541d7 Fix reputation issue by adding tls_cipher in the datamodel 2025-09-03 22:05:34 +02:00
adator
6b7fd16a44 Connectecting to inspircd 2025-09-03 22:01:52 +02:00
adator
e79c15188e Quick updates:
- Set default language for admins when running the db patch
    - Updating addaccess command.
    - Update levels for some commands in mod_command.
2025-08-30 23:09:03 +02:00
adator
b306968115 Merge pull request #89 from adator85/v6.2.5
V6.2.5
2025-08-29 21:45:20 +02:00
adator
184e90adce New updates changelog:
- Update info command (mod_defender.py)
    - Update help on commands (mod_clone.py)
2025-08-29 21:43:22 +02:00
adator
c7b88150b5 New updates for v6.2.5:
- Adding tls_cipher to MUser, MAdmin and MClient
    - Switch parser command in Irc Instance (To Monitor closly)
    - New auth method in Admin.py
    - Adding the capability to auto auth Admins via their fingerprints
    - Update few core translation.
2025-08-27 00:52:48 +02:00
adator
02f0608b75 Adding current_admin to the global settings:
- New property current_admin added to the Settings.py
    - Fix also translation function
2025-08-26 01:41:15 +02:00
adator
25bbddf459 Adding language field to admin db and local variable 2025-08-25 23:22:50 +02:00
64 changed files with 6840 additions and 4730 deletions

8
.gitignore vendored
View File

@@ -1,9 +1,15 @@
.pyenv/
.vscode/
.venv/
.idea/
db/
logs/
__pycache__/
configuration.json
configuration.yaml
configuration_inspircd.json
configuration_unreal6.json
*.log
test.py
test.py
users.txt
modules.txt

65
Makefile Normal file
View File

@@ -0,0 +1,65 @@
OS := $(shell uname -s)
CURRENT_USER := $(shell whoami)
PYTHON_VERSION := $(shell python3 -V)
HOME_DIR := $(shell echo $$HOME)
SHELL := /bin/bash
install:
ifeq ($(wildcard config/configuration.yaml),)
$(error You must provide the Configuration file: config/configuration.yaml)
endif
ifeq ($(OS), Linux)
$(info Installation for os : $(OS))
$(info Python version: $(PYTHON_VERSION))
$(info Home directory: $(HOME_DIR))
@python3 core/install.py --check-version
@if [ $$? -eq 0 ]; then \
echo "Python Version OK! Well done :)"; \
else \
echo "Error: Script failed with exit code $$?"; \
exit 1; \
fi
$(info Creating the systemd user folder...)
mkdir -p $(HOME_DIR)/.config/systemd/user
$(info Creating Python Virtual Environment...)
python3 -m venv .pyenv
@. .pyenv/bin/activate && \
python -m pip install --upgrade pip && \
pip cache purge && \
pip install -r requirements.txt
@. .pyenv/bin/activate && python core/install.py --install
loginctl enable-linger $(CURRENT_USER)
@sleep 2
@export echo $DBUS_SESSION_BUS_ADDRESS && \
systemctl --user daemon-reload && \
systemctl --user start defender
endif
clean:
ifeq ($(OS), Linux)
@export echo $DBUS_SESSION_BUS_ADDRESS && \
systemctl --user stop defender
$(info Defender has been stopped...)
@if [ -e .pyenv ]; then \
rm -rf .pyenv; \
echo "Virtual Env has been removed!"; \
fi
@if [ -e $(HOME_DIR)/.config/systemd/user/defender.service ]; then \
rm $(HOME_DIR)/.config/systemd/user/defender.service; \
echo "Systemd file has been removed!"; \
fi
@export echo $DBUS_SESSION_BUS_ADDRESS && systemctl --user daemon-reload && echo "Systemd Daemon reloaded!"
endif
update:
ifeq ($(OS), Linux)
$(info Starting update from the main repository...)
@. .pyenv/bin/activate && python core/install.py --git-update
$(info Update done!)
endif

139
README.md
View File

@@ -34,10 +34,11 @@ Il permet aux opérateurs de gérer efficacement un canal, tout en offrant aux u
- Python version 3.10 ou supérieure
```bash
# Bash
$ git clone https://github.com/adator85/IRC_DEFENDER_MODULES.git
$ git clone https://github.com/adator85/DEFENDER.git defender
$ cd defender/
# Renommer le fichier exemple_configuration.json en configuration.json
# Configurer le fichier configuration.json
$ python3 main.py
$ make install
```
Si votre configuration est bonne, votre service est censé etre connecté a votre réseau IRC
Pour Les prochains lancement de defender vous devez utiliser la commande suivante:
@@ -49,11 +50,11 @@ Pour Les prochains lancement de defender vous devez utiliser la commande suivant
# Installation manuelle:
```bash
# Bash
$ git clone https://github.com/adator85/IRC_DEFENDER_MODULES.git
$ cd IRC_DEFENDER_MODULES
$ git clone https://github.com/adator85/DEFENDER.git defender
$ cd defender/
$ python3 -m venv .pyenv
$ source .pyenv/bin/activate
(pyenv)$ pip install sqlalchemy, psutil, requests, faker, unrealircd_rpc_py
(pyenv)$ pip install -r requirements.txt
# Créer un service nommé "defender.service"
# pour votre service et placer le dans "/PATH/TO/USER/.config/systemd/user/"
@@ -104,87 +105,91 @@ Pour Les prochains lancement de defender vous devez utiliser la commande suivant
GLINE_DURATION: Durée de bannissement temporaire d'un utilisateur en minutes. (default : "30")
DEBUG (Debug)
DEBUG_LEVEL: Niveau de verbosité des messages de debug (plus grand est le nombre, plus il y a d'informations). (default : 20) Pour une production
DEBUG_LEVEL: Niveau de verbosité des messages de debug (plus petit est le nombre, plus il y a d'informations). (default : 20) Pour une production
DEBUG_HARD: Généralement utiliser pour les developpeurs.
```
Modification de la configuration
Vous devez modifier le fichier configuration.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.
Vous devez modifier le fichier configuration.yaml 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.
## Exemple de configuration de base
```json
{
"SERVEUR_IP": "IP.DE.TON.SERVER",
"SERVEUR_HOSTNAME": "HOST.DE.TON.SERVER",
"SERVEUR_LINK": "LINK.DE.TON.SERVER",
"SERVEUR_PORT": 6901,
"SERVEUR_PASSWORD": "MOT_DE_PASS_DE_TON_LINK",
"SERVEUR_ID": "10Z",
"SERVEUR_SSL": true,
```yaml
configuration:
SERVEUR_IP: "YOUR.SERVER.IP"
SERVEUR_HOSTNAME: "YOUR.SERVER.HOST"
SERVEUR_LINK: "LINK.DE.TON.SERVER"
SERVEUR_PORT: 7002
SERVEUR_PASSWORD: "YOUR_LINK_PASSWORD"
SERVEUR_ID: "006"
SERVEUR_SSL: true
"SERVICE_NAME": "defender",
"SERVICE_NICKNAME": "PyDefender",
"SERVICE_REALNAME": "Python Defender Security",
"SERVICE_USERNAME": "PyDefender",
"SERVICE_HOST": "HOST.DE.TON.DEFENDER",
SERVICE_NAME: "defender"
SERVICE_NICKNAME: "PyDefender"
SERVICE_REALNAME: "Python Defender Security"
SERVICE_USERNAME: "PyDefender"
SERVICE_HOST: "HOST.DE.TON.DEFENDER"
SERVICE_INFO: "Network IRC Service"
SERVICE_CHANLOG: "#services"
SERVICE_SMODES: "+ioqBS"
SERVICE_CMODES: "ntsOP"
SERVICE_UMODES: "o"
SERVICE_PREFIX: "!"
"OWNER": "TON_NICK_NAME",
"PASSWORD": "TON_PASSWORD"
}
OWNER: "TON_NICK_NAME"
PASSWORD: "TON_PASSWORD"
```
## Exemple complet de configuration
```json
{
"SERVEUR_IP": "YOUR.SERVER.IP",
"SERVEUR_HOSTNAME": "YOUR.SERVER.HOST",
"SERVEUR_LINK": "LINK.DE.TON.SERVER",
"SERVEUR_PORT": 6901,
"SERVEUR_PASSWORD": "YOUR_LINK_PASSWORD",
"SERVEUR_ID": "10Z",
"SERVEUR_SSL": true,
```yaml
configuration:
SERVEUR_IP: "YOUR.SERVER.IP"
SERVEUR_HOSTNAME: "YOUR.SERVER.HOST"
SERVEUR_LINK: "LINK.DE.TON.SERVER"
SERVEUR_PORT: 7002
SERVEUR_PASSWORD: "YOUR_LINK_PASSWORD"
SERVEUR_ID: "006"
SERVEUR_SSL: true
"SERVICE_NAME": "defender",
"SERVICE_NICKNAME": "PyDefender",
"SERVICE_REALNAME": "Python Defender Security",
"SERVICE_USERNAME": "PyDefender",
"SERVICE_HOST": "HOST.DE.TON.DEFENDER",
"SERVICE_INFO": "Network IRC Service",
"SERVICE_CHANLOG": "#services",
"SERVICE_SMODES": "+ioqBS",
"SERVICE_CMODES": "ntsOP",
"SERVICE_UMODES": "o",
"SERVICE_PREFIX": "!",
SERVICE_NAME: "defender"
SERVICE_NICKNAME: "PyDefender"
SERVICE_REALNAME: "Python Defender Security"
SERVICE_USERNAME: "PyDefender"
SERVICE_HOST: "HOST.DE.TON.DEFENDER"
SERVICE_INFO: "Network IRC Service"
SERVICE_CHANLOG: "#services"
SERVICE_SMODES: "+ioqBS"
SERVICE_CMODES: "ntsOP"
SERVICE_UMODES: "o"
SERVICE_PREFIX: "!"
"OWNER": "TON_NICK_NAME",
"PASSWORD": "TON_PASSWORD",
OWNER: "TON_NICK_NAME"
PASSWORD: "TON_PASSWORD"
"JSONRPC_URL": "https://your.domaine.com:8600/api",
"JSONRPC_PATH_TO_SOCKET_FILE": "/PATH/TO/YOUR/IRCD/data/rpc.socket",
"JSONRPC_METHOD": "socket",
"JSONRPC_USER": "YOUR_RPC_USER",
"JSONRPC_PASSWORD": "YOUR_RPC_PASSWORD",
JSONRPC_URL: "https://your.domaine.com:8600/api"
JSONRPC_PATH_TO_SOCKET_FILE: "/PATH/TO/YOUR/IRCD/data/rpc.socket"
JSONRPC_METHOD: "unixsocket"
JSONRPC_USER: "YOUR_RPC_USER"
JSONRPC_PASSWORD: "YOUR_RPC_PASSWORD"
"SALON_JAIL": "#jail",
"SALON_JAIL_MODES": "sS",
"SALON_LIBERER": "#welcome",
SALON_JAIL: "#jail"
SALON_JAIL_MODES: "sS"
SALON_LIBERER: "#welcome"
"CLONE_CHANNEL": "#clones",
"CLONE_CMODES": "+nts",
"CLONE_LOG_HOST_EXEMPT": ["HOST.TO.SKIP"],
"CLONE_CHANNEL_PASSWORD": "YOUR_CHANNEL_PASSWORD",
CLONE_CHANNEL: "#clones"
CLONE_CMODES: "+nts"
CLONE_LOG_HOST_EXEMPT: ["HOST.TO.SKIP"]
CLONE_CHANNEL_PASSWORD: "YOUR_CHANNEL_PASSWORD"
"API_TIMEOUT": 2,
API_TIMEOUT: 2
"PORTS_TO_SCAN": [3028, 8080, 1080, 1085, 4145, 9050],
"WHITELISTED_IP": ["127.0.0.1"],
"GLINE_DURATION": "30",
PORTS_TO_SCAN: [3028 8080 1080 1085 4145 9050]
WHITELISTED_IP: ["127.0.0.1"]
GLINE_DURATION: "30"
"DEBUG_LEVEL": 20
}
DEBUG_LEVEL: 20
DEBUG_HARD: true
```
# \\!/ Attention \\!/
@@ -192,7 +197,7 @@ Le mot de passe de l'administrateur et le mot de passe du service doivent être
Ne partagez pas vos informations de connexion au serveur IRC avec des tiers.
a votre premiere connexion vous devez tapez
```
/msg [NomDuService] auth [nickname] [password]
/msg [NomDuService] firstauth [nickname] [password]
-- Une fois identifié tapez la commande suivante
/msg [NomDuService] editaccess [nickname] [Nouveau-Password] 5
```

View File

@@ -1,48 +0,0 @@
{
"SERVEUR_IP": "YOUR.SERVER.IP",
"SERVEUR_HOSTNAME": "YOUR.SERVER.HOST",
"SERVEUR_LINK": "LINK.DE.TON.SERVER",
"SERVEUR_PORT": 7002,
"SERVEUR_PASSWORD": "YOUR_LINK_PASSWORD",
"SERVEUR_ID": "006",
"SERVEUR_SSL": true,
"SERVICE_NAME": "defender",
"SERVICE_NICKNAME": "PyDefender",
"SERVICE_REALNAME": "Python Defender Security",
"SERVICE_USERNAME": "PyDefender",
"SERVICE_HOST": "HOST.DE.TON.DEFENDER",
"SERVICE_INFO": "Network IRC Service",
"SERVICE_CHANLOG": "#services",
"SERVICE_SMODES": "+ioqBS",
"SERVICE_CMODES": "ntsOP",
"SERVICE_UMODES": "o",
"SERVICE_PREFIX": "!",
"OWNER": "TON_NICK_NAME",
"PASSWORD": "TON_PASSWORD",
"JSONRPC_URL": "https://your.domaine.com:8600/api",
"JSONRPC_PATH_TO_SOCKET_FILE": "/PATH/TO/YOUR/IRCD/data/rpc.socket",
"JSONRPC_METHOD": "socket",
"JSONRPC_USER": "YOUR_RPC_USER",
"JSONRPC_PASSWORD": "YOUR_RPC_PASSWORD",
"SALON_JAIL": "#jail",
"SALON_JAIL_MODES": "sS",
"SALON_LIBERER": "#welcome",
"CLONE_CHANNEL": "#clones",
"CLONE_CMODES": "+nts",
"CLONE_LOG_HOST_EXEMPT": ["HOST.TO.SKIP"],
"CLONE_CHANNEL_PASSWORD": "YOUR_CHANNEL_PASSWORD",
"API_TIMEOUT": 2,
"PORTS_TO_SCAN": [3028, 8080, 1080, 1085, 4145, 9050],
"WHITELISTED_IP": ["127.0.0.1"],
"GLINE_DURATION": "30",
"DEBUG_LEVEL": 20
}

View File

@@ -0,0 +1,48 @@
configuration:
SERVEUR_IP: "YOUR.SERVER.IP"
SERVEUR_HOSTNAME: "YOUR.SERVER.HOST"
SERVEUR_LINK: "LINK.DE.TON.SERVER"
SERVEUR_PORT: 7002
SERVEUR_PASSWORD: "YOUR_LINK_PASSWORD"
SERVEUR_ID: "006"
SERVEUR_SSL: true
SERVEUR_PROTOCOL: "unreal6" # unreal6 or inspircd
SERVICE_NAME: "defender"
SERVICE_NICKNAME: "PyDefender"
SERVICE_REALNAME: "Python Defender Security"
SERVICE_USERNAME: "PyDefender"
SERVICE_HOST: "HOST.DE.TON.DEFENDER"
SERVICE_INFO: "Network IRC Service"
SERVICE_CHANLOG: "#services"
SERVICE_SMODES: "+ioqBS"
SERVICE_CMODES: "ntsOP"
SERVICE_UMODES: "o"
SERVICE_PREFIX: "!"
OWNER: "TON_NICK_NAME"
PASSWORD: "TON_PASSWORD"
JSONRPC_URL: "https://your.domaine.com:8600/api"
JSONRPC_PATH_TO_SOCKET_FILE: "/PATH/TO/YOUR/IRCD/data/rpc.socket"
JSONRPC_METHOD: "unixsocket"
JSONRPC_USER: "YOUR_RPC_USER"
JSONRPC_PASSWORD: "YOUR_RPC_PASSWORD"
SALON_JAIL: "#jail"
SALON_JAIL_MODES: "sS"
SALON_LIBERER: "#welcome"
CLONE_CHANNEL: "#clones"
CLONE_CMODES: "+nts"
CLONE_LOG_HOST_EXEMPT: ["HOST.TO.SKIP"]
CLONE_CHANNEL_PASSWORD: "YOUR_CHANNEL_PASSWORD"
API_TIMEOUT: 2
PORTS_TO_SCAN: [3028, 8080, 1080, 1085, 4145, 9050]
WHITELISTED_IP: ["127.0.0.1", "192.168.1.1"]
GLINE_DURATION: "30"
DEBUG_LEVEL: 20
DEBUG_HARD: true

View File

@@ -0,0 +1 @@
__version__ = '6.3'

View File

@@ -1,21 +1,19 @@
import importlib
import asyncio
import os
import re
import json
import sys
import time
import socket
import threading
import ipaddress
import ast
import requests
from pathlib import Path
from types import ModuleType
import concurrent.futures
from dataclasses import fields
from typing import Any, Optional, TYPE_CHECKING
from typing import Any, Awaitable, Callable, Optional, TYPE_CHECKING, Union
from base64 import b64decode, b64encode
from sqlalchemy import create_engine, Engine, Connection, CursorResult
from sqlalchemy.sql import text
import core.definition as dfn
if TYPE_CHECKING:
from core.loader import Loader
@@ -30,7 +28,8 @@ class Base:
self.Utils = loader.Utils
self.logs = loader.Logs
self.check_for_new_version(True) # Verifier si une nouvelle version est disponible
# Check if new Defender version is available
self.check_for_new_version(True)
# Liste des timers en cours
self.running_timers: list[threading.Timer] = self.Settings.RUNNING_TIMERS
@@ -41,15 +40,26 @@ class Base:
# Les sockets ouvert
self.running_sockets: list[socket.socket] = self.Settings.RUNNING_SOCKETS
# List of all asyncio tasks
self.running_iotasks: list[asyncio.Task] = self.Settings.RUNNING_ASYNC_TASKS
# List of all asyncio threads pool executors
self.running_iothreads: list[dfn.MThread] = self.Settings.RUNNING_ASYNC_THREADS
# Liste des fonctions en attentes
self.periodic_func: dict[object] = self.Settings.PERIODIC_FUNC
# Création du lock
self.lock = self.Settings.LOCK
# Init install variable
self.install: bool = False
# Init database connection
self.engine, self.cursor = self.db_init()
# Create the database
# self.__create_db()
self.install: bool = False # Initialisation de la variable d'installation
self.engine, self.cursor = self.db_init() # Initialisation de la connexion a la base de données
self.__create_db() # Initialisation de la base de données
async def init(self) -> None:
await self.__create_db()
def __set_current_defender_version(self) -> None:
"""This will put the current version of Defender
@@ -65,32 +75,32 @@ class Base:
return None
def __get_latest_defender_version(self) -> None:
try:
self.logs.debug(f'-- Looking for a new version available on Github')
token = ''
json_url = f'https://raw.githubusercontent.com/adator85/DEFENDER/main/version.json'
headers = {
'Authorization': f'token {token}',
'Accept': 'application/vnd.github.v3.raw' # Indique à GitHub que nous voulons le contenu brut du fichier
}
self.logs.debug(f'-- Looking for a new version available on Github')
token = ''
json_url = f'https://raw.githubusercontent.com/adator85/DEFENDER/main/version.json'
headers = {
'Authorization': f'token {token}',
'Accept': 'application/vnd.github.v3.raw' # Indique à GitHub que nous voulons le contenu brut du fichier
}
if token == '':
response = requests.get(json_url, timeout=self.Config.API_TIMEOUT)
else:
response = requests.get(json_url, headers=headers, timeout=self.Config.API_TIMEOUT)
with requests.Session() as sess:
try:
if token == '':
response = sess.get(json_url, timeout=self.Config.API_TIMEOUT)
else:
response = sess.get(json_url, headers=headers, timeout=self.Config.API_TIMEOUT)
response.raise_for_status() # Vérifie si la requête a réussi
json_response:dict = response.json()
# self.LATEST_DEFENDER_VERSION = json_response["version"]
self.Config.LATEST_VERSION = json_response['version']
response.raise_for_status() # Vérifie si la requête a réussi
json_response:dict = response.json()
self.Config.LATEST_VERSION = json_response.get('version', '')
return None
return None
except requests.HTTPError as err:
self.logs.error(f'Github not available to fetch latest version: {err}')
except:
self.logs.warning(f'Github not available to fetch latest version')
except requests.HTTPError as err:
self.logs.error(f'Github not available to fetch latest version: {err}')
except:
self.logs.warning(f'Github not available to fetch latest version')
def check_for_new_version(self, online:bool) -> bool:
def check_for_new_version(self, online: bool) -> bool:
"""Check if there is a new version available
Args:
@@ -136,7 +146,7 @@ class Base:
except Exception as err:
self.logs.error(f'General Error: {err}')
def create_log(self, log_message: str) -> None:
async def create_log(self, log_message: str) -> None:
"""Enregiste les logs
Args:
@@ -147,11 +157,11 @@ class Base:
"""
sql_insert = f"INSERT INTO {self.Config.TABLE_LOG} (datetime, server_msg) VALUES (:datetime, :server_msg)"
mes_donnees = {'datetime': str(self.Utils.get_sdatetime()),'server_msg': f'{log_message}'}
self.db_execute_query(sql_insert, mes_donnees)
await self.db_execute_query(sql_insert, mes_donnees)
return None
def log_cmd(self, user_cmd: str, cmd: str) -> None:
async def log_cmd(self, user_cmd: str, cmd: str) -> None:
"""Enregistre les commandes envoyées par les utilisateurs
Args:
@@ -167,11 +177,11 @@ class Base:
insert_cmd_query = f"INSERT INTO {self.Config.TABLE_COMMAND} (datetime, user, commande) VALUES (:datetime, :user, :commande)"
mes_donnees = {'datetime': self.Utils.get_sdatetime(), 'user': user_cmd, 'commande': cmd}
self.db_execute_query(insert_cmd_query, mes_donnees)
await self.db_execute_query(insert_cmd_query, mes_donnees)
return None
def db_sync_core_config(self, module_name: str, dataclassObj: object) -> bool:
async 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
@@ -198,7 +208,7 @@ class Base:
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)
excecute_search_query = await self.db_execute_query(search_query, param_to_search)
result_search_query = excecute_search_query.fetchone()
if result_search_query is None:
@@ -210,7 +220,7 @@ class Base:
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)
execution = await 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}')
@@ -218,14 +228,14 @@ class Base:
# 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)
execute_query_select = await 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)
execute_delete = await 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}')
@@ -233,7 +243,7 @@ class Base:
# 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)
response = await self.db_execute_query(query, parameter)
result = response.fetchall()
for param, value in result:
@@ -250,43 +260,43 @@ class Base:
self.logs.error(err)
return False
def db_update_core_config(self, module_name:str, dataclassObj: object, param_key:str, param_value: str) -> bool:
async def db_update_core_config(self, module_name:str, dataclass_obj: object, param_key:str, param_value: str) -> bool:
core_table = self.Config.TABLE_CONFIG
# Check if the param exist
if not hasattr(dataclassObj, param_key):
if not hasattr(dataclass_obj, 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()
result = await self.db_execute_query(search_param_query, mes_donnees)
is_param_exist = result.fetchone()
if not isParamExist is None:
if not is_param_exist is None:
mes_donnees = {'datetime': self.Utils.get_sdatetime(),
'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)
update = await 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))
setattr(dataclass_obj, param_key, self.int_if_possible(param_value))
self.logs.debug(f'Parameter updated : {param_key} - {param_value} | Module: {module_name}')
else:
self.logs.error(f'Parameter NOT updated : {param_key} - {param_value} | Module: {module_name}')
else:
self.logs.error(f'Parameter and Module do not exist: Param ({param_key}) - Value ({param_value}) | Module ({module_name})')
self.logs.debug(dataclassObj)
self.logs.debug(dataclass_obj)
return True
def db_create_first_admin(self) -> None:
async def db_create_first_admin(self) -> None:
user = self.db_execute_query(f"SELECT id FROM {self.Config.TABLE_ADMIN}")
user = await self.db_execute_query(f"SELECT id FROM {self.Config.TABLE_ADMIN}")
if not user.fetchall():
admin = self.Config.OWNER
password = self.Utils.hash_password(self.Config.PASSWORD)
@@ -296,13 +306,14 @@ class Base:
'password': password,
'hostname': '*',
'vhost': '*',
'language': 'EN',
'level': 5
}
self.db_execute_query(f"""
await self.db_execute_query(f"""
INSERT INTO {self.Config.TABLE_ADMIN}
(createdOn, user, password, hostname, vhost, level)
(createdOn, user, password, hostname, vhost, language, level)
VALUES
(:createdOn, :user, :password, :hostname, :vhost, :level)"""
(:createdOn, :user, :password, :hostname, :vhost, :language, :level)"""
, mes_donnees)
return None
@@ -324,7 +335,7 @@ class Base:
self.logs.error(f'Assertion Error -> {ae}')
return None
def create_thread(self, func:object, func_args: tuple = (), run_once:bool = False, daemon: bool = True) -> None:
def create_thread(self, func: object, func_args: tuple = (), run_once: bool = False, daemon: bool = True) -> None:
"""Create a new thread and store it into running_threads variable
Args:
@@ -333,6 +344,9 @@ class Base:
run_once (bool, optional): If you want to ensure that this method/function run once. Defaults to False.
"""
try:
# Clean unused threads first
self.garbage_collector_thread()
func_name = func.__name__
if run_once:
@@ -346,8 +360,94 @@ class Base:
self.running_threads.append(th)
self.logs.debug(f"-- Thread ID : {str(th.ident)} | Thread name : {th.name} | Running Threads : {len(threading.enumerate())}")
except AssertionError as ae:
self.logs.error(f'{ae}')
except Exception as err:
self.logs.error(err, exc_info=True)
def create_asynctask(self, func: Callable[..., Awaitable[Any]], *, async_name: str = None, run_once: bool = False) -> Optional[asyncio.Task]:
"""Create a new asynchrone and store it into running_iotasks variable
Args:
func (Callable): The function you want to call in asynchrone way
async_name (str, optional): The task name. Defaults to None.
run_once (bool, optional): If true the task will be run once. Defaults to False.
Returns:
asyncio.Task: The Task
"""
name = func.__name__ if async_name is None else async_name
if run_once:
for task in asyncio.all_tasks():
if task.get_name().lower() == async_name.lower():
return None
task = asyncio.create_task(func, name=name)
task.add_done_callback(self.asynctask_done)
self.running_iotasks.append(task)
self.logs.debug(f"=== New IO task created as: {task.get_name()}")
return task
async def create_thread_io(self, func: Callable[..., Any], *args, run_once: bool = False, thread_flag: bool = False) -> Optional[Any]:
"""Run threads via asyncio.
Args:
func (Callable[..., Any]): The blocking IO function
run_once (bool, optional): If it should be run once.. Defaults to False.
thread_flag (bool, optional): If you are using a endless loop, use the threading Event object. Defaults to False.
Returns:
Any: The final result of the blocking IO function
"""
if run_once:
for iothread in self.running_iothreads:
if func.__name__.lower() == iothread.name.lower():
return None
with concurrent.futures.ThreadPoolExecutor(max_workers=1, thread_name_prefix=func.__name__) as executor:
loop = asyncio.get_event_loop()
largs = list(args)
thread_event: Optional[threading.Event] = None
if thread_flag:
thread_event = threading.Event()
thread_event.set()
largs.insert(0, thread_event)
future = loop.run_in_executor(executor, func, *tuple(largs))
future.add_done_callback(self.asynctask_done)
id_obj = self.Loader.Definition.MThread(
name=func.__name__,
thread_id=list(executor._threads)[0].native_id,
thread_event=thread_event,
thread_obj=list(executor._threads)[0],
executor=executor,
future=future)
self.running_iothreads.append(id_obj)
self.logs.debug(f"=== New thread started {func.__name__} with max workers set to: {executor._max_workers}")
result = await future
self.running_iothreads.remove(id_obj)
return result
def asynctask_done(self, task: Union[asyncio.Task, asyncio.Future]):
"""Log task when done
Args:
task (asyncio.Task): The Asyncio Task callback
"""
name = task.get_name() if isinstance(task, asyncio.Task) else "Thread"
task_or_future = "Task" if isinstance(task, asyncio.Task) else "Future"
try:
if task.exception():
self.logs.error(f"[ASYNCIO] {task_or_future} {name} failed with exception: {task.exception()}")
else:
self.logs.debug(f"[ASYNCIO] {task_or_future} {name} completed successfully.")
except asyncio.CancelledError as ce:
self.logs.debug(f"[ASYNCIO] {task_or_future} {name} terminated with cancelled error. {ce}")
except asyncio.InvalidStateError as ie:
self.logs.debug(f"[ASYNCIO] {task_or_future} {name} terminated with invalid state error. {ie}")
def is_thread_alive(self, thread_name: str) -> bool:
"""Check if the thread is still running! using the is_alive method of Threads.
@@ -392,7 +492,7 @@ class Base:
Returns:
int: Number of threads
"""
with self.lock:
with self.Settings.LOCK:
count = 0
for thr in self.running_threads:
@@ -424,11 +524,11 @@ class Base:
if thread.name != 'heartbeat':
if not thread.is_alive():
self.running_threads.remove(thread)
self.logs.info(f"-- Thread {str(thread.name)} {str(thread.native_id)} removed")
self.logs.debug(f"-- Thread {str(thread.name)} {str(thread.native_id)} has been removed!")
# print(threading.enumerate())
except AssertionError as ae:
self.logs.error(f'Assertion Error -> {ae}')
except Exception as err:
self.logs.error(err, exc_info=True)
def garbage_collector_sockets(self) -> None:
@@ -441,38 +541,6 @@ class Base:
self.running_sockets.remove(soc)
self.logs.debug(f"-- Socket ==> closed {str(soc.fileno())}")
def shutdown(self) -> None:
"""Methode qui va préparer l'arrêt complêt du service
"""
# Nettoyage des timers
self.logs.debug(f"=======> Checking for Timers to stop")
for timer in self.running_timers:
while timer.is_alive():
self.logs.debug(f"> waiting for {timer.name} to close")
timer.cancel()
time.sleep(0.2)
self.running_timers.remove(timer)
self.logs.debug(f"> Cancelling {timer.name} {timer.native_id}")
self.logs.debug(f"=======> Checking for Threads to stop")
for thread in self.running_threads:
if thread.name == 'heartbeat' and thread.is_alive():
self.execute_periodic_action()
self.logs.debug(f"> Running the last periodic action")
self.running_threads.remove(thread)
self.logs.debug(f"> Cancelling {thread.name} {thread.native_id}")
self.logs.debug(f"=======> Checking for Sockets to stop")
for soc in self.running_sockets:
soc.close()
while soc.fileno() != -1:
soc.close()
self.running_sockets.remove(soc)
self.logs.debug(f"> Socket ==> closed {str(soc.fileno())}")
return None
def db_init(self) -> tuple[Engine, Connection]:
db_directory = self.Config.DB_PATH
@@ -484,10 +552,10 @@ class Base:
engine = create_engine(f'sqlite:///{full_path_db}.db', echo=False)
cursor = engine.connect()
self.logs.info("-- database connexion has been initiated")
self.logs.info("-- Database connexion has been initiated")
return engine, cursor
def __create_db(self) -> None:
async def __create_db(self) -> None:
table_core_log = f'''CREATE TABLE IF NOT EXISTS {self.Config.TABLE_LOG} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -538,6 +606,7 @@ class Base:
vhost TEXT,
password TEXT,
fingerprint TEXT,
language TEXT,
level INTEGER
)
'''
@@ -556,27 +625,27 @@ class Base:
)
'''
self.db_execute_query(table_core_log)
self.db_execute_query(table_core_log_command)
self.db_execute_query(table_core_module)
self.db_execute_query(table_core_admin)
self.db_execute_query(table_core_client)
self.db_execute_query(table_core_channel)
self.db_execute_query(table_core_config)
await self.db_execute_query(table_core_log)
await self.db_execute_query(table_core_log_command)
await self.db_execute_query(table_core_module)
await self.db_execute_query(table_core_admin)
await self.db_execute_query(table_core_client)
await self.db_execute_query(table_core_channel)
await self.db_execute_query(table_core_config)
# Patch database
self.db_patch(self.Config.TABLE_ADMIN, "language", "TEXT")
await self.db_patch(self.Config.TABLE_ADMIN, "language", "TEXT")
if self.install:
self.Loader.ModuleUtils.db_register_module('mod_command', 'sys', True)
self.Loader.ModuleUtils.db_register_module('mod_defender', 'sys', True)
await self.Loader.ModuleUtils.db_register_module('mod_command', 'sys', True)
await self.Loader.ModuleUtils.db_register_module('mod_defender', 'sys', True)
self.install = False
return None
def db_execute_query(self, query:str, params:dict = {}) -> CursorResult:
async def db_execute_query(self, query:str, params:dict = {}) -> CursorResult:
with self.lock:
async with self.Loader.Settings.AILOCK:
insert_query = text(query)
if not params:
response = self.cursor.execute(insert_query)
@@ -587,8 +656,8 @@ class Base:
return response
def db_is_column_exist(self, table_name: str, column_name: str) -> bool:
q = self.db_execute_query(f"PRAGMA table_info({table_name})")
async def db_is_column_exist(self, table_name: str, column_name: str) -> bool:
q = await self.db_execute_query(f"PRAGMA table_info({table_name})")
existing_columns = [col[1] for col in q.fetchall()]
if column_name in existing_columns:
@@ -596,10 +665,12 @@ class Base:
else:
return False
def db_patch(self, table_name: str, column_name: str, column_type: str) -> bool:
if not self.db_is_column_exist(table_name, column_name):
patch = f"ALTER TABLE {self.Config.TABLE_ADMIN} ADD COLUMN {column_name} {column_type}"
self.db_execute_query(patch)
async def db_patch(self, table_name: str, column_name: str, column_type: str) -> bool:
if not await self.db_is_column_exist(table_name, column_name):
patch = f"ALTER TABLE {table_name} ADD COLUMN {column_name} {column_type}"
update_row = f"UPDATE {table_name} SET language = 'EN' WHERE language is null"
await self.db_execute_query(patch)
await self.db_execute_query(update_row)
self.logs.debug(f"The patch has been applied")
self.logs.debug(f"Table name: {table_name}, Column name: {column_name}, Column type: {column_type}")
return True
@@ -607,9 +678,9 @@ class Base:
return False
def db_close(self) -> None:
try:
self.cursor.close()
self.logs.debug("Database engine closed!")
except AttributeError as ae:
self.logs.error(f"Attribute Error : {ae}")
@@ -680,14 +751,13 @@ class Base:
return False
def decode_ip(self, ip_b64encoded: str) -> Optional[str]:
binary_ip = b64decode(ip_b64encoded)
try:
binary_ip = b64decode(ip_b64encoded)
decoded_ip = ipaddress.ip_address(binary_ip)
return decoded_ip.exploded
except ValueError as ve:
self.logs.critical(f'This remote ip is not valid : {ve}')
self.logs.critical(f'This remote ip ({ip_b64encoded}) is not valid : {ve}')
return None
def encode_ip(self, remote_ip_address: str) -> Optional[str]:

View File

@@ -1,64 +0,0 @@
from json import load
from sys import exit
from os import sep
from typing import Any, Optional, Union, TYPE_CHECKING
from core.definition import MConfig
if TYPE_CHECKING:
from core.loader import Loader
class Configuration:
def __init__(self, loader: 'Loader') -> None:
self.Loader = loader
self.Logs = loader.Logs
self._config_model: MConfig = self.__load_service_configuration()
loader.ServiceLogging.set_file_handler_level(self._config_model.DEBUG_LEVEL)
loader.ServiceLogging.set_stdout_handler_level(self._config_model.DEBUG_LEVEL)
loader.ServiceLogging.update_handler_format(self._config_model.DEBUG_HARD)
return None
def get_config_model(self) -> MConfig:
return self._config_model
def __load_json_service_configuration(self) -> Optional[dict[str, Any]]:
try:
conf_filename = f'config{sep}configuration.json'
with open(conf_filename, 'r') as configuration_data:
configuration: dict[str, Union[str, int, list, dict]] = load(configuration_data)
return configuration
except FileNotFoundError as fe:
self.Logs.error(f'FileNotFound: {fe}')
self.Logs.error('Configuration file not found please create config/configuration.json')
exit(0)
except KeyError as ke:
self.Logs.error(f'Key Error: {ke}')
self.Logs.error('The key must be defined in core/configuration.json')
def __load_service_configuration(self) -> MConfig:
try:
import_config = self.__load_json_service_configuration()
Model_keys = MConfig().to_dict()
model_key_list: list = []
json_config_key_list: list = []
for key in Model_keys:
model_key_list.append(key)
for key in import_config:
json_config_key_list.append(key)
for json_conf in json_config_key_list:
if not json_conf in model_key_list:
import_config.pop(json_conf, None)
self.Logs.warning(f"[!] The key {json_conf} is not expected, it has been removed from the system ! please remove it from configuration.json file [!]")
self.Logs.debug(f"[LOADING CONFIGURATION]: Loading configuration with {len(import_config)} parameters!")
return MConfig(**import_config)
except TypeError as te:
self.Logs.error(te)

View File

@@ -0,0 +1,84 @@
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Optional, Union
from dataclasses import dataclass
if TYPE_CHECKING:
from core.loader import Loader
class IModule(ABC):
@abstractmethod
@dataclass
class ModConfModel:
"""The Model containing the module parameters
"""
def __init__(self, uplink: 'Loader') -> None:
# import the context
self.ctx = uplink
# Module name (Mandatory)
self.module_name = 'mod_' + str(self.__class__.__name__).lower()
# Log the module
self.ctx.Logs.debug(f'Loading Module {self.module_name} ...')
async def sync_db(self) -> None:
# Sync the configuration with core configuration (Mandatory)
await self.ctx.Base.db_sync_core_config(self.module_name, self.mod_config)
return None
async def update_configuration(self, param_key: str, param_value: Union[str, int]) -> None:
"""Update the local and core configuration
Args:
param_key (str): The parameter key
param_value (str): The parameter value
"""
await self.ctx.Base.db_update_core_config(self.module_name, self.mod_config, param_key, param_value)
@property
@abstractmethod
def mod_config(self) -> ModConfModel:
"""
The module configuration model
"""
@abstractmethod
def create_tables(self) -> None:
"""Method that will create the database if it does not exist.
A single Session for this class will be created, which will be used within this class/module.
Returns:
None: No return is expected
"""
@abstractmethod
async def load(self) -> None:
"""This method is executed when the module is loaded or reloaded.
"""
@abstractmethod
async def unload(self) -> None:
"""This method is executed when the module is unloaded or reloaded.
"""
@abstractmethod
async def cmd(self, data: list) -> None:
"""When recieving server messages.
Args:
data (list): The recieved message
"""
@abstractmethod
async def hcmds(self, user: str, channel: Optional[str], cmd: list[str], fullcmd: Optional[list[str]] = None) -> None:
"""These are the commands recieved from a client
Args:
user (str): The client
channel (str|None): The channel if available
cmd (list): The user command sent
fullcmd (list, optional): The full server message. Defaults to [].
"""

View File

@@ -0,0 +1,565 @@
from abc import ABC, abstractmethod
from typing import Optional, TYPE_CHECKING
from core.classes.protocols.command_handler import CommandHandler
if TYPE_CHECKING:
from core.definition import MClient, MSasl, MUser, MChannel
from core.loader import Loader
class IProtocol(ABC):
Handler: Optional[CommandHandler] = None
def __init__(self, context: 'Loader'):
self.name: Optional[str] = None
self.protocol_version: int = -1
self.known_protocol: set[str] = set()
self._ctx = context
self.Handler = CommandHandler(context)
self.init_protocol()
self._ctx.Logs.info(f"[PROTOCOL] Protocol [{self.__class__.__name__}] loaded!")
@abstractmethod
def init_protocol(self):
"""Init protocol
"""
@abstractmethod
def get_ircd_protocol_poisition(self, cmd: list[str], log: bool = False) -> tuple[int, Optional[str]]:
"""Get the position of known commands
Args:
cmd (list[str]): The server response
log (bool): If true it will log in the logger
Returns:
tuple[int, Optional[str]]: The position and the command.
"""
@abstractmethod
def register_command(self):
"""Register all commands that you need to handle
"""
@abstractmethod
async def send2socket(self, message: str, print_log: bool = True) -> None:
"""Envoit les commandes à envoyer au serveur.
Args:
message (str): contient la commande à envoyer au serveur.
print_log (bool): If True then print logs
"""
@abstractmethod
async def send_priv_msg(self, nick_from: str, msg: str, channel: str = None, nick_to: str = None):
"""Sending PRIVMSG to a channel or to a nickname by batches
could be either channel or nickname not both together
Args:
msg (str): The message to send
nick_from (str): The sender nickname
channel (str, optional): The receiver channel. Defaults to None.
nick_to (str, optional): The reciever nickname. Defaults to None.
"""
@abstractmethod
async def send_notice(self, nick_from: str, nick_to: str, msg: str) -> None:
"""Sending NOTICE by batches
Args:
msg (str): The message to send to the server
nick_from (str): The sender Nickname
nick_to (str): The reciever nickname
"""
@abstractmethod
async def send_link(self) -> None:
"""Créer le link et envoyer les informations nécessaires pour la
connexion au serveur.
"""
@abstractmethod
async def send_gline(self, nickname: str, hostname: str, set_by: str, expire_timestamp: int, set_at_timestamp: int, reason: str) -> None:
"""Send a gline command to the server
Args:
nickname (str): The nickname of the client.
hostname (str): The hostname of the client.
set_by (str): The nickname who send the gline
expire_timestamp (int): Expire timestamp
set_at_timestamp (int): Set at timestamp
reason (str): The reason of the gline.
"""
@abstractmethod
async def send_set_nick(self, newnickname: str) -> None:
"""Change nickname of the server
\n This method will also update the User object
Args:
newnickname (str): New nickname of the server
"""
@abstractmethod
async def send_set_mode(self, modes: str, *, nickname: Optional[str] = None, channel_name: Optional[str] = None, params: Optional[str] = None) -> None:
"""Set a mode to channel or to a nickname or for a user in a channel
Args:
modes (str): The selected mode
nickname (Optional[str]): The nickname
channel_name (Optional[str]): The channel name
params (Optional[str]): Parameters like password.
"""
@abstractmethod
async def send_squit(self, server_id: str, server_link: str, reason: str) -> None:
"""_summary_
Args:
server_id (str): _description_
server_link (str): _description_
reason (str): _description_
"""
@abstractmethod
async def send_ungline(self, nickname:str, hostname: str) -> None:
"""_summary_
Args:
nickname (str): _description_
hostname (str): _description_
"""
@abstractmethod
async def send_kline(self, nickname: str, hostname: str, set_by: str, expire_timestamp: int, set_at_timestamp: int, reason: str) -> None:
"""_summary_
Args:
nickname (str): _description_
hostname (str): _description_
set_by (str): _description_
expire_timestamp (int): _description_
set_at_timestamp (int): _description_
reason (str): _description_
"""
@abstractmethod
async def send_unkline(self, nickname:str, hostname: str) -> None:
"""_summary_
Args:
nickname (str): _description_
hostname (str): _description_
"""
@abstractmethod
async def send_sjoin(self, channel: str) -> None:
"""Server will join a channel with pre defined umodes
Args:
channel (str): Channel to join
"""
@abstractmethod
async def send_sapart(self, nick_to_sapart: str, channel_name: str) -> None:
"""_summary_
Args:
nick_to_sapart (str): _description_
channel_name (str): _description_
"""
@abstractmethod
async def send_sajoin(self, nick_to_sajoin: str, channel_name: str) -> None:
"""_summary_
Args:
nick_to_sajoin (str): _description_
channel_name (str): _description_
"""
@abstractmethod
async def send_svspart(self, nick_to_part: str, channels: list[str], reason: str) -> None:
"""_summary_
Args:
nick_to_part (str): _description_
channels (list[str]): _description_
reason (str): _description_
"""
@abstractmethod
async def send_svsjoin(self, nick_to_part: str, channels: list[str], keys: list[str]) -> None:
"""_summary_
Args:
nick_to_part (str): _description_
channels (list[str]): _description_
keys (list[str]): _description_
"""
@abstractmethod
async def send_svsmode(self, nickname: str, user_mode: str) -> None:
"""_summary_
Args:
nickname (str): _description_
user_mode (str): _description_
"""
@abstractmethod
async def send_svs2mode(self, nickname: str, user_mode: str) -> None:
"""_summary_
Args:
nickname (str): _description_
user_mode (str): _description_
"""
@abstractmethod
async def send_svslogin(self, client_uid: str, user_account: str) -> None:
"""Log a client into his account.
Args:
client_uid (str): Client UID
user_account (str): The account of the user
"""
@abstractmethod
async def send_svslogout(self, client_obj: 'MClient') -> None:
"""Logout a client from his account
Args:
client_obj (MClient): The Client UID
"""
@abstractmethod
async def send_quit(self, uid: str, reason: str, print_log: bool = True) -> None:
"""Send quit message
- Delete uid from User object
- Delete uid from Reputation object
Args:
uid (str): The UID or the Nickname
reason (str): The reason for the quit
print_log (bool): If True then print logs
"""
@abstractmethod
async def send_uid(self, nickname:str, username: str, hostname: str, uid:str, umodes: str, vhost: str, remote_ip: str, realname: str, print_log: bool = True) -> None:
"""Send UID to the server
- Insert User to User Object
Args:
nickname (str): Nickname of the client
username (str): Username of the client
hostname (str): Hostname of the client you want to create
uid (str): UID of the client you want to create
umodes (str): umodes of the client you want to create
vhost (str): vhost of the client you want to create
remote_ip (str): remote_ip of the client you want to create
realname (str): realname of the client you want to create
print_log (bool, optional): print logs if true. Defaults to True.
"""
@abstractmethod
async def send_join_chan(self, uidornickname: str, channel: str, password: str = None, print_log: bool = True) -> None:
"""Joining a channel
Args:
uidornickname (str): UID or nickname that need to join
channel (str): channel to join
password (str, optional): The password of the channel to join. Default to None
print_log (bool, optional): Write logs. Defaults to True.
"""
@abstractmethod
async def send_part_chan(self, uidornickname:str, channel: str, print_log: bool = True) -> None:
"""Part from a channel
Args:
uidornickname (str): UID or nickname that need to join
channel (str): channel to join
print_log (bool, optional): Write logs. Defaults to True.
"""
@abstractmethod
async def send_mode_chan(self, channel_name: str, channel_mode: str) -> None:
"""_summary_
Args:
channel_name (str): _description_
channel_mode (str): _description_
"""
@abstractmethod
async def send_raw(self, raw_command: str) -> None:
"""Send raw message to the server
Args:
raw_command (str): The raw command you want to send.
"""
# ------------------------------------------------------------------------
# COMMON IRC PARSER
# ------------------------------------------------------------------------
@abstractmethod
def parse_uid(self, server_msg: list[str]) -> Optional['MUser']:
"""Parse UID and return dictionary.
Args:
server_msg (list[str]): The UID IRCD message
Returns:
Optional[MUser]: The MUser object or None
"""
@abstractmethod
def parse_quit(self, server_msg: list[str]) -> tuple[Optional['MUser'], str]:
"""Parse quit and return dictionary.
>>> [':97KAAAAAB', 'QUIT', ':Quit:', 'this', 'is', 'my', 'reason', 'to', 'quit']
Args:
server_msg (list[str]): The server message to parse
Returns:
tuple[MUser, str]: The User Who Quit Object and the reason.
"""
@abstractmethod
def parse_nick(self, server_msg: list[str]) -> tuple[Optional['MUser'], str, str]:
"""Parse nick changes and return dictionary.
>>> [':97KAAAAAC', 'NICK', 'testinspir', '1757360740']
Args:
server_msg (list[str]): The server message to parse
Returns:
tuple(MUser, newnickname(str), timestamp(str)): Tuple of the response.
>>> MUser, newnickname, timestamp
"""
@abstractmethod
def parse_privmsg(self, server_msg: list[str]) -> tuple[Optional['MUser'], Optional['MUser'], Optional['MChannel'], str]:
"""Parse PRIVMSG message.
>>> [':97KAAAAAE', 'PRIVMSG', '#welcome', ':This', 'is', 'my', 'public', 'message']
Args:
server_msg (list[str]): The server message to parse
Returns:
tuple[MUser(Sender), MUser(Reciever), MChannel, str]: Sender user model, reciever user model, Channel model, messgae.
"""
# ------------------------------------------------------------------------
# EVENT HANDLER
# ------------------------------------------------------------------------
@abstractmethod
async def on_svs2mode(self, server_msg: list[str]) -> None:
"""Handle svs2mode coming from a server
>>> [':00BAAAAAG', 'SVS2MODE', '001U01R03', '-r']
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
async def on_mode(self, server_msg: list[str]) -> None:
"""Handle mode coming from a server
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
async def on_umode2(self, server_msg: list[str]) -> None:
"""Handle umode2 coming from a server
>>> [':adator_', 'UMODE2', '-i']
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
async def on_quit(self, server_msg: list[str]) -> None:
"""Handle quit coming from a server
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
async def on_squit(self, server_msg: list[str]) -> None:
"""Handle squit coming from a server
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
async def on_protoctl(self, server_msg: list[str]) -> None:
"""Handle protoctl coming from a server
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
async def on_nick(self, server_msg: list[str]) -> None:
"""Handle nick coming from a server
new nickname
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
async def on_sjoin(self, server_msg: list[str]) -> None:
"""Handle sjoin coming from a server
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
async def on_part(self, server_msg: list[str]) -> None:
"""Handle part coming from a server
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
async def on_eos(self, server_msg: list[str]) -> None:
"""Handle EOS coming from a server
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
async def on_reputation(self, server_msg: list[str]) -> None:
"""Handle REPUTATION coming from a server
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
async def on_uid(self, server_msg: list[str]) -> None:
"""Handle uid message coming from the server
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
async def on_privmsg(self, server_msg: list[str]) -> None:
"""Handle PRIVMSG message coming from the server
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
async def on_server_ping(self, server_msg: list[str]) -> None:
"""Send a PONG message to the server
Args:
server_msg (list[str]): List of str coming from the server
"""
@abstractmethod
async def on_server(self, server_msg: list[str]) -> None:
"""_summary_
Args:
server_msg (list[str]): _description_
"""
@abstractmethod
async def on_version(self, server_msg: list[str]) -> None:
"""Sending Server Version to the server
Args:
server_msg (list[str]): List of str coming from the server
"""
@abstractmethod
async def on_time(self, server_msg: list[str]) -> None:
"""Sending TIME answer to a requestor
Args:
server_msg (list[str]): List of str coming from the server
"""
@abstractmethod
async def on_ping(self, server_msg: list[str]) -> None:
"""Sending a PING answer to requestor
Args:
server_msg (list[str]): List of str coming from the server
"""
@abstractmethod
async def on_version_msg(self, server_msg: list[str]) -> None:
"""Handle version coming from the server
\n ex. /version Defender
Args:
server_msg (list[str]): Original message from the server
"""
@abstractmethod
async def on_smod(self, server_msg: list[str]) -> None:
"""Handle SMOD message coming from the server
Args:
server_msg (list[str]): Original server message
"""
@abstractmethod
async def on_sasl(self, server_msg: list[str]) -> Optional['MSasl']:
"""Handle SASL coming from a server
Args:
server_msg (list[str]): Original server message
Returns:
"""
@abstractmethod
async def on_sasl_authentication_process(self, sasl_model: 'MSasl') -> bool:
"""Finalize sasl authentication
Args:
sasl_model (MSasl): The sasl dataclass model
Returns:
bool: True if success
"""
@abstractmethod
async def on_md(self, server_msg: list[str]) -> None:
"""Handle MD responses
[':001', 'MD', 'client', '001MYIZ03', 'certfp', ':d1235648...']
Args:
server_msg (list[str]): The server reply
"""
@abstractmethod
async def on_kick(self, server_msg: list[str]) -> None:
"""When a user is kicked out from a channel
Eg. ['@unrealircd.org...', ':001', 'KICK', '#jsonrpc', '001ELW13T', ':Kicked', 'from', 'JSONRPC', 'User']
Args:
server_msg (list[str]): The server message
"""
@abstractmethod
async def on_sethost(self, server_msg: list[str]) -> None:
"""On SETHOST command
>>> [':001DN7305', 'SETHOST', ':netadmin.example.org']
Args:
server_msg (list[str]): _description_
"""

View File

@@ -0,0 +1,36 @@
import starlette.status as http_status_code
from typing import TYPE_CHECKING
from core.classes.modules.rpc.rpc_errors import JSONRPCErrorCode
if TYPE_CHECKING:
from core.loader import Loader
class IRPC:
def __init__(self, loader: 'Loader'):
self.ctx = loader
self.http_status_code = http_status_code
self.response_model = {
"jsonrpc": "2.0",
"method": 'unknown',
"id": 123
}
def reset(self):
self.response_model = {
"jsonrpc": "2.0",
"method": 'unknown',
"id": 123
}
def create_error_response(self, error_code: JSONRPCErrorCode, details: dict = None) -> dict[str, str]:
"""Create a JSON-RPC error!"""
response = {
"code": error_code.value,
"message": error_code.description(),
}
if details:
response["data"] = details
return response

View File

@@ -9,7 +9,12 @@ class Admin:
UID_ADMIN_DB: list[MAdmin] = []
def __init__(self, loader: 'Loader') -> None:
self.Logs = loader.Logs
"""
Args:
loader (Loader): The Loader Instance.
"""
self._ctx = loader
def insert(self, new_admin: MAdmin) -> bool:
"""Insert a new admin object model
@@ -23,11 +28,11 @@ class Admin:
for record in self.UID_ADMIN_DB:
if record.uid == new_admin.uid:
self.Logs.debug(f'{record.uid} already exist')
self._ctx.Logs.debug(f'{record.uid} already exist')
return False
self.UID_ADMIN_DB.append(new_admin)
self.Logs.debug(f'A new admin ({new_admin.nickname}) has been created')
self._ctx.Logs.debug(f'A new admin ({new_admin.nickname}) has been created')
return True
def update_nickname(self, uid: str, new_admin_nickname: str) -> bool:
@@ -45,11 +50,11 @@ class Admin:
if record.uid == uid:
# If the admin exist, update and do not go further
record.nickname = new_admin_nickname
self.Logs.debug(f'UID ({record.uid}) has been updated with new nickname {new_admin_nickname}')
self._ctx.Logs.debug(f'UID ({record.uid}) has been updated with new nickname {new_admin_nickname}')
return True
self.Logs.debug(f'The new nickname {new_admin_nickname} was not updated, uid = {uid} - The Client is not an admin')
self._ctx.Logs.debug(f'The new nickname {new_admin_nickname} was not updated, uid = {uid} - The Client is not an admin')
return False
def update_level(self, nickname: str, new_admin_level: int) -> bool:
@@ -67,10 +72,10 @@ class Admin:
if record.nickname == nickname:
# If the admin exist, update and do not go further
record.level = new_admin_level
self.Logs.debug(f'Admin ({record.nickname}) has been updated with new level {new_admin_level}')
self._ctx.Logs.debug(f'Admin ({record.nickname}) has been updated with new level {new_admin_level}')
return True
self.Logs.debug(f'The new level {new_admin_level} was not updated, nickname = {nickname} - The Client is not an admin')
self._ctx.Logs.debug(f'The new level {new_admin_level} was not updated, nickname = {nickname} - The Client is not an admin')
return False
@@ -83,20 +88,13 @@ class Admin:
Returns:
bool: True if the admin has been deleted
"""
admin_obj = self.get_admin(uidornickname)
if admin_obj:
self.UID_ADMIN_DB.remove(admin_obj)
self._ctx.Logs.debug(f'UID ({admin_obj.uid}) has been deleted')
return True
for record in self.UID_ADMIN_DB:
if record.uid == uidornickname:
# If the admin exist, delete and do not go further
self.UID_ADMIN_DB.remove(record)
self.Logs.debug(f'UID ({record.uid}) has been deleted')
return True
if record.nickname.lower() == uidornickname.lower():
# If the admin exist, delete and do not go further
self.UID_ADMIN_DB.remove(record)
self.Logs.debug(f'nickname ({record.nickname}) has been deleted')
return True
self.Logs.debug(f'The UID {uidornickname} was not deleted')
self._ctx.Logs.debug(f'The UID {uidornickname} was not deleted')
return False
@@ -152,4 +150,70 @@ class Admin:
if record.uid == uidornickname:
return record.nickname
return None
return None
def get_language(self, uidornickname: str) -> Optional[str]:
"""Get the language of the admin
Args:
uidornickname (str): The user ID or the Nickname of the admin
Returns:
Optional[str]: The language selected by the admin.
"""
admin = self.get_admin(uidornickname)
if admin is None:
return None
return admin.language
async def db_auth_admin_via_fingerprint(self, fp: str, uidornickname: str) -> bool:
"""Check the fingerprint
Args:
fp (str): The unique fingerprint of the user
uidornickname (str): The UID or the Nickname of the user
Returns:
bool: True if found
"""
if fp is None:
return False
query = f"SELECT user, level, language FROM {self._ctx.Config.TABLE_ADMIN} WHERE fingerprint = :fp"
data = {'fp': fp}
exe = await self._ctx.Base.db_execute_query(query, data)
result = exe.fetchone()
if result:
account = result[0]
level = result[1]
language = result[2]
user_obj = self._ctx.User.get_user(uidornickname)
if user_obj:
admin_obj = self._ctx.Definition.MAdmin(**user_obj.to_dict(), account=account, level=level, language=language)
if self.insert(admin_obj):
self._ctx.Settings.current_admin = admin_obj
self._ctx.Logs.debug(f"[Fingerprint login] {user_obj.nickname} ({admin_obj.account}) has been logged in successfully!")
return True
return False
async def db_is_admin_exist(self, admin_nickname: str) -> bool:
"""Verify if the admin exist in the database!
Args:
admin_nickname (str): The nickname admin to check.
Returns:
bool: True if the admin exist otherwise False.
"""
mes_donnees = {'admin': admin_nickname}
query_search_user = f"SELECT id FROM {self._ctx.Config.TABLE_ADMIN} WHERE user = :admin"
r = await self._ctx.Base.db_execute_query(query_search_user, mes_donnees)
exist_user = r.fetchone()
if exist_user:
return True
else:
return False

View File

@@ -11,13 +11,13 @@ class Channel:
"""List that contains all the Channels objects (ChannelModel)
"""
def __init__(self, loader: 'Loader') -> None:
def __init__(self, loader: 'Loader'):
"""
self.Logs = loader.Logs
self.Base = loader.Base
self.Utils = loader.Utils
return None
Args:
loader (Loader): The Loader Instance
"""
self._ctx = loader
def insert(self, new_channel: 'MChannel') -> bool:
"""This method will insert a new channel and if the channel exist it will update the user list (uids)
@@ -32,14 +32,14 @@ class Channel:
exist = False
if not self.is_valid_channel(new_channel.name):
self.Logs.error(f"The channel {new_channel.name} is not valid, channel must start with #")
self._ctx.Logs.error(f"The channel {new_channel.name} is not valid, channel must start with #")
return False
for record in self.UID_CHANNEL_DB:
if record.name.lower() == new_channel.name.lower():
# If the channel exist, update the user list and do not go further
exist = True
# self.Logs.debug(f'{record.name} already exist')
# self._ctx.Logs.debug(f'{record.name} already exist')
for user in new_channel.uids:
record.uids.append(user)
@@ -47,7 +47,7 @@ class Channel:
# Supprimer les doublons
del_duplicates = list(set(record.uids))
record.uids = del_duplicates
# self.Logs.debug(f'Updating a new UID to the channel {record}')
# self._ctx.Logs.debug(f'Updating a new UID to the channel {record}')
return result
if not exist:
@@ -55,10 +55,10 @@ class Channel:
new_channel.name = new_channel.name.lower()
self.UID_CHANNEL_DB.append(new_channel)
result = True
# self.Logs.debug(f'New Channel Created: ({new_channel})')
# self._ctx.Logs.debug(f'New Channel Created: ({new_channel})')
if not result:
self.Logs.critical(f'The Channel Object was not inserted {new_channel}')
self._ctx.Logs.critical(f'The Channel Object was not inserted {new_channel}')
self.clean_channel()
@@ -101,7 +101,7 @@ class Channel:
return result
for userid in chan_obj.uids:
if self.Utils.clean_uid(userid) == self.Utils.clean_uid(uid):
if self._ctx.Utils.clean_uid(userid) == self._ctx.Utils.clean_uid(uid):
chan_obj.uids.remove(userid)
result = True
@@ -109,7 +109,8 @@ class Channel:
return result
except ValueError as ve:
self.Logs.error(f'{ve}')
self._ctx.Logs.error(f'{ve}')
return False
def delete_user_from_all_channel(self, uid:str) -> bool:
"""Delete a client from all channels
@@ -125,7 +126,7 @@ class Channel:
for record in self.UID_CHANNEL_DB:
for user_id in record.uids:
if self.Utils.clean_uid(user_id) == self.Utils.clean_uid(uid):
if self._ctx.Utils.clean_uid(user_id) == self._ctx.Utils.clean_uid(uid):
record.uids.remove(user_id)
result = True
@@ -133,7 +134,8 @@ class Channel:
return result
except ValueError as ve:
self.Logs.error(f'{ve}')
self._ctx.Logs.error(f'{ve}')
return False
def add_user_to_a_channel(self, channel_name: str, uid: str) -> bool:
"""Add a client to a channel
@@ -150,7 +152,7 @@ class Channel:
if chan_obj is None:
# Create a new channel if the channel don't exist
self.Logs.debug(f"New channel will be created ({channel_name} - {uid})")
self._ctx.Logs.debug(f"New channel will be created ({channel_name} - {uid})")
return self.insert(MChannel(channel_name, uids=[uid]))
chan_obj.uids.append(uid)
@@ -159,7 +161,7 @@ class Channel:
return True
except Exception as err:
self.Logs.error(f'{err}')
self._ctx.Logs.error(f'{err}')
return False
def is_user_present_in_channel(self, channel_name: str, uid: str) -> bool:
@@ -176,9 +178,9 @@ class Channel:
if chan is None:
return False
clean_uid = self.Utils.clean_uid(uid=uid)
clean_uid = self._ctx.Utils.clean_uid(uid=uid)
for chan_uid in chan.uids:
if self.Utils.clean_uid(chan_uid) == clean_uid:
if self._ctx.Utils.clean_uid(chan_uid) == clean_uid:
return True
return False
@@ -193,7 +195,7 @@ class Channel:
return None
except Exception as err:
self.Logs.error(f'{err}')
self._ctx.Logs.error(f'{err}')
def get_channel(self, channel_name: str) -> Optional['MChannel']:
"""Get the channel object
@@ -226,18 +228,20 @@ class Channel:
return False
pattern = fr'^#'
isChannel = findall(pattern, channel_to_check)
is_channel = findall(pattern, channel_to_check)
if not isChannel:
if not is_channel:
return False
else:
return True
except TypeError as te:
self.Logs.error(f'TypeError: [{channel_to_check}] - {te}')
self._ctx.Logs.error(f'TypeError: [{channel_to_check}] - {te}')
return False
except Exception as err:
self.Logs.error(f'Error Not defined: {err}')
self._ctx.Logs.error(f'Error Not defined: {err}')
return False
def db_query_channel(self, action: Literal['add','del'], module_name: str, channel_name: str) -> bool:
async 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:
@@ -250,40 +254,49 @@ class Channel:
"""
try:
channel_name = channel_name.lower() if self.is_valid_channel(channel_name) else None
core_table = self.Base.Config.TABLE_CHANNEL
core_table = self._ctx.Base.Config.TABLE_CHANNEL
if not channel_name:
self.Logs.warning(f'The channel [{channel_name}] is not correct')
self._ctx.Logs.warning(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.Base.db_execute_query(f"SELECT id FROM {core_table} WHERE module_name = :module_name AND channel_name = :channel_name", mes_donnees)
response = await self._ctx.Base.db_execute_query(f"SELECT id FROM {core_table} WHERE module_name = :module_name AND channel_name = :channel_name", mes_donnees)
is_channel_exist = response.fetchone()
if is_channel_exist is None:
mes_donnees = {'datetime': self.Utils.get_sdatetime(), 'channel_name': channel_name, 'module_name': module_name}
insert = self.Base.db_execute_query(f"INSERT INTO {core_table} (datetime, channel_name, module_name) VALUES (:datetime, :channel_name, :module_name)", mes_donnees)
mes_donnees = {'datetime': self._ctx.Utils.get_sdatetime(), 'channel_name': channel_name, 'module_name': module_name}
insert = await self._ctx.Base.db_execute_query(f"INSERT INTO {core_table} (datetime, channel_name, module_name) VALUES (:datetime, :channel_name, :module_name)", mes_donnees)
if insert.rowcount:
self.Logs.debug(f'Channel added to DB: channel={channel_name} / module_name={module_name}')
self._ctx.Logs.debug(f'Channel added to DB: channel={channel_name} / module_name={module_name}')
return True
else:
return False
case 'del':
mes_donnes = {'channel_name': channel_name, 'module_name': module_name}
response = self.Base.db_execute_query(f"DELETE FROM {core_table} WHERE channel_name = :channel_name AND module_name = :module_name", mes_donnes)
response = await self._ctx.Base.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 from DB: channel={channel_name} / module: {module_name}')
self._ctx.Logs.debug(f'Channel deleted from DB: channel={channel_name} / module: {module_name}')
return True
else:
return False
case _:
return False
except Exception as err:
self.Logs.error(err)
self._ctx.Logs.error(err)
return False
async def db_join_saved_channels(self) -> None:
"""## Joining saved channels"""
exec_query = await self._ctx.Base.db_execute_query(f'SELECT distinct channel_name FROM {self._ctx.Config.TABLE_CHANNEL}')
result_query = exec_query.fetchall()
if result_query:
for chan_name in result_query:
chan = chan_name[0]
await self._ctx.Irc.Protocol.send_sjoin(channel=chan)

View File

@@ -10,9 +10,12 @@ class Client:
CLIENT_DB: list['MClient'] = []
def __init__(self, loader: 'Loader'):
"""
self.Logs = loader.Logs
self.Base = loader.Base
Args:
loader (Loader): The Loader instance.
"""
self._ctx = loader
def insert(self, new_client: 'MClient') -> bool:
"""Insert a new User object
@@ -24,7 +27,7 @@ class Client:
bool: True if inserted
"""
client_obj = self.get_Client(new_client.uid)
client_obj = self.get_client(new_client.uid)
if not client_obj is None:
# User already created return False
@@ -34,22 +37,22 @@ class Client:
return True
def update_nickname(self, uid: str, newNickname: str) -> bool:
def update_nickname(self, uid: str, new_nickname: str) -> bool:
"""Update the nickname starting from the UID
Args:
uid (str): UID of the user
newNickname (str): New nickname
new_nickname (str): New nickname
Returns:
bool: True if updated
"""
user_obj = self.get_Client(uidornickname=uid)
user_obj = self.get_client(uidornickname=uid)
if user_obj is None:
return False
user_obj.nickname = newNickname
user_obj.nickname = new_nickname
return True
@@ -64,7 +67,7 @@ class Client:
bool: True if user mode has been updaed
"""
response = True
user_obj = self.get_Client(uidornickname=uidornickname)
user_obj = self.get_client(uidornickname=uidornickname)
if user_obj is None:
return False
@@ -89,7 +92,7 @@ class Client:
return False
liste_umodes = list(umodes)
final_umodes_liste = [x for x in self.Base.Settings.PROTOCTL_USER_MODES if x in liste_umodes]
final_umodes_liste = [x for x in self._ctx.Base.Settings.PROTOCTL_USER_MODES if x in liste_umodes]
final_umodes = ''.join(final_umodes_liste)
user_obj.umodes = f"+{final_umodes}"
@@ -106,7 +109,7 @@ class Client:
bool: True if deleted
"""
user_obj = self.get_Client(uidornickname=uid)
user_obj = self.get_client(uidornickname=uid)
if user_obj is None:
return False
@@ -115,7 +118,7 @@ class Client:
return True
def get_Client(self, uidornickname: str) -> Optional['MClient']:
def get_client(self, uidornickname: str) -> Optional['MClient']:
"""Get The Client Object model
Args:
@@ -142,7 +145,7 @@ class Client:
str|None: Return the UID
"""
client_obj = self.get_Client(uidornickname=uidornickname)
client_obj = self.get_client(uidornickname=uidornickname)
if client_obj is None:
return None
@@ -158,30 +161,14 @@ class Client:
Returns:
str|None: the nickname
"""
client_obj = self.get_Client(uidornickname=uidornickname)
client_obj = self.get_client(uidornickname=uidornickname)
if client_obj is None:
return None
return client_obj.nickname
def get_client_asdict(self, uidornickname: str) -> Optional[dict[str, Any]]:
"""Transform User Object to a dictionary
Args:
uidornickname (str): The UID or The nickname
Returns:
Union[dict[str, any], None]: User Object as a dictionary or None
"""
client_obj = self.get_Client(uidornickname=uidornickname)
if client_obj is None:
return None
return client_obj.to_dict()
def is_exist(self, uidornikname: str) -> bool:
def is_exist(self, uidornickname: str) -> bool:
"""Check if the UID or the nickname exist in the USER DB
Args:
@@ -190,14 +177,14 @@ class Client:
Returns:
bool: True if exist
"""
user_obj = self.get_Client(uidornickname=uidornikname)
user_obj = self.get_client(uidornickname=uidornickname)
if user_obj is None:
return False
return True
def db_is_account_exist(self, account: str) -> bool:
async def db_is_account_exist(self, account: str) -> bool:
"""Check if the account exist in the database
Args:
@@ -207,15 +194,15 @@ class Client:
bool: True if exist
"""
table_client = self.Base.Config.TABLE_CLIENT
table_client = self._ctx.Base.Config.TABLE_CLIENT
account_to_check = {'account': account.lower()}
account_to_check_query = self.Base.db_execute_query(f"""
account_to_check_query = await self._ctx.Base.db_execute_query(f"""
SELECT id FROM {table_client} WHERE LOWER(account) = :account
""", account_to_check)
account_to_check_result = account_to_check_query.fetchone()
if account_to_check_result:
self.Logs.error(f"Account ({account}) already exist")
self._ctx.Logs.error(f"Account ({account}) already exist")
return True
return False
@@ -231,9 +218,9 @@ class Client:
"""
pattern = fr'[:|@|%|\+|~|\*]*'
parsed_UID = sub(pattern, '', uid)
parsed_uid = sub(pattern, '', uid)
if not parsed_UID:
if not parsed_uid:
return None
return parsed_UID
return parsed_uid

View File

@@ -9,11 +9,25 @@ class Command:
DB_COMMANDS: list['MCommand'] = []
def __init__(self, loader: 'Loader'):
self.Loader = loader
self.Base = loader.Base
self.Logs = loader.Logs
"""
Args:
loader (Loader): The Loader instance.
"""
self._ctx = loader
def build(self, new_command_obj: MCommand) -> bool:
def build_command(self, level: int, module_name: str, command_name: str, command_description: str) -> bool:
"""This method build the commands variable
Args:
level (int): The Level of the command
module_name (str): The module name
command_name (str): The command name
command_description (str): The description of the command
"""
# Build Model.
return self._build(self._ctx.Definition.MCommand(module_name, command_name, command_description, level))
def _build(self, new_command_obj: MCommand) -> bool:
command = self.get_command(new_command_obj.command_name, new_command_obj.module_name)
if command is None:
@@ -32,7 +46,7 @@ class Command:
def get_command(self, command_name: str, module_name: str) -> Optional[MCommand]:
for command in self.DB_COMMANDS:
if command.command_name.lower() == command_name and command.module_name == module_name:
if command.command_name.lower() == command_name.lower() and command.module_name.lower() == module_name.lower():
return command
return None
@@ -64,7 +78,7 @@ class Command:
for c in tmp_model:
self.DB_COMMANDS.remove(c)
self.Logs.debug(f"[COMMAND] Drop command for module {module_name}")
self._ctx.Logs.debug(f"[COMMAND] Drop command for module {module_name}")
return True
def get_ordered_commands(self) -> list[MCommand]:
@@ -82,11 +96,11 @@ class Command:
return new_list
def is_client_allowed_to_run_command(self, nickname: str, command_name: str) -> bool:
admin = self.Loader.Admin.get_admin(nickname)
admin = self._ctx.Admin.get_admin(nickname)
admin_level = admin.level if admin else 0
commands = self.get_commands_by_level(admin_level)
if command_name in [command.command_name for command in commands]:
if command_name.lower() in [command.command_name.lower() for command in commands]:
return True
return False

View File

@@ -0,0 +1,60 @@
import sys
import yaml
from json import load
from sys import exit
from os import sep
from typing import Any, Optional, Union, TYPE_CHECKING
from core.definition import MConfig
if TYPE_CHECKING:
from core.loader import Loader
class Configuration:
def __init__(self, loader: 'Loader') -> None:
self.Loader = loader
self.Logs = loader.Logs
self.configuration_model = self.__load_service_configuration()
loader.ServiceLogging.set_file_handler_level(self._config_model.DEBUG_LEVEL)
loader.ServiceLogging.set_stdout_handler_level(self._config_model.DEBUG_LEVEL)
loader.ServiceLogging.update_handler_format(self._config_model.DEBUG_HARD)
return None
@property
def configuration_model(self) -> MConfig:
return self._config_model
@configuration_model.setter
def configuration_model(self, conf_model: MConfig):
self._config_model = conf_model
def __load_config_file(self) -> Optional[dict[str, Any]]:
try:
conf_filename = f'config{sep}configuration.yaml'
with open(conf_filename, 'r') as conf:
configuration: dict[str, dict[str, Any]] = yaml.safe_load(conf)
return configuration.get('configuration', None)
except FileNotFoundError as fe:
self.Logs.error(f'FileNotFound: {fe}')
self.Logs.error('Configuration file not found please create config/configuration.yaml')
exit("Configuration file not found please create config/configuration.yaml")
def __load_service_configuration(self) -> MConfig:
try:
import_config = self.__load_config_file()
if import_config is None:
self.Logs.error("Error While importing configuration file!", exc_info=True)
raise Exception("Error While importing yaml configuration")
list_key_to_remove: list[str] = [key_to_del for key_to_del in import_config if key_to_del not in MConfig().get_attributes()]
for key_to_remove in list_key_to_remove:
import_config.pop(key_to_remove, None)
self.Logs.warning(f"[!] The key {key_to_remove} is not expected, it has been removed from the system ! please remove it from configuration.json file [!]")
self.Logs.debug(f"[LOADING CONFIGURATION]: Loading configuration with {len(import_config)} parameters!")
return MConfig(**import_config)
except TypeError as te:
self.Logs.error(te)

View File

@@ -0,0 +1,189 @@
import asyncio
import importlib
import sys
import time
from typing import TYPE_CHECKING
import socket
if TYPE_CHECKING:
from core.loader import Loader
# Modules impacted by rehashing!
REHASH_MODULES = [
'core.definition',
'core.utils',
'core.classes.modules.config',
'core.base',
'core.classes.modules.commands',
'core.classes.modules.rpc.rpc_channel',
'core.classes.modules.rpc.rpc_command',
'core.classes.modules.rpc.rpc_user',
'core.classes.modules.rpc.rpc',
'core.classes.interfaces.iprotocol',
'core.classes.interfaces.imodule',
'core.classes.protocols.command_handler',
'core.classes.protocols.factory',
'core.classes.protocols.unreal6',
'core.classes.protocols.inspircd'
]
async def restart_service(uplink: 'Loader', reason: str = "Restarting with no reason!") -> None:
"""
Args:
uplink (Irc): The Irc instance
reason (str): The reason of the restart.
"""
# unload modules.
for module in uplink.ModuleUtils.model_get_loaded_modules().copy():
await uplink.ModuleUtils.unload_one_module(module.module_name)
uplink.Base.garbage_collector_thread()
uplink.Logs.debug(f'[{uplink.Config.SERVICE_NICKNAME} RESTART]: Reloading configuration!')
await uplink.Irc.Protocol.send_squit(server_id=uplink.Config.SERVEUR_ID, server_link=uplink.Config.SERVEUR_LINK, reason=reason)
uplink.Logs.debug('Restarting Defender ...')
for mod in REHASH_MODULES:
importlib.reload(sys.modules[mod])
# Reload configuration
uplink.Config = uplink.ConfModule.Configuration(uplink).configuration_model
uplink.Base = uplink.BaseModule.Base(uplink)
uplink.ModuleUtils.model_clear() # Clear loaded modules.
uplink.User.UID_DB.clear() # Clear User Object
uplink.Channel.UID_CHANNEL_DB.clear() # Clear Channel Object
uplink.Client.CLIENT_DB.clear() # Clear Client object
uplink.Irc.Protocol.Handler.DB_IRCDCOMMS.clear()
# Reload Service modules
for module in uplink.ModuleUtils.model_get_loaded_modules().copy():
await uplink.ModuleUtils.reload_one_module(module.module_name, uplink.Settings.current_admin)
uplink.Irc.signal = True
await uplink.Irc.run()
uplink.Config.DEFENDER_RESTART = 0
async def rehash_service(uplink: 'Loader', nickname: str) -> None:
need_a_restart = ["SERVEUR_ID"]
uplink.Settings.set_cache('db_commands', uplink.Commands.DB_COMMANDS)
await uplink.RpcServer.stop_rpc_server()
restart_flag = False
config_model_bakcup = uplink.Config
mods = REHASH_MODULES
for mod in mods:
importlib.reload(sys.modules[mod])
await uplink.Irc.Protocol.send_priv_msg(
nick_from=uplink.Config.SERVICE_NICKNAME,
msg=f'[REHASH] Module [{mod}] reloaded',
channel=uplink.Config.SERVICE_CHANLOG
)
uplink.Utils = sys.modules['core.utils']
uplink.Config = uplink.ConfModule.Configuration(uplink).configuration_model
uplink.Config.HSID = config_model_bakcup.HSID
uplink.Config.DEFENDER_INIT = config_model_bakcup.DEFENDER_INIT
uplink.Config.DEFENDER_RESTART = config_model_bakcup.DEFENDER_RESTART
uplink.Config.SSL_VERSION = config_model_bakcup.SSL_VERSION
uplink.Config.CURRENT_VERSION = config_model_bakcup.CURRENT_VERSION
uplink.Config.LATEST_VERSION = config_model_bakcup.LATEST_VERSION
conf_bkp_dict: dict = config_model_bakcup.to_dict()
config_dict: dict = uplink.Config.to_dict()
for key, value in conf_bkp_dict.items():
if config_dict[key] != value and key != 'COLORS':
await uplink.Irc.Protocol.send_priv_msg(
nick_from=uplink.Config.SERVICE_NICKNAME,
msg=f'[{key}]: {value} ==> {config_dict[key]}',
channel=uplink.Config.SERVICE_CHANLOG
)
if key in need_a_restart:
restart_flag = True
if config_model_bakcup.SERVICE_NICKNAME != uplink.Config.SERVICE_NICKNAME:
await uplink.Irc.Protocol.send_set_nick(uplink.Config.SERVICE_NICKNAME)
if restart_flag:
uplink.Config.SERVEUR_ID = config_model_bakcup.SERVEUR_ID
await uplink.Irc.Protocol.send_priv_msg(
nick_from=uplink.Config.SERVICE_NICKNAME,
channel=uplink.Config.SERVICE_CHANLOG,
msg='You need to restart defender !')
# Reload Main Commands Module
uplink.Commands = uplink.CommandModule.Command(uplink)
uplink.Commands.DB_COMMANDS = uplink.Settings.get_cache('db_commands')
uplink.Base = uplink.BaseModule.Base(uplink)
uplink.Irc.Protocol = uplink.PFactory.get()
uplink.Irc.Protocol.register_command()
uplink.RpcServer = uplink.RpcServerModule.JSonRpcServer(uplink)
uplink.Base.create_asynctask(uplink.RpcServer.start_rpc_server())
# Reload Service modules
for module in uplink.ModuleUtils.model_get_loaded_modules().copy():
await uplink.ModuleUtils.reload_one_module(module.module_name, nickname)
return None
async def shutdown(uplink: 'Loader') -> None:
"""Methode qui va préparer l'arrêt complêt du service
"""
# Stop RpcServer if running
await uplink.RpcServer.stop_rpc_server()
# unload modules.
uplink.Logs.debug(f"=======> Unloading all modules!")
for module in uplink.ModuleUtils.model_get_loaded_modules().copy():
await uplink.ModuleUtils.unload_one_module(module.module_name)
# Nettoyage des timers
uplink.Logs.debug(f"=======> Closing all timers!")
for timer in uplink.Base.running_timers:
while timer.is_alive():
uplink.Logs.debug(f"> waiting for {timer.name} to close")
timer.cancel()
await asyncio.sleep(0.2)
uplink.Logs.debug(f"> Cancelling {timer.name} {timer.native_id}")
uplink.Logs.debug(f"=======> Closing all Threads!")
for thread in uplink.Base.running_threads:
if thread.name == 'heartbeat' and thread.is_alive():
uplink.Base.execute_periodic_action()
uplink.Logs.debug(f"> Running the last periodic action")
uplink.Logs.debug(f"> Cancelling {thread.name} {thread.native_id}")
uplink.Logs.debug(f"=======> Closing all IO Threads!")
[th.thread_event.clear() for th in uplink.Base.running_iothreads]
uplink.Logs.debug(f"=======> Closing all IO TASKS!")
try:
await asyncio.wait_for(asyncio.gather(*uplink.Base.running_iotasks), timeout=5)
except asyncio.exceptions.TimeoutError as te:
uplink.Logs.debug(f"Asyncio Timeout reached! {te}")
for task in uplink.Base.running_iotasks:
task.cancel()
except asyncio.exceptions.CancelledError as cerr:
uplink.Logs.debug(f"Asyncio CancelledError reached! {cerr}")
uplink.Logs.debug(f"=======> Closing all Sockets!")
for soc in uplink.Base.running_sockets:
soc.close()
while soc.fileno() != -1:
soc.close()
uplink.Base.running_sockets.remove(soc)
uplink.Logs.debug(f"> Socket ==> closed {str(soc.fileno())}")
uplink.Base.running_timers.clear()
uplink.Base.running_threads.clear()
uplink.Base.running_iotasks.clear()
uplink.Base.running_iothreads.clear()
uplink.Base.running_sockets.clear()
uplink.Base.db_close()
return None

View File

@@ -9,9 +9,12 @@ class Reputation:
UID_REPUTATION_DB: list[MReputation] = []
def __init__(self, loader: 'Loader'):
"""
self.Logs = loader.Logs
self.MReputation: MReputation = MReputation
Args:
loader (Loader): The Loader instance.
"""
self._ctx = loader
def insert(self, new_reputation_user: MReputation) -> bool:
"""Insert a new Reputation User object
@@ -29,16 +32,16 @@ class Reputation:
if record.uid == new_reputation_user.uid:
# If the user exist then return False and do not go further
exist = True
self.Logs.debug(f'{record.uid} already exist')
self._ctx.Logs.debug(f'{record.uid} already exist')
return result
if not exist:
self.UID_REPUTATION_DB.append(new_reputation_user)
result = True
self.Logs.debug(f'New Reputation User Captured: ({new_reputation_user})')
self._ctx.Logs.debug(f'New Reputation User Captured: ({new_reputation_user})')
if not result:
self.Logs.critical(f'The Reputation User Object was not inserted {new_reputation_user}')
self._ctx.Logs.critical(f'The Reputation User Object was not inserted {new_reputation_user}')
return result
@@ -47,13 +50,13 @@ class Reputation:
Args:
uid (str): UID of the user
newNickname (str): New nickname
new_nickname (str): New nickname
Returns:
bool: True if updated
"""
reputation_obj = self.get_Reputation(uid)
reputation_obj = self.get_reputation(uid)
if reputation_obj is None:
return False
@@ -81,15 +84,15 @@ class Reputation:
# If the user exist then remove and return True and do not go further
self.UID_REPUTATION_DB.remove(record)
result = True
self.Logs.debug(f'UID ({record.uid}) has been deleted')
self._ctx.Logs.debug(f'UID ({record.uid}) has been deleted')
return result
if not result:
self.Logs.critical(f'The UID {uid} was not deleted')
self._ctx.Logs.critical(f'The UID {uid} was not deleted')
return result
def get_Reputation(self, uidornickname: str) -> Optional[MReputation]:
def get_reputation(self, uidornickname: str) -> Optional[MReputation]:
"""Get The User Object model
Args:
@@ -116,7 +119,7 @@ class Reputation:
str|None: Return the UID
"""
reputation_obj = self.get_Reputation(uidornickname)
reputation_obj = self.get_reputation(uidornickname)
if reputation_obj is None:
return None
@@ -132,7 +135,7 @@ class Reputation:
Returns:
str|None: the nickname
"""
reputation_obj = self.get_Reputation(uidornickname)
reputation_obj = self.get_reputation(uidornickname)
if reputation_obj is None:
return None
@@ -149,7 +152,7 @@ class Reputation:
bool: True if exist
"""
reputation_obj = self.get_Reputation(uidornickname)
reputation_obj = self.get_reputation(uidornickname)
if isinstance(reputation_obj, MReputation):
return True

View File

@@ -0,0 +1,2 @@
__version__ = '1.0.0'
__all__ = ['start_rpc_server', 'stop_rpc_server']

View File

@@ -0,0 +1,140 @@
import base64
import json
import uvicorn
import core.classes.modules.rpc.rpc_errors as rpcerr
import starlette.status as http_status_code
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.requests import Request
from starlette.routing import Route
from typing import TYPE_CHECKING, Any, Optional
from core.classes.modules.rpc.rpc_user import RPCUser
from core.classes.modules.rpc.rpc_channel import RPCChannel
from core.classes.modules.rpc.rpc_command import RPCCommand
if TYPE_CHECKING:
from core.loader import Loader
class JSonRpcServer:
def __init__(self, context: 'Loader'):
self._ctx = context
self.live: bool = False
self.host = context.Config.RPC_HOST
self.port = context.Config.RPC_PORT
self.routes: list[Route] = []
self.server: Optional[uvicorn.Server] = None
self.methods: dict = {
'user.list': RPCUser(context).user_list,
'user.get': RPCUser(context).user_get,
'channel.list': RPCChannel(context).channel_list,
'command.list': RPCCommand(context).command_list,
'command.get.by.name': RPCCommand(context).command_get_by_name,
'command.get.by.module': RPCCommand(context).command_get_by_module
}
async def start_rpc_server(self):
if not self.live:
self.routes = [Route('/api', self.request_handler, methods=['POST'])]
self.app_jsonrpc = Starlette(debug=False, routes=self.routes)
config = uvicorn.Config(self.app_jsonrpc, host=self.host, port=self.port, log_level=self._ctx.Config.DEBUG_LEVEL+10)
self.server = uvicorn.Server(config)
self.live = True
await self._ctx.Irc.Protocol.send_priv_msg(
self._ctx.Config.SERVICE_NICKNAME,
"[DEFENDER JSONRPC SERVER] RPC Server started!",
self._ctx.Config.SERVICE_CHANLOG
)
await self.server.serve()
self._ctx.Logs.debug("Server is going to shutdown!")
else:
self._ctx.Logs.debug("Server already running")
async def stop_rpc_server(self):
if self.server:
self.server.should_exit = True
await self.server.shutdown()
self.live = False
self._ctx.Logs.debug("JSON-RPC Server off!")
await self._ctx.Irc.Protocol.send_priv_msg(
self._ctx.Config.SERVICE_NICKNAME,
"[DEFENDER JSONRPC SERVER] RPC Server Stopped!",
self._ctx.Config.SERVICE_CHANLOG
)
async def request_handler(self, request: Request) -> JSONResponse:
request_data: dict = await request.json()
method = request_data.get("method", None)
params: dict[str, Any] = request_data.get("params", {})
auth: JSONResponse = self.authenticate(request.headers, request_data)
if not json.loads(auth.body).get('result', False):
return auth
response_data = {
"jsonrpc": "2.0",
"method": method,
"id": request_data.get('id', 123)
}
rip = request.client.host
rport = request.client.port
http_code = http_status_code.HTTP_200_OK
if method in self.methods:
r: JSONResponse = self.methods[method](**params)
resp = json.loads(r.body)
resp['id'] = request_data.get('id', 123)
resp['method'] = method
return JSONResponse(resp, r.status_code)
response_data['error'] = rpcerr.create_error_response(rpcerr.JSONRPCErrorCode.METHOD_NOT_FOUND)
self._ctx.Logs.debug(f'[RPC ERROR] {method} recieved from {rip}:{rport}')
http_code = http_status_code.HTTP_404_NOT_FOUND
return JSONResponse(response_data, http_code)
def authenticate(self, headers: dict, body: dict) -> JSONResponse:
ok_auth = {
'jsonrpc': '2.0',
'id': body.get('id', 123),
'result': True
}
logs = self._ctx.Logs
auth: str = headers.get('Authorization', '')
if not auth:
return self.send_auth_error(body)
# Authorization header format: Basic base64(username:password)
auth_type, auth_string = auth.split(' ', 1)
if auth_type.lower() != 'basic':
return self.send_auth_error(body)
try:
# Decode the base64-encoded username:password
decoded_credentials = base64.b64decode(auth_string).decode('utf-8')
username, password = decoded_credentials.split(":", 1)
# Check the username and password.
for rpcuser in self._ctx.Config.RPC_USERS:
if rpcuser.get('USERNAME', None) == username and rpcuser.get('PASSWORD', None) == password:
return JSONResponse(ok_auth)
return self.send_auth_error(body)
except Exception as e:
logs.error(e)
return self.send_auth_error(body)
def send_auth_error(self, request_data: dict) -> JSONResponse:
response_data = {
'jsonrpc': '2.0',
'id': request_data.get('id', 123),
'error': rpcerr.create_error_response(rpcerr.JSONRPCErrorCode.AUTHENTICATION_ERROR)
}
return JSONResponse(response_data, http_status_code.HTTP_403_FORBIDDEN)

View File

@@ -0,0 +1,16 @@
from typing import TYPE_CHECKING
from starlette.responses import JSONResponse
from core.classes.interfaces.irpc_endpoint import IRPC
from core.classes.modules.rpc.rpc_errors import JSONRPCErrorCode
if TYPE_CHECKING:
from core.loader import Loader
class RPCChannel(IRPC):
def __init__(self, loader: 'Loader'):
super().__init__(loader)
def channel_list(self, **kwargs) -> JSONResponse:
self.reset()
self.response_model['result'] = [chan.to_dict() for chan in self.ctx.Channel.UID_CHANNEL_DB]
return JSONResponse(self.response_model)

View File

@@ -0,0 +1,44 @@
from typing import TYPE_CHECKING
from starlette.responses import JSONResponse
from core.classes.interfaces.irpc_endpoint import IRPC
from core.classes.modules.rpc.rpc_errors import JSONRPCErrorCode
if TYPE_CHECKING:
from core.loader import Loader
class RPCCommand(IRPC):
def __init__(self, loader: 'Loader'):
super().__init__(loader)
def command_list(self, **kwargs) -> JSONResponse:
self.reset()
self.response_model['result'] = [command.to_dict() for command in self.ctx.Commands.DB_COMMANDS]
return JSONResponse(self.response_model)
def command_get_by_module(self, **kwargs) -> JSONResponse:
self.reset()
module_name: str = kwargs.get('module_name', '')
if not module_name:
self.response_model['error'] = self.create_error_response(JSONRPCErrorCode.INVALID_PARAMS, {'module_name': 'The param to use is module_name'})
return JSONResponse(self.response_model, self.http_status_code.HTTP_405_METHOD_NOT_ALLOWED)
self.response_model['result'] = [command.to_dict() for command in self.ctx.Commands.DB_COMMANDS if command.module_name.lower() == module_name.lower()]
return JSONResponse(self.response_model)
def command_get_by_name(self, **kwargs) -> JSONResponse:
self.reset()
command_name: str = kwargs.get('command_name', '')
if not command_name:
self.response_model['error'] = self.create_error_response(JSONRPCErrorCode.INVALID_PARAMS, {'command_name': f'The param to use is command_name'})
return JSONResponse(self.response_model, self.http_status_code.HTTP_405_METHOD_NOT_ALLOWED)
command_to_return: list[dict] = []
for command in self.ctx.Commands.DB_COMMANDS:
if command.command_name.lower() == command_name.lower():
command_to_return.append(command.to_dict())
self.response_model['result'] = command_to_return
return JSONResponse(self.response_model)

View File

@@ -0,0 +1,43 @@
from enum import Enum
class JSONRPCErrorCode(Enum):
PARSE_ERROR = -32700 # Syntax error in the request (malformed JSON)
INVALID_REQUEST = -32600 # Invalid Request (incorrect structure or missing fields)
METHOD_NOT_FOUND = -32601 # Method not found (the requested method does not exist)
INVALID_PARAMS = -32602 # Invalid Params (the parameters provided are incorrect)
INTERNAL_ERROR = -32603 # Internal Error (an internal server error occurred)
# Custom application-specific errors (beyond standard JSON-RPC codes)
CUSTOM_ERROR = 1001 # Custom application-defined error (e.g., user not found)
AUTHENTICATION_ERROR = 1002 # Authentication failure (e.g., invalid credentials)
PERMISSION_ERROR = 1003 # Permission error (e.g., user does not have access to this method)
RESOURCE_NOT_FOUND = 1004 # Resource not found (e.g., the requested resource does not exist)
DUPLICATE_REQUEST = 1005 # Duplicate request (e.g., a similar request has already been processed)
def description(self):
"""Returns a description associated with each error code"""
descriptions = {
JSONRPCErrorCode.PARSE_ERROR: "The JSON request is malformed.",
JSONRPCErrorCode.INVALID_REQUEST: "The request is invalid (missing or incorrect fields).",
JSONRPCErrorCode.METHOD_NOT_FOUND: "The requested method could not be found.",
JSONRPCErrorCode.INVALID_PARAMS: "The parameters provided are invalid.",
JSONRPCErrorCode.INTERNAL_ERROR: "An internal error occurred on the server.",
JSONRPCErrorCode.CUSTOM_ERROR: "A custom error defined by the application.",
JSONRPCErrorCode.AUTHENTICATION_ERROR: "User authentication failed.",
JSONRPCErrorCode.PERMISSION_ERROR: "User does not have permission to access this method.",
JSONRPCErrorCode.RESOURCE_NOT_FOUND: "The requested resource could not be found.",
JSONRPCErrorCode.DUPLICATE_REQUEST: "The request is a duplicate or is already being processed.",
}
return descriptions.get(self, "Unknown error")
def create_error_response(error_code: JSONRPCErrorCode, details: dict = None) -> dict[str, str]:
"""Create a JSON-RPC error!"""
response = {
"code": error_code.value,
"message": error_code.description(),
}
if details:
response["data"] = details
return response

View File

@@ -0,0 +1,45 @@
from typing import TYPE_CHECKING, Optional
from starlette.responses import JSONResponse
from core.classes.interfaces.irpc_endpoint import IRPC
from core.classes.modules.rpc.rpc_errors import JSONRPCErrorCode
if TYPE_CHECKING:
from core.loader import Loader
from core.definition import MUser
class RPCUser(IRPC):
def __init__(self, loader: 'Loader'):
super().__init__(loader)
def user_list(self, **kwargs) -> JSONResponse:
self.reset()
users = self.ctx.User.UID_DB.copy()
copy_users: list['MUser'] = []
for user in users:
copy_user = user.copy()
copy_user.connexion_datetime = copy_user.connexion_datetime.strftime('%d-%m-%Y')
copy_users.append(copy_user)
self.response_model['result'] = [user.to_dict() for user in copy_users]
return JSONResponse(self.response_model)
def user_get(self, **kwargs) -> JSONResponse:
self.reset()
uidornickname = kwargs.get('uid_or_nickname', '')
if not uidornickname:
self.response_model['error'] = self.create_error_response(JSONRPCErrorCode.INVALID_PARAMS, {'uid_or_nickname': 'The param to use is uid_or_nickname'})
return JSONResponse(self.response_model, self.http_status_code.HTTP_405_METHOD_NOT_ALLOWED)
user = self.ctx.User.get_user(uidornickname)
if user:
user_copy = user.copy()
user_copy.connexion_datetime = user_copy.connexion_datetime.strftime('%d-%m-%Y')
self.response_model['result'] = user_copy.to_dict()
return JSONResponse(self.response_model)
self.response_model['result'] = 'User not found!'
return JSONResponse(self.response_model, self.http_status_code.HTTP_404_NOT_FOUND)

View File

@@ -1,4 +1,4 @@
from typing import Optional, Union, TYPE_CHECKING
from typing import Optional, TYPE_CHECKING
if TYPE_CHECKING:
from core.definition import MSasl
@@ -9,13 +9,18 @@ class Sasl:
DB_SASL: list['MSasl'] = []
def __init__(self, loader: 'Loader'):
"""
Args:
loader (Loader): The Loader instance.
"""
self.Logs = loader.Logs # logger
def insert_sasl_client(self, psasl: 'MSasl') -> bool:
"""Insert a new Sasl authentication
Args:
new_user (UserModel): New userModel object
psasl (MSasl): New userModel object
Returns:
bool: True if inserted
@@ -38,7 +43,7 @@ class Sasl:
"""Delete the User starting from the UID
Args:
uid (str): UID of the user
client_uid (str): UID of the user
Returns:
bool: True if deleted

View File

@@ -1,13 +1,15 @@
'''This class should never be reloaded.
'''
"""This class should never be reloaded.
"""
import asyncio
from logging import Logger
from threading import Timer, Thread, RLock
from asyncio.locks import Lock
from socket import socket
from typing import Any, Optional, TYPE_CHECKING
from core.definition import MSModule
from core.definition import MSModule, MAdmin, MThread
if TYPE_CHECKING:
from core.classes.user import User
from core.classes.modules.user import User
class Settings:
"""This Class will never be reloaded.
@@ -15,33 +17,44 @@ class Settings:
the whole life of the app
"""
RUNNING_TIMERS: list[Timer] = []
RUNNING_THREADS: list[Thread] = []
RUNNING_SOCKETS: list[socket] = []
PERIODIC_FUNC: dict[object] = {}
LOCK: RLock = RLock()
RUNNING_TIMERS: list[Timer] = []
RUNNING_THREADS: list[Thread] = []
RUNNING_SOCKETS: list[socket] = []
RUNNING_ASYNC_TASKS: list[asyncio.Task] = []
RUNNING_ASYNC_THREADS: list[MThread] = []
PERIODIC_FUNC: dict[str, Any] = {}
CONSOLE: bool = False
THLOCK: RLock = RLock()
AILOCK: Lock = Lock()
MAIN_SERVER_HOSTNAME: str = None
PROTOCTL_USER_MODES: list[str] = []
PROTOCTL_PREFIX: list[str] = []
CONSOLE: bool = False
SMOD_MODULES: list[MSModule] = []
MAIN_SERVER_HOSTNAME: str = None
MAIN_SERVER_ID: str = None
PROTOCTL_PREFIX_MODES_SIGNES : dict[str, str] = {}
PROTOCTL_PREFIX_SIGNES_MODES : dict[str, str] = {}
PROTOCTL_USER_MODES: list[str] = []
PROTOCTL_CHANNEL_MODES: list[str] = []
PROTOCTL_PREFIX: list[str] = []
SMOD_MODULES: list[MSModule] = []
"""List contains all Server modules"""
__CACHE: dict[str, Any] = {}
__CACHE: dict[str, Any] = {}
"""Use set_cache or get_cache instead"""
__TRANSLATION: dict[str, list[list[str]]] = dict()
__TRANSLATION: dict[str, list[list[str]]] = dict()
"""Translation Varibale"""
__LANG: str = "EN"
__LANG: str = "EN"
__INSTANCE_OF_USER_UTILS: Optional['User'] = None
__INSTANCE_OF_USER_UTILS: Optional['User'] = None
"""Instance of the User Utils class"""
__LOGGER: Optional[Logger] = None
__CURRENT_ADMIN: Optional['MAdmin'] = None
"""The Current Admin Object Model"""
__LOGGER: Optional[Logger] = None
"""Instance of the logger"""
def set_cache(self, key: str, value_to_cache: Any):
@@ -75,6 +88,7 @@ class Settings:
@property
def global_translation(self) -> dict[str, list[list[str]]]:
"""Get/set global translation variable"""
return self.__TRANSLATION
@global_translation.setter
@@ -83,6 +97,7 @@ class Settings:
@property
def global_lang(self) -> str:
"""Global default language."""
return self.__LANG
@global_lang.setter
@@ -97,8 +112,18 @@ class Settings:
def global_user(self, user_utils_instance: 'User') -> None:
self.__INSTANCE_OF_USER_UTILS = user_utils_instance
@property
def current_admin(self) -> MAdmin:
"""Current admin data model."""
return self.__CURRENT_ADMIN
@current_admin.setter
def current_admin(self, current_admin: MAdmin) -> None:
self.__CURRENT_ADMIN = current_admin
@property
def global_logger(self) -> Logger:
"""Global logger Instance"""
return self.__LOGGER
@global_logger.setter

View File

@@ -11,9 +11,13 @@ if TYPE_CHECKING:
class Translation:
def __init__(self, loader: 'Loader') -> None:
"""
Args:
loader (Loader): The Loader instance.
"""
self.Logs = loader.Logs
self.Settings = loader.Settings
return None
def get_translation(self) -> dict[str, list[list[str]]]:
try:
@@ -89,4 +93,4 @@ class Translation:
except Exception as err:
self.Logs.error(f'General Error: {err}')
return {}
return dict()

View File

@@ -11,14 +11,16 @@ class User:
UID_DB: list['MUser'] = []
@property
def get_current_user(self) -> 'MUser':
return self.current_user
def current_user(self) -> 'MUser':
return self._current_user
@current_user.setter
def current_user(self, muser: 'MUser') -> None:
self._current_user = muser
def __init__(self, loader: 'Loader'):
self.Logs = loader.Logs
self.Base = loader.Base
self.current_user: Optional['MUser'] = None
self._ctx = loader
self._current_user: Optional['MUser'] = None
def insert(self, new_user: 'MUser') -> bool:
"""Insert a new User object
@@ -55,7 +57,7 @@ class User:
return False
user_obj.nickname = new_nickname
self._ctx.Logs.debug(f"UID ({uid}) has benn update with new nickname ({new_nickname}).")
return True
def update_mode(self, uidornickname: str, modes: str) -> bool:
@@ -94,7 +96,7 @@ class User:
return False
liste_umodes = list(umodes)
final_umodes_liste = [x for x in self.Base.Settings.PROTOCTL_USER_MODES if x in liste_umodes]
final_umodes_liste = [x for x in self._ctx.Base.Settings.PROTOCTL_USER_MODES if x in liste_umodes]
final_umodes = ''.join(final_umodes_liste)
user_obj.umodes = f"+{final_umodes}"

View File

@@ -1,19 +0,0 @@
from typing import Literal, TYPE_CHECKING
from .protocols.unreal6 import Unrealircd6
from .protocols.inspircd import Inspircd
if TYPE_CHECKING:
from core.irc import Irc
class Protocol:
def __init__(self, protocol: Literal['unreal6','inspircd'], ircInstance: 'Irc'):
self.Protocol = None
match protocol:
case 'unreal6':
self.Protocol: Unrealircd6 = Unrealircd6(ircInstance)
case 'inspircd':
self.Protocol: Inspircd = Inspircd(ircInstance)
case _:
self.Protocol: Unrealircd6 = Unrealircd6(ircInstance)

View File

@@ -0,0 +1,54 @@
from typing import Optional, TYPE_CHECKING
if TYPE_CHECKING:
from core.definition import MIrcdCommand
from core.loader import Loader
class CommandHandler:
DB_IRCDCOMMS: list['MIrcdCommand'] = []
DB_SUBSCRIBE: list = []
def __init__(self, loader: 'Loader'):
"""Init method
Args:
loader (Loader): The loader Object
"""
self.__Logs = loader.Logs
def register(self, ircd_command_model: 'MIrcdCommand') -> None:
"""Register a new command in the Handler
Args:
ircd_command_model (MIrcdCommand): The IRCD Command Object
"""
ircd_command = self.get_registred_ircd_command(ircd_command_model.command_name)
if ircd_command is None:
self.__Logs.debug(f'[IRCD COMMAND HANDLER] New IRCD command ({ircd_command_model.command_name}) added to the handler.')
self.DB_IRCDCOMMS.append(ircd_command_model)
return None
else:
self.__Logs.debug(f'[IRCD COMMAND HANDLER] This IRCD command ({ircd_command.command_name}) already exist in the handler.')
return None
def get_registred_ircd_command(self, command_name: str) -> Optional['MIrcdCommand']:
"""Get the registred IRCD command model
Returns:
MIrcdCommand: The IRCD Command object
"""
com = command_name.upper()
for ircd_com in self.DB_IRCDCOMMS:
if com == ircd_com.command_name.upper():
return ircd_com
return None
def get_ircd_commands(self) -> list['MIrcdCommand']:
"""Get the list of IRCD Commands
Returns:
list[MIrcdCommand]: a list of all registred commands
"""
return self.DB_IRCDCOMMS.copy()

View File

@@ -0,0 +1,32 @@
from typing import TYPE_CHECKING, Optional
from .unreal6 import Unrealircd6
from .inspircd import Inspircd
from ..interfaces.iprotocol import IProtocol
if TYPE_CHECKING:
from core.loader import Loader
class ProtocolFactorty:
def __init__(self, context: 'Loader'):
"""ProtocolFactory init.
Args:
context (Loader): The Context object
"""
self.__ctx = context
def get(self) -> Optional[IProtocol]:
protocol = self.__ctx.Config.SERVEUR_PROTOCOL
match protocol:
case 'unreal6':
self.__ctx.Logs.debug(f"[PROTOCOL] {protocol} has been loaded")
return Unrealircd6(self.__ctx)
case 'inspircd':
self.__ctx.Logs.debug(f"[PROTOCOL] {protocol} has been loaded")
return Inspircd(self.__ctx)
case _:
self.__ctx.Logs.critical(f"[PROTOCOL ERROR] This protocol name ({protocol} is not valid!)")
raise Exception("Unknown protocol!")

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,8 +1,13 @@
import asyncio
import concurrent
import concurrent.futures
import threading
from datetime import datetime
from json import dumps
from dataclasses import dataclass, field, asdict, fields
from dataclasses import dataclass, field, asdict, fields, replace
from typing import Literal, Any, Optional
from os import sep
from core.classes.interfaces.imodule import IModule
@dataclass
class MainModel:
@@ -14,6 +19,10 @@ class MainModel:
def to_json(self) -> str:
"""Return the object of a dataclass a json str."""
return dumps(self.to_dict())
def copy(self):
"""Return the object of a dataclass a json str."""
return replace(self)
def get_attributes(self) -> list[str]:
"""Return a list of attributes name"""
@@ -31,6 +40,7 @@ class MClient(MainModel):
umodes: str = None
vhost: str = None
fingerprint: str = None
tls_cipher: str = None
isWebirc: bool = False
isWebsocket: bool = False
remote_ip: str = None
@@ -50,6 +60,7 @@ class MUser(MainModel):
umodes: str = None
vhost: str = None
fingerprint: str = None
tls_cipher: str = None
isWebirc: bool = False
isWebsocket: bool = False
remote_ip: str = None
@@ -70,12 +81,14 @@ class MAdmin(MainModel):
umodes: str = None
vhost: str = None
fingerprint: str = None
tls_cipher: str = None
isWebirc: bool = False
isWebsocket: bool = False
remote_ip: str = None
score_connexion: int = 0
geoip: str = None
connexion_datetime: datetime = field(default=datetime.now())
language: str = "EN"
level: int = 0
@dataclass
@@ -89,6 +102,7 @@ class MReputation(MainModel):
umodes: str = None
vhost: str = None
fingerprint: str = None
tls_cipher: str = None
isWebirc: bool = False
isWebsocket: bool = False
remote_ip: str = None
@@ -200,6 +214,15 @@ class MConfig(MainModel):
PASSWORD: str = "password"
"""The password of the admin of the service"""
RPC_HOST: str = "127.0.0.1"
"""The host to bind. Default: 127.0.0.1"""
RPC_PORT: int = 5000
"""The port of the defender json rpc. Default: 5000"""
RPC_USERS: list[dict] = field(default_factory=list)
"""The Defender rpc users"""
JSONRPC_URL: str = None
"""The RPC url, if local https://127.0.0.1:PORT/api should be fine"""
@@ -331,6 +354,15 @@ class MConfig(MainModel):
self.SERVEUR_CHARSET: list = ["utf-8", "iso-8859-1"]
"""0: utf-8 | 1: iso-8859-1"""
@dataclass
class MThread(MainModel):
name: str
thread_id: Optional[int]
thread_event: Optional[threading.Event]
thread_obj: threading.Thread
executor: concurrent.futures.ThreadPoolExecutor
future: asyncio.Future
@dataclass
class MCommand(MainModel):
module_name: str = None
@@ -342,7 +374,15 @@ class MCommand(MainModel):
class MModule(MainModel):
module_name: str = None
class_name: str = None
class_instance: Optional[Any] = None
class_instance: Optional[IModule] = None
@dataclass
class DefenderModuleHeader(MainModel):
name: str = ''
version: str = ''
description: str = ''
author: str = ''
core_version: str = ''
@dataclass
class MSModule:
@@ -362,5 +402,16 @@ class MSasl(MainModel):
username: Optional[str] = None
password: Optional[str] = None
fingerprint: Optional[str] = None
language: str = "EN"
auth_success: bool = False
level: int = 0
level: int = 0
@dataclass
class MRegister:
command_name: str
func: Any
@dataclass
class MIrcdCommand:
command_name: str
func: Any

150
core/install.py Normal file
View File

@@ -0,0 +1,150 @@
import argparse
import os
import sys
import json
from dataclasses import dataclass
from subprocess import check_call, CalledProcessError, check_output
from pathlib import Path
from platform import python_version_tuple
import traceback
parser = argparse.ArgumentParser(description="Python Installation Code")
parser.add_argument('--check-version', action='store_true', help='Check if the python version is ok!')
parser.add_argument('--install', action='store_true', help='Run the installation')
parser.add_argument('--git-update', action='store_true', help='Update from git (main repository)')
args = parser.parse_args()
PYTHON_REQUIRED_VERSION = (3, 10, 0)
PYTHON_SYSTEM_VERSION = tuple(map(int, python_version_tuple()))
ROOT_PATH = os.getcwd()
PYENV = Path(ROOT_PATH).joinpath('.pyenv/bin/python') if os.name != 'nt' else Path(ROOT_PATH).joinpath('.pyenv/Scripts/python.exe')
PIPENV = Path(f'{ROOT_PATH}/.pyenv/bin/pip') if os.name != 'nt' else Path(f'{ROOT_PATH}/.pyenv/Scripts/pip.exe')
USER_HOME_DIRECTORY = Path.home()
SYSTEMD_PATH = Path(USER_HOME_DIRECTORY).joinpath('.config', 'systemd', 'user')
PY_EXEC = 'defender.py'
SERVICE_FILE_NAME = 'defender.service'
@dataclass
class Package:
name: str = None
version: str = None
def __load_required_package_versions() -> list[Package]:
"""This will create Package model with package names and required version
"""
try:
DB_PACKAGES: list[Package] = []
version_filename = Path(ROOT_PATH).joinpath('version.json') # f'.{os.sep}version.json'
with open(version_filename, 'r') as version_data:
package_info:dict[str, str] = json.load(version_data)
for name, version in package_info.items():
if name == 'version':
continue
DB_PACKAGES.append(
Package(name=name, version=version)
)
return DB_PACKAGES
except FileNotFoundError as fe:
print(f"File not found: {fe}")
except Exception as err:
print(f"General Error: {err}")
def update_packages() -> None:
try:
newVersion = False
db_packages = __load_required_package_versions()
print(ROOT_PATH)
if sys.prefix not in PYENV.__str__():
print(f"You are probably running a new installation or you are not using your virtual env {PYENV}")
return newVersion
print(f"> Checking for dependencies versions ==> WAIT")
for package in db_packages:
newVersion = False
_required_version = package.version
_installed_version: str = None
output = check_output([PIPENV, 'show', package.name])
for line in output.decode().splitlines():
if line.startswith('Version:'):
_installed_version = line.split(':')[1].strip()
break
required_version = tuple(map(int, _required_version.split('.')))
installed_version = tuple(map(int, _installed_version.split('.')))
if required_version > installed_version:
print(f'> New version of {package.name} is available {installed_version} ==> {required_version}')
newVersion = True
if newVersion:
check_call([PIPENV, 'install', '--upgrade', package.name])
print(f"> Dependencies versions ==> OK")
return newVersion
except CalledProcessError:
print(f"[!] Package {package.name} not installed [!]")
except Exception as err:
print(f"UpdatePackage Error: {err}")
traceback.print_exc()
def run_git_update() -> None:
check_call(['git', 'pull', 'origin', 'main'])
def check_python_requirement():
if PYTHON_SYSTEM_VERSION < PYTHON_REQUIRED_VERSION:
raise RuntimeError(f"Your Python Version is not meeting the requirement, System Version: {PYTHON_SYSTEM_VERSION} < Required Version {PYTHON_REQUIRED_VERSION}")
def create_service_file():
pyenv = PYENV
systemd_path = SYSTEMD_PATH
py_exec = PY_EXEC
service_file_name = SERVICE_FILE_NAME
if not Path(systemd_path).exists():
print("[!] Folder not available")
sys.exit(1)
contain = f'''[Unit]
Description=Defender IRC Service
[Service]
ExecStart={pyenv} {py_exec}
WorkingDirectory={ROOT_PATH}
SyslogIdentifier=Defender
Restart=on-failure
[Install]
WantedBy=default.target
'''
with open(Path(systemd_path).joinpath(service_file_name), "w") as file:
file.write(contain)
print('Service file generated with current configuration')
print('Running IRC Service ...')
print(f"#"*24)
print("Installation complete ...")
print("If the configuration is correct, then you must see your service connected to your irc server")
print(f"If any issue, you can see the log file for debug {ROOT_PATH}{os.sep}logs{os.sep}defender.log")
print(f"#"*24)
def main():
if args.check_version:
check_python_requirement()
sys.exit(0)
if args.install:
create_service_file()
sys.exit(0)
if args.git_update:
run_git_update()
sys.exit(0)
if __name__ == "__main__":
main()

View File

@@ -1,314 +0,0 @@
import os
import json
from sys import exit, prefix
from dataclasses import dataclass
from subprocess import check_call, run, CalledProcessError, PIPE, check_output
from platform import python_version, python_version_tuple
class Install:
@dataclass
class CoreConfig:
install_log_file: str
unix_systemd_folder: str
service_file_name: str
service_cmd_executable: list
service_cmd_daemon_reload: list
defender_main_executable: str
python_min_version: str
python_current_version_tuple: tuple[int, int, int]
python_current_version: tuple[int, int, int]
defender_install_folder: str
venv_folder: str
venv_cmd_installation: list
venv_cmd_requirements: list[str]
venv_pip_executable: str
venv_python_executable: str
@dataclass
class Package:
name: str = None
version: str = None
DB_PACKAGES: list[Package] = []
def __init__(self) -> None:
self.set_configuration()
if self.skip_install:
self.install_dependencies()
self.check_packages_version()
return None
self.check_packages_version()
# Sinon tester les dependances python et les installer avec pip
if self.do_install():
self.install_dependencies()
self.create_service_file()
self.print_final_message()
return None
def set_configuration(self):
self.skip_install = False
defender_install_folder = os.getcwd()
venv_folder = '.pyenv'
unix_user_home_directory = os.path.expanduser("~")
unix_systemd_folder = os.path.join(unix_user_home_directory, '.config', 'systemd', 'user')
defender_main_executable = os.path.join(defender_install_folder, 'defender.py')
self.config = self.CoreConfig(
install_log_file='install.log',
unix_systemd_folder=unix_systemd_folder,
service_file_name='defender.service',
service_cmd_executable=['systemctl', '--user', 'start', 'defender'],
service_cmd_daemon_reload=['systemctl', '--user', 'daemon-reload'],
defender_main_executable=defender_main_executable,
python_min_version=(3, 10, 0),
python_current_version_tuple=tuple(map(int, 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','pyyaml','unrealircd_rpc_py'],
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'
)
if not self.check_python_version():
# If the Python version is not good then Exit
exit("[!] Python version error [!]")
if not os.path.exists(os.path.join(self.config.defender_install_folder, 'config', 'configuration.json')):
# If configuration file do not exist
exit("[!] Configuration file (core/configuration.json) doesn't exist! please create it [!]")
# Exclude Windows OS from the installation
if os.name == 'nt':
# If windows, modify pip and python virtual environment executable
self.config.venv_pip_executable = f'{os.path.join(defender_install_folder, venv_folder, "Scripts")}{os.sep}pip.exe'
self.config.venv_python_executable = f'{os.path.join(defender_install_folder, venv_folder, "Scripts")}{os.sep}python.exe'
self.skip_install = True
return False
if self.is_root():
exit(f'[!] I highly not recommend running Defender as root [!]')
self.skip_install = True
return False
def is_root(self) -> bool:
if os.geteuid() != 0:
print('> User without privileges ==> OK')
return False
elif os.geteuid() == 0:
print('[!] Do not use root to install Defender [!]')
exit("Do not use root to install Defender")
return True
def do_install(self) -> bool:
full_service_file_path = os.path.join(self.config.unix_systemd_folder, self.config.service_file_name)
if not os.path.exists(full_service_file_path):
print(f'[!] Service file does not exist [!]')
return True
# Check if virtual env exist
if not os.path.exists(f'{os.path.join(self.config.defender_install_folder, self.config.venv_folder)}'):
self.run_subprocess(self.config.venv_cmd_installation)
print(f'[!] Virtual env does not exist run the install [!]')
return True
def run_subprocess(self, command:list) -> None:
print(f'> {command}')
try:
check_call(command)
print("The command completed successfully.")
except CalledProcessError as e:
print(f"The command failed with the return code: {e.returncode}")
print(f"Try to install dependencies ...")
exit(5)
def get_packages_version_from_json(self) -> None:
"""This will create Package model with package names and required version
"""
try:
version_filename = f'.{os.sep}version.json'
with open(version_filename, 'r') as version_data:
package_info:dict[str, str] = json.load(version_data)
for name, version in package_info.items():
if name == 'version':
continue
self.DB_PACKAGES.append(
self.Package(name=name, version=version)
)
return None
except FileNotFoundError as fe:
print(f"File not found: {fe}")
except Exception as err:
print(f"General Error: {err}")
def check_packages_version(self) -> bool:
try:
newVersion = False
self.get_packages_version_from_json()
if not self.config.venv_folder in prefix:
print(f"You are probably running a new installation or you are not using your virtual env {self.config.venv_folder}")
return newVersion
print(f"> Checking for dependencies versions ==> WAIT")
for package in self.DB_PACKAGES:
newVersion = False
_required_version = package.version
_installed_version: str = None
output = check_output([self.config.venv_pip_executable, 'show', package.name])
for line in output.decode().splitlines():
if line.startswith('Version:'):
_installed_version = line.split(':')[1].strip()
break
required_version = tuple(map(int, _required_version.split('.')))
installed_version = tuple(map(int, _installed_version.split('.')))
if required_version > installed_version:
print(f'> New version of {package.name} is available {installed_version} ==> {required_version}')
newVersion = True
if newVersion:
self.run_subprocess([self.config.venv_pip_executable, 'install', '--upgrade', package.name])
print(f"> Dependencies versions ==> OK")
return newVersion
except CalledProcessError:
print(f"[!] Package {package.name} not installed [!]")
except Exception as err:
print(f"General Error: {err}")
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
"""
if self.config.python_current_version_tuple < self.config.python_min_version:
print(f"## Your python version must be greather than or equal to {self.config.python_min_version} ##")
return False
print(f"> Version of python : {self.config.python_current_version} ==> OK")
return True
def check_package(self, package_name) -> bool:
try:
# Run a command in the virtual environment's Python to check if the package is installed
run([self.config.venv_python_executable, '-c', f'import {package_name}'], check=True, stdout=PIPE, stderr=PIPE)
return True
except CalledProcessError as cpe:
print(cpe)
return False
def install_dependencies(self) -> None:
"""### Verifie les dépendances si elles sont installées
- Test si les modules sont installés
- Met a jour pip
- Install les modules manquants
"""
do_install = False
# 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)
do_install = True
for module in self.config.venv_cmd_requirements:
module = module.replace('pyyaml', 'yaml')
if not self.check_package(module):
do_install = True
if not do_install:
return None
print("===> Clean pip cache")
self.run_subprocess([self.config.venv_pip_executable, 'cache', 'purge'])
print("===> Check if pip is up to date")
self.run_subprocess([self.config.venv_python_executable, '-m', 'pip', 'install', '--upgrade', 'pip'])
if not self.check_package('greenlet'):
self.run_subprocess([self.config.venv_pip_executable, 'install', '--only-binary', ':all:', 'greenlet'])
print('====> Greenlet installed')
for module in self.config.venv_cmd_requirements:
if not self.check_package(module):
print("### Trying to install missing python packages ###")
self.run_subprocess([self.config.venv_pip_executable, 'install', module])
print(f"====> Module {module} installed!")
else:
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 [!]')
self.run_subprocess(self.config.service_cmd_executable)
return None
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=default.target
'''
# 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])
with open(full_service_file_path, 'w+') as servicefile:
servicefile.write(contain)
servicefile.close()
print('Service file generated with current configuration')
print('Running IRC Service ...')
self.run_subprocess(self.config.service_cmd_daemon_reload)
self.run_subprocess(self.config.service_cmd_executable)
else:
with open(full_service_file_path, 'w+') as servicefile:
servicefile.write(contain)
servicefile.close()
print('Service file generated with current configuration')
print('Running IRC Service ...')
self.run_subprocess(self.config.service_cmd_daemon_reload)
self.run_subprocess(self.config.service_cmd_executable)
def print_final_message(self) -> None:
print(f"#"*24)
print("Installation complete ...")
print("If the configuration is correct, then you must see your service connected to your irc server")
print(f"If any issue, you can see the log file for debug {self.config.defender_install_folder}{os.sep}logs{os.sep}defender.log")
print(f"#"*24)
exit(1)

File diff suppressed because it is too large Load Diff

View File

@@ -2,3 +2,20 @@ traduction:
# Message help
- orig: "Access denied!"
trad: "Accès refusé."
- orig: "Wrong password!"
trad: "Mot de passe incorrect!"
- orig: "%s - %sLoaded%s by %s on %s"
trad: "%s - %sChargé%s par %s le %s"
- orig: "Module %s loaded!"
trad: "Module %s chargé!"
- orig: "cmd method is not available in the module (%s)"
trad: "La méthode cmd n'est pas disponible dans le module (%s)"
- orig: "[%sMODULE ERROR%s] Module %s is facing issues! %s"
trad: "[%sMODULE ERREUR%s] Le module %s a rencontré une erreur! %s"
- orig: "%s - %sNot Loaded%s"
trad: "%s - %sNon chargé%s"
- orig: "Successfuly connected to %s"
trad: "Connecter a %s avec succés"
- orig: "[ %sINFORMATION%s ] >> %s is ready!"
trad: "[ %sINFORMATION%s ] >> %s est prêt!"

View File

@@ -1,16 +1,28 @@
from logging import Logger
from core.classes.settings import global_settings
from core.classes import translation, user, admin, client, channel, reputation, settings, sasl
from core.classes.modules.settings import global_settings
from core.classes.modules import translation, user, admin, client, channel, reputation, settings, sasl
import core.logs as logs
import core.definition as df
import core.utils as utils
import core.base as base_mod
import core.module as module_mod
import core.classes.commands as commands_mod
import core.classes.config as conf_mod
import core.classes.modules.commands as commands_mod
import core.classes.modules.config as conf_mod
import core.classes.modules.rpc.rpc as rpc_mod
import core.irc as irc
import core.classes.protocols.factory as factory
class Loader:
_instance = None
def __new__(cls, *agrs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
# Load Main Modules
@@ -24,6 +36,8 @@ class Loader:
self.LoggingModule: logs = logs
self.RpcServerModule: rpc_mod = rpc_mod
self.Utils: utils = utils
# Load Classes
@@ -33,7 +47,7 @@ class Loader:
self.Logs: Logger = self.ServiceLogging.get_logger()
self.Config: df.MConfig = self.ConfModule.Configuration(self).get_config_model()
self.Config: df.MConfig = self.ConfModule.Configuration(self).configuration_model
self.Settings.global_lang = self.Config.LANG if self.Config.LANG else "EN"
@@ -63,4 +77,13 @@ class Loader:
self.Sasl: sasl.Sasl = sasl.Sasl(self)
self.Irc: irc.Irc = irc.Irc(self)
self.PFactory: factory.ProtocolFactorty = factory.ProtocolFactorty(self)
self.RpcServer: rpc_mod.JSonRpcServer = rpc_mod.JSonRpcServer(self)
self.Logs.debug(self.Utils.tr("Loader %s success", __name__))
async def start(self):
await self.Base.init()

View File

@@ -15,7 +15,7 @@ class ServiceLogging:
self.SERVER_PREFIX = None
self.LOGGING_CONSOLE = True
self.LOG_FILTERS: list[str] = ['PING', f":{self.SERVER_PREFIX}auth", "['PASS'"]
self.LOG_FILTERS: list[str] = ["PING", f":{self.SERVER_PREFIX}auth", "['PASS'"]
self.file_handler = None
self.stdout_handler = None

View File

@@ -1,27 +1,26 @@
'''
This is the main operational file to handle modules
'''
from pathlib import Path
import sys
import importlib
from pathlib import Path
from types import ModuleType
from typing import TYPE_CHECKING, Optional
from core.definition import MModule
from core.definition import DefenderModuleHeader, MModule
from core.utils import tr
if TYPE_CHECKING:
from core.loader import Loader
from core.irc import Irc
from core.classes.interfaces.imodule import IModule
class Module:
DB_MODULES: list[MModule] = []
DB_MODULE_HEADERS: list[DefenderModuleHeader] = []
def __init__(self, loader: 'Loader') -> None:
self.__Loader = loader
self.__Base = loader.Base
self.__Logs = loader.Logs
self.__Utils = loader.Utils
self.__Config = loader.Config
self._ctx = loader
def get_all_available_modules(self) -> list[str]:
"""Get list of all main modules
@@ -31,71 +30,126 @@ class Module:
list[str]: List of all module names.
"""
base_path = Path('mods')
return [file.name.replace('.py', '') for file in base_path.rglob('mod_*.py')]
modules_available = [file.name.replace('.py', '') for file in base_path.rglob('mod_*.py')]
self._ctx.Logs.debug(f"Modules available: {modules_available}")
return modules_available
def get_module_information(self, module_name: str) -> tuple[str, str, str]:
def get_module_information(self, module_name: str) -> tuple[Optional[str], Optional[str], Optional[str]]:
# module_name : mod_defender
if not module_name.lower().startswith('mod_'):
return None, None, None
module_name = module_name.lower()
module_name = module_name.lower() # --> mod_defender
module_folder = module_name.split('_')[1].lower() # --> defender
class_name = module_name.split('_')[1].capitalize() # --> Defender
self._ctx.Logs.debug(f"Module information Folder: {module_folder}, Name: {module_name}, Class: {class_name}")
return module_folder, module_name, class_name
def load_one_module(self, uplink: 'Irc', module_name: str, nickname: str, is_default: bool = False) -> bool:
def get_module_header(self, module_name: str) -> Optional[DefenderModuleHeader]:
for mod_h in self.DB_MODULE_HEADERS:
if module_name.lower() == mod_h.name.lower():
self._ctx.Logs.debug(f"Module Header found: {mod_h}")
return mod_h
return None
def create_module_header(self, module_header: dict[str, str]) -> bool:
"""Create a new module header into DB_MODULE_HEADERS
Args:
module_header (dict[str, str]): The module header
Returns:
bool: True if the module header has been created.
"""
mod_header = DefenderModuleHeader(**module_header)
if self.get_module_header(mod_header.name) is None:
self._ctx.Logs.debug(f"[MOD_HEADER] The module header has been created! ({mod_header.name} v{mod_header.version})")
self.DB_MODULE_HEADERS.append(mod_header)
return True
return False
def delete_module_header(self, module_name: str) -> bool:
mod_header = self.get_module_header(module_name)
if mod_header is not None:
self._ctx.Logs.debug(f"[MOD_HEADER] The module header has been deleted ({mod_header.name} v{mod_header.version})")
self.DB_MODULE_HEADERS.remove(mod_header)
return True
self._ctx.Logs.debug(f"[MOD_HEADER ERROR] Impossible to remove the module header ({module_name})")
return False
async def load_one_module(self, module_name: str, nickname: str, is_default: bool = False) -> bool:
module_folder, module_name, class_name = self.get_module_information(module_name)
if module_folder is None or module_name is None or class_name is None:
self.__Logs.error(f"There is an error with the module name! {module_folder}, {module_name}, {class_name}")
self._ctx.Logs.error(f"There is an error with the module name! {module_folder}, {module_name}, {class_name}")
return False
if self.is_module_exist_in_sys_module(module_name):
self.__Logs.debug(f"Module [{module_folder}.{module_name}] already loaded!")
self._ctx.Logs.debug(f"Module [{module_folder}.{module_name}] already loaded!")
if self.model_is_module_exist(module_name):
# Si le module existe dans la variable globale retourne False
self.__Logs.debug(f"Module [{module_folder}.{module_name}] exist in the local variable!")
uplink.Protocol.send_priv_msg(
nick_from=self.__Config.SERVICE_NICKNAME,
msg=f"Le module {module_name} est déja chargé ! si vous souhaiter le recharge tapez {self.__Config.SERVICE_PREFIX}reload {module_name}",
channel=self.__Config.SERVICE_CHANLOG
self._ctx.Logs.debug(f"Module [{module_folder}.{module_name}] exist in the local variable!")
await self._ctx.Irc.Protocol.send_priv_msg(
nick_from=self._ctx.Config.SERVICE_NICKNAME,
msg=f"Le module {module_name} est déja chargé ! si vous souhaiter le recharge tapez {self._ctx.Config.SERVICE_PREFIX}reload {module_name}",
channel=self._ctx.Config.SERVICE_CHANLOG
)
return False
return self.reload_one_module(uplink, module_name, nickname)
return self.reload_one_module(module_name, nickname)
# Charger le module
loaded_module = importlib.import_module(f'mods.{module_folder}.{module_name}')
my_class = getattr(loaded_module, class_name, None) # Récuperer le nom de classe
create_instance_of_the_class = my_class(uplink) # Créer une nouvelle instance de la classe
try:
loaded_module = importlib.import_module(f'mods.{module_folder}.{module_name}')
my_class = getattr(loaded_module, class_name, None) # Récuperer le nom de classe
create_instance_of_the_class: 'IModule' = my_class(self._ctx) # Créer une nouvelle instance de la classe
await create_instance_of_the_class.load() if self._ctx.Utils.is_coroutinefunction(create_instance_of_the_class.load) else create_instance_of_the_class.load()
self.create_module_header(create_instance_of_the_class.MOD_HEADER)
except AttributeError as attr:
red = self._ctx.Config.COLORS.red
nogc = self._ctx.Config.COLORS.red
await self._ctx.Irc.Protocol.send_priv_msg(
nick_from=self._ctx.Config.SERVICE_NICKNAME,
msg=tr("[%sMODULE ERROR%s] Module %s is facing issues! %s", red, nogc, module_name, attr),
channel=self._ctx.Config.SERVICE_CHANLOG
)
self._ctx.Logs.error(msg=attr, exc_info=True)
return False
if not hasattr(create_instance_of_the_class, 'cmd'):
uplink.Protocol.send_priv_msg(
nick_from=self.__Config.SERVICE_NICKNAME,
msg=f"Module {module_name} ne contient pas de méthode cmd",
channel=self.__Config.SERVICE_CHANLOG
await self._ctx.Irc.Protocol.send_priv_msg(
nick_from=self._ctx.Config.SERVICE_NICKNAME,
msg=tr("cmd method is not available in the module (%s)", module_name),
channel=self._ctx.Config.SERVICE_CHANLOG
)
self.__Logs.critical(f"The Module {module_name} has not been loaded because cmd method is not available")
self.db_delete_module(module_name)
self._ctx.Logs.critical(f"The Module {module_name} has not been loaded because cmd method is not available")
await self.db_delete_module(module_name)
return False
# Charger la nouvelle class dans la variable globale
if self.model_insert_module(MModule(module_name, class_name, create_instance_of_the_class)):
# Enregistrer le module dans la base de données
self.db_register_module(module_name, nickname, is_default)
uplink.Protocol.send_priv_msg(
nick_from=self.__Config.SERVICE_NICKNAME,
msg=f"Module {module_name} chargé",
channel=self.__Config.SERVICE_CHANLOG
await self.db_register_module(module_name, nickname, is_default)
await self._ctx.Irc.Protocol.send_priv_msg(
nick_from=self._ctx.Config.SERVICE_NICKNAME,
msg=tr("Module %s loaded!", module_name),
channel=self._ctx.Config.SERVICE_CHANLOG
)
self.__Logs.debug(f"Module {class_name} has been loaded")
self._ctx.Logs.debug(f"Module {class_name} has been loaded")
return True
return False
def load_all_modules(self) -> bool:
...
def reload_one_module(self, uplink: 'Irc', module_name: str, nickname: str) -> bool:
async def reload_one_module(self, module_name: str, nickname: str) -> bool:
"""Reloading one module and insert it into the model as well as the database
Args:
@@ -107,20 +161,21 @@ class Module:
bool: True if the module has been reloaded
"""
module_folder, module_name, class_name = self.get_module_information(module_name)
red = self.__Config.COLORS.red
nogc = self.__Config.COLORS.nogc
red = self._ctx.Config.COLORS.red
nogc = self._ctx.Config.COLORS.nogc
try:
if self.is_module_exist_in_sys_module(module_name):
module_model = self.model_get_module(module_name)
if module_model:
module_model.class_instance.unload()
self.delete_module_header(module_model.class_instance.MOD_HEADER['name'])
await module_model.class_instance.unload() if self._ctx.Utils.is_coroutinefunction(module_model.class_instance.unload) else module_model.class_instance.unload()
else:
uplink.Protocol.send_priv_msg(
nick_from=self.__Config.SERVICE_NICKNAME,
msg=f"[ {red}RELOAD MODULE ERROR{nogc} ] Module [{module_folder}.{module_name}] hasn't been reloaded! You must use {self.__Config.SERVICE_PREFIX}load {module_name}",
channel=self.__Config.SERVICE_CHANLOG
await self._ctx.Irc.Protocol.send_priv_msg(
nick_from=self._ctx.Config.SERVICE_NICKNAME,
msg=f"[ {red}RELOAD MODULE ERROR{nogc} ] Module [{module_folder}.{module_name}] hasn't been reloaded! You must use {self._ctx.Config.SERVICE_PREFIX}load {module_name}",
channel=self._ctx.Config.SERVICE_CHANLOG
)
self.__Logs.debug(f"Module [{module_folder}.{module_name}] not found! Please use {self.__Config.SERVICE_PREFIX}load {module_name}")
self._ctx.Logs.debug(f"Module [{module_folder}.{module_name}] not found! Please use {self._ctx.Config.SERVICE_PREFIX}load {module_name}")
return False
# reload module dependencies
@@ -129,36 +184,38 @@ class Module:
the_module = sys.modules[f'mods.{module_folder}.{module_name}']
importlib.reload(the_module)
my_class = getattr(the_module, class_name, None)
new_instance = my_class(uplink)
new_instance: 'IModule' = my_class(self._ctx)
await new_instance.load() if self._ctx.Utils.is_coroutinefunction(new_instance.load) else new_instance.load()
self.create_module_header(new_instance.MOD_HEADER)
module_model.class_instance = new_instance
# Créer le module dans la base de données
self.db_register_module(module_name, nickname)
uplink.Protocol.send_priv_msg(
nick_from=self.__Config.SERVICE_NICKNAME,
await self.db_register_module(module_name, nickname)
await self._ctx.Irc.Protocol.send_priv_msg(
nick_from=self._ctx.Config.SERVICE_NICKNAME,
msg=f"Module [{module_folder}.{module_name}] has been reloaded!",
channel=self.__Config.SERVICE_CHANLOG
channel=self._ctx.Config.SERVICE_CHANLOG
)
self.__Logs.debug(f"Module [{module_folder}.{module_name}] reloaded!")
self._ctx.Logs.debug(f"Module [{module_folder}.{module_name}] reloaded!")
return True
else:
# Module is not loaded! Nothing to reload
self.__Logs.debug(f"[RELOAD MODULE ERROR] [{module_folder}.{module_name}] is not loaded! You must use {self.__Config.SERVICE_PREFIX}load {module_name}")
uplink.Protocol.send_priv_msg(
nick_from=self.__Config.SERVICE_NICKNAME,
msg=f"[ {red}RELOAD MODULE ERROR{nogc} ] Module [{module_folder}.{module_name}] is not loaded! You must use {self.__Config.SERVICE_PREFIX}load {module_name}",
channel=self.__Config.SERVICE_CHANLOG
self._ctx.Logs.debug(f"[RELOAD MODULE ERROR] [{module_folder}.{module_name}] is not loaded! You must use {self._ctx.Config.SERVICE_PREFIX}load {module_name}")
await self._ctx.Irc.Protocol.send_priv_msg(
nick_from=self._ctx.Config.SERVICE_NICKNAME,
msg=f"[ {red}RELOAD MODULE ERROR{nogc} ] Module [{module_folder}.{module_name}] is not loaded! You must use {self._ctx.Config.SERVICE_PREFIX}load {module_name}",
channel=self._ctx.Config.SERVICE_CHANLOG
)
return False
except (TypeError, AttributeError, KeyError, Exception) as err:
self.__Logs.error(f"[RELOAD MODULE ERROR]: {err}")
uplink.Protocol.send_priv_msg(
nick_from=self.__Config.SERVICE_NICKNAME,
self._ctx.Logs.error(f"[RELOAD MODULE ERROR]: {err}", exc_info=True)
await self._ctx.Irc.Protocol.send_priv_msg(
nick_from=self._ctx.Config.SERVICE_NICKNAME,
msg=f"[RELOAD MODULE ERROR]: {err}",
channel=self.__Config.SERVICE_CHANLOG
channel=self._ctx.Config.SERVICE_CHANLOG
)
self.db_delete_module(module_name)
await self.db_delete_module(module_name)
def reload_all_modules(self) -> bool:
...
@@ -184,37 +241,41 @@ class Module:
try:
if 'mod_' not in name and 'schemas' not in name:
importlib.reload(module)
self.__Logs.debug(f'[LOAD_MODULE] Module {module} success')
self._ctx.Logs.debug(f'[LOAD_MODULE] Module {module} success')
except Exception as err:
self.__Logs.error(f'[LOAD_MODULE] Module {module} failed [!] - {err}')
self._ctx.Logs.error(f'[LOAD_MODULE] Module {module} failed [!] - {err}')
def unload_one_module(self, uplink: 'Irc', module_name: str, keep_in_db: bool = True) -> bool:
async def unload_one_module(self, module_name: str, keep_in_db: bool = True) -> bool:
"""Unload a module
Args:
mod_name (str): Module name ex mod_defender
uplink (Irc): The Irc instance
module_name (str): Module name ex mod_defender
keep_in_db (bool): Keep in database
Returns:
bool: True if success
"""
try:
# Le nom du module. exemple: mod_defender
red = self.__Config.COLORS.red
nogc = self.__Config.COLORS.nogc
red = self._ctx.Config.COLORS.red
nogc = self._ctx.Config.COLORS.nogc
module_folder, module_name, class_name = self.get_module_information(module_name)
module = self.model_get_module(module_name)
if module is None:
self.__Logs.debug(f"[ UNLOAD MODULE ERROR ] This module {module_name} is not loaded!")
uplink.Protocol.send_priv_msg(
nick_from=self.__Config.SERVICE_NICKNAME,
self._ctx.Logs.debug(f"[ UNLOAD MODULE ERROR ] This module {module_name} is not loaded!")
await self.db_delete_module(module_name)
await self._ctx.Irc.Protocol.send_priv_msg(
nick_from=self._ctx.Config.SERVICE_NICKNAME,
msg=f"[ {red}UNLOAD MODULE ERROR{nogc} ] This module {module_name} is not loaded!",
channel=self.__Config.SERVICE_CHANLOG
channel=self._ctx.Config.SERVICE_CHANLOG
)
return False
if module:
module.class_instance.unload()
self.delete_module_header(module.class_instance.MOD_HEADER['name'])
await module.class_instance.unload() if self._ctx.Utils.is_coroutinefunction(module.class_instance.unload) else module.class_instance.unload()
self.DB_MODULES.remove(module)
# Delete from the sys.modules.
@@ -222,25 +283,25 @@ class Module:
del sys.modules[f"mods.{module_folder}.{module_name}"]
if sys.modules.get(f'mods.{module_folder}.{module_name}'):
self.__Logs.debug(f"Module mods.{module_folder}.{module_name} still in the sys.modules")
self._ctx.Logs.debug(f"Module mods.{module_folder}.{module_name} still in the sys.modules")
# Supprimer le module de la base de données
if not keep_in_db:
self.db_delete_module(module_name)
await self.db_delete_module(module_name)
uplink.Protocol.send_priv_msg(
nick_from=self.__Config.SERVICE_NICKNAME,
await self._ctx.Irc.Protocol.send_priv_msg(
nick_from=self._ctx.Config.SERVICE_NICKNAME,
msg=f"[ UNLOAD MODULE INFO ] Module {module_name} has been unloaded!",
channel=self.__Config.SERVICE_CHANLOG
channel=self._ctx.Config.SERVICE_CHANLOG
)
self.__Logs.debug(f"[ UNLOAD MODULE ] {module_name} has been unloaded!")
self._ctx.Logs.debug(f"[ UNLOAD MODULE ] {module_name} has been unloaded!")
return True
self.__Logs.debug(f"[UNLOAD MODULE]: Module {module_name} not found in DB_MODULES variable!")
self._ctx.Logs.debug(f"[UNLOAD MODULE]: Module {module_name} not found in DB_MODULES variable!")
return False
except Exception as err:
self.__Logs.error(f"General Error: {err}")
self._ctx.Logs.error(f"General Error: {err}", exc_info=True)
return False
def unload_all_modules(self) -> bool:
@@ -257,7 +318,9 @@ class Module:
"""
module_folder, module_name, class_name = self.get_module_information(module_name)
if "mods." + module_folder + "." + module_name in sys.modules:
self._ctx.Logs.debug(f"[SYS MODULE] (mods.{module_folder}.{module_name}) found in sys.modules")
return True
self._ctx.Logs.debug(f"[SYS MODULE] (mods.{module_folder}.{module_name}) not found in sys.modules")
return False
'''
@@ -274,10 +337,10 @@ class Module:
"""
for module in self.DB_MODULES:
if module.module_name.lower() == module_name.lower():
self.__Logs.debug(f"[MODEL MODULE GET] The module {module_name} has been found in the model DB_MODULES")
self._ctx.Logs.debug(f"[MODEL MODULE GET] The module {module_name} has been found in the model DB_MODULES")
return module
self.__Logs.debug(f"[MODEL MODULE GET] The module {module_name} not found in the model DB_MODULES")
self._ctx.Logs.debug(f"[MODEL MODULE GET] The module {module_name} not found in the model DB_MODULES")
return None
def model_get_loaded_modules(self) -> list[MModule]:
@@ -287,7 +350,7 @@ class Module:
Returns:
list[MModule]: A list of module model object
"""
# self.__Logs.debug(f"[MODEL MODULE LOADED MODULES] {len(self.DB_MODULES)} modules found!")
# self._ctx.Logs.debug(f"[MODEL MODULE LOADED MODULES] {len(self.DB_MODULES)} modules found!")
return self.DB_MODULES
def model_insert_module(self, module_model: MModule) -> bool:
@@ -302,17 +365,17 @@ class Module:
module = self.model_get_module(module_model.module_name)
if module is None:
self.DB_MODULES.append(module_model)
self.__Logs.debug(f"[MODEL MODULE INSERT] The module {module_model.module_name} has been inserted in the local variable model DB_MODULES")
self._ctx.Logs.debug(f"[MODEL MODULE INSERT] The module {module_model.module_name} has been inserted in the local variable model DB_MODULES")
return True
self.__Logs.debug(f"[MODEL MODULE INSERT] The module {module_model.module_name} already exist in the local variable model DB_MODULES")
self._ctx.Logs.debug(f"[MODEL MODULE INSERT] The module {module_model.module_name} already exist in the local variable model DB_MODULES")
return False
def model_clear(self) -> None:
"""Clear DB_MODULES list!
"""
self.DB_MODULES.clear()
self.__Logs.debug("[MODEL MODULE CLEAR] The local variable model DB_MODULES has been cleared")
self._ctx.Logs.debug("[MODEL MODULE CLEAR] The local variable model DB_MODULES has been cleared")
return None
def model_is_module_exist(self, module_name: str) -> bool:
@@ -325,30 +388,30 @@ class Module:
bool: True if the module_name exist
"""
if self.model_get_module(module_name):
self.__Logs.debug(f"[MODEL MODULE EXIST] The module {module_name} exist in the local model DB_MODULES!")
self._ctx.Logs.debug(f"[MODEL MODULE EXIST] The module {module_name} exist in the local model DB_MODULES!")
return True
self.__Logs.debug(f"[MODEL MODULE EXIST] The module {module_name} is not available in the local model DB_MODULES!")
self._ctx.Logs.debug(f"[MODEL MODULE EXIST] The module {module_name} is not available in the local model DB_MODULES!")
return False
'''
OPERATION DEDICATED TO DATABASE MANAGEMENT
'''
def db_load_all_existing_modules(self, uplink: 'Irc') -> bool:
async def db_load_all_existing_modules(self) -> bool:
"""Charge les modules qui existe déja dans la base de données
Returns:
None: Aucun retour requis, elle charge puis c'est tout
"""
self.__Logs.debug("[DB LOAD MODULE] Loading modules from the database!")
result = self.__Base.db_execute_query(f"SELECT module_name FROM {self.__Config.TABLE_MODULE}")
self._ctx.Logs.debug("[DB LOAD MODULE] Loading modules from the database!")
result = await self._ctx.Base.db_execute_query(f"SELECT module_name FROM {self._ctx.Config.TABLE_MODULE}")
for r in result.fetchall():
self.load_one_module(uplink, r[0], 'sys', True)
await self.load_one_module(r[0], 'sys', True)
return True
def db_is_module_exist(self, module_name: str) -> bool:
async def db_is_module_exist(self, module_name: str) -> bool:
"""Check if the module exist in the database
Args:
@@ -357,18 +420,18 @@ class Module:
Returns:
bool: True if the module exist in the database
"""
query = f"SELECT id FROM {self.__Config.TABLE_MODULE} WHERE module_name = :module_name"
query = f"SELECT id FROM {self._ctx.Config.TABLE_MODULE} WHERE module_name = :module_name"
mes_donnes = {'module_name': module_name.lower()}
results = self.__Base.db_execute_query(query, mes_donnes)
results = await self._ctx.Base.db_execute_query(query, mes_donnes)
if results.fetchall():
self.__Logs.debug(f"[DB MODULE EXIST] The module {module_name} exist in the database!")
self._ctx.Logs.debug(f"[DB MODULE EXIST] The module {module_name} exist in the database!")
return True
else:
self.__Logs.debug(f"[DB MODULE EXIST] The module {module_name} is not available in the database!")
self._ctx.Logs.debug(f"[DB MODULE EXIST] The module {module_name} is not available in the database!")
return False
def db_register_module(self, module_name: str, nickname: str, is_default: bool = False) -> bool:
async def db_register_module(self, module_name: str, nickname: str, is_default: bool = False) -> bool:
"""Insert a new module in the database
Args:
@@ -376,49 +439,49 @@ class Module:
nickname (str): The user who loaded the module
isdefault (int): Is this a default module. Default 0
"""
if not self.db_is_module_exist(module_name):
insert_cmd_query = f"INSERT INTO {self.__Config.TABLE_MODULE} (datetime, user, module_name, isdefault) VALUES (:datetime, :user, :module_name, :isdefault)"
mes_donnees = {'datetime': self.__Utils.get_sdatetime(), 'user': nickname, 'module_name': module_name.lower(), 'isdefault': is_default}
insert = self.__Base.db_execute_query(insert_cmd_query, mes_donnees)
if not await self.db_is_module_exist(module_name):
insert_cmd_query = f"INSERT INTO {self._ctx.Config.TABLE_MODULE} (datetime, user, module_name, isdefault) VALUES (:datetime, :user, :module_name, :isdefault)"
mes_donnees = {'datetime': self._ctx.Utils.get_sdatetime(), 'user': nickname, 'module_name': module_name.lower(), 'isdefault': is_default}
insert = await self._ctx.Base.db_execute_query(insert_cmd_query, mes_donnees)
if insert.rowcount > 0:
self.__Logs.debug(f"[DB REGISTER MODULE] Module {module_name} has been inserted to the database!")
self._ctx.Logs.debug(f"[DB REGISTER MODULE] Module {module_name} has been inserted to the database!")
return True
else:
self.__Logs.debug(f"[DB REGISTER MODULE] Module {module_name} not inserted to the database!")
self._ctx.Logs.debug(f"[DB REGISTER MODULE] Module {module_name} not inserted to the database!")
return False
self.__Logs.debug(f"[DB REGISTER MODULE] Module {module_name} already exist in the database! Nothing to insert!")
self._ctx.Logs.debug(f"[DB REGISTER MODULE] Module {module_name} already exist in the database! Nothing to insert!")
return False
def db_update_module(self, module_name: str, nickname: str) -> None:
async def db_update_module(self, module_name: str, nickname: str) -> None:
"""Update the datetime and the user that updated the module
Args:
module_name (str): The module name to update
nickname (str): The nickname who updated the module
"""
update_cmd_query = f"UPDATE {self.__Config.TABLE_MODULE} SET datetime = :datetime, LOWER(user) = :user WHERE LOWER(module_name) = :module_name"
mes_donnees = {'datetime': self.__Utils.get_sdatetime(), 'user': nickname.lower(), 'module_name': module_name.lower()}
result = self.__Base.db_execute_query(update_cmd_query, mes_donnees)
update_cmd_query = f"UPDATE {self._ctx.Config.TABLE_MODULE} SET datetime = :datetime, LOWER(user) = :user WHERE LOWER(module_name) = :module_name"
mes_donnees = {'datetime': self._ctx.Utils.get_sdatetime(), 'user': nickname.lower(), 'module_name': module_name.lower()}
result = await self._ctx.Base.db_execute_query(update_cmd_query, mes_donnees)
if result.rowcount > 0:
self.__Logs.debug(f"[DB UPDATE MODULE] Module {module_name} has been updated!")
self._ctx.Logs.debug(f"[DB UPDATE MODULE] Module {module_name} has been updated!")
return True
else:
self.__Logs.debug(f"[DB UPDATE MODULE] Module {module_name} not found! Nothing to update!")
self._ctx.Logs.debug(f"[DB UPDATE MODULE] Module {module_name} not found! Nothing to update!")
return False
def db_delete_module(self, module_name:str) -> None:
async def db_delete_module(self, module_name:str) -> None:
"""Delete a module from the database
Args:
module_name (str): The module name you want to delete
"""
insert_cmd_query = f"DELETE FROM {self.__Config.TABLE_MODULE} WHERE LOWER(module_name) = :module_name"
insert_cmd_query = f"DELETE FROM {self._ctx.Config.TABLE_MODULE} WHERE LOWER(module_name) = :module_name"
mes_donnees = {'module_name': module_name.lower()}
delete = self.__Base.db_execute_query(insert_cmd_query, mes_donnees)
delete = await self._ctx.Base.db_execute_query(insert_cmd_query, mes_donnees)
if delete.rowcount > 0:
self.__Logs.debug(f"[DB MODULE DELETE] The module {module_name} has been deleted from the dabatase!")
self._ctx.Logs.debug(f"[DB MODULE DELETE] The module {module_name} has been deleted from the dabatase!")
return True
self.__Logs.debug(f"[DB MODULE DELETE] The module {module_name} is not available in the database! Nothing to delete!")
self._ctx.Logs.debug(f"[DB MODULE DELETE] The module {module_name} is not available in the database! Nothing to delete!")
return False

View File

@@ -1,23 +1,22 @@
'''
"""
Main utils library.
'''
"""
import gc
import glob
import ssl
import socket
import sys
from pathlib import Path
from re import match, sub
from base64 import b64decode
import threading
from typing import Literal, Optional, Any, TYPE_CHECKING
from datetime import datetime, timedelta, timezone
from time import time
from datetime import datetime
from time import time, sleep
from random import choice
from hashlib import md5, sha3_512
from core.classes.settings import global_settings
from core.classes.modules.settings import global_settings
from asyncio import iscoroutinefunction
if TYPE_CHECKING:
from core.irc import Irc
from threading import Event
from core.loader import Loader
def tr(message: str, *args) -> str:
"""Translation Engine system
@@ -37,28 +36,27 @@ def tr(message: str, *args) -> str:
is_args_available = True if args else False
g = global_settings
try:
# Access to user object ==> global_instance.get_user_option
client_language = global_settings.global_user.current_user.geoip if global_settings.global_user.current_user else 'en'
client_language = client_language if client_language else 'en'
# Access to admin object
client_language = g.current_admin.language if g.current_admin else g.global_lang
if count_args != count_placeholder:
global_settings.global_logger.error(f"Translation: Original message: {message} | Args: {count_args} - Placeholder: {count_placeholder}")
g.global_logger.error(f"Translation: Original message: {message} | Args: {count_args} - Placeholder: {count_placeholder}")
return message
if g.global_lang is None:
return message % args if is_args_available else message
if g.global_lang.lower() == 'en':
if client_language.lower() == 'en':
return message % args if is_args_available else message
for trads in global_settings.global_translation[global_settings.global_lang.lower()]:
for trads in g.global_translation[client_language.lower()]:
if sub(r"\s+", "", message) == sub(r"\s+", "", trads[0]):
return trads[1] % args if is_args_available else trads[1]
return message % args if is_args_available else message
except KeyError as ke:
g.global_logger.error(f"Key Error: {ke}")
g.global_logger.error(f"KeyError: {ke}")
return message % args if is_args_available else message
except Exception as err:
@@ -86,9 +84,9 @@ def get_unixtime() -> int:
Returns:
int: Current time in seconds since the Epoch (int)
"""
cet_offset = timezone(timedelta(hours=2))
now_cet = datetime.now(cet_offset)
unixtime_cet = int(now_cet.timestamp())
# cet_offset = timezone(timedelta(hours=2))
# now_cet = datetime.now(cet_offset)
# unixtime_cet = int(now_cet.timestamp())
return int(time())
def get_sdatetime() -> str:
@@ -117,37 +115,6 @@ def get_ssl_context() -> ssl.SSLContext:
ctx.verify_mode = ssl.CERT_NONE
return ctx
def create_socket(uplink: 'Irc') -> None:
"""Create a socket to connect SSL or Normal connection
"""
try:
soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM or socket.SOCK_NONBLOCK)
connexion_information = (uplink.Config.SERVEUR_IP, uplink.Config.SERVEUR_PORT)
if uplink.Config.SERVEUR_SSL:
# Create SSL Context object
ssl_context = get_ssl_context()
ssl_connexion = ssl_context.wrap_socket(soc, server_hostname=uplink.Config.SERVEUR_HOSTNAME)
ssl_connexion.connect(connexion_information)
uplink.IrcSocket = ssl_connexion
uplink.Config.SSL_VERSION = uplink.IrcSocket.version()
uplink.Logs.info(f"-- Connected using SSL : Version = {uplink.Config.SSL_VERSION}")
else:
soc.connect(connexion_information)
uplink.IrcSocket = soc
uplink.Logs.info("-- Connected in a normal mode!")
return None
except (ssl.SSLEOFError, ssl.SSLError) as soe:
uplink.Logs.critical(f"[SSL ERROR]: {soe}")
except OSError as oe:
uplink.Logs.critical(f"[OS Error]: {oe}")
if 'connection refused' in str(oe).lower():
sys.exit(oe)
except AttributeError as ae:
uplink.Logs.critical(f"AttributeError: {ae}")
def run_python_garbage_collector() -> int:
"""Run Python garbage collector
@@ -167,6 +134,21 @@ def get_number_gc_objects(your_object_to_count: Optional[Any] = None) -> int:
return sum(1 for obj in gc.get_objects() if isinstance(obj, your_object_to_count))
def heartbeat(event: 'Event', loader: 'Loader', beat: float) -> None:
"""Execute certaines commandes de nettoyage toutes les x secondes
x étant définit a l'initialisation de cette class (self.beat)
Args:
beat (float): Nombre de secondes entre chaque exécution
"""
while event.is_set():
loader.Base.execute_periodic_action()
sleep(beat)
loader.Logs.debug("Heartbeat is off!")
return None
def generate_random_string(lenght: int) -> str:
"""Retourn une chaîne aléatoire en fonction de la longueur spécifiée.
@@ -178,7 +160,7 @@ def generate_random_string(lenght: int) -> str:
return randomize
def hash_password(password: str, algorithm: Literal["md5, sha3_512"] = 'md5') -> str:
def hash_password(password: str, algorithm: Literal["md5", "sha3_512"] = 'md5') -> str:
"""Return the crypted password following the selected algorithm
Args:
@@ -191,16 +173,16 @@ def hash_password(password: str, algorithm: Literal["md5, sha3_512"] = 'md5') ->
match algorithm:
case 'md5':
password = md5(password.encode()).hexdigest()
return password
hashed_password = md5(password.encode()).hexdigest()
return hashed_password
case 'sha3_512':
password = sha3_512(password.encode()).hexdigest()
return password
hashed_password = sha3_512(password.encode()).hexdigest()
return hashed_password
case _:
password = md5(password.encode()).hexdigest()
return password
hashed_password = md5(password.encode()).hexdigest()
return hashed_password
def get_all_modules() -> list[str]:
"""Get list of all main modules
@@ -225,9 +207,9 @@ def clean_uid(uid: str) -> Optional[str]:
return None
pattern = fr'[:|@|%|\+|~|\*]*'
parsed_UID = sub(pattern, '', uid)
parsed_uid = sub(pattern, '', uid)
return parsed_UID
return parsed_uid
def hide_sensitive_data(srvmsg: list[str]) -> list[str]:
try:
@@ -243,4 +225,15 @@ def hide_sensitive_data(srvmsg: list[str]) -> list[str]:
return srv_msg
except ValueError:
return srvmsg
return srvmsg
def is_coroutinefunction(func: Any) -> bool:
"""Check if the function is a coroutine or not
Args:
func (Any): an callable object
Returns:
bool: True if the function is a coroutine
"""
return iscoroutinefunction(func)

View File

@@ -1,24 +1,21 @@
from core import installation
import asyncio
from core import install
#############################################
# @Version : 6.2 #
# @Version : 6.4 #
# Requierements : #
# Python3.10 or higher #
# SQLAlchemy, requests, psutil #
# unrealircd-rpc-py #
# unrealircd-rpc-py, pyyaml #
# uvicorn, starlette, faker #
# UnrealIRCD 6.2.2 or higher #
#############################################
try:
installation.Install()
async def main():
install.update_packages()
from core.loader import Loader
from core.irc import Irc
ircInstance = Irc(Loader())
ircInstance.init_irc(ircInstance)
loader = Loader()
await loader.start()
await loader.Irc.run()
except AssertionError as ae:
print(f'Assertion Error -> {ae}')
except KeyboardInterrupt as k:
ircInstance.Base.execute_periodic_action()
if __name__ == "__main__":
asyncio.run(main(), debug=False)

View File

@@ -10,7 +10,7 @@ class CloneManager:
def __init__(self, uplink: 'Clone'):
self.Logs = uplink.Logs
self.Logs = uplink.ctx.Logs
def insert(self, new_clone_object: MClone) -> bool:
"""Create new Clone object

View File

@@ -1,90 +1,38 @@
from dataclasses import dataclass
from typing import TYPE_CHECKING, Optional, Any
from core.classes.interfaces.imodule import IModule
import mods.clone.utils as utils
import mods.clone.threads as thds
import mods.clone.schemas as schemas
from mods.clone.clone_manager import CloneManager
if TYPE_CHECKING:
from core.irc import Irc
from faker import Faker
from core.loader import Loader
class Clone:
class Clone(IModule):
def __init__(self, irc_instance: 'Irc') -> None:
@dataclass
class ModConfModel(schemas.ModConfModel):
...
# Module name (Mandatory)
self.module_name = 'mod_' + str(self.__class__.__name__).lower()
MOD_HEADER: dict[str, str] = {
'name':'Clone',
'version':'1.0.0',
'description':'Connect thousands of clones to your IRCD, by group. You can use them as security moderation.',
'author':'Defender Team',
'core_version':'Defender-6'
}
# Add Irc Object to the module (Mandatory)
self.Irc = irc_instance
def __init__(self, context: 'Loader') -> None:
super().__init__(context)
self._mod_config: Optional[schemas.ModConfModel] = None
# Add Irc Protocol Object to the module (Mandatory)
self.Protocol = irc_instance.Protocol
@property
def mod_config(self) -> ModConfModel:
return self._mod_config
# Add Global Configuration to the module (Mandatory)
self.Config = irc_instance.Config
# Add Base object to the module (Mandatory)
self.Base = irc_instance.Base
# Add logs object to the module (Mandatory)
self.Logs = irc_instance.Loader.Logs
# Add User object to the module (Mandatory)
self.User = irc_instance.User
# Add Channel object to the module (Mandatory)
self.Channel = irc_instance.Channel
# Add global definitions
self.Definition = irc_instance.Loader.Definition
# The Global Settings
self.Settings = irc_instance.Loader.Settings
self.Schemas = schemas
self.Utils = utils
self.Threads = thds
self.Faker: Optional['Faker'] = self.Utils.create_faker_object('en_GB')
self.Clone = CloneManager(self)
metadata = self.Settings.get_cache('UID_CLONE_DB')
if metadata is not None:
self.Clone.UID_CLONE_DB = metadata
self.Logs.debug(f"Cache Size = {self.Settings.get_cache_size()}")
# Créer les nouvelles commandes du module
self.Irc.build_command(1, self.module_name, 'clone', 'Connect, join, part, kill and say clones')
# Init the module (Mandatory)
self.__init_module()
# Log the module
self.Logs.debug(f'Module {self.module_name} loaded ...')
def __init_module(self) -> None:
# Créer les tables necessaire a votre module (ce n'es pas obligatoire)
self.__create_tables()
self.stop = False
# Load module configuration (Mandatory)
self.__load_module_configuration()
self.Channel.db_query_channel(action='add', module_name=self.module_name, channel_name=self.Config.CLONE_CHANNEL)
self.Protocol.send_join_chan(self.Config.SERVICE_NICKNAME, self.Config.CLONE_CHANNEL)
self.Protocol.send2socket(f":{self.Config.SERVICE_NICKNAME} SAMODE {self.Config.CLONE_CHANNEL} +o {self.Config.SERVICE_NICKNAME}")
self.Protocol.send2socket(f":{self.Config.SERVICE_NICKNAME} MODE {self.Config.CLONE_CHANNEL} +nts")
self.Protocol.send2socket(f":{self.Config.SERVICE_NICKNAME} MODE {self.Config.CLONE_CHANNEL} +k {self.Config.CLONE_CHANNEL_PASSWORD}")
def __create_tables(self) -> None:
async 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
@@ -100,55 +48,70 @@ class Clone:
)
'''
# self.Base.db_execute_query(table_channel)
# await self.ctx.Base.db_execute_query(table_channel)
return None
def __load_module_configuration(self) -> None:
"""### Load Module Configuration
"""
try:
# Variable qui va contenir les options de configuration du module Defender
self.ModConfig = self.Schemas.ModConfModel()
async def load(self) -> None:
# Sync the configuration with core configuration (Mandatory)
# self.Base.db_sync_core_config(self.module_name, self.ModConfig)
# Variable qui va contenir les options de configuration du module Defender
self._mod_config: schemas.ModConfModel = self.ModConfModel()
return None
# sync the database with local variable (Mandatory)
await self.sync_db()
except TypeError as te:
self.Logs.critical(te)
self.stop = False
self.Schemas = schemas
self.Utils = utils
self.Threads = thds
self.Faker: Optional['Faker'] = self.Utils.create_faker_object('en_GB')
self.Clone = CloneManager(self)
metadata = self.ctx.Settings.get_cache('UID_CLONE_DB')
def unload(self) -> None:
if metadata is not None:
self.Clone.UID_CLONE_DB = metadata
self.ctx.Logs.debug(f"Cache Size = {self.ctx.Settings.get_cache_size()}")
# Créer les nouvelles commandes du module
self.ctx.Commands.build_command(1, self.module_name, 'clone', 'Connect, join, part, kill and say clones')
await self.ctx.Channel.db_query_channel(action='add', module_name=self.module_name, channel_name=self.ctx.Config.CLONE_CHANNEL)
await self.ctx.Irc.Protocol.send_sjoin(self.ctx.Config.CLONE_CHANNEL)
await self.ctx.Irc.Protocol.send_set_mode('+o', nickname=self.ctx.Config.SERVICE_NICKNAME, channel_name=self.ctx.Config.CLONE_CHANNEL)
await self.ctx.Irc.Protocol.send_set_mode('+nts', channel_name=self.ctx.Config.CLONE_CHANNEL)
await self.ctx.Irc.Protocol.send_set_mode('+k', channel_name=self.ctx.Config.CLONE_CHANNEL, params=self.ctx.Config.CLONE_CHANNEL_PASSWORD)
async def unload(self) -> None:
"""Cette methode sera executée a chaque désactivation ou
rechargement de module
"""
# Store Clones DB into the global Settings to retrieve it after the reload.
self.Settings.set_cache('UID_CLONE_DB', self.Clone.UID_CLONE_DB)
self.ctx.Settings.set_cache('UID_CLONE_DB', self.Clone.UID_CLONE_DB)
self.Channel.db_query_channel(action='del', module_name=self.module_name, channel_name=self.Config.CLONE_CHANNEL)
self.Protocol.send2socket(f":{self.Config.SERVICE_NICKNAME} MODE {self.Config.CLONE_CHANNEL} -nts")
self.Protocol.send2socket(f":{self.Config.SERVICE_NICKNAME} MODE {self.Config.CLONE_CHANNEL} -k {self.Config.CLONE_CHANNEL_PASSWORD}")
self.Protocol.send_part_chan(self.Config.SERVICE_NICKNAME, self.Config.CLONE_CHANNEL)
await self.ctx.Channel.db_query_channel(action='del', module_name=self.module_name, channel_name=self.ctx.Config.CLONE_CHANNEL)
await self.ctx.Irc.Protocol.send_set_mode('-nts', channel_name=self.ctx.Config.CLONE_CHANNEL)
await self.ctx.Irc.Protocol.send_set_mode('-k', channel_name=self.ctx.Config.CLONE_CHANNEL)
await self.ctx.Irc.Protocol.send_part_chan(self.ctx.Config.SERVICE_NICKNAME, self.ctx.Config.CLONE_CHANNEL)
self.Irc.Commands.drop_command_by_module(self.module_name)
self.ctx.Commands.drop_command_by_module(self.module_name)
return None
def cmd(self, data:list) -> None:
async def cmd(self, data:list) -> None:
try:
if not data or len(data) < 2:
return None
cmd = data.copy() if isinstance(data, list) else list(data).copy()
index, command = self.Irc.Protocol.get_ircd_protocol_poisition(cmd)
index, command = self.ctx.Irc.Protocol.get_ircd_protocol_poisition(cmd)
if index == -1:
return None
match command:
case 'PRIVMSG':
return self.Utils.handle_on_privmsg(self, cmd)
await self.Utils.handle_on_privmsg(self, cmd)
return None
case 'QUIT':
return None
@@ -157,10 +120,10 @@ class Clone:
return None
except Exception as err:
self.Logs.error(f'General Error: {err}', exc_info=True)
self.ctx.Logs.error(f'General Error: {err}', exc_info=True)
return None
def hcmds(self, user: str, channel: Any, cmd: list, fullcmd: list = []) -> None:
async def hcmds(self, user: str, channel: Any, cmd: list, fullcmd: list = []) -> None:
try:
@@ -169,18 +132,18 @@ class Clone:
command = str(cmd[0]).lower()
fromuser = user
dnickname = self.Config.SERVICE_NICKNAME
dnickname = self.ctx.Config.SERVICE_NICKNAME
match command:
case 'clone':
if len(cmd) == 1:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone connect NUMBER GROUP_NAME INTERVAL")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone kill [all | group_name | nickname]")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone join [all | group_name | nickname] #channel")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone part [all | group_name | nickname] #channel")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone list [group name]")
if len(cmd) < 2:
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone connect NUMBER GROUP_NAME INTERVAL")
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone kill [all | group_name | nickname]")
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone join [all | group_name | nickname] #channel")
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone part [all | group_name | nickname] #channel")
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone list [group name]")
return None
option = str(cmd[1]).lower()
@@ -195,15 +158,13 @@ class Clone:
group = str(cmd[3]).lower()
connection_interval = int(cmd[4]) if len(cmd) == 5 else 0.2
self.Base.create_thread(
func=self.Threads.thread_connect_clones,
func_args=(self, number_of_clones, group, False, connection_interval)
self.ctx.Base.create_asynctask(
func=self.Threads.coro_connect_clones(self, number_of_clones, group, False, connection_interval)
)
except Exception as err:
self.Logs.error(f'{err}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone connect [number of clone you want to connect] [Group]")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Exemple /msg {dnickname} clone connect 6 Ambiance")
except IndexError:
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone connect [number of clone you want to connect] [Group] [freq]")
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Exemple /msg {dnickname} clone connect 6 Ambiance")
case 'kill':
try:
@@ -212,28 +173,26 @@ class Clone:
option = str(cmd[2])
if option.lower() == 'all':
self.Base.create_thread(func=self.Threads.thread_kill_clones, func_args=(self, ))
self.ctx.Base.create_asynctask(func=self.Threads.thread_kill_clones(self))
elif self.Clone.group_exists(option):
list_of_clones_in_group = self.Clone.get_clones_from_groupname(option)
if len(list_of_clones_in_group) > 0:
self.Logs.debug(f"[Clone Kill Group] - Killing {len(list_of_clones_in_group)} clones in the group {option}")
self.ctx.Logs.debug(f"[Clone Kill Group] - Killing {len(list_of_clones_in_group)} clones in the group {option}")
for clone in list_of_clones_in_group:
self.Protocol.send_quit(clone.uid, "Now i am leaving irc but i'll come back soon ...", print_log=False)
await self.ctx.Irc.Protocol.send_quit(clone.uid, "Now i am leaving irc but i'll come back soon ...", print_log=False)
self.Clone.delete(clone.uid)
else:
clone_obj = self.Clone.get_clone(option)
if not clone_obj is None:
self.Protocol.send_quit(clone_obj.uid, 'Goood bye', print_log=False)
await self.ctx.Irc.Protocol.send_quit(clone_obj.uid, 'Goood bye', print_log=False)
self.Clone.delete(clone_obj.uid)
except Exception as err:
self.Logs.error(f'{err}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone kill all")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone kill clone_nickname")
except IndexError:
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone kill [all | group name | nickname]")
case 'join':
try:
@@ -244,26 +203,24 @@ class Clone:
if option.lower() == 'all':
for clone in self.Clone.UID_CLONE_DB:
self.Protocol.send_join_chan(uidornickname=clone.uid, channel=clone_channel_to_join, print_log=False)
await self.ctx.Irc.Protocol.send_join_chan(uidornickname=clone.uid, channel=clone_channel_to_join, print_log=False)
elif self.Clone.group_exists(option):
list_of_clones_in_group = self.Clone.get_clones_from_groupname(option)
if len(list_of_clones_in_group) > 0:
self.Logs.debug(f"[Clone Join Group] - Joining {len(list_of_clones_in_group)} clones from group {option} in the channel {clone_channel_to_join}")
self.ctx.Logs.debug(f"[Clone Join Group] - Joining {len(list_of_clones_in_group)} clones from group {option} in the channel {clone_channel_to_join}")
for clone in list_of_clones_in_group:
self.Protocol.send_join_chan(uidornickname=clone.nickname, channel=clone_channel_to_join, print_log=False)
await self.ctx.Irc.Protocol.send_join_chan(uidornickname=clone.nickname, channel=clone_channel_to_join, print_log=False)
else:
if self.Clone.nickname_exists(option):
clone_uid = self.Clone.get_clone(option).uid
self.Protocol.send_join_chan(uidornickname=clone_uid, channel=clone_channel_to_join, print_log=False)
await self.ctx.Irc.Protocol.send_join_chan(uidornickname=clone_uid, channel=clone_channel_to_join, print_log=False)
except Exception as err:
self.Logs.error(f'{err}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone join all #channel")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone join clone_nickname #channel")
except IndexError:
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone join [all | group name | nickname] #channel")
case 'part':
try:
@@ -274,68 +231,66 @@ class Clone:
if option.lower() == 'all':
for clone in self.Clone.UID_CLONE_DB:
self.Protocol.send_part_chan(uidornickname=clone.uid, channel=clone_channel_to_part, print_log=False)
await self.ctx.Irc.Protocol.send_part_chan(uidornickname=clone.uid, channel=clone_channel_to_part, print_log=False)
elif self.Clone.group_exists(option):
list_of_clones_in_group = self.Clone.get_clones_from_groupname(option)
if len(list_of_clones_in_group) > 0:
self.Logs.debug(f"[Clone Part Group] - Part {len(list_of_clones_in_group)} clones from group {option} from the channel {clone_channel_to_part}")
self.ctx.Logs.debug(f"[Clone Part Group] - Part {len(list_of_clones_in_group)} clones from group {option} from the channel {clone_channel_to_part}")
for clone in list_of_clones_in_group:
self.Protocol.send_part_chan(uidornickname=clone.uid, channel=clone_channel_to_part, print_log=False)
await self.ctx.Irc.Protocol.send_part_chan(uidornickname=clone.uid, channel=clone_channel_to_part, print_log=False)
else:
if self.Clone.nickname_exists(option):
clone_uid = self.Clone.get_uid(option)
if not clone_uid is None:
self.Protocol.send_part_chan(uidornickname=clone_uid, channel=clone_channel_to_part, print_log=False)
await self.ctx.Irc.Protocol.send_part_chan(uidornickname=clone_uid, channel=clone_channel_to_part, print_log=False)
except Exception as err:
self.Logs.error(f'{err}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone part all #channel")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone part clone_nickname #channel")
except IndexError:
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone part [all | group name | nickname] #channel")
case 'list':
try:
# Syntax. /msg defender clone list <group_name>
header = f" {'Nickname':<12}| {'Real name':<25}| {'Group name':<15}| {'Connected':<35}"
line = "-"*67
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=header)
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" {line}")
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=header)
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" {line}")
group_name = cmd[2] if len(cmd) > 2 else None
if group_name is None:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Number of connected clones: {len(self.Clone.UID_CLONE_DB)}")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" {line}")
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Number of connected clones: {len(self.Clone.UID_CLONE_DB)}")
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" {line}")
for clone_name in self.Clone.UID_CLONE_DB:
self.Protocol.send_notice(
await self.ctx.Irc.Protocol.send_notice(
nick_from=dnickname,
nick_to=fromuser,
msg=f" {clone_name.nickname:<12}| {clone_name.realname:<25}| {clone_name.group:<15}| {clone_name.connected:<35}")
else:
if not self.Clone.group_exists(group_name):
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="This Group name doesn't exist!")
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="This Group name doesn't exist!")
return None
clones = self.Clone.get_clones_from_groupname(group_name)
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Number of connected clones: {len(clones)}")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" {line}")
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Number of connected clones: {len(clones)}")
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" {line}")
for clone in clones:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,
msg=f" {clone.nickname:<12}| {clone.realname:<25}| {clone.group:<15}| {clone.connected:<35}")
except Exception as err:
self.Logs.error(f'{err}')
except IndexError:
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone list [group name]")
case 'say':
try:
# clone say clone_nickname #channel message
clone_name = str(cmd[2])
clone_channel = str(cmd[3]) if self.Channel.is_valid_channel(str(cmd[3])) else None
clone_channel = str(cmd[3]) if self.ctx.Channel.is_valid_channel(str(cmd[3])) else None
final_message = ' '.join(cmd[4:])
if clone_channel is None or not self.Clone.nickname_exists(clone_name):
self.Protocol.send_notice(
await self.ctx.Irc.Protocol.send_notice(
nick_from=dnickname,
nick_to=fromuser,
msg=f"/msg {dnickname} clone say [clone_nickname] #channel message"
@@ -343,24 +298,21 @@ class Clone:
return None
if self.Clone.nickname_exists(clone_name):
self.Protocol.send_priv_msg(nick_from=clone_name, msg=final_message, channel=clone_channel)
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=clone_name, msg=final_message, channel=clone_channel)
except Exception as err:
self.Logs.error(f'{err}')
self.Protocol.send_notice(
except IndexError:
await self.ctx.Irc.Protocol.send_notice(
nick_from=dnickname,
nick_to=fromuser,
msg=f"/msg {dnickname} clone say [clone_nickname] #channel message"
)
case _:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone connect NUMBER GROUP_NAME INTERVAL")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone kill [all | group name | nickname]")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone join [all | group name | nickname] #channel")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone part [all | group name | nickname] #channel")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone list [group name]")
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone connect NUMBER GROUP_NAME INTERVAL")
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone kill [all | group name | nickname]")
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone join [all | group name | nickname] #channel")
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone part [all | group name | nickname] #channel")
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone list [group name]")
except IndexError as ie:
self.Logs.error(f'Index Error: {ie}')
except Exception as err:
self.Logs.error(f'General Error: {err}')
self.ctx.Logs.error(f'General Error: {err}', exc_info=True)

View File

@@ -1,10 +1,11 @@
import asyncio
from typing import TYPE_CHECKING
from time import sleep
if TYPE_CHECKING:
from mods.clone.mod_clone import Clone
def thread_connect_clones(uplink: 'Clone',
async def coro_connect_clones(uplink: 'Clone',
number_of_clones:int ,
group: str = 'Default',
auto_remote_ip: bool = False,
@@ -27,18 +28,18 @@ def thread_connect_clones(uplink: 'Clone',
break
if not clone.connected:
uplink.Protocol.send_uid(clone.nickname, clone.username, clone.hostname, clone.uid, clone.umodes, clone.vhost, clone.remote_ip, clone.realname, print_log=False)
uplink.Protocol.send_join_chan(uidornickname=clone.uid, channel=uplink.Config.CLONE_CHANNEL, password=uplink.Config.CLONE_CHANNEL_PASSWORD, print_log=False)
await uplink.ctx.Irc.Protocol.send_uid(clone.nickname, clone.username, clone.hostname, clone.uid, clone.umodes, clone.vhost, clone.remote_ip, clone.realname, print_log=False)
await uplink.ctx.Irc.Protocol.send_join_chan(uidornickname=clone.uid, channel=uplink.ctx.Config.CLONE_CHANNEL, password=uplink.ctx.Config.CLONE_CHANNEL_PASSWORD, print_log=False)
sleep(interval)
await asyncio.sleep(interval)
clone.connected = True
def thread_kill_clones(uplink: 'Clone'):
async def thread_kill_clones(uplink: 'Clone'):
clone_to_kill = uplink.Clone.UID_CLONE_DB.copy()
for clone in clone_to_kill:
uplink.Protocol.send_quit(clone.uid, 'Gooood bye', print_log=False)
await uplink.ctx.Irc.Protocol.send_quit(clone.uid, 'Gooood bye', print_log=False)
uplink.Clone.delete(clone.uid)
del clone_to_kill

View File

@@ -125,8 +125,8 @@ def create_new_clone(uplink: 'Clone', faker_instance: 'Faker', group: str = 'Def
"""
faker = faker_instance
uid = generate_uid_for_clone(faker, uplink.Config.SERVEUR_ID)
umodes = uplink.Config.CLONE_UMODES
uid = generate_uid_for_clone(faker, uplink.ctx.Config.SERVEUR_ID)
umodes = uplink.ctx.Config.CLONE_UMODES
# Generate Username
username = generate_username_for_clone(faker)
@@ -153,7 +153,7 @@ def create_new_clone(uplink: 'Clone', faker_instance: 'Faker', group: str = 'Def
checkNickname = uplink.Clone.nickname_exists(nickname)
while checkUid:
uid = generate_uid_for_clone(faker, uplink.Config.SERVEUR_ID)
uid = generate_uid_for_clone(faker, uplink.ctx.Config.SERVEUR_ID)
checkUid = uplink.Clone.uid_exists(uid=uid)
clone = uplink.Schemas.MClone(
@@ -174,25 +174,25 @@ def create_new_clone(uplink: 'Clone', faker_instance: 'Faker', group: str = 'Def
return True
def handle_on_privmsg(uplink: 'Clone', srvmsg: list[str]):
async def handle_on_privmsg(uplink: 'Clone', srvmsg: list[str]) -> None:
uid_sender = uplink.Irc.Utils.clean_uid(srvmsg[1])
senderObj = uplink.User.get_user(uid_sender)
senderObj, recieverObj, channel, message = uplink.ctx.Irc.Protocol.parse_privmsg(srvmsg)
if senderObj.hostname in uplink.Config.CLONE_LOG_HOST_EXEMPT:
return
if not senderObj is None:
senderMsg = ' '.join(srvmsg[4:])
clone_obj = uplink.Clone.get_clone(srvmsg[3])
if senderObj is not None:
if senderObj.hostname in uplink.ctx.Config.CLONE_LOG_HOST_EXEMPT:
return
senderMsg = message
clone_obj = recieverObj
if clone_obj is None:
return
if clone_obj.uid != uplink.Config.SERVICE_ID:
if clone_obj.uid != uplink.ctx.Config.SERVICE_ID:
final_message = f"{senderObj.nickname}!{senderObj.username}@{senderObj.hostname} > {senderMsg.lstrip(':')}"
uplink.Protocol.send_priv_msg(
await uplink.ctx.Irc.Protocol.send_priv_msg(
nick_from=clone_obj.uid,
msg=final_message,
channel=uplink.Config.CLONE_CHANNEL
channel=uplink.ctx.Config.CLONE_CHANNEL
)
return None

File diff suppressed because it is too large Load Diff

View File

@@ -4,240 +4,243 @@ if TYPE_CHECKING:
from mods.command.mod_command import Command
def set_automode(uplink: 'Command', cmd: list[str], client: str) -> None:
async def set_automode(uplink: 'Command', cmd: list[str], client: str) -> None:
command: str = str(cmd[0]).lower()
option: str = str(cmd[1]).lower()
allowed_modes: list[str] = uplink.Loader.Settings.PROTOCTL_PREFIX # ['q','a','o','h','v']
dnickname = uplink.Config.SERVICE_NICKNAME
service_id = uplink.Config.SERVICE_ID
allowed_modes: list[str] = uplink.ctx.Settings.PROTOCTL_PREFIX # ['q','a','o','h','v']
dnickname = uplink.ctx.Config.SERVICE_NICKNAME
service_id = uplink.ctx.Config.SERVICE_ID
fromuser = client
match option:
case 'set':
if len(cmd) < 5:
uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {command.upper()} [nickname] [+/-mode] [#channel]")
uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"AutoModes available: {' / '.join(allowed_modes)}")
await uplink.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {command.upper()} [nickname] [+/-mode] [#channel]")
await uplink.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"AutoModes available: {' / '.join(allowed_modes)}")
return None
nickname = str(cmd[2])
mode = str(cmd[3])
chan: str = str(cmd[4]).lower() if uplink.Channel.is_valid_channel(cmd[4]) else None
chan: str = str(cmd[4]).lower() if uplink.ctx.Channel.is_valid_channel(cmd[4]) else None
sign = mode[0] if mode.startswith( ('+', '-')) else None
clean_mode = mode[1:] if len(mode) > 0 else None
if sign is None:
uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="You must provide the flag mode + or -")
await uplink.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="You must provide the flag mode + or -")
return None
if clean_mode not in allowed_modes:
uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"You should use one of those modes {' / '.join(allowed_modes)}")
await uplink.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"You should use one of those modes {' / '.join(allowed_modes)}")
return None
if chan is None:
uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"You should use one of those modes {' / '.join(allowed_modes)}")
await uplink.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"You should use one of those modes {' / '.join(allowed_modes)}")
return None
db_data: dict[str, str] = {"nickname": nickname, "channel": chan}
db_query = uplink.Base.db_execute_query(query="SELECT id FROM command_automode WHERE nickname = :nickname and channel = :channel", params=db_data)
db_query = await uplink.ctx.Base.db_execute_query(query="SELECT id FROM command_automode WHERE nickname = :nickname and channel = :channel", params=db_data)
db_result = db_query.fetchone()
if db_result is not None:
if sign == '+':
db_data = {"updated_on": uplink.MainUtils.get_sdatetime(), "nickname": nickname, "channel": chan, "mode": mode}
db_result = uplink.Base.db_execute_query(query="UPDATE command_automode SET mode = :mode, updated_on = :updated_on WHERE nickname = :nickname and channel = :channel",
db_data = {"updated_on": uplink.ctx.Utils.get_sdatetime(), "nickname": nickname, "channel": chan, "mode": mode}
db_result = await uplink.ctx.Base.db_execute_query(query="UPDATE command_automode SET mode = :mode, updated_on = :updated_on WHERE nickname = :nickname and channel = :channel",
params=db_data)
if db_result.rowcount > 0:
uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Automode {mode} edited for {nickname} in {chan}")
await uplink.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Automode {mode} edited for {nickname} in {chan}")
elif sign == '-':
db_data = {"nickname": nickname, "channel": chan, "mode": f"+{clean_mode}"}
db_result = uplink.Base.db_execute_query(query="DELETE FROM command_automode WHERE nickname = :nickname and channel = :channel and mode = :mode",
db_result = await uplink.ctx.Base.db_execute_query(query="DELETE FROM command_automode WHERE nickname = :nickname and channel = :channel and mode = :mode",
params=db_data)
if db_result.rowcount > 0:
uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Automode {mode} deleted for {nickname} in {chan}")
await uplink.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Automode {mode} deleted for {nickname} in {chan}")
else:
uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"The mode [{mode}] has not been found for {nickname} in channel {chan}")
await uplink.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"The mode [{mode}] has not been found for {nickname} in channel {chan}")
return None
# Instert a new automode
if sign == '+':
db_data = {"created_on": uplink.MainUtils.get_sdatetime(), "updated_on": uplink.MainUtils.get_sdatetime(), "nickname": nickname, "channel": chan, "mode": mode}
db_query = uplink.Base.db_execute_query(
db_data = {"created_on": uplink.ctx.Utils.get_sdatetime(), "updated_on": uplink.ctx.Utils.get_sdatetime(), "nickname": nickname, "channel": chan, "mode": mode}
db_query = await uplink.ctx.Base.db_execute_query(
query="INSERT INTO command_automode (created_on, updated_on, nickname, channel, mode) VALUES (:created_on, :updated_on, :nickname, :channel, :mode)",
params=db_data
)
if db_query.rowcount > 0:
uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Automode {mode} applied to {nickname} in {chan}")
if uplink.Channel.is_user_present_in_channel(chan, uplink.User.get_uid(nickname)):
uplink.Protocol.send2socket(f":{service_id} MODE {chan} {mode} {nickname}")
await uplink.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Automode {mode} applied to {nickname} in {chan}")
if uplink.ctx.Channel.is_user_present_in_channel(chan, uplink.ctx.User.get_uid(nickname)):
await uplink.ctx.Irc.Protocol.send2socket(f":{service_id} MODE {chan} {mode} {nickname}")
else:
uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"AUTOMODE {mode} cannot be added to {nickname} in {chan} because it doesn't exist")
await uplink.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"AUTOMODE {mode} cannot be added to {nickname} in {chan} because it doesn't exist")
case 'list':
db_query = uplink.Base.db_execute_query("SELECT nickname, channel, mode FROM command_automode")
db_query = await uplink.ctx.Base.db_execute_query("SELECT nickname, channel, mode FROM command_automode")
db_results = db_query.fetchall()
if not db_results:
uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,
await uplink.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,
msg="There is no automode to display.")
for db_result in db_results:
db_nickname, db_channel, db_mode = db_result
uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,
await uplink.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,
msg=f"Nickname: {db_nickname} | Channel: {db_channel} | Mode: {db_mode}")
case _:
uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {command.upper()} SET [nickname] [+/-mode] [#channel]")
uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {command.upper()} LIST")
uplink.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"[AUTOMODES AVAILABLE] are {' / '.join(allowed_modes)}")
await uplink.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {command.upper()} SET [nickname] [+/-mode] [#channel]")
await uplink.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {command.upper()} LIST")
await uplink.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"[AUTOMODES AVAILABLE] are {' / '.join(allowed_modes)}")
def set_deopall(uplink: 'Command', channel_name: str) -> None:
async def set_deopall(uplink: 'Command', channel_name: str) -> None:
service_id = uplink.Config.SERVICE_ID
uplink.Protocol.send2socket(f":{service_id} SVSMODE {channel_name} -o")
service_id = uplink.ctx.Config.SERVICE_ID
await uplink.ctx.Irc.Protocol.send2socket(f":{service_id} SVSMODE {channel_name} -o")
return None
def set_devoiceall(uplink: 'Command', channel_name: str) -> None:
async def set_devoiceall(uplink: 'Command', channel_name: str) -> None:
service_id = uplink.Config.SERVICE_ID
uplink.Protocol.send2socket(f":{service_id} SVSMODE {channel_name} -v")
service_id = uplink.ctx.Config.SERVICE_ID
await uplink.ctx.Irc.Protocol.send2socket(f":{service_id} SVSMODE {channel_name} -v")
return None
def set_mode_to_all(uplink: 'Command', channel_name: str, action: Literal['+', '-'], pmode: str) -> None:
async def set_mode_to_all(uplink: 'Command', channel_name: str, action: Literal['+', '-'], pmode: str) -> None:
chan_info = uplink.Channel.get_channel(channel_name)
service_id = uplink.Config.SERVICE_ID
dnickname = uplink.Config.SERVICE_NICKNAME
chan_info = uplink.ctx.Channel.get_channel(channel_name)
service_id = uplink.ctx.Config.SERVICE_ID
dnickname = uplink.ctx.Config.SERVICE_NICKNAME
set_mode = pmode
mode:str = ''
users:str = ''
uids_split = [chan_info.uids[i:i + 6] for i in range(0, len(chan_info.uids), 6)]
uplink.Protocol.send2socket(f":{service_id} MODE {channel_name} {action}{set_mode} {dnickname}")
await uplink.ctx.Irc.Protocol.send2socket(f":{service_id} MODE {channel_name} {action}{set_mode} {dnickname}")
for uid in uids_split:
for i in range(0, len(uid)):
mode += set_mode
users += f'{uplink.User.get_nickname(uplink.MainUtils.clean_uid(uid[i]))} '
users += f'{uplink.ctx.User.get_nickname(uplink.ctx.Utils.clean_uid(uid[i]))} '
if i == len(uid) - 1:
uplink.Protocol.send2socket(f":{service_id} MODE {channel_name} {action}{mode} {users}")
await uplink.ctx.Irc.Protocol.send2socket(f":{service_id} MODE {channel_name} {action}{mode} {users}")
mode = ''
users = ''
def set_operation(uplink: 'Command', cmd: list[str], channel_name: Optional[str], client: str, mode: str) -> None:
async def set_operation(uplink: 'Command', cmd: list[str], channel_name: Optional[str], client: str, mode: str) -> None:
dnickname = uplink.Config.SERVICE_NICKNAME
service_id = uplink.Config.SERVICE_ID
dnickname = uplink.ctx.Config.SERVICE_NICKNAME
service_id = uplink.ctx.Config.SERVICE_ID
if channel_name is None:
uplink.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f" Right command : /msg {dnickname} {mode} [#SALON] [NICKNAME]")
await uplink.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f" Right command : /msg {dnickname} {mode} [#SALON] [NICKNAME]")
return False
if len(cmd) == 1:
uplink.Protocol.send2socket(f":{dnickname} MODE {channel_name} {mode} {client}")
# await uplink.ctx.Irc.Protocol.send2socket(f":{service_id} MODE {channel_name} {mode} {client}")
await uplink.ctx.Irc.Protocol.send_set_mode(mode, nickname=client, channel_name=channel_name)
return None
# deop nickname
if len(cmd) == 2:
nickname = cmd[1]
uplink.Protocol.send2socket(f":{service_id} MODE {channel_name} {mode} {nickname}")
# await uplink.ctx.Irc.Protocol.send2socket(f":{service_id} MODE {channel_name} {mode} {nickname}")
await uplink.ctx.Irc.Protocol.send_set_mode(mode, nickname=nickname, channel_name=channel_name)
return None
nickname = cmd[2]
uplink.Protocol.send2socket(f":{service_id} MODE {channel_name} {mode} {nickname}")
# await uplink.ctx.Irc.Protocol.send2socket(f":{service_id} MODE {channel_name} {mode} {nickname}")
await uplink.ctx.Irc.Protocol.send_set_mode(mode, nickname=nickname, channel_name=channel_name)
return None
def set_ban(uplink: 'Command', cmd: list[str], action: Literal['+', '-'], client: str) -> None:
async def set_ban(uplink: 'Command', cmd: list[str], action: Literal['+', '-'], client: str) -> None:
command = str(cmd[0])
dnickname = uplink.Config.SERVICE_NICKNAME
service_id = uplink.Config.SERVICE_ID
sentchannel = str(cmd[1]) if uplink.Channel.is_valid_channel(cmd[1]) else None
dnickname = uplink.ctx.Config.SERVICE_NICKNAME
service_id = uplink.ctx.Config.SERVICE_ID
sentchannel = str(cmd[1]) if uplink.ctx.Channel.is_valid_channel(cmd[1]) else None
if sentchannel is None:
uplink.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON] [NICKNAME]")
await uplink.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON] [NICKNAME]")
return None
nickname = cmd[2]
uplink.Protocol.send2socket(f":{service_id} MODE {sentchannel} {action}b {nickname}!*@*")
uplink.Logs.debug(f'{client} has banned {nickname} from {sentchannel}')
await uplink.ctx.Irc.Protocol.send2socket(f":{service_id} MODE {sentchannel} {action}b {nickname}!*@*")
uplink.ctx.Logs.debug(f'{client} has banned {nickname} from {sentchannel}')
return None
def set_kick(uplink: 'Command', cmd: list[str], client: str) -> None:
async def set_kick(uplink: 'Command', cmd: list[str], client: str) -> None:
command = str(cmd[0])
dnickname = uplink.Config.SERVICE_NICKNAME
service_id = uplink.Config.SERVICE_ID
dnickname = uplink.ctx.Config.SERVICE_NICKNAME
service_id = uplink.ctx.Config.SERVICE_ID
sentchannel = str(cmd[1]) if uplink.Channel.is_valid_channel(cmd[1]) else None
sentchannel = str(cmd[1]) if uplink.ctx.Channel.is_valid_channel(cmd[1]) else None
if sentchannel is None:
uplink.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f" Right command : /msg {dnickname} {command} [#SALON] [NICKNAME]")
await uplink.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f" Right command : /msg {dnickname} {command} [#SALON] [NICKNAME]")
return False
nickname = cmd[2]
final_reason = ' '.join(cmd[3:])
uplink.Protocol.send2socket(f":{service_id} KICK {sentchannel} {nickname} {final_reason}")
uplink.Logs.debug(f'{client} has kicked {nickname} from {sentchannel} : {final_reason}')
await uplink.ctx.Irc.Protocol.send2socket(f":{service_id} KICK {sentchannel} {nickname} {final_reason}")
uplink.ctx.Logs.debug(f'{client} has kicked {nickname} from {sentchannel} : {final_reason}')
return None
def set_kickban(uplink: 'Command', cmd: list[str], client: str) -> None:
async def set_kickban(uplink: 'Command', cmd: list[str], client: str) -> None:
command = str(cmd[0])
dnickname = uplink.Config.SERVICE_NICKNAME
service_id = uplink.Config.SERVICE_ID
dnickname = uplink.ctx.Config.SERVICE_NICKNAME
service_id = uplink.ctx.Config.SERVICE_ID
sentchannel = str(cmd[1]) if uplink.Channel.is_valid_channel(cmd[1]) else None
sentchannel = str(cmd[1]) if uplink.ctx.Channel.is_valid_channel(cmd[1]) else None
if sentchannel is None:
uplink.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f" Right command : /msg {dnickname} {command} [#SALON] [NICKNAME]")
await uplink.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f" Right command : /msg {dnickname} {command} [#SALON] [NICKNAME]")
return False
nickname = cmd[2]
final_reason = ' '.join(cmd[3:])
uplink.Protocol.send2socket(f":{service_id} KICK {sentchannel} {nickname} {final_reason}")
uplink.Protocol.send2socket(f":{service_id} MODE {sentchannel} +b {nickname}!*@*")
uplink.Logs.debug(f'{client} has kicked and banned {nickname} from {sentchannel} : {final_reason}')
await uplink.ctx.Irc.Protocol.send2socket(f":{service_id} KICK {sentchannel} {nickname} {final_reason}")
await uplink.ctx.Irc.Protocol.send2socket(f":{service_id} MODE {sentchannel} +b {nickname}!*@*")
uplink.ctx.Logs.debug(f'{client} has kicked and banned {nickname} from {sentchannel} : {final_reason}')
def set_assign_channel_to_service(uplink: 'Command', cmd: list[str], client: str) -> None:
async def set_assign_channel_to_service(uplink: 'Command', cmd: list[str], client: str) -> None:
if len(cmd) < 2:
raise IndexError(f"{cmd[0].upper()} is expecting the channel parameter")
command = str(cmd[0])
dnickname = uplink.Config.SERVICE_NICKNAME
sent_channel = str(cmd[1]) if uplink.Channel.is_valid_channel(cmd[1]) else None
dnickname = uplink.ctx.Config.SERVICE_NICKNAME
sent_channel = str(cmd[1]) if uplink.ctx.Channel.is_valid_channel(cmd[1]) else None
if sent_channel is None:
uplink.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON]")
await uplink.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON]")
return None
# self.Protocol.send2socket(f':{service_id} JOIN {sent_channel}')
uplink.Protocol.send_join_chan(uidornickname=dnickname,channel=sent_channel)
uplink.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f" Has joined {sent_channel}")
uplink.Channel.db_query_channel('add', uplink.module_name, sent_channel)
await uplink.ctx.Irc.Protocol.send_join_chan(uidornickname=dnickname,channel=sent_channel)
await uplink.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f" Has joined {sent_channel}")
await uplink.ctx.Channel.db_query_channel('add', uplink.module_name, sent_channel)
return None
def set_unassign_channel_to_service(uplink: 'Command', cmd: list[str], client: str) -> None:
async def set_unassign_channel_to_service(uplink: 'Command', cmd: list[str], client: str) -> None:
if len(cmd) < 2:
raise IndexError(f"{cmd[0].upper()} is expecting the channel parameter")
command = str(cmd[0])
dnickname = uplink.Config.SERVICE_NICKNAME
dchanlog = uplink.Config.SERVICE_CHANLOG
dnickname = uplink.ctx.Config.SERVICE_NICKNAME
dchanlog = uplink.ctx.Config.SERVICE_CHANLOG
sent_channel = str(cmd[1]) if uplink.Channel.is_valid_channel(cmd[1]) else None
sent_channel = str(cmd[1]) if uplink.ctx.Channel.is_valid_channel(cmd[1]) else None
if sent_channel is None:
uplink.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON]")
await uplink.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON]")
return None
if sent_channel == dchanlog:
uplink.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f"[!] CAN'T LEFT {sent_channel} AS IT IS LOG CHANNEL [!]")
await uplink.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f"[!] CAN'T LEFT {sent_channel} AS IT IS LOG CHANNEL [!]")
return None
uplink.Protocol.send_part_chan(uidornickname=dnickname, channel=sent_channel)
uplink.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f" Has left {sent_channel}")
await uplink.ctx.Irc.Protocol.send_part_chan(uidornickname=dnickname, channel=sent_channel)
await uplink.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=client, msg=f" Has left {sent_channel}")
uplink.Channel.db_query_channel('del', uplink.module_name, sent_channel)
await uplink.ctx.Channel.db_query_channel('del', uplink.module_name, sent_channel)
return None

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,7 @@ class ModConfModel(MainModel):
autolimit: int = 0
autolimit_amount: int = 3
autolimit_interval: int = 3
sentinel: int = 0
@dataclass
class FloodUser(MainModel):
@@ -27,9 +28,11 @@ class FloodUser(MainModel):
nbr_msg: int = 0
first_msg_time: int = 0
DB_FLOOD_USERS: list[FloodUser] = []
DB_ABUSEIPDB_USERS: list[MUser] = []
DB_FREEIPAPI_USERS: list[MUser] = []
DB_CLOUDFILT_USERS: list[MUser] = []
DB_PSUTIL_USERS: list[MUser] = []
DB_LOCALSCAN_USERS: list[MUser] = []
class RepDB:
DB_FLOOD_USERS: list[FloodUser] = []
DB_ABUSEIPDB_USERS: list[MUser] = []
DB_FREEIPAPI_USERS: list[MUser] = []
DB_CLOUDFILT_USERS: list[MUser] = []
DB_PSUTIL_USERS: list[MUser] = []
DB_LOCALSCAN_USERS: list[MUser] = []

View File

@@ -1,115 +1,279 @@
from typing import TYPE_CHECKING
from time import sleep
import asyncio
from typing import TYPE_CHECKING, Optional
if TYPE_CHECKING:
from mods.defender.mod_defender import Defender
def thread_apply_reputation_sanctions(uplink: 'Defender'):
async def coro_apply_reputation_sanctions(uplink: 'Defender'):
uplink.reputationTimer_isRunning = True
while uplink.reputationTimer_isRunning:
uplink.Utils.action_apply_reputation_santions(uplink)
sleep(5)
await uplink.mod_utils.action_apply_reputation_santions(uplink)
await asyncio.sleep(5)
def thread_cloudfilt_scan(uplink: 'Defender'):
async def coro_cloudfilt_scan(uplink: 'Defender'):
uplink.cloudfilt_isRunning = True
service_id = uplink.ctx.Config.SERVICE_ID
service_chanlog = uplink.ctx.Config.SERVICE_CHANLOG
color_red = uplink.ctx.Config.COLORS.red
nogc = uplink.ctx.Config.COLORS.nogc
nogc = uplink.ctx.Config.COLORS.nogc
p = uplink.ctx.Irc.Protocol
while uplink.cloudfilt_isRunning:
list_to_remove:list = []
for user in uplink.Schemas.DB_CLOUDFILT_USERS:
uplink.Utils.action_scan_client_with_cloudfilt(uplink, user)
list_to_remove.append(user)
sleep(1)
try:
list_to_remove:list = []
for user in uplink.Schemas.DB_CLOUDFILT_USERS:
if user.remote_ip not in uplink.ctx.Config.WHITELISTED_IP:
result: Optional[dict] = await uplink.ctx.Base.create_thread_io(
uplink.mod_utils.action_scan_client_with_cloudfilt,
uplink, user
)
list_to_remove.append(user)
for user_model in list_to_remove:
uplink.Schemas.DB_CLOUDFILT_USERS.remove(user_model)
if not result:
continue
sleep(1)
remote_ip = user.remote_ip
fullname = f'{user.nickname}!{user.username}@{user.hostname}'
def thread_freeipapi_scan(uplink: 'Defender'):
r_host = result.get('host', None)
r_countryiso = result.get('countryiso', None)
r_listed = result.get('listed', False)
r_listedby = result.get('listed_by', None)
await p.send_priv_msg(
nick_from=service_id,
msg=f"[ {color_red}CLOUDFILT_SCAN{nogc} ] : Connexion de {fullname} ({remote_ip}) ==> Host: {r_host} | country: {r_countryiso} | listed: {r_listed} | listed by : {r_listedby}",
channel=service_chanlog)
uplink.ctx.Logs.debug(f"[CLOUDFILT SCAN] ({fullname}) connected from ({r_countryiso}), Listed: {r_listed}, by: {r_listedby}")
if r_listed:
await p.send2socket(f":{service_id} GLINE +*@{remote_ip} {uplink.ctx.Config.GLINE_DURATION} Your connexion is listed as dangerous {r_listed} {r_listedby} - detected by cloudfilt")
uplink.ctx.Logs.debug(f"[CLOUDFILT SCAN GLINE] Dangerous connection ({fullname}) from ({r_countryiso}) Listed: {r_listed}, by: {r_listedby}")
await asyncio.sleep(1)
for user_model in list_to_remove:
uplink.Schemas.DB_CLOUDFILT_USERS.remove(user_model)
await asyncio.sleep(1.5)
except ValueError as ve:
uplink.ctx.Logs.debug(f"The value to remove is not in the list. {ve}")
except TimeoutError as te:
uplink.ctx.Logs.debug(f"Timeout Error {te}")
async def coro_freeipapi_scan(uplink: 'Defender'):
uplink.freeipapi_isRunning = True
service_id = uplink.ctx.Config.SERVICE_ID
service_chanlog = uplink.ctx.Config.SERVICE_CHANLOG
color_red = uplink.ctx.Config.COLORS.red
nogc = uplink.ctx.Config.COLORS.nogc
p = uplink.ctx.Irc.Protocol
while uplink.freeipapi_isRunning:
try:
list_to_remove: list = []
for user in uplink.Schemas.DB_FREEIPAPI_USERS:
if user.remote_ip not in uplink.ctx.Config.WHITELISTED_IP:
result: Optional[dict] = await uplink.ctx.Base.create_thread_io(
uplink.mod_utils.action_scan_client_with_freeipapi,
uplink, user
)
list_to_remove: list = []
for user in uplink.Schemas.DB_FREEIPAPI_USERS:
uplink.Utils.action_scan_client_with_freeipapi(uplink, user)
list_to_remove.append(user)
sleep(1)
if not result:
continue
for user_model in list_to_remove:
uplink.Schemas.DB_FREEIPAPI_USERS.remove(user_model)
# pseudo!ident@host
remote_ip = user.remote_ip
fullname = f'{user.nickname}!{user.username}@{user.hostname}'
sleep(1)
await p.send_priv_msg(
nick_from=service_id,
msg=f"[ {color_red}FREEIPAPI_SCAN{nogc} ] : Connexion de {fullname} ({remote_ip}) ==> Proxy: {str(result['isProxy'])} | Country : {str(result['countryCode'])}",
channel=service_chanlog)
uplink.ctx.Logs.debug(f"[FREEIPAPI SCAN] ({fullname}) connected from ({result['countryCode']}), Proxy: {result['isProxy']}")
def thread_abuseipdb_scan(uplink: 'Defender'):
if result['isProxy']:
await p.send2socket(f":{service_id} GLINE +*@{remote_ip} {uplink.ctx.Config.GLINE_DURATION} This server do not allow proxy connexions {str(result['isProxy'])} - detected by freeipapi")
uplink.ctx.Logs.debug(f"[FREEIPAPI SCAN GLINE] Server do not allow proxy connexions {result['isProxy']}")
list_to_remove.append(user)
await asyncio.sleep(1)
# remove users from the list
for user_model in list_to_remove:
uplink.Schemas.DB_FREEIPAPI_USERS.remove(user_model)
await asyncio.sleep(1.5)
except ValueError as ve:
uplink.ctx.Logs.debug(f"The value to remove is not in the list. {ve}")
except TimeoutError as te:
uplink.ctx.Logs.debug(f"Timeout Error {te}")
async def coro_abuseipdb_scan(uplink: 'Defender'):
uplink.abuseipdb_isRunning = True
service_id = uplink.ctx.Config.SERVICE_ID
service_chanlog = uplink.ctx.Config.SERVICE_CHANLOG
color_red = uplink.ctx.Config.COLORS.red
nogc = uplink.ctx.Config.COLORS.nogc
p = uplink.ctx.Irc.Protocol
while uplink.abuseipdb_isRunning:
try:
list_to_remove: list = []
for user in uplink.Schemas.DB_ABUSEIPDB_USERS:
if user.remote_ip not in uplink.ctx.Config.WHITELISTED_IP:
list_to_remove: list = []
for user in uplink.Schemas.DB_ABUSEIPDB_USERS:
uplink.Utils.action_scan_client_with_abuseipdb(uplink, user)
list_to_remove.append(user)
sleep(1)
result: Optional[dict] = await uplink.ctx.Base.create_thread_io(
uplink.mod_utils.action_scan_client_with_abuseipdb,
uplink, user
)
list_to_remove.append(user)
for user_model in list_to_remove:
uplink.Schemas.DB_ABUSEIPDB_USERS.remove(user_model)
if not result:
continue
sleep(1)
remote_ip = user.remote_ip
fullname = f'{user.nickname}!{user.username}@{user.hostname}'
def thread_local_scan(uplink: 'Defender'):
await p.send_priv_msg(
nick_from=service_id,
msg=f"[ {color_red}ABUSEIPDB_SCAN{nogc} ] : Connexion de {fullname} ({remote_ip}) ==> Score: {str(result['score'])} | Country : {result['country']} | Tor : {str(result['isTor'])} | Total Reports : {str(result['totalReports'])}",
channel=service_chanlog
)
uplink.ctx.Logs.debug(f"[ABUSEIPDB SCAN] ({fullname}) connected from ({result['country']}), Score: {result['score']}, Tor: {result['isTor']}")
if result['isTor']:
await p.send2socket(f":{service_id} GLINE +*@{remote_ip} {uplink.ctx.Config.GLINE_DURATION} This server do not allow Tor connexions {str(result['isTor'])} - Detected by Abuseipdb")
uplink.ctx.Logs.debug(f"[ABUSEIPDB SCAN GLINE] Server do not allow Tor connections Tor: {result['isTor']}, Score: {result['score']}")
elif result['score'] >= 95:
await p.send2socket(f":{service_id} GLINE +*@{remote_ip} {uplink.ctx.Config.GLINE_DURATION} You were banned from this server because your abuse score is = {str(result['score'])} - Detected by Abuseipdb")
uplink.ctx.Logs.debug(f"[ABUSEIPDB SCAN GLINE] Server do not high risk connections Country: {result['country']}, Score: {result['score']}")
await asyncio.sleep(1)
for user_model in list_to_remove:
uplink.Schemas.DB_ABUSEIPDB_USERS.remove(user_model)
await asyncio.sleep(1.5)
except ValueError as ve:
uplink.ctx.Logs.debug(f"The value to remove is not in the list. {ve}", exc_info=True)
except TimeoutError as te:
uplink.ctx.Logs.debug(f"Timeout Error {te}", exc_info=True)
async def coro_local_scan(uplink: 'Defender'):
uplink.localscan_isRunning = True
service_id = uplink.ctx.Config.SERVICE_ID
service_chanlog = uplink.ctx.Config.SERVICE_CHANLOG
color_red = uplink.ctx.Config.COLORS.red
nogc = uplink.ctx.Config.COLORS.nogc
p = uplink.ctx.Irc.Protocol
while uplink.localscan_isRunning:
list_to_remove:list = []
for user in uplink.Schemas.DB_LOCALSCAN_USERS:
uplink.Utils.action_scan_client_with_local_socket(uplink, user)
list_to_remove.append(user)
sleep(1)
try:
list_to_remove:list = []
for user in uplink.Schemas.DB_LOCALSCAN_USERS:
if user.remote_ip not in uplink.ctx.Config.WHITELISTED_IP:
list_to_remove.append(user)
result = await uplink.ctx.Base.create_thread_io(
uplink.mod_utils.action_scan_client_with_local_socket,
uplink, user
)
for user_model in list_to_remove:
uplink.Schemas.DB_LOCALSCAN_USERS.remove(user_model)
if not result:
continue
fullname = f'{user.nickname}!{user.username}@{user.hostname}'
opened_ports = result['opened_ports']
closed_ports = result['closed_ports']
if opened_ports:
await p.send_priv_msg(
nick_from=service_id,
msg=f"[ {color_red}LOCAL_SCAN{nogc} ] {fullname} ({user.remote_ip}) : The Port(s) {opened_ports} are opened on this remote ip [{user.remote_ip}]",
channel=service_chanlog
)
if closed_ports:
await p.send_priv_msg(
nick_from=service_id,
msg=f"[ {color_red}LOCAL_SCAN{nogc} ] {fullname} ({user.remote_ip}) : The Port(s) {closed_ports} are closed on this remote ip [{user.remote_ip}]",
channel=service_chanlog
)
await asyncio.sleep(1)
sleep(1)
for user_model in list_to_remove:
uplink.Schemas.DB_LOCALSCAN_USERS.remove(user_model)
def thread_psutil_scan(uplink: 'Defender'):
await asyncio.sleep(1.5)
except ValueError as ve:
uplink.ctx.Logs.debug(f"The value to remove is not in the list. {ve}")
except TimeoutError as te:
uplink.ctx.Logs.debug(f"Timeout Error {te}")
while uplink.psutil_isRunning:
async def coro_psutil_scan(uplink: 'Defender'):
uplink.psutil_isRunning = True
service_id = uplink.ctx.Config.SERVICE_ID
service_chanlog = uplink.ctx.Config.SERVICE_CHANLOG
color_red = uplink.ctx.Config.COLORS.red
nogc = uplink.ctx.Config.COLORS.nogc
p = uplink.ctx.Irc.Protocol
while uplink.psutil_isRunning:
try:
list_to_remove:list = []
for user in uplink.Schemas.DB_PSUTIL_USERS:
uplink.Utils.action_scan_client_with_psutil(uplink, user)
result = await uplink.ctx.Base.create_thread_io(uplink.mod_utils.action_scan_client_with_psutil, uplink, user)
list_to_remove.append(user)
sleep(1)
if not result:
continue
fullname = f'{user.nickname}!{user.username}@{user.hostname}'
await p.send_priv_msg(
nick_from=service_id,
msg=f"[ {color_red}PSUTIL_SCAN{nogc} ] {fullname} ({user.remote_ip}) is using ports {result}",
channel=service_chanlog
)
await asyncio.sleep(1)
for user_model in list_to_remove:
uplink.Schemas.DB_PSUTIL_USERS.remove(user_model)
sleep(1)
await asyncio.sleep(1.5)
except ValueError as ve:
uplink.ctx.Logs.debug(f"The value to remove is not in the list. {ve}")
except TimeoutError as te:
uplink.ctx.Logs.debug(f"Timeout Error {te}")
def thread_autolimit(uplink: 'Defender'):
async def coro_autolimit(uplink: 'Defender'):
if uplink.ModConfig.autolimit == 0:
uplink.Logs.debug("autolimit deactivated ... canceling the thread")
if uplink.mod_config.autolimit == 0:
uplink.ctx.Logs.debug("autolimit deactivated ... canceling the thread")
return None
while uplink.Irc.autolimit_started:
sleep(0.2)
while uplink.ctx.Irc.autolimit_started:
await asyncio.sleep(0.2)
uplink.Irc.autolimit_started = True
init_amount = uplink.ModConfig.autolimit_amount
p = uplink.Protocol
uplink.ctx.Irc.autolimit_started = True
init_amount = uplink.mod_config.autolimit_amount
p = uplink.ctx.Irc.Protocol
INIT = 1
# Copy Channels to a list of dict
chanObj_copy: list[dict[str, int]] = [{"name": c.name, "uids_count": len(c.uids)} for c in uplink.Channel.UID_CHANNEL_DB]
chan_list: list[str] = [c.name for c in uplink.Channel.UID_CHANNEL_DB]
chanObj_copy: list[dict[str, int]] = [{"name": c.name, "uids_count": len(c.uids)} for c in uplink.ctx.Channel.UID_CHANNEL_DB]
chan_list: list[str] = [c.name for c in uplink.ctx.Channel.UID_CHANNEL_DB]
while uplink.autolimit_isRunning:
if uplink.ModConfig.autolimit == 0:
uplink.Logs.debug("autolimit deactivated ... stopping the current thread")
if uplink.mod_config.autolimit == 0:
uplink.ctx.Logs.debug("autolimit deactivated ... stopping the current thread")
break
for chan in uplink.Channel.UID_CHANNEL_DB:
for chan in uplink.ctx.Channel.UID_CHANNEL_DB:
for chan_copy in chanObj_copy:
if chan_copy["name"] == chan.name and len(chan.uids) != chan_copy["uids_count"]:
p.send2socket(f":{uplink.Config.SERVICE_ID} MODE {chan.name} +l {len(chan.uids) + uplink.ModConfig.autolimit_amount}")
await p.send_set_mode('+l', channel_name=chan.name, params=len(chan.uids) + uplink.mod_config.autolimit_amount)
chan_copy["uids_count"] = len(chan.uids)
if chan.name not in chan_list:
@@ -117,51 +281,53 @@ def thread_autolimit(uplink: 'Defender'):
chanObj_copy.append({"name": chan.name, "uids_count": 0})
# Verifier si un salon a été vidé
current_chan_in_list = [d.name for d in uplink.Channel.UID_CHANNEL_DB]
current_chan_in_list = [d.name for d in uplink.ctx.Channel.UID_CHANNEL_DB]
for c in chan_list:
if c not in current_chan_in_list:
chan_list.remove(c)
# Si c'est la premiere execution
if INIT == 1:
for chan in uplink.Channel.UID_CHANNEL_DB:
p.send2socket(f":{uplink.Config.SERVICE_ID} MODE {chan.name} +l {len(chan.uids) + uplink.ModConfig.autolimit_amount}")
for chan in uplink.ctx.Channel.UID_CHANNEL_DB:
await p.send_set_mode('+l', channel_name=chan.name, params=len(chan.uids) + uplink.mod_config.autolimit_amount)
# Si le nouveau amount est différent de l'initial
if init_amount != uplink.ModConfig.autolimit_amount:
init_amount = uplink.ModConfig.autolimit_amount
for chan in uplink.Channel.UID_CHANNEL_DB:
p.send2socket(f":{uplink.Config.SERVICE_ID} MODE {chan.name} +l {len(chan.uids) + uplink.ModConfig.autolimit_amount}")
if init_amount != uplink.mod_config.autolimit_amount:
init_amount = uplink.mod_config.autolimit_amount
for chan in uplink.ctx.Channel.UID_CHANNEL_DB:
await p.send_set_mode('+l', channel_name=chan.name, params=len(chan.uids) + uplink.mod_config.autolimit_amount)
INIT = 0
if uplink.autolimit_isRunning:
sleep(uplink.ModConfig.autolimit_interval)
await asyncio.sleep(uplink.mod_config.autolimit_interval)
for chan in uplink.Channel.UID_CHANNEL_DB:
p.send2socket(f":{uplink.Config.SERVICE_ID} MODE {chan.name} -l")
for chan in uplink.ctx.Channel.UID_CHANNEL_DB:
await p.send_set_mode('-l', channel_name=chan.name)
uplink.Irc.autolimit_started = False
uplink.ctx.Irc.autolimit_started = False
return None
def timer_release_mode_mute(uplink: 'Defender', action: str, channel: str):
"""DO NOT EXECUTE THIS FUNCTION WITHOUT THREADING
async def coro_release_mode_mute(uplink: 'Defender', action: str, channel: str):
"""DO NOT EXECUTE THIS FUNCTION DIRECTLY
IT WILL BLOCK THE PROCESS
Args:
action (str): _description_
action (str): mode-m
channel (str): The related channel
"""
service_id = uplink.Config.SERVICE_ID
timeout = uplink.mod_config.flood_timer
await asyncio.sleep(timeout)
if not uplink.Channel.is_valid_channel(channel):
uplink.Logs.debug(f"Channel is not valid {channel}")
if not uplink.ctx.Channel.is_valid_channel(channel):
uplink.ctx.Logs.debug(f"Channel is not valid {channel}")
return
match action:
case 'mode-m':
# Action -m sur le salon
uplink.Protocol.send2socket(f":{service_id} MODE {channel} -m")
await uplink.ctx.Irc.Protocol.send_set_mode('-m', channel_name=channel)
case _:
pass

View File

@@ -9,6 +9,7 @@ from typing import TYPE_CHECKING, Optional
from mods.defender.schemas import FloodUser
if TYPE_CHECKING:
from core.loader import Loader
from core.definition import MUser
from mods.defender.mod_defender import Defender
@@ -28,10 +29,10 @@ def handle_on_reputation(uplink: 'Defender', srvmsg: list[str]):
return
# Possibilité de déclancher les bans a ce niveau.
if not uplink.Base.is_valid_ip(ip):
if not uplink.ctx.Base.is_valid_ip(ip):
return
def handle_on_mode(uplink: 'Defender', srvmsg: list[str]):
async def handle_on_mode(uplink: 'Defender', srvmsg: list[str]):
"""_summary_
>>> srvmsg = ['@unrealircd.org/...', ':001C0MF01', 'MODE', '#services', '+l', '1']
>>> srvmsg = ['...', ':001XSCU0Q', 'MODE', '#jail', '+b', '~security-group:unknown-users']
@@ -40,10 +41,10 @@ def handle_on_mode(uplink: 'Defender', srvmsg: list[str]):
srvmsg (list[str]): The Server MSG
confmodel (ModConfModel): The Module Configuration
"""
irc = uplink.Irc
gconfig = uplink.Config
p = uplink.Protocol
confmodel = uplink.ModConfig
irc = uplink.ctx.Irc
gconfig = uplink.ctx.Config
p = irc.Protocol
confmodel = uplink.mod_config
channel = str(srvmsg[3])
mode = str(srvmsg[4])
@@ -52,20 +53,25 @@ def handle_on_mode(uplink: 'Defender', srvmsg: list[str]):
if confmodel.autolimit == 1:
if mode == '+l' or mode == '-l':
chan = irc.Channel.get_channel(channel)
p.send2socket(f":{gconfig.SERVICE_ID} MODE {chan.name} +l {len(chan.uids) + confmodel.autolimit_amount}")
chan = uplink.ctx.Channel.get_channel(channel)
await p.send2socket(f":{gconfig.SERVICE_ID} MODE {chan.name} +l {len(chan.uids) + confmodel.autolimit_amount}")
if gconfig.SALON_JAIL == channel:
if mode == '+b' and group_to_unban in group_to_check:
p.send2socket(f":{gconfig.SERVICE_ID} MODE {gconfig.SALON_JAIL} -b ~security-group:unknown-users")
p.send2socket(f":{gconfig.SERVICE_ID} MODE {gconfig.SALON_JAIL} -eee ~security-group:webirc-users ~security-group:known-users ~security-group:websocket-users")
await p.send2socket(f":{gconfig.SERVICE_ID} MODE {gconfig.SALON_JAIL} -b ~security-group:unknown-users")
await p.send2socket(f":{gconfig.SERVICE_ID} MODE {gconfig.SALON_JAIL} -eee ~security-group:webirc-users ~security-group:known-users ~security-group:websocket-users")
def handle_on_privmsg(uplink: 'Defender', srvmsg: list[str]):
async def handle_on_privmsg(uplink: 'Defender', srvmsg: list[str]):
# ['@mtag....',':python', 'PRIVMSG', '#defender', ':zefzefzregreg', 'regg', 'aerg']
action_on_flood(uplink, srvmsg)
sender, reciever, channel, message = uplink.ctx.Irc.Protocol.parse_privmsg(srvmsg)
if uplink.mod_config.sentinel == 1 and channel.name != uplink.ctx.Config.SERVICE_CHANLOG:
await uplink.ctx.Irc.Protocol.send_priv_msg(uplink.ctx.Config.SERVICE_NICKNAME, f"{sender.nickname} say on {channel.name}: {' '.join(message)}", uplink.ctx.Config.SERVICE_CHANLOG)
await action_on_flood(uplink, srvmsg)
return None
def handle_on_sjoin(uplink: 'Defender', srvmsg: list[str]):
async def handle_on_sjoin(uplink: 'Defender', srvmsg: list[str]):
"""If Joining a new channel, it applies group bans.
>>> srvmsg = ['@msgid..', ':001', 'SJOIN', '1702138958', '#welcome', ':0015L1AHL']
@@ -75,37 +81,37 @@ def handle_on_sjoin(uplink: 'Defender', srvmsg: list[str]):
srvmsg (list[str]): The Server MSG
confmodel (ModConfModel): The Module Configuration
"""
irc = uplink.Irc
irc = uplink.ctx.Irc
p = irc.Protocol
gconfig = uplink.Config
confmodel = uplink.ModConfig
gconfig = uplink.ctx.Config
confmodel = uplink.mod_config
parsed_chan = srvmsg[4] if irc.Channel.is_valid_channel(srvmsg[4]) else None
parsed_UID = uplink.Loader.Utils.clean_uid(srvmsg[5])
parsed_chan = srvmsg[4] if uplink.ctx.Channel.is_valid_channel(srvmsg[4]) else None
parsed_UID = uplink.ctx.Utils.clean_uid(srvmsg[5])
if parsed_chan is None or parsed_UID is None:
return
if confmodel.reputation == 1:
get_reputation = irc.Reputation.get_Reputation(parsed_UID)
get_reputation = uplink.ctx.Reputation.get_reputation(parsed_UID)
if parsed_chan != gconfig.SALON_JAIL:
p.send2socket(f":{gconfig.SERVICE_ID} MODE {parsed_chan} +b ~security-group:unknown-users")
p.send2socket(f":{gconfig.SERVICE_ID} MODE {parsed_chan} +eee ~security-group:webirc-users ~security-group:known-users ~security-group:websocket-users")
await p.send2socket(f":{gconfig.SERVICE_ID} MODE {parsed_chan} +b ~security-group:unknown-users")
await p.send2socket(f":{gconfig.SERVICE_ID} MODE {parsed_chan} +eee ~security-group:webirc-users ~security-group:known-users ~security-group:websocket-users")
if get_reputation is not None:
isWebirc = get_reputation.isWebirc
if not isWebirc:
if parsed_chan != gconfig.SALON_JAIL:
p.send_sapart(nick_to_sapart=get_reputation.nickname, channel_name=parsed_chan)
await p.send_sapart(nick_to_sapart=get_reputation.nickname, channel_name=parsed_chan)
if confmodel.reputation_ban_all_chan == 1 and not isWebirc:
if parsed_chan != gconfig.SALON_JAIL:
p.send2socket(f":{gconfig.SERVICE_ID} MODE {parsed_chan} +b {get_reputation.nickname}!*@*")
p.send2socket(f":{gconfig.SERVICE_ID} KICK {parsed_chan} {get_reputation.nickname}")
await p.send2socket(f":{gconfig.SERVICE_ID} MODE {parsed_chan} +b {get_reputation.nickname}!*@*")
await p.send2socket(f":{gconfig.SERVICE_ID} KICK {parsed_chan} {get_reputation.nickname}")
irc.Logs.debug(f'SJOIN parsed_uid : {parsed_UID}')
uplink.ctx.Logs.debug(f'SJOIN parsed_uid : {parsed_UID}')
def handle_on_slog(uplink: 'Defender', srvmsg: list[str]):
"""Handling SLOG messages
@@ -117,85 +123,96 @@ def handle_on_slog(uplink: 'Defender', srvmsg: list[str]):
"""
['@unrealircd...', ':001', 'SLOG', 'info', 'blacklist', 'BLACKLIST_HIT', ':[Blacklist]', 'IP', '162.x.x.x', 'matches', 'blacklist', 'dronebl', '(dnsbl.dronebl.org/reply=6)']
if not uplink.Base.is_valid_ip(srvmsg[8]):
if not uplink.ctx.Base.is_valid_ip(srvmsg[8]):
return None
# if self.ModConfig.local_scan == 1 and not cmd[7] in self.Config.WHITELISTED_IP:
# if self.mod_config.local_scan == 1 and not cmd[7] in self.Config.WHITELISTED_IP:
# self.localscan_remote_ip.append(cmd[7])
# if self.ModConfig.psutil_scan == 1 and not cmd[7] in self.Config.WHITELISTED_IP:
# if self.mod_config.psutil_scan == 1 and not cmd[7] in self.Config.WHITELISTED_IP:
# self.psutil_remote_ip.append(cmd[7])
# if self.ModConfig.abuseipdb_scan == 1 and not cmd[7] in self.Config.WHITELISTED_IP:
# if self.mod_config.abuseipdb_scan == 1 and not cmd[7] in self.Config.WHITELISTED_IP:
# self.abuseipdb_remote_ip.append(cmd[7])
# if self.ModConfig.freeipapi_scan == 1 and not cmd[7] in self.Config.WHITELISTED_IP:
# if self.mod_config.freeipapi_scan == 1 and not cmd[7] in self.Config.WHITELISTED_IP:
# self.freeipapi_remote_ip.append(cmd[7])
# if self.ModConfig.cloudfilt_scan == 1 and not cmd[7] in self.Config.WHITELISTED_IP:
# if self.mod_config.cloudfilt_scan == 1 and not cmd[7] in self.Config.WHITELISTED_IP:
# self.cloudfilt_remote_ip.append(cmd[7])
return None
def handle_on_nick(uplink: 'Defender', srvmsg: list[str]):
"""_summary_
async def handle_on_nick(uplink: 'Defender', srvmsg: list[str]):
"""Handle nickname changes.
>>> srvmsg = ['@unrealircd.org...', ':001MZQ0RB', 'NICK', 'newnickname', '1754663712']
>>> [':97KAAAAAC', 'NICK', 'testinspir', '1757360740']
Args:
irc_instance (Irc): The Irc instance
srvmsg (list[str]): The Server MSG
confmodel (ModConfModel): The Module Configuration
"""
uid = uplink.Loader.Utils.clean_uid(str(srvmsg[1]))
p = uplink.Protocol
confmodel = uplink.ModConfig
p = uplink.ctx.Irc.Protocol
u, new_nickname, timestamp = p.parse_nick(srvmsg)
get_reputation = uplink.Reputation.get_Reputation(uid)
jail_salon = uplink.Config.SALON_JAIL
service_id = uplink.Config.SERVICE_ID
if u is None:
uplink.ctx.Logs.error(f"[USER OBJ ERROR {timestamp}] - {srvmsg}")
return None
uid = u.uid
confmodel = uplink.mod_config
get_reputation = uplink.ctx.Reputation.get_reputation(uid)
jail_salon = uplink.ctx.Config.SALON_JAIL
service_id = uplink.ctx.Config.SERVICE_ID
if get_reputation is None:
uplink.Logs.debug(f'This UID: {uid} is not listed in the reputation dataclass')
uplink.ctx.Logs.debug(f'This UID: {uid} is not listed in the reputation dataclass')
return None
# Update the new nickname
oldnick = get_reputation.nickname
newnickname = srvmsg[3]
newnickname = new_nickname
get_reputation.nickname = newnickname
# If ban in all channel is ON then unban old nickname an ban the new nickname
if confmodel.reputation_ban_all_chan == 1:
for chan in uplink.Channel.UID_CHANNEL_DB:
for chan in uplink.ctx.Channel.UID_CHANNEL_DB:
if chan.name != jail_salon:
p.send2socket(f":{service_id} MODE {chan.name} -b {oldnick}!*@*")
p.send2socket(f":{service_id} MODE {chan.name} +b {newnickname}!*@*")
await p.send2socket(f":{service_id} MODE {chan.name} -b {oldnick}!*@*")
await p.send2socket(f":{service_id} MODE {chan.name} +b {newnickname}!*@*")
def handle_on_quit(uplink: 'Defender', srvmsg: list[str]):
"""_summary_
async def handle_on_quit(uplink: 'Defender', srvmsg: list[str]):
"""Handle on quit message
>>> srvmsg = ['@unrealircd.org...', ':001MZQ0RB', 'QUIT', ':Quit:', 'quit message']
Args:
uplink (Irc): The Defender Module instance
srvmsg (list[str]): The Server MSG
"""
p = uplink.Protocol
confmodel = uplink.ModConfig
p = uplink.ctx.Irc.Protocol
userobj, reason = p.parse_quit(srvmsg)
confmodel = uplink.mod_config
if userobj is None:
uplink.ctx.Logs.debug(f"This UID do not exist anymore: {srvmsg}")
return None
ban_all_chan = uplink.Base.int_if_possible(confmodel.reputation_ban_all_chan)
final_UID = uplink.Loader.Utils.clean_uid(str(srvmsg[1]))
jail_salon = uplink.Config.SALON_JAIL
service_id = uplink.Config.SERVICE_ID
get_user_reputation = uplink.Reputation.get_Reputation(final_UID)
ban_all_chan = uplink.ctx.Base.int_if_possible(confmodel.reputation_ban_all_chan)
jail_salon = uplink.ctx.Config.SALON_JAIL
service_id = uplink.ctx.Config.SERVICE_ID
get_user_reputation = uplink.ctx.Reputation.get_reputation(userobj.uid)
if get_user_reputation is not None:
final_nickname = get_user_reputation.nickname
for chan in uplink.Channel.UID_CHANNEL_DB:
for chan in uplink.ctx.Channel.UID_CHANNEL_DB:
if chan.name != jail_salon and ban_all_chan == 1:
p.send2socket(f":{service_id} MODE {chan.name} -b {final_nickname}!*@*")
uplink.Logs.debug(f"Mode -b {final_nickname} on channel {chan.name}")
await p.send2socket(f":{service_id} MODE {chan.name} -b {final_nickname}!*@*")
uplink.ctx.Logs.debug(f"Mode -b {final_nickname} on channel {chan.name}")
uplink.Reputation.delete(final_UID)
uplink.Logs.debug(f"Client {get_user_reputation.nickname} has been removed from Reputation local DB")
uplink.ctx.Reputation.delete(userobj.uid)
uplink.ctx.Logs.debug(f"Client {get_user_reputation.nickname} has been removed from Reputation local DB")
def handle_on_uid(uplink: 'Defender', srvmsg: list[str]):
async def handle_on_uid(uplink: 'Defender', srvmsg: list[str]):
"""_summary_
>>> ['@s2s-md...', ':001', 'UID', 'nickname', '0', '1754675249', '...', '125-168-141-239.hostname.net', '001BAPN8M',
'0', '+iwx', '*', '32001BBE.25ACEFE7.429FE90D.IP', 'ZA2ic7w==', ':realname']
@@ -204,19 +221,18 @@ def handle_on_uid(uplink: 'Defender', srvmsg: list[str]):
uplink (Defender): The Defender instance
srvmsg (list[str]): The Server MSG
"""
gconfig = uplink.Config
irc = uplink.Irc
confmodel = uplink.ModConfig
irc = uplink.ctx.Irc
_User = irc.Protocol.parse_uid(srvmsg)
gconfig = uplink.ctx.Config
confmodel = uplink.mod_config
# If Init then do nothing
if gconfig.DEFENDER_INIT == 1:
return None
# Get User information
_User = irc.User.get_user(str(srvmsg[8]))
if _User is None:
irc.Logs.warning(f'This UID: [{srvmsg[8]}] is not available please check why')
uplink.ctx.Logs.warning(f'Error when parsing UID', exc_info=True)
return
# If user is not service or IrcOp then scan them
@@ -235,37 +251,39 @@ def handle_on_uid(uplink: 'Defender', srvmsg: list[str]):
if not match(r'^.*[S|o?].*$', _User.umodes):
if reputation_flag == 1 and _User.score_connexion <= reputation_seuil:
# currentDateTime = self.Base.get_datetime()
irc.Reputation.insert(
irc.Loader.Definition.MReputation(
uplink.ctx.Reputation.insert(
uplink.ctx.Definition.MReputation(
**_User.to_dict(),
secret_code=irc.Utils.generate_random_string(8)
secret_code=uplink.ctx.Utils.generate_random_string(8)
)
)
if irc.Reputation.is_exist(_User.uid):
if uplink.ctx.Reputation.is_exist(_User.uid):
if reputation_flag == 1 and _User.score_connexion <= reputation_seuil:
action_add_reputation_sanctions(uplink, _User.uid)
irc.Logs.info(f'[REPUTATION] Reputation system ON (Nickname: {_User.nickname}, uid: {_User.uid})')
await action_add_reputation_sanctions(uplink, _User.uid)
uplink.ctx.Logs.info(f'[REPUTATION] Reputation system ON (Nickname: {_User.nickname}, uid: {_User.uid})')
####################
# ACTION FUNCTIONS #
####################
# [:<sid>] UID <uid> <ts> <nick> <real-host> <displayed-host> <real-user> <ip> <signon> <modes> [<mode-parameters>]+ :<real>
# [:<sid>] UID nickname hopcount timestamp username hostname uid servicestamp umodes virthost cloakedhost ip :gecos
def action_on_flood(uplink: 'Defender', srvmsg: list[str]):
async def action_on_flood(uplink: 'Defender', srvmsg: list[str]):
confmodel = uplink.ModConfig
confmodel = uplink.mod_config
if confmodel.flood == 0:
return None
irc = uplink.Irc
gconfig = uplink.Config
p = uplink.Protocol
irc = uplink.ctx.Irc
gconfig = uplink.ctx.Config
p = irc.Protocol
flood_users = uplink.Schemas.DB_FLOOD_USERS
user_trigger = str(srvmsg[1]).replace(':','')
channel = srvmsg[3]
User = irc.User.get_user(user_trigger)
User = uplink.ctx.User.get_user(user_trigger)
if User is None or not irc.Channel.is_valid_channel(channel_to_check=channel):
if User is None or not uplink.ctx.Channel.is_valid_channel(channel_to_check=channel):
return
flood_time = confmodel.flood_time
@@ -278,7 +296,7 @@ def action_on_flood(uplink: 'Defender', srvmsg: list[str]):
get_detected_uid = User.uid
get_detected_nickname = User.nickname
unixtime = irc.Utils.get_unixtime()
unixtime = uplink.ctx.Utils.get_unixtime()
get_diff_secondes = 0
def get_flood_user(uid: str) -> Optional[FloodUser]:
@@ -299,30 +317,33 @@ def action_on_flood(uplink: 'Defender', srvmsg: list[str]):
fu.nbr_msg = 0
get_diff_secondes = unixtime - fu.first_msg_time
elif fu.nbr_msg > flood_message:
irc.Logs.info('system de flood detecté')
p.send_priv_msg(
await p.send_set_mode('+m', channel_name=channel)
await p.send_priv_msg(
nick_from=dnickname,
msg=f"{color_red} {color_bold} Flood detected. Apply the +m mode (Ô_o)",
channel=channel
)
p.send2socket(f":{service_id} MODE {channel} +m")
irc.Logs.info(f'FLOOD Détecté sur {get_detected_nickname} mode +m appliqué sur le salon {channel}')
uplink.ctx.Logs.debug(f'[FLOOD] {get_detected_nickname} triggered +m mode on the channel {channel}')
fu.nbr_msg = 0
fu.first_msg_time = unixtime
irc.Base.create_timer(flood_timer, dthreads.timer_release_mode_mute, (uplink, 'mode-m', channel))
uplink.ctx.Base.create_asynctask(uplink.Threads.coro_release_mode_mute(uplink, 'mode-m', channel))
def action_add_reputation_sanctions(uplink: 'Defender', jailed_uid: str ):
async def action_add_reputation_sanctions(uplink: 'Defender', jailed_uid: str ):
irc = uplink.Irc
gconfig = uplink.Config
p = uplink.Protocol
confmodel = uplink.ModConfig
get_reputation = irc.Reputation.get_Reputation(jailed_uid)
irc = uplink.ctx.Irc
gconfig = uplink.ctx.Config
p = irc.Protocol
confmodel = uplink.mod_config
get_reputation = uplink.ctx.Reputation.get_reputation(jailed_uid)
if get_reputation is None:
irc.Logs.warning(f'UID {jailed_uid} has not been found')
return
uplink.ctx.Logs.warning(f'UID {jailed_uid} has not been found')
return None
if get_reputation.isWebirc or get_reputation.isWebsocket:
uplink.ctx.Logs.debug(f'This nickname is exampted from the reputation system (Webirc or Websocket). {get_reputation.nickname} ({get_reputation.uid})')
uplink.ctx.Reputation.delete(get_reputation.uid)
return None
salon_logs = gconfig.SERVICE_CHANLOG
salon_jail = gconfig.SALON_JAIL
@@ -341,33 +362,33 @@ def action_add_reputation_sanctions(uplink: 'Defender', jailed_uid: str ):
if not get_reputation.isWebirc:
# Si le user ne vient pas de webIrc
p.send_sajoin(nick_to_sajoin=jailed_nickname, channel_name=salon_jail)
p.send_priv_msg(nick_from=gconfig.SERVICE_NICKNAME,
msg=f" [{color_red} REPUTATION {nogc}] : Connexion de {jailed_nickname} ({jailed_score}) ==> {salon_jail}",
await p.send_sajoin(nick_to_sajoin=jailed_nickname, channel_name=salon_jail)
await p.send_priv_msg(nick_from=gconfig.SERVICE_NICKNAME,
msg=f" [ {color_red}REPUTATION{nogc} ]: The nickname {jailed_nickname} has been sent to {salon_jail} because his reputation score is ({jailed_score})",
channel=salon_logs
)
p.send_notice(
await p.send_notice(
nick_from=gconfig.SERVICE_NICKNAME,
nick_to=jailed_nickname,
msg=f"[{color_red} {jailed_nickname} {color_black}] : Merci de tapez la commande suivante {color_bold}{service_prefix}code {code}{color_bold}"
)
if reputation_ban_all_chan == 1:
for chan in irc.Channel.UID_CHANNEL_DB:
for chan in uplink.ctx.Channel.UID_CHANNEL_DB:
if chan.name != salon_jail:
p.send2socket(f":{service_id} MODE {chan.name} +b {jailed_nickname}!*@*")
p.send2socket(f":{service_id} KICK {chan.name} {jailed_nickname}")
await p.send_set_mode('+b', channel_name=chan.name, params=f'{jailed_nickname}!*@*')
await p.send2socket(f":{service_id} KICK {chan.name} {jailed_nickname}")
irc.Logs.info(f"[REPUTATION] {jailed_nickname} jailed (UID: {jailed_uid}, score: {jailed_score})")
uplink.ctx.Logs.info(f"[REPUTATION] {jailed_nickname} jailed (UID: {jailed_uid}, score: {jailed_score})")
else:
irc.Logs.info(f"[REPUTATION] {jailed_nickname} skipped (trusted or WebIRC)")
irc.Reputation.delete(jailed_uid)
uplink.ctx.Logs.info(f"[REPUTATION] {jailed_nickname} skipped (trusted or WebIRC)")
uplink.ctx.Reputation.delete(jailed_uid)
def action_apply_reputation_santions(uplink: 'Defender') -> None:
async def action_apply_reputation_santions(uplink: 'Defender') -> None:
irc = uplink.Irc
gconfig = uplink.Config
p = uplink.Protocol
confmodel = uplink.ModConfig
irc = uplink.ctx.Irc
gconfig = uplink.ctx.Config
p = irc.Protocol
confmodel = uplink.mod_config
reputation_flag = confmodel.reputation
reputation_timer = confmodel.reputation_timer
@@ -378,39 +399,36 @@ def action_apply_reputation_santions(uplink: 'Defender') -> None:
color_red = gconfig.COLORS.red
nogc = gconfig.COLORS.nogc
salon_jail = gconfig.SALON_JAIL
if reputation_flag == 0:
return None
elif reputation_timer == 0:
return None
uid_to_clean = []
for user in irc.Reputation.UID_REPUTATION_DB:
if reputation_flag == 0 or reputation_timer == 0 or not uplink.ctx.Reputation.UID_REPUTATION_DB:
return None
for user in uplink.ctx.Reputation.UID_REPUTATION_DB:
if not user.isWebirc: # Si il ne vient pas de WebIRC
if irc.User.get_user_uptime_in_minutes(user.uid) >= reputation_timer and int(user.score_connexion) <= int(reputation_seuil):
p.send_priv_msg(
if uplink.ctx.User.get_user_uptime_in_minutes(user.uid) >= reputation_timer and int(user.score_connexion) <= int(reputation_seuil):
await p.send_priv_msg(
nick_from=service_id,
msg=f"[{color_red} REPUTATION {nogc}] : Action sur {user.nickname} aprés {str(reputation_timer)} minutes d'inactivité",
channel=dchanlog
)
p.send2socket(f":{service_id} KILL {user.nickname} After {str(reputation_timer)} minutes of inactivity you should reconnect and type the password code")
p.send2socket(f":{gconfig.SERVEUR_LINK} REPUTATION {user.remote_ip} 0")
await p.send2socket(f":{service_id} KILL {user.nickname} After {str(reputation_timer)} minutes of inactivity you should reconnect and type the password code")
await p.send2socket(f":{gconfig.SERVEUR_LINK} REPUTATION {user.remote_ip} 0")
irc.Logs.info(f"Nickname: {user.nickname} KILLED after {str(reputation_timer)} minutes of inactivity")
uplink.ctx.Logs.info(f"Nickname: {user.nickname} KILLED after {str(reputation_timer)} minutes of inactivity")
uid_to_clean.append(user.uid)
for uid in uid_to_clean:
# Suppression des éléments dans {UID_DB} et {REPUTATION_DB}
for chan in irc.Channel.UID_CHANNEL_DB:
for chan in uplink.ctx.Channel.UID_CHANNEL_DB:
if chan.name != salon_jail and ban_all_chan == 1:
get_user_reputation = irc.Reputation.get_Reputation(uid)
p.send2socket(f":{service_id} MODE {chan.name} -b {get_user_reputation.nickname}!*@*")
get_user_reputation = uplink.ctx.Reputation.get_reputation(uid)
await p.send_set_mode('-b', channel_name=chan.name, params=f"{get_user_reputation.nickname}!*@*")
# Lorsqu'un utilisateur quitte, il doit être supprimé de {UID_DB}.
irc.Channel.delete_user_from_all_channel(uid)
irc.Reputation.delete(uid)
irc.User.delete(uid)
uplink.ctx.Channel.delete_user_from_all_channel(uid)
uplink.ctx.Reputation.delete(uid)
uplink.ctx.User.delete(uid)
def action_scan_client_with_cloudfilt(uplink: 'Defender', user_model: 'MUser') -> Optional[dict[str, str]]:
"""Analyse l'ip avec cloudfilt
@@ -424,62 +442,33 @@ def action_scan_client_with_cloudfilt(uplink: 'Defender', user_model: 'MUser') -
"""
remote_ip = user_model.remote_ip
username = user_model.username
hostname = user_model.hostname
nickname = user_model.nickname
p = uplink.Protocol
if remote_ip in uplink.Config.WHITELISTED_IP:
if remote_ip in uplink.ctx.Config.WHITELISTED_IP:
return None
if uplink.ModConfig.cloudfilt_scan == 0:
if uplink.mod_config.cloudfilt_scan == 0:
return None
if uplink.cloudfilt_key == '':
return None
service_id = uplink.Config.SERVICE_ID
service_chanlog = uplink.Config.SERVICE_CHANLOG
color_red = uplink.Config.COLORS.red
nogc = uplink.Config.COLORS.nogc
url = "https://developers18334.cloudfilt.com/"
data = {'ip': remote_ip, 'key': uplink.cloudfilt_key}
with requests.Session() as sess:
response = sess.post(url=url, data=data)
data = {
'ip': remote_ip,
'key': uplink.cloudfilt_key
}
# Formatted output
decoded_response: dict = loads(response.text)
status_code = response.status_code
if status_code != 200:
uplink.ctx.Logs.warning(f'Error connecting to cloudfilt API | Code: {str(status_code)}')
return
response = requests.post(url=url, data=data)
# Formatted output
decoded_response: dict = loads(response.text)
status_code = response.status_code
if status_code != 200:
uplink.Logs.warning(f'Error connecting to cloudfilt API | Code: {str(status_code)}')
return
result = {
'countryiso': decoded_response.get('countryiso', None),
'listed': decoded_response.get('listed', False),
'listed_by': decoded_response.get('listed_by', None),
'host': decoded_response.get('host', None)
}
result = {
'countryiso': decoded_response.get('countryiso', None),
'listed': decoded_response.get('listed', None),
'listed_by': decoded_response.get('listed_by', None),
'host': decoded_response.get('host', None)
}
# pseudo!ident@host
fullname = f'{nickname}!{username}@{hostname}'
p.send_priv_msg(
nick_from=service_id,
msg=f"[ {color_red}CLOUDFILT_SCAN{nogc} ] : Connexion de {fullname} ({remote_ip}) ==> Host: {str(result['host'])} | country: {str(result['countryiso'])} | listed: {str(result['listed'])} | listed by : {str(result['listed_by'])}",
channel=service_chanlog)
uplink.Logs.debug(f"[CLOUDFILT SCAN] ({fullname}) connected from ({result['countryiso']}), Listed: {result['listed']}, by: {result['listed_by']}")
if result['listed']:
p.send2socket(f":{service_id} GLINE +*@{remote_ip} {uplink.Config.GLINE_DURATION} Your connexion is listed as dangerous {str(result['listed'])} {str(result['listed_by'])} - detected by cloudfilt")
uplink.Logs.debug(f"[CLOUDFILT SCAN GLINE] Dangerous connection ({fullname}) from ({result['countryiso']}) Listed: {result['listed']}, by: {result['listed_by']}")
response.close()
return result
return result
def action_scan_client_with_freeipapi(uplink: 'Defender', user_model: 'MUser') -> Optional[dict[str, str]]:
"""Analyse l'ip avec Freeipapi
@@ -491,62 +480,33 @@ def action_scan_client_with_freeipapi(uplink: 'Defender', user_model: 'MUser') -
dict[str, any] | None: les informations du provider
keys : 'countryCode', 'isProxy'
"""
p = uplink.Protocol
remote_ip = user_model.remote_ip
username = user_model.username
hostname = user_model.hostname
nickname = user_model.nickname
if remote_ip in uplink.Config.WHITELISTED_IP:
if remote_ip in uplink.ctx.Config.WHITELISTED_IP:
return None
if uplink.ModConfig.freeipapi_scan == 0:
if uplink.mod_config.freeipapi_scan == 0:
return None
service_id = uplink.Config.SERVICE_ID
service_chanlog = uplink.Config.SERVICE_CHANLOG
color_red = uplink.Config.COLORS.red
nogc = uplink.Config.COLORS.nogc
with requests.Session() as sess:
url = f'https://freeipapi.com/api/json/{remote_ip}'
headers = {'Accept': 'application/json'}
response = sess.request(method='GET', url=url, headers=headers, timeout=uplink.timeout)
url = f'https://freeipapi.com/api/json/{remote_ip}'
# Formatted output
decoded_response: dict = loads(response.text)
headers = {
'Accept': 'application/json',
}
status_code = response.status_code
if status_code == 429:
uplink.ctx.Logs.warning('Too Many Requests - The rate limit for the API has been exceeded.')
return None
elif status_code != 200:
uplink.ctx.Logs.warning(f'status code = {str(status_code)}')
return None
response = requests.request(method='GET', url=url, headers=headers, timeout=uplink.timeout)
# Formatted output
decoded_response: dict = loads(response.text)
status_code = response.status_code
if status_code == 429:
uplink.Logs.warning('Too Many Requests - The rate limit for the API has been exceeded.')
return None
elif status_code != 200:
uplink.Logs.warning(f'status code = {str(status_code)}')
return None
result = {
'countryCode': decoded_response.get('countryCode', None),
'isProxy': decoded_response.get('isProxy', None)
}
# pseudo!ident@host
fullname = f'{nickname}!{username}@{hostname}'
p.send_priv_msg(
nick_from=service_id,
msg=f"[ {color_red}FREEIPAPI_SCAN{nogc} ] : Connexion de {fullname} ({remote_ip}) ==> Proxy: {str(result['isProxy'])} | Country : {str(result['countryCode'])}",
channel=service_chanlog)
uplink.Logs.debug(f"[FREEIPAPI SCAN] ({fullname}) connected from ({result['countryCode']}), Proxy: {result['isProxy']}")
if result['isProxy']:
p.send2socket(f":{service_id} GLINE +*@{remote_ip} {uplink.Config.GLINE_DURATION} This server do not allow proxy connexions {str(result['isProxy'])} - detected by freeipapi")
uplink.Logs.debug(f"[FREEIPAPI SCAN GLINE] Server do not allow proxy connexions {result['isProxy']}")
response.close()
return result
result = {
'countryCode': decoded_response.get('countryCode', None),
'isProxy': decoded_response.get('isProxy', None)
}
return result
def action_scan_client_with_abuseipdb(uplink: 'Defender', user_model: 'MUser') -> Optional[dict[str, str]]:
"""Analyse l'ip avec AbuseIpDB
@@ -558,119 +518,79 @@ def action_scan_client_with_abuseipdb(uplink: 'Defender', user_model: 'MUser') -
Returns:
dict[str, str] | None: les informations du provider
"""
p = uplink.Protocol
remote_ip = user_model.remote_ip
username = user_model.username
hostname = user_model.hostname
nickname = user_model.nickname
if remote_ip in uplink.Config.WHITELISTED_IP:
if remote_ip in uplink.ctx.Config.WHITELISTED_IP:
return None
if uplink.ModConfig.abuseipdb_scan == 0:
if uplink.mod_config.abuseipdb_scan == 0:
return None
if uplink.abuseipdb_key == '':
return None
url = 'https://api.abuseipdb.com/api/v2/check'
querystring = {
'ipAddress': remote_ip,
'maxAgeInDays': '90'
}
with requests.Session() as sess:
url = 'https://api.abuseipdb.com/api/v2/check'
querystring = {'ipAddress': remote_ip, 'maxAgeInDays': '90'}
headers = {
'Accept': 'application/json',
'Key': uplink.abuseipdb_key
}
headers = {
'Accept': 'application/json',
'Key': uplink.abuseipdb_key
}
response = sess.request(method='GET', url=url, headers=headers, params=querystring, timeout=uplink.timeout)
response = requests.request(method='GET', url=url, headers=headers, params=querystring, timeout=uplink.timeout)
if response.status_code != 200:
uplink.ctx.Logs.warning(f'status code = {str(response.status_code)}')
return None
# Formatted output
decoded_response: dict[str, dict] = loads(response.text)
# Formatted output
decoded_response: dict[str, dict] = loads(response.text)
if 'data' not in decoded_response:
return None
if 'data' not in decoded_response:
return None
result = {
'score': decoded_response.get('data', {}).get('abuseConfidenceScore', 0),
'country': decoded_response.get('data', {}).get('countryCode', None),
'isTor': decoded_response.get('data', {}).get('isTor', None),
'totalReports': decoded_response.get('data', {}).get('totalReports', 0)
}
service_id = uplink.Config.SERVICE_ID
service_chanlog = uplink.Config.SERVICE_CHANLOG
color_red = uplink.Config.COLORS.red
nogc = uplink.Config.COLORS.nogc
# pseudo!ident@host
fullname = f'{nickname}!{username}@{hostname}'
p.send_priv_msg(
nick_from=service_id,
msg=f"[ {color_red}ABUSEIPDB_SCAN{nogc} ] : Connexion de {fullname} ({remote_ip}) ==> Score: {str(result['score'])} | Country : {result['country']} | Tor : {str(result['isTor'])} | Total Reports : {str(result['totalReports'])}",
channel=service_chanlog
)
uplink.Logs.debug(f"[ABUSEIPDB SCAN] ({fullname}) connected from ({result['country']}), Score: {result['score']}, Tor: {result['isTor']}")
if result['isTor']:
p.send2socket(f":{service_id} GLINE +*@{remote_ip} {uplink.Config.GLINE_DURATION} This server do not allow Tor connexions {str(result['isTor'])} - Detected by Abuseipdb")
uplink.Logs.debug(f"[ABUSEIPDB SCAN GLINE] Server do not allow Tor connections Tor: {result['isTor']}, Score: {result['score']}")
elif result['score'] >= 95:
p.send2socket(f":{service_id} GLINE +*@{remote_ip} {uplink.Config.GLINE_DURATION} You were banned from this server because your abuse score is = {str(result['score'])} - Detected by Abuseipdb")
uplink.Logs.debug(f"[ABUSEIPDB SCAN GLINE] Server do not high risk connections Country: {result['country']}, Score: {result['score']}")
response.close()
result = {
'score': decoded_response.get('data', {}).get('abuseConfidenceScore', 0),
'country': decoded_response.get('data', {}).get('countryCode', None),
'isTor': decoded_response.get('data', {}).get('isTor', None),
'totalReports': decoded_response.get('data', {}).get('totalReports', 0)
}
return result
def action_scan_client_with_local_socket(uplink: 'Defender', user_model: 'MUser'):
def action_scan_client_with_local_socket(uplink: 'Defender', user_model: 'MUser') -> Optional[dict[str, str]]:
"""local_scan
Args:
uplink (Defender): Defender instance object
user_model (MUser): l'objet User qui contient l'ip
"""
p = uplink.Protocol
remote_ip = user_model.remote_ip
username = user_model.username
hostname = user_model.hostname
nickname = user_model.nickname
fullname = f'{nickname}!{username}@{hostname}'
if remote_ip in uplink.Config.WHITELISTED_IP:
if remote_ip in uplink.ctx.Config.WHITELISTED_IP:
return None
for port in uplink.Config.PORTS_TO_SCAN:
try:
newSocket = ''
newSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM or socket.SOCK_NONBLOCK)
newSocket.settimeout(0.5)
result = {'opened_ports': [], 'closed_ports': []}
connection = (remote_ip, uplink.Base.int_if_possible(port))
newSocket.connect(connection)
for port in uplink.ctx.Config.PORTS_TO_SCAN:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM or socket.SOCK_NONBLOCK) as sock:
try:
sock.settimeout(0.5)
connection = (remote_ip, uplink.ctx.Base.int_if_possible(port))
sock.connect(connection)
p.send_priv_msg(
nick_from=uplink.Config.SERVICE_NICKNAME,
msg=f"[ {uplink.Config.COLORS.red}PROXY_SCAN{uplink.Config.COLORS.nogc} ] {fullname} ({remote_ip}) : Port [{str(port)}] ouvert sur l'adresse ip [{remote_ip}]",
channel=uplink.Config.SERVICE_CHANLOG
)
# print(f"=======> Le port {str(port)} est ouvert !!")
uplink.Base.running_sockets.append(newSocket)
# print(newSocket)
newSocket.shutdown(socket.SHUT_RDWR)
newSocket.close()
result['opened_ports'].append(port)
uplink.ctx.Base.running_sockets.append(sock)
sock.shutdown(socket.SHUT_RDWR)
uplink.ctx.Base.running_sockets.remove(sock)
return result
except (socket.timeout, ConnectionRefusedError):
uplink.Logs.info(f"Le port {remote_ip}:{str(port)} est fermé")
except AttributeError as ae:
uplink.Logs.warning(f"AttributeError ({remote_ip}): {ae}")
except socket.gaierror as err:
uplink.Logs.warning(f"Address Info Error ({remote_ip}): {err}")
finally:
# newSocket.shutdown(socket.SHUT_RDWR)
newSocket.close()
uplink.Logs.info('=======> Fermeture de la socket')
except (socket.timeout, ConnectionRefusedError):
uplink.ctx.Logs.debug(f"[LOCAL SCAN] Port {remote_ip}:{str(port)} is close.")
result['closed_ports'].append(port)
except AttributeError as ae:
uplink.ctx.Logs.warning(f"AttributeError ({remote_ip}): {ae}")
except socket.gaierror as err:
uplink.ctx.Logs.warning(f"Address Info Error ({remote_ip}): {err}")
return result
def action_scan_client_with_psutil(uplink: 'Defender', user_model: 'MUser') -> list[int]:
"""psutil_scan for Linux (should be run on the same location as the unrealircd server)
@@ -681,30 +601,18 @@ def action_scan_client_with_psutil(uplink: 'Defender', user_model: 'MUser') -> l
Returns:
list[int]: list of ports
"""
p = uplink.Protocol
remote_ip = user_model.remote_ip
username = user_model.username
hostname = user_model.hostname
nickname = user_model.nickname
if remote_ip in uplink.Config.WHITELISTED_IP:
if remote_ip in uplink.ctx.Config.WHITELISTED_IP:
return None
if uplink.mod_config.psutil_scan == 0:
return None
try:
connections = psutil.net_connections(kind='inet')
fullname = f'{nickname}!{username}@{hostname}'
matching_ports = [conn.raddr.port for conn in connections if conn.raddr and conn.raddr.ip == remote_ip]
uplink.Logs.info(f"Connexion of {fullname} ({remote_ip}) using ports : {str(matching_ports)}")
if matching_ports:
p.send_priv_msg(
nick_from=uplink.Config.SERVICE_NICKNAME,
msg=f"[ {uplink.Config.COLORS.red}PSUTIL_SCAN{uplink.Config.COLORS.black} ] {fullname} ({remote_ip}) : is using ports {matching_ports}",
channel=uplink.Config.SERVICE_CHANLOG
)
uplink.ctx.Logs.debug(f"Connexion of ({remote_ip}) using ports : {str(matching_ports)}")
return matching_ports
except psutil.AccessDenied as ad:
uplink.Logs.critical(f'psutil_scan: Permission error: {ad}')
uplink.ctx.Logs.critical(f'psutil_scan: Permission error: {ad}')

View File

@@ -1,180 +1,63 @@
import logging
import asyncio
from typing import TYPE_CHECKING, Any, Optional
from unrealircd_rpc_py.objects.Definition import LiveRPCResult
from core.classes.interfaces.imodule import IModule
import mods.jsonrpc.schemas as schemas
import mods.jsonrpc.utils as utils
import mods.jsonrpc.threads as thds
from time import sleep
from types import SimpleNamespace
from typing import TYPE_CHECKING
from dataclasses import dataclass
from unrealircd_rpc_py.Live import LiveWebsocket, LiveUnixSocket
from unrealircd_rpc_py.Loader import Loader
from unrealircd_rpc_py.ConnectionFactory import ConnectionFactory
from unrealircd_rpc_py.LiveConnectionFactory import LiveConnectionFactory
if TYPE_CHECKING:
from core.irc import Irc
from core.loader import Loader
class Jsonrpc():
class Jsonrpc(IModule):
@dataclass
class ModConfModel:
class ModConfModel(schemas.ModConfModel):
"""The Model containing the module parameters
"""
jsonrpc: int = 0
...
def __init__(self, ircInstance: 'Irc') -> None:
MOD_HEADER: dict[str, str] = {
'name':'JsonRPC',
'version':'1.0.0',
'description':'Module using the unrealircd-rpc-py library',
'author':'Defender Team',
'core_version':'Defender-6'
}
# Module name (Mandatory)
self.module_name = 'mod_' + str(self.__class__.__name__).lower()
def __init__(self, context: 'Loader') -> None:
super().__init__(context)
self._mod_config: Optional[schemas.ModConfModel] = None
# Add Irc Object to the module (Mandatory)
self.Irc = ircInstance
@property
def mod_config(self) -> ModConfModel:
return self._mod_config
# Add Protocol to the module (Mandatory)
self.Protocol = ircInstance.Protocol
async def callback_sent_to_irc(self, response: LiveRPCResult) -> None:
# Add Global Configuration to the module (Mandatory)
self.Config = ircInstance.Config
dnickname = self.ctx.Config.SERVICE_NICKNAME
dchanlog = self.ctx.Config.SERVICE_CHANLOG
green = self.ctx.Config.COLORS.green
nogc = self.ctx.Config.COLORS.nogc
bold = self.ctx.Config.COLORS.bold
red = self.ctx.Config.COLORS.red
# Add Base object to the module (Mandatory)
self.Base = ircInstance.Base
# Add Main Utils (Mandatory)
self.MainUtils = ircInstance.Utils
# Add logs object to the module (Mandatory)
self.Logs = ircInstance.Loader.Logs
# Add User object to the module (Mandatory)
self.User = ircInstance.User
# Add Channel object to the module (Mandatory)
self.Channel = ircInstance.Channel
# Is RPC Active?
self.is_streaming = False
# Module Utils
self.Utils = utils
# Module threads
self.Threads = thds
# Run Garbage collector.
self.Base.create_timer(10, self.MainUtils.run_python_garbage_collector)
# Create module commands (Mandatory)
self.Irc.build_command(1, self.module_name, 'jsonrpc', 'Activate the JSON RPC Live connection [ON|OFF]')
self.Irc.build_command(1, self.module_name, 'jruser', 'Get Information about a user using JSON RPC')
self.Irc.build_command(1, self.module_name, 'jrinstances', 'Get number of instances')
# Init the module
self.__init_module()
# Log the module
self.Logs.debug(f'Module {self.module_name} loaded ...')
def __init_module(self) -> None:
logging.getLogger('websockets').setLevel(logging.WARNING)
logging.getLogger('unrealircd-rpc-py').setLevel(logging.CRITICAL)
logging.getLogger('unrealircd-liverpc-py').setLevel(logging.CRITICAL)
# Create you own tables (Mandatory)
# self.__create_tables()
# Load module configuration and sync with core one (Mandatory)
self.__load_module_configuration()
# End of mandatory methods you can start your customization #
self.UnrealIrcdRpcLive: LiveWebsocket = LiveWebsocket(
url=self.Config.JSONRPC_URL,
username=self.Config.JSONRPC_USER,
password=self.Config.JSONRPC_PASSWORD,
callback_object_instance=self,
callback_method_or_function_name='callback_sent_to_irc'
)
if self.UnrealIrcdRpcLive.get_error.code != 0:
self.Logs.error(f"{self.UnrealIrcdRpcLive.get_error.message} ({self.UnrealIrcdRpcLive.get_error.code})")
self.Protocol.send_priv_msg(
nick_from=self.Config.SERVICE_NICKNAME,
msg=f"[{self.Config.COLORS.red}ERROR{self.Config.COLORS.nogc}] {self.UnrealIrcdRpcLive.get_error.message}",
channel=self.Config.SERVICE_CHANLOG
)
raise Exception(f"[LIVE-JSONRPC ERROR] {self.UnrealIrcdRpcLive.get_error.message}")
self.Rpc: Loader = Loader(
req_method=self.Config.JSONRPC_METHOD,
url=self.Config.JSONRPC_URL,
username=self.Config.JSONRPC_USER,
password=self.Config.JSONRPC_PASSWORD
)
if self.Rpc.get_error.code != 0:
self.Logs.error(f"{self.Rpc.get_error.message} ({self.Rpc.get_error.code})")
self.Protocol.send_priv_msg(
nick_from=self.Config.SERVICE_NICKNAME,
msg=f"[{self.Config.COLORS.red}JSONRPC ERROR{self.Config.COLORS.nogc}] {self.Rpc.get_error.message}",
channel=self.Config.SERVICE_CHANLOG
)
raise Exception(f"[JSONRPC ERROR] {self.Rpc.get_error.message}")
if self.ModConfig.jsonrpc == 1:
self.Base.create_thread(func=self.Threads.thread_subscribe, func_args=(self, ), run_once=True)
return None
def __create_tables(self) -> None:
"""Methode qui va créer la base de donnée si elle n'existe pas.
Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module
Args:
database_name (str): Nom de la base de données ( pas d'espace dans le nom )
Returns:
None: Aucun retour n'es attendu
"""
table_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 callback_sent_to_irc(self, response: SimpleNamespace) -> None:
dnickname = self.Config.SERVICE_NICKNAME
dchanlog = self.Config.SERVICE_CHANLOG
green = self.Config.COLORS.green
nogc = self.Config.COLORS.nogc
bold = self.Config.COLORS.bold
red = self.Config.COLORS.red
if self.UnrealIrcdRpcLive.get_error.code != 0:
self.Protocol.send_priv_msg(nick_from=dnickname,
msg=f"[{bold}{red}JSONRPC ERROR{nogc}{bold}] {self.UnrealIrcdRpcLive.get_error.message}",
if response.error.code != 0:
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname,
msg=f"[{bold}{red}JSONRPC ERROR{nogc}{bold}] {response.error.message} ({response.error.code})",
channel=dchanlog)
return None
if hasattr(response, 'error'):
if response.error.code != 0:
self.Protocol.send_priv_msg(
nick_from=self.Config.SERVICE_NICKNAME,
msg=f"[{bold}{red}JSONRPC{nogc}{bold}] JSONRPC Event activated on {self.Config.JSONRPC_URL}",
if isinstance(response.result, bool):
if response.result:
await self.ctx.Irc.Protocol.send_priv_msg(
nick_from=self.ctx.Config.SERVICE_NICKNAME,
msg=f"[{bold}{green}JSONRPC{nogc}{bold}] JSONRPC Event activated on {self.ctx.Config.JSONRPC_URL}",
channel=dchanlog)
return None
if hasattr(response, 'result'):
if isinstance(response.result, bool):
if response.result:
self.Protocol.send_priv_msg(
nick_from=self.Config.SERVICE_NICKNAME,
msg=f"[{bold}{green}JSONRPC{nogc}{bold}] JSONRPC Event activated on {self.Config.JSONRPC_URL}",
channel=dchanlog)
return None
return None
level = response.result.level if hasattr(response.result, 'level') else ''
subsystem = response.result.subsystem if hasattr(response.result, 'subsystem') else ''
@@ -183,56 +66,91 @@ class Jsonrpc():
msg = response.result.msg if hasattr(response.result, 'msg') else ''
build_msg = f"{green}{log_source}{nogc}: [{bold}{level}{bold}] {subsystem}.{event_id} - {msg}"
self.Protocol.send_priv_msg(nick_from=dnickname, msg=build_msg, channel=dchanlog)
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=build_msg, channel=dchanlog)
return None
def __load_module_configuration(self) -> None:
"""### Load Module Configuration
"""
try:
# Build the default configuration model (Mandatory)
self.ModConfig = self.ModConfModel(jsonrpc=0)
def create_tables(self) -> None:
return None
# Sync the configuration with core configuration (Mandatory)
self.Base.db_sync_core_config(self.module_name, self.ModConfig)
async def load(self) -> None:
logging.getLogger('websockets').setLevel(logging.WARNING)
logging.getLogger('unrealircd-rpc-py').setLevel(logging.CRITICAL)
logging.getLogger('unrealircd-liverpc-py').setLevel(logging.CRITICAL)
self._mod_config = self.ModConfModel(jsonrpc=0)
await self.sync_db()
if self.ctx.Config.SERVEUR_PROTOCOL.lower() != 'unreal6':
await self.ctx.ModuleUtils.unload_one_module(self.module_name, False)
return None
except TypeError as te:
self.Logs.critical(te)
# Is RPC Active?
self.is_streaming = False
def update_configuration(self, param_key: str, param_value: str) -> None:
"""Update the local and core configuration
# Create module commands (Mandatory)
self.ctx.Commands.build_command(1, self.module_name, 'jsonrpc', 'Activate the JSON RPC Live connection [ON|OFF]')
self.ctx.Commands.build_command(1, self.module_name, 'jruser', 'Get Information about a user using JSON RPC')
self.ctx.Commands.build_command(1, self.module_name, 'jrinstances', 'Get number of instances')
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)
try:
self.Rpc = ConnectionFactory(self.ctx.Config.DEBUG_LEVEL).get(self.ctx.Config.JSONRPC_METHOD)
self.LiveRpc = LiveConnectionFactory(self.ctx.Config.DEBUG_LEVEL).get(self.ctx.Config.JSONRPC_METHOD)
sync_unixsocket = {'path_to_socket_file': self.ctx.Config.JSONRPC_PATH_TO_SOCKET_FILE}
sync_http = {'url': self.ctx.Config.JSONRPC_URL, 'username': self.ctx.Config.JSONRPC_USER, 'password': self.ctx.Config.JSONRPC_PASSWORD}
live_unixsocket = {'path_to_socket_file': self.ctx.Config.JSONRPC_PATH_TO_SOCKET_FILE,
'callback_object_instance' : self, 'callback_method_or_function_name': 'callback_sent_to_irc'}
live_http = {'url': self.ctx.Config.JSONRPC_URL, 'username': self.ctx.Config.JSONRPC_USER, 'password': self.ctx.Config.JSONRPC_PASSWORD,
'callback_object_instance' : self, 'callback_method_or_function_name': 'callback_sent_to_irc'}
def unload(self) -> None:
if self.is_streaming:
self.Protocol.send_priv_msg(
nick_from=self.Config.SERVICE_NICKNAME,
msg=f"[{self.Config.COLORS.green}JSONRPC INFO{self.Config.COLORS.nogc}] Shutting down RPC system!",
channel=self.Config.SERVICE_CHANLOG
sync_param = sync_unixsocket if self.ctx.Config.JSONRPC_METHOD == 'unixsocket' else sync_http
live_param = live_unixsocket if self.ctx.Config.JSONRPC_METHOD == 'unixsocket' else live_http
self.Rpc.setup(sync_param)
self.LiveRpc.setup(live_param)
if self.mod_config.jsonrpc == 1:
self.ctx.Base.create_asynctask(thds.thread_subscribe(self))
return None
except Exception as err:
await self.ctx.Irc.Protocol.send_priv_msg(
nick_from=self.ctx.Config.SERVICE_NICKNAME,
msg=f"[{self.ctx.Config.COLORS.red}JSONRPC ERROR{self.ctx.Config.COLORS.nogc}] {err.__str__()}",
channel=self.ctx.Config.SERVICE_CHANLOG
)
self.Base.create_thread(func=self.Threads.thread_unsubscribe, func_args=(self, ), run_once=True)
self.update_configuration('jsonrpc', 0)
self.Irc.Commands.drop_command_by_module(self.module_name)
self.Logs.debug(f"Unloading {self.module_name}")
self.ctx.Logs.error(f"JSONRPC ERROR: {err.__str__()}")
async def unload(self) -> None:
if self.ctx.Config.SERVEUR_PROTOCOL != 'unreal6':
await self.ctx.ModuleUtils.unload_one_module(self.ctx.Irc, self.module_name, False)
return None
if self.is_streaming:
await self.ctx.Irc.Protocol.send_priv_msg(
nick_from=self.ctx.Config.SERVICE_NICKNAME,
msg=f"[{self.ctx.Config.COLORS.green}JSONRPC INFO{self.ctx.Config.COLORS.nogc}] Shutting down RPC system!",
channel=self.ctx.Config.SERVICE_CHANLOG
)
self.ctx.Base.create_asynctask(thds.thread_unsubscribe(self))
self.ctx.Commands.drop_command_by_module(self.module_name)
self.ctx.Logs.debug(f"Unloading {self.module_name}")
return None
def cmd(self, data: list) -> None:
def cmd(self, data: list[str]) -> None:
return None
def hcmds(self, user:str, channel: any, cmd: list, fullcmd: list = []) -> None:
async def hcmds(self, user: str, channel: Any, cmd: list[str], fullcmd: list[str] = []) -> None:
command = str(cmd[0]).lower()
dnickname = self.Config.SERVICE_NICKNAME
dchannel = self.Config.SERVICE_CHANLOG
dnickname = self.ctx.Config.SERVICE_NICKNAME
dchannel = self.ctx.Config.SERVICE_CHANLOG
fromuser = user
fromchannel = str(channel) if not channel is None else None
@@ -241,89 +159,74 @@ class Jsonrpc():
case 'jsonrpc':
try:
if len(cmd) < 2:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'/msg {dnickname} jsonrpc on')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'/msg {dnickname} jsonrpc off')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'/msg {dnickname} jsonrpc on')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'/msg {dnickname} jsonrpc off')
return None
option = str(cmd[1]).lower()
match option:
case 'on':
thread_name = 'thread_subscribe'
if self.Base.is_thread_alive(thread_name):
self.Protocol.send_priv_msg(nick_from=dnickname, channel=dchannel, msg=f"The Subscription is running")
return None
elif self.Base.is_thread_exist(thread_name):
self.Protocol.send_priv_msg(
nick_from=dnickname, channel=dchannel,
msg=f"The subscription is not running, wait untill the process will be cleaned up"
)
return None
self.Base.create_thread(func=self.Threads.thread_subscribe, func_args=(self, ), run_once=True)
self.update_configuration('jsonrpc', 1)
self.ctx.Base.create_asynctask(thds.thread_subscribe(self))
await self.update_configuration('jsonrpc', 1)
case 'off':
self.Base.create_thread(func=self.Threads.thread_unsubscribe, func_args=(self, ), run_once=True)
self.update_configuration('jsonrpc', 0)
self.ctx.Base.create_asynctask(thds.thread_unsubscribe(self))
await self.update_configuration('jsonrpc', 0)
except IndexError as ie:
self.Logs.error(ie)
self.ctx.Logs.error(ie)
case 'jruser':
try:
if len(cmd) < 2:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'/msg {dnickname} jruser get nickname')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'/msg {dnickname} jruser get nickname')
return None
option = str(cmd[1]).lower()
match option:
case 'get':
nickname = str(cmd[2])
uid_to_get = self.User.get_uid(nickname)
if uid_to_get is None:
return None
rpc = self.Rpc
UserInfo = rpc.User.get(uid_to_get)
if rpc.get_error.code != 0:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'{rpc.get_error.message}')
UserInfo = rpc.User.get(nickname)
if UserInfo.error.code != 0:
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'{UserInfo.error.message}')
return None
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'UID : {UserInfo.id}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'NICKNAME : {UserInfo.name}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'USERNAME : {UserInfo.user.username}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'REALNAME : {UserInfo.user.realname}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'MODES : {UserInfo.user.modes}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'CHANNELS : {[chan.name for chan in UserInfo.user.channels]}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'SECURITY GROUP : {UserInfo.user.security_groups}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'REPUTATION : {UserInfo.user.reputation}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'UID : {UserInfo.id}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'NICKNAME : {UserInfo.name}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'USERNAME : {UserInfo.user.username}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'REALNAME : {UserInfo.user.realname}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'MODES : {UserInfo.user.modes}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'CHANNELS : {[chan.name for chan in UserInfo.user.channels]}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'SECURITY GROUP : {UserInfo.user.security_groups}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'REPUTATION : {UserInfo.user.reputation}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'IP : {UserInfo.ip}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'COUNTRY CODE : {UserInfo.geoip.country_code}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'ASN : {UserInfo.geoip.asn}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'ASNAME : {UserInfo.geoip.asname}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'CLOAKED HOST : {UserInfo.user.cloakedhost}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'HOSTNAME : {UserInfo.hostname}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'VHOST : {UserInfo.user.vhost}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'CLIENT PORT : {UserInfo.client_port}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'SERVER PORT : {UserInfo.server_port}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'IP : {UserInfo.ip}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'COUNTRY CODE : {UserInfo.geoip.country_code}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'ASN : {UserInfo.geoip.asn}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'ASNAME : {UserInfo.geoip.asname}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'CLOAKED HOST : {UserInfo.user.cloakedhost}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'HOSTNAME : {UserInfo.hostname}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'VHOST : {UserInfo.user.vhost}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'CLIENT PORT : {UserInfo.client_port}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'SERVER PORT : {UserInfo.server_port}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'CERTFP : {UserInfo.tls.certfp}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'CIPHER : {UserInfo.tls.cipher}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'CERTFP : {UserInfo.tls.certfp}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'CIPHER : {UserInfo.tls.cipher}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'IDLE SINCE : {UserInfo.idle_since}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'CONNECTED SINCE : {UserInfo.connected_since}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'IDLE SINCE : {UserInfo.idle_since}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'CONNECTED SINCE : {UserInfo.connected_since}')
except IndexError as ie:
self.Logs.error(ie)
self.ctx.Logs.error(ie)
case 'jrinstances':
try:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"GC Collect: {self.MainUtils.run_python_garbage_collector()}")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance LiveWebsock: {self.MainUtils.get_number_gc_objects(LiveWebsocket)}")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance LiveUnixSocket: {self.MainUtils.get_number_gc_objects(LiveUnixSocket)}")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance Loader: {self.MainUtils.get_number_gc_objects(Loader)}")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre de toute les instances: {self.MainUtils.get_number_gc_objects()}")
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"GC Collect: {self.ctx.Utils.run_python_garbage_collector()}")
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance LiveWebsock: {self.ctx.Utils.get_number_gc_objects(LiveConnectionFactory)}")
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance ConnectionFactory: {self.ctx.Utils.get_number_gc_objects(ConnectionFactory)}")
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre de toute les instances: {self.ctx.Utils.get_number_gc_objects()}")
except Exception as err:
self.Logs.error(f"Unknown Error: {err}")
self.ctx.Logs.error(f"Unknown Error: {err}")

5
mods/jsonrpc/schemas.py Normal file
View File

@@ -0,0 +1,5 @@
from core.definition import MainModel, dataclass
@dataclass
class ModConfModel(MainModel):
jsonrpc: int = 0

View File

@@ -1,60 +1,67 @@
import asyncio
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from mods.jsonrpc.mod_jsonrpc import Jsonrpc
def thread_subscribe(uplink: 'Jsonrpc') -> None:
response: dict[str, dict] = {}
snickname = uplink.Config.SERVICE_NICKNAME
schannel = uplink.Config.SERVICE_CHANLOG
async def thread_subscribe(uplink: 'Jsonrpc') -> None:
if uplink.UnrealIrcdRpcLive.get_error.code == 0:
uplink.is_streaming = True
response = asyncio.run(uplink.UnrealIrcdRpcLive.subscribe(["all"]))
else:
uplink.Protocol.send_priv_msg(nick_from=snickname,
msg=f"[{uplink.Config.COLORS.red}JSONRPC ERROR{uplink.Config.COLORS.nogc}] {uplink.UnrealIrcdRpcLive.get_error.message}",
snickname = uplink.ctx.Config.SERVICE_NICKNAME
schannel = uplink.ctx.Config.SERVICE_CHANLOG
if uplink.is_streaming:
await uplink.ctx.Irc.Protocol.send_priv_msg(nick_from=snickname,
msg=f"[{uplink.ctx.Config.COLORS.green}JSONRPC INFO{uplink.ctx.Config.COLORS.nogc}] IRCd Json-rpc already connected!",
channel=schannel
)
if response is None:
return
code = response.get('error', {}).get('code', 0)
message = response.get('error', {}).get('message', None)
if code == 0:
uplink.Protocol.send_priv_msg(
nick_from=snickname,
msg=f"[{uplink.Config.COLORS.green}JSONRPC{uplink.Config.COLORS.nogc}] Stream is OFF",
channel=schannel
)
else:
uplink.Protocol.send_priv_msg(
nick_from=snickname,
msg=f"[{uplink.Config.COLORS.red}JSONRPC{uplink.Config.COLORS.nogc}] Stream has crashed! {code} - {message}",
channel=schannel
)
def thread_unsubscribe(uplink: 'Jsonrpc') -> None:
response: dict[str, dict] = asyncio.run(uplink.UnrealIrcdRpcLive.unsubscribe())
uplink.Logs.debug("[JSONRPC UNLOAD] Unsubscribe from the stream!")
uplink.is_streaming = False
uplink.update_configuration('jsonrpc', 0)
snickname = uplink.Config.SERVICE_NICKNAME
schannel = uplink.Config.SERVICE_CHANLOG
if response is None:
return None
code = response.get('error', {}).get('code', 0)
message = response.get('error', {}).get('message', None)
uplink.is_streaming = True
response = await uplink.LiveRpc.subscribe(["all"])
if code != 0:
uplink.Protocol.send_priv_msg(
nick_from=snickname,
msg=f"[{uplink.Config.COLORS.red}JSONRPC ERROR{uplink.Config.COLORS.nogc}] {message} ({code})",
if response.error.code != 0:
await uplink.ctx.Irc.Protocol.send_priv_msg(nick_from=snickname,
msg=f"[{uplink.ctx.Config.COLORS.red}JSONRPC ERROR{uplink.ctx.Config.COLORS.nogc}] {response.error.message}",
channel=schannel
)
code = response.error.code
message = response.error.message
if code == 0:
await uplink.ctx.Irc.Protocol.send_priv_msg(
nick_from=snickname,
msg=f"[{uplink.ctx.Config.COLORS.green}JSONRPC{uplink.ctx.Config.COLORS.nogc}] Stream is OFF",
channel=schannel
)
uplink.is_streaming = False
else:
await uplink.ctx.Irc.Protocol.send_priv_msg(
nick_from=snickname,
msg=f"[{uplink.ctx.Config.COLORS.red}JSONRPC{uplink.ctx.Config.COLORS.nogc}] Stream has crashed! {code} - {message}",
channel=schannel
)
uplink.is_streaming = False
async def thread_unsubscribe(uplink: 'Jsonrpc') -> None:
snickname = uplink.ctx.Config.SERVICE_NICKNAME
schannel = uplink.ctx.Config.SERVICE_CHANLOG
if not uplink.is_streaming:
await uplink.ctx.Irc.Protocol.send_priv_msg(nick_from=snickname,
msg=f"[{uplink.ctx.Config.COLORS.green}JSONRPC INFO{uplink.ctx.Config.COLORS.nogc}] IRCd Json-rpc is already off!",
channel=schannel
)
return None
response = await uplink.LiveRpc.unsubscribe()
uplink.ctx.Logs.debug("[JSONRPC UNLOAD] Unsubscribe from the stream!")
uplink.is_streaming = False
code = response.error.code
message = response.error.message
if code != 0:
await uplink.ctx.Irc.Protocol.send_priv_msg(
nick_from=snickname,
msg=f"[{uplink.ctx.Config.COLORS.red}JSONRPC ERROR{uplink.ctx.Config.COLORS.nogc}] {message} ({code})",
channel=schannel
)

View File

@@ -1,79 +1,38 @@
from typing import TYPE_CHECKING
from dataclasses import dataclass, fields
import asyncio
from typing import Any, TYPE_CHECKING, Optional
from core.classes.interfaces.imodule import IModule
from dataclasses import dataclass
if TYPE_CHECKING:
from core.irc import Irc
from core.loader import Loader
class Test():
class Test(IModule):
@dataclass
class ModConfModel:
"""The Model containing the module parameters
"""The Model containing the module parameters (Mandatory)
you can leave it without params.
just use pass | if you leave it empty, in the load() method just init empty object ==> self.ModConfig = ModConfModel()
"""
param_exemple1: str
param_exemple2: int
def __init__(self, ircInstance: 'Irc') -> None:
MOD_HEADER: dict[str, str] = {
'name':'Test',
'version':'1.0.0',
'description':'The test module',
'author':'Defender Team',
'core_version':'Defender-6'
}
"""Module Header (Mandatory)"""
# Module name (Mandatory)
self.module_name = 'mod_' + str(self.__class__.__name__).lower()
def __init__(self, uplink: 'Loader'):
super().__init__(uplink)
self._mod_config: Optional[Test.ModConfModel] = None
# Add Irc Object to the module (Mandatory)
self.Irc = ircInstance
# Add Loader Object to the module (Mandatory)
self.Loader = ircInstance.Loader
# Add server protocol Object to the module (Mandatory)
self.Protocol = ircInstance.Protocol
# Add Global Configuration to the module (Mandatory)
self.Config = ircInstance.Config
# Add Base object to the module (Mandatory)
self.Base = ircInstance.Base
# Add logs object to the module (Mandatory)
self.Logs = ircInstance.Loader.Logs
# Add User object to the module (Mandatory)
self.User = ircInstance.User
# Add Channel object to the module (Mandatory)
self.Channel = ircInstance.Channel
# Add Reputation object to the module (Optional)
self.Reputation = ircInstance.Reputation
# Create module commands (Mandatory)
self.Irc.build_command(0, self.module_name, 'test-command', 'Execute a test command')
self.Irc.build_command(1, self.module_name, 'test_level_1', 'Execute a level 1 test command')
self.Irc.build_command(2, self.module_name, 'test_level_2', 'Execute a level 2 test command')
self.Irc.build_command(3, self.module_name, 'test_level_3', 'Execute a level 3 test command')
# Init the module
self.__init_module()
# Log the module
self.Logs.debug(f'Module {self.module_name} loaded ...')
def __init_module(self) -> None:
# Create you own tables (Mandatory)
self.__create_tables()
# Load module configuration and sync with core one (Mandatory)
self.__load_module_configuration()
# End of mandatory methods you can start your customization #
return None
def __create_tables(self) -> None:
def create_tables(self) -> None:
"""Methode qui va créer la base de donnée si elle n'existe pas.
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
@@ -86,72 +45,97 @@ class Test():
)
'''
self.Base.db_execute_query(table_logs)
# self.ctx.Base.db_execute_query(table_logs)
return None
def __load_module_configuration(self) -> None:
"""### Load Module Configuration
async def load(self) -> None:
"""### Load Module Configuration (Mandatory)
"""
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)
# Create module commands (Mandatory)
self.ctx.Commands.build_command(0, self.module_name, 'test-command', 'Execute a test command')
self.ctx.Commands.build_command(0, self.module_name, 'asyncio', 'Create a new asynchron task!')
self.ctx.Commands.build_command(1, self.module_name, 'test_level_1', 'Execute a level 1 test command')
self.ctx.Commands.build_command(2, self.module_name, 'test_level_2', 'Execute a level 2 test command')
self.ctx.Commands.build_command(3, self.module_name, 'test_level_3', 'Execute a level 3 test command')
return None
# Build the default configuration model (Mandatory)
self._mod_config = self.ModConfModel(param_exemple1='str', param_exemple2=1)
except TypeError as te:
self.Logs.critical(te)
# sync the database with local variable (Mandatory)
await self.sync_db()
def __update_configuration(self, param_key: str, param_value: str):
"""Update the local and core configuration
if self.mod_config.param_exemple2 == 1:
await self.ctx.Irc.Protocol.send_priv_msg(self.ctx.Config.SERVICE_NICKNAME, "Param activated", self.ctx.Config.SERVICE_CHANLOG)
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)
@property
def mod_config(self) -> ModConfModel:
return self._mod_config
def unload(self) -> None:
self.Irc.Commands.drop_command_by_module(self.module_name)
"""### This method is called when you unload, or you reload the module (Mandatory)"""
self.ctx.Commands.drop_command_by_module(self.module_name)
return None
def cmd(self, data:list) -> None:
def cmd(self, data: list[str]) -> None:
"""All messages coming from the IRCD server will be handled using this method (Mandatory)
Args:
data (list): Messages coming from the IRCD server.
"""
cmd = list(data).copy()
try:
cmd = list(data).copy()
return None
except KeyError as ke:
self.Logs.error(f"Key Error: {ke}")
except IndexError as ie:
self.Logs.error(f"{ie} / {cmd} / length {str(len(cmd))}")
except Exception as err:
self.Logs.error(f"General Error: {err}")
self.ctx.Logs.error(f"General Error: {err}")
def hcmds(self, user:str, channel: any, cmd: list, fullcmd: list = []) -> None:
async def asyncio_func(self) -> None:
self.ctx.Logs.debug(f"Starting async method in a task: {self.__class__.__name__}")
await asyncio.sleep(2)
self.ctx.Logs.debug(f"End of the task: {self.__class__.__name__}")
async def hcmds(self, user: str, channel: Any, cmd: list, fullcmd: Optional[list] = None) -> None:
"""All messages coming from the user commands (Mandatory)
Args:
user (str): The user who send the request.
channel (Any): The channel from where is coming the message (could be None).
cmd (list): The messages coming from the IRCD server.
fullcmd (list, optional): The full messages coming from the IRCD server. Defaults to [].
"""
u = self.ctx.User.get_user(user)
c = self.ctx.Channel.get_channel(channel) if self.ctx.Channel.is_valid_channel(channel) else None
if u is None:
return None
command = str(cmd[0]).lower()
dnickname = self.Config.SERVICE_NICKNAME
fromuser = user
fromchannel = str(channel) if not channel is None else None
dnickname = self.ctx.Config.SERVICE_NICKNAME
match command:
case 'asyncio':
self.ctx.Base.create_asynctask(self.asyncio_func())
return None
case 'test-command':
try:
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=u.nickname, msg="This is a notice to the sender ...")
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"This is private message to the sender ...", nick_to=u.nickname)
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="This is a notice to the sender ...")
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"This is private message to the sender ...", nick_to=fromuser)
if not fromchannel is None:
self.Protocol.send_priv_msg(nick_from=dnickname, msg=f"This is private message to the sender ...", channel=fromchannel)
if c is not None:
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"This is private message to the sender ...", channel=c.name)
# How to update your module configuration
self.__update_configuration('param_exemple2', 7)
self.update_configuration('param_exemple2', 7)
self.update_configuration('param_exemple1', 'my_value')
# Log if you want the result
self.Logs.debug(f"Test logs ready")
self.ctx.Logs.debug(f"Test logs ready")
return None
except Exception as err:
self.Logs.error(f"Unknown Error: {err}")
self.ctx.Logs.error(f"Unknown Error: {err}")
return None
case _:
return None

View File

@@ -1,95 +1,49 @@
"""
File : mod_votekick.py
Version : 1.0.0
Version : 1.0.2
Description : Manages votekick sessions for multiple channels.
Handles activation, ongoing vote checks, and cleanup.
Author : adator
Created : 2025-08-16
Last Updated: 2025-08-16
Last Updated: 2025-11-01
-----------------------------------------
"""
from dataclasses import dataclass
import re
from core.classes.interfaces.imodule import IModule
import mods.votekick.schemas as schemas
import mods.votekick.utils as utils
from mods.votekick.votekick_manager import VotekickManager
import mods.votekick.threads as thds
from typing import TYPE_CHECKING, Any, Optional
if TYPE_CHECKING:
from core.irc import Irc
from core.loader import Loader
class Votekick(IModule):
class Votekick:
@dataclass
class ModConfModel(schemas.VoteChannelModel):
...
def __init__(self, uplink: 'Irc') -> None:
MOD_HEADER: dict[str, str] = {
'name':'votekick',
'version':'1.0.2',
'description':'Channel Democraty',
'author':'Defender Team',
'core_version':'Defender-6'
}
# Module name (Mandatory)
self.module_name = 'mod_' + str(self.__class__.__name__).lower()
def __init__(self, context: 'Loader') -> None:
super().__init__(context)
self._mod_config: Optional[schemas.VoteChannelModel] = None
# Add Irc Object to the module
self.Irc = uplink
@property
def mod_config(self) -> ModConfModel:
return self._mod_config
# Add Loader Object to the module (Mandatory)
self.Loader = uplink.Loader
# Add server protocol Object to the module (Mandatory)
self.Protocol = uplink.Protocol
# Add Global Configuration to the module
self.Config = uplink.Config
# Add Base object to the module
self.Base = uplink.Base
# Add logs object to the module
self.Logs = uplink.Logs
# Add User object to the module
self.User = uplink.User
# Add Channel object to the module
self.Channel = uplink.Channel
# Add Utils.
self.Utils = uplink.Utils
# Add Utils module
self.ModUtils = utils
# Add Schemas module
self.Schemas = schemas
# Add Threads module
self.Threads = thds
# Add VoteKick Manager
self.VoteKickManager = VotekickManager(self)
metadata = uplink.Loader.Settings.get_cache('VOTEKICK')
if metadata is not None:
self.VoteKickManager.VOTE_CHANNEL_DB = metadata
# self.VOTE_CHANNEL_DB = metadata
# Créer les nouvelles commandes du module
self.Irc.build_command(1, self.module_name, 'vote', 'The kick vote module')
# Init the module
self.__init_module()
# Log the module
self.Logs.debug(f'-- Module {self.module_name} loaded ...')
def __init_module(self) -> None:
# Add admin object to retrieve admin users
self.Admin = self.Irc.Admin
self.__create_tables()
self.ModUtils.join_saved_channels(self)
return None
def __create_tables(self) -> None:
def create_tables(self) -> None:
"""Methode qui va créer la base de donnée si elle n'existe pas.
Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module
@@ -111,30 +65,52 @@ class Votekick:
)
'''
self.Base.db_execute_query(table_logs)
self.Base.db_execute_query(table_vote)
self.ctx.Base.db_execute_query(table_logs)
self.ctx.Base.db_execute_query(table_vote)
return None
def unload(self) -> None:
async def load(self) -> None:
self._mod_config = self.ModConfModel()
await self.sync_db()
# Add VoteKick Manager
self.VoteKickManager = VotekickManager(self)
# Add Threads module
self.Threads = thds
await utils.join_saved_channels(self)
metadata = self.ctx.Settings.get_cache('VOTEKICK')
if metadata is not None:
self.VoteKickManager.VOTE_CHANNEL_DB = metadata
# Créer les nouvelles commandes du module
self.ctx.Commands.build_command(1, self.module_name, 'vote', 'The kick vote module')
async def unload(self) -> None:
try:
# Cache the local DB with current votes.
self.Loader.Settings.set_cache('VOTEKICK', self.VoteKickManager.VOTE_CHANNEL_DB)
if self.VoteKickManager.VOTE_CHANNEL_DB:
self.ctx.Settings.set_cache('VOTEKICK', self.VoteKickManager.VOTE_CHANNEL_DB)
for chan in self.VoteKickManager.VOTE_CHANNEL_DB:
self.Protocol.send_part_chan(uidornickname=self.Config.SERVICE_ID, channel=chan.channel_name)
await self.ctx.Irc.Protocol.send_part_chan(uidornickname=self.ctx.Config.SERVICE_ID, channel=chan.channel_name)
self.VoteKickManager.VOTE_CHANNEL_DB = []
self.Logs.debug(f'Delete memory DB VOTE_CHANNEL_DB: {self.VoteKickManager.VOTE_CHANNEL_DB}')
self.ctx.Logs.debug(f'Delete memory DB VOTE_CHANNEL_DB: {self.VoteKickManager.VOTE_CHANNEL_DB}')
self.Irc.Commands.drop_command_by_module(self.module_name)
self.ctx.Commands.drop_command_by_module(self.module_name)
return None
except UnboundLocalError as ne:
self.Logs.error(f'{ne}')
self.ctx.Logs.error(f'{ne}')
except NameError as ue:
self.Logs.error(f'{ue}')
self.ctx.Logs.error(f'{ue}')
except Exception as err:
self.Logs.error(f'General Error: {err}')
self.ctx.Logs.error(f'General Error: {err}')
def cmd(self, data: list) -> None:
@@ -142,7 +118,7 @@ class Votekick:
return None
cmd = data.copy() if isinstance(data, list) else list(data).copy()
index, command = self.Irc.Protocol.get_ircd_protocol_poisition(cmd)
index, command = self.ctx.Irc.Protocol.get_ircd_protocol_poisition(cmd)
if index == -1:
return None
@@ -160,19 +136,19 @@ class Votekick:
return None
except KeyError as ke:
self.Logs.error(f"Key Error: {ke}")
self.ctx.Logs.error(f"Key Error: {ke}")
except IndexError as ie:
self.Logs.error(f"{ie} / {cmd} / length {str(len(cmd))}")
self.ctx.Logs.error(f"{ie} / {cmd} / length {str(len(cmd))}")
except Exception as err:
self.Logs.error(f"General Error: {err}")
self.ctx.Logs.error(f"General Error: {err}")
def hcmds(self, user:str, channel: Any, cmd: list, fullcmd: Optional[list] = None) -> None:
async def hcmds(self, user:str, channel: Any, cmd: list, fullcmd: Optional[list] = None) -> None:
# cmd is the command starting from the user command
# full cmd is sending the entire server response
command = str(cmd[0]).lower()
fullcmd = fullcmd
dnickname = self.Config.SERVICE_NICKNAME
dnickname = self.ctx.Config.SERVICE_NICKNAME
fromuser = user
fromchannel = channel
@@ -181,14 +157,14 @@ class Votekick:
case 'vote':
if len(cmd) == 1:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote activate #channel')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote deactivate #channel')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote +')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote -')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote cancel')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote status')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote submit nickname')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote verdict')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote activate #channel')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote deactivate #channel')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote +')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote -')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote cancel')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote status')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote submit nickname')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote verdict')
return None
option = str(cmd[1]).lower()
@@ -198,19 +174,19 @@ class Votekick:
case 'activate':
try:
# vote activate #channel
if self.Admin.get_admin(fromuser) is None:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' :Your are not allowed to execute this command')
if self.ctx.Admin.get_admin(fromuser) is None:
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' :Your are not allowed to execute this command')
return None
sentchannel = str(cmd[2]).lower() if self.Channel.is_valid_channel(str(cmd[2]).lower()) else None
sentchannel = str(cmd[2]).lower() if self.ctx.Channel.is_valid_channel(str(cmd[2]).lower()) else None
if sentchannel is None:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f" The correct command is {self.Config.SERVICE_PREFIX}{command} {option} #CHANNEL")
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f" The correct command is {self.ctx.Config.SERVICE_PREFIX}{command} {option} #CHANNEL")
if self.VoteKickManager.activate_new_channel(sentchannel):
self.Channel.db_query_channel('add', self.module_name, sentchannel)
self.Protocol.send_join_chan(uidornickname=dnickname, channel=sentchannel)
self.Protocol.send2socket(f":{dnickname} SAMODE {sentchannel} +o {dnickname}")
self.Protocol.send_priv_msg(nick_from=dnickname,
await self.ctx.Channel.db_query_channel('add', self.module_name, sentchannel)
await self.ctx.Irc.Protocol.send_join_chan(uidornickname=dnickname, channel=sentchannel)
await self.ctx.Irc.Protocol.send2socket(f":{dnickname} SAMODE {sentchannel} +o {dnickname}")
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname,
msg="You can now use !submit <nickname> to decide if he will stay or not on this channel ",
channel=sentchannel
)
@@ -218,117 +194,117 @@ class Votekick:
return None
except Exception as err:
self.Logs.error(f'{err}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option} #channel')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' Exemple /msg {dnickname} {command} {option} #welcome')
self.ctx.Logs.error(f'{err}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option} #channel')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' Exemple /msg {dnickname} {command} {option} #welcome')
case 'deactivate':
try:
# vote deactivate #channel
if self.Admin.get_admin(fromuser) is None:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f" Your are not allowed to execute this command")
if self.ctx.Admin.get_admin(fromuser) is None:
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f" Your are not allowed to execute this command")
return None
sentchannel = str(cmd[2]).lower() if self.Channel.is_valid_channel(str(cmd[2]).lower()) else None
sentchannel = str(cmd[2]).lower() if self.ctx.Channel.is_valid_channel(str(cmd[2]).lower()) else None
if sentchannel is None:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f" The correct command is {self.Config.SERVICE_PREFIX}{command} {option} #CHANNEL")
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f" The correct command is {self.ctx.Config.SERVICE_PREFIX}{command} {option} #CHANNEL")
self.Protocol.send2socket(f":{dnickname} SAMODE {sentchannel} -o {dnickname}")
self.Protocol.send_part_chan(uidornickname=dnickname, channel=sentchannel)
await self.ctx.Irc.Protocol.send2socket(f":{dnickname} SAMODE {sentchannel} -o {dnickname}")
await self.ctx.Irc.Protocol.send_part_chan(uidornickname=dnickname, channel=sentchannel)
if self.VoteKickManager.drop_vote_channel_model(sentchannel):
self.Channel.db_query_channel('del', self.module_name, sentchannel)
await self.ctx.Channel.db_query_channel('del', self.module_name, sentchannel)
return None
except Exception as err:
self.Logs.error(f'{err}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f" /msg {dnickname} {command} {option} #channel")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f" Exemple /msg {dnickname} {command} {option} #welcome")
self.ctx.Logs.error(f'{err}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f" /msg {dnickname} {command} {option} #channel")
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f" Exemple /msg {dnickname} {command} {option} #welcome")
case '+':
try:
# vote +
channel = fromchannel
if self.VoteKickManager.action_vote(channel, fromuser, '+'):
self.Protocol.send_priv_msg(nick_from=dnickname, msg="Vote recorded, thank you",channel=channel)
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg="Vote recorded, thank you",channel=channel)
else:
self.Protocol.send_priv_msg(nick_from=dnickname, msg="You already submitted a vote", channel=channel)
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg="You already submitted a vote", channel=channel)
except Exception as err:
self.Logs.error(f'{err}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' Exemple /msg {dnickname} {command} {option}')
self.ctx.Logs.error(f'{err}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' Exemple /msg {dnickname} {command} {option}')
case '-':
try:
# vote -
channel = fromchannel
if self.VoteKickManager.action_vote(channel, fromuser, '-'):
self.Protocol.send_priv_msg(nick_from=dnickname, msg="Vote recorded, thank you",channel=channel)
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg="Vote recorded, thank you",channel=channel)
else:
self.Protocol.send_priv_msg(nick_from=dnickname, msg="You already submitted a vote", channel=channel)
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg="You already submitted a vote", channel=channel)
except Exception as err:
self.Logs.error(f'{err}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' Exemple /msg {dnickname} {command} {option}')
self.ctx.Logs.error(f'{err}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' Exemple /msg {dnickname} {command} {option}')
case 'cancel':
try:
# vote cancel
if self.Admin.get_admin(fromuser) is None:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' Your are not allowed to execute this command')
if self.ctx.Admin.get_admin(fromuser) is None:
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' Your are not allowed to execute this command')
return None
if channel is None:
self.Logs.error(f"The channel is not known, defender can't cancel the vote")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' You need to specify the channel => /msg {dnickname} vote_cancel #channel')
self.ctx.Logs.error(f"The channel is not known, defender can't cancel the vote")
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' You need to specify the channel => /msg {dnickname} vote_cancel #channel')
for vote in self.VoteKickManager.VOTE_CHANNEL_DB:
if vote.channel_name == channel:
if self.VoteKickManager.init_vote_system(channel):
self.Protocol.send_priv_msg(nick_from=dnickname,
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname,
msg="Vote system re-initiated",
channel=channel
)
except Exception as err:
self.Logs.error(f'{err}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' Exemple /msg {dnickname} {command} {option}')
self.ctx.Logs.error(f'{err}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' Exemple /msg {dnickname} {command} {option}')
case 'status':
try:
# vote status
for chan in self.VoteKickManager.VOTE_CHANNEL_DB:
if chan.channel_name == channel:
self.Protocol.send_priv_msg(nick_from=dnickname,
msg=f"Channel: {chan.channel_name} | Target: {self.User.get_nickname(chan.target_user)} | For: {chan.vote_for} | Against: {chan.vote_against} | Number of voters: {str(len(chan.voter_users))}",
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname,
msg=f"Channel: {chan.channel_name} | Target: {self.ctx.User.get_nickname(chan.target_user)} | For: {chan.vote_for} | Against: {chan.vote_against} | Number of voters: {str(len(chan.voter_users))}",
channel=channel
)
except Exception as err:
self.Logs.error(f'{err}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' Exemple /msg {dnickname} {command} {option}')
self.ctx.Logs.error(f'{err}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' Exemple /msg {dnickname} {command} {option}')
case 'submit':
try:
# vote submit nickname
if self.Admin.get_admin(fromuser) is None:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' Your are not allowed to execute this command')
if self.ctx.Admin.get_admin(fromuser) is None:
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' Your are not allowed to execute this command')
return None
nickname_submitted = cmd[2]
uid_submitted = self.User.get_uid(nickname_submitted)
user_submitted = self.User.get_user(nickname_submitted)
uid_submitted = self.ctx.User.get_uid(nickname_submitted)
user_submitted = self.ctx.User.get_user(nickname_submitted)
ongoing_user = None
# check if there is an ongoing vote
if self.VoteKickManager.is_vote_ongoing(channel):
votec = self.VoteKickManager.get_vote_channel_model(channel)
if votec:
ongoing_user = self.User.get_nickname(votec.target_user)
self.Protocol.send_priv_msg(nick_from=dnickname,
ongoing_user = self.ctx.User.get_nickname(votec.target_user)
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname,
msg=f"There is an ongoing vote on {ongoing_user}",
channel=channel
)
@@ -336,24 +312,24 @@ class Votekick:
# check if the user exist
if user_submitted is None:
self.Protocol.send_priv_msg(nick_from=dnickname,
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname,
msg=f"This nickname <{nickname_submitted}> do not exist",
channel=channel
)
return None
uid_cleaned = self.Loader.Utils.clean_uid(uid_submitted)
channel_obj = self.Channel.get_channel(channel)
uid_cleaned = self.ctx.Utils.clean_uid(uid_submitted)
channel_obj = self.ctx.Channel.get_channel(channel)
if channel_obj is None:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' This channel [{channel}] do not exist in the Channel Object')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' This channel [{channel}] do not exist in the Channel Object')
return None
clean_uids_in_channel: list = []
for uid in channel_obj.uids:
clean_uids_in_channel.append(self.Loader.Utils.clean_uid(uid))
clean_uids_in_channel.append(self.ctx.Utils.clean_uid(uid))
if not uid_cleaned in clean_uids_in_channel:
self.Protocol.send_priv_msg(nick_from=dnickname,
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname,
msg=f"This nickname <{nickname_submitted}> is not available in this channel",
channel=channel
)
@@ -363,7 +339,7 @@ class Votekick:
pattern = fr'[o|B|S]'
operator_user = re.findall(pattern, user_submitted.umodes)
if operator_user:
self.Protocol.send_priv_msg(nick_from=dnickname,
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname,
msg="You cant vote for this user ! he/she is protected",
channel=channel
)
@@ -371,49 +347,49 @@ class Votekick:
for chan in self.VoteKickManager.VOTE_CHANNEL_DB:
if chan.channel_name == channel:
chan.target_user = self.User.get_uid(nickname_submitted)
chan.target_user = self.ctx.User.get_uid(nickname_submitted)
self.Protocol.send_priv_msg(nick_from=dnickname,
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname,
msg=f"{nickname_submitted} has been targeted for a vote",
channel=channel
)
self.Base.create_timer(60, self.Threads.timer_vote_verdict, (self, channel))
self.Protocol.send_priv_msg(nick_from=dnickname,
self.ctx.Base.create_asynctask(thds.timer_vote_verdict(self, channel))
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname,
msg="This vote will end after 60 secondes",
channel=channel
)
except Exception as err:
self.Logs.error(f'{err}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option} nickname')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' Exemple /msg {dnickname} {command} {option} adator')
self.ctx.Logs.error(f'{err}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option} nickname')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' Exemple /msg {dnickname} {command} {option} adator')
case 'verdict':
try:
# vote verdict
if self.Admin.get_admin(fromuser) is None:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f'Your are not allowed to execute this command')
if self.ctx.Admin.get_admin(fromuser) is None:
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f'Your are not allowed to execute this command')
return None
votec = self.VoteKickManager.get_vote_channel_model(channel)
if votec:
target_user = self.User.get_nickname(votec.target_user)
target_user = self.ctx.User.get_nickname(votec.target_user)
if votec.vote_for >= votec.vote_against:
self.Protocol.send_priv_msg(nick_from=dnickname,
msg=f"User {self.Config.COLORS.bold}{target_user}{self.Config.COLORS.nogc} has {votec.vote_against} votes against and {votec.vote_for} votes for. For this reason, it\'ll be kicked from the channel",
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname,
msg=f"User {self.ctx.Config.COLORS.bold}{target_user}{self.ctx.Config.COLORS.nogc} has {votec.vote_against} votes against and {votec.vote_for} votes for. For this reason, it\'ll be kicked from the channel",
channel=channel
)
self.Protocol.send2socket(f":{dnickname} KICK {channel} {target_user} Following the vote, you are not welcome in {channel}")
await self.ctx.Irc.Protocol.send2socket(f":{dnickname} KICK {channel} {target_user} Following the vote, you are not welcome in {channel}")
else:
self.Protocol.send_priv_msg(
await self.ctx.Irc.Protocol.send_priv_msg(
nick_from=dnickname,
msg=f"User {self.Config.COLORS.bold}{target_user}{self.Config.COLORS.nogc} has {votec.vote_against} votes against and {votec.vote_for} votes for. For this reason, it\'ll remain in the channel",
msg=f"User {self.ctx.Config.COLORS.bold}{target_user}{self.ctx.Config.COLORS.nogc} has {votec.vote_against} votes against and {votec.vote_for} votes for. For this reason, it\'ll remain in the channel",
channel=channel
)
if self.VoteKickManager.init_vote_system(channel):
self.Protocol.send_priv_msg(
await self.ctx.Irc.Protocol.send_priv_msg(
nick_from=dnickname,
msg="System vote re initiated",
channel=channel
@@ -421,19 +397,19 @@ class Votekick:
return None
except Exception as err:
self.Logs.error(f'{err}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option}')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' Exemple /msg {dnickname} {command} {option}')
self.ctx.Logs.error(f'{err}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option}')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' Exemple /msg {dnickname} {command} {option}')
case _:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote activate #channel')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote deactivate #channel')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote +')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote -')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote cancel')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote status')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote submit nickname')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote verdict')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote activate #channel')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote deactivate #channel')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote +')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote -')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote cancel')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote status')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote submit nickname')
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote verdict')
return None
case _:

View File

@@ -1,35 +1,38 @@
import asyncio
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from mods.votekick.mod_votekick import Votekick
def timer_vote_verdict(uplink: 'Votekick', channel: str) -> None:
async def timer_vote_verdict(uplink: 'Votekick', channel: str) -> None:
dnickname = uplink.Config.SERVICE_NICKNAME
dnickname = uplink.ctx.Config.SERVICE_NICKNAME
if not uplink.VoteKickManager.is_vote_ongoing(channel):
return None
await asyncio.sleep(60)
votec = uplink.VoteKickManager.get_vote_channel_model(channel)
if votec:
target_user = uplink.User.get_nickname(votec.target_user)
target_user = uplink.ctx.User.get_nickname(votec.target_user)
if votec.vote_for >= votec.vote_against and votec.vote_for != 0:
uplink.Protocol.send_priv_msg(nick_from=dnickname,
msg=f"User {uplink.Config.COLORS.bold}{target_user}{uplink.Config.COLORS.nogc} has {votec.vote_against} votes against and {votec.vote_for} votes for. For this reason, it\'ll be kicked from the channel",
await uplink.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname,
msg=f"User {uplink.ctx.Config.COLORS.bold}{target_user}{uplink.ctx.Config.COLORS.nogc} has {votec.vote_against} votes against and {votec.vote_for} votes for. For this reason, it\'ll be kicked from the channel",
channel=channel
)
uplink.Protocol.send2socket(f":{dnickname} KICK {channel} {target_user} Following the vote, you are not welcome in {channel}")
await uplink.ctx.Irc.Protocol.send2socket(f":{dnickname} KICK {channel} {target_user} Following the vote, you are not welcome in {channel}")
else:
uplink.Protocol.send_priv_msg(
await uplink.ctx.Irc.Protocol.send_priv_msg(
nick_from=dnickname,
msg=f"User {uplink.Config.COLORS.bold}{target_user}{uplink.Config.COLORS.nogc} has {votec.vote_against} votes against and {votec.vote_for} votes for. For this reason, it\'ll remain in the channel",
msg=f"User {uplink.ctx.Config.COLORS.bold}{target_user}{uplink.ctx.Config.COLORS.nogc} has {votec.vote_against} votes against and {votec.vote_for} votes for. For this reason, it\'ll remain in the channel",
channel=channel
)
if uplink.VoteKickManager.init_vote_system(channel):
uplink.Protocol.send_priv_msg(
await uplink.ctx.Irc.Protocol.send_priv_msg(
nick_from=dnickname,
msg="System vote re initiated",
channel=channel

View File

@@ -4,7 +4,7 @@ from typing import TYPE_CHECKING
if TYPE_CHECKING:
from mods.votekick.mod_votekick import Votekick
def add_vote_channel_to_database(uplink: 'Votekick', channel: str) -> bool:
async def add_vote_channel_to_database(uplink: 'Votekick', channel: str) -> bool:
"""Adds a new channel to the votekick database if it doesn't already exist.
This function checks if the specified channel is already registered in the
@@ -18,16 +18,16 @@ def add_vote_channel_to_database(uplink: 'Votekick', channel: str) -> bool:
bool: True if the channel was successfully inserted into the database.
False if the channel already exists or the insertion failed.
"""
current_datetime = uplink.Utils.get_sdatetime()
current_datetime = uplink.ctx.Utils.get_sdatetime()
mes_donnees = {'channel': channel}
response = uplink.Base.db_execute_query("SELECT id FROM votekick_channel WHERE channel = :channel", mes_donnees)
response = await uplink.ctx.Base.db_execute_query("SELECT id FROM votekick_channel WHERE channel = :channel", mes_donnees)
is_channel_exist = response.fetchone()
if is_channel_exist is None:
mes_donnees = {'datetime': current_datetime, 'channel': channel}
insert = uplink.Base.db_execute_query(f"INSERT INTO votekick_channel (datetime, channel) VALUES (:datetime, :channel)", mes_donnees)
insert = await uplink.ctx.Base.db_execute_query(f"INSERT INTO votekick_channel (datetime, channel) VALUES (:datetime, :channel)", mes_donnees)
if insert.rowcount > 0:
return True
else:
@@ -35,7 +35,7 @@ def add_vote_channel_to_database(uplink: 'Votekick', channel: str) -> bool:
else:
return False
def delete_vote_channel_from_database(uplink: 'Votekick', channel: str) -> bool:
async def delete_vote_channel_from_database(uplink: 'Votekick', channel: str) -> bool:
"""Deletes a channel entry from the votekick database.
This function removes the specified channel from the `votekick_channel` table
@@ -49,7 +49,7 @@ def delete_vote_channel_from_database(uplink: 'Votekick', channel: str) -> bool:
bool: True if the channel was successfully deleted, False if no rows were affected.
"""
mes_donnes = {'channel': channel}
response = uplink.Base.db_execute_query("DELETE FROM votekick_channel WHERE channel = :channel", mes_donnes)
response = await uplink.ctx.Base.db_execute_query("DELETE FROM votekick_channel WHERE channel = :channel", mes_donnes)
affected_row = response.rowcount
@@ -58,17 +58,17 @@ def delete_vote_channel_from_database(uplink: 'Votekick', channel: str) -> bool:
else:
return False
def join_saved_channels(uplink: 'Votekick') -> None:
async def join_saved_channels(uplink: 'Votekick') -> None:
param = {'module_name': uplink.module_name}
result = uplink.Base.db_execute_query(f"SELECT id, channel_name FROM {uplink.Config.TABLE_CHANNEL} WHERE module_name = :module_name", param)
result = await uplink.ctx.Base.db_execute_query(f"SELECT id, channel_name FROM {uplink.ctx.Config.TABLE_CHANNEL} WHERE module_name = :module_name", param)
channels = result.fetchall()
for channel in channels:
id_, chan = channel
uplink.VoteKickManager.activate_new_channel(chan)
uplink.Protocol.send_sjoin(channel=chan)
uplink.Protocol.send2socket(f":{uplink.Config.SERVICE_NICKNAME} SAMODE {chan} +o {uplink.Config.SERVICE_NICKNAME}")
await uplink.ctx.Irc.Protocol.send_sjoin(channel=chan)
await uplink.ctx.Irc.Protocol.send2socket(f":{uplink.ctx.Config.SERVICE_NICKNAME} SAMODE {chan} +o {uplink.ctx.Config.SERVICE_NICKNAME}")
return None

View File

@@ -10,8 +10,8 @@ class VotekickManager:
def __init__(self, uplink: 'Votekick'):
self.uplink = uplink
self.Logs = uplink.Logs
self.Utils = uplink.Utils
self.Logs = uplink.ctx.Logs
self.Utils = uplink.ctx.Utils
def activate_new_channel(self, channel_name: str) -> bool:
"""Activate a new channel in the votekick systeme

8
requirements.txt Normal file
View File

@@ -0,0 +1,8 @@
Faker==37.12.0
psutil==7.1.2
PyYAML==6.0.3
requests==2.32.5
SQLAlchemy==2.0.44
unrealircd_rpc_py==3.0.4
starlette==0.50.0
uvicorn==0.38.0

View File

@@ -1,10 +1,12 @@
{
"version": "6.2.5",
"version": "6.4.0",
"requests": "2.32.3",
"psutil": "6.0.0",
"unrealircd_rpc_py": "2.0.5",
"sqlalchemy": "2.0.35",
"faker": "30.1.0",
"pyyaml": "6.0.2"
}
"requests": "2.32.5",
"psutil": "7.1.2",
"unrealircd_rpc_py": "3.0.4",
"sqlalchemy": "2.0.44",
"faker": "37.12.0",
"pyyaml": "6.0.3",
"starlette":"0.50.0",
"uvicorn":"0.38.0"
}