mirror of
https://github.com/iio612/DEFENDER.git
synced 2026-02-13 19:24:23 +00:00
Compare commits
179 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3002313697 | ||
|
|
735553549f | ||
|
|
4d4d0db102 | ||
|
|
6f6e6d6c22 | ||
|
|
91210f96ec | ||
|
|
54ea946da0 | ||
|
|
26364f854a | ||
|
|
bfd434eca4 | ||
|
|
1d7a31ec20 | ||
|
|
f2323f7a3c | ||
|
|
fe96d2e906 | ||
|
|
6c797b8efb | ||
|
|
8bc417abf9 | ||
|
|
9cd338ecf8 | ||
|
|
ab4df40a4c | ||
|
|
d989dcd762 | ||
|
|
2694d68cdc | ||
|
|
cbe527d7d9 | ||
|
|
5938a1511b | ||
|
|
eb7c6ef8d0 | ||
|
|
4c93f85008 | ||
|
|
226340e1aa | ||
|
|
d66d297a33 | ||
|
|
9e688f7964 | ||
|
|
18da187e2e | ||
|
|
b527282bf2 | ||
|
|
2eb0fa5f5d | ||
|
|
6af1377823 | ||
|
|
fe4b68e115 | ||
|
|
e9af3f9155 | ||
|
|
3a8c7f0632 | ||
|
|
1e72906f7b | ||
|
|
5b7c2e83d1 | ||
|
|
51f709e4a1 | ||
|
|
aa15aea749 | ||
|
|
1b30b1ff4e | ||
|
|
af992f7721 | ||
|
|
3926d7270d | ||
|
|
c371910066 | ||
|
|
2e422c93e5 | ||
|
|
ba989b7f26 | ||
|
|
fc01de34b2 | ||
|
|
a3dcc20a06 | ||
|
|
7ffc58d4ff | ||
|
|
6a0d4e2286 | ||
|
|
7dd15f2dac | ||
|
|
10cad7cda6 | ||
|
|
999072a88a | ||
|
|
8932e1441a | ||
|
|
9cee758b6f | ||
|
|
511e0c0715 | ||
|
|
371c8fb5f1 | ||
|
|
401e785383 | ||
|
|
a7efede75e | ||
|
|
a1254c7a39 | ||
|
|
c05990f862 | ||
|
|
de2b5fa8e2 | ||
|
|
371645149d | ||
|
|
a6cf11ae2a | ||
|
|
445cbc27b0 | ||
|
|
f9eb374798 | ||
|
|
17cb2ada5f | ||
|
|
b52a57f95a | ||
|
|
1bfd95c291 | ||
|
|
0c6c3cd6ac | ||
|
|
0e6384c26c | ||
|
|
79c1b94a92 | ||
|
|
5a1432c1e6 | ||
|
|
34b5b4204e | ||
|
|
ff58cbb022 | ||
|
|
6450418859 | ||
|
|
9f2da13f88 | ||
|
|
0117e1dd3a | ||
|
|
deb76baf30 | ||
|
|
29f049b3c3 | ||
|
|
fb41a13d0a | ||
|
|
769ab8b632 | ||
|
|
2fbe75b83e | ||
|
|
8abae5df3e | ||
|
|
1a71a6eb4d | ||
|
|
b182aa8bcb | ||
|
|
e5a5f01603 | ||
|
|
99f8949681 | ||
|
|
05b15f2f18 | ||
|
|
35c3faf68c | ||
|
|
2e9bfd2c3b | ||
|
|
80131b7b7a | ||
|
|
ffb30f12ec | ||
|
|
b7b61081be | ||
|
|
030b706b65 | ||
|
|
c428ea2b41 | ||
|
|
9036e4f626 | ||
|
|
fd79ada13d | ||
|
|
8323f6cc9b | ||
|
|
6fcd553481 | ||
|
|
5cd82a174d | ||
|
|
beec16f39d | ||
|
|
a043a58f45 | ||
|
|
fd9643eddc | ||
|
|
ed1a048603 | ||
|
|
3dfde9b1aa | ||
|
|
5e35a10193 | ||
|
|
ff776541d7 | ||
|
|
6b7fd16a44 | ||
|
|
e79c15188e | ||
|
|
b306968115 | ||
|
|
184e90adce | ||
|
|
c7b88150b5 | ||
|
|
02f0608b75 | ||
|
|
25bbddf459 | ||
|
|
0c6fcb7710 | ||
|
|
0a2e3f724b | ||
|
|
1224604460 | ||
|
|
43072acecc | ||
|
|
bcf6b6b675 | ||
|
|
b50205c766 | ||
|
|
bd95b6b448 | ||
|
|
ebbad1d9e4 | ||
|
|
ae1f0ed424 | ||
|
|
80854aea98 | ||
|
|
483638dab4 | ||
|
|
4806de4cca | ||
|
|
f4b76eaf09 | ||
|
|
37617fc286 | ||
|
|
06fc6c4d82 | ||
|
|
0a4e185fe8 | ||
|
|
4c327940dd | ||
|
|
a15a5b1026 | ||
|
|
25262d4049 | ||
|
|
8d23827e1e | ||
|
|
f5212deacf | ||
|
|
3fc49e9069 | ||
|
|
e075b7b8d5 | ||
|
|
30a89e7d14 | ||
|
|
aee1dca124 | ||
|
|
9e255e806d | ||
|
|
6b22d786e3 | ||
|
|
21a2619f49 | ||
|
|
1686c4a0b5 | ||
|
|
5629dcfde6 | ||
|
|
f3fe3c43cb | ||
|
|
c0cd4db3af | ||
|
|
9ea5ae50d5 | ||
|
|
0a655b2df0 | ||
|
|
7a50bc9632 | ||
|
|
275d37ed2d | ||
|
|
9ae6e5bf4c | ||
|
|
2feed6878f | ||
|
|
0b173e8d18 | ||
|
|
d2b46f2618 | ||
|
|
7a9bcff988 | ||
|
|
cc80e35482 | ||
|
|
d1568106f9 | ||
|
|
9df18de0b1 | ||
|
|
64f1490942 | ||
|
|
9ddb2a28b2 | ||
|
|
eacfe0d087 | ||
|
|
680a446c2c | ||
|
|
a10aa9b94e | ||
|
|
56265985f7 | ||
|
|
be36c56ceb | ||
|
|
ac332e6802 | ||
|
|
6063ceba35 | ||
|
|
2204c4fdf8 | ||
|
|
befe452df8 | ||
|
|
b98a20ad45 | ||
|
|
cb042a5411 | ||
|
|
dc63df08cf | ||
|
|
a3edf48120 | ||
|
|
a3a61c332f | ||
|
|
f7664c9874 | ||
|
|
eeaacddbf2 | ||
|
|
d37c152160 | ||
|
|
39412fc1c0 | ||
|
|
b81f502b95 | ||
|
|
e148659d00 | ||
|
|
44da01945c | ||
|
|
71a7d29b08 | ||
|
|
bd9713006a |
10
.gitignore
vendored
10
.gitignore
vendored
@@ -1,7 +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
65
Makefile
Normal 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
139
README.md
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
67
config/exemple_configuration.yaml
Normal file
67
config/exemple_configuration.yaml
Normal file
@@ -0,0 +1,67 @@
|
||||
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"
|
||||
|
||||
##########################################
|
||||
# Defender JSON-RPC Configuration #
|
||||
##########################################
|
||||
RPC_HOST: "0.0.0.0"
|
||||
RPC_PORT: 5000
|
||||
RPC_USERS:
|
||||
- USERNAME: "RPC_USER_1"
|
||||
PASSWORD: "RPC_USER_1_PASSWORD"
|
||||
- USERNAME: "RPC_USER_2"
|
||||
PASSWORD: "RPC_USER_2_PASSWORD"
|
||||
|
||||
##########################################
|
||||
# UnrealIRCD JSON-RPC Configuration #
|
||||
##########################################
|
||||
# unixsocket or http
|
||||
JSONRPC_METHOD: "unixsocket"
|
||||
|
||||
# If the method is unixsocket you don't need URL, USER or PASSWORD
|
||||
JSONRPC_PATH_TO_SOCKET_FILE: "/PATH/TO/YOUR/IRCD/data/rpc.socket"
|
||||
|
||||
# If METHOD is http
|
||||
JSONRPC_URL: "https://your.domaine.com:8600/api"
|
||||
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
|
||||
@@ -0,0 +1 @@
|
||||
__version__ = '6.3'
|
||||
652
core/base.py
652
core/base.py
@@ -1,55 +1,65 @@
|
||||
import asyncio
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
import time
|
||||
import random
|
||||
import socket
|
||||
import hashlib
|
||||
import logging
|
||||
import threading
|
||||
import ipaddress
|
||||
|
||||
import ast
|
||||
import requests
|
||||
|
||||
import concurrent.futures
|
||||
from dataclasses import fields
|
||||
from typing import Union, Literal, TYPE_CHECKING
|
||||
from typing import Any, Awaitable, Callable, Optional, TYPE_CHECKING, Union
|
||||
from base64 import b64decode, b64encode
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from sqlalchemy import create_engine, Engine, Connection, CursorResult
|
||||
from sqlalchemy.sql import text
|
||||
from core.definition import MConfig
|
||||
import core.definition as dfn
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.classes.settings import Settings
|
||||
from core.loader import Loader
|
||||
|
||||
class Base:
|
||||
|
||||
def __init__(self, Config: MConfig, settings: 'Settings') -> None:
|
||||
def __init__(self, loader: 'Loader') -> None:
|
||||
|
||||
self.Config = Config # Assigner l'objet de configuration
|
||||
self.Settings: Settings = settings
|
||||
self.init_log_system() # Demarrer le systeme de log
|
||||
self.check_for_new_version(True) # Verifier si une nouvelle version est disponible
|
||||
self.Loader = loader
|
||||
self.Config = loader.Config
|
||||
self.Settings = loader.Settings
|
||||
self.Utils = loader.Utils
|
||||
self.logs = loader.Logs
|
||||
|
||||
# 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
|
||||
self.running_timers: list[threading.Timer] = self.Settings.RUNNING_TIMERS
|
||||
|
||||
# Liste des threads en cours
|
||||
self.running_threads:list[threading.Thread] = self.Settings.RUNNING_THREADS
|
||||
self.running_threads: list[threading.Thread] = self.Settings.RUNNING_THREADS
|
||||
|
||||
# Les sockets ouvert
|
||||
self.running_sockets: list[socket.socket] = self.Settings.RUNNING_SOCKETS
|
||||
|
||||
# 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
|
||||
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,115 +146,26 @@ class Base:
|
||||
except Exception as err:
|
||||
self.logs.error(f'General Error: {err}')
|
||||
|
||||
def get_unixtime(self) -> int:
|
||||
"""
|
||||
Cette fonction retourne un UNIXTIME de type 12365456
|
||||
Return: Current time in seconds since the Epoch (int)
|
||||
"""
|
||||
cet_offset = timezone(timedelta(hours=2))
|
||||
now_cet = datetime.now(cet_offset)
|
||||
unixtime_cet = int(now_cet.timestamp())
|
||||
unixtime = int( time.time() )
|
||||
|
||||
return unixtime
|
||||
|
||||
def get_datetime(self) -> str:
|
||||
"""
|
||||
Retourne une date au format string (24-12-2023 20:50:59)
|
||||
"""
|
||||
currentdate = datetime.now().strftime('%d-%m-%Y %H:%M:%S')
|
||||
return currentdate
|
||||
|
||||
def get_all_modules(self) -> list:
|
||||
|
||||
all_files = os.listdir('mods/')
|
||||
all_modules: list = []
|
||||
for module in all_files:
|
||||
if module.endswith('.py') and not module == '__init__.py':
|
||||
all_modules.append(module.replace('.py', '').lower())
|
||||
|
||||
return all_modules
|
||||
|
||||
def create_log(self, log_message: str) -> None:
|
||||
async def create_log(self, log_message: str) -> None:
|
||||
"""Enregiste les logs
|
||||
|
||||
Args:
|
||||
string (str): Le message a enregistrer
|
||||
log_message (str): Le message a enregistrer
|
||||
|
||||
Returns:
|
||||
None: Aucun retour
|
||||
"""
|
||||
sql_insert = f"INSERT INTO {self.Config.TABLE_LOG} (datetime, server_msg) VALUES (:datetime, :server_msg)"
|
||||
mes_donnees = {'datetime': str(self.get_datetime()),'server_msg': f'{log_message}'}
|
||||
self.db_execute_query(sql_insert, mes_donnees)
|
||||
mes_donnees = {'datetime': str(self.Utils.get_sdatetime()),'server_msg': f'{log_message}'}
|
||||
await self.db_execute_query(sql_insert, mes_donnees)
|
||||
|
||||
return None
|
||||
|
||||
def init_log_system(self) -> None:
|
||||
# Create folder if not available
|
||||
logs_directory = f'logs{self.Config.OS_SEP}'
|
||||
if not os.path.exists(f'{logs_directory}'):
|
||||
os.makedirs(logs_directory)
|
||||
|
||||
# Init logs object
|
||||
self.logs = logging.getLogger(self.Config.LOGGING_NAME)
|
||||
self.logs.setLevel(self.Config.DEBUG_LEVEL)
|
||||
|
||||
# Add Handlers
|
||||
file_hanlder = logging.FileHandler(f'logs{self.Config.OS_SEP}defender.log',encoding='UTF-8')
|
||||
file_hanlder.setLevel(self.Config.DEBUG_LEVEL)
|
||||
|
||||
# Define log format
|
||||
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(filename)s - %(lineno)d - %(funcName)s - %(message)s')
|
||||
|
||||
# Apply log format
|
||||
file_hanlder.setFormatter(formatter)
|
||||
|
||||
# Add handler to logs
|
||||
self.logs.addHandler(file_hanlder)
|
||||
|
||||
# Apply the filter
|
||||
self.logs.addFilter(self.replace_filter)
|
||||
|
||||
# self.logs.Logger('defender').addFilter(self.replace_filter)
|
||||
self.logs.info('#################### STARTING DEFENDER ####################')
|
||||
|
||||
return None
|
||||
|
||||
def replace_filter(self, record: logging.LogRecord) -> bool:
|
||||
|
||||
response = True
|
||||
filter: list[str] = ['PING', f":{self.Config.SERVICE_PREFIX}auth"]
|
||||
|
||||
# record.msg = record.getMessage().replace("PING", "[REDACTED]")
|
||||
if self.Settings.CONSOLE:
|
||||
print(record.getMessage())
|
||||
|
||||
for f in filter:
|
||||
if f in record.getMessage():
|
||||
response = False
|
||||
|
||||
return response # Retourne True pour permettre l'affichage du message
|
||||
|
||||
def delete_logger(self, logger_name: str) -> None:
|
||||
|
||||
# Récupérer le logger
|
||||
logger = logging.getLogger(logger_name)
|
||||
|
||||
# Retirer tous les gestionnaires du logger et les fermer
|
||||
for handler in logger.handlers[:]: # Utiliser une copie de la liste
|
||||
logger.removeHandler(handler)
|
||||
handler.close()
|
||||
|
||||
# Supprimer le logger du dictionnaire global
|
||||
logging.Logger.manager.loggerDict.pop(logger_name, None)
|
||||
|
||||
return None
|
||||
|
||||
def log_cmd(self, user_cmd:str, cmd:str) -> None:
|
||||
async def log_cmd(self, user_cmd: str, cmd: str) -> None:
|
||||
"""Enregistre les commandes envoyées par les utilisateurs
|
||||
|
||||
Args:
|
||||
user_cmd (str): The user who performed the command
|
||||
cmd (str): la commande a enregistrer
|
||||
"""
|
||||
cmd_list = cmd.split()
|
||||
@@ -255,72 +176,12 @@ class Base:
|
||||
cmd = ' '.join(cmd_list)
|
||||
|
||||
insert_cmd_query = f"INSERT INTO {self.Config.TABLE_COMMAND} (datetime, user, commande) VALUES (:datetime, :user, :commande)"
|
||||
mes_donnees = {'datetime': self.get_datetime(), 'user': user_cmd, 'commande': cmd}
|
||||
self.db_execute_query(insert_cmd_query, mes_donnees)
|
||||
mes_donnees = {'datetime': self.Utils.get_sdatetime(), 'user': user_cmd, 'commande': cmd}
|
||||
await self.db_execute_query(insert_cmd_query, mes_donnees)
|
||||
|
||||
return False
|
||||
return None
|
||||
|
||||
def db_isModuleExist(self, module_name:str) -> bool:
|
||||
"""Teste si un module existe déja dans la base de données
|
||||
|
||||
Args:
|
||||
module_name (str): le non du module a chercher dans la base de données
|
||||
|
||||
Returns:
|
||||
bool: True si le module existe déja dans la base de données sinon False
|
||||
"""
|
||||
query = f"SELECT id FROM {self.Config.TABLE_MODULE} WHERE module_name = :module_name"
|
||||
mes_donnes = {'module_name': module_name}
|
||||
results = self.db_execute_query(query, mes_donnes)
|
||||
|
||||
if results.fetchall():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def db_record_module(self, user_cmd:str, module_name:str, isdefault:int = 0) -> None:
|
||||
"""Enregistre les modules dans la base de données
|
||||
|
||||
Args:
|
||||
cmd (str): le module a enregistrer
|
||||
"""
|
||||
|
||||
if not self.db_isModuleExist(module_name):
|
||||
self.logs.debug(f"Le module {module_name} n'existe pas alors ont le créer")
|
||||
insert_cmd_query = f"INSERT INTO {self.Config.TABLE_MODULE} (datetime, user, module_name, isdefault) VALUES (:datetime, :user, :module_name, :isdefault)"
|
||||
mes_donnees = {'datetime': self.get_datetime(), 'user': user_cmd, 'module_name': module_name, 'isdefault': isdefault}
|
||||
self.db_execute_query(insert_cmd_query, mes_donnees)
|
||||
else:
|
||||
self.logs.debug(f"Le module {module_name} existe déja dans la base de données")
|
||||
|
||||
return False
|
||||
|
||||
def db_update_module(self, user_cmd: str, module_name: str) -> None:
|
||||
"""Modifie la date et le user qui a rechargé le module
|
||||
|
||||
Args:
|
||||
user_cmd (str): le user qui a rechargé le module
|
||||
module_name (str): le module a rechargé
|
||||
"""
|
||||
update_cmd_query = f"UPDATE {self.Config.TABLE_MODULE} SET datetime = :datetime, user = :user WHERE module_name = :module_name"
|
||||
mes_donnees = {'datetime': self.get_datetime(), 'user': user_cmd, 'module_name': module_name}
|
||||
self.db_execute_query(update_cmd_query, mes_donnees)
|
||||
|
||||
return False
|
||||
|
||||
def db_delete_module(self, module_name:str) -> None:
|
||||
"""Supprime les modules de la base de données
|
||||
|
||||
Args:
|
||||
cmd (str): le module a supprimer
|
||||
"""
|
||||
insert_cmd_query = f"DELETE FROM {self.Config.TABLE_MODULE} WHERE module_name = :module_name"
|
||||
mes_donnees = {'module_name': module_name}
|
||||
self.db_execute_query(insert_cmd_query, mes_donnees)
|
||||
|
||||
return False
|
||||
|
||||
def db_sync_core_config(self, module_name: str, dataclassObj: object) -> bool:
|
||||
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
|
||||
@@ -336,7 +197,7 @@ class Base:
|
||||
"""
|
||||
try:
|
||||
response = True
|
||||
current_date = self.get_datetime()
|
||||
current_date = self.Utils.get_sdatetime()
|
||||
core_table = self.Config.TABLE_CONFIG
|
||||
|
||||
# Add local parameters to DB
|
||||
@@ -347,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:
|
||||
@@ -359,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}')
|
||||
@@ -367,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}')
|
||||
@@ -382,11 +243,11 @@ 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:
|
||||
if type(getattr(dataclassObj, param)) == list:
|
||||
if isinstance(getattr(dataclassObj, param), list):
|
||||
value = ast.literal_eval(value)
|
||||
|
||||
setattr(dataclassObj, param, self.int_if_possible(value))
|
||||
@@ -399,59 +260,60 @@ 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:
|
||||
mes_donnees = {'datetime': self.get_datetime(),
|
||||
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.crypt_password(self.Config.PASSWORD)
|
||||
password = self.Utils.hash_password(self.Config.PASSWORD)
|
||||
|
||||
mes_donnees = {'createdOn': self.get_datetime(),
|
||||
mes_donnees = {'createdOn': self.Utils.get_sdatetime(),
|
||||
'user': admin,
|
||||
'password': password,
|
||||
'hostname': '*',
|
||||
'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
|
||||
@@ -460,17 +322,20 @@ class Base:
|
||||
|
||||
try:
|
||||
t = threading.Timer(interval=time_to_wait, function=func, args=func_args)
|
||||
t.setName(func.__name__)
|
||||
t.name = func.__name__
|
||||
t.start()
|
||||
|
||||
self.running_timers.append(t)
|
||||
|
||||
self.logs.debug(f"-- Timer ID : {str(t.ident)} | Running Threads : {len(threading.enumerate())}")
|
||||
|
||||
return None
|
||||
|
||||
except AssertionError as ae:
|
||||
self.logs.error(f'Assertion Error -> {ae}')
|
||||
return None
|
||||
|
||||
def create_thread(self, func:object, func_args: tuple = (), run_once:bool = False, daemon: bool = True) -> None:
|
||||
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:
|
||||
@@ -479,21 +344,147 @@ 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:
|
||||
for thread in self.running_threads:
|
||||
if thread.getName() == func_name:
|
||||
if thread.name == func_name:
|
||||
return None
|
||||
|
||||
th = threading.Thread(target=func, args=func_args, name=str(func_name), daemon=daemon)
|
||||
th.start()
|
||||
|
||||
self.running_threads.append(th)
|
||||
self.logs.debug(f"-- Thread ID : {str(th.ident)} | Thread name : {th.getName()} | Running Threads : {len(threading.enumerate())}")
|
||||
self.logs.debug(f"-- Thread ID : {str(th.ident)} | Thread name : {th.name} | Running Threads : {len(threading.enumerate())}")
|
||||
|
||||
except AssertionError as ae:
|
||||
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() == 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], context: Optional[dict[str, Any]] = None):
|
||||
"""Log task when done
|
||||
|
||||
Args:
|
||||
task (asyncio.Task): The Asyncio Task callback
|
||||
"""
|
||||
|
||||
if context:
|
||||
print(context)
|
||||
|
||||
task_name = "Future" if isinstance(task, asyncio.Future) else task.get_name()
|
||||
task_or_future = "Task"
|
||||
try:
|
||||
if task.exception():
|
||||
self.logs.error(f"[ASYNCIO] {task_or_future} {task_name} failed with exception: {task.exception()}")
|
||||
else:
|
||||
self.logs.debug(f"[ASYNCIO] {task_or_future} {task_name} completed successfully.")
|
||||
except asyncio.CancelledError as ce:
|
||||
self.logs.debug(f"[ASYNCIO] {task_or_future} {task_name} terminated with cancelled error. {ce}")
|
||||
except asyncio.InvalidStateError as ie:
|
||||
self.logs.debug(f"[ASYNCIO] {task_or_future} {task_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.
|
||||
|
||||
Args:
|
||||
thread_name (str): The thread name
|
||||
|
||||
Returns:
|
||||
bool: True if is alive
|
||||
"""
|
||||
for thread in self.running_threads:
|
||||
if thread.name.lower() == thread_name.lower():
|
||||
if thread.is_alive():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
def is_thread_exist(self, thread_name: str) -> bool:
|
||||
"""Check if the thread exist in the local var (running_threads)
|
||||
|
||||
Args:
|
||||
thread_name (str): The thread name
|
||||
|
||||
Returns:
|
||||
bool: True if the thread exist
|
||||
"""
|
||||
for thread in self.running_threads:
|
||||
if thread.name.lower() == thread_name.lower():
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def thread_count(self, thread_name: str) -> int:
|
||||
"""This method return the number of existing threads
|
||||
@@ -505,11 +496,11 @@ class Base:
|
||||
Returns:
|
||||
int: Number of threads
|
||||
"""
|
||||
with self.lock:
|
||||
with self.Settings.LOCK:
|
||||
count = 0
|
||||
|
||||
for thr in self.running_threads:
|
||||
if thread_name == thr.getName():
|
||||
if thread_name == thr.name:
|
||||
count += 1
|
||||
|
||||
return count
|
||||
@@ -518,12 +509,11 @@ class Base:
|
||||
"""Methode qui supprime les timers qui ont finis leurs job
|
||||
"""
|
||||
try:
|
||||
|
||||
for timer in self.running_timers:
|
||||
if not timer.is_alive():
|
||||
timer.cancel()
|
||||
self.running_timers.remove(timer)
|
||||
self.logs.info(f"-- Timer {str(timer)} removed")
|
||||
self.logs.debug(f"-- Timer {str(timer)} removed")
|
||||
else:
|
||||
self.logs.debug(f"--* Timer {str(timer)} Still running ...")
|
||||
|
||||
@@ -535,15 +525,14 @@ class Base:
|
||||
"""
|
||||
try:
|
||||
for thread in self.running_threads:
|
||||
if thread.getName() != 'heartbeat':
|
||||
# print(thread.getName(), thread.is_alive(), sep=' / ')
|
||||
if thread.name != 'heartbeat':
|
||||
if not thread.is_alive():
|
||||
self.running_threads.remove(thread)
|
||||
self.logs.info(f"-- Thread {str(thread.getName())} {str(thread.native_id)} removed")
|
||||
self.logs.debug(f"-- Thread {str(thread.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:
|
||||
|
||||
@@ -556,38 +545,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.getName()} to close")
|
||||
timer.cancel()
|
||||
time.sleep(0.2)
|
||||
self.running_timers.remove(timer)
|
||||
self.logs.debug(f"> Cancelling {timer.getName()} {timer.native_id}")
|
||||
|
||||
self.logs.debug(f"=======> Checking for Threads to stop")
|
||||
for thread in self.running_threads:
|
||||
if thread.getName() == '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.getName()} {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
|
||||
@@ -599,10 +556,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,
|
||||
@@ -652,27 +609,32 @@ class Base:
|
||||
hostname TEXT,
|
||||
vhost TEXT,
|
||||
password TEXT,
|
||||
fingerprint TEXT,
|
||||
language TEXT,
|
||||
level INTEGER
|
||||
)
|
||||
'''
|
||||
|
||||
self.db_execute_query(table_core_log)
|
||||
self.db_execute_query(table_core_log_command)
|
||||
self.db_execute_query(table_core_module)
|
||||
self.db_execute_query(table_core_admin)
|
||||
self.db_execute_query(table_core_channel)
|
||||
self.db_execute_query(table_core_config)
|
||||
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_channel)
|
||||
await self.db_execute_query(table_core_config)
|
||||
|
||||
# Patch database
|
||||
await self.db_patch(self.Config.TABLE_ADMIN, "language", "TEXT")
|
||||
|
||||
if self.install:
|
||||
self.db_record_module('sys', 'mod_command', 1)
|
||||
self.db_record_module('sys', 'mod_defender', 1)
|
||||
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)
|
||||
@@ -683,26 +645,34 @@ class Base:
|
||||
|
||||
return response
|
||||
|
||||
def db_close(self) -> None:
|
||||
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:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
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
|
||||
else:
|
||||
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}")
|
||||
|
||||
def crypt_password(self, password:str) -> str:
|
||||
"""Retourne un mot de passe chiffré en MD5
|
||||
|
||||
Args:
|
||||
password (str): Le password en clair
|
||||
|
||||
Returns:
|
||||
str: Le password en MD5
|
||||
"""
|
||||
md5_password = hashlib.md5(password.encode()).hexdigest()
|
||||
|
||||
return md5_password
|
||||
|
||||
def int_if_possible(self, value):
|
||||
"""Convertit la valeur reçue en entier, si possible.
|
||||
Sinon elle retourne la valeur initiale.
|
||||
@@ -721,7 +691,24 @@ class Base:
|
||||
except TypeError:
|
||||
return value
|
||||
|
||||
def is_valid_ip(self, ip_to_control:str) -> bool:
|
||||
def convert_to_int(self, value: Any) -> Optional[int]:
|
||||
"""Convert a value to int
|
||||
|
||||
Args:
|
||||
value (any): Value to convert to int if possible
|
||||
|
||||
Returns:
|
||||
int: Return the int value or None if not possible
|
||||
"""
|
||||
try:
|
||||
response = int(value)
|
||||
return response
|
||||
except ValueError:
|
||||
return None
|
||||
except TypeError:
|
||||
return None
|
||||
|
||||
def is_valid_ip(self, ip_to_control: str) -> bool:
|
||||
|
||||
try:
|
||||
if ip_to_control in self.Config.WHITELISTED_IP:
|
||||
@@ -732,18 +719,37 @@ class Base:
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
def decode_ip(self, ip_b64encoded: str) -> Union[str, None]:
|
||||
def is_valid_email(self, email_to_control: str) -> bool:
|
||||
"""Check if the email is valid
|
||||
|
||||
binary_ip = b64decode(ip_b64encoded)
|
||||
Args:
|
||||
email_to_control (str): email to control
|
||||
|
||||
Returns:
|
||||
bool: True is the email is correct
|
||||
"""
|
||||
try:
|
||||
pattern = '^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'
|
||||
if re.match(pattern, email_to_control):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
except Exception as err:
|
||||
self.logs.error(f'General Error: {err}')
|
||||
return False
|
||||
|
||||
def decode_ip(self, ip_b64encoded: str) -> Optional[str]:
|
||||
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) -> Union[str, None]:
|
||||
def encode_ip(self, remote_ip_address: str) -> Optional[str]:
|
||||
|
||||
binary_ip = socket.inet_aton(remote_ip_address)
|
||||
try:
|
||||
@@ -757,15 +763,6 @@ class Base:
|
||||
self.logs.critical(f'General Error: {err}')
|
||||
return None
|
||||
|
||||
def get_random(self, lenght:int) -> str:
|
||||
"""
|
||||
Retourn une chaîne aléatoire en fonction de la longueur spécifiée.
|
||||
"""
|
||||
caracteres = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
randomize = ''.join(random.choice(caracteres) for _ in range(lenght))
|
||||
|
||||
return randomize
|
||||
|
||||
def execute_periodic_action(self) -> None:
|
||||
|
||||
if not self.periodic_func:
|
||||
@@ -785,22 +782,23 @@ class Base:
|
||||
# Vider le dictionnaire de fonction
|
||||
self.periodic_func.clear()
|
||||
|
||||
def clean_uid(self, uid:str) -> Union[str, None]:
|
||||
"""Clean UID by removing @ / % / + / ~ / * / :
|
||||
def execute_dynamic_method(self, obj: object, method_name: str, params: list) -> None:
|
||||
"""#### Ajouter les méthodes a éxecuter dans un dictionnaire
|
||||
Les methodes seront exécuter par heartbeat.
|
||||
|
||||
Args:
|
||||
uid (str): The UID to clean
|
||||
obj (object): Une instance de la classe qui va etre executer
|
||||
method_name (str): Le nom de la méthode a executer
|
||||
params (list): les parametres a faire passer
|
||||
|
||||
Returns:
|
||||
str: Clean UID without any sign
|
||||
None: aucun retour attendu
|
||||
"""
|
||||
try:
|
||||
if uid is None:
|
||||
return None
|
||||
self.periodic_func[len(self.periodic_func) + 1] = {
|
||||
'object': obj,
|
||||
'method_name': method_name,
|
||||
'param': params
|
||||
}
|
||||
|
||||
pattern = fr'[:|@|%|\+|~|\*]*'
|
||||
parsed_UID = re.sub(pattern, '', uid)
|
||||
|
||||
return parsed_UID
|
||||
except TypeError as te:
|
||||
self.logs.error(f'Type Error: {te}')
|
||||
self.logs.debug(f'Method to execute : {str(self.periodic_func)}')
|
||||
return None
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
from typing import Union
|
||||
import core.definition as df
|
||||
from core.base import Base
|
||||
|
||||
|
||||
class Admin:
|
||||
|
||||
UID_ADMIN_DB: list[df.MAdmin] = []
|
||||
|
||||
def __init__(self, baseObj: Base) -> None:
|
||||
self.Logs = baseObj.logs
|
||||
pass
|
||||
|
||||
def insert(self, newAdmin: df.MAdmin) -> bool:
|
||||
|
||||
result = False
|
||||
exist = False
|
||||
|
||||
for record in self.UID_ADMIN_DB:
|
||||
if record.uid == newAdmin.uid:
|
||||
# If the admin exist then return False and do not go further
|
||||
exist = True
|
||||
self.Logs.debug(f'{record.uid} already exist')
|
||||
return result
|
||||
|
||||
if not exist:
|
||||
self.UID_ADMIN_DB.append(newAdmin)
|
||||
result = True
|
||||
self.Logs.debug(f'UID ({newAdmin.uid}) has been created')
|
||||
|
||||
if not result:
|
||||
self.Logs.critical(f'The User Object was not inserted {newAdmin}')
|
||||
|
||||
return result
|
||||
|
||||
def update_nickname(self, uid: str, newNickname: str) -> bool:
|
||||
|
||||
result = False
|
||||
|
||||
for record in self.UID_ADMIN_DB:
|
||||
if record.uid == uid:
|
||||
# If the admin exist, update and do not go further
|
||||
record.nickname = newNickname
|
||||
result = True
|
||||
self.Logs.debug(f'UID ({record.uid}) has been updated with new nickname {newNickname}')
|
||||
return result
|
||||
|
||||
if not result:
|
||||
self.Logs.critical(f'The new nickname {newNickname} was not updated, uid = {uid}')
|
||||
|
||||
return result
|
||||
|
||||
def update_level(self, nickname: str, newLevel: int) -> bool:
|
||||
|
||||
result = False
|
||||
|
||||
for record in self.UID_ADMIN_DB:
|
||||
if record.nickname == nickname:
|
||||
# If the admin exist, update and do not go further
|
||||
record.level = newLevel
|
||||
result = True
|
||||
self.Logs.debug(f'Admin ({record.nickname}) has been updated with new level {newLevel}')
|
||||
return result
|
||||
|
||||
if not result:
|
||||
self.Logs.critical(f'The new level {newLevel} was not updated, nickname = {nickname}')
|
||||
|
||||
return result
|
||||
|
||||
def delete(self, uidornickname: str) -> bool:
|
||||
|
||||
result = False
|
||||
|
||||
for record in self.UID_ADMIN_DB:
|
||||
if record.uid == uidornickname:
|
||||
# If the admin exist, delete and do not go further
|
||||
self.UID_ADMIN_DB.remove(record)
|
||||
result = True
|
||||
self.Logs.debug(f'UID ({record.uid}) has been deleted')
|
||||
return result
|
||||
if record.nickname == uidornickname:
|
||||
# If the admin exist, delete and do not go further
|
||||
self.UID_ADMIN_DB.remove(record)
|
||||
result = True
|
||||
self.Logs.debug(f'nickname ({record.nickname}) has been deleted')
|
||||
return result
|
||||
|
||||
if not result:
|
||||
self.Logs.critical(f'The UID {uidornickname} was not deleted')
|
||||
|
||||
return result
|
||||
|
||||
def get_Admin(self, uidornickname: str) -> Union[df.MAdmin, None]:
|
||||
|
||||
Admin = None
|
||||
for record in self.UID_ADMIN_DB:
|
||||
if record.uid == uidornickname:
|
||||
Admin = record
|
||||
elif record.nickname == uidornickname:
|
||||
Admin = record
|
||||
|
||||
#self.Logs.debug(f'Search {uidornickname} -- result = {Admin}')
|
||||
|
||||
return Admin
|
||||
|
||||
def get_uid(self, uidornickname:str) -> Union[str, None]:
|
||||
|
||||
uid = None
|
||||
for record in self.UID_ADMIN_DB:
|
||||
if record.uid == uidornickname:
|
||||
uid = record.uid
|
||||
if record.nickname == uidornickname:
|
||||
uid = record.uid
|
||||
|
||||
self.Logs.debug(f'The UID that you are looking for {uidornickname} has been found {uid}')
|
||||
return uid
|
||||
|
||||
def get_nickname(self, uidornickname:str) -> Union[str, None]:
|
||||
|
||||
nickname = None
|
||||
for record in self.UID_ADMIN_DB:
|
||||
if record.nickname == uidornickname:
|
||||
nickname = record.nickname
|
||||
if record.uid == uidornickname:
|
||||
nickname = record.nickname
|
||||
self.Logs.debug(f'The value {uidornickname} -- {nickname}')
|
||||
return nickname
|
||||
@@ -1,240 +0,0 @@
|
||||
from re import findall
|
||||
from typing import Union, Literal, TYPE_CHECKING
|
||||
from dataclasses import asdict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.definition import MChannel
|
||||
from core.base import Base
|
||||
|
||||
class Channel:
|
||||
|
||||
UID_CHANNEL_DB: list['MChannel'] = []
|
||||
"""List that contains all the Channels objects (ChannelModel)
|
||||
"""
|
||||
|
||||
def __init__(self, baseObj: 'Base') -> None:
|
||||
|
||||
self.Logs = baseObj.logs
|
||||
self.Base = baseObj
|
||||
|
||||
return None
|
||||
|
||||
def insert(self, newChan: 'MChannel') -> bool:
|
||||
"""This method will insert a new channel and if the channel exist it will update the user list (uids)
|
||||
|
||||
Args:
|
||||
newChan (ChannelModel): The channel model object
|
||||
|
||||
Returns:
|
||||
bool: True if new channel, False if channel exist (However UID could be updated)
|
||||
"""
|
||||
result = False
|
||||
exist = False
|
||||
|
||||
for record in self.UID_CHANNEL_DB:
|
||||
if record.name.lower() == newChan.name.lower():
|
||||
# If the channel exist, update the user list and do not go further
|
||||
exist = True
|
||||
# self.Logs.debug(f'{record.name} already exist')
|
||||
|
||||
for user in newChan.uids:
|
||||
record.uids.append(user)
|
||||
|
||||
# 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}')
|
||||
return result
|
||||
|
||||
if not exist:
|
||||
# If the channel don't exist, then create it
|
||||
newChan.name = newChan.name.lower()
|
||||
self.UID_CHANNEL_DB.append(newChan)
|
||||
result = True
|
||||
# self.Logs.debug(f'New Channel Created: ({newChan})')
|
||||
|
||||
if not result:
|
||||
self.Logs.critical(f'The Channel Object was not inserted {newChan}')
|
||||
|
||||
self.clean_channel()
|
||||
|
||||
return result
|
||||
|
||||
def delete(self, channel_name: str) -> bool:
|
||||
|
||||
chanObj = self.get_Channel(channel_name)
|
||||
|
||||
if chanObj is None:
|
||||
return False
|
||||
|
||||
self.UID_CHANNEL_DB.remove(chanObj)
|
||||
|
||||
return True
|
||||
|
||||
def delete_user_from_channel(self, channel_name: str, uid:str) -> bool:
|
||||
try:
|
||||
result = False
|
||||
|
||||
chanObj = self.get_Channel(channel_name.lower())
|
||||
|
||||
if chanObj is None:
|
||||
return result
|
||||
|
||||
for userid in chanObj.uids:
|
||||
if self.Base.clean_uid(userid) == self.Base.clean_uid(uid):
|
||||
chanObj.uids.remove(userid)
|
||||
result = True
|
||||
|
||||
self.clean_channel()
|
||||
|
||||
return result
|
||||
except ValueError as ve:
|
||||
self.Logs.error(f'{ve}')
|
||||
|
||||
def delete_user_from_all_channel(self, uid:str) -> bool:
|
||||
try:
|
||||
result = False
|
||||
|
||||
for record in self.UID_CHANNEL_DB:
|
||||
for user_id in record.uids:
|
||||
if self.Base.clean_uid(user_id) == self.Base.clean_uid(uid):
|
||||
record.uids.remove(user_id)
|
||||
# self.Logs.debug(f'The UID {uid} has been removed, here is the new object: {record}')
|
||||
result = True
|
||||
|
||||
self.clean_channel()
|
||||
|
||||
return result
|
||||
except ValueError as ve:
|
||||
self.Logs.error(f'{ve}')
|
||||
|
||||
def add_user_to_a_channel(self, channel_name: str, uid: str) -> bool:
|
||||
try:
|
||||
result = False
|
||||
chanObj = self.get_Channel(channel_name)
|
||||
self.Logs.debug(f"** {__name__}")
|
||||
|
||||
if chanObj is None:
|
||||
result = self.insert(MChannel(channel_name, uids=[uid]))
|
||||
# self.Logs.debug(f"** {__name__} - result: {result}")
|
||||
# self.Logs.debug(f'New Channel Created: ({chanObj})')
|
||||
return result
|
||||
|
||||
chanObj.uids.append(uid)
|
||||
del_duplicates = list(set(chanObj.uids))
|
||||
chanObj.uids = del_duplicates
|
||||
# self.Logs.debug(f'New Channel Created: ({chanObj})')
|
||||
|
||||
return True
|
||||
except Exception as err:
|
||||
self.Logs.error(f'{err}')
|
||||
|
||||
def clean_channel(self) -> None:
|
||||
"""Remove Channels if empty
|
||||
"""
|
||||
try:
|
||||
for record in self.UID_CHANNEL_DB:
|
||||
if not record.uids:
|
||||
self.UID_CHANNEL_DB.remove(record)
|
||||
# self.Logs.debug(f'The Channel {record.name} has been removed, here is the new object: {record}')
|
||||
return None
|
||||
except Exception as err:
|
||||
self.Logs.error(f'{err}')
|
||||
|
||||
def get_Channel(self, channel_name: str) -> Union['MChannel', None]:
|
||||
|
||||
Channel = None
|
||||
|
||||
for record in self.UID_CHANNEL_DB:
|
||||
if record.name == channel_name:
|
||||
Channel = record
|
||||
|
||||
return Channel
|
||||
|
||||
def get_Channel_AsDict(self, chan_name: str) -> Union[dict[str, any], None]:
|
||||
|
||||
chanObj = self.get_Channel(chan_name=chan_name)
|
||||
|
||||
if not chanObj is None:
|
||||
chan_as_dict = asdict(chanObj)
|
||||
return chan_as_dict
|
||||
else:
|
||||
return None
|
||||
|
||||
def Is_Channel(self, channelToCheck: str) -> bool:
|
||||
"""Check if the string has the # caractere and return True if this is a channel
|
||||
|
||||
Args:
|
||||
channelToCheck (str): The string to test if it is a channel or not
|
||||
|
||||
Returns:
|
||||
bool: True if the string is a channel / False if this is not a channel
|
||||
"""
|
||||
try:
|
||||
|
||||
if channelToCheck is None:
|
||||
return False
|
||||
|
||||
pattern = fr'^#'
|
||||
isChannel = findall(pattern, channelToCheck)
|
||||
|
||||
if not isChannel:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
except TypeError as te:
|
||||
self.Logs.error(f'TypeError: [{channelToCheck}] - {te}')
|
||||
except Exception as err:
|
||||
self.Logs.error(f'Error Not defined: {err}')
|
||||
|
||||
def db_query_channel(self, action: Literal['add','del'], module_name: str, channel_name: str) -> bool:
|
||||
"""You can add a channel or delete a channel.
|
||||
|
||||
Args:
|
||||
action (Literal['add','del']): Action on the database
|
||||
module_name (str): The module name (mod_test)
|
||||
channel_name (str): The channel name (With #)
|
||||
|
||||
Returns:
|
||||
bool: True if action done
|
||||
"""
|
||||
try:
|
||||
channel_name = channel_name.lower() if self.Is_Channel(channel_name) else None
|
||||
core_table = self.Base.Config.TABLE_CHANNEL
|
||||
|
||||
if not channel_name:
|
||||
self.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)
|
||||
isChannelExist = response.fetchone()
|
||||
|
||||
if isChannelExist is None:
|
||||
mes_donnees = {'datetime': self.Base.get_datetime(), 'channel_name': channel_name, 'module_name': module_name}
|
||||
insert = self.Base.db_execute_query(f"INSERT INTO {core_table} (datetime, channel_name, module_name) VALUES (:datetime, :channel_name, :module_name)", mes_donnees)
|
||||
if insert.rowcount:
|
||||
self.Logs.debug(f'New channel added: 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)
|
||||
|
||||
if response.rowcount > 0:
|
||||
self.Logs.debug(f'Channel deleted: channel={channel_name} / module: {module_name}')
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
case _:
|
||||
return False
|
||||
|
||||
except Exception as err:
|
||||
self.Logs.error(err)
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
from dataclasses import asdict
|
||||
from core.definition import MClone
|
||||
from typing import Union
|
||||
from core.base import Base
|
||||
|
||||
class Clone:
|
||||
|
||||
UID_CLONE_DB: list[MClone] = []
|
||||
|
||||
def __init__(self, baseObj: Base) -> None:
|
||||
|
||||
self.Logs = baseObj.logs
|
||||
|
||||
return None
|
||||
|
||||
def insert(self, newCloneObject: MClone) -> bool:
|
||||
"""Create new Clone object
|
||||
|
||||
Args:
|
||||
newCloneObject (CloneModel): New CloneModel object
|
||||
|
||||
Returns:
|
||||
bool: True if inserted
|
||||
"""
|
||||
result = False
|
||||
exist = False
|
||||
|
||||
for record in self.UID_CLONE_DB:
|
||||
if record.nickname == newCloneObject.nickname:
|
||||
# If the user exist then return False and do not go further
|
||||
exist = True
|
||||
self.Logs.warning(f'Nickname {record.nickname} already exist')
|
||||
return result
|
||||
if record.uid == newCloneObject.uid:
|
||||
exist = True
|
||||
self.Logs.warning(f'UID: {record.uid} already exist')
|
||||
return result
|
||||
|
||||
if not exist:
|
||||
self.UID_CLONE_DB.append(newCloneObject)
|
||||
result = True
|
||||
# self.Logs.debug(f'New Clone Object Created: ({newCloneObject})')
|
||||
|
||||
if not result:
|
||||
self.Logs.critical(f'The Clone Object was not inserted {newCloneObject}')
|
||||
|
||||
return result
|
||||
|
||||
def delete(self, uidornickname: str) -> bool:
|
||||
"""Delete the Clone Object starting from the nickname or the UID
|
||||
|
||||
Args:
|
||||
uidornickname (str): UID or nickname of the clone
|
||||
|
||||
Returns:
|
||||
bool: True if deleted
|
||||
"""
|
||||
|
||||
cloneObj = self.get_Clone(uidornickname=uidornickname)
|
||||
|
||||
if cloneObj is None:
|
||||
return False
|
||||
|
||||
self.UID_CLONE_DB.remove(cloneObj)
|
||||
|
||||
return True
|
||||
|
||||
def exists(self, nickname: str) -> bool:
|
||||
"""Check if the nickname exist
|
||||
|
||||
Args:
|
||||
nickname (str): Nickname of the clone
|
||||
|
||||
Returns:
|
||||
bool: True if the nickname exist
|
||||
"""
|
||||
response = False
|
||||
|
||||
for cloneObject in self.UID_CLONE_DB:
|
||||
if cloneObject.nickname == nickname:
|
||||
response = True
|
||||
|
||||
return response
|
||||
|
||||
def uid_exists(self, uid: str) -> bool:
|
||||
"""Check if the nickname exist
|
||||
|
||||
Args:
|
||||
uid (str): uid of the clone
|
||||
|
||||
Returns:
|
||||
bool: True if the nickname exist
|
||||
"""
|
||||
response = False
|
||||
|
||||
for cloneObject in self.UID_CLONE_DB:
|
||||
if cloneObject.uid == uid:
|
||||
response = True
|
||||
|
||||
return response
|
||||
|
||||
def get_Clone(self, uidornickname: str) -> Union[MClone, None]:
|
||||
"""Get MClone object or None
|
||||
|
||||
Args:
|
||||
uidornickname (str): The UID or the Nickname
|
||||
|
||||
Returns:
|
||||
Union[MClone, None]: Return MClone object or None
|
||||
"""
|
||||
cloneObj = None
|
||||
|
||||
for clone in self.UID_CLONE_DB:
|
||||
if clone.uid == uidornickname:
|
||||
cloneObj = clone
|
||||
if clone.nickname == uidornickname:
|
||||
cloneObj = clone
|
||||
|
||||
return cloneObj
|
||||
|
||||
def get_uid(self, uidornickname: str) -> Union[str, None]:
|
||||
"""Get the UID of the clone starting from the UID or the Nickname
|
||||
|
||||
Args:
|
||||
uidornickname (str): UID or Nickname
|
||||
|
||||
Returns:
|
||||
str|None: Return the UID
|
||||
"""
|
||||
uid = None
|
||||
for record in self.UID_CLONE_DB:
|
||||
if record.uid == uidornickname:
|
||||
uid = record.uid
|
||||
if record.nickname == uidornickname:
|
||||
uid = record.uid
|
||||
|
||||
# if not uid is None:
|
||||
# self.Logs.debug(f'The UID that you are looking for {uidornickname} has been found {uid}')
|
||||
|
||||
return uid
|
||||
|
||||
def get_Clone_AsDict(self, uidornickname: str) -> Union[dict[str, any], None]:
|
||||
|
||||
cloneObj = self.get_Clone(uidornickname=uidornickname)
|
||||
|
||||
if not cloneObj is None:
|
||||
cloneObj_as_dict = asdict(cloneObj)
|
||||
return cloneObj_as_dict
|
||||
else:
|
||||
return None
|
||||
|
||||
def kill(self, nickname:str) -> bool:
|
||||
|
||||
response = False
|
||||
|
||||
for cloneObject in self.UID_CLONE_DB:
|
||||
if cloneObject.nickname == nickname:
|
||||
cloneObject.alive = False # Kill the clone
|
||||
response = True
|
||||
|
||||
return response
|
||||
@@ -1,57 +0,0 @@
|
||||
from json import load
|
||||
from sys import exit
|
||||
from os import sep
|
||||
from typing import Union
|
||||
from core.definition import MConfig
|
||||
|
||||
|
||||
|
||||
class Configuration:
|
||||
|
||||
def __init__(self) -> None:
|
||||
|
||||
self.ConfigObject: MConfig = self.__load_service_configuration()
|
||||
return None
|
||||
|
||||
def __load_json_service_configuration(self):
|
||||
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:
|
||||
print(f'FileNotFound: {fe}')
|
||||
print('Configuration file not found please create config/configuration.json')
|
||||
exit(0)
|
||||
except KeyError as ke:
|
||||
print(f'Key Error: {ke}')
|
||||
print('The key must be defined in core/configuration.json')
|
||||
|
||||
def __load_service_configuration(self) -> MConfig:
|
||||
try:
|
||||
import_config = self.__load_json_service_configuration()
|
||||
|
||||
Model_keys = MConfig().__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)
|
||||
print(f"\!/ The key {json_conf} is not expected, it has been removed from the system ! please remove it from configuration.json file \!/")
|
||||
|
||||
ConfigObject: MConfig = MConfig(
|
||||
**import_config
|
||||
)
|
||||
|
||||
return ConfigObject
|
||||
except TypeError as te:
|
||||
print(te)
|
||||
84
core/classes/interfaces/imodule.py
Normal file
84
core/classes/interfaces/imodule.py
Normal 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
|
||||
async 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 [].
|
||||
"""
|
||||
563
core/classes/interfaces/iprotocol.py
Normal file
563
core/classes/interfaces/iprotocol.py
Normal file
@@ -0,0 +1,563 @@
|
||||
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 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) -> None:
|
||||
"""Logout a client from his account
|
||||
"""
|
||||
|
||||
@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, geoip: 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
|
||||
geoip (str): The country code 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_
|
||||
"""
|
||||
36
core/classes/interfaces/irpc_endpoint.py
Normal file
36
core/classes/interfaces/irpc_endpoint.py
Normal 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
|
||||
218
core/classes/modules/admin.py
Normal file
218
core/classes/modules/admin.py
Normal file
@@ -0,0 +1,218 @@
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from core.definition import MAdmin
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.loader import Loader
|
||||
|
||||
class Admin:
|
||||
|
||||
UID_ADMIN_DB: list[MAdmin] = []
|
||||
|
||||
def __init__(self, loader: 'Loader') -> None:
|
||||
"""
|
||||
|
||||
Args:
|
||||
loader (Loader): The Loader Instance.
|
||||
"""
|
||||
self._ctx = loader
|
||||
|
||||
def insert(self, new_admin: MAdmin) -> bool:
|
||||
"""Insert a new admin object model
|
||||
|
||||
Args:
|
||||
new_admin (MAdmin): The new admin object model to insert
|
||||
|
||||
Returns:
|
||||
bool: True if it was inserted
|
||||
"""
|
||||
|
||||
for record in self.UID_ADMIN_DB:
|
||||
if record.uid == new_admin.uid:
|
||||
self._ctx.Logs.debug(f'{record.uid} already exist')
|
||||
return False
|
||||
|
||||
self.UID_ADMIN_DB.append(new_admin)
|
||||
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:
|
||||
"""Update nickname of an admin
|
||||
|
||||
Args:
|
||||
uid (str): The Admin UID
|
||||
new_admin_nickname (str): The new nickname of the admin
|
||||
|
||||
Returns:
|
||||
bool: True if the nickname has been updated.
|
||||
"""
|
||||
|
||||
for record in self.UID_ADMIN_DB:
|
||||
if record.uid == uid:
|
||||
# If the admin exist, update and do not go further
|
||||
record.nickname = new_admin_nickname
|
||||
self._ctx.Logs.debug(f'UID ({record.uid}) has been updated with new nickname {new_admin_nickname}')
|
||||
return True
|
||||
|
||||
|
||||
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:
|
||||
"""Update the admin level
|
||||
|
||||
Args:
|
||||
nickname (str): The admin nickname
|
||||
new_admin_level (int): The new level of the admin
|
||||
|
||||
Returns:
|
||||
bool: True if the admin level has been updated
|
||||
"""
|
||||
admin_obj = self.get_admin(nickname)
|
||||
if admin_obj:
|
||||
# If the admin exist, update and do not go further
|
||||
admin_obj.level = new_admin_level
|
||||
self._ctx.Logs.debug(f'Admin ({admin_obj.nickname}) has been updated with new level {new_admin_level}')
|
||||
return True
|
||||
|
||||
self._ctx.Logs.debug(f'The new level {new_admin_level} was not updated in local variable, nickname = {nickname} is not logged in')
|
||||
|
||||
return False
|
||||
|
||||
def delete(self, uidornickname: str) -> bool:
|
||||
"""Delete admin
|
||||
|
||||
Args:
|
||||
uidornickname (str): The UID or nickname of the admin
|
||||
|
||||
Returns:
|
||||
bool: True if the admin has been deleted
|
||||
"""
|
||||
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
|
||||
|
||||
self._ctx.Logs.debug(f'The UID {uidornickname} was not deleted from the local variable (admin not connected)')
|
||||
|
||||
return False
|
||||
|
||||
def get_admin(self, uidornickname: str) -> Optional[MAdmin]:
|
||||
"""Get the admin object model
|
||||
|
||||
Args:
|
||||
uidornickname (str): UID or Nickname of the admin
|
||||
|
||||
Returns:
|
||||
Optional[MAdmin]: The MAdmin object model if exist
|
||||
"""
|
||||
|
||||
for record in self.UID_ADMIN_DB:
|
||||
if record.uid == uidornickname:
|
||||
return record
|
||||
elif record.nickname.lower() == uidornickname.lower():
|
||||
return record
|
||||
|
||||
return None
|
||||
|
||||
def get_uid(self, uidornickname:str) -> Optional[str]:
|
||||
"""Get the UID of the admin
|
||||
|
||||
Args:
|
||||
uidornickname (str): The UID or nickname of the admin
|
||||
|
||||
Returns:
|
||||
Optional[str]: The UID of the admin
|
||||
"""
|
||||
|
||||
for record in self.UID_ADMIN_DB:
|
||||
if record.uid == uidornickname:
|
||||
return record.uid
|
||||
if record.nickname.lower() == uidornickname.lower():
|
||||
return record.uid
|
||||
|
||||
return None
|
||||
|
||||
def get_nickname(self, uidornickname:str) -> Optional[str]:
|
||||
"""Get the nickname of the admin
|
||||
|
||||
Args:
|
||||
uidornickname (str): The UID or the nickname of the admin
|
||||
|
||||
Returns:
|
||||
Optional[str]: The nickname of the admin
|
||||
"""
|
||||
|
||||
for record in self.UID_ADMIN_DB:
|
||||
if record.nickname.lower() == uidornickname.lower():
|
||||
return record.nickname
|
||||
if record.uid == uidornickname:
|
||||
return record.nickname
|
||||
|
||||
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
|
||||
302
core/classes/modules/channel.py
Normal file
302
core/classes/modules/channel.py
Normal file
@@ -0,0 +1,302 @@
|
||||
from re import findall
|
||||
from typing import Any, Optional, Literal, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.definition import MChannel
|
||||
from core.loader import Loader
|
||||
|
||||
class Channel:
|
||||
|
||||
UID_CHANNEL_DB: list['MChannel'] = []
|
||||
"""List that contains all the Channels objects (ChannelModel)
|
||||
"""
|
||||
|
||||
def __init__(self, loader: 'Loader'):
|
||||
"""
|
||||
|
||||
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)
|
||||
|
||||
Args:
|
||||
new_channel (MChannel): The channel model object
|
||||
|
||||
Returns:
|
||||
bool: True if new channel, False if channel exist (However UID could be updated)
|
||||
"""
|
||||
result = False
|
||||
exist = False
|
||||
|
||||
if not self.is_valid_channel(new_channel.name):
|
||||
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._ctx.Logs.debug(f'{record.name} already exist')
|
||||
|
||||
for user in new_channel.uids:
|
||||
record.uids.append(user)
|
||||
|
||||
# Supprimer les doublons
|
||||
del_duplicates = list(set(record.uids))
|
||||
record.uids = del_duplicates
|
||||
# self._ctx.Logs.debug(f'Updating a new UID to the channel {record}')
|
||||
return result
|
||||
|
||||
if not exist:
|
||||
# If the channel don't exist, then create it
|
||||
new_channel.name = new_channel.name.lower()
|
||||
self.UID_CHANNEL_DB.append(new_channel)
|
||||
result = True
|
||||
# self._ctx.Logs.debug(f'New Channel Created: ({new_channel})')
|
||||
|
||||
if not result:
|
||||
self._ctx.Logs.critical(f'The Channel Object was not inserted {new_channel}')
|
||||
|
||||
self.clean_channel()
|
||||
|
||||
return result
|
||||
|
||||
def delete(self, channel_name: str) -> bool:
|
||||
"""Delete channel from the UID_CHANNEL_DB
|
||||
|
||||
Args:
|
||||
channel_name (str): The Channel name
|
||||
|
||||
Returns:
|
||||
bool: True if it was deleted
|
||||
"""
|
||||
|
||||
chan_obj = self.get_channel(channel_name)
|
||||
|
||||
if chan_obj is None:
|
||||
return False
|
||||
|
||||
self.UID_CHANNEL_DB.remove(chan_obj)
|
||||
|
||||
return True
|
||||
|
||||
def delete_user_from_channel(self, channel_name: str, uid: str) -> bool:
|
||||
"""Delete a user from a channel
|
||||
|
||||
Args:
|
||||
channel_name (str): The channel name
|
||||
uid (str): The Client UID
|
||||
|
||||
Returns:
|
||||
bool: True if the client has been deleted from the channel
|
||||
"""
|
||||
try:
|
||||
result = False
|
||||
chan_obj = self.get_channel(channel_name)
|
||||
|
||||
if chan_obj is None:
|
||||
return result
|
||||
|
||||
for userid in chan_obj.uids:
|
||||
if self._ctx.Utils.clean_uid(userid) == self._ctx.Utils.clean_uid(uid):
|
||||
chan_obj.uids.remove(userid)
|
||||
result = True
|
||||
|
||||
self.clean_channel()
|
||||
|
||||
return result
|
||||
except ValueError as 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
|
||||
|
||||
Args:
|
||||
uid (str): The client UID
|
||||
|
||||
Returns:
|
||||
bool: True if the client has been deleted from all channels
|
||||
"""
|
||||
try:
|
||||
result = False
|
||||
|
||||
for record in self.UID_CHANNEL_DB:
|
||||
for user_id in record.uids:
|
||||
if self._ctx.Utils.clean_uid(user_id) == self._ctx.Utils.clean_uid(uid):
|
||||
record.uids.remove(user_id)
|
||||
result = True
|
||||
|
||||
self.clean_channel()
|
||||
|
||||
return result
|
||||
except ValueError as 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
|
||||
|
||||
Args:
|
||||
channel_name (str): The channel name
|
||||
uid (str): The client UID
|
||||
|
||||
Returns:
|
||||
bool: True is the clien has been added
|
||||
"""
|
||||
try:
|
||||
chan_obj = self.get_channel(channel_name)
|
||||
|
||||
if chan_obj is None:
|
||||
# Create a new channel if the channel don't exist
|
||||
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)
|
||||
del_duplicates = list(set(chan_obj.uids))
|
||||
chan_obj.uids = del_duplicates
|
||||
|
||||
return True
|
||||
except Exception as err:
|
||||
self._ctx.Logs.error(f'{err}')
|
||||
return False
|
||||
|
||||
def is_user_present_in_channel(self, channel_name: str, uid: str) -> bool:
|
||||
"""Check if a user is present in the channel
|
||||
|
||||
Args:
|
||||
channel_name (str): The channel name to check
|
||||
uid (str): The client UID
|
||||
|
||||
Returns:
|
||||
bool: True if the user is present in the channel
|
||||
"""
|
||||
chan = self.get_channel(channel_name=channel_name)
|
||||
if chan is None:
|
||||
return False
|
||||
|
||||
clean_uid = self._ctx.Utils.clean_uid(uid=uid)
|
||||
for chan_uid in chan.uids:
|
||||
if self._ctx.Utils.clean_uid(chan_uid) == clean_uid:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def clean_channel(self) -> None:
|
||||
"""If channel doesn't contain any client this method will remove the channel
|
||||
"""
|
||||
try:
|
||||
for record in self.UID_CHANNEL_DB:
|
||||
if not record.uids:
|
||||
self.UID_CHANNEL_DB.remove(record)
|
||||
|
||||
return None
|
||||
except Exception as err:
|
||||
self._ctx.Logs.error(f'{err}')
|
||||
|
||||
def get_channel(self, channel_name: str) -> Optional['MChannel']:
|
||||
"""Get the channel object
|
||||
|
||||
Args:
|
||||
channel_name (str): The Channel name
|
||||
|
||||
Returns:
|
||||
MChannel: The channel object model if exist else None
|
||||
"""
|
||||
|
||||
for record in self.UID_CHANNEL_DB:
|
||||
if record.name.lower() == channel_name.lower():
|
||||
return record
|
||||
|
||||
return None
|
||||
|
||||
def is_valid_channel(self, channel_to_check: str) -> bool:
|
||||
"""Check if the string has the # caractere and return True if this is a valid channel
|
||||
|
||||
Args:
|
||||
channel_to_check (str): The string to test if it is a channel or not
|
||||
|
||||
Returns:
|
||||
bool: True if the string is a channel / False if this is not a channel
|
||||
"""
|
||||
try:
|
||||
|
||||
if channel_to_check is None:
|
||||
return False
|
||||
|
||||
pattern = fr'^#'
|
||||
is_channel = findall(pattern, channel_to_check)
|
||||
|
||||
if not is_channel:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
except TypeError as te:
|
||||
self._ctx.Logs.error(f'TypeError: [{channel_to_check}] - {te}')
|
||||
return False
|
||||
except Exception as err:
|
||||
self._ctx.Logs.error(f'Error Not defined: {err}')
|
||||
return False
|
||||
|
||||
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:
|
||||
action (Literal['add','del']): Action on the database
|
||||
module_name (str): The module name (mod_test)
|
||||
channel_name (str): The channel name (With #)
|
||||
|
||||
Returns:
|
||||
bool: True if action done
|
||||
"""
|
||||
try:
|
||||
channel_name = channel_name.lower() if self.is_valid_channel(channel_name) else None
|
||||
core_table = self._ctx.Base.Config.TABLE_CHANNEL
|
||||
|
||||
if not channel_name:
|
||||
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 = 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._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._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 = 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._ctx.Logs.debug(f'Channel deleted from DB: channel={channel_name} / module: {module_name}')
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
except Exception as 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)
|
||||
119
core/classes/modules/commands.py
Normal file
119
core/classes/modules/commands.py
Normal file
@@ -0,0 +1,119 @@
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from core.definition import MCommand
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.loader import Loader
|
||||
|
||||
class Command:
|
||||
|
||||
DB_COMMANDS: list['MCommand'] = []
|
||||
|
||||
def __init__(self, loader: 'Loader'):
|
||||
"""
|
||||
Args:
|
||||
loader (Loader): The Loader instance.
|
||||
"""
|
||||
self._ctx = loader
|
||||
|
||||
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:
|
||||
self.DB_COMMANDS.append(new_command_obj)
|
||||
return True
|
||||
|
||||
# Update command if it exist
|
||||
# Removing the object
|
||||
if self.drop_command(command.command_name, command.module_name):
|
||||
# Add the new object
|
||||
self.DB_COMMANDS.append(new_command_obj)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def get_command(self, command_name: str, module_name: str) -> Optional[MCommand]:
|
||||
|
||||
for command in self.DB_COMMANDS:
|
||||
if command.command_name.lower() == command_name.lower() and command.module_name.lower() == module_name.lower():
|
||||
return command
|
||||
|
||||
return None
|
||||
|
||||
def drop_command(self, command_name: str, module_name: str) -> bool:
|
||||
|
||||
cmd = self.get_command(command_name, module_name)
|
||||
if cmd is not None:
|
||||
self.DB_COMMANDS.remove(cmd)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def drop_command_by_module(self, module_name: str) -> bool:
|
||||
"""Drop all command by module
|
||||
|
||||
Args:
|
||||
module_name (str): The module name
|
||||
|
||||
Returns:
|
||||
bool: True
|
||||
"""
|
||||
tmp_model: list[MCommand] = []
|
||||
|
||||
for command in self.DB_COMMANDS:
|
||||
if command.module_name.lower() == module_name.lower():
|
||||
tmp_model.append(command)
|
||||
|
||||
for c in tmp_model:
|
||||
self.DB_COMMANDS.remove(c)
|
||||
|
||||
self._ctx.Logs.debug(f"[COMMAND] Drop command for module {module_name}")
|
||||
return True
|
||||
|
||||
def get_ordered_commands(self) -> list[MCommand]:
|
||||
return sorted(self.DB_COMMANDS, key=lambda c: (c.command_level, c.module_name))
|
||||
|
||||
def get_commands_by_level(self, level: int = 0) -> Optional[list[MCommand]]:
|
||||
|
||||
cmd_list = self.get_ordered_commands()
|
||||
new_list: list[MCommand] = []
|
||||
|
||||
for cmd in cmd_list:
|
||||
if cmd.command_level <= level:
|
||||
new_list.append(cmd)
|
||||
|
||||
return new_list
|
||||
|
||||
def is_client_allowed_to_run_command(self, nickname: str, command_name: str) -> bool:
|
||||
admin = self._ctx.Admin.get_admin(nickname)
|
||||
admin_level = admin.level if admin else 0
|
||||
commands = self.get_commands_by_level(admin_level)
|
||||
|
||||
if command_name.lower() in [command.command_name.lower() for command in commands]:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def is_command_exist(self, command_name: str) -> bool:
|
||||
"""Check if the command name exist
|
||||
|
||||
Args:
|
||||
command_name (str): The command name you want to check
|
||||
|
||||
Returns:
|
||||
bool: True if the command exist
|
||||
"""
|
||||
if command_name.lower() in [command.command_name.lower() for command in self.get_ordered_commands()]:
|
||||
return True
|
||||
return False
|
||||
60
core/classes/modules/config.py
Normal file
60
core/classes/modules/config.py
Normal 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)
|
||||
250
core/classes/modules/rehash.py
Normal file
250
core/classes/modules/rehash.py
Normal file
@@ -0,0 +1,250 @@
|
||||
import asyncio
|
||||
import importlib
|
||||
import sys
|
||||
import threading
|
||||
from typing import TYPE_CHECKING
|
||||
import core.module as module_mod
|
||||
from core.classes.modules import user, admin, channel, reputation, sasl
|
||||
from core.utils import tr
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.loader import Loader
|
||||
|
||||
# Modules impacted by rehashing!
|
||||
REHASH_MODULES = [
|
||||
'core.definition',
|
||||
'core.utils',
|
||||
'core.base',
|
||||
'core.module',
|
||||
'core.classes.modules.config',
|
||||
'core.classes.modules.commands',
|
||||
'core.classes.modules.user',
|
||||
'core.classes.modules.admin',
|
||||
'core.classes.modules.channel',
|
||||
'core.classes.modules.reputation',
|
||||
'core.classes.modules.sasl',
|
||||
'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.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)
|
||||
|
||||
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('commands', uplink.Commands.DB_COMMANDS)
|
||||
uplink.Settings.set_cache('users', uplink.User.UID_DB)
|
||||
uplink.Settings.set_cache('admins', uplink.Admin.UID_ADMIN_DB)
|
||||
uplink.Settings.set_cache('reputations', uplink.Reputation.UID_REPUTATION_DB)
|
||||
uplink.Settings.set_cache('channels', uplink.Channel.UID_CHANNEL_DB)
|
||||
uplink.Settings.set_cache('sasl', uplink.Sasl.DB_SASL)
|
||||
uplink.Settings.set_cache('modules', uplink.ModuleUtils.DB_MODULES)
|
||||
uplink.Settings.set_cache('module_headers', uplink.ModuleUtils.DB_MODULE_HEADERS)
|
||||
|
||||
await uplink.RpcServer.stop_rpc_server()
|
||||
|
||||
restart_flag = False
|
||||
config_model_bakcup = uplink.Config
|
||||
mods = REHASH_MODULES
|
||||
_count_reloaded_modules = len(mods)
|
||||
for mod in mods:
|
||||
importlib.reload(sys.modules[mod])
|
||||
|
||||
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('commands')
|
||||
uplink.Base = uplink.BaseModule.Base(uplink)
|
||||
|
||||
uplink.User = user.User(uplink)
|
||||
uplink.Admin = admin.Admin(uplink)
|
||||
uplink.Channel = channel.Channel(uplink)
|
||||
uplink.Reputation = reputation.Reputation(uplink)
|
||||
uplink.ModuleUtils = module_mod.Module(uplink)
|
||||
uplink.Sasl = sasl.Sasl(uplink)
|
||||
|
||||
# Backup data
|
||||
uplink.User.UID_DB = uplink.Settings.get_cache('users')
|
||||
uplink.Admin.UID_ADMIN_DB = uplink.Settings.get_cache('admins')
|
||||
uplink.Channel.UID_CHANNEL_DB = uplink.Settings.get_cache('channels')
|
||||
uplink.Reputation.UID_REPUTATION_DB = uplink.Settings.get_cache('reputations')
|
||||
uplink.Sasl.DB_SASL = uplink.Settings.get_cache('sasl')
|
||||
uplink.ModuleUtils.DB_MODULE_HEADERS = uplink.Settings.get_cache('module_headers')
|
||||
uplink.ModuleUtils.DB_MODULES = uplink.Settings.get_cache('modules')
|
||||
|
||||
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)
|
||||
|
||||
color_green = uplink.Config.COLORS.green
|
||||
color_reset = uplink.Config.COLORS.nogc
|
||||
|
||||
await uplink.Irc.Protocol.send_priv_msg(
|
||||
uplink.Config.SERVICE_NICKNAME,
|
||||
tr("[ %sREHASH INFO%s ] Rehash completed! %s modules reloaded.", color_green, color_reset, _count_reloaded_modules),
|
||||
uplink.Config.SERVICE_CHANLOG
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
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())}")
|
||||
|
||||
# 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 if isinstance(th.thread_event, threading.Event)]
|
||||
|
||||
uplink.Logs.debug(f"=======> Closing all IO TASKS!")
|
||||
t = None
|
||||
for task in uplink.Base.running_iotasks:
|
||||
if 'force_shutdown' == task.get_name():
|
||||
t = task
|
||||
if t:
|
||||
uplink.Base.running_iotasks.remove(t)
|
||||
|
||||
task_already_canceled: list = []
|
||||
for task in uplink.Base.running_iotasks:
|
||||
try:
|
||||
if not task.cancel():
|
||||
print(task.get_name())
|
||||
task_already_canceled.append(task)
|
||||
except asyncio.exceptions.CancelledError as cerr:
|
||||
uplink.Logs.debug(f"Asyncio CancelledError reached! {task}")
|
||||
|
||||
for task in task_already_canceled:
|
||||
uplink.Base.running_iotasks.remove(task)
|
||||
|
||||
for task in uplink.Base.running_iotasks:
|
||||
try:
|
||||
await asyncio.wait_for(asyncio.gather(task), timeout=5)
|
||||
except asyncio.exceptions.TimeoutError as te:
|
||||
uplink.Logs.debug(f"Asyncio Timeout reached! {te} {task}")
|
||||
for task in uplink.Base.running_iotasks:
|
||||
task.cancel()
|
||||
except asyncio.exceptions.CancelledError as cerr:
|
||||
uplink.Logs.debug(f"Asyncio CancelledError reached! {cerr} {task}")
|
||||
|
||||
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
|
||||
|
||||
async def force_shutdown(uplink: 'Loader') -> None:
|
||||
await asyncio.sleep(10)
|
||||
uplink.Logs.critical("The system has been killed because something is blocking the loop")
|
||||
uplink.Logs.critical(asyncio.all_tasks())
|
||||
sys.exit('The system has been killed')
|
||||
@@ -1,23 +1,26 @@
|
||||
from typing import Union
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from core.definition import MReputation
|
||||
from core.base import Base
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.loader import Loader
|
||||
|
||||
class Reputation:
|
||||
|
||||
UID_REPUTATION_DB: list[MReputation] = []
|
||||
|
||||
def __init__(self, baseObj: Base) -> None:
|
||||
def __init__(self, loader: 'Loader'):
|
||||
"""
|
||||
|
||||
self.Logs = baseObj.logs
|
||||
self.MReputation: MReputation = MReputation
|
||||
Args:
|
||||
loader (Loader): The Loader instance.
|
||||
"""
|
||||
self._ctx = loader
|
||||
|
||||
return None
|
||||
|
||||
def insert(self, newReputationUser: MReputation) -> bool:
|
||||
def insert(self, new_reputation_user: MReputation) -> bool:
|
||||
"""Insert a new Reputation User object
|
||||
|
||||
Args:
|
||||
newReputationUser (MReputation): New Reputation Model object
|
||||
new_reputation_user (MReputation): New Reputation Model object
|
||||
|
||||
Returns:
|
||||
bool: True if inserted
|
||||
@@ -26,39 +29,39 @@ class Reputation:
|
||||
exist = False
|
||||
|
||||
for record in self.UID_REPUTATION_DB:
|
||||
if record.uid == newReputationUser.uid:
|
||||
if record.uid == new_reputation_user.uid:
|
||||
# If the user exist then return False and do not go further
|
||||
exist = True
|
||||
self.Logs.debug(f'{record.uid} already exist')
|
||||
self._ctx.Logs.debug(f'{record.uid} already exist')
|
||||
return result
|
||||
|
||||
if not exist:
|
||||
self.UID_REPUTATION_DB.append(newReputationUser)
|
||||
self.UID_REPUTATION_DB.append(new_reputation_user)
|
||||
result = True
|
||||
self.Logs.debug(f'New Reputation User Captured: ({newReputationUser})')
|
||||
self._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 {newReputationUser}')
|
||||
self._ctx.Logs.critical(f'The Reputation User Object was not inserted {new_reputation_user}')
|
||||
|
||||
return result
|
||||
|
||||
def update(self, uid: str, newNickname: str) -> bool:
|
||||
def update(self, uid: str, new_nickname: str) -> bool:
|
||||
"""Update the nickname starting from the UID
|
||||
|
||||
Args:
|
||||
uid (str): UID of the user
|
||||
newNickname (str): New nickname
|
||||
new_nickname (str): New nickname
|
||||
|
||||
Returns:
|
||||
bool: True if updated
|
||||
"""
|
||||
|
||||
reputationObj = self.get_Reputation(uid)
|
||||
reputation_obj = self.get_reputation(uid)
|
||||
|
||||
if reputationObj is None:
|
||||
if reputation_obj is None:
|
||||
return False
|
||||
|
||||
reputationObj.nickname = newNickname
|
||||
reputation_obj.nickname = new_nickname
|
||||
|
||||
return True
|
||||
|
||||
@@ -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) -> Union[MReputation, None]:
|
||||
def get_reputation(self, uidornickname: str) -> Optional[MReputation]:
|
||||
"""Get The User Object model
|
||||
|
||||
Args:
|
||||
@@ -98,16 +101,15 @@ class Reputation:
|
||||
Returns:
|
||||
UserModel|None: The UserModel Object | None
|
||||
"""
|
||||
User = None
|
||||
for record in self.UID_REPUTATION_DB:
|
||||
if record.uid == uidornickname:
|
||||
User = record
|
||||
return record
|
||||
elif record.nickname == uidornickname:
|
||||
User = record
|
||||
return record
|
||||
|
||||
return User
|
||||
return None
|
||||
|
||||
def get_uid(self, uidornickname:str) -> Union[str, None]:
|
||||
def get_uid(self, uidornickname: str) -> Optional[str]:
|
||||
"""Get the UID of the user starting from the UID or the Nickname
|
||||
|
||||
Args:
|
||||
@@ -117,14 +119,14 @@ class Reputation:
|
||||
str|None: Return the UID
|
||||
"""
|
||||
|
||||
reputationObj = self.get_Reputation(uidornickname)
|
||||
reputation_obj = self.get_reputation(uidornickname)
|
||||
|
||||
if reputationObj is None:
|
||||
if reputation_obj is None:
|
||||
return None
|
||||
|
||||
return reputationObj.uid
|
||||
return reputation_obj.uid
|
||||
|
||||
def get_nickname(self, uidornickname:str) -> Union[str, None]:
|
||||
def get_nickname(self, uidornickname: str) -> Optional[str]:
|
||||
"""Get the Nickname starting from UID or the nickname
|
||||
|
||||
Args:
|
||||
@@ -133,12 +135,12 @@ class Reputation:
|
||||
Returns:
|
||||
str|None: the nickname
|
||||
"""
|
||||
reputationObj = self.get_Reputation(uidornickname)
|
||||
reputation_obj = self.get_reputation(uidornickname)
|
||||
|
||||
if reputationObj is None:
|
||||
if reputation_obj is None:
|
||||
return None
|
||||
|
||||
return reputationObj.nickname
|
||||
return reputation_obj.nickname
|
||||
|
||||
def is_exist(self, uidornickname: str) -> bool:
|
||||
"""Check if the UID or the nickname exist in the reputation DB
|
||||
@@ -150,9 +152,9 @@ class Reputation:
|
||||
bool: True if exist
|
||||
"""
|
||||
|
||||
reputationObj = self.get_Reputation(uidornickname)
|
||||
reputation_obj = self.get_reputation(uidornickname)
|
||||
|
||||
if reputationObj is None:
|
||||
return False
|
||||
else:
|
||||
if isinstance(reputation_obj, MReputation):
|
||||
return True
|
||||
|
||||
return False
|
||||
2
core/classes/modules/rpc/__init__.py
Normal file
2
core/classes/modules/rpc/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
__version__ = '1.0.0'
|
||||
__all__ = ['start_rpc_server', 'stop_rpc_server']
|
||||
140
core/classes/modules/rpc/rpc.py
Normal file
140
core/classes/modules/rpc/rpc.py
Normal 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)
|
||||
16
core/classes/modules/rpc/rpc_channel.py
Normal file
16
core/classes/modules/rpc/rpc_channel.py
Normal 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)
|
||||
44
core/classes/modules/rpc/rpc_command.py
Normal file
44
core/classes/modules/rpc/rpc_command.py
Normal 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)
|
||||
43
core/classes/modules/rpc/rpc_errors.py
Normal file
43
core/classes/modules/rpc/rpc_errors.py
Normal 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
|
||||
45
core/classes/modules/rpc/rpc_user.py
Normal file
45
core/classes/modules/rpc/rpc_user.py
Normal 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)
|
||||
75
core/classes/modules/sasl.py
Normal file
75
core/classes/modules/sasl.py
Normal file
@@ -0,0 +1,75 @@
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.definition import MSasl
|
||||
from core.loader import Loader
|
||||
|
||||
class Sasl:
|
||||
|
||||
DB_SASL: list['MSasl'] = []
|
||||
|
||||
def __init__(self, loader: 'Loader'):
|
||||
"""
|
||||
|
||||
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:
|
||||
psasl (MSasl): New userModel object
|
||||
|
||||
Returns:
|
||||
bool: True if inserted
|
||||
"""
|
||||
|
||||
if psasl is None:
|
||||
return False
|
||||
|
||||
sasl_obj = self.get_sasl_obj(psasl.client_uid)
|
||||
|
||||
if sasl_obj is not None:
|
||||
# User already created return False
|
||||
return False
|
||||
|
||||
self.DB_SASL.append(psasl)
|
||||
|
||||
return True
|
||||
|
||||
def delete_sasl_client(self, client_uid: str) -> bool:
|
||||
"""Delete the User starting from the UID
|
||||
|
||||
Args:
|
||||
client_uid (str): UID of the user
|
||||
|
||||
Returns:
|
||||
bool: True if deleted
|
||||
"""
|
||||
|
||||
sasl_obj = self.get_sasl_obj(client_uid)
|
||||
|
||||
if sasl_obj is None:
|
||||
return False
|
||||
|
||||
self.DB_SASL.remove(sasl_obj)
|
||||
|
||||
return True
|
||||
|
||||
def get_sasl_obj(self, client_uid: str) -> Optional['MSasl']:
|
||||
"""Get sasl client Object model
|
||||
|
||||
Args:
|
||||
client_uid (str): UID of the client
|
||||
|
||||
Returns:
|
||||
UserModel|None: The SASL Object | None
|
||||
"""
|
||||
|
||||
for record in self.DB_SASL:
|
||||
if record.client_uid == client_uid:
|
||||
return record
|
||||
|
||||
return None
|
||||
133
core/classes/modules/settings.py
Normal file
133
core/classes/modules/settings.py
Normal file
@@ -0,0 +1,133 @@
|
||||
"""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, MAdmin, MThread
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.classes.modules.user import User
|
||||
|
||||
class Settings:
|
||||
"""This Class will never be reloaded.
|
||||
Means that the variables are available during
|
||||
the whole life of the app
|
||||
"""
|
||||
|
||||
RUNNING_TIMERS: list[Timer] = []
|
||||
RUNNING_THREADS: list[Thread] = []
|
||||
RUNNING_SOCKETS: list[socket] = []
|
||||
RUNNING_ASYNC_TASKS: list[asyncio.Task] = []
|
||||
RUNNING_ASYNC_THREADS: list[MThread] = []
|
||||
PERIODIC_FUNC: dict[str, Any] = {}
|
||||
|
||||
THLOCK: RLock = RLock()
|
||||
AILOCK: Lock = Lock()
|
||||
|
||||
CONSOLE: bool = False
|
||||
|
||||
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] = {}
|
||||
"""Use set_cache or get_cache instead"""
|
||||
|
||||
__TRANSLATION: dict[str, list[list[str]]] = dict()
|
||||
"""Translation Varibale"""
|
||||
|
||||
__LANG: str = "EN"
|
||||
|
||||
__INSTANCE_OF_USER_UTILS: Optional['User'] = None
|
||||
"""Instance of the User Utils class"""
|
||||
|
||||
__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):
|
||||
"""When you want to store a variable
|
||||
|
||||
Ex.
|
||||
```python
|
||||
set_cache('MY_KEY', {'key1': 'value1', 'key2', 'value2'})
|
||||
```
|
||||
Args:
|
||||
key (str): The key you want to add.
|
||||
value_to_cache (Any): The Value you want to store.
|
||||
"""
|
||||
self.__CACHE[key] = value_to_cache
|
||||
|
||||
def get_cache(self, key) -> Optional[Any]:
|
||||
"""It returns the value associated to the key and finally it removes the entry"""
|
||||
if self.__CACHE.get(key, None) is not None:
|
||||
return self.__CACHE.pop(key)
|
||||
|
||||
return None
|
||||
|
||||
def get_cache_size(self) -> int:
|
||||
return len(self.__CACHE)
|
||||
|
||||
def clear_cache(self) -> None:
|
||||
self.__CACHE.clear()
|
||||
|
||||
def show_cache(self) -> dict[str, Any]:
|
||||
return self.__CACHE.copy()
|
||||
|
||||
@property
|
||||
def global_translation(self) -> dict[str, list[list[str]]]:
|
||||
"""Get/set global translation variable"""
|
||||
return self.__TRANSLATION
|
||||
|
||||
@global_translation.setter
|
||||
def global_translation(self, translation_var: dict) -> None:
|
||||
self.__TRANSLATION = translation_var
|
||||
|
||||
@property
|
||||
def global_lang(self) -> str:
|
||||
"""Global default language."""
|
||||
return self.__LANG
|
||||
|
||||
@global_lang.setter
|
||||
def global_lang(self, lang: str) -> None:
|
||||
self.__LANG = lang
|
||||
|
||||
@property
|
||||
def global_user(self) -> 'User':
|
||||
return self.__INSTANCE_OF_USER_UTILS
|
||||
|
||||
@global_user.setter
|
||||
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
|
||||
def global_logger(self, logger: Logger) -> None:
|
||||
self.__LOGGER = logger
|
||||
|
||||
global_settings = Settings()
|
||||
96
core/classes/modules/translation.py
Normal file
96
core/classes/modules/translation.py
Normal file
@@ -0,0 +1,96 @@
|
||||
import yaml
|
||||
import yaml.scanner
|
||||
from os import sep
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.loader import Loader
|
||||
|
||||
|
||||
class Translation:
|
||||
|
||||
def __init__(self, loader: 'Loader') -> None:
|
||||
"""
|
||||
|
||||
Args:
|
||||
loader (Loader): The Loader instance.
|
||||
"""
|
||||
self.Logs = loader.Logs
|
||||
self.Settings = loader.Settings
|
||||
|
||||
def get_translation(self) -> dict[str, list[list[str]]]:
|
||||
try:
|
||||
translation: dict[str, list[list[str]]] = dict()
|
||||
sfs: dict[str, list[list[str]]] = {}
|
||||
|
||||
module_translation_directory = Path("mods")
|
||||
core_translation_directory = Path("core")
|
||||
sfs_core = self.get_subfolders_name(core_translation_directory.__str__())
|
||||
sfs_module = self.get_subfolders_name(module_translation_directory.__str__())
|
||||
|
||||
# Combine the 2 dict
|
||||
for d in (sfs_core, sfs_module):
|
||||
for k, v in d.items():
|
||||
sfs.setdefault(k, []).extend(v)
|
||||
|
||||
loaded_files: list[str] = []
|
||||
|
||||
for module, filenames in sfs.items():
|
||||
translation[module] = []
|
||||
for filename in filenames:
|
||||
with open(f"{filename}", "r", encoding="utf-8") as fyaml:
|
||||
data: dict[str, list[dict[str, str]]] = yaml.safe_load(fyaml)
|
||||
|
||||
if not isinstance(data, dict):
|
||||
continue
|
||||
|
||||
for key, list_trad in data.items():
|
||||
for vlist in list_trad:
|
||||
translation[module].append([vlist["orig"], vlist["trad"]])
|
||||
|
||||
loaded_files.append(f"{filename}")
|
||||
|
||||
return translation
|
||||
|
||||
except yaml.scanner.ScannerError as se:
|
||||
self.Logs.error(f"[!] {se} [!]")
|
||||
return {}
|
||||
except yaml.YAMLError as ye:
|
||||
if hasattr(ye, 'problem_mark'):
|
||||
mark = ye.problem_mark
|
||||
self.Logs.error(f"Error YAML: {ye.with_traceback(None)}")
|
||||
self.Logs.error("Error position: (%s:%s)" % (mark.line+1, mark.column+1))
|
||||
return {}
|
||||
except yaml.error.MarkedYAMLError as me:
|
||||
self.Logs.error(f"[!] {me} [!]")
|
||||
return {}
|
||||
except Exception as err:
|
||||
self.Logs.error(f'General Error: {err}', exc_info=True)
|
||||
return {}
|
||||
|
||||
finally:
|
||||
self.Logs.debug("Translation files loaded")
|
||||
for f in loaded_files:
|
||||
self.Logs.debug(f" - {f}")
|
||||
|
||||
def get_subfolders_name(self, directory: str) -> dict[str, list[str]]:
|
||||
try:
|
||||
translation_information: dict[str, list[str]] = dict()
|
||||
main_directory = Path(directory)
|
||||
|
||||
# Init the dictionnary
|
||||
for subfolder in main_directory.rglob(f'*language{sep}*{sep}*.yaml'):
|
||||
if subfolder.name != '__pycache__':
|
||||
translation_information[subfolder.parent.name.lower()] = []
|
||||
|
||||
|
||||
for subfolder in main_directory.rglob(f'*language{sep}*{sep}*.yaml'):
|
||||
if subfolder.name != '__pycache__':
|
||||
translation_information[subfolder.parent.name.lower()].append(subfolder)
|
||||
|
||||
return translation_information
|
||||
|
||||
except Exception as err:
|
||||
self.Logs.error(f'General Error: {err}')
|
||||
return dict()
|
||||
258
core/classes/modules/user.py
Normal file
258
core/classes/modules/user.py
Normal file
@@ -0,0 +1,258 @@
|
||||
from re import sub
|
||||
from typing import Any, Optional, TYPE_CHECKING
|
||||
from datetime import datetime
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.loader import Loader
|
||||
from core.definition import MUser
|
||||
|
||||
class User:
|
||||
|
||||
UID_DB: list['MUser'] = []
|
||||
|
||||
@property
|
||||
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._ctx = loader
|
||||
self._current_user: Optional['MUser'] = None
|
||||
|
||||
def insert(self, new_user: 'MUser') -> bool:
|
||||
"""Insert a new User object
|
||||
|
||||
Args:
|
||||
newUser (UserModel): New userModel object
|
||||
|
||||
Returns:
|
||||
bool: True if inserted
|
||||
"""
|
||||
|
||||
user_obj = self.get_user(new_user.uid)
|
||||
if not user_obj is None:
|
||||
# User already created return False
|
||||
return False
|
||||
|
||||
self.UID_DB.append(new_user)
|
||||
|
||||
return True
|
||||
|
||||
def update_nickname(self, uid: str, new_nickname: str) -> bool:
|
||||
"""Update the nickname starting from the UID
|
||||
|
||||
Args:
|
||||
uid (str): UID of the user
|
||||
new_nickname (str): New nickname
|
||||
|
||||
Returns:
|
||||
bool: True if updated
|
||||
"""
|
||||
user_obj = self.get_user(uidornickname=uid)
|
||||
|
||||
if user_obj is None:
|
||||
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:
|
||||
"""Updating user mode
|
||||
|
||||
Args:
|
||||
uidornickname (str): The UID or Nickname of the user
|
||||
modes (str): new modes to update
|
||||
|
||||
Returns:
|
||||
bool: True if user mode has been updaed
|
||||
"""
|
||||
response = True
|
||||
user_obj = self.get_user(uidornickname=uidornickname)
|
||||
|
||||
if user_obj is None:
|
||||
return False
|
||||
|
||||
action = modes[0]
|
||||
new_modes = modes[1:]
|
||||
|
||||
existing_umodes = user_obj.umodes
|
||||
umodes = user_obj.umodes
|
||||
|
||||
if action == '+':
|
||||
|
||||
for nm in new_modes:
|
||||
if nm not in existing_umodes:
|
||||
umodes += nm
|
||||
|
||||
elif action == '-':
|
||||
for nm in new_modes:
|
||||
if nm in existing_umodes:
|
||||
umodes = umodes.replace(nm, '')
|
||||
else:
|
||||
return False
|
||||
|
||||
liste_umodes = list(umodes)
|
||||
final_umodes_liste = [x for x in self._ctx.Base.Settings.PROTOCTL_USER_MODES if x in liste_umodes]
|
||||
final_umodes = ''.join(final_umodes_liste)
|
||||
|
||||
user_obj.umodes = f"+{final_umodes}"
|
||||
|
||||
return response
|
||||
|
||||
def delete(self, uid: str) -> bool:
|
||||
"""Delete the User starting from the UID
|
||||
|
||||
Args:
|
||||
uid (str): UID of the user
|
||||
|
||||
Returns:
|
||||
bool: True if deleted
|
||||
"""
|
||||
|
||||
user_obj = self.get_user(uidornickname=uid)
|
||||
|
||||
if user_obj is None:
|
||||
return False
|
||||
|
||||
self.UID_DB.remove(user_obj)
|
||||
|
||||
return True
|
||||
|
||||
def get_user(self, uidornickname: str) -> Optional['MUser']:
|
||||
"""Get The User Object model
|
||||
|
||||
Args:
|
||||
uidornickname (str): UID or Nickname
|
||||
|
||||
Returns:
|
||||
UserModel|None: The UserModel Object | None
|
||||
"""
|
||||
for record in self.UID_DB:
|
||||
if record.uid == uidornickname:
|
||||
self.current_user = record
|
||||
return record
|
||||
elif record.nickname == uidornickname:
|
||||
self.current_user = record
|
||||
return record
|
||||
|
||||
return None
|
||||
|
||||
def get_uid(self, uidornickname:str) -> Optional[str]:
|
||||
"""Get the UID of the user starting from the UID or the Nickname
|
||||
|
||||
Args:
|
||||
uidornickname (str): UID or Nickname
|
||||
|
||||
Returns:
|
||||
str|None: Return the UID
|
||||
"""
|
||||
|
||||
user_obj = self.get_user(uidornickname=uidornickname)
|
||||
|
||||
if user_obj is None:
|
||||
return None
|
||||
|
||||
self.current_user = user_obj
|
||||
return user_obj.uid
|
||||
|
||||
def get_nickname(self, uidornickname:str) -> Optional[str]:
|
||||
"""Get the Nickname starting from UID or the nickname
|
||||
|
||||
Args:
|
||||
uidornickname (str): UID or Nickname of the user
|
||||
|
||||
Returns:
|
||||
str|None: the nickname
|
||||
"""
|
||||
user_obj = self.get_user(uidornickname=uidornickname)
|
||||
|
||||
if user_obj is None:
|
||||
return None
|
||||
|
||||
self.current_user = user_obj
|
||||
return user_obj.nickname
|
||||
|
||||
def get_user_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
|
||||
"""
|
||||
user_obj = self.get_user(uidornickname=uidornickname)
|
||||
|
||||
if user_obj is None:
|
||||
return None
|
||||
|
||||
return user_obj.to_dict()
|
||||
|
||||
def is_exist(self, uidornikname: str) -> bool:
|
||||
"""Check if the UID or the nickname exist in the USER DB
|
||||
|
||||
Args:
|
||||
uidornickname (str): The UID or the NICKNAME
|
||||
|
||||
Returns:
|
||||
bool: True if exist
|
||||
"""
|
||||
user_obj = self.get_user(uidornickname=uidornikname)
|
||||
|
||||
if user_obj is None:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def clean_uid(self, uid: str) -> Optional[str]:
|
||||
"""Clean UID by removing @ / % / + / ~ / * / :
|
||||
|
||||
Args:
|
||||
uid (str): The UID to clean
|
||||
|
||||
Returns:
|
||||
str: Clean UID without any sign
|
||||
"""
|
||||
|
||||
pattern = fr'[:|@|%|\+|~|\*]*'
|
||||
parsed_UID = sub(pattern, '', uid)
|
||||
|
||||
if not parsed_UID:
|
||||
return None
|
||||
|
||||
return parsed_UID
|
||||
|
||||
def get_user_uptime_in_minutes(self, uidornickname: str) -> float:
|
||||
"""Retourne depuis quand l'utilisateur est connecté (in minutes).
|
||||
|
||||
Args:
|
||||
uid (str): The uid or le nickname
|
||||
|
||||
Returns:
|
||||
int: How long in minutes has the user been connected?
|
||||
"""
|
||||
|
||||
get_user = self.get_user(uidornickname)
|
||||
if get_user is None:
|
||||
return 0
|
||||
|
||||
# Convertir la date enregistrée dans UID_DB en un objet {datetime}
|
||||
connected_time_string = get_user.connexion_datetime
|
||||
|
||||
if isinstance(connected_time_string, datetime):
|
||||
connected_time = connected_time_string
|
||||
else:
|
||||
connected_time = datetime.strptime(connected_time_string, "%Y-%m-%d %H:%M:%S.%f")
|
||||
|
||||
# What time is it ?
|
||||
current_datetime = datetime.now()
|
||||
|
||||
uptime = current_datetime - connected_time
|
||||
convert_to_minutes = uptime.seconds / 60
|
||||
uptime_minutes = round(number=convert_to_minutes, ndigits=2)
|
||||
|
||||
return uptime_minutes
|
||||
@@ -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)
|
||||
54
core/classes/protocols/command_handler.py
Normal file
54
core/classes/protocols/command_handler.py
Normal 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()
|
||||
32
core/classes/protocols/factory.py
Normal file
32
core/classes/protocols/factory.py
Normal 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
@@ -1,14 +0,0 @@
|
||||
from threading import Timer, Thread, RLock
|
||||
from socket import socket
|
||||
|
||||
class Settings:
|
||||
|
||||
RUNNING_TIMERS: list[Timer] = []
|
||||
RUNNING_THREADS: list[Thread] = []
|
||||
RUNNING_SOCKETS: list[socket] = []
|
||||
PERIODIC_FUNC: dict[object] = {}
|
||||
LOCK: RLock = RLock()
|
||||
|
||||
CONSOLE: bool = False
|
||||
|
||||
USER_MODES: list[str] = []
|
||||
@@ -1,220 +0,0 @@
|
||||
from re import sub
|
||||
from typing import Union, TYPE_CHECKING
|
||||
from dataclasses import asdict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.base import Base
|
||||
from core.definition import MUser
|
||||
|
||||
class User:
|
||||
|
||||
UID_DB: list['MUser'] = []
|
||||
|
||||
def __init__(self, baseObj: 'Base') -> None:
|
||||
|
||||
self.Logs = baseObj.logs
|
||||
self.Base = baseObj
|
||||
|
||||
return None
|
||||
|
||||
def insert(self, newUser: 'MUser') -> bool:
|
||||
"""Insert a new User object
|
||||
|
||||
Args:
|
||||
newUser (UserModel): New userModel object
|
||||
|
||||
Returns:
|
||||
bool: True if inserted
|
||||
"""
|
||||
|
||||
userObj = self.get_User(newUser.uid)
|
||||
|
||||
if not userObj is None:
|
||||
# User already created return False
|
||||
return False
|
||||
|
||||
self.UID_DB.append(newUser)
|
||||
|
||||
return True
|
||||
|
||||
def update(self, uid: str, newNickname: str) -> bool:
|
||||
"""Update the nickname starting from the UID
|
||||
|
||||
Args:
|
||||
uid (str): UID of the user
|
||||
newNickname (str): New nickname
|
||||
|
||||
Returns:
|
||||
bool: True if updated
|
||||
"""
|
||||
userObj = self.get_User(uidornickname=uid)
|
||||
|
||||
if userObj is None:
|
||||
return False
|
||||
|
||||
userObj.nickname = newNickname
|
||||
|
||||
return True
|
||||
|
||||
def update_mode(self, uidornickname: str, modes: str) -> bool:
|
||||
"""Updating user mode
|
||||
|
||||
Args:
|
||||
uidornickname (str): The UID or Nickname of the user
|
||||
modes (str): new modes to update
|
||||
|
||||
Returns:
|
||||
bool: True if user mode has been updaed
|
||||
"""
|
||||
response = True
|
||||
userObj = self.get_User(uidornickname=uidornickname)
|
||||
|
||||
if userObj is None:
|
||||
return False
|
||||
|
||||
action = modes[0]
|
||||
new_modes = modes[1:]
|
||||
|
||||
existing_umodes = userObj.umodes
|
||||
umodes = userObj.umodes
|
||||
|
||||
if action == '+':
|
||||
|
||||
for nm in new_modes:
|
||||
if nm not in existing_umodes:
|
||||
umodes += nm
|
||||
|
||||
elif action == '-':
|
||||
for nm in new_modes:
|
||||
if nm in existing_umodes:
|
||||
umodes = umodes.replace(nm, '')
|
||||
else:
|
||||
return False
|
||||
|
||||
liste_umodes = list(umodes)
|
||||
final_umodes_liste = [x for x in self.Base.Settings.USER_MODES if x in liste_umodes]
|
||||
final_umodes = ''.join(final_umodes_liste)
|
||||
|
||||
userObj.umodes = f"+{final_umodes}"
|
||||
|
||||
return response
|
||||
|
||||
def delete(self, uid: str) -> bool:
|
||||
"""Delete the User starting from the UID
|
||||
|
||||
Args:
|
||||
uid (str): UID of the user
|
||||
|
||||
Returns:
|
||||
bool: True if deleted
|
||||
"""
|
||||
|
||||
userObj = self.get_User(uidornickname=uid)
|
||||
|
||||
if userObj is None:
|
||||
return False
|
||||
|
||||
self.UID_DB.remove(userObj)
|
||||
|
||||
return True
|
||||
|
||||
def get_User(self, uidornickname: str) -> Union['MUser', None]:
|
||||
"""Get The User Object model
|
||||
|
||||
Args:
|
||||
uidornickname (str): UID or Nickname
|
||||
|
||||
Returns:
|
||||
UserModel|None: The UserModel Object | None
|
||||
"""
|
||||
User = None
|
||||
for record in self.UID_DB:
|
||||
if record.uid == uidornickname:
|
||||
User = record
|
||||
elif record.nickname == uidornickname:
|
||||
User = record
|
||||
|
||||
return User
|
||||
|
||||
def get_uid(self, uidornickname:str) -> Union[str, None]:
|
||||
"""Get the UID of the user starting from the UID or the Nickname
|
||||
|
||||
Args:
|
||||
uidornickname (str): UID or Nickname
|
||||
|
||||
Returns:
|
||||
str|None: Return the UID
|
||||
"""
|
||||
|
||||
userObj = self.get_User(uidornickname=uidornickname)
|
||||
|
||||
if userObj is None:
|
||||
return None
|
||||
|
||||
return userObj.uid
|
||||
|
||||
def get_nickname(self, uidornickname:str) -> Union[str, None]:
|
||||
"""Get the Nickname starting from UID or the nickname
|
||||
|
||||
Args:
|
||||
uidornickname (str): UID or Nickname of the user
|
||||
|
||||
Returns:
|
||||
str|None: the nickname
|
||||
"""
|
||||
userObj = self.get_User(uidornickname=uidornickname)
|
||||
|
||||
if userObj is None:
|
||||
return None
|
||||
|
||||
return userObj.nickname
|
||||
|
||||
def get_User_AsDict(self, uidornickname: str) -> Union[dict[str, any], None]:
|
||||
"""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
|
||||
"""
|
||||
userObj = self.get_User(uidornickname=uidornickname)
|
||||
|
||||
if userObj is None:
|
||||
return None
|
||||
|
||||
return asdict(userObj)
|
||||
|
||||
def is_exist(self, uidornikname: str) -> bool:
|
||||
"""Check if the UID or the nickname exist in the USER DB
|
||||
|
||||
Args:
|
||||
uidornickname (str): The UID or the NICKNAME
|
||||
|
||||
Returns:
|
||||
bool: True if exist
|
||||
"""
|
||||
userObj = self.get_User(uidornickname=uidornikname)
|
||||
|
||||
if userObj is None:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def clean_uid(self, uid: str) -> Union[str, None]:
|
||||
"""Clean UID by removing @ / % / + / ~ / * / :
|
||||
|
||||
Args:
|
||||
uid (str): The UID to clean
|
||||
|
||||
Returns:
|
||||
str: Clean UID without any sign
|
||||
"""
|
||||
|
||||
pattern = fr'[:|@|%|\+|~|\*]*'
|
||||
parsed_UID = sub(pattern, '', uid)
|
||||
|
||||
if not parsed_UID:
|
||||
return None
|
||||
|
||||
return parsed_UID
|
||||
@@ -1,10 +1,35 @@
|
||||
import asyncio
|
||||
import concurrent
|
||||
import concurrent.futures
|
||||
import threading
|
||||
from datetime import datetime
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Literal
|
||||
from json import dumps
|
||||
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 MUser:
|
||||
class MainModel:
|
||||
"""Parent Model contains important methods"""
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
"""Return the fields of a dataclass instance as a new dictionary mapping field names to field values."""
|
||||
return asdict(self)
|
||||
|
||||
def to_json(self) -> str:
|
||||
"""Return the object of a dataclass a json str."""
|
||||
return dumps(self.to_dict())
|
||||
|
||||
def 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"""
|
||||
return [f.name for f in fields(self)]
|
||||
|
||||
@dataclass
|
||||
class MUser(MainModel):
|
||||
"""Model User"""
|
||||
|
||||
uid: str = None
|
||||
@@ -14,6 +39,8 @@ class MUser:
|
||||
hostname: str = None
|
||||
umodes: str = None
|
||||
vhost: str = None
|
||||
fingerprint: str = None
|
||||
tls_cipher: str = None
|
||||
isWebirc: bool = False
|
||||
isWebsocket: bool = False
|
||||
remote_ip: str = None
|
||||
@@ -22,26 +49,30 @@ class MUser:
|
||||
connexion_datetime: datetime = field(default=datetime.now())
|
||||
|
||||
@dataclass
|
||||
class MAdmin:
|
||||
class MAdmin(MainModel):
|
||||
"""Model Admin"""
|
||||
|
||||
uid: str = None
|
||||
account: str = None
|
||||
nickname: str = None
|
||||
username: str = None
|
||||
realname: str = None
|
||||
hostname: str = None
|
||||
umodes: str = None
|
||||
vhost: str = None
|
||||
fingerprint: str = None
|
||||
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
|
||||
class MReputation:
|
||||
class MReputation(MainModel):
|
||||
"""Model Reputation"""
|
||||
uid: str = None
|
||||
nickname: str = None
|
||||
@@ -50,6 +81,8 @@ class MReputation:
|
||||
hostname: str = None
|
||||
umodes: str = None
|
||||
vhost: str = None
|
||||
fingerprint: str = None
|
||||
tls_cipher: str = None
|
||||
isWebirc: bool = False
|
||||
isWebsocket: bool = False
|
||||
remote_ip: str = None
|
||||
@@ -59,7 +92,7 @@ class MReputation:
|
||||
secret_code: str = None
|
||||
|
||||
@dataclass
|
||||
class MChannel:
|
||||
class MChannel(MainModel):
|
||||
"""Model Channel"""
|
||||
|
||||
name: str = None
|
||||
@@ -74,7 +107,7 @@ class MChannel:
|
||||
"""
|
||||
|
||||
@dataclass
|
||||
class ColorModel:
|
||||
class ColorModel(MainModel):
|
||||
white: str = "\x0300"
|
||||
black: str = "\x0301"
|
||||
blue: str = "\x0302"
|
||||
@@ -83,9 +116,10 @@ class ColorModel:
|
||||
yellow: str = "\x0306"
|
||||
bold: str = "\x02"
|
||||
nogc: str = "\x03"
|
||||
underline: str = "\x1F"
|
||||
|
||||
@dataclass
|
||||
class MConfig:
|
||||
class MConfig(MainModel):
|
||||
"""Model Configuration"""
|
||||
|
||||
SERVEUR_IP: str = "127.0.0.1"
|
||||
@@ -151,12 +185,24 @@ class MConfig:
|
||||
SERVICE_ID: str = field(init=False)
|
||||
"""The service unique ID"""
|
||||
|
||||
LANG: str = "EN"
|
||||
"""The default language of Defender. default: EN"""
|
||||
|
||||
OWNER: str = "admin"
|
||||
"""The nickname of the admin of the service"""
|
||||
|
||||
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"""
|
||||
|
||||
@@ -211,11 +257,14 @@ class MConfig:
|
||||
DEBUG_LEVEL:Literal[10, 20, 30, 40, 50] = 20
|
||||
"""Logs level: DEBUG 10 | INFO 20 | WARNING 30 | ERROR 40 | CRITICAL 50. (default: 20)"""
|
||||
|
||||
DEBUG_HARD: bool = False
|
||||
"""Adding filename, function name and the line number to the logs. Default False"""
|
||||
|
||||
LOGGING_NAME: str = "defender"
|
||||
"""The name of the Logging instance"""
|
||||
|
||||
TABLE_ADMIN: str = "core_admin"
|
||||
"""Admin table"""
|
||||
"""Core Admin table"""
|
||||
|
||||
TABLE_COMMAND: str = "core_command"
|
||||
"""Core command table"""
|
||||
@@ -283,16 +332,63 @@ class MConfig:
|
||||
"""0: utf-8 | 1: iso-8859-1"""
|
||||
|
||||
@dataclass
|
||||
class MClone:
|
||||
"""Model Clone"""
|
||||
connected: bool = False
|
||||
uid: str = None
|
||||
nickname: str = None
|
||||
username: str = None
|
||||
realname: str = None
|
||||
channels: list = field(default_factory=list)
|
||||
vhost: str = None
|
||||
hostname: str = 'localhost'
|
||||
umodes: str = None
|
||||
remote_ip: str = '127.0.0.1'
|
||||
group: str = 'Default'
|
||||
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
|
||||
command_name: str = None
|
||||
description: str = None
|
||||
command_level: int = 0
|
||||
|
||||
@dataclass
|
||||
class MModule(MainModel):
|
||||
module_name: str = None
|
||||
class_name: str = None
|
||||
class_instance: Optional[IModule] = None
|
||||
|
||||
@dataclass
|
||||
class DefenderModuleHeader(MainModel):
|
||||
name: str = ''
|
||||
version: str = ''
|
||||
description: str = ''
|
||||
author: str = ''
|
||||
core_version: str = ''
|
||||
|
||||
@dataclass
|
||||
class MSModule:
|
||||
"""Server Modules model"""
|
||||
name: str = None
|
||||
version: str = None
|
||||
type: str = None
|
||||
|
||||
@dataclass
|
||||
class MSasl(MainModel):
|
||||
"""Sasl model"""
|
||||
|
||||
remote_ip: Optional[str] = None
|
||||
mechanisme: Optional[str] = None
|
||||
message_type: Optional[str] = None
|
||||
client_uid: Optional[str] = None
|
||||
username: Optional[str] = None
|
||||
password: Optional[str] = None
|
||||
fingerprint: Optional[str] = None
|
||||
language: str = "EN"
|
||||
auth_success: bool = False
|
||||
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
150
core/install.py
Normal 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()
|
||||
@@ -1,331 +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[str, str, str]
|
||||
python_current_version: str
|
||||
defender_install_folder: str
|
||||
venv_folder: str
|
||||
venv_cmd_installation: list
|
||||
venv_cmd_requirements: list
|
||||
venv_pip_executable: str
|
||||
venv_python_executable: str
|
||||
|
||||
@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',
|
||||
python_current_version_tuple=python_version_tuple(),
|
||||
python_current_version=python_version(),
|
||||
defender_install_folder=defender_install_folder,
|
||||
venv_folder=venv_folder,
|
||||
venv_cmd_installation=['python3', '-m', 'venv', venv_folder],
|
||||
venv_cmd_requirements=['sqlalchemy','psutil','requests','faker','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 = 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_major, required_minor, required_patch = required_version.split('.')
|
||||
installed_major, installed_minor, installed_patch = installed_version.split('.')
|
||||
|
||||
if required_major > installed_major:
|
||||
print(f'> New version of {package.name} is available {installed_version} ==> {required_version}')
|
||||
newVersion = True
|
||||
elif required_major == installed_major and required_minor > installed_minor:
|
||||
print(f'> New version of {package.name} is available {installed_version} ==> {required_version}')
|
||||
newVersion = True
|
||||
elif required_major == installed_major and required_minor == installed_minor and required_patch > installed_patch:
|
||||
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
|
||||
"""
|
||||
# Current system version
|
||||
sys_major, sys_minor, sys_patch = self.config.python_current_version_tuple
|
||||
|
||||
# min python version required
|
||||
python_required_version = self.config.python_min_version.split('.')
|
||||
min_major, min_minor = tuple((python_required_version[0], python_required_version[1]))
|
||||
|
||||
if int(sys_major) < int(min_major):
|
||||
print(f"## Your python version must be greather than or equal to {self.config.python_min_version} ##")
|
||||
return False
|
||||
|
||||
elif (int(sys_major) <= int(min_major)) and (int(sys_minor) < int(min_minor)):
|
||||
print(f"## Your python version must be greather than or equal to {self.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:
|
||||
if not self.check_package(module):
|
||||
do_install = True
|
||||
|
||||
if not do_install:
|
||||
return None
|
||||
|
||||
print("===> Vider le cache de pip")
|
||||
self.run_subprocess([self.config.venv_pip_executable, 'cache', 'purge'])
|
||||
|
||||
print("===> Verifier si pip est a jour")
|
||||
self.run_subprocess([self.config.venv_python_executable, '-m', 'pip', 'install', '--upgrade', 'pip'])
|
||||
|
||||
if not self.check_package('greenlet'):
|
||||
self.run_subprocess([self.config.venv_pip_executable, 'install', '--only-binary', ':all:', 'greenlet'])
|
||||
print('====> Module Greenlet installé')
|
||||
|
||||
for module in self.config.venv_cmd_requirements:
|
||||
if not self.check_package(module):
|
||||
print("### Trying to install missing python packages ###")
|
||||
self.run_subprocess([self.config.venv_pip_executable, 'install', module])
|
||||
print(f"====> Module {module} installé")
|
||||
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(f'Service file generated with current configuration')
|
||||
print(f'Running Defender 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(f'Service file generated with current configuration')
|
||||
print(f'Running Defender 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)
|
||||
1880
core/irc.py
1880
core/irc.py
File diff suppressed because it is too large
Load Diff
45
core/language/fr/core-fr.yaml
Normal file
45
core/language/fr/core-fr.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
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: "[%sERREUR MODULE%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: "Connecté à %s avec succés"
|
||||
- orig: "[ %sINFORMATION%s ] >> %s is ready!"
|
||||
trad: "[ %sINFORMATION%s ] >> %s est prêt!"
|
||||
|
||||
# core/irc.py
|
||||
- orig: "[ %s%s%s ] - %s has been disconnected from %s"
|
||||
trad: "[ %s%s%s ] - %s a été déconnecté de %s"
|
||||
- orig: "You can't use this command anymore ! Please use [%sauth] instead"
|
||||
trad: "Vous ne pouvez plus utiliser cette commande ! Utilisez [%auth] à la place."
|
||||
- orig: "The nickname sent [%s] is different than the one set in the configuration file!"
|
||||
trad: "Le pseudo transmis, [%s], ne correspond pas à celui renseigné dans le fichier de configuration !"
|
||||
- orig: "[%s %s %s] - %s is now connected to %s"
|
||||
trad: "[%s %s %s] - %s est désormais connecté à %s"
|
||||
- orig: "[ %s %s %s ] - %s provided a wrong password!"
|
||||
trad: "[ %s %s %s ] - %s a donné un mauvais mot de passe !"
|
||||
- orig: "%s - %sNot Loaded%s"
|
||||
trad: "%s - Non chargé%s"
|
||||
|
||||
# core/classe/protocols/unreal6.py & core/classe/protocols/inspircd.py
|
||||
- orig: "[ %sINFORMATION%s ] >> %s is ready!"
|
||||
trad: "[ %sINFORMATION%s ] >> %s est prêt !"
|
||||
- orig: "[ %sSASL AUTH%s ] - %s (%s) is now connected successfuly to %s"
|
||||
trad: "[ %sSASL AUTH%s ] - %s (%s) s'est connecté avec succès à %s"
|
||||
- orig: "[ %sSASL AUTH%s ] - %s provided a wrong password for this username %s"
|
||||
trad: "[ %sSASL AUTH%s ] - %s : Mauvais certificat fourni avec le compte %s"
|
||||
- orig: "[ %sFINGERPRINT AUTH%s ] - %s (%s) is now connected successfuly to %s"
|
||||
trad: "[ %sFINGERPRINT AUTH%s ] - %s (%s) est maintenant authentifié sur %s !"
|
||||
@@ -1,32 +1,87 @@
|
||||
from core.classes import user, admin, channel, clone, reputation, settings
|
||||
from logging import Logger
|
||||
from core.classes.modules.settings import global_settings
|
||||
from core.classes.modules import translation, user, admin, channel, reputation, settings, sasl
|
||||
import core.logs as logs
|
||||
import core.definition as df
|
||||
import core.base as baseModule
|
||||
import core.classes.config as confModule
|
||||
import core.utils as utils
|
||||
import core.base as base_mod
|
||||
import core.module as module_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 Modules
|
||||
self.Definition: df = df
|
||||
# Load Main Modules
|
||||
self.Definition: df = df
|
||||
|
||||
self.ConfModule: confModule = confModule
|
||||
self.ConfModule: conf_mod = conf_mod
|
||||
|
||||
self.BaseModule: baseModule = baseModule
|
||||
self.BaseModule: base_mod = base_mod
|
||||
|
||||
self.CommandModule: commands_mod = commands_mod
|
||||
|
||||
self.LoggingModule: logs = logs
|
||||
|
||||
self.RpcServerModule: rpc_mod = rpc_mod
|
||||
|
||||
self.Utils: utils = utils
|
||||
|
||||
# Load Classes
|
||||
self.Settings: settings = settings.Settings()
|
||||
self.Settings: settings.Settings = global_settings
|
||||
|
||||
self.Config: df.MConfig = self.ConfModule.Configuration().ConfigObject
|
||||
self.ServiceLogging: logs.ServiceLogging = self.LoggingModule.ServiceLogging()
|
||||
|
||||
self.Base: baseModule.Base = self.BaseModule.Base(self.Config, self.Settings)
|
||||
self.Logs: Logger = self.ServiceLogging.get_logger()
|
||||
|
||||
self.User: user.User = user.User(self.Base)
|
||||
self.Config: df.MConfig = self.ConfModule.Configuration(self).configuration_model
|
||||
|
||||
self.Admin: admin.Admin = admin.Admin(self.Base)
|
||||
self.Settings.global_lang = self.Config.LANG if self.Config.LANG else "EN"
|
||||
|
||||
self.Channel: channel.Channel = channel.Channel(self.Base)
|
||||
self.Settings.global_logger = self.Logs
|
||||
|
||||
self.Clone: clone.Clone = clone.Clone(self.Base)
|
||||
self.Translation: translation.Translation = translation.Translation(self)
|
||||
|
||||
self.Reputation: reputation.Reputation = reputation.Reputation(self.Base)
|
||||
self.Settings.global_translation = self.Translation.get_translation()
|
||||
|
||||
self.Base: base_mod.Base = self.BaseModule.Base(self)
|
||||
|
||||
self.User: user.User = user.User(self)
|
||||
|
||||
self.Settings.global_user = self.User
|
||||
|
||||
self.Admin: admin.Admin = admin.Admin(self)
|
||||
|
||||
self.Channel: channel.Channel = channel.Channel(self)
|
||||
|
||||
self.Reputation: reputation.Reputation = reputation.Reputation(self)
|
||||
|
||||
self.Commands: commands_mod.Command = commands_mod.Command(self)
|
||||
|
||||
self.ModuleUtils: module_mod.Module = module_mod.Module(self)
|
||||
|
||||
self.Sasl: sasl.Sasl = sasl.Sasl(self)
|
||||
|
||||
self.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()
|
||||
|
||||
170
core/logs.py
Normal file
170
core/logs.py
Normal file
@@ -0,0 +1,170 @@
|
||||
import logging
|
||||
from os import path, makedirs, sep
|
||||
from typing import Optional
|
||||
|
||||
class ServiceLogging:
|
||||
|
||||
def __init__(self, loggin_name: str = "defender"):
|
||||
"""Create the Logging object
|
||||
"""
|
||||
self.OS_SEP = sep
|
||||
self.LOGGING_NAME = loggin_name
|
||||
self.remove_logger(loggin_name) # Remove logger if exists
|
||||
|
||||
self.DEBUG_LEVEL, self.DEBUG_FILE_LEVEL, self.DEBUG_STDOUT_LEVEL = (10, 10, 10)
|
||||
self.SERVER_PREFIX = None
|
||||
self.LOGGING_CONSOLE = True
|
||||
|
||||
self.LOG_FILTERS: list[str] = ["PING", f":{self.SERVER_PREFIX}auth", "['PASS'"]
|
||||
|
||||
self.file_handler = None
|
||||
self.stdout_handler = None
|
||||
|
||||
self.logs: logging.Logger = self.start_log_system()
|
||||
|
||||
def get_logger(self) -> logging.Logger:
|
||||
|
||||
logs_obj: logging.Logger = self.logs
|
||||
|
||||
return logs_obj
|
||||
|
||||
def remove_logger(self, logger_name: Optional[str] = None) -> None:
|
||||
|
||||
if logger_name is None:
|
||||
logger_name = self.LOGGING_NAME
|
||||
|
||||
# Récupérer le logger
|
||||
logger = logging.getLogger(logger_name)
|
||||
|
||||
# Retirer tous les gestionnaires du logger et les fermer
|
||||
for handler in logger.handlers[:]: # Utiliser une copie de la liste
|
||||
# print(handler)
|
||||
logger.removeHandler(handler)
|
||||
handler.close()
|
||||
|
||||
# Supprimer le logger du dictionnaire global
|
||||
logging.Logger.manager.loggerDict.pop(logger_name, None)
|
||||
|
||||
return None
|
||||
|
||||
def start_log_system(self) -> logging.Logger:
|
||||
|
||||
os_sep = self.OS_SEP
|
||||
logging_name = self.LOGGING_NAME
|
||||
debug_level = self.DEBUG_LEVEL
|
||||
debug_file_level = self.DEBUG_FILE_LEVEL
|
||||
debug_stdout_level = self.DEBUG_STDOUT_LEVEL
|
||||
|
||||
# Create folder if not available
|
||||
logs_directory = f'logs{os_sep}'
|
||||
if not path.exists(f'{logs_directory}'):
|
||||
makedirs(logs_directory)
|
||||
|
||||
# Init logs object
|
||||
logs = logging.getLogger(logging_name)
|
||||
logs.setLevel(debug_level)
|
||||
|
||||
# Add Handlers
|
||||
self.file_handler = logging.FileHandler(f'logs{os_sep}{logging_name}.log',encoding='UTF-8')
|
||||
self.file_handler.setLevel(debug_file_level)
|
||||
|
||||
self.stdout_handler = logging.StreamHandler()
|
||||
self.stdout_handler.setLevel(debug_stdout_level)
|
||||
|
||||
# Define log format
|
||||
formatter = logging.Formatter(
|
||||
fmt='%(asctime)s - %(levelname)s - %(message)s (%(filename)s:%(funcName)s:%(lineno)d)',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
# Apply log format
|
||||
self.file_handler.setFormatter(formatter)
|
||||
self.stdout_handler.setFormatter(formatter)
|
||||
|
||||
# Add handler to logs
|
||||
logs.addHandler(self.file_handler)
|
||||
logs.addHandler(self.stdout_handler)
|
||||
|
||||
# Apply the filter
|
||||
logs.addFilter(self.replace_filter)
|
||||
logs.info(f'#################### STARTING {self.LOGGING_NAME} ####################')
|
||||
|
||||
return logs
|
||||
|
||||
def set_stdout_handler_level(self, level: int) -> None:
|
||||
self.logs.debug(f"[STDOUT LEVEL] New level {level}")
|
||||
self.stdout_handler.setLevel(level)
|
||||
|
||||
def set_file_handler_level(self, level: int) -> None:
|
||||
self.logs.debug(f"[LOG FILE LEVEL] new level {level}")
|
||||
self.file_handler.setLevel(level)
|
||||
|
||||
def update_handler_format(self, debug_hard: bool = False) -> None:
|
||||
"""Updating logging formatter format!
|
||||
|
||||
Args:
|
||||
debug_hard (bool, optional): If true you will have filename,
|
||||
function name and the line number. Defaults to False.
|
||||
"""
|
||||
# Updating logging formatter
|
||||
if debug_hard:
|
||||
new_formatter = logging.Formatter(
|
||||
fmt='%(asctime)s - %(levelname)s - %(message)s (%(filename)s:%(funcName)s:%(lineno)d)',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
else:
|
||||
new_formatter = logging.Formatter(
|
||||
fmt='%(asctime)s - %(levelname)s - %(message)s',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
for handler in self.logs.handlers:
|
||||
handler.setFormatter(new_formatter)
|
||||
|
||||
def regenerate_handlers(self, logger: logging.Logger) -> logging.Logger:
|
||||
os_sep = self.OS_SEP
|
||||
logging_name = self.LOGGING_NAME
|
||||
debug_file_level = self.DEBUG_FILE_LEVEL
|
||||
debug_stdout_level = self.DEBUG_STDOUT_LEVEL
|
||||
|
||||
# Add Handlers
|
||||
self.file_handler = logging.FileHandler(f'logs{os_sep}{logging_name}.log',encoding='UTF-8')
|
||||
self.file_handler.setLevel(debug_file_level)
|
||||
|
||||
self.stdout_handler = logging.StreamHandler()
|
||||
self.stdout_handler.setLevel(debug_stdout_level)
|
||||
|
||||
# Define log format
|
||||
formatter = logging.Formatter(
|
||||
fmt='%(asctime)s - %(levelname)s - %(message)s (%(filename)s:%(funcName)s:%(lineno)d)',
|
||||
datefmt='%Y-%m-%d %H:%M:%S'
|
||||
)
|
||||
|
||||
# Apply log format
|
||||
self.file_handler.setFormatter(formatter)
|
||||
self.stdout_handler.setFormatter(formatter)
|
||||
|
||||
# Add handler to logs
|
||||
logger.addHandler(self.file_handler)
|
||||
logger.addHandler(self.stdout_handler)
|
||||
|
||||
# Apply the filter
|
||||
logger.addFilter(self.replace_filter)
|
||||
logger.info(f'REGENRATING LOGGER {self.LOGGING_NAME}')
|
||||
|
||||
return logger
|
||||
|
||||
def replace_filter(self, record: logging.LogRecord) -> bool:
|
||||
|
||||
response = True
|
||||
filter: list[str] = self.LOG_FILTERS
|
||||
|
||||
# record.msg = record.getMessage().replace("PING", "[REDACTED]")
|
||||
# if self.LOGGING_CONSOLE:
|
||||
# print(record.getMessage())
|
||||
|
||||
for f in filter:
|
||||
if f in record.getMessage():
|
||||
response = False
|
||||
|
||||
return response # Retourne True to write the log!
|
||||
536
core/module.py
Normal file
536
core/module.py
Normal file
@@ -0,0 +1,536 @@
|
||||
'''
|
||||
This is the main operational file to handle modules
|
||||
'''
|
||||
import sys
|
||||
import importlib
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
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._ctx = loader
|
||||
|
||||
def is_module_compliant(self, obj: object) -> bool:
|
||||
class_name = obj.__name__
|
||||
is_compliant = True
|
||||
attributs = {'MOD_HEADER', 'mod_config'}
|
||||
methods = {'load', 'unload', 'create_tables', 'cmd', 'hcmds', 'ModConfModel'}
|
||||
|
||||
obj_attributs: set = set([attribut for attribut in dir(obj) if not callable(getattr(obj, attribut)) and not attribut.startswith('__')])
|
||||
obj_methods: set = set([method for method in dir(obj) if callable(getattr(obj, method)) and not method.startswith('__')])
|
||||
|
||||
if not attributs.issubset(obj_attributs):
|
||||
self._ctx.Logs.error(f'[{class_name}] Your module is not valid make sure you have implemented required attributes {attributs}')
|
||||
raise AttributeError(f'[{class_name}] Your module is not valid make sure you have implemented required attributes {attributs}')
|
||||
|
||||
if not methods.issubset(obj_methods):
|
||||
self._ctx.Logs.error(f'[{class_name}] Your module is not valid make sure you have implemented required methods {methods}')
|
||||
raise AttributeError(f'[{class_name}] Your module is not valid make sure you have implemented required methods {methods}')
|
||||
|
||||
return is_compliant
|
||||
|
||||
def get_all_available_modules(self) -> list[str]:
|
||||
"""Get list of all main modules
|
||||
using this pattern mod_*.py
|
||||
all files starting with mod_
|
||||
Returns:
|
||||
list[str]: List of all module names.
|
||||
"""
|
||||
base_path = Path('mods')
|
||||
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[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() # --> 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 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._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._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._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
|
||||
reload_mod = await self.reload_one_module(module_name, nickname)
|
||||
return reload_mod
|
||||
|
||||
# Charger le module
|
||||
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
|
||||
self.is_module_compliant(my_class)
|
||||
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.drop_module_from_sys_modules(module_name)
|
||||
await self.db_delete_module(module_name)
|
||||
self._ctx.Logs.error(msg=attr, exc_info=True)
|
||||
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
|
||||
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._ctx.Logs.debug(f"Module {class_name} has been loaded")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def load_all_modules(self) -> 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:
|
||||
uplink (Irc): The Irc service instance
|
||||
module_name (str): The module name
|
||||
nickname (str): The nickname
|
||||
|
||||
Returns:
|
||||
bool: True if the module has been reloaded
|
||||
"""
|
||||
module_folder, module_name, class_name = self.get_module_information(module_name)
|
||||
red = self._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:
|
||||
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:
|
||||
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._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
|
||||
self.reload_all_modules_with_all_dependencies(f'mods.{module_folder}')
|
||||
|
||||
the_module = sys.modules[f'mods.{module_folder}.{module_name}']
|
||||
importlib.reload(the_module)
|
||||
my_class = getattr(the_module, class_name, None)
|
||||
self.is_module_compliant(my_class)
|
||||
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
|
||||
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._ctx.Config.SERVICE_CHANLOG
|
||||
)
|
||||
self._ctx.Logs.debug(f"Module [{module_folder}.{module_name}] reloaded!")
|
||||
return True
|
||||
else:
|
||||
# Module is not loaded! Nothing to reload
|
||||
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._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._ctx.Config.SERVICE_CHANLOG
|
||||
)
|
||||
self.drop_module_from_sys_modules(module_name)
|
||||
await self.db_delete_module(module_name)
|
||||
return False
|
||||
|
||||
def reload_all_modules(self) -> bool:
|
||||
...
|
||||
|
||||
def reload_all_modules_with_all_dependencies(self, prefix: str = 'mods') -> bool:
|
||||
"""
|
||||
Reload all modules in sys.modules that start with the given prefix.
|
||||
Useful for reloading a full package during development.
|
||||
"""
|
||||
modules_to_reload = []
|
||||
|
||||
# Collect target modules
|
||||
for name, module in sys.modules.items():
|
||||
if (
|
||||
isinstance(module, ModuleType)
|
||||
and module is not None
|
||||
and name.startswith(prefix)
|
||||
):
|
||||
modules_to_reload.append((name, module))
|
||||
|
||||
# Sort to reload submodules before parent modules
|
||||
for name, module in sorted(modules_to_reload, key=lambda x: x[0], reverse=True):
|
||||
try:
|
||||
if 'mod_' not in name and 'schemas' not in name:
|
||||
importlib.reload(module)
|
||||
self._ctx.Logs.debug(f'[LOAD_MODULE] Module {module} success')
|
||||
|
||||
except Exception as err:
|
||||
self._ctx.Logs.error(f'[LOAD_MODULE] Module {module} failed [!] - {err}')
|
||||
|
||||
async def unload_one_module(self, module_name: str, keep_in_db: bool = True) -> bool:
|
||||
"""Unload a module
|
||||
|
||||
Args:
|
||||
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._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._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._ctx.Config.SERVICE_CHANLOG
|
||||
)
|
||||
return False
|
||||
|
||||
if module:
|
||||
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.
|
||||
if sys.modules.get(f'mods.{module_folder}.{module_name}'):
|
||||
del sys.modules[f"mods.{module_folder}.{module_name}"]
|
||||
|
||||
if sys.modules.get(f'mods.{module_folder}.{module_name}'):
|
||||
self._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:
|
||||
await self.db_delete_module(module_name)
|
||||
|
||||
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._ctx.Config.SERVICE_CHANLOG
|
||||
)
|
||||
self._ctx.Logs.debug(f"[ UNLOAD MODULE ] {module_name} has been unloaded!")
|
||||
return True
|
||||
|
||||
self._ctx.Logs.debug(f"[UNLOAD MODULE]: Module {module_name} not found in DB_MODULES variable!")
|
||||
return False
|
||||
|
||||
except Exception as err:
|
||||
self._ctx.Logs.error(f"General Error: {err}", exc_info=True)
|
||||
return False
|
||||
|
||||
def unload_all_modules(self) -> bool:
|
||||
...
|
||||
|
||||
def is_module_exist_in_sys_module(self, module_name: str) -> bool:
|
||||
"""Check if the module exist in the sys.modules
|
||||
This will check only in the folder mods/
|
||||
Args:
|
||||
module_name (str): The module name
|
||||
|
||||
Returns:
|
||||
bool: True if the module exist
|
||||
"""
|
||||
module_folder, module_name, class_name = self.get_module_information(module_name)
|
||||
if "mods." + module_folder + "." + module_name in sys.modules:
|
||||
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
|
||||
|
||||
def drop_module_from_sys_modules(self, module_name: str) -> bool:
|
||||
"""_summary_
|
||||
|
||||
Args:
|
||||
module_name (str): _description_
|
||||
|
||||
Returns:
|
||||
bool: _description_
|
||||
"""
|
||||
module_folder, module_name, class_name = self.get_module_information(module_name)
|
||||
full_module_name = "mods." + module_folder + "." + module_name
|
||||
del sys.modules[full_module_name]
|
||||
|
||||
if not self.is_module_exist_in_sys_module(module_name):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
'''
|
||||
ALL METHODS RELATED TO THE MModule MODEL DATACLASS
|
||||
'''
|
||||
def model_get_module(self, module_name: str) -> Optional[MModule]:
|
||||
"""Get The module model object if exist otherwise it returns None
|
||||
|
||||
Args:
|
||||
module_name (str): The module name you want to fetch
|
||||
|
||||
Returns:
|
||||
Optional[MModule]: The Module Model Object
|
||||
"""
|
||||
for module in self.DB_MODULES:
|
||||
if module.module_name.lower() == module_name.lower():
|
||||
self._ctx.Logs.debug(f"[MODEL MODULE GET] The module {module_name} has been found in the model DB_MODULES")
|
||||
return module
|
||||
|
||||
self._ctx.Logs.debug(f"[MODEL MODULE GET] The module {module_name} not found in the model DB_MODULES")
|
||||
return None
|
||||
|
||||
def model_drop_module(self, module_name: str) -> bool:
|
||||
"""Drop a module model object from DB_MODULES
|
||||
|
||||
Args:
|
||||
module_name (str): The module name you want to drop
|
||||
|
||||
Returns:
|
||||
bool: True if the model has been dropped
|
||||
"""
|
||||
module = self.model_get_module(module_name)
|
||||
if module:
|
||||
self.DB_MODULES.remove(module)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def model_get_loaded_modules(self) -> list[MModule]:
|
||||
"""Get the instance of DB_MODULES.
|
||||
Warning: You should use a copy if you want to loop through the list!
|
||||
|
||||
Returns:
|
||||
list[MModule]: A list of module model object
|
||||
"""
|
||||
# self._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:
|
||||
"""Insert a new module model object
|
||||
|
||||
Args:
|
||||
module_model (MModule): The module model object
|
||||
|
||||
Returns:
|
||||
bool: True if the model has been inserted
|
||||
"""
|
||||
module = self.model_get_module(module_model.module_name)
|
||||
if module is None:
|
||||
self.DB_MODULES.append(module_model)
|
||||
self._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._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._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:
|
||||
"""Check if the module exist in the module model object
|
||||
|
||||
Args:
|
||||
module_name (str): The module name
|
||||
|
||||
Returns:
|
||||
bool: True if the module_name exist
|
||||
"""
|
||||
if self.model_get_module(module_name):
|
||||
self._ctx.Logs.debug(f"[MODEL MODULE EXIST] The module {module_name} exist in the local model DB_MODULES!")
|
||||
return True
|
||||
|
||||
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
|
||||
'''
|
||||
|
||||
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._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():
|
||||
await self.load_one_module(r[0], 'sys', True)
|
||||
|
||||
return True
|
||||
|
||||
async def db_is_module_exist(self, module_name: str) -> bool:
|
||||
"""Check if the module exist in the database
|
||||
|
||||
Args:
|
||||
module_name (str): The module name you want to check
|
||||
|
||||
Returns:
|
||||
bool: True if the module exist in the database
|
||||
"""
|
||||
query = f"SELECT id FROM {self._ctx.Config.TABLE_MODULE} WHERE module_name = :module_name"
|
||||
mes_donnes = {'module_name': module_name.lower()}
|
||||
results = await self._ctx.Base.db_execute_query(query, mes_donnes)
|
||||
|
||||
if results.fetchall():
|
||||
self._ctx.Logs.debug(f"[DB MODULE EXIST] The module {module_name} exist in the database!")
|
||||
return True
|
||||
else:
|
||||
self._ctx.Logs.debug(f"[DB MODULE EXIST] The module {module_name} is not available in the database!")
|
||||
return False
|
||||
|
||||
async def db_register_module(self, module_name: str, nickname: str, is_default: bool = False) -> bool:
|
||||
"""Insert a new module in the database
|
||||
|
||||
Args:
|
||||
module_name (str): The module name
|
||||
nickname (str): The user who loaded the module
|
||||
isdefault (int): Is this a default module. Default 0
|
||||
"""
|
||||
if not 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._ctx.Logs.debug(f"[DB REGISTER MODULE] Module {module_name} has been inserted to the database!")
|
||||
return True
|
||||
else:
|
||||
self._ctx.Logs.debug(f"[DB REGISTER MODULE] Module {module_name} not inserted to the database!")
|
||||
return False
|
||||
|
||||
self._ctx.Logs.debug(f"[DB REGISTER MODULE] Module {module_name} already exist in the database! Nothing to insert!")
|
||||
return False
|
||||
|
||||
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._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._ctx.Logs.debug(f"[DB UPDATE MODULE] Module {module_name} has been updated!")
|
||||
return True
|
||||
else:
|
||||
self._ctx.Logs.debug(f"[DB UPDATE MODULE] Module {module_name} not found! Nothing to update!")
|
||||
return False
|
||||
|
||||
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._ctx.Config.TABLE_MODULE} WHERE LOWER(module_name) = :module_name"
|
||||
mes_donnees = {'module_name': module_name.lower()}
|
||||
delete = await self._ctx.Base.db_execute_query(insert_cmd_query, mes_donnees)
|
||||
if delete.rowcount > 0:
|
||||
self._ctx.Logs.debug(f"[DB MODULE DELETE] The module {module_name} has been deleted from the dabatase!")
|
||||
return True
|
||||
|
||||
self._ctx.Logs.debug(f"[DB MODULE DELETE] The module {module_name} is not available in the database! Nothing to delete!")
|
||||
return False
|
||||
250
core/utils.py
Normal file
250
core/utils.py
Normal file
@@ -0,0 +1,250 @@
|
||||
"""
|
||||
Main utils library.
|
||||
"""
|
||||
import gc
|
||||
import ssl
|
||||
from pathlib import Path
|
||||
from re import match, sub
|
||||
from typing import Literal, Optional, Any, TYPE_CHECKING
|
||||
from datetime import datetime, timedelta
|
||||
from time import time, sleep
|
||||
from random import choice
|
||||
from hashlib import md5, sha3_512
|
||||
from core.classes.modules.settings import global_settings
|
||||
from asyncio import iscoroutinefunction
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from threading import Event
|
||||
from core.loader import Loader
|
||||
|
||||
def tr(message: str, *args) -> str:
|
||||
"""Translation Engine system
|
||||
```python
|
||||
example:
|
||||
_('Hello my firstname is %s and my lastname is %s', firstname, lastname)
|
||||
```
|
||||
Args:
|
||||
message (str): The message to translate
|
||||
*args (any) : Whatever the variable you want to pass
|
||||
|
||||
Returns:
|
||||
str: The translated message
|
||||
"""
|
||||
count_args = len(args) # Count number of args sent
|
||||
count_placeholder = message.count('%s') # Count number of placeholder in the message
|
||||
is_args_available = True if args else False
|
||||
g = global_settings
|
||||
try:
|
||||
# Access to admin object
|
||||
client_language = g.current_admin.language if g.current_admin else g.global_lang
|
||||
|
||||
if count_args != 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 client_language.lower() == 'en':
|
||||
return message % args if is_args_available else message
|
||||
|
||||
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"KeyError: {ke}")
|
||||
return message % args if is_args_available else message
|
||||
|
||||
except Exception as err:
|
||||
global_settings.global_logger.error(f"General Error: {err} / {message}")
|
||||
return message
|
||||
|
||||
def convert_to_int(value: Any) -> Optional[int]:
|
||||
"""Convert a value to int
|
||||
|
||||
Args:
|
||||
value (Any): Value to convert to int if possible
|
||||
|
||||
Returns:
|
||||
int: Return the int value or None if not possible
|
||||
"""
|
||||
try:
|
||||
value_to_int = int(value)
|
||||
return value_to_int
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
def get_unixtime() -> int:
|
||||
"""Cette fonction retourne un UNIXTIME de type 12365456
|
||||
|
||||
Returns:
|
||||
int: Current time in seconds since the Epoch (int)
|
||||
"""
|
||||
# cet_offset = timezone(timedelta(hours=2))
|
||||
# now_cet = datetime.now(cet_offset)
|
||||
# unixtime_cet = int(now_cet.timestamp())
|
||||
return int(time())
|
||||
|
||||
def get_sdatetime() -> str:
|
||||
"""Retourne une date au format string (24-12-2023 20:50:59)
|
||||
|
||||
Returns:
|
||||
str: Current datetime in this format %d-%m-%Y %H:%M:%S
|
||||
"""
|
||||
currentdate = datetime.now().strftime('%d-%m-%Y %H:%M:%S')
|
||||
return currentdate
|
||||
|
||||
def get_datetime() -> datetime:
|
||||
"""
|
||||
Return the current datetime in a datetime object
|
||||
"""
|
||||
return datetime.now()
|
||||
|
||||
def get_ssl_context() -> ssl.SSLContext:
|
||||
"""Generate the ssl context
|
||||
|
||||
Returns:
|
||||
SSLContext: The SSL Context
|
||||
"""
|
||||
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
||||
ctx.check_hostname = False
|
||||
ctx.verify_mode = ssl.CERT_NONE
|
||||
return ctx
|
||||
|
||||
def get_defender_uptime(loader: 'Loader') -> str:
|
||||
"""Savoir depuis quand Defender est connecté
|
||||
|
||||
Returns:
|
||||
str: L'écart entre la date du jour et celle de la connexion de Defender
|
||||
"""
|
||||
current_datetime = datetime.now()
|
||||
diff_date = current_datetime - loader.Irc.defender_connexion_datetime
|
||||
uptime = timedelta(days=diff_date.days, seconds=diff_date.seconds)
|
||||
|
||||
return uptime
|
||||
|
||||
def run_python_garbage_collector() -> int:
|
||||
"""Run Python garbage collector
|
||||
|
||||
Returns:
|
||||
int: The number of unreachable objects is returned.
|
||||
"""
|
||||
return gc.collect()
|
||||
|
||||
def get_number_gc_objects(your_object_to_count: Optional[Any] = None) -> int:
|
||||
"""Get The number of objects tracked by the collector (excluding the list returned).
|
||||
|
||||
Returns:
|
||||
int: Number of tracked objects by the collector
|
||||
"""
|
||||
if your_object_to_count is None:
|
||||
return len(gc.get_objects())
|
||||
|
||||
return sum(1 for obj in gc.get_objects() if isinstance(obj, your_object_to_count))
|
||||
|
||||
def 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.
|
||||
|
||||
Returns:
|
||||
str: The random string
|
||||
"""
|
||||
caracteres = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
randomize = ''.join(choice(caracteres) for _ in range(lenght))
|
||||
|
||||
return randomize
|
||||
|
||||
def hash_password(password: str, algorithm: Literal["md5", "sha3_512"] = 'md5') -> str:
|
||||
"""Return the crypted password following the selected algorithm
|
||||
|
||||
Args:
|
||||
password (str): The plain text password
|
||||
algorithm (str): The algorithm to use
|
||||
|
||||
Returns:
|
||||
str: The crypted password, default md5
|
||||
"""
|
||||
|
||||
match algorithm:
|
||||
case 'md5':
|
||||
hashed_password = md5(password.encode()).hexdigest()
|
||||
return hashed_password
|
||||
|
||||
case 'sha3_512':
|
||||
hashed_password = sha3_512(password.encode()).hexdigest()
|
||||
return hashed_password
|
||||
|
||||
case _:
|
||||
hashed_password = md5(password.encode()).hexdigest()
|
||||
return hashed_password
|
||||
|
||||
def get_all_modules() -> list[str]:
|
||||
"""Get list of all main modules
|
||||
using this pattern mod_*.py
|
||||
|
||||
Returns:
|
||||
list[str]: List of module names.
|
||||
"""
|
||||
base_path = Path('mods')
|
||||
return [file.name.replace('.py', '') for file in base_path.rglob('mod_*.py')]
|
||||
|
||||
def clean_uid(uid: str) -> Optional[str]:
|
||||
"""Clean UID by removing @ / % / + / ~ / * / :
|
||||
|
||||
Args:
|
||||
uid (str): The UID to clean
|
||||
|
||||
Returns:
|
||||
str: Clean UID without any sign
|
||||
"""
|
||||
if uid is None:
|
||||
return None
|
||||
|
||||
pattern = fr'[:|@|%|\+|~|\*]*'
|
||||
parsed_uid = sub(pattern, '', uid)
|
||||
|
||||
return parsed_uid
|
||||
|
||||
def hide_sensitive_data(srvmsg: list[str]) -> list[str]:
|
||||
try:
|
||||
srv_msg = srvmsg.copy()
|
||||
privmsg_index = srv_msg.index('PRIVMSG')
|
||||
auth_index = privmsg_index + 2
|
||||
if match(r'^:{1}\W?(auth)$', srv_msg[auth_index]) is None:
|
||||
return srv_msg
|
||||
|
||||
for l in range(auth_index + 1, len(srv_msg)):
|
||||
srv_msg[l] = '*' * len(srv_msg[l])
|
||||
|
||||
return srv_msg
|
||||
|
||||
except ValueError:
|
||||
return srvmsg
|
||||
|
||||
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)
|
||||
34
defender.py
34
defender.py
@@ -1,33 +1,21 @@
|
||||
from core import installation
|
||||
|
||||
import asyncio
|
||||
from core import install
|
||||
#############################################
|
||||
# @Version : 1 #
|
||||
# @Version : 6.4 #
|
||||
# Requierements : #
|
||||
# Python3.10 or higher #
|
||||
# SQLAlchemy, requests, psutil #
|
||||
# unrealircd-rpc-py, pyyaml #
|
||||
# uvicorn, starlette, faker #
|
||||
# UnrealIRCD 6.2.2 or higher #
|
||||
#############################################
|
||||
|
||||
#########################
|
||||
# LANCEMENT DE DEFENDER #
|
||||
#########################
|
||||
|
||||
# 1. Chargement de la configuration
|
||||
# 2. Chargement de l'ensemble des classes
|
||||
# 3.
|
||||
#
|
||||
|
||||
try:
|
||||
|
||||
installation.Install()
|
||||
|
||||
async def main():
|
||||
# install.update_packages()
|
||||
from core.loader import Loader
|
||||
from core.irc import Irc
|
||||
loader = Loader()
|
||||
ircInstance = Irc(loader)
|
||||
ircInstance.init_irc(ircInstance)
|
||||
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)
|
||||
|
||||
163
mods/clone/clone_manager.py
Normal file
163
mods/clone/clone_manager.py
Normal file
@@ -0,0 +1,163 @@
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
from mods.clone.schemas import MClone
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mods.clone.mod_clone import Clone
|
||||
|
||||
class CloneManager:
|
||||
|
||||
UID_CLONE_DB: list[MClone] = []
|
||||
|
||||
def __init__(self, uplink: 'Clone'):
|
||||
|
||||
self.Logs = uplink.ctx.Logs
|
||||
|
||||
def insert(self, new_clone_object: MClone) -> bool:
|
||||
"""Create new Clone object
|
||||
|
||||
Args:
|
||||
new_clone_object (MClone): New Clone object
|
||||
|
||||
Returns:
|
||||
bool: True if inserted
|
||||
"""
|
||||
if new_clone_object is None:
|
||||
self.Logs.debug('New Clone object must not be None')
|
||||
return False
|
||||
|
||||
for record in self.UID_CLONE_DB:
|
||||
if record.nickname == new_clone_object.nickname or record.uid == new_clone_object.uid:
|
||||
# If the user exist then return False and do not go further
|
||||
self.Logs.debug(f'Nickname/UID {record.nickname}/{record.uid} already exist')
|
||||
return False
|
||||
|
||||
self.UID_CLONE_DB.append(new_clone_object)
|
||||
self.Logs.debug(f'New Clone object created: {new_clone_object}')
|
||||
return True
|
||||
|
||||
def delete(self, uidornickname: str) -> bool:
|
||||
"""Delete the Clone Object starting from the nickname or the UID
|
||||
|
||||
Args:
|
||||
uidornickname (str): UID or nickname of the clone
|
||||
|
||||
Returns:
|
||||
bool: True if deleted
|
||||
"""
|
||||
|
||||
clone_obj = self.get_clone(uidornickname=uidornickname)
|
||||
|
||||
if clone_obj is None:
|
||||
return False
|
||||
|
||||
self.UID_CLONE_DB.remove(clone_obj)
|
||||
|
||||
return True
|
||||
|
||||
def nickname_exists(self, nickname: str) -> bool:
|
||||
"""Check if the nickname exist
|
||||
|
||||
Args:
|
||||
nickname (str): Nickname of the clone
|
||||
|
||||
Returns:
|
||||
bool: True if the nickname exist
|
||||
"""
|
||||
clone = self.get_clone(nickname)
|
||||
if isinstance(clone, MClone):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def uid_exists(self, uid: str) -> bool:
|
||||
"""Check if the nickname exist
|
||||
|
||||
Args:
|
||||
uid (str): uid of the clone
|
||||
|
||||
Returns:
|
||||
bool: True if the nickname exist
|
||||
"""
|
||||
clone = self.get_clone(uid)
|
||||
if isinstance(clone, MClone):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def group_exists(self, groupname: str) -> bool:
|
||||
"""Verify if a group exist
|
||||
|
||||
Args:
|
||||
groupname (str): The group name
|
||||
|
||||
Returns:
|
||||
bool: _description_
|
||||
"""
|
||||
for clone in self.UID_CLONE_DB:
|
||||
if clone.group.strip().lower() == groupname.strip().lower():
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def get_clone(self, uidornickname: str) -> Optional[MClone]:
|
||||
"""Get MClone object or None
|
||||
|
||||
Args:
|
||||
uidornickname (str): The UID or the Nickname
|
||||
|
||||
Returns:
|
||||
Union[MClone, None]: Return MClone object or None
|
||||
"""
|
||||
for clone in self.UID_CLONE_DB:
|
||||
if clone.uid == uidornickname:
|
||||
return clone
|
||||
if clone.nickname == uidornickname:
|
||||
return clone
|
||||
|
||||
return None
|
||||
|
||||
def get_clones_from_groupname(self, groupname: str) -> list[MClone]:
|
||||
"""Get list of clone objects by group name
|
||||
|
||||
Args:
|
||||
groupname (str): The group name
|
||||
|
||||
Returns:
|
||||
list[MClone]: List of clones in the group
|
||||
"""
|
||||
group_of_clone: list[MClone] = []
|
||||
|
||||
if self.group_exists(groupname):
|
||||
for clone in self.UID_CLONE_DB:
|
||||
if clone.group.strip().lower() == groupname.strip().lower():
|
||||
group_of_clone.append(clone)
|
||||
|
||||
return group_of_clone
|
||||
|
||||
def get_uid(self, uidornickname: str) -> Optional[str]:
|
||||
"""Get the UID of the clone starting from the UID or the Nickname
|
||||
|
||||
Args:
|
||||
uidornickname (str): UID or Nickname
|
||||
|
||||
Returns:
|
||||
str|None: Return the UID
|
||||
"""
|
||||
for record in self.UID_CLONE_DB:
|
||||
if record.uid == uidornickname:
|
||||
return record.uid
|
||||
if record.nickname == uidornickname:
|
||||
return record.uid
|
||||
|
||||
return None
|
||||
|
||||
def kill(self, nickname:str) -> bool:
|
||||
|
||||
response = False
|
||||
|
||||
for clone in self.UID_CLONE_DB:
|
||||
if clone.nickname == nickname:
|
||||
clone.alive = False # Kill the clone
|
||||
response = True
|
||||
|
||||
return response
|
||||
4
mods/clone/language/es/clone-es_1.yaml
Normal file
4
mods/clone/language/es/clone-es_1.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
traduction:
|
||||
# Message help
|
||||
- orig: "Hi my name is clone-es"
|
||||
trad: "Hola mi name is clone-es"
|
||||
6
mods/clone/language/fr/clone-fr_1.yaml
Normal file
6
mods/clone/language/fr/clone-fr_1.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
traduction:
|
||||
# Message help
|
||||
- orig: "You are now logged in"
|
||||
trad: "Vous étes désomais identifier"
|
||||
- orig: "NSUser ==> nsuid: %s | cuid: %s | Account: %s | Nickname: %s | email: %s"
|
||||
trad: "NSUser ==> nsuid: %s | cuid: %s | Compte: %s | Pseudo: %s | email: %s"
|
||||
0
mods/clone/language/fr/clone-fr_2.yaml
Normal file
0
mods/clone/language/fr/clone-fr_2.yaml
Normal file
318
mods/clone/mod_clone.py
Normal file
318
mods/clone/mod_clone.py
Normal file
@@ -0,0 +1,318 @@
|
||||
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 faker import Faker
|
||||
from core.loader import Loader
|
||||
|
||||
class Clone(IModule):
|
||||
|
||||
@dataclass
|
||||
class ModConfModel(schemas.ModConfModel):
|
||||
...
|
||||
|
||||
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'
|
||||
}
|
||||
|
||||
def __init__(self, context: 'Loader') -> None:
|
||||
super().__init__(context)
|
||||
self._mod_config: Optional[schemas.ModConfModel] = None
|
||||
|
||||
@property
|
||||
def mod_config(self) -> ModConfModel:
|
||||
return self._mod_config
|
||||
|
||||
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
|
||||
|
||||
Returns:
|
||||
None: Aucun retour n'es attendu
|
||||
"""
|
||||
|
||||
table_channel = '''CREATE TABLE IF NOT EXISTS clone_list (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
datetime TEXT,
|
||||
nickname TEXT,
|
||||
username TEXT
|
||||
)
|
||||
'''
|
||||
|
||||
# await self.ctx.Base.db_execute_query(table_channel)
|
||||
|
||||
return None
|
||||
|
||||
async def load(self) -> None:
|
||||
|
||||
# Variable qui va contenir les options de configuration du module Defender
|
||||
self._mod_config: schemas.ModConfModel = self.ModConfModel()
|
||||
|
||||
# sync the database with local variable (Mandatory)
|
||||
await self.sync_db()
|
||||
|
||||
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')
|
||||
|
||||
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.ctx.Settings.set_cache('UID_CLONE_DB', self.Clone.UID_CLONE_DB)
|
||||
|
||||
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.ctx.Commands.drop_command_by_module(self.module_name)
|
||||
|
||||
return 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.ctx.Irc.Protocol.get_ircd_protocol_poisition(cmd)
|
||||
if index == -1:
|
||||
return None
|
||||
|
||||
match command:
|
||||
|
||||
case 'PRIVMSG':
|
||||
await self.Utils.handle_on_privmsg(self, cmd)
|
||||
return None
|
||||
|
||||
case 'QUIT':
|
||||
return None
|
||||
|
||||
case _:
|
||||
return None
|
||||
|
||||
except Exception as err:
|
||||
self.ctx.Logs.error(f'General Error: {err}', exc_info=True)
|
||||
return None
|
||||
|
||||
async def hcmds(self, user: str, channel: Any, cmd: list, fullcmd: list = []) -> None:
|
||||
|
||||
try:
|
||||
|
||||
if len(cmd) < 1:
|
||||
return
|
||||
|
||||
command = str(cmd[0]).lower()
|
||||
fromuser = user
|
||||
dnickname = self.ctx.Config.SERVICE_NICKNAME
|
||||
|
||||
match command:
|
||||
|
||||
case 'clone':
|
||||
|
||||
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()
|
||||
|
||||
match option:
|
||||
|
||||
case 'connect':
|
||||
try:
|
||||
# clone connect 5 GroupName 3
|
||||
self.stop = False
|
||||
number_of_clones = int(cmd[2])
|
||||
group = str(cmd[3]).lower()
|
||||
connection_interval = int(cmd[4]) if len(cmd) == 5 else 0.2
|
||||
|
||||
self.ctx.Base.create_asynctask(
|
||||
func=self.Threads.coro_connect_clones(self, number_of_clones, group, False, connection_interval)
|
||||
)
|
||||
|
||||
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:
|
||||
# clone kill [ALL | group name | nickname]
|
||||
self.stop = True
|
||||
option = str(cmd[2])
|
||||
|
||||
if option.lower() == 'all':
|
||||
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.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:
|
||||
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:
|
||||
await self.ctx.Irc.Protocol.send_quit(clone_obj.uid, 'Goood bye', print_log=False)
|
||||
self.Clone.delete(clone_obj.uid)
|
||||
|
||||
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:
|
||||
# clone join [all | group name | nickname] #channel
|
||||
option = str(cmd[2])
|
||||
clone_channel_to_join = str(cmd[3])
|
||||
|
||||
if option.lower() == 'all':
|
||||
|
||||
for clone in self.Clone.UID_CLONE_DB:
|
||||
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.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:
|
||||
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
|
||||
await self.ctx.Irc.Protocol.send_join_chan(uidornickname=clone_uid, channel=clone_channel_to_join, print_log=False)
|
||||
|
||||
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:
|
||||
# clone part [all | nickname] #channel
|
||||
option = str(cmd[2])
|
||||
clone_channel_to_part = str(cmd[3])
|
||||
|
||||
if option.lower() == 'all':
|
||||
|
||||
for clone in self.Clone.UID_CLONE_DB:
|
||||
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.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:
|
||||
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:
|
||||
await self.ctx.Irc.Protocol.send_part_chan(uidornickname=clone_uid, channel=clone_channel_to_part, print_log=False)
|
||||
|
||||
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
|
||||
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:
|
||||
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:
|
||||
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):
|
||||
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)
|
||||
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:
|
||||
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 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.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):
|
||||
await self.ctx.Irc.Protocol.send_notice(
|
||||
nick_from=dnickname,
|
||||
nick_to=fromuser,
|
||||
msg=f"/msg {dnickname} clone say [clone_nickname] #channel message"
|
||||
)
|
||||
return None
|
||||
|
||||
if self.Clone.nickname_exists(clone_name):
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=clone_name, msg=final_message, channel=clone_channel)
|
||||
|
||||
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 _:
|
||||
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 Exception as err:
|
||||
self.ctx.Logs.error(f'General Error: {err}', exc_info=True)
|
||||
23
mods/clone/schemas.py
Normal file
23
mods/clone/schemas.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from core.definition import MainModel, dataclass, field
|
||||
|
||||
@dataclass
|
||||
class ModConfModel(MainModel):
|
||||
clone_nicknames: list[str] = field(default_factory=list)
|
||||
|
||||
@dataclass
|
||||
class MClone(MainModel):
|
||||
"""Model Clone"""
|
||||
connected: bool = False
|
||||
uid: str = None
|
||||
nickname: str = None
|
||||
username: str = None
|
||||
realname: str = None
|
||||
channels: list = field(default_factory=list)
|
||||
vhost: str = None
|
||||
hostname: str = 'localhost'
|
||||
umodes: str = None
|
||||
remote_ip: str = '127.0.0.1'
|
||||
group: str = 'Default',
|
||||
geoip: str = 'XX'
|
||||
|
||||
# DB_CLONES: list[MClone] = []
|
||||
45
mods/clone/threads.py
Normal file
45
mods/clone/threads.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import asyncio
|
||||
from typing import TYPE_CHECKING
|
||||
from time import sleep
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mods.clone.mod_clone import Clone
|
||||
|
||||
async def coro_connect_clones(uplink: 'Clone',
|
||||
number_of_clones:int ,
|
||||
group: str = 'Default',
|
||||
auto_remote_ip: bool = False,
|
||||
interval: float = 0.2
|
||||
):
|
||||
|
||||
for i in range(0, number_of_clones):
|
||||
uplink.Utils.create_new_clone(
|
||||
uplink=uplink,
|
||||
faker_instance=uplink.Faker,
|
||||
group=group,
|
||||
auto_remote_ip=auto_remote_ip
|
||||
)
|
||||
|
||||
for clone in uplink.Clone.UID_CLONE_DB:
|
||||
|
||||
if uplink.stop:
|
||||
print(f"Stop creating clones ...")
|
||||
uplink.stop = False
|
||||
break
|
||||
|
||||
if not clone.connected:
|
||||
await uplink.ctx.Irc.Protocol.send_uid(clone.nickname, clone.username, clone.hostname, clone.uid, clone.umodes, clone.vhost, clone.remote_ip, clone.realname, clone.geoip, 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)
|
||||
|
||||
await asyncio.sleep(interval)
|
||||
clone.connected = True
|
||||
|
||||
async def thread_kill_clones(uplink: 'Clone'):
|
||||
|
||||
clone_to_kill = uplink.Clone.UID_CLONE_DB.copy()
|
||||
|
||||
for clone in clone_to_kill:
|
||||
await uplink.ctx.Irc.Protocol.send_quit(clone.uid, 'Gooood bye', print_log=False)
|
||||
uplink.Clone.delete(clone.uid)
|
||||
|
||||
del clone_to_kill
|
||||
212
mods/clone/utils.py
Normal file
212
mods/clone/utils.py
Normal file
@@ -0,0 +1,212 @@
|
||||
import logging
|
||||
import random
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
from faker import Faker
|
||||
|
||||
logging.getLogger('faker').setLevel(logging.CRITICAL)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mods.clone.mod_clone import Clone
|
||||
|
||||
def create_faker_object(faker_local: Optional[str] = 'en_GB') -> Faker:
|
||||
"""Create a new faker object
|
||||
|
||||
Args:
|
||||
faker_local (Optional[str], optional): _description_. Defaults to 'en_GB'.
|
||||
|
||||
Returns:
|
||||
Faker: The Faker Object
|
||||
"""
|
||||
if faker_local not in ['en_GB', 'fr_FR']:
|
||||
faker_local = 'en_GB'
|
||||
|
||||
return Faker(faker_local)
|
||||
|
||||
def generate_uid_for_clone(faker_instance: 'Faker', server_id: str) -> str:
|
||||
chaine = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
return server_id + ''.join(faker_instance.random_sample(chaine, 6))
|
||||
|
||||
def generate_vhost_for_clone(faker_instance: 'Faker') -> str:
|
||||
"""Generate new vhost for the clone
|
||||
|
||||
Args:
|
||||
faker_instance (Faker): The Faker instance
|
||||
|
||||
Returns:
|
||||
str: _description_
|
||||
"""
|
||||
rand_1 = faker_instance.random_elements(['A','B','C','D','E','F','0','1','2','3','4','5','6','7','8','9'], unique=True, length=8)
|
||||
rand_2 = faker_instance.random_elements(['A','B','C','D','E','F','0','1','2','3','4','5','6','7','8','9'], unique=True, length=8)
|
||||
rand_3 = faker_instance.random_elements(['A','B','C','D','E','F','0','1','2','3','4','5','6','7','8','9'], unique=True, length=8)
|
||||
|
||||
vhost = ''.join(rand_1) + '.' + ''.join(rand_2) + '.' + ''.join(rand_3) + '.IP'
|
||||
return vhost
|
||||
|
||||
def generate_username_for_clone(faker_instance: 'Faker') -> str:
|
||||
"""Generate vhosts for clones
|
||||
|
||||
Returns:
|
||||
str: The vhost
|
||||
"""
|
||||
chaine = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||
return ''.join(faker_instance.random_sample(chaine, 9))
|
||||
|
||||
def generate_realname_for_clone(faker_instance: 'Faker') -> tuple[int, str, str]:
|
||||
"""Generate realname for clone
|
||||
Ex: XX F|M Department
|
||||
Args:
|
||||
faker_instance (Faker): _description_
|
||||
|
||||
Returns:
|
||||
tuple: Age | Gender | Department
|
||||
"""
|
||||
# Create realname XX F|M Department
|
||||
gender = faker_instance.random_choices(['F','M'], 1)
|
||||
gender = ''.join(gender)
|
||||
age = random.randint(20, 60)
|
||||
if faker_instance.locales[0] == 'fr_FR':
|
||||
department = faker_instance.department_name()
|
||||
else:
|
||||
department = faker_instance.city()
|
||||
|
||||
return (age, gender, department)
|
||||
|
||||
def generate_nickname_for_clone(faker_instance: 'Faker', gender: Optional[str] = 'AUTO') -> str:
|
||||
"""Generate nickname for clone
|
||||
|
||||
Args:
|
||||
faker_instance (Faker): The Faker Instance
|
||||
gender (str): The Gender.Default F
|
||||
|
||||
Returns:
|
||||
str: Nickname Based on the Gender
|
||||
"""
|
||||
if gender.upper() == 'AUTO' or gender.upper() not in ['F', 'M']:
|
||||
# Generate new gender
|
||||
gender = faker_instance.random_choices(['F','M'], 1)
|
||||
gender = ''.join(gender)
|
||||
|
||||
if gender.upper() == 'F':
|
||||
return faker_instance.first_name_female()
|
||||
elif gender.upper() == 'M':
|
||||
return faker_instance.first_name_male()
|
||||
|
||||
def generate_ipv4_for_clone(faker_instance: 'Faker', auto: bool = True) -> str:
|
||||
"""Generate remote ipv4 for clone
|
||||
|
||||
Args:
|
||||
faker_instance (Faker): The Faker Instance
|
||||
auto (bool): Set auto generation of ip or 127.0.0.1 will be returned
|
||||
|
||||
Returns:
|
||||
str: Remote IPV4
|
||||
"""
|
||||
return faker_instance.ipv4_private() if auto else '127.0.0.1'
|
||||
|
||||
def generate_country_code_for_clone(faker_instance: 'Faker') -> str:
|
||||
"""Generate the alpha-2 country code for clone
|
||||
|
||||
Args:
|
||||
faker_instance (Faker): The Faker Instance
|
||||
|
||||
Returns:
|
||||
str: The Country Code
|
||||
"""
|
||||
return faker_instance.country_code('alpha-2')
|
||||
|
||||
def generate_hostname_for_clone(faker_instance: 'Faker') -> str:
|
||||
"""Generate hostname for clone
|
||||
|
||||
Args:
|
||||
faker_instance (Faker): The Faker Instance
|
||||
|
||||
Returns:
|
||||
str: New hostname
|
||||
"""
|
||||
return faker_instance.hostname()
|
||||
|
||||
def create_new_clone(uplink: 'Clone', faker_instance: 'Faker', group: str = 'Default', auto_remote_ip: bool = False) -> bool:
|
||||
"""Create a new Clone object in the DB_CLONES.
|
||||
|
||||
Args:
|
||||
faker_instance (Faker): The Faker instance
|
||||
|
||||
Returns:
|
||||
bool: True if it was created
|
||||
"""
|
||||
faker = faker_instance
|
||||
|
||||
uid = generate_uid_for_clone(faker, uplink.ctx.Config.SERVEUR_ID)
|
||||
umodes = uplink.ctx.Config.CLONE_UMODES
|
||||
|
||||
# Generate Username
|
||||
username = generate_username_for_clone(faker)
|
||||
|
||||
# Generate realname (XX F|M Department)
|
||||
age, gender, department = generate_realname_for_clone(faker)
|
||||
realname = f'{age} {gender} {department}'
|
||||
|
||||
# Generate nickname
|
||||
nickname = generate_nickname_for_clone(faker, gender)
|
||||
|
||||
# Generate decoded ipv4 and hostname
|
||||
decoded_ip = generate_ipv4_for_clone(faker, auto_remote_ip)
|
||||
hostname = generate_hostname_for_clone(faker)
|
||||
vhost = generate_vhost_for_clone(faker)
|
||||
|
||||
geoip = generate_country_code_for_clone(faker)
|
||||
|
||||
checkNickname = uplink.Clone.nickname_exists(nickname)
|
||||
checkUid = uplink.Clone.uid_exists(uid=uid)
|
||||
|
||||
while checkNickname:
|
||||
caracteres = '0123456789'
|
||||
randomize = ''.join(random.choice(caracteres) for _ in range(2))
|
||||
nickname = nickname + str(randomize)
|
||||
checkNickname = uplink.Clone.nickname_exists(nickname)
|
||||
|
||||
while checkUid:
|
||||
uid = generate_uid_for_clone(faker, uplink.ctx.Config.SERVEUR_ID)
|
||||
checkUid = uplink.Clone.uid_exists(uid=uid)
|
||||
|
||||
clone = uplink.Schemas.MClone(
|
||||
connected=False,
|
||||
nickname=nickname,
|
||||
username=username,
|
||||
realname=realname,
|
||||
hostname=hostname,
|
||||
umodes=umodes,
|
||||
uid=uid,
|
||||
remote_ip=decoded_ip,
|
||||
vhost=vhost,
|
||||
group=group,
|
||||
channels=[],
|
||||
geoip=geoip
|
||||
)
|
||||
|
||||
uplink.Clone.insert(clone)
|
||||
|
||||
return True
|
||||
|
||||
async def handle_on_privmsg(uplink: 'Clone', srvmsg: list[str]) -> None:
|
||||
|
||||
senderObj, recieverObj, channel, message = uplink.ctx.Irc.Protocol.parse_privmsg(srvmsg)
|
||||
|
||||
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.ctx.Config.SERVICE_ID:
|
||||
final_message = f"{senderObj.nickname}!{senderObj.username}@{senderObj.hostname} > {senderMsg.lstrip(':')}"
|
||||
await uplink.ctx.Irc.Protocol.send_priv_msg(
|
||||
nick_from=clone_obj.uid,
|
||||
msg=final_message,
|
||||
channel=uplink.ctx.Config.CLONE_CHANNEL
|
||||
)
|
||||
|
||||
return None
|
||||
946
mods/command/mod_command.py
Normal file
946
mods/command/mod_command.py
Normal file
@@ -0,0 +1,946 @@
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
from dataclasses import dataclass
|
||||
from core.classes.interfaces.imodule import IModule
|
||||
import mods.command.utils as utils
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.definition import MUser
|
||||
from core.loader import Loader
|
||||
|
||||
class Command(IModule):
|
||||
|
||||
@dataclass
|
||||
class ModConfModel:
|
||||
"""The Model containing the module parameters
|
||||
"""
|
||||
pass
|
||||
|
||||
MOD_HEADER: dict[str, str] = {
|
||||
'name':'Command',
|
||||
'version':'1.0.0',
|
||||
'description':'Module contains all IRC commands',
|
||||
'author':'Defender Team',
|
||||
'core_version':'Defender-6'
|
||||
}
|
||||
|
||||
def __init__(self, uplink: 'Loader'):
|
||||
super().__init__(uplink)
|
||||
self._mod_config: Optional[Command.ModConfModel] = self.ModConfModel()
|
||||
|
||||
@property
|
||||
def mod_config(self) -> ModConfModel:
|
||||
return self._mod_config
|
||||
|
||||
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
|
||||
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_automode = '''CREATE TABLE IF NOT EXISTS command_automode (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
created_on TEXT,
|
||||
updated_on TEXT,
|
||||
nickname TEXT,
|
||||
channel TEXT,
|
||||
mode TEXT
|
||||
)
|
||||
'''
|
||||
|
||||
await self.ctx.Base.db_execute_query(table_automode)
|
||||
return None
|
||||
|
||||
async def load(self) -> None:
|
||||
|
||||
# Create the database
|
||||
await self.create_tables()
|
||||
|
||||
# Module Utils
|
||||
self.mod_utils = utils
|
||||
self.user_to_notice: str = ''
|
||||
self.show_219: bool = True
|
||||
|
||||
# Register new commands into the protocol
|
||||
new_cmds = {'403', '401', '006', '018', '219', '223'}
|
||||
for c in new_cmds:
|
||||
self.ctx.Irc.Protocol.known_protocol.add(c)
|
||||
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'join', 'Join a channel')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'part', 'Leave a channel')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'owner', 'Give channel ownership to a user')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'deowner', 'Remove channel ownership from a user')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'protect', 'Protect a user from being kicked')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'deprotect', 'Remove protection from a user')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'op', 'Grant operator privileges to a user')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'deop', 'Remove operator privileges from a user')
|
||||
self.ctx.Commands.build_command(1, self.module_name, 'halfop', 'Grant half-operator privileges to a user')
|
||||
self.ctx.Commands.build_command(1, self.module_name, 'dehalfop', 'Remove half-operator privileges from a user')
|
||||
self.ctx.Commands.build_command(1, self.module_name, 'voice', 'Grant voice privileges to a user')
|
||||
self.ctx.Commands.build_command(1, self.module_name, 'devoice', 'Remove voice privileges from a user')
|
||||
self.ctx.Commands.build_command(1, self.module_name, 'topic', 'Change the topic of a channel')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'opall', 'Grant operator privileges to all users')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'deopall', 'Remove operator privileges from all users')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'devoiceall', 'Remove voice privileges from all users')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'voiceall', 'Grant voice privileges to all users')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'ban', 'Ban a user from a channel')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'automode', 'Automatically set user modes upon join')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'unban', 'Remove a ban from a user')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'kick', 'Kick a user from a channel')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'kickban', 'Kick and ban a user from a channel')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'umode', 'Set user mode')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'mode', 'Set channel mode')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'get_mode', 'Retrieve current channel mode')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'svsjoin', 'Force a user to join a channel')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'svspart', 'Force a user to leave a channel')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'svsnick', 'Force a user to change their nickname')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'wallops', 'Send a message to all operators')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'globops', 'Send a global operator message')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'gnotice', 'Send a global notice')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'whois', 'Get information about a user')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'names', 'List users in a channel')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'invite', 'Invite a user to a channel')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'inviteme', 'Invite yourself to a channel')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'sajoin', 'Force yourself into a channel')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'sapart', 'Force yourself to leave a channel')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'kill', 'Disconnect a user from the server')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'gline', 'Ban a user from the entire server')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'ungline', 'Remove a global server ban')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'kline', 'Ban a user based on their hostname')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'unkline', 'Remove a K-line ban')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'shun', 'Prevent a user from sending messages')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'unshun', 'Remove a shun from a user')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'glinelist', 'List all global bans')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'shunlist', 'List all shunned users')
|
||||
self.ctx.Commands.build_command(2, self.module_name, 'klinelist', 'List all K-line bans')
|
||||
self.ctx.Commands.build_command(3, self.module_name, 'map', 'Show the server network map')
|
||||
|
||||
def unload(self) -> None:
|
||||
self.ctx.Commands.drop_command_by_module(self.module_name)
|
||||
return None
|
||||
|
||||
async def cmd(self, data: list[str]) -> None:
|
||||
try:
|
||||
# service_id = self.ctx.Config.SERVICE_ID
|
||||
dnickname = self.ctx.Config.SERVICE_NICKNAME
|
||||
# dchanlog = self.ctx.Config.SERVICE_CHANLOG
|
||||
red = self.ctx.Config.COLORS.red
|
||||
green = self.ctx.Config.COLORS.green
|
||||
bold = self.ctx.Config.COLORS.bold
|
||||
nogc = self.ctx.Config.COLORS.nogc
|
||||
cmd = list(data).copy()
|
||||
|
||||
pos, parsed_cmd = self.ctx.Irc.Protocol.get_ircd_protocol_poisition(cmd=cmd, log=True)
|
||||
|
||||
if pos == -1:
|
||||
return None
|
||||
|
||||
match parsed_cmd:
|
||||
# [':irc.deb.biz.st', '403', 'Dev-PyDefender', '#Z', ':No', 'such', 'channel']
|
||||
case '403' | '401':
|
||||
try:
|
||||
message = ' '.join(cmd[3:])
|
||||
await self.ctx.Irc.Protocol.send_notice(
|
||||
nick_from=dnickname,
|
||||
nick_to=self.user_to_notice,
|
||||
msg=f"[{red}ERROR MSG{nogc}] {message}"
|
||||
)
|
||||
self.ctx.Logs.error(f"{cmd[1]} - {message}")
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(ke)
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case '006' | '018':
|
||||
try:
|
||||
# [':irc.deb.biz.st', '006', 'Dev-PyDefender', ':`-services.deb.biz.st', '------', '|', 'Users:', '9', '(47.37%)', '[00B]']
|
||||
# [':irc.deb.biz.st', '018', 'Dev-PyDefender', ':4', 'servers', 'and', '19', 'users,', 'average', '4.75', 'users', 'per', 'server']
|
||||
message = ' '.join(cmd[3:])
|
||||
await self.ctx.Irc.Protocol.send_notice(
|
||||
nick_from=dnickname,
|
||||
nick_to=self.user_to_notice,
|
||||
msg=f"[{green}SERVER MSG{nogc}] {message}"
|
||||
)
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(ke)
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case '219':
|
||||
try:
|
||||
# [':irc.deb.biz.st', '219', 'Dev-PyDefender', 's', ':End', 'of', '/STATS', 'report']
|
||||
if not self.show_219:
|
||||
# If there is a result in 223 then stop here
|
||||
self.show_219 = True
|
||||
return None
|
||||
|
||||
type_of_stats = str(cmd[3])
|
||||
|
||||
match type_of_stats:
|
||||
case 's':
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname,nick_to=self.user_to_notice, msg="No shun")
|
||||
case 'G':
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname,nick_to=self.user_to_notice, msg="No gline")
|
||||
case 'k':
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname,nick_to=self.user_to_notice, msg="No kline")
|
||||
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(ke)
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case '223':
|
||||
try:
|
||||
# [':irc.deb.biz.st', '223', 'Dev-PyDefender', 'G', '*@162.142.125.217', '67624', '18776', 'irc.deb.biz.st', ':Proxy/Drone', 'detected.', 'Check', 'https://dronebl.org/lookup?ip=162.142.125.217', 'for', 'details.']
|
||||
self.show_219 = False
|
||||
host = str(cmd[4])
|
||||
author = str(cmd[7])
|
||||
reason = ' '.join(cmd[8:])
|
||||
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname,nick_to=self.user_to_notice,
|
||||
msg=f"{bold}Author{nogc}: {author} - {bold}Host{nogc}: {host} - {bold}Reason{nogc}: {reason}"
|
||||
)
|
||||
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(ke)
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'SJOIN':
|
||||
# ['@msgid=yldTlbwAGbzCGUcCIHi3ku;time=2024-11-11T17:56:24.297Z', ':001', 'SJOIN', '1728815963', '#znc', ':001LQ0L0C']
|
||||
# Check if the user has an automode
|
||||
try:
|
||||
user_uid = self.ctx.User.clean_uid(cmd[5])
|
||||
userObj: MUser = self.ctx.User.get_user(user_uid)
|
||||
channel_name = cmd[4] if self.ctx.Channel.is_valid_channel(cmd[4]) else None
|
||||
nickname = userObj.nickname if userObj is not None else None
|
||||
|
||||
if userObj is None:
|
||||
return None
|
||||
|
||||
if 'r' not in userObj.umodes and 'o' not in userObj.umodes:
|
||||
return None
|
||||
|
||||
db_data: dict[str, str] = {"nickname": nickname.lower(), "channel": channel_name.lower()}
|
||||
db_query = await self.ctx.Base.db_execute_query("SELECT id, mode FROM command_automode WHERE LOWER(nickname) = :nickname AND LOWER(channel) = :channel", db_data)
|
||||
db_result = db_query.fetchone()
|
||||
if db_result:
|
||||
id, mode = db_result
|
||||
await self.ctx.Irc.Protocol.send2socket(f":{self.ctx.Config.SERVICE_ID} MODE {channel_name} {mode} {userObj.nickname}")
|
||||
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(f"Key Error: {err}")
|
||||
|
||||
case _:
|
||||
pass
|
||||
|
||||
except Exception as err:
|
||||
self.ctx.Logs.error(f"General Error: {err}", exc_info=True)
|
||||
|
||||
async def hcmds(self, uidornickname: str, channel_name: Optional[str], cmd: list, fullcmd: list = []):
|
||||
|
||||
command = str(cmd[0]).lower()
|
||||
dnickname = self.ctx.Config.SERVICE_NICKNAME
|
||||
service_id = self.ctx.Config.SERVICE_ID
|
||||
dchanlog = self.ctx.Config.SERVICE_CHANLOG
|
||||
self.user_to_notice = uidornickname
|
||||
fromuser = uidornickname
|
||||
fromchannel = channel_name
|
||||
|
||||
match command:
|
||||
|
||||
case 'automode':
|
||||
try:
|
||||
await self.mod_utils.set_automode(self, cmd, fromuser)
|
||||
except IndexError:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {command.upper()} SET [nickname] [+/-mode] [#channel]")
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {command.upper()} LIST")
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"[AUTOMODES AVAILABLE] are {' / '.join(self.ctx.Settings.PROTOCTL_PREFIX)}")
|
||||
except Exception as err:
|
||||
self.ctx.Logs.error(f"General Error: {err}")
|
||||
|
||||
case 'deopall':
|
||||
try:
|
||||
await self.mod_utils.set_deopall(self, fromchannel)
|
||||
except Exception as err:
|
||||
self.ctx.Logs.error(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'devoiceall':
|
||||
try:
|
||||
await self.mod_utils.set_devoiceall(self, fromchannel)
|
||||
except Exception as err:
|
||||
self.ctx.Logs.error(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'voiceall':
|
||||
try:
|
||||
await self.mod_utils.set_mode_to_all(self, fromchannel, '+', 'v')
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'opall':
|
||||
try:
|
||||
await self.mod_utils.set_mode_to_all(self, fromchannel, '+', 'o')
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'op':
|
||||
try:
|
||||
await self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '+o')
|
||||
except IndexError as e:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} op [#SALON] [NICKNAME]")
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'deop':
|
||||
try:
|
||||
await self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '-o')
|
||||
except IndexError as e:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} deop [#SALON] [NICKNAME]")
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'owner':
|
||||
try:
|
||||
await self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '+q')
|
||||
|
||||
except IndexError as e:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} owner [#SALON] [NICKNAME]")
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'deowner':
|
||||
try:
|
||||
await self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '-q')
|
||||
|
||||
except IndexError as e:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} deowner [#SALON] [NICKNAME]")
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'protect':
|
||||
try:
|
||||
await self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '+a')
|
||||
|
||||
except IndexError as e:
|
||||
self.ctx.Logs.warning(f'_hcmd DEOWNER: {str(e)}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON] [NICKNAME]")
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'deprotect':
|
||||
try:
|
||||
await self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '-a')
|
||||
|
||||
except IndexError as e:
|
||||
self.ctx.Logs.warning(f'_hcmd DEOWNER: {str(e)}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON] [NICKNAME]")
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'halfop':
|
||||
try:
|
||||
await self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '+h')
|
||||
|
||||
except IndexError as e:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} {command} [#SALON] [NICKNAME]")
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'dehalfop':
|
||||
try:
|
||||
await self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '-h')
|
||||
|
||||
except IndexError as e:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} dehalfop [#SALON] [NICKNAME]")
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'voice':
|
||||
try:
|
||||
await self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '+v')
|
||||
except IndexError as e:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} voice [#SALON] [NICKNAME]")
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'devoice':
|
||||
try:
|
||||
await self.mod_utils.set_operation(self, cmd, fromchannel, fromuser, '-v')
|
||||
except IndexError as e:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} devoice [#SALON] [NICKNAME]")
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'ban':
|
||||
try:
|
||||
await self.mod_utils.set_ban(self, cmd, '+', fromuser)
|
||||
except IndexError as e:
|
||||
self.ctx.Logs.warning(f'_hcmd BAN: {str(e)}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON] [NICKNAME]")
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'unban':
|
||||
try:
|
||||
await self.mod_utils.set_ban(self, cmd, '-', fromuser)
|
||||
except IndexError as e:
|
||||
self.ctx.Logs.warning(f'_hcmd UNBAN: {str(e)}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON] [NICKNAME]")
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'kick':
|
||||
try:
|
||||
await self.mod_utils.set_kick(self, cmd, fromuser)
|
||||
except IndexError as e:
|
||||
self.ctx.Logs.warning(f'_hcmd KICK: {str(e)}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON] [NICKNAME] [REASON]")
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'kickban':
|
||||
try:
|
||||
await self.mod_utils.set_kickban(self, cmd, fromuser)
|
||||
except IndexError as e:
|
||||
self.ctx.Logs.warning(f'_hcmd KICKBAN: {str(e)}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON] [NICKNAME] [REASON]")
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'join':
|
||||
try:
|
||||
await self.mod_utils.set_assign_channel_to_service(self, cmd, fromuser)
|
||||
except IndexError as ie:
|
||||
self.ctx.Logs.debug(f'{ie}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON]")
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'part':
|
||||
try:
|
||||
# Syntax. !part #channel
|
||||
await self.mod_utils.set_unassign_channel_to_service(self, cmd, fromuser)
|
||||
except IndexError as ie:
|
||||
self.ctx.Logs.debug(f'{ie}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} {command.upper()} [#SALON]")
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'topic':
|
||||
try:
|
||||
if len(cmd) == 1:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} TOPIC #channel THE_TOPIC_MESSAGE")
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} TOPIC #channel THE_TOPIC_MESSAGE")
|
||||
return None
|
||||
|
||||
chan = str(cmd[1])
|
||||
if not self.ctx.Channel.is_valid_channel(chan):
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="The channel must start with #")
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} TOPIC #channel THE_TOPIC_MESSAGE")
|
||||
return None
|
||||
|
||||
topic_msg = ' '.join(cmd[2:]).strip()
|
||||
|
||||
if topic_msg:
|
||||
await self.ctx.Irc.Protocol.send2socket(f':{dnickname} TOPIC {chan} :{topic_msg}')
|
||||
else:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="You need to specify the topic")
|
||||
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(ke)
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'wallops':
|
||||
try:
|
||||
if len(cmd) == 1:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} WALLOPS THE_WALLOPS_MESSAGE")
|
||||
return None
|
||||
|
||||
wallops_msg = ' '.join(cmd[1:]).strip()
|
||||
|
||||
if wallops_msg:
|
||||
await self.ctx.Irc.Protocol.send2socket(f':{dnickname} WALLOPS {wallops_msg} ({dnickname})')
|
||||
else:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="You need to specify the wallops message")
|
||||
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(ke)
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'globops':
|
||||
try:
|
||||
if len(cmd) == 1:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} GLOBOPS THE_GLOBOPS_MESSAGE")
|
||||
return None
|
||||
|
||||
globops_msg = ' '.join(cmd[1:]).strip()
|
||||
|
||||
if globops_msg:
|
||||
await self.ctx.Irc.Protocol.send2socket(f':{dnickname} GLOBOPS {globops_msg} ({dnickname})')
|
||||
else:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="You need to specify the globops message")
|
||||
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(ke)
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'gnotice':
|
||||
try:
|
||||
if len(cmd) == 1:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {str(cmd[0]).upper()} THE_GLOBAL_NOTICE_MESSAGE")
|
||||
return None
|
||||
|
||||
gnotice_msg = ' '.join(cmd[1:]).strip()
|
||||
|
||||
if gnotice_msg:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to='$*.*', msg=f"[{self.ctx.Config.COLORS.red}GLOBAL NOTICE{self.ctx.Config.COLORS.nogc}] {gnotice_msg}")
|
||||
else:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="You need to specify the global notice message")
|
||||
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(ke)
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'whois':
|
||||
try:
|
||||
self.user_to_notice = fromuser
|
||||
if len(cmd) == 1:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {str(cmd[0]).upper()} NICKNAME")
|
||||
return None
|
||||
|
||||
nickname = str(cmd[1])
|
||||
|
||||
if self.ctx.User.get_nickname(nickname) is None:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="Nickname not found !")
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {str(cmd[0]).upper()} NICKNAME")
|
||||
return None
|
||||
|
||||
await self.ctx.Irc.Protocol.send2socket(f':{dnickname} WHOIS {nickname}')
|
||||
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(ke)
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'names':
|
||||
try:
|
||||
if len(cmd) == 1:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {str(cmd[0]).upper()} #CHANNEL")
|
||||
return None
|
||||
|
||||
chan = str(cmd[1])
|
||||
|
||||
if not self.ctx.Channel.is_valid_channel(chan):
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="The channel must start with #")
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {str(cmd[0]).upper()} #channel")
|
||||
return None
|
||||
|
||||
await self.ctx.Irc.Protocol.send2socket(f':{dnickname} NAMES {chan}')
|
||||
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(ke)
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'invite':
|
||||
try:
|
||||
if len(cmd) < 3:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {str(cmd[0]).upper()} NICKNAME #CHANNEL")
|
||||
return None
|
||||
|
||||
nickname = str(cmd[1])
|
||||
chan = str(cmd[2])
|
||||
|
||||
if not self.ctx.Channel.is_valid_channel(chan):
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="The channel must start with #")
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {str(cmd[0]).upper()} NICKNAME #CHANNEL")
|
||||
return None
|
||||
|
||||
if self.ctx.User.get_nickname(nickname) is None:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="Nickname not found !")
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {str(cmd[0]).upper()} NICKNAME #CHANNEL")
|
||||
return None
|
||||
|
||||
await self.ctx.Irc.Protocol.send2socket(f':{dnickname} INVITE {nickname} {chan}')
|
||||
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(f"KeyError: {ke}")
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'inviteme':
|
||||
try:
|
||||
if len(cmd) == 0:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} {str(cmd[0]).upper()}")
|
||||
return None
|
||||
|
||||
await self.ctx.Irc.Protocol.send2socket(f':{dnickname} INVITE {fromuser} {self.ctx.Config.SERVICE_CHANLOG}')
|
||||
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(f"KeyError: {ke}")
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'map':
|
||||
try:
|
||||
self.user_to_notice = fromuser
|
||||
await self.ctx.Irc.Protocol.send2socket(f':{dnickname} MAP')
|
||||
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(f"KeyError: {ke}")
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'umode':
|
||||
try:
|
||||
# .umode nickname +mode
|
||||
if len(cmd) < 2:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} {command.upper()} [NICKNAME] [+/-]mode")
|
||||
return None
|
||||
|
||||
nickname = str(cmd[1])
|
||||
umode = str(cmd[2])
|
||||
|
||||
await self.ctx.Irc.Protocol.send_svsmode(nickname=nickname, user_mode=umode)
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(ke)
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'mode':
|
||||
# .mode #channel +/-mode
|
||||
# .mode +/-mode
|
||||
try:
|
||||
|
||||
if len(cmd) < 2:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} {command.upper()} [#CHANNEL] [+/-]mode")
|
||||
return None
|
||||
|
||||
if fromchannel is None:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} {command.upper()} [#CHANNEL] [+/-]mode")
|
||||
return None
|
||||
|
||||
if len(cmd) == 2:
|
||||
channel_mode = cmd[1]
|
||||
if self.ctx.Channel.is_valid_channel(fromchannel):
|
||||
await self.ctx.Irc.Protocol.send2socket(f":{dnickname} MODE {fromchannel} {channel_mode}")
|
||||
else:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : Channel [{fromchannel}] is not correct should start with #")
|
||||
return None
|
||||
|
||||
if len(cmd) == 3:
|
||||
provided_channel = cmd[1]
|
||||
channel_mode = cmd[2]
|
||||
await self.ctx.Irc.Protocol.send2socket(f":{service_id} MODE {provided_channel} {channel_mode}")
|
||||
return None
|
||||
|
||||
except IndexError as e:
|
||||
self.ctx.Logs.warning(f'_hcmd OP: {str(e)}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} {command.upper()} [#CHANNEL] [+/-]mode")
|
||||
except Exception as err:
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'get_mode':
|
||||
try:
|
||||
await self.ctx.Irc.Protocol.send2socket(f'MODE {fromchannel}')
|
||||
except Exception as err:
|
||||
self.ctx.Logs.error(f"General Error {err}")
|
||||
|
||||
case 'svsjoin':
|
||||
try:
|
||||
# SVSJOIN <nick> <channel>[,<channel2>..] [key1[,key2[..]]]
|
||||
if len(cmd) < 4:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSJOIN <nick> <channel>[,<channel2>..] [key1[,key2[..]]]")
|
||||
return None
|
||||
|
||||
nickname = str(cmd[1])
|
||||
channels = str(cmd[2]).split(',')
|
||||
keys = str(cmd[3]).split(',')
|
||||
|
||||
await self.ctx.Irc.Protocol.send_svsjoin(nickname, channels, keys)
|
||||
except IndexError as ke:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSJOIN <nick> <channel>[,<channel2>..] [key1[,key2[..]]]")
|
||||
self.ctx.Logs.error(ke)
|
||||
except Exception as err:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSJOIN <nick> <channel>[,<channel2>..] [key1[,key2[..]]]")
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'svspart':
|
||||
try:
|
||||
# SVSPART <nick> <channel>[,<channel2>..] [<comment>]
|
||||
if len(cmd) < 4:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSPART <nick> <channel>[,<channel2>..] [<comment>]")
|
||||
return None
|
||||
|
||||
nickname = str(cmd[1])
|
||||
channels = str(cmd[2]).split(',')
|
||||
reason = ' '.join(cmd[3:])
|
||||
|
||||
await self.ctx.Irc.Protocol.send_svspart(nickname, channels, reason)
|
||||
except IndexError as ke:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSPART <nick> <channel>[,<channel2>..] [<comment>]")
|
||||
self.ctx.Logs.error(ke)
|
||||
except Exception as err:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSPART <nick> <channel>[,<channel2>..] [<comment>]")
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'svsnick':
|
||||
try:
|
||||
# .svsnick nickname newnickname
|
||||
nickname = str(cmd[1])
|
||||
newnickname = str(cmd[2])
|
||||
unixtime = self.ctx.Utils.get_unixtime()
|
||||
|
||||
if self.ctx.User.get_nickname(nickname) is None:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" This nickname do not exist")
|
||||
return None
|
||||
|
||||
if len(cmd) != 3:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname newnickname")
|
||||
return None
|
||||
|
||||
await self.ctx.Irc.Protocol.send2socket(f':{self.ctx.Config.SERVEUR_ID} SVSNICK {nickname} {newnickname} {unixtime}')
|
||||
|
||||
except IndexError as ke:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname newnickname")
|
||||
self.ctx.Logs.error(ke)
|
||||
except Exception as err:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname newnickname")
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'sajoin':
|
||||
try:
|
||||
# .sajoin nickname #channel
|
||||
nickname = str(cmd[1])
|
||||
channel = str(cmd[2])
|
||||
if len(cmd) < 3:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname #channel")
|
||||
return None
|
||||
|
||||
await self.ctx.Irc.Protocol.send_sajoin(nick_to_sajoin=nickname, channel_name=channel)
|
||||
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(ke)
|
||||
except Exception as err:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname #channel")
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'sapart':
|
||||
try:
|
||||
# .sapart nickname #channel
|
||||
if len(cmd) < 3:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname #channel")
|
||||
return None
|
||||
|
||||
nickname = str(cmd[1])
|
||||
channel = str(cmd[2])
|
||||
|
||||
await self.ctx.Irc.Protocol.send_sapart(nick_to_sapart=nickname, channel_name=channel)
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(ke)
|
||||
except Exception as err:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname #channel")
|
||||
self.ctx.Logs.error(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'kill':
|
||||
try:
|
||||
# 'kill', 'gline', 'ungline', 'shun', 'unshun'
|
||||
# .kill nickname reason
|
||||
if len(cmd) < 3:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname reason")
|
||||
return None
|
||||
|
||||
nickname = str(cmd[1])
|
||||
kill_reason = ' '.join(cmd[2:])
|
||||
|
||||
await self.ctx.Irc.Protocol.send2socket(f":{service_id} KILL {nickname} {kill_reason} ({self.ctx.Config.COLORS.red}{dnickname}{self.ctx.Config.COLORS.nogc})")
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(ke)
|
||||
except Exception as err:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} SVSNICK nickname newnickname")
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'gline':
|
||||
try:
|
||||
# TKL + G user host set_by expire_timestamp set_at_timestamp :reason
|
||||
# .gline [nickname] [host] [reason]
|
||||
if len(cmd) < 4:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname host reason")
|
||||
return None
|
||||
|
||||
nickname = str(cmd[1])
|
||||
hostname = str(cmd[2])
|
||||
set_at_timestamp = self.ctx.Utils.get_unixtime()
|
||||
expire_time = (60 * 60 * 24) + set_at_timestamp
|
||||
gline_reason = ' '.join(cmd[3:])
|
||||
|
||||
if nickname == '*' and hostname == '*':
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" You want to close the server ? i would recommand ./unrealircd stop :)")
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname host reason")
|
||||
return None
|
||||
|
||||
await self.ctx.Irc.Protocol.send_gline(nickname=nickname, hostname=hostname, set_by=dnickname, expire_timestamp=expire_time, set_at_timestamp=set_at_timestamp, reason=gline_reason)
|
||||
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(ke)
|
||||
except Exception as err:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname host reason")
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'ungline':
|
||||
try:
|
||||
# 'shun', 'unshun'
|
||||
# TKL + G user host set_by expire_timestamp set_at_timestamp :reason
|
||||
# .ungline nickname host
|
||||
if len(cmd) < 2:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname hostname")
|
||||
return None
|
||||
|
||||
nickname = str(cmd[1])
|
||||
hostname = str(cmd[2])
|
||||
|
||||
# await self.ctx.Irc.Protocol.send2socket(f":{self.ctx.Config.SERVEUR_ID} TKL - G {nickname} {hostname} {dnickname}")
|
||||
await self.ctx.Irc.Protocol.send_ungline(nickname=nickname, hostname=hostname)
|
||||
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(ke)
|
||||
except Exception as err:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname hostname")
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'kline':
|
||||
try:
|
||||
# TKL + k user host set_by expire_timestamp set_at_timestamp :reason
|
||||
# .gline [nickname] [host] [reason]
|
||||
if len(cmd) < 4:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname host reason")
|
||||
return None
|
||||
|
||||
nickname = str(cmd[1])
|
||||
hostname = str(cmd[2])
|
||||
set_at_timestamp = self.ctx.Utils.get_unixtime()
|
||||
expire_time = (60 * 60 * 24) + set_at_timestamp
|
||||
gline_reason = ' '.join(cmd[3:])
|
||||
|
||||
if nickname == '*' and hostname == '*':
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" You want to close the server ? i would recommand ./unrealircd stop :)")
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname host reason")
|
||||
return None
|
||||
|
||||
await self.ctx.Irc.Protocol.send_kline(nickname=nickname, hostname=hostname, set_by=dnickname, expire_timestamp=expire_time, set_at_timestamp=set_at_timestamp, reason=gline_reason)
|
||||
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(ke)
|
||||
except Exception as err:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname host reason")
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'unkline':
|
||||
try:
|
||||
# 'shun', 'unshun'
|
||||
# TKL + G user host set_by expire_timestamp set_at_timestamp :reason
|
||||
# .ungline nickname host
|
||||
if len(cmd) < 2:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname hostname")
|
||||
return None
|
||||
|
||||
nickname = str(cmd[1])
|
||||
hostname = str(cmd[2])
|
||||
|
||||
await self.ctx.Irc.Protocol.send_unkline(nickname=nickname, hostname=hostname)
|
||||
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(ke)
|
||||
except Exception as err:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname hostname")
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'shun':
|
||||
try:
|
||||
# TKL + G user host set_by expire_timestamp set_at_timestamp :reason
|
||||
# .shun [nickname] [host] [reason]
|
||||
|
||||
if len(cmd) < 4:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname host reason")
|
||||
return None
|
||||
|
||||
nickname = str(cmd[1])
|
||||
hostname = str(cmd[2])
|
||||
set_at_timestamp = self.ctx.Utils.get_unixtime()
|
||||
expire_time = (60 * 60 * 24) + set_at_timestamp
|
||||
shun_reason = ' '.join(cmd[3:])
|
||||
|
||||
if nickname == '*' and hostname == '*':
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" You want to close the server ? i would recommand ./unrealircd stop :)")
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname host reason")
|
||||
return None
|
||||
|
||||
await self.ctx.Irc.Protocol.send2socket(f":{self.ctx.Config.SERVEUR_ID} TKL + s {nickname} {hostname} {dnickname} {expire_time} {set_at_timestamp} :{shun_reason}")
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(ke)
|
||||
except Exception as err:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname host reason")
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'unshun':
|
||||
try:
|
||||
# 'shun', 'unshun'
|
||||
# TKL + G user host set_by expire_timestamp set_at_timestamp :reason
|
||||
# .unshun nickname host
|
||||
if len(cmd) < 2:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname hostname")
|
||||
return None
|
||||
|
||||
nickname = str(cmd[1])
|
||||
hostname = str(cmd[2])
|
||||
|
||||
await self.ctx.Irc.Protocol.send2socket(f":{self.ctx.Config.SERVEUR_ID} TKL - s {nickname} {hostname} {dnickname}")
|
||||
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(ke)
|
||||
except Exception as err:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()} nickname hostname")
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'glinelist':
|
||||
try:
|
||||
self.user_to_notice = fromuser
|
||||
await self.ctx.Irc.Protocol.send2socket(f":{self.ctx.Config.SERVICE_ID} STATS G")
|
||||
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(ke)
|
||||
except Exception as err:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()}")
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'shunlist':
|
||||
try:
|
||||
self.user_to_notice = fromuser
|
||||
await self.ctx.Irc.Protocol.send2socket(f":{self.ctx.Config.SERVICE_ID} STATS s")
|
||||
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(ke)
|
||||
except Exception as err:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()}")
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case 'klinelist':
|
||||
try:
|
||||
self.user_to_notice = fromuser
|
||||
await self.ctx.Irc.Protocol.send2socket(f":{self.ctx.Config.SERVICE_ID} STATS k")
|
||||
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(ke)
|
||||
except Exception as err:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" /msg {dnickname} {command.upper()}")
|
||||
self.ctx.Logs.warning(f'Unknown Error: {str(err)}')
|
||||
|
||||
case _:
|
||||
pass
|
||||
248
mods/command/utils.py
Normal file
248
mods/command/utils.py
Normal file
@@ -0,0 +1,248 @@
|
||||
from typing import TYPE_CHECKING, Literal, Optional
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mods.command.mod_command import Command
|
||||
|
||||
|
||||
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.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:
|
||||
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.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:
|
||||
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:
|
||||
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:
|
||||
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 = 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.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:
|
||||
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 = 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:
|
||||
await uplink.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Automode {mode} deleted for {nickname} in {chan}")
|
||||
else:
|
||||
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.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:
|
||||
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:
|
||||
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 = await uplink.ctx.Base.db_execute_query("SELECT nickname, channel, mode FROM command_automode")
|
||||
db_results = db_query.fetchall()
|
||||
|
||||
if not db_results:
|
||||
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
|
||||
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 _:
|
||||
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)}")
|
||||
|
||||
async def set_deopall(uplink: 'Command', channel_name: str) -> None:
|
||||
|
||||
service_id = uplink.ctx.Config.SERVICE_ID
|
||||
await uplink.ctx.Irc.Protocol.send2socket(f":{service_id} SVSMODE {channel_name} -o")
|
||||
return None
|
||||
|
||||
async def set_devoiceall(uplink: 'Command', channel_name: str) -> None:
|
||||
|
||||
service_id = uplink.ctx.Config.SERVICE_ID
|
||||
await uplink.ctx.Irc.Protocol.send2socket(f":{service_id} SVSMODE {channel_name} -v")
|
||||
return None
|
||||
|
||||
async def set_mode_to_all(uplink: 'Command', channel_name: str, action: Literal['+', '-'], pmode: str) -> None:
|
||||
|
||||
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)]
|
||||
|
||||
# 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)):
|
||||
if uplink.ctx.Utils.clean_uid(uid[i]) == uplink.ctx.Config.SERVICE_ID:
|
||||
continue
|
||||
mode += set_mode
|
||||
users += f'{uplink.ctx.User.get_nickname(uplink.ctx.Utils.clean_uid(uid[i]))} '
|
||||
if i == len(uid) - 1:
|
||||
await uplink.ctx.Irc.Protocol.send2socket(f":{service_id} MODE {channel_name} {action}{mode} {users}")
|
||||
mode = ''
|
||||
users = ''
|
||||
|
||||
async def set_operation(uplink: 'Command', cmd: list[str], channel_name: Optional[str], client: str, mode: str) -> None:
|
||||
|
||||
dnickname = uplink.ctx.Config.SERVICE_NICKNAME
|
||||
service_id = uplink.ctx.Config.SERVICE_ID
|
||||
if channel_name is None:
|
||||
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:
|
||||
# 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]
|
||||
# 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]
|
||||
# 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
|
||||
|
||||
async def set_ban(uplink: 'Command', cmd: list[str], action: Literal['+', '-'], client: str) -> None:
|
||||
|
||||
command = str(cmd[0])
|
||||
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:
|
||||
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]
|
||||
|
||||
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
|
||||
|
||||
async def set_kick(uplink: 'Command', cmd: list[str], client: str) -> None:
|
||||
|
||||
command = str(cmd[0])
|
||||
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:
|
||||
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:])
|
||||
|
||||
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
|
||||
|
||||
async def set_kickban(uplink: 'Command', cmd: list[str], client: str) -> None:
|
||||
|
||||
command = str(cmd[0])
|
||||
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:
|
||||
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:])
|
||||
|
||||
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}')
|
||||
|
||||
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.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:
|
||||
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}')
|
||||
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
|
||||
|
||||
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.ctx.Config.SERVICE_NICKNAME
|
||||
dchanlog = uplink.ctx.Config.SERVICE_CHANLOG
|
||||
|
||||
sent_channel = str(cmd[1]) if uplink.ctx.Channel.is_valid_channel(cmd[1]) else None
|
||||
if sent_channel is None:
|
||||
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:
|
||||
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
|
||||
|
||||
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}")
|
||||
|
||||
await uplink.ctx.Channel.db_query_channel('del', uplink.module_name, sent_channel)
|
||||
return None
|
||||
971
mods/defender/mod_defender.py
Normal file
971
mods/defender/mod_defender.py
Normal file
@@ -0,0 +1,971 @@
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Any, TYPE_CHECKING, Optional
|
||||
from core.classes.interfaces.imodule import IModule
|
||||
import mods.defender.schemas as schemas
|
||||
import mods.defender.utils as utils
|
||||
import mods.defender.threads as thds
|
||||
from core.utils import tr
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.loader import Loader
|
||||
|
||||
class Defender(IModule):
|
||||
|
||||
@dataclass
|
||||
class ModConfModel(schemas.ModConfModel):
|
||||
...
|
||||
|
||||
MOD_HEADER: dict[str, str] = {
|
||||
'name':'Defender',
|
||||
'version':'1.0.0',
|
||||
'description':'Defender main module that uses the reputation security.',
|
||||
'author':'Defender Team',
|
||||
'core_version':'Defender-6'
|
||||
}
|
||||
|
||||
def __init__(self, context: 'Loader') -> None:
|
||||
super().__init__(context)
|
||||
self._mod_config: Optional[schemas.ModConfModel] = None
|
||||
self.Schemas = schemas.RepDB()
|
||||
self.Threads = thds
|
||||
|
||||
@property
|
||||
def mod_config(self) -> ModConfModel:
|
||||
return self._mod_config
|
||||
|
||||
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_autoop = '''CREATE TABLE IF NOT EXISTS defender_autoop (
|
||||
# id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
# datetime TEXT,
|
||||
# nickname TEXT,
|
||||
# channel TEXT
|
||||
# )
|
||||
# '''
|
||||
|
||||
# self.ctx.Base.db_execute_query(table_autoop)
|
||||
# self.ctx.Base.db_execute_query(table_config)
|
||||
# self.ctx.Base.db_execute_query(table_trusted)
|
||||
return None
|
||||
|
||||
async def load(self):
|
||||
# Variable qui va contenir les options de configuration du module Defender
|
||||
self._mod_config: schemas.ModConfModel = self.ModConfModel()
|
||||
|
||||
# sync the database with local variable (Mandatory)
|
||||
await self.sync_db()
|
||||
|
||||
# Add module utils functions
|
||||
self.mod_utils = utils
|
||||
|
||||
# Create module commands (Mandatory)
|
||||
self.ctx.Commands.build_command(0, self.module_name, 'code', 'Display the code or key for access')
|
||||
self.ctx.Commands.build_command(1, self.module_name, 'info', 'Provide information about the channel or server')
|
||||
self.ctx.Commands.build_command(1, self.module_name, 'autolimit', 'Automatically set channel user limits')
|
||||
self.ctx.Commands.build_command(3, self.module_name, 'reputation', 'Check or manage user reputation')
|
||||
self.ctx.Commands.build_command(3, self.module_name, 'proxy_scan', 'Scan users for proxy connections')
|
||||
self.ctx.Commands.build_command(3, self.module_name, 'flood', 'Handle flood detection and mitigation')
|
||||
self.ctx.Commands.build_command(3, self.module_name, 'status', 'Check the status of the server or bot')
|
||||
self.ctx.Commands.build_command(3, self.module_name, 'show_reputation', 'Display reputation information')
|
||||
self.ctx.Commands.build_command(3, self.module_name, 'sentinel', 'Monitor and guard the channel or server')
|
||||
|
||||
self.timeout = self.ctx.Config.API_TIMEOUT
|
||||
|
||||
# Listes qui vont contenir les ip a scanner avec les différentes API
|
||||
self.Schemas.DB_ABUSEIPDB_USERS = []
|
||||
self.Schemas.DB_FREEIPAPI_USERS = []
|
||||
self.Schemas.DB_CLOUDFILT_USERS = []
|
||||
self.Schemas.DB_PSUTIL_USERS = []
|
||||
self.Schemas.DB_LOCALSCAN_USERS = []
|
||||
|
||||
# Variables qui indique que les threads sont en cours d'éxecutions
|
||||
self.abuseipdb_isRunning = True if self.mod_config.abuseipdb_scan == 1 else False
|
||||
self.freeipapi_isRunning = True if self.mod_config.freeipapi_scan == 1 else False
|
||||
self.cloudfilt_isRunning = True if self.mod_config.cloudfilt_scan == 1 else False
|
||||
self.psutil_isRunning = True if self.mod_config.psutil_scan == 1 else False
|
||||
self.localscan_isRunning = True if self.mod_config.local_scan == 1 else False
|
||||
self.reputationTimer_isRunning = True if self.mod_config.reputation == 1 else False
|
||||
self.autolimit_isRunning = True if self.mod_config.autolimit == 1 else False
|
||||
|
||||
# Variable qui va contenir les users
|
||||
self.flood_system = {}
|
||||
|
||||
# Contient les premieres informations de connexion
|
||||
self.reputation_first_connexion = {'ip': '', 'score': -1}
|
||||
|
||||
# Laisser vide si aucune clé
|
||||
self.abuseipdb_key = '13c34603fee4d2941a2c443cc5c77fd750757ca2a2c1b304bd0f418aff80c24be12651d1a3cfe674'
|
||||
self.cloudfilt_key = 'r1gEtjtfgRQjtNBDMxsg'
|
||||
|
||||
# Démarrer les threads pour démarrer les api
|
||||
self.ctx.Base.create_asynctask(self.Threads.coro_freeipapi_scan(self)) if self.mod_config.freeipapi_scan == 1 else None
|
||||
self.ctx.Base.create_asynctask(self.Threads.coro_cloudfilt_scan(self)) if self.mod_config.cloudfilt_scan == 1 else None
|
||||
self.ctx.Base.create_asynctask(self.Threads.coro_abuseipdb_scan(self)) if self.mod_config.abuseipdb_scan == 1 else None
|
||||
self.ctx.Base.create_asynctask(self.Threads.coro_local_scan(self)) if self.mod_config.local_scan == 1 else None
|
||||
self.ctx.Base.create_asynctask(self.Threads.coro_psutil_scan(self)) if self.mod_config.psutil_scan == 1 else None
|
||||
self.ctx.Base.create_asynctask(self.Threads.coro_apply_reputation_sanctions(self)) if self.mod_config.reputation == 1 else None
|
||||
self.ctx.Base.create_asynctask(self.Threads.coro_autolimit(self)) if self.mod_config.autolimit == 1 else None
|
||||
|
||||
if self.mod_config.reputation == 1:
|
||||
await self.ctx.Irc.Protocol.send_sjoin(self.ctx.Config.SALON_JAIL)
|
||||
await self.ctx.Irc.Protocol.send2socket(f":{self.ctx.Config.SERVICE_NICKNAME} SAMODE {self.ctx.Config.SALON_JAIL} +o {self.ctx.Config.SERVICE_NICKNAME}")
|
||||
for chan in self.ctx.Channel.UID_CHANNEL_DB:
|
||||
if chan.name != self.ctx.Config.SALON_JAIL:
|
||||
await self.ctx.Irc.Protocol.send_set_mode('+b', channel_name=chan.name, params='~security-group:unknown-users')
|
||||
await self.ctx.Irc.Protocol.send_set_mode('+eee', channel_name=chan.name, params='~security-group:webirc-users ~security-group:known-users ~security-group:websocket-users')
|
||||
|
||||
def __onload(self):
|
||||
|
||||
abuseipdb = self.ctx.Settings.get_cache('ABUSEIPDB')
|
||||
freeipapi = self.ctx.Settings.get_cache('FREEIPAPI')
|
||||
cloudfilt = self.ctx.Settings.get_cache('CLOUDFILT')
|
||||
psutils = self.ctx.Settings.get_cache('PSUTIL')
|
||||
localscan = self.ctx.Settings.get_cache('LOCALSCAN')
|
||||
|
||||
if abuseipdb:
|
||||
self.Schemas.DB_ABUSEIPDB_USERS = abuseipdb
|
||||
|
||||
if freeipapi:
|
||||
self.Schemas.DB_FREEIPAPI_USERS = freeipapi
|
||||
|
||||
if cloudfilt:
|
||||
self.Schemas.DB_CLOUDFILT_USERS = cloudfilt
|
||||
|
||||
if psutils:
|
||||
self.Schemas.DB_PSUTIL_USERS = psutils
|
||||
|
||||
if localscan:
|
||||
self.Schemas.DB_LOCALSCAN_USERS = localscan
|
||||
|
||||
async def unload(self) -> None:
|
||||
"""Cette methode sera executée a chaque désactivation ou
|
||||
rechargement de module
|
||||
"""
|
||||
self.Schemas.DB_ABUSEIPDB_USERS = []
|
||||
self.Schemas.DB_FREEIPAPI_USERS = []
|
||||
self.Schemas.DB_CLOUDFILT_USERS = []
|
||||
self.Schemas.DB_PSUTIL_USERS = []
|
||||
self.Schemas.DB_LOCALSCAN_USERS = []
|
||||
|
||||
self.abuseipdb_isRunning:bool = False
|
||||
self.freeipapi_isRunning:bool = False
|
||||
self.cloudfilt_isRunning:bool = False
|
||||
self.psutil_isRunning:bool = False
|
||||
self.localscan_isRunning:bool = False
|
||||
self.reputationTimer_isRunning:bool = False
|
||||
self.autolimit_isRunning: bool = False
|
||||
|
||||
self.ctx.Commands.drop_command_by_module(self.module_name)
|
||||
|
||||
if self.mod_config.reputation == 1:
|
||||
await self.ctx.Irc.Protocol.send_part_chan(self.ctx.Config.SERVICE_ID, self.ctx.Config.SALON_JAIL)
|
||||
for chan in self.ctx.Channel.UID_CHANNEL_DB:
|
||||
if chan.name != self.ctx.Config.SALON_JAIL:
|
||||
await self.ctx.Irc.Protocol.send_set_mode('-b', channel_name=chan.name, params='~security-group:unknown-users')
|
||||
await self.ctx.Irc.Protocol.send_set_mode('-eee', channel_name=chan.name, params='~security-group:webirc-users ~security-group:known-users ~security-group:websocket-users')
|
||||
|
||||
return None
|
||||
|
||||
async def insert_db_trusted(self, uid: str, nickname:str) -> None:
|
||||
u = self.ctx.User.get_user(uid)
|
||||
if u is None:
|
||||
return None
|
||||
|
||||
uid = u.uid
|
||||
nickname = u.nickname
|
||||
|
||||
query = "SELECT id FROM def_trusted WHERE user = ?"
|
||||
exec_query = await self.ctx.Base.db_execute_query(query, {"user": nickname})
|
||||
response = exec_query.fetchone()
|
||||
|
||||
if response is not None:
|
||||
q_insert = "INSERT INTO def_trusted (datetime, user, host, vhost) VALUES (?, ?, ?, ?)"
|
||||
mes_donnees = {'datetime': self.ctx.mod_utils.get_datetime(), 'user': nickname, 'host': '*', 'vhost': '*'}
|
||||
exec_query = self.ctx.Base.db_execute_query(q_insert, mes_donnees)
|
||||
pass
|
||||
|
||||
async def join_saved_channels(self) -> None:
|
||||
"""_summary_
|
||||
"""
|
||||
try:
|
||||
result = await self.ctx.Base.db_execute_query(f"SELECT distinct channel_name FROM {self.ctx.Config.TABLE_CHANNEL}")
|
||||
channels = result.fetchall()
|
||||
jail_chan = self.ctx.Config.SALON_JAIL
|
||||
jail_chan_mode = self.ctx.Config.SALON_JAIL_MODES
|
||||
service_id = self.ctx.Config.SERVICE_ID
|
||||
dumodes = self.ctx.Config.SERVICE_UMODES
|
||||
dnickname = self.ctx.Config.SERVICE_NICKNAME
|
||||
|
||||
for channel in channels:
|
||||
chan = channel[0]
|
||||
await self.ctx.Irc.Protocol.send_sjoin(chan)
|
||||
if chan == jail_chan:
|
||||
await self.ctx.Irc.Protocol.send2socket(f":{service_id} SAMODE {jail_chan} +{dumodes} {dnickname}")
|
||||
await self.ctx.Irc.Protocol.send2socket(f":{service_id} MODE {jail_chan} +{jail_chan_mode}")
|
||||
|
||||
return None
|
||||
|
||||
except Exception as err:
|
||||
self.ctx.Logs.error(f"General Error: {err}")
|
||||
|
||||
async def cmd(self, data: list[str]) -> None:
|
||||
|
||||
if not data or len(data) < 2:
|
||||
return None
|
||||
cmd = data.copy() if isinstance(data, list) else list(data).copy()
|
||||
|
||||
try:
|
||||
index, command = self.ctx.Irc.Protocol.get_ircd_protocol_poisition(cmd)
|
||||
if index == -1:
|
||||
return None
|
||||
|
||||
match command:
|
||||
|
||||
case 'REPUTATION':
|
||||
self.mod_utils.handle_on_reputation(self, cmd)
|
||||
return None
|
||||
|
||||
case 'MODE':
|
||||
await self.mod_utils.handle_on_mode(self, cmd)
|
||||
return None
|
||||
|
||||
case 'PRIVMSG':
|
||||
await self.mod_utils.handle_on_privmsg(self, cmd)
|
||||
return None
|
||||
|
||||
case 'UID':
|
||||
await self.mod_utils.handle_on_uid(self, cmd)
|
||||
return None
|
||||
|
||||
case 'SJOIN':
|
||||
await self.mod_utils.handle_on_sjoin(self, cmd)
|
||||
return None
|
||||
|
||||
case 'SLOG':
|
||||
self.mod_utils.handle_on_slog(self, cmd)
|
||||
return None
|
||||
|
||||
case 'NICK':
|
||||
await self.mod_utils.handle_on_nick(self, cmd)
|
||||
return None
|
||||
|
||||
case 'QUIT':
|
||||
await self.mod_utils.handle_on_quit(self, cmd)
|
||||
return None
|
||||
|
||||
case _:
|
||||
return None
|
||||
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(f"{ke} / {cmd} / length {str(len(cmd))}")
|
||||
except IndexError as ie:
|
||||
self.ctx.Logs.error(f"{ie} / {cmd} / length {str(len(cmd))}")
|
||||
except Exception as err:
|
||||
self.ctx.Logs.error(f"General Error: {err}", exc_info=True)
|
||||
|
||||
async def hcmds(self, user: str, channel: Any, cmd: list, fullcmd: list = []) -> None:
|
||||
u = self.ctx.User.get_user(user)
|
||||
if u is None:
|
||||
return None
|
||||
|
||||
command = str(cmd[0]).lower()
|
||||
fromuser = u.nickname
|
||||
channel = fromchannel = channel if self.ctx.Channel.is_valid_channel(channel) else None
|
||||
|
||||
dnickname = self.ctx.Config.SERVICE_NICKNAME # Defender nickname
|
||||
dchanlog = self.ctx.Config.SERVICE_CHANLOG # Defender chan log
|
||||
dumodes = self.ctx.Config.SERVICE_UMODES # Les modes de Defender
|
||||
service_id = self.ctx.Config.SERVICE_ID # Defender serveur id
|
||||
jail_chan = self.ctx.Config.SALON_JAIL # Salon pot de miel
|
||||
jail_chan_mode = self.ctx.Config.SALON_JAIL_MODES # Mode du salon "pot de miel"
|
||||
|
||||
color_green = self.ctx.Config.COLORS.green
|
||||
color_red = self.ctx.Config.COLORS.red
|
||||
color_black = self.ctx.Config.COLORS.black
|
||||
color_nogc = self.ctx.Config.COLORS.nogc
|
||||
|
||||
match command:
|
||||
|
||||
case 'show_reputation':
|
||||
|
||||
if self.mod_config.reputation == 0:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="Reputation system if off!")
|
||||
return None
|
||||
|
||||
if not self.ctx.Reputation.UID_REPUTATION_DB:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="No one is suspected")
|
||||
|
||||
for suspect in self.ctx.Reputation.UID_REPUTATION_DB:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname,
|
||||
nick_to=fromuser,
|
||||
msg=f" Uid: {suspect.uid} | Nickname: {suspect.nickname} | Reputation: {suspect.score_connexion} | Secret code: {suspect.secret_code} | Connected on: {suspect.connexion_datetime}")
|
||||
|
||||
case 'code':
|
||||
try:
|
||||
release_code = cmd[1]
|
||||
jailed_nickname = u.nickname
|
||||
jailed_UID = u.uid
|
||||
get_reputation = self.ctx.Reputation.get_reputation(jailed_UID)
|
||||
|
||||
if get_reputation is None:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=" No code is requested ...")
|
||||
return False
|
||||
|
||||
jailed_IP = get_reputation.remote_ip
|
||||
jailed_salon = self.ctx.Config.SALON_JAIL
|
||||
reputation_seuil = self.mod_config.reputation_seuil
|
||||
welcome_salon = self.ctx.Config.SALON_LIBERER
|
||||
|
||||
self.ctx.Logs.debug(f"IP de {jailed_nickname} : {jailed_IP}")
|
||||
link = self.ctx.Config.SERVEUR_LINK
|
||||
color_green = self.ctx.Config.COLORS.green
|
||||
color_black = self.ctx.Config.COLORS.black
|
||||
|
||||
if release_code == get_reputation.secret_code:
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg="Bon mot de passe. Allez du vent !", channel=jailed_salon)
|
||||
|
||||
if self.mod_config.reputation_ban_all_chan == 1:
|
||||
for chan in self.ctx.Channel.UID_CHANNEL_DB:
|
||||
if chan.name != jailed_salon:
|
||||
await self.ctx.Irc.Protocol.send2socket(f":{service_id} MODE {chan.name} -b {jailed_nickname}!*@*")
|
||||
|
||||
self.ctx.Reputation.delete(jailed_UID)
|
||||
self.ctx.Logs.debug(f'{jailed_UID} - {jailed_nickname} removed from REPUTATION_DB')
|
||||
await self.ctx.Irc.Protocol.send_sapart(nick_to_sapart=jailed_nickname, channel_name=jailed_salon)
|
||||
await self.ctx.Irc.Protocol.send_sajoin(nick_to_sajoin=jailed_nickname, channel_name=welcome_salon)
|
||||
await self.ctx.Irc.Protocol.send2socket(f":{link} REPUTATION {jailed_IP} {self.mod_config.reputation_score_after_release}")
|
||||
u.score_connexion = reputation_seuil + 1
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname,
|
||||
msg=f"[{color_green} MOT DE PASS CORRECT {color_black}] : You have now the right to enjoy the network !",
|
||||
nick_to=jailed_nickname)
|
||||
|
||||
else:
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(
|
||||
nick_from=dnickname,
|
||||
msg="Mauvais password",
|
||||
channel=jailed_salon
|
||||
)
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(
|
||||
nick_from=dnickname,
|
||||
msg=f"[{color_green} MAUVAIS PASSWORD {color_black}] You have typed a wrong code. for recall your password is: {self.ctx.Config.SERVICE_PREFIX}code {get_reputation.secret_code}",
|
||||
nick_to=jailed_nickname
|
||||
)
|
||||
|
||||
except IndexError as ie:
|
||||
self.ctx.Logs.error(f'Index Error: {ie}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} code [code]")
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(f'_hcmd code: KeyError {ke}')
|
||||
|
||||
case 'autolimit':
|
||||
try:
|
||||
# autolimit on
|
||||
# autolimit set [amount] [interval]
|
||||
if len(cmd) < 2:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {self.ctx.Config.SERVICE_NICKNAME} {command.upper()} ON")
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {self.ctx.Config.SERVICE_NICKNAME} {command.upper()} SET [AMOUNT] [INTERVAL]")
|
||||
return None
|
||||
|
||||
arg = str(cmd[1]).lower()
|
||||
|
||||
match arg:
|
||||
case 'on':
|
||||
if self.mod_config.autolimit == 0:
|
||||
await self.update_configuration('autolimit', 1)
|
||||
self.autolimit_isRunning = True
|
||||
self.ctx.Base.create_asynctask(thds.coro_autolimit(self), async_name='coro_autolimit')
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[{self.ctx.Config.COLORS.green}AUTOLIMIT{self.ctx.Config.COLORS.nogc}] Activated", channel=self.ctx.Config.SERVICE_CHANLOG)
|
||||
else:
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[{self.ctx.Config.COLORS.red}AUTOLIMIT{self.ctx.Config.COLORS.nogc}] Already activated", channel=self.ctx.Config.SERVICE_CHANLOG)
|
||||
|
||||
case 'off':
|
||||
if self.mod_config.autolimit == 1:
|
||||
await self.update_configuration('autolimit', 0)
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[{self.ctx.Config.COLORS.green}AUTOLIMIT{self.ctx.Config.COLORS.nogc}] Deactivated", channel=self.ctx.Config.SERVICE_CHANLOG)
|
||||
else:
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[{self.ctx.Config.COLORS.red}AUTOLIMIT{self.ctx.Config.COLORS.nogc}] Already Deactivated", channel=self.ctx.Config.SERVICE_CHANLOG)
|
||||
|
||||
case 'set':
|
||||
amount = int(cmd[2])
|
||||
interval = int(cmd[3])
|
||||
|
||||
await self.update_configuration('autolimit_amount', amount)
|
||||
await self.update_configuration('autolimit_interval', interval)
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(
|
||||
nick_from=dnickname,
|
||||
msg=f"[{self.ctx.Config.COLORS.green}AUTOLIMIT{self.ctx.Config.COLORS.nogc}] Amount set to ({amount}) | Interval set to ({interval})",
|
||||
channel=self.ctx.Config.SERVICE_CHANLOG
|
||||
)
|
||||
|
||||
case _:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {self.ctx.Config.SERVICE_NICKNAME} {command.upper()} ON")
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {self.ctx.Config.SERVICE_NICKNAME} {command.upper()} SET [AMOUNT] [INTERVAL]")
|
||||
|
||||
except Exception as err:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {self.ctx.Config.SERVICE_NICKNAME} {command.upper()} ON")
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {self.ctx.Config.SERVICE_NICKNAME} {command.upper()} SET [AMOUNT] [INTERVAL]")
|
||||
self.ctx.Logs.error(f"Value Error -> {err}")
|
||||
|
||||
case 'reputation':
|
||||
# .reputation [on/off] --> activate or deactivate reputation system
|
||||
# .reputation set banallchan [on/off] --> activate or deactivate ban in all channel
|
||||
# .reputation set limit [xxxx] --> change the reputation threshold
|
||||
# .reputation release [nick]
|
||||
# .reputation [arg1] [arg2] [arg3]
|
||||
try:
|
||||
len_cmd = len(cmd)
|
||||
if len_cmd < 2:
|
||||
raise IndexError("Showing help!")
|
||||
|
||||
activation = str(cmd[1]).lower()
|
||||
|
||||
# Nous sommes dans l'activation ON / OFF
|
||||
if len_cmd == 2:
|
||||
key = 'reputation'
|
||||
if activation == 'on':
|
||||
|
||||
if self.mod_config.reputation == 1:
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {self.ctx.Config.COLORS.green}REPUTATION{self.ctx.Config.COLORS.black} ] : Already activated", channel=dchanlog)
|
||||
return None
|
||||
|
||||
await self.update_configuration(key, 1)
|
||||
self.ctx.Base.create_asynctask(self.Threads.coro_apply_reputation_sanctions(self))
|
||||
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {self.ctx.Config.COLORS.green}REPUTATION{self.ctx.Config.COLORS.black} ] : Activated by {fromuser}", channel=dchanlog)
|
||||
|
||||
await self.ctx.Irc.Protocol.send_join_chan(uidornickname=dnickname, channel=jail_chan)
|
||||
await self.ctx.Irc.Protocol.send2socket(f":{service_id} SAMODE {jail_chan} +{dumodes} {dnickname}")
|
||||
await self.ctx.Irc.Protocol.send_set_mode(f'+{jail_chan_mode}', channel_name=jail_chan)
|
||||
|
||||
if self.mod_config.reputation_sg == 1:
|
||||
for chan in self.ctx.Channel.UID_CHANNEL_DB:
|
||||
if chan.name != jail_chan:
|
||||
await self.ctx.Irc.Protocol.send_set_mode('+b', channel_name=chan.name, params='~security-group:unknown-users')
|
||||
await self.ctx.Irc.Protocol.send_set_mode(
|
||||
'+eee',
|
||||
channel_name=chan.name,
|
||||
params='~security-group:webirc-users ~security-group:known-users ~security-group:websocket-users'
|
||||
)
|
||||
|
||||
await self.ctx.Channel.db_query_channel('add', self.module_name, jail_chan)
|
||||
|
||||
if activation == 'off':
|
||||
|
||||
if self.mod_config.reputation == 0:
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(
|
||||
nick_from=dnickname,
|
||||
msg=f"[ {self.ctx.Config.COLORS.green}REPUTATION{self.ctx.Config.COLORS.black} ] : Already deactivated",
|
||||
channel=dchanlog
|
||||
)
|
||||
return False
|
||||
|
||||
await self.update_configuration(key, 0)
|
||||
self.reputationTimer_isRunning = False
|
||||
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(
|
||||
nick_from=dnickname,
|
||||
msg=f"[ {self.ctx.Config.COLORS.red}REPUTATION{self.ctx.Config.COLORS.black} ] : Deactivated by {fromuser}",
|
||||
channel=dchanlog
|
||||
)
|
||||
|
||||
await self.ctx.Irc.Protocol.send2socket(f":{service_id} SAMODE {jail_chan} -{dumodes} {dnickname}")
|
||||
await self.ctx.Irc.Protocol.send_set_mode('-sS', channel_name=jail_chan)
|
||||
await self.ctx.Irc.Protocol.send_part_chan(service_id, jail_chan)
|
||||
|
||||
for chan in self.ctx.Channel.UID_CHANNEL_DB:
|
||||
if chan.name != jail_chan:
|
||||
await self.ctx.Irc.Protocol.send_set_mode('-b', channel_name=chan.name, params='~security-group:unknown-users')
|
||||
await self.ctx.Irc.Protocol.send_set_mode(
|
||||
'-eee',
|
||||
channel_name=chan.name,
|
||||
params='~security-group:webirc-users ~security-group:known-users ~security-group:websocket-users'
|
||||
)
|
||||
|
||||
await self.ctx.Channel.db_query_channel('del', self.module_name, jail_chan)
|
||||
|
||||
if len_cmd == 3:
|
||||
get_options = str(cmd[1]).lower()
|
||||
|
||||
match get_options:
|
||||
case 'release':
|
||||
# .reputation release [nick]
|
||||
link = self.ctx.Config.SERVEUR_LINK
|
||||
jailed_salon = self.ctx.Config.SALON_JAIL
|
||||
welcome_salon = self.ctx.Config.SALON_LIBERER
|
||||
client_obj = self.ctx.User.get_user(str(cmd[2]))
|
||||
|
||||
if self.mod_config.reputation != 1:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname,
|
||||
nick_to=fromuser,
|
||||
msg="The reputation system is not activated!")
|
||||
return None
|
||||
|
||||
if client_obj is None:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname,
|
||||
nick_to=fromuser,
|
||||
msg=f"This nickname ({str(cmd[2])}) is not connected to the network!")
|
||||
return None
|
||||
|
||||
client_to_release = self.ctx.Reputation.get_reputation(client_obj.uid)
|
||||
|
||||
if client_to_release is None:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname,
|
||||
nick_to=fromuser, msg=f"This nickname ({str(cmd[2])}) doesn't exist in the reputation databalse!")
|
||||
return None
|
||||
|
||||
if self.ctx.Reputation.delete(client_to_release.uid):
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(
|
||||
nick_from=dnickname,
|
||||
msg=f"[ {self.ctx.Config.COLORS.green}REPUTATION RELEASE{self.ctx.Config.COLORS.black} ] : {client_to_release.nickname} has been released",
|
||||
channel=dchanlog)
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname,
|
||||
nick_to=fromuser, msg=f"This nickname has been released from reputation system")
|
||||
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname,
|
||||
nick_to=client_to_release.nickname, msg=f"You have been released from the reputation system by ({fromuser})")
|
||||
|
||||
await self.ctx.Irc.Protocol.send_sapart(nick_to_sapart=client_to_release.nickname, channel_name=jailed_salon)
|
||||
await self.ctx.Irc.Protocol.send_sajoin(nick_to_sajoin=client_to_release.nickname, channel_name=welcome_salon)
|
||||
await self.ctx.Irc.Protocol.send2socket(f":{link} REPUTATION {client_to_release.remote_ip} {self.mod_config.reputation_score_after_release}")
|
||||
return None
|
||||
else:
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(
|
||||
nick_from=dnickname,
|
||||
msg=f"[ {self.ctx.Config.COLORS.red}REPUTATION RELEASE ERROR{self.ctx.Config.COLORS.black} ] : "
|
||||
f"{client_to_release.nickname} has not been released! as he is not in the reputation database",
|
||||
channel=dchanlog
|
||||
)
|
||||
if len_cmd > 4:
|
||||
get_set = str(cmd[1]).lower()
|
||||
|
||||
if get_set != 'set':
|
||||
raise IndexError('Showing help')
|
||||
|
||||
get_options = str(cmd[2]).lower()
|
||||
|
||||
match get_options:
|
||||
|
||||
case 'banallchan':
|
||||
key = 'reputation_ban_all_chan'
|
||||
get_value = str(cmd[3]).lower()
|
||||
if get_value == 'on':
|
||||
|
||||
if self.mod_config.reputation_ban_all_chan == 1:
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(
|
||||
nick_from=dnickname,
|
||||
msg=f"[ {self.ctx.Config.COLORS.red}BAN ON ALL CHANS{self.ctx.Config.COLORS.black} ] : Already activated",
|
||||
channel=dchanlog
|
||||
)
|
||||
return False
|
||||
|
||||
# self.update_db_configuration(key, 1)
|
||||
await self.update_configuration(key, 1)
|
||||
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(
|
||||
nick_from=dnickname,
|
||||
msg=f"[ {self.ctx.Config.COLORS.green}BAN ON ALL CHANS{self.ctx.Config.COLORS.black} ] : Activated by {fromuser}",
|
||||
channel=dchanlog
|
||||
)
|
||||
|
||||
elif get_value == 'off':
|
||||
if self.mod_config.reputation_ban_all_chan == 0:
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(
|
||||
nick_from=dnickname,
|
||||
msg=f"[ {self.ctx.Config.COLORS.red}BAN ON ALL CHANS{self.ctx.Config.COLORS.black} ] : Already deactivated",
|
||||
channel=dchanlog
|
||||
)
|
||||
return False
|
||||
|
||||
# self.update_db_configuration(key, 0)
|
||||
await self.update_configuration(key, 0)
|
||||
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(
|
||||
nick_from=dnickname,
|
||||
msg=f"[ {self.ctx.Config.COLORS.green}BAN ON ALL CHANS{self.ctx.Config.COLORS.black} ] : Deactivated by {fromuser}",
|
||||
channel=dchanlog
|
||||
)
|
||||
|
||||
case 'limit':
|
||||
reputation_seuil = int(cmd[3])
|
||||
key = 'reputation_seuil'
|
||||
|
||||
# self.update_db_configuration(key, reputation_seuil)
|
||||
await self.update_configuration(key, reputation_seuil)
|
||||
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(
|
||||
nick_from=dnickname,
|
||||
msg=f"[ {self.ctx.Config.COLORS.green}REPUTATION SEUIL{self.ctx.Config.COLORS.black} ] : Limit set to {str(reputation_seuil)} by {fromuser}",
|
||||
channel=dchanlog
|
||||
)
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Reputation set to {reputation_seuil}")
|
||||
|
||||
case 'timer':
|
||||
reputation_timer = int(cmd[3])
|
||||
key = 'reputation_timer'
|
||||
await self.update_configuration(key, reputation_timer)
|
||||
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(
|
||||
nick_from=dnickname,
|
||||
msg=f"[ {self.ctx.Config.COLORS.green}REPUTATION TIMER{self.ctx.Config.COLORS.black} ] : Timer set to {str(reputation_timer)} minute(s) by {fromuser}",
|
||||
channel=dchanlog
|
||||
)
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Reputation set to {reputation_timer}")
|
||||
|
||||
case 'score_after_release':
|
||||
reputation_score_after_release = int(cmd[3])
|
||||
key = 'reputation_score_after_release'
|
||||
await self.update_configuration(key, reputation_score_after_release)
|
||||
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(
|
||||
nick_from=dnickname,
|
||||
msg=f"[ {self.ctx.Config.COLORS.green}REPUTATION SCORE AFTER RELEASE{self.ctx.Config.COLORS.black} ] : Reputation score after release set to {str(reputation_score_after_release)} by {fromuser}",
|
||||
channel=dchanlog
|
||||
)
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Reputation score after release set to {reputation_score_after_release}")
|
||||
|
||||
case 'security_group':
|
||||
reputation_sg = int(cmd[3])
|
||||
key = 'reputation_sg'
|
||||
await self.update_configuration(key, reputation_sg)
|
||||
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(
|
||||
nick_from=dnickname,
|
||||
msg=f"[ {self.ctx.Config.COLORS.green}REPUTATION SECURITY-GROUP{self.ctx.Config.COLORS.black} ] : Reputation Security-group set to {str(reputation_sg)} by {fromuser}",
|
||||
channel=dchanlog
|
||||
)
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Reputation score after release set to {reputation_sg}")
|
||||
|
||||
case _:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} reputation [ON/OFF]")
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} reputation release [nickname]")
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} reputation set banallchan [ON/OFF]")
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} reputation set limit [1234]")
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} reputation set score_after_release [1234]")
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} reputation set timer [1234]")
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} reputation set action [kill|None]")
|
||||
|
||||
except IndexError as ie:
|
||||
self.ctx.Logs.warning(f'{ie}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} reputation [ON/OFF]")
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} reputation release [nickname]")
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} reputation set banallchan [ON/OFF]")
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} reputation set limit [1234]")
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} reputation set score_after_release [1234]")
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} reputation set timer [1234]")
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Right command : /msg {dnickname} reputation set action [kill|None]")
|
||||
|
||||
except ValueError as ve:
|
||||
self.ctx.Logs.warning(f'{ve}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=" La valeur devrait etre un entier >= 0")
|
||||
|
||||
case 'proxy_scan':
|
||||
|
||||
# .proxy_scan set local_scan on/off --> Va activer le scan des ports
|
||||
# .proxy_scan set psutil_scan on/off --> Active les informations de connexion a la machine locale
|
||||
# .proxy_scan set abuseipdb_scan on/off --> Active le scan via l'api abuseipdb
|
||||
len_cmd = len(cmd)
|
||||
|
||||
if len_cmd == 4:
|
||||
set_key = str(cmd[1]).lower()
|
||||
|
||||
if set_key != 'set':
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' Right command : /msg {dnickname} proxy_scan set local_scan [ON/OFF]')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' Right command : /msg {dnickname} proxy_scan set psutil_scan [ON/OFF]')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' Right command : /msg {dnickname} proxy_scan set abuseipdb_scan [ON/OFF]')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' Right command : /msg {dnickname} proxy_scan set freeipapi_scan [ON/OFF]')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' Right command : /msg {dnickname} proxy_scan set cloudfilt_scan [ON/OFF]')
|
||||
|
||||
option = str(cmd[2]).lower() # => local_scan, psutil_scan, abuseipdb_scan
|
||||
action = str(cmd[3]).lower() # => on / off
|
||||
|
||||
match option:
|
||||
case 'local_scan':
|
||||
if action == 'on':
|
||||
if self.mod_config.local_scan == 1:
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Already activated", channel=dchanlog)
|
||||
return None
|
||||
|
||||
self.ctx.Base.create_asynctask(self.Threads.coro_local_scan(self))
|
||||
await self.update_configuration(option, 1)
|
||||
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Activated by {fromuser}", channel=dchanlog)
|
||||
elif action == 'off':
|
||||
if self.mod_config.local_scan == 0:
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Already Deactivated", channel=dchanlog)
|
||||
return None
|
||||
|
||||
await self.update_configuration(option, 0)
|
||||
self.localscan_isRunning = False
|
||||
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Deactivated by {fromuser}", channel=dchanlog)
|
||||
|
||||
case 'psutil_scan':
|
||||
if action == 'on':
|
||||
if self.mod_config.psutil_scan == 1:
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Already activated", channel=dchanlog)
|
||||
return None
|
||||
|
||||
self.ctx.Base.create_asynctask(self.Threads.coro_psutil_scan(self))
|
||||
await self.update_configuration(option, 1)
|
||||
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Activated by {fromuser}", channel=dchanlog)
|
||||
elif action == 'off':
|
||||
if self.mod_config.psutil_scan == 0:
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Already Deactivated", channel=dchanlog)
|
||||
return None
|
||||
|
||||
await self.update_configuration(option, 0)
|
||||
self.psutil_isRunning = False
|
||||
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Deactivated by {fromuser}", channel=dchanlog)
|
||||
|
||||
case 'abuseipdb_scan':
|
||||
if action == 'on':
|
||||
if self.mod_config.abuseipdb_scan == 1:
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Already activated", channel=dchanlog)
|
||||
return None
|
||||
|
||||
self.ctx.Base.create_asynctask(self.Threads.coro_abuseipdb_scan(self))
|
||||
await self.update_configuration(option, 1)
|
||||
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Activated by {fromuser}", channel=dchanlog)
|
||||
elif action == 'off':
|
||||
if self.mod_config.abuseipdb_scan == 0:
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Already Deactivated", channel=dchanlog)
|
||||
return None
|
||||
|
||||
await self.update_configuration(option, 0)
|
||||
self.abuseipdb_isRunning = False
|
||||
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Deactivated by {fromuser}", channel=dchanlog)
|
||||
|
||||
case 'freeipapi_scan':
|
||||
if action == 'on':
|
||||
if self.mod_config.freeipapi_scan == 1:
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Already activated", channel=dchanlog)
|
||||
return None
|
||||
|
||||
self.ctx.Base.create_asynctask(self.Threads.coro_freeipapi_scan(self))
|
||||
await self.update_configuration(option, 1)
|
||||
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Activated by {fromuser}", channel=dchanlog)
|
||||
elif action == 'off':
|
||||
if self.mod_config.freeipapi_scan == 0:
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Already Deactivated", channel=dchanlog)
|
||||
return None
|
||||
|
||||
await self.update_configuration(option, 0)
|
||||
self.freeipapi_isRunning = False
|
||||
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Deactivated by {fromuser}", channel=dchanlog)
|
||||
|
||||
case 'cloudfilt_scan':
|
||||
if action == 'on':
|
||||
if self.mod_config.cloudfilt_scan == 1:
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Already activated", channel=dchanlog)
|
||||
return None
|
||||
|
||||
self.ctx.Base.create_asynctask(self.Threads.coro_cloudfilt_scan(self))
|
||||
await self.update_configuration(option, 1)
|
||||
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_green}PROXY_SCAN {option.upper()}{color_black} ] : Activated by {fromuser}", channel=dchanlog)
|
||||
elif action == 'off':
|
||||
if self.mod_config.cloudfilt_scan == 0:
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Already Deactivated", channel=dchanlog)
|
||||
return None
|
||||
|
||||
await self.update_configuration(option, 0)
|
||||
self.cloudfilt_isRunning = False
|
||||
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {color_red}PROXY_SCAN {option.upper()}{color_black} ] : Deactivated by {fromuser}", channel=dchanlog)
|
||||
|
||||
case _:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' Right command : /msg {dnickname} proxy_scan set local_scan [ON/OFF]')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' Right command : /msg {dnickname} proxy_scan set psutil_scan [ON/OFF]')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' Right command : /msg {dnickname} proxy_scan set abuseipdb_scan [ON/OFF]')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' Right command : /msg {dnickname} proxy_scan set freeipapi_scan [ON/OFF]')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' Right command : /msg {dnickname} proxy_scan set cloudfilt_scan [ON/OFF]')
|
||||
else:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' Right command : /msg {dnickname} proxy_scan set local_scan [ON/OFF]')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' Right command : /msg {dnickname} proxy_scan set psutil_scan [ON/OFF]')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' Right command : /msg {dnickname} proxy_scan set abuseipdb_scan [ON/OFF]')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' Right command : /msg {dnickname} proxy_scan set freeipapi_scan [ON/OFF]')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' Right command : /msg {dnickname} proxy_scan set cloudfilt_scan [ON/OFF]')
|
||||
|
||||
case 'flood':
|
||||
# .flood on/off
|
||||
# .flood set flood_message 5
|
||||
# .flood set flood_time 1
|
||||
# .flood set flood_timer 20
|
||||
try:
|
||||
len_cmd = len(cmd)
|
||||
|
||||
if len_cmd == 2:
|
||||
activation = str(cmd[1]).lower()
|
||||
key = 'flood'
|
||||
if activation == 'on':
|
||||
if self.mod_config.flood == 1:
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {self.ctx.Config.COLORS.green}FLOOD{self.ctx.Config.COLORS.black} ] : Already activated", channel=dchanlog)
|
||||
return False
|
||||
|
||||
await self.update_configuration(key, 1)
|
||||
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {self.ctx.Config.COLORS.green}FLOOD{self.ctx.Config.COLORS.black} ] : Activated by {fromuser}", channel=dchanlog)
|
||||
|
||||
if activation == 'off':
|
||||
if self.mod_config.flood == 0:
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {self.ctx.Config.COLORS.red}FLOOD{self.ctx.Config.COLORS.black} ] : Already Deactivated", channel=dchanlog)
|
||||
return False
|
||||
|
||||
await self.update_configuration(key, 0)
|
||||
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=f"[ {self.ctx.Config.COLORS.green}FLOOD{self.ctx.Config.COLORS.black} ] : Deactivated by {fromuser}", channel=dchanlog)
|
||||
|
||||
if len_cmd == 4:
|
||||
set_key = str(cmd[2]).lower()
|
||||
|
||||
if str(cmd[1]).lower() == 'set':
|
||||
match set_key:
|
||||
case 'flood_message':
|
||||
key = 'flood_message'
|
||||
set_value = int(cmd[3])
|
||||
await self.update_configuration(key, set_value)
|
||||
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname,
|
||||
msg=f"[ {self.ctx.Config.COLORS.green}FLOOD{self.ctx.Config.COLORS.black} ] : Flood message set to {set_value} by {fromuser}",
|
||||
channel=dchanlog)
|
||||
|
||||
case 'flood_time':
|
||||
key = 'flood_time'
|
||||
set_value = int(cmd[3])
|
||||
await self.update_configuration(key, set_value)
|
||||
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname,
|
||||
msg=f"[ {self.ctx.Config.COLORS.green}FLOOD{self.ctx.Config.COLORS.black} ] : Flood time set to {set_value} by {fromuser}",
|
||||
channel=dchanlog)
|
||||
|
||||
case 'flood_timer':
|
||||
key = 'flood_timer'
|
||||
set_value = int(cmd[3])
|
||||
await self.update_configuration(key, set_value)
|
||||
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname,
|
||||
msg=f"[ {self.ctx.Config.COLORS.green}FLOOD{self.ctx.Config.COLORS.black} ] : Flood timer set to {set_value} by {fromuser}",
|
||||
channel=dchanlog)
|
||||
|
||||
case _:
|
||||
pass
|
||||
|
||||
except ValueError as ve:
|
||||
self.ctx.Logs.error(f"{self.__class__.__name__} Value Error : {ve}")
|
||||
|
||||
case 'status':
|
||||
color_green = self.ctx.Config.COLORS.green
|
||||
color_red = self.ctx.Config.COLORS.red
|
||||
color_black = self.ctx.Config.COLORS.black
|
||||
nogc = self.ctx.Config.COLORS.nogc
|
||||
try:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' [{color_green if self.mod_config.reputation == 1 else color_red}Reputation{nogc}] ==> {self.mod_config.reputation}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' reputation_seuil ==> {self.mod_config.reputation_seuil}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' reputation_after_release ==> {self.mod_config.reputation_score_after_release}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' reputation_ban_all_chan ==> {self.mod_config.reputation_ban_all_chan}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' reputation_timer ==> {self.mod_config.reputation_timer}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=' [Proxy_scan]')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' {color_green if self.mod_config.local_scan == 1 else color_red}local_scan{nogc} ==> {self.mod_config.local_scan}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' {color_green if self.mod_config.psutil_scan == 1 else color_red}psutil_scan{nogc} ==> {self.mod_config.psutil_scan}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' {color_green if self.mod_config.abuseipdb_scan == 1 else color_red}abuseipdb_scan{nogc} ==> {self.mod_config.abuseipdb_scan}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' {color_green if self.mod_config.freeipapi_scan == 1 else color_red}freeipapi_scan{nogc} ==> {self.mod_config.freeipapi_scan}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' {color_green if self.mod_config.cloudfilt_scan == 1 else color_red}cloudfilt_scan{nogc} ==> {self.mod_config.cloudfilt_scan}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' [{color_green if self.mod_config.autolimit == 1 else color_red}Autolimit{nogc}] ==> {self.mod_config.autolimit}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' {color_green if self.mod_config.autolimit == 1 else color_red}Autolimit Amount{nogc} ==> {self.mod_config.autolimit_amount}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' {color_green if self.mod_config.autolimit == 1 else color_red}Autolimit Interval{nogc} ==> {self.mod_config.autolimit_interval}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' [{color_green if self.mod_config.flood == 1 else color_red}Flood{nogc}] ==> {self.mod_config.flood}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=' flood_action ==> Coming soon')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' flood_message ==> {self.mod_config.flood_message}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' flood_time ==> {self.mod_config.flood_time}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' flood_timer ==> {self.mod_config.flood_timer}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' [{color_green if self.mod_config.flood == 1 else color_red}Sentinel{nogc}] ==> {self.mod_config.sentinel}')
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(f"Key Error : {ke}")
|
||||
|
||||
case 'info':
|
||||
try:
|
||||
if len(cmd) < 2:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Syntax. /msg {dnickname} INFO [nickname]")
|
||||
return None
|
||||
|
||||
nickoruid = cmd[1]
|
||||
UserObject = self.ctx.User.get_user(nickoruid)
|
||||
|
||||
if UserObject is not None:
|
||||
channels: list = [chan.name for chan in self.ctx.Channel.UID_CHANNEL_DB for uid_in_chan in chan.uids if self.ctx.User.clean_uid(uid_in_chan) == UserObject.uid]
|
||||
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' UID : {UserObject.uid}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' NICKNAME : {UserObject.nickname}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' USERNAME : {UserObject.username}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' REALNAME : {UserObject.realname}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' HOSTNAME : {UserObject.hostname}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' VHOST : {UserObject.vhost}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' IP : {UserObject.remote_ip}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' Country : {UserObject.geoip}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' WebIrc : {UserObject.isWebirc}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' WebWebsocket : {UserObject.isWebsocket}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' REPUTATION : {UserObject.score_connexion}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' MODES : {UserObject.umodes}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' CHANNELS : {", ".join(channels)}')
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f' CONNECTION TIME : {UserObject.connexion_datetime}')
|
||||
else:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"This user {nickoruid} doesn't exist")
|
||||
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.warning(f"Key error info user : {ke}")
|
||||
|
||||
case 'sentinel':
|
||||
# .sentinel on
|
||||
if len(cmd) < 2:
|
||||
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Syntax. /msg {dnickname} sentinel [ON | OFF]")
|
||||
return None
|
||||
|
||||
activation = str(cmd[1]).lower()
|
||||
channel_to_dont_quit = [self.ctx.Config.SALON_JAIL, self.ctx.Config.SERVICE_CHANLOG]
|
||||
|
||||
if activation == 'on':
|
||||
result = await self.ctx.Base.db_execute_query(f"SELECT distinct channel_name FROM {self.ctx.Config.TABLE_CHANNEL}")
|
||||
channels = result.fetchall()
|
||||
channel_in_db = [channel[0] for channel in channels]
|
||||
channel_to_dont_quit.extend(channel_in_db)
|
||||
|
||||
await self.update_configuration('sentinel', 1)
|
||||
for chan in self.ctx.Channel.UID_CHANNEL_DB:
|
||||
if chan.name not in channel_to_dont_quit:
|
||||
await self.ctx.Irc.Protocol.send_join_chan(uidornickname=dnickname, channel=chan.name)
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(dnickname, f"Sentinel mode activated on {channel}", channel=chan.name)
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(dnickname, f"[ {color_green}SENTINEL{color_nogc} ] Activated by {fromuser}", channel=self.ctx.Config.SERVICE_CHANLOG)
|
||||
return None
|
||||
|
||||
if activation == 'off':
|
||||
result = await self.ctx.Base.db_execute_query(f"SELECT distinct channel_name FROM {self.ctx.Config.TABLE_CHANNEL}")
|
||||
channels = result.fetchall()
|
||||
channel_in_db = [channel[0] for channel in channels]
|
||||
channel_to_dont_quit.extend(channel_in_db)
|
||||
await self.update_configuration('sentinel', 0)
|
||||
for chan in self.ctx.Channel.UID_CHANNEL_DB:
|
||||
if chan.name not in channel_to_dont_quit:
|
||||
await self.ctx.Irc.Protocol.send_part_chan(uidornickname=dnickname, channel=chan.name)
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(dnickname, f"Sentinel mode deactivated on {channel}", channel=chan.name)
|
||||
|
||||
await self.join_saved_channels()
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(dnickname, f"[ {color_red}SENTINEL{color_nogc} ] Deactivated by {fromuser}", channel=self.ctx.Config.SERVICE_CHANLOG)
|
||||
return None
|
||||
|
||||
case _:
|
||||
pass
|
||||
38
mods/defender/schemas.py
Normal file
38
mods/defender/schemas.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from core.definition import MainModel, dataclass, MUser
|
||||
|
||||
@dataclass
|
||||
class ModConfModel(MainModel):
|
||||
reputation: int = 0
|
||||
reputation_timer: int = 1
|
||||
reputation_seuil: int = 26
|
||||
reputation_score_after_release: int = 27
|
||||
reputation_ban_all_chan: int = 0
|
||||
reputation_sg: int = 1
|
||||
local_scan: int = 0
|
||||
psutil_scan: int = 0
|
||||
abuseipdb_scan: int = 0
|
||||
freeipapi_scan: int = 0
|
||||
cloudfilt_scan: int = 0
|
||||
flood: int = 0
|
||||
flood_message: int = 5
|
||||
flood_time: int = 1
|
||||
flood_timer: int = 20
|
||||
autolimit: int = 0
|
||||
autolimit_amount: int = 3
|
||||
autolimit_interval: int = 3
|
||||
sentinel: int = 0
|
||||
|
||||
@dataclass
|
||||
class FloodUser(MainModel):
|
||||
uid: str = None
|
||||
nbr_msg: int = 0
|
||||
first_msg_time: int = 0
|
||||
|
||||
|
||||
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] = []
|
||||
334
mods/defender/threads.py
Normal file
334
mods/defender/threads.py
Normal file
@@ -0,0 +1,334 @@
|
||||
import asyncio
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mods.defender.mod_defender import Defender
|
||||
|
||||
async def coro_apply_reputation_sanctions(uplink: 'Defender'):
|
||||
uplink.reputationTimer_isRunning = True
|
||||
while uplink.reputationTimer_isRunning:
|
||||
await uplink.mod_utils.action_apply_reputation_santions(uplink)
|
||||
await asyncio.sleep(5)
|
||||
|
||||
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:
|
||||
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)
|
||||
|
||||
if not result:
|
||||
continue
|
||||
|
||||
remote_ip = user.remote_ip
|
||||
fullname = f'{user.nickname}!{user.username}@{user.hostname}'
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
if not result:
|
||||
continue
|
||||
|
||||
# pseudo!ident@host
|
||||
remote_ip = user.remote_ip
|
||||
fullname = f'{user.nickname}!{user.username}@{user.hostname}'
|
||||
|
||||
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']}")
|
||||
|
||||
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:
|
||||
|
||||
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)
|
||||
|
||||
if not result:
|
||||
continue
|
||||
|
||||
remote_ip = user.remote_ip
|
||||
fullname = f'{user.nickname}!{user.username}@{user.hostname}'
|
||||
|
||||
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:
|
||||
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
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
for user_model in list_to_remove:
|
||||
uplink.Schemas.DB_LOCALSCAN_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_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:
|
||||
result = await uplink.ctx.Base.create_thread_io(uplink.mod_utils.action_scan_client_with_psutil, uplink, user)
|
||||
list_to_remove.append(user)
|
||||
|
||||
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)
|
||||
|
||||
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_autolimit(uplink: 'Defender'):
|
||||
|
||||
if uplink.mod_config.autolimit == 0:
|
||||
uplink.ctx.Logs.debug("autolimit deactivated ... canceling the thread")
|
||||
return None
|
||||
|
||||
while uplink.ctx.Irc.autolimit_started:
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
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.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.mod_config.autolimit == 0:
|
||||
uplink.ctx.Logs.debug("autolimit deactivated ... stopping the current thread")
|
||||
break
|
||||
|
||||
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"]:
|
||||
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:
|
||||
chan_list.append(chan.name)
|
||||
chanObj_copy.append({"name": chan.name, "uids_count": 0})
|
||||
|
||||
# Verifier si un salon a été vidé
|
||||
current_chan_in_list = [d.name for d in uplink.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.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.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:
|
||||
await asyncio.sleep(uplink.mod_config.autolimit_interval)
|
||||
|
||||
for chan in uplink.ctx.Channel.UID_CHANNEL_DB:
|
||||
await p.send_set_mode('-l', channel_name=chan.name)
|
||||
|
||||
uplink.ctx.Irc.autolimit_started = False
|
||||
|
||||
return None
|
||||
|
||||
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): mode-m
|
||||
channel (str): The related channel
|
||||
|
||||
"""
|
||||
timeout = uplink.mod_config.flood_timer
|
||||
await asyncio.sleep(timeout)
|
||||
|
||||
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
|
||||
await uplink.ctx.Irc.Protocol.send_set_mode('-m', channel_name=channel)
|
||||
case _:
|
||||
pass
|
||||
624
mods/defender/utils.py
Normal file
624
mods/defender/utils.py
Normal file
@@ -0,0 +1,624 @@
|
||||
from calendar import c
|
||||
import socket
|
||||
import psutil
|
||||
import requests
|
||||
import mods.defender.threads as dthreads
|
||||
from json import loads
|
||||
from re import match
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from mods.defender.schemas import FloodUser
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.loader import Loader
|
||||
from core.definition import MUser
|
||||
from mods.defender.mod_defender import Defender
|
||||
|
||||
def handle_on_reputation(uplink: 'Defender', srvmsg: list[str]):
|
||||
"""Handle reputation server message
|
||||
>>> srvmsg = [':001', 'REPUTATION', '128.128.128.128', '0']
|
||||
>>> srvmsg = [':001', 'REPUTATION', '128.128.128.128', '*0']
|
||||
Args:
|
||||
irc_instance (Irc): The Irc instance
|
||||
srvmsg (list[str]): The Server MSG
|
||||
"""
|
||||
ip = srvmsg[2]
|
||||
score = srvmsg[3]
|
||||
|
||||
if str(ip).find('*') != -1:
|
||||
# If the reputation changed, we do not need to scan the IP
|
||||
return
|
||||
|
||||
# Possibilité de déclancher les bans a ce niveau.
|
||||
if not uplink.ctx.Base.is_valid_ip(ip):
|
||||
return
|
||||
|
||||
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']
|
||||
Args:
|
||||
irc_instance (Irc): The Irc instance
|
||||
srvmsg (list[str]): The Server MSG
|
||||
confmodel (ModConfModel): The Module Configuration
|
||||
"""
|
||||
irc = uplink.ctx.Irc
|
||||
gconfig = uplink.ctx.Config
|
||||
p = irc.Protocol
|
||||
confmodel = uplink.mod_config
|
||||
|
||||
channel = str(srvmsg[3])
|
||||
mode = str(srvmsg[4])
|
||||
group_to_check = str(srvmsg[5:])
|
||||
group_to_unban = '~security-group:unknown-users'
|
||||
|
||||
if confmodel.autolimit == 1:
|
||||
if mode == '+l' or mode == '-l':
|
||||
chan = 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:
|
||||
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")
|
||||
|
||||
async def handle_on_privmsg(uplink: 'Defender', srvmsg: list[str]):
|
||||
# ['@mtag....',':python', 'PRIVMSG', '#defender', ':zefzefzregreg', 'regg', 'aerg']
|
||||
|
||||
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
|
||||
|
||||
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']
|
||||
|
||||
Args:
|
||||
irc_instance (Irc): The Irc instance
|
||||
srvmsg (list[str]): The Server MSG
|
||||
confmodel (ModConfModel): The Module Configuration
|
||||
"""
|
||||
irc = uplink.ctx.Irc
|
||||
p = irc.Protocol
|
||||
gconfig = uplink.ctx.Config
|
||||
confmodel = uplink.mod_config
|
||||
|
||||
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 = uplink.ctx.Reputation.get_reputation(parsed_UID)
|
||||
|
||||
if parsed_chan != gconfig.SALON_JAIL:
|
||||
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:
|
||||
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:
|
||||
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}")
|
||||
|
||||
uplink.ctx.Logs.debug(f'SJOIN parsed_uid : {parsed_UID}')
|
||||
|
||||
def handle_on_slog(uplink: 'Defender', srvmsg: list[str]):
|
||||
"""Handling SLOG messages
|
||||
>>> srvmsg = ['@unrealircd...', ':001', 'SLOG', 'info', 'blacklist', 'BLACKLIST_HIT', ':[Blacklist]', 'IP', '162.x.x.x', 'matches', 'blacklist', 'dronebl', '(dnsbl.dronebl.org/reply=6)']
|
||||
Args:
|
||||
irc_instance (Irc): The Irc instance
|
||||
srvmsg (list[str]): The Server MSG
|
||||
confmodel (ModConfModel): The Module Configuration
|
||||
"""
|
||||
['@unrealircd...', ':001', 'SLOG', 'info', 'blacklist', 'BLACKLIST_HIT', ':[Blacklist]', 'IP', '162.x.x.x', 'matches', 'blacklist', 'dronebl', '(dnsbl.dronebl.org/reply=6)']
|
||||
|
||||
if not uplink.ctx.Base.is_valid_ip(srvmsg[8]):
|
||||
return None
|
||||
|
||||
# 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.mod_config.psutil_scan == 1 and not cmd[7] in self.Config.WHITELISTED_IP:
|
||||
# self.psutil_remote_ip.append(cmd[7])
|
||||
|
||||
# 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.mod_config.freeipapi_scan == 1 and not cmd[7] in self.Config.WHITELISTED_IP:
|
||||
# self.freeipapi_remote_ip.append(cmd[7])
|
||||
|
||||
# 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
|
||||
|
||||
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
|
||||
"""
|
||||
p = uplink.ctx.Irc.Protocol
|
||||
u, new_nickname, timestamp = p.parse_nick(srvmsg)
|
||||
|
||||
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.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 = 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.ctx.Channel.UID_CHANNEL_DB:
|
||||
if chan.name != jail_salon:
|
||||
await p.send2socket(f":{service_id} MODE {chan.name} -b {oldnick}!*@*")
|
||||
await p.send2socket(f":{service_id} MODE {chan.name} +b {newnickname}!*@*")
|
||||
|
||||
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.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.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.ctx.Channel.UID_CHANNEL_DB:
|
||||
if chan.name != jail_salon and ban_all_chan == 1:
|
||||
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.ctx.Reputation.delete(userobj.uid)
|
||||
uplink.ctx.Logs.debug(f"Client {get_user_reputation.nickname} has been removed from Reputation local DB")
|
||||
|
||||
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']
|
||||
|
||||
Args:
|
||||
uplink (Defender): The Defender instance
|
||||
srvmsg (list[str]): The Server MSG
|
||||
"""
|
||||
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
|
||||
if _User is None:
|
||||
uplink.ctx.Logs.warning(f'Error when parsing UID', exc_info=True)
|
||||
return
|
||||
|
||||
# If user is not service or IrcOp then scan them
|
||||
if not match(r'^.*[S|o?].*$', _User.umodes):
|
||||
uplink.Schemas.DB_ABUSEIPDB_USERS.append(_User) if confmodel.abuseipdb_scan == 1 and _User.remote_ip not in gconfig.WHITELISTED_IP else None
|
||||
uplink.Schemas.DB_FREEIPAPI_USERS.append(_User) if confmodel.freeipapi_scan == 1 and _User.remote_ip not in gconfig.WHITELISTED_IP else None
|
||||
uplink.Schemas.DB_CLOUDFILT_USERS.append(_User) if confmodel.cloudfilt_scan == 1 and _User.remote_ip not in gconfig.WHITELISTED_IP else None
|
||||
uplink.Schemas.DB_PSUTIL_USERS.append(_User) if confmodel.psutil_scan == 1 and _User.remote_ip not in gconfig.WHITELISTED_IP else None
|
||||
uplink.Schemas.DB_LOCALSCAN_USERS.append(_User) if confmodel.local_scan == 1 and _User.remote_ip not in gconfig.WHITELISTED_IP else None
|
||||
|
||||
reputation_flag = confmodel.reputation
|
||||
reputation_seuil = confmodel.reputation_seuil
|
||||
|
||||
if gconfig.DEFENDER_INIT == 0:
|
||||
# Si le user n'es pas un service ni un IrcOP
|
||||
if not match(r'^.*[S|o?].*$', _User.umodes):
|
||||
if reputation_flag == 1 and _User.score_connexion <= reputation_seuil:
|
||||
# currentDateTime = self.Base.get_datetime()
|
||||
uplink.ctx.Reputation.insert(
|
||||
uplink.ctx.Definition.MReputation(
|
||||
**_User.to_dict(),
|
||||
secret_code=uplink.ctx.Utils.generate_random_string(8)
|
||||
)
|
||||
)
|
||||
if uplink.ctx.Reputation.is_exist(_User.uid):
|
||||
if reputation_flag == 1 and _User.score_connexion <= reputation_seuil:
|
||||
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
|
||||
|
||||
async def action_on_flood(uplink: 'Defender', srvmsg: list[str]):
|
||||
|
||||
confmodel = uplink.mod_config
|
||||
if confmodel.flood == 0:
|
||||
return None
|
||||
|
||||
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 = uplink.ctx.User.get_user(user_trigger)
|
||||
|
||||
if User is None or not uplink.ctx.Channel.is_valid_channel(channel_to_check=channel):
|
||||
return
|
||||
|
||||
flood_time = confmodel.flood_time
|
||||
flood_message = confmodel.flood_message
|
||||
flood_timer = confmodel.flood_timer
|
||||
service_id = gconfig.SERVICE_ID
|
||||
dnickname = gconfig.SERVICE_NICKNAME
|
||||
color_red = gconfig.COLORS.red
|
||||
color_bold = gconfig.COLORS.bold
|
||||
|
||||
get_detected_uid = User.uid
|
||||
get_detected_nickname = User.nickname
|
||||
unixtime = uplink.ctx.Utils.get_unixtime()
|
||||
get_diff_secondes = 0
|
||||
|
||||
def get_flood_user(uid: str) -> Optional[FloodUser]:
|
||||
for flood_user in flood_users:
|
||||
if flood_user.uid == uid:
|
||||
return flood_user
|
||||
|
||||
fu = get_flood_user(get_detected_uid)
|
||||
if fu is None:
|
||||
fu = FloodUser(get_detected_uid, 0, unixtime)
|
||||
flood_users.append(fu)
|
||||
|
||||
fu.nbr_msg += 1
|
||||
|
||||
get_diff_secondes = unixtime - fu.first_msg_time
|
||||
if get_diff_secondes > flood_time:
|
||||
fu.first_msg_time = unixtime
|
||||
fu.nbr_msg = 0
|
||||
get_diff_secondes = unixtime - fu.first_msg_time
|
||||
elif fu.nbr_msg > flood_message:
|
||||
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
|
||||
)
|
||||
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
|
||||
uplink.ctx.Base.create_asynctask(uplink.Threads.coro_release_mode_mute(uplink, 'mode-m', channel))
|
||||
|
||||
async def action_add_reputation_sanctions(uplink: 'Defender', jailed_uid: str ):
|
||||
|
||||
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:
|
||||
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
|
||||
|
||||
code = get_reputation.secret_code
|
||||
jailed_nickname = get_reputation.nickname
|
||||
jailed_score = get_reputation.score_connexion
|
||||
|
||||
color_red = gconfig.COLORS.red
|
||||
color_black = gconfig.COLORS.black
|
||||
color_bold = gconfig.COLORS.bold
|
||||
nogc = gconfig.COLORS.nogc
|
||||
service_id = gconfig.SERVICE_ID
|
||||
service_prefix = gconfig.SERVICE_PREFIX
|
||||
reputation_ban_all_chan = confmodel.reputation_ban_all_chan
|
||||
|
||||
if not get_reputation.isWebirc:
|
||||
# Si le user ne vient pas de webIrc
|
||||
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
|
||||
)
|
||||
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 uplink.ctx.Channel.UID_CHANNEL_DB:
|
||||
if chan.name != salon_jail:
|
||||
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}")
|
||||
|
||||
uplink.ctx.Logs.info(f"[REPUTATION] {jailed_nickname} jailed (UID: {jailed_uid}, score: {jailed_score})")
|
||||
else:
|
||||
uplink.ctx.Logs.info(f"[REPUTATION] {jailed_nickname} skipped (trusted or WebIRC)")
|
||||
uplink.ctx.Reputation.delete(jailed_uid)
|
||||
|
||||
async def action_apply_reputation_santions(uplink: 'Defender') -> None:
|
||||
|
||||
irc = uplink.ctx.Irc
|
||||
gconfig = uplink.ctx.Config
|
||||
p = irc.Protocol
|
||||
confmodel = uplink.mod_config
|
||||
|
||||
reputation_flag = confmodel.reputation
|
||||
reputation_timer = confmodel.reputation_timer
|
||||
reputation_seuil = confmodel.reputation_seuil
|
||||
ban_all_chan = confmodel.reputation_ban_all_chan
|
||||
service_id = gconfig.SERVICE_ID
|
||||
dchanlog = gconfig.SERVICE_CHANLOG
|
||||
color_red = gconfig.COLORS.red
|
||||
nogc = gconfig.COLORS.nogc
|
||||
salon_jail = gconfig.SALON_JAIL
|
||||
uid_to_clean = []
|
||||
|
||||
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 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
|
||||
)
|
||||
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")
|
||||
|
||||
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 uplink.ctx.Channel.UID_CHANNEL_DB:
|
||||
if chan.name != salon_jail and ban_all_chan == 1:
|
||||
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}.
|
||||
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
|
||||
Cette methode devra etre lancer toujours via un thread ou un timer.
|
||||
Args:
|
||||
uplink (Defender): Defender Instance
|
||||
|
||||
Returns:
|
||||
dict[str, any] | None: les informations du provider
|
||||
keys : 'countryCode', 'isProxy'
|
||||
"""
|
||||
|
||||
remote_ip = user_model.remote_ip
|
||||
if remote_ip in uplink.ctx.Config.WHITELISTED_IP:
|
||||
return None
|
||||
if uplink.mod_config.cloudfilt_scan == 0:
|
||||
return None
|
||||
if uplink.cloudfilt_key == '':
|
||||
return None
|
||||
|
||||
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)
|
||||
|
||||
# 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
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
def action_scan_client_with_freeipapi(uplink: 'Defender', user_model: 'MUser') -> Optional[dict[str, str]]:
|
||||
"""Analyse l'ip avec Freeipapi
|
||||
Cette methode devra etre lancer toujours via un thread ou un timer.
|
||||
Args:
|
||||
uplink (Defender): The Defender object Instance
|
||||
|
||||
Returns:
|
||||
dict[str, any] | None: les informations du provider
|
||||
keys : 'countryCode', 'isProxy'
|
||||
"""
|
||||
remote_ip = user_model.remote_ip
|
||||
if remote_ip in uplink.ctx.Config.WHITELISTED_IP:
|
||||
return None
|
||||
if uplink.mod_config.freeipapi_scan == 0:
|
||||
return None
|
||||
|
||||
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)
|
||||
|
||||
# Formatted output
|
||||
decoded_response: dict = loads(response.text)
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
Cette methode devra etre lancer toujours via un thread ou un timer.
|
||||
Args:
|
||||
uplink (Defender): Defender instance object
|
||||
user_model (MUser): l'objet User qui contient l'ip
|
||||
|
||||
Returns:
|
||||
dict[str, str] | None: les informations du provider
|
||||
"""
|
||||
remote_ip = user_model.remote_ip
|
||||
|
||||
if remote_ip in uplink.ctx.Config.WHITELISTED_IP:
|
||||
return None
|
||||
if uplink.mod_config.abuseipdb_scan == 0:
|
||||
return None
|
||||
if uplink.abuseipdb_key == '':
|
||||
return None
|
||||
|
||||
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
|
||||
}
|
||||
try:
|
||||
response = sess.request(method='GET', url=url, headers=headers, params=querystring, timeout=uplink.timeout)
|
||||
except (requests.exceptions.ReadTimeout, requests.exceptions.ConnectTimeout) as err:
|
||||
uplink.ctx.Logs.error(f"Time-out Error: {err}")
|
||||
return None
|
||||
except Exception as e:
|
||||
uplink.ctx.Logs.error(f"Time-out Error: {e}")
|
||||
return None
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
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
|
||||
"""
|
||||
remote_ip = user_model.remote_ip
|
||||
if remote_ip in uplink.ctx.Config.WHITELISTED_IP:
|
||||
return None
|
||||
|
||||
result = {'opened_ports': [], 'closed_ports': []}
|
||||
|
||||
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)
|
||||
|
||||
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.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)
|
||||
|
||||
Args:
|
||||
userModel (UserModel): The User Model Object
|
||||
|
||||
Returns:
|
||||
list[int]: list of ports
|
||||
"""
|
||||
remote_ip = user_model.remote_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')
|
||||
matching_ports = [conn.raddr.port for conn in connections if conn.raddr and conn.raddr.ip == remote_ip]
|
||||
uplink.ctx.Logs.debug(f"Connexion of ({remote_ip}) using ports : {str(matching_ports)}")
|
||||
|
||||
return matching_ports
|
||||
|
||||
except psutil.AccessDenied as ad:
|
||||
uplink.ctx.Logs.critical(f'psutil_scan: Permission error: {ad}')
|
||||
232
mods/jsonrpc/mod_jsonrpc.py
Normal file
232
mods/jsonrpc/mod_jsonrpc.py
Normal file
@@ -0,0 +1,232 @@
|
||||
import logging
|
||||
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 dataclasses import dataclass
|
||||
from unrealircd_rpc_py.ConnectionFactory import ConnectionFactory
|
||||
from unrealircd_rpc_py.LiveConnectionFactory import LiveConnectionFactory
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.loader import Loader
|
||||
|
||||
class Jsonrpc(IModule):
|
||||
|
||||
@dataclass
|
||||
class ModConfModel(schemas.ModConfModel):
|
||||
"""The Model containing the module parameters
|
||||
"""
|
||||
...
|
||||
|
||||
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'
|
||||
}
|
||||
|
||||
def __init__(self, context: 'Loader') -> None:
|
||||
super().__init__(context)
|
||||
self._mod_config: Optional[schemas.ModConfModel] = None
|
||||
|
||||
@property
|
||||
def mod_config(self) -> ModConfModel:
|
||||
return self._mod_config
|
||||
|
||||
async def callback_sent_to_irc(self, response: LiveRPCResult) -> None:
|
||||
|
||||
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
|
||||
|
||||
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 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
|
||||
|
||||
level = response.result.level if hasattr(response.result, 'level') else ''
|
||||
subsystem = response.result.subsystem if hasattr(response.result, 'subsystem') else ''
|
||||
event_id = response.result.event_id if hasattr(response.result, 'event_id') else ''
|
||||
log_source = response.result.log_source if hasattr(response.result, 'log_source') else ''
|
||||
msg = response.result.msg if hasattr(response.result, 'msg') else ''
|
||||
|
||||
build_msg = f"{green}{log_source}{nogc}: [{bold}{level}{bold}] {subsystem}.{event_id} - {msg}"
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg=build_msg, channel=dchanlog)
|
||||
|
||||
return None
|
||||
|
||||
def create_tables(self) -> None:
|
||||
return None
|
||||
|
||||
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
|
||||
|
||||
# Is RPC Active?
|
||||
self.is_streaming = False
|
||||
|
||||
# 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')
|
||||
|
||||
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'}
|
||||
|
||||
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.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[str]) -> None:
|
||||
|
||||
return None
|
||||
|
||||
async def hcmds(self, user: str, channel: Any, cmd: list[str], fullcmd: list[str] = []) -> None:
|
||||
|
||||
command = str(cmd[0]).lower()
|
||||
dnickname = self.ctx.Config.SERVICE_NICKNAME
|
||||
dchannel = self.ctx.Config.SERVICE_CHANLOG
|
||||
fromuser = user
|
||||
fromchannel = str(channel) if not channel is None else None
|
||||
|
||||
match command:
|
||||
|
||||
case 'jsonrpc':
|
||||
try:
|
||||
if len(cmd) < 2:
|
||||
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':
|
||||
self.ctx.Base.create_asynctask(thds.thread_subscribe(self))
|
||||
await self.update_configuration('jsonrpc', 1)
|
||||
|
||||
case 'off':
|
||||
self.ctx.Base.create_asynctask(thds.thread_unsubscribe(self))
|
||||
await self.update_configuration('jsonrpc', 0)
|
||||
|
||||
except IndexError as ie:
|
||||
self.ctx.Logs.error(ie)
|
||||
|
||||
case 'jruser':
|
||||
try:
|
||||
if len(cmd) < 2:
|
||||
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])
|
||||
rpc = self.Rpc
|
||||
|
||||
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}')
|
||||
|
||||
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}')
|
||||
|
||||
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}')
|
||||
|
||||
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.ctx.Logs.error(ie)
|
||||
|
||||
case 'jrinstances':
|
||||
try:
|
||||
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.ctx.Logs.error(f"Unknown Error: {err}")
|
||||
5
mods/jsonrpc/schemas.py
Normal file
5
mods/jsonrpc/schemas.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from core.definition import MainModel, dataclass
|
||||
|
||||
@dataclass
|
||||
class ModConfModel(MainModel):
|
||||
jsonrpc: int = 0
|
||||
67
mods/jsonrpc/threads.py
Normal file
67
mods/jsonrpc/threads.py
Normal file
@@ -0,0 +1,67 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mods.jsonrpc.mod_jsonrpc import Jsonrpc
|
||||
|
||||
async def thread_subscribe(uplink: 'Jsonrpc') -> None:
|
||||
|
||||
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
|
||||
)
|
||||
return None
|
||||
|
||||
uplink.is_streaming = True
|
||||
response = await uplink.LiveRpc.subscribe(["all"])
|
||||
|
||||
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
|
||||
)
|
||||
0
mods/jsonrpc/utils.py
Normal file
0
mods/jsonrpc/utils.py
Normal file
@@ -1,460 +0,0 @@
|
||||
from dataclasses import dataclass, fields, field
|
||||
import copy
|
||||
import random, faker, time, logging
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.irc import Irc
|
||||
|
||||
class Clone():
|
||||
|
||||
@dataclass
|
||||
class ModConfModel:
|
||||
clone_nicknames: list[str]
|
||||
|
||||
def __init__(self, ircInstance: 'Irc') -> None:
|
||||
|
||||
# Module name (Mandatory)
|
||||
self.module_name = 'mod_' + str(self.__class__.__name__).lower()
|
||||
|
||||
# Add Irc Object to the module (Mandatory)
|
||||
self.Irc = ircInstance
|
||||
|
||||
# Add Irc 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.Base.logs
|
||||
|
||||
# Add User object to the module (Mandatory)
|
||||
self.User = ircInstance.User
|
||||
|
||||
# Add Channel object to the module (Mandatory)
|
||||
self.Channel = ircInstance.Channel
|
||||
|
||||
# Add clone object to the module (Optionnal)
|
||||
self.Clone = ircInstance.Clone
|
||||
|
||||
self.Definition = ircInstance.Loader.Definition
|
||||
|
||||
# Créer les nouvelles commandes du module
|
||||
self.commands_level = {
|
||||
1: ['clone']
|
||||
}
|
||||
|
||||
# Init the module (Mandatory)
|
||||
self.__init_module()
|
||||
|
||||
# Log the module
|
||||
self.Logs.debug(f'Module {self.module_name} loaded ...')
|
||||
|
||||
def __init_module(self) -> None:
|
||||
|
||||
# Enrigstrer les nouvelles commandes dans le code
|
||||
self.__set_commands(self.commands_level)
|
||||
|
||||
# Créer les tables necessaire a votre module (ce n'es pas obligatoire)
|
||||
self.__create_tables()
|
||||
|
||||
self.stop = False
|
||||
logging.getLogger('faker').setLevel(logging.CRITICAL)
|
||||
|
||||
self.fakeEN = faker.Faker('en_GB')
|
||||
self.fakeFR = faker.Faker('fr_FR')
|
||||
|
||||
# Load module configuration (Mandatory)
|
||||
self.__load_module_configuration()
|
||||
|
||||
self.Channel.db_query_channel(action='add', module_name=self.module_name, channel_name=self.Config.CLONE_CHANNEL)
|
||||
self.Protocol.sendChanJoin(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 __set_commands(self, commands:dict[int, list[str]]) -> None:
|
||||
"""### Rajoute les commandes du module au programme principal
|
||||
|
||||
Args:
|
||||
commands (list): Liste des commandes du module
|
||||
"""
|
||||
for level, com in commands.items():
|
||||
for c in commands[level]:
|
||||
if not c in self.Irc.commands:
|
||||
self.Irc.commands_level[level].append(c)
|
||||
self.Irc.commands.append(c)
|
||||
|
||||
return None
|
||||
|
||||
def __create_tables(self) -> None:
|
||||
"""Methode qui va créer la base de donnée si elle n'existe pas.
|
||||
Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module
|
||||
Args:
|
||||
database_name (str): Nom de la base de données ( pas d'espace dans le nom )
|
||||
|
||||
Returns:
|
||||
None: Aucun retour n'es attendu
|
||||
"""
|
||||
|
||||
table_channel = '''CREATE TABLE IF NOT EXISTS clone_list (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
datetime TEXT,
|
||||
nickname TEXT,
|
||||
username TEXT
|
||||
)
|
||||
'''
|
||||
|
||||
# self.Base.db_execute_query(table_channel)
|
||||
|
||||
return None
|
||||
|
||||
def __load_module_configuration(self) -> None:
|
||||
"""### Load Module Configuration
|
||||
"""
|
||||
try:
|
||||
# Variable qui va contenir les options de configuration du module Defender
|
||||
self.ModConfig = self.ModConfModel(
|
||||
clone_nicknames=[]
|
||||
)
|
||||
|
||||
# Sync the configuration with core configuration (Mandatory)
|
||||
# self.Base.db_sync_core_config(self.module_name, self.ModConfig)
|
||||
|
||||
return None
|
||||
|
||||
except TypeError as te:
|
||||
self.Logs.critical(te)
|
||||
|
||||
def unload(self) -> None:
|
||||
"""Cette methode sera executée a chaque désactivation ou
|
||||
rechargement de module
|
||||
"""
|
||||
|
||||
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.sendChanPart(self.Config.SERVICE_NICKNAME, self.Config.CLONE_CHANNEL)
|
||||
|
||||
return None
|
||||
|
||||
def generate_vhost(self) -> str:
|
||||
|
||||
fake = self.fakeEN
|
||||
|
||||
rand_1 = fake.random_elements(['A','B','C','D','E','F','0','1','2','3','4','5','6','7','8','9'], unique=True, length=8)
|
||||
rand_2 = fake.random_elements(['A','B','C','D','E','F','0','1','2','3','4','5','6','7','8','9'], unique=True, length=8)
|
||||
rand_3 = fake.random_elements(['A','B','C','D','E','F','0','1','2','3','4','5','6','7','8','9'], unique=True, length=8)
|
||||
|
||||
vhost = ''.join(rand_1) + '.' + ''.join(rand_2) + '.' + ''.join(rand_3) + '.IP'
|
||||
return vhost
|
||||
|
||||
def generate_clones(self, group: str = 'Default') -> None:
|
||||
try:
|
||||
|
||||
fakeEN = self.fakeEN
|
||||
fakeFR = self.fakeFR
|
||||
unixtime = self.Base.get_unixtime()
|
||||
|
||||
chaine = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
generate_uid = fakeEN.random_sample(chaine, 6)
|
||||
uid = self.Config.SERVEUR_ID + ''.join(generate_uid)
|
||||
|
||||
umodes = self.Config.CLONE_UMODES
|
||||
|
||||
# Generate Username
|
||||
chaine = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
|
||||
new_username = fakeEN.random_sample(chaine, 9)
|
||||
username = ''.join(new_username)
|
||||
|
||||
# Create realname XX F|M Department
|
||||
gender = fakeEN.random_choices(['F','M'], 1)
|
||||
gender = ''.join(gender)
|
||||
|
||||
if gender == 'F':
|
||||
nickname = fakeEN.first_name_female()
|
||||
elif gender == 'M':
|
||||
nickname = fakeEN.first_name_male()
|
||||
else:
|
||||
nickname = fakeEN.first_name()
|
||||
|
||||
age = random.randint(20, 60)
|
||||
department = fakeFR.department_name()
|
||||
realname = f'{age} {gender} {department}'
|
||||
|
||||
decoded_ip = fakeEN.ipv4_private()
|
||||
hostname = fakeEN.hostname()
|
||||
|
||||
vhost = self.generate_vhost()
|
||||
|
||||
checkNickname = self.Clone.exists(nickname=nickname)
|
||||
checkUid = self.Clone.uid_exists(uid=uid)
|
||||
|
||||
while checkNickname:
|
||||
caracteres = '0123456789'
|
||||
randomize = ''.join(random.choice(caracteres) for _ in range(2))
|
||||
nickname = nickname + str(randomize)
|
||||
checkNickname = self.Clone.exists(nickname=nickname)
|
||||
|
||||
while checkUid:
|
||||
chaine = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||
generate_uid = fakeEN.random_sample(chaine, 6)
|
||||
uid = self.Config.SERVEUR_ID + ''.join(generate_uid)
|
||||
checkUid = self.Clone.uid_exists(uid=uid)
|
||||
|
||||
clone = self.Definition.MClone(
|
||||
connected=False,
|
||||
nickname=nickname,
|
||||
username=username,
|
||||
realname=realname,
|
||||
hostname=hostname,
|
||||
umodes=umodes,
|
||||
uid=uid,
|
||||
remote_ip=decoded_ip,
|
||||
vhost=vhost,
|
||||
group=group,
|
||||
channels=[]
|
||||
)
|
||||
|
||||
self.Clone.insert(clone)
|
||||
|
||||
return None
|
||||
|
||||
except AttributeError as ae:
|
||||
self.Logs.error(f'Attribute Error : {ae}')
|
||||
except Exception as err:
|
||||
self.Logs.error(f"General Error: {err}")
|
||||
|
||||
def thread_connect_clones(self, number_of_clones:int , group: str, interval: float = 0.2) -> None:
|
||||
|
||||
for i in range(0, number_of_clones):
|
||||
self.generate_clones(group=group)
|
||||
|
||||
for clone in self.Clone.UID_CLONE_DB:
|
||||
|
||||
if self.stop:
|
||||
print(f"Stop creating clones ...")
|
||||
self.stop = False
|
||||
break
|
||||
|
||||
if not clone.connected:
|
||||
self.Protocol.sendUID(clone.nickname, clone.username, clone.hostname, clone.uid, clone.umodes, clone.vhost, clone.remote_ip, clone.realname, print_log=False)
|
||||
self.Protocol.sendChanJoin(uidornickname=clone.uid, channel=self.Config.CLONE_CHANNEL, password=self.Config.CLONE_CHANNEL_PASSWORD, print_log=False)
|
||||
|
||||
time.sleep(interval)
|
||||
clone.connected = True
|
||||
|
||||
def thread_kill_clones(self, fromuser: str) -> None:
|
||||
|
||||
clone_to_kill: list[str] = []
|
||||
for clone in self.Clone.UID_CLONE_DB:
|
||||
clone_to_kill.append(clone.uid)
|
||||
|
||||
for clone_uid in clone_to_kill:
|
||||
self.Protocol.sendQuit(clone_uid, 'Gooood bye', print_log=False)
|
||||
|
||||
del clone_to_kill
|
||||
|
||||
return None
|
||||
|
||||
def cmd(self, data:list) -> None:
|
||||
try:
|
||||
service_id = self.Config.SERVICE_ID # Defender serveur id
|
||||
cmd = list(data).copy()
|
||||
|
||||
if len(cmd) < 2:
|
||||
return None
|
||||
|
||||
match cmd[1]:
|
||||
|
||||
case 'REPUTATION':
|
||||
pass
|
||||
|
||||
if len(cmd) < 3:
|
||||
return None
|
||||
|
||||
match cmd[2]:
|
||||
case 'PRIVMSG':
|
||||
# print(cmd)
|
||||
uid_sender = self.User.clean_uid(cmd[1])
|
||||
senderObj = self.User.get_User(uid_sender)
|
||||
|
||||
if senderObj.hostname in self.Config.CLONE_LOG_HOST_EXEMPT:
|
||||
return None
|
||||
|
||||
if not senderObj is None:
|
||||
senderMsg = ' '.join(cmd[4:])
|
||||
getClone = self.Clone.get_Clone(cmd[3])
|
||||
|
||||
if getClone is None:
|
||||
return None
|
||||
|
||||
if getClone.uid != self.Config.SERVICE_ID:
|
||||
final_message = f"{senderObj.nickname}!{senderObj.username}@{senderObj.hostname} > {senderMsg.lstrip(':')}"
|
||||
self.Protocol.sendPrivMsg(
|
||||
nick_from=getClone.uid,
|
||||
msg=final_message,
|
||||
channel=self.Config.CLONE_CHANNEL
|
||||
)
|
||||
|
||||
except Exception as err:
|
||||
self.Logs.error(f'General Error: {err}')
|
||||
|
||||
def _hcmds(self, user:str, channel: any, cmd: list, fullcmd: list = []) -> None:
|
||||
|
||||
try:
|
||||
command = str(cmd[0]).lower()
|
||||
fromuser = user
|
||||
|
||||
dnickname = self.Config.SERVICE_NICKNAME # Defender nickname
|
||||
|
||||
match command:
|
||||
|
||||
case 'clone':
|
||||
|
||||
if len(cmd) == 1:
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone connect NUMBER GROUP_NAME INTERVAL")
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone kill [all | nickname]")
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone join [all | nickname] #channel")
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone part [all | nickname] #channel")
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone list")
|
||||
|
||||
option = str(cmd[1]).lower()
|
||||
|
||||
match option:
|
||||
|
||||
case 'connect':
|
||||
try:
|
||||
# clone connect 5 Group 3
|
||||
self.stop = False
|
||||
number_of_clones = int(cmd[2])
|
||||
group = str(cmd[3]).lower()
|
||||
connection_interval = int(cmd[4]) if len(cmd) == 5 else 0.5
|
||||
|
||||
self.Base.create_thread(
|
||||
func=self.thread_connect_clones,
|
||||
func_args=(number_of_clones, group, connection_interval)
|
||||
)
|
||||
|
||||
except Exception as err:
|
||||
self.Logs.error(f'{err}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone connect [number of clone you want to connect] [Group]")
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f"Exemple /msg {dnickname} clone connect 6 Ambiance")
|
||||
|
||||
case 'kill':
|
||||
try:
|
||||
# clone kill [all | nickname]
|
||||
self.stop = True
|
||||
clone_name = str(cmd[2])
|
||||
clone_to_kill: list[str] = []
|
||||
|
||||
if clone_name.lower() == 'all':
|
||||
self.Base.create_thread(func=self.thread_kill_clones, func_args=(fromuser, ))
|
||||
|
||||
else:
|
||||
cloneObj = self.Clone.get_Clone(clone_name)
|
||||
if not cloneObj is None:
|
||||
self.Protocol.sendQuit(cloneObj.uid, 'Goood bye', print_log=False)
|
||||
|
||||
except Exception as err:
|
||||
self.Logs.error(f'{err}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone kill all")
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone kill clone_nickname")
|
||||
|
||||
case 'join':
|
||||
try:
|
||||
# clone join [all | nickname] #channel
|
||||
clone_name = str(cmd[2])
|
||||
clone_channel_to_join = str(cmd[3])
|
||||
|
||||
if clone_name.lower() == 'all':
|
||||
|
||||
for clone in self.Clone.UID_CLONE_DB:
|
||||
self.Protocol.sendChanJoin(uidornickname=clone.uid, channel=clone_channel_to_join, print_log=False)
|
||||
|
||||
else:
|
||||
if self.Clone.exists(clone_name):
|
||||
if not self.Clone.get_uid(clone_name) is None:
|
||||
self.Protocol.sendChanJoin(uidornickname=clone_name, channel=clone_channel_to_join, print_log=False)
|
||||
|
||||
except Exception as err:
|
||||
self.Logs.error(f'{err}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone join all #channel")
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone join clone_nickname #channel")
|
||||
|
||||
case 'part':
|
||||
try:
|
||||
# clone part [all | nickname] #channel
|
||||
clone_name = str(cmd[2])
|
||||
clone_channel_to_part = str(cmd[3])
|
||||
|
||||
if clone_name.lower() == 'all':
|
||||
|
||||
for clone in self.Clone.UID_CLONE_DB:
|
||||
self.Protocol.sendChanPart(uidornickname=clone.uid, channel=clone_channel_to_part, print_log=False)
|
||||
|
||||
else:
|
||||
if self.Clone.exists(clone_name):
|
||||
clone_uid = self.Clone.get_uid(clone_name)
|
||||
if not clone_uid is None:
|
||||
self.Protocol.sendChanPart(uidornickname=clone_uid, channel=clone_channel_to_part, print_log=False)
|
||||
|
||||
except Exception as err:
|
||||
self.Logs.error(f'{err}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone part all #channel")
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone part clone_nickname #channel")
|
||||
|
||||
case 'list':
|
||||
try:
|
||||
clone_count = len(self.Clone.UID_CLONE_DB)
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f">> Number of connected clones: {clone_count}")
|
||||
for clone_name in self.Clone.UID_CLONE_DB:
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,
|
||||
msg=f">> Nickname: {clone_name.nickname} | Username: {clone_name.username} | Realname: {clone_name.realname} | Vhost: {clone_name.vhost} | UID: {clone_name.uid} | Group: {clone_name.group} | Connected: {clone_name.connected}")
|
||||
except Exception as err:
|
||||
self.Logs.error(f'{err}')
|
||||
|
||||
case 'say':
|
||||
try:
|
||||
# clone say clone_nickname #channel message
|
||||
clone_name = str(cmd[2])
|
||||
clone_channel = str(cmd[3]) if self.Channel.Is_Channel(str(cmd[3])) else None
|
||||
|
||||
final_message = ' '.join(cmd[4:])
|
||||
|
||||
if clone_channel is None or not self.Clone.exists(clone_name):
|
||||
self.Protocol.sendNotice(
|
||||
nick_from=dnickname,
|
||||
nick_to=fromuser,
|
||||
msg=f"/msg {dnickname} clone say [clone_nickname] #channel message"
|
||||
)
|
||||
return None
|
||||
|
||||
if self.Clone.exists(clone_name):
|
||||
self.Protocol.sendPrivMsg(nick_from=clone_name, msg=final_message, channel=clone_channel)
|
||||
|
||||
except Exception as err:
|
||||
self.Logs.error(f'{err}')
|
||||
self.Protocol.sendNotice(
|
||||
nick_from=dnickname,
|
||||
nick_to=fromuser,
|
||||
msg=f"/msg {dnickname} clone say [clone_nickname] #channel message"
|
||||
)
|
||||
|
||||
case _:
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone connect NUMBER GROUP_NAME INTERVAL")
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone kill [all | nickname]")
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone join [all | nickname] #channel")
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone part [all | nickname] #channel")
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone list")
|
||||
|
||||
except IndexError as ie:
|
||||
self.Logs.error(f'Index Error: {ie}')
|
||||
except Exception as err:
|
||||
self.Logs.error(f'Index Error: {err}')
|
||||
1194
mods/mod_command.py
1194
mods/mod_command.py
File diff suppressed because it is too large
Load Diff
1793
mods/mod_defender.py
1793
mods/mod_defender.py
File diff suppressed because it is too large
Load Diff
@@ -1,335 +0,0 @@
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
from dataclasses import dataclass
|
||||
from unrealircd_rpc_py.Live import Live
|
||||
from unrealircd_rpc_py.Loader import Loader
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.irc import Irc
|
||||
|
||||
class Jsonrpc():
|
||||
|
||||
@dataclass
|
||||
class ModConfModel:
|
||||
"""The Model containing the module parameters
|
||||
"""
|
||||
jsonrpc: int = 0
|
||||
|
||||
def __init__(self, ircInstance: 'Irc') -> None:
|
||||
|
||||
# Module name (Mandatory)
|
||||
self.module_name = 'mod_' + str(self.__class__.__name__).lower()
|
||||
|
||||
# Add Irc Object to the module (Mandatory)
|
||||
self.Irc = ircInstance
|
||||
|
||||
# Add Protocol 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.Base.logs
|
||||
|
||||
# Add User object to the module (Mandatory)
|
||||
self.User = ircInstance.User
|
||||
|
||||
# Add Channel object to the module (Mandatory)
|
||||
self.Channel = ircInstance.Channel
|
||||
|
||||
# Create module commands (Mandatory)
|
||||
self.commands_level = {
|
||||
1: ['jsonrpc', 'jruser']
|
||||
}
|
||||
|
||||
# Init the module
|
||||
self.__init_module()
|
||||
|
||||
# Log the module
|
||||
self.Logs.debug(f'Module {self.module_name} loaded ...')
|
||||
|
||||
def __init_module(self) -> None:
|
||||
|
||||
# Insert module commands into the core one (Mandatory)
|
||||
self.__set_commands(self.commands_level)
|
||||
logging.getLogger('websockets').setLevel(logging.WARNING)
|
||||
|
||||
# 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: Live = Live(
|
||||
# req_method='unixsocket',
|
||||
# path_to_socket_file=self.Config.JSONRPC_PATH_TO_SOCKET_FILE,
|
||||
# callback_object_instance=self,
|
||||
# callback_method_name='callback_sent_to_irc'
|
||||
# )
|
||||
|
||||
self.UnrealIrcdRpcLive: Live = Live(
|
||||
req_method='websocket',
|
||||
url=self.Config.JSONRPC_URL,
|
||||
username=self.Config.JSONRPC_USER,
|
||||
password=self.Config.JSONRPC_PASSWORD,
|
||||
callback_object_instance=self,
|
||||
callback_method_name='callback_sent_to_irc'
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
self.subscribed = False
|
||||
|
||||
if self.Rpc.Error.code != 0:
|
||||
self.Protocol.sendPrivMsg(
|
||||
nick_from=self.Config.SERVICE_NICKNAME,
|
||||
msg=f"[{self.Config.COLORS.red}ERROR{self.Config.COLORS.nogc}] {self.Rpc.Error.message}",
|
||||
channel=self.Config.SERVICE_CHANLOG
|
||||
)
|
||||
|
||||
if self.UnrealIrcdRpcLive.Error.code != 0:
|
||||
self.Protocol.sendPrivMsg(
|
||||
nick_from=self.Config.SERVICE_NICKNAME,
|
||||
msg=f"[{self.Config.COLORS.red}ERROR{self.Config.COLORS.nogc}] {self.UnrealIrcdRpcLive.Error.message}",
|
||||
channel=self.Config.SERVICE_CHANLOG
|
||||
)
|
||||
|
||||
if self.ModConfig.jsonrpc == 1:
|
||||
self.Base.create_thread(self.thread_start_jsonrpc, run_once=True)
|
||||
|
||||
return None
|
||||
|
||||
def __set_commands(self, commands:dict[int, list[str]]) -> None:
|
||||
"""### Rajoute les commandes du module au programme principal
|
||||
|
||||
Args:
|
||||
commands (list): Liste des commandes du module
|
||||
"""
|
||||
for level, com in commands.items():
|
||||
for c in commands[level]:
|
||||
if not c in self.Irc.commands:
|
||||
self.Irc.commands_level[level].append(c)
|
||||
self.Irc.commands.append(c)
|
||||
|
||||
return None
|
||||
|
||||
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, json_response: str):
|
||||
|
||||
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 json_response.result == True:
|
||||
self.Protocol.sendPrivMsg(
|
||||
nick_from=self.Config.SERVICE_NICKNAME,
|
||||
msg=f"[{bold}{green}JSONRPC{nogc}{bold}] Event activated",
|
||||
channel=dchanlog)
|
||||
return None
|
||||
|
||||
level = json_response.result.level
|
||||
subsystem = json_response.result.subsystem
|
||||
event_id = json_response.result.event_id
|
||||
log_source = json_response.result.log_source
|
||||
msg = json_response.result.msg
|
||||
|
||||
build_msg = f"{green}{log_source}{nogc}: [{bold}{level}{bold}] {subsystem}.{event_id} - {msg}"
|
||||
|
||||
self.Protocol.sendPrivMsg(nick_from=dnickname, msg=build_msg, channel=dchanlog)
|
||||
|
||||
def thread_start_jsonrpc(self):
|
||||
|
||||
if self.UnrealIrcdRpcLive.Error.code == 0:
|
||||
self.UnrealIrcdRpcLive.subscribe(["all"])
|
||||
self.subscribed = True
|
||||
else:
|
||||
self.Protocol.sendPrivMsg(
|
||||
nick_from=self.Config.SERVICE_NICKNAME,
|
||||
msg=f"[{self.Config.COLORS.red}ERROR{self.Config.COLORS.nogc}] {self.UnrealIrcdRpcLive.Error.message}",
|
||||
channel=self.Config.SERVICE_CHANLOG
|
||||
)
|
||||
|
||||
def __load_module_configuration(self) -> None:
|
||||
"""### Load Module Configuration
|
||||
"""
|
||||
try:
|
||||
# Build the default configuration model (Mandatory)
|
||||
self.ModConfig = self.ModConfModel(jsonrpc=0)
|
||||
|
||||
# Sync the configuration with core configuration (Mandatory)
|
||||
self.Base.db_sync_core_config(self.module_name, self.ModConfig)
|
||||
|
||||
return None
|
||||
|
||||
except TypeError as te:
|
||||
self.Logs.critical(te)
|
||||
|
||||
def __update_configuration(self, param_key: str, param_value: str):
|
||||
"""Update the local and core configuration
|
||||
|
||||
Args:
|
||||
param_key (str): The parameter key
|
||||
param_value (str): The parameter value
|
||||
"""
|
||||
self.Base.db_update_core_config(self.module_name, self.ModConfig, param_key, param_value)
|
||||
|
||||
def unload(self) -> None:
|
||||
if self.UnrealIrcdRpcLive.Error.code != -1:
|
||||
self.UnrealIrcdRpcLive.unsubscribe()
|
||||
return None
|
||||
|
||||
def cmd(self, data:list) -> None:
|
||||
|
||||
return None
|
||||
|
||||
def _hcmds(self, user:str, channel: any, cmd: list, fullcmd: list = []) -> None:
|
||||
|
||||
command = str(cmd[0]).lower()
|
||||
dnickname = self.Config.SERVICE_NICKNAME
|
||||
dchannel = self.Config.SERVICE_CHANLOG
|
||||
fromuser = user
|
||||
fromchannel = str(channel) if not channel is None else None
|
||||
|
||||
match command:
|
||||
|
||||
case 'jsonrpc':
|
||||
try:
|
||||
option = str(cmd[1]).lower()
|
||||
|
||||
if len(command) == 1:
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f'/msg {dnickname} jsonrpc on')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f'/msg {dnickname} jsonrpc off')
|
||||
|
||||
match option:
|
||||
|
||||
case 'on':
|
||||
for thread in self.Base.running_threads:
|
||||
if thread.getName() == 'thread_start_jsonrpc':
|
||||
if thread.is_alive():
|
||||
self.Protocol.sendPrivMsg(
|
||||
nick_from=self.Config.SERVICE_NICKNAME,
|
||||
msg=f"Thread {thread.getName()} is running",
|
||||
channel=dchannel
|
||||
)
|
||||
else:
|
||||
self.Protocol.sendPrivMsg(
|
||||
nick_from=self.Config.SERVICE_NICKNAME,
|
||||
msg=f"Thread {thread.getName()} is not running, wait untill the process will be cleaned up",
|
||||
channel=dchannel
|
||||
)
|
||||
|
||||
self.Base.create_thread(self.thread_start_jsonrpc, run_once=True)
|
||||
self.__update_configuration('jsonrpc', 1)
|
||||
|
||||
case 'off':
|
||||
self.UnrealIrcdRpcLive.unsubscribe()
|
||||
self.__update_configuration('jsonrpc', 0)
|
||||
|
||||
except IndexError as ie:
|
||||
self.Logs.error(ie)
|
||||
|
||||
case 'jruser':
|
||||
try:
|
||||
option = str(cmd[1]).lower()
|
||||
|
||||
if len(command) == 1:
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f'/msg {dnickname} jruser get nickname')
|
||||
|
||||
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.Error.code != 0:
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f'{rpc.Error.message}')
|
||||
return None
|
||||
|
||||
chan_list = []
|
||||
for chan in UserInfo.user.channels:
|
||||
chan_list.append(chan.name)
|
||||
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f'UID : {UserInfo.id}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f'NICKNAME : {UserInfo.name}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f'USERNAME : {UserInfo.user.username}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f'REALNAME : {UserInfo.user.realname}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f'MODES : {UserInfo.user.modes}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f'CHANNELS : {chan_list}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f'SECURITY GROUP : {UserInfo.user.security_groups}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f'REPUTATION : {UserInfo.user.reputation}')
|
||||
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f'IP : {UserInfo.ip}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f'COUNTRY CODE : {UserInfo.geoip.country_code}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f'ASN : {UserInfo.geoip.asn}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f'ASNAME : {UserInfo.geoip.asname}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f'CLOAKED HOST : {UserInfo.user.cloakedhost}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f'HOSTNAME : {UserInfo.hostname}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f'VHOST : {UserInfo.user.vhost}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f'CLIENT PORT : {UserInfo.client_port}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f'SERVER PORT : {UserInfo.server_port}')
|
||||
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f'CERTFP : {UserInfo.tls.certfp}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f'CIPHER : {UserInfo.tls.cipher}')
|
||||
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f'IDLE SINCE : {UserInfo.idle_since}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f'CONNECTED SINCE : {UserInfo.connected_since}')
|
||||
|
||||
except IndexError as ie:
|
||||
self.Logs.error(ie)
|
||||
|
||||
case 'ia':
|
||||
try:
|
||||
|
||||
self.Base.create_thread(self.thread_ask_ia, ('',))
|
||||
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg=f" This is a notice to the sender ...")
|
||||
self.Protocol.sendPrivMsg(nick_from=dnickname, msg="This is private message to the sender ...", nick_to=fromuser)
|
||||
|
||||
if not fromchannel is None:
|
||||
self.Protocol.sendPrivMsg(nick_from=dnickname, msg="This is channel message to the sender ...", channel=fromchannel)
|
||||
|
||||
# How to update your module configuration
|
||||
self.__update_configuration('param_exemple2', 7)
|
||||
|
||||
# Log if you want the result
|
||||
self.Logs.debug(f"Test logs ready")
|
||||
|
||||
except Exception as err:
|
||||
self.Logs.error(f"Unknown Error: {err}")
|
||||
175
mods/mod_test.py
175
mods/mod_test.py
@@ -1,175 +0,0 @@
|
||||
from typing import TYPE_CHECKING
|
||||
from dataclasses import dataclass, fields
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.irc import Irc
|
||||
|
||||
class Test():
|
||||
|
||||
@dataclass
|
||||
class ModConfModel:
|
||||
"""The Model containing the module parameters
|
||||
"""
|
||||
param_exemple1: str
|
||||
param_exemple2: int
|
||||
|
||||
def __init__(self, ircInstance: 'Irc') -> None:
|
||||
|
||||
# Module name (Mandatory)
|
||||
self.module_name = 'mod_' + str(self.__class__.__name__).lower()
|
||||
|
||||
# Add Irc Object to the module (Mandatory)
|
||||
self.Irc = ircInstance
|
||||
|
||||
# Add 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.Base.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.commands_level = {
|
||||
0: ['test-command'],
|
||||
1: ['test_level_1'],
|
||||
2: ['test_level_2'],
|
||||
3: ['test_level_3']
|
||||
}
|
||||
|
||||
# Init the module
|
||||
self.__init_module()
|
||||
|
||||
# Log the module
|
||||
self.Logs.debug(f'Module {self.module_name} loaded ...')
|
||||
|
||||
def __init_module(self) -> None:
|
||||
|
||||
# Insert module commands into the core one (Mandatory)
|
||||
self.__set_commands(self.commands_level)
|
||||
|
||||
# Create you own tables (Mandatory)
|
||||
self.__create_tables()
|
||||
|
||||
# Load module configuration and sync with core one (Mandatory)
|
||||
self.__load_module_configuration()
|
||||
# End of mandatory methods you can start your customization #
|
||||
|
||||
return None
|
||||
|
||||
def __set_commands(self, commands:dict[int, list[str]]) -> None:
|
||||
"""### Rajoute les commandes du module au programme principal
|
||||
|
||||
Args:
|
||||
commands (list): Liste des commandes du module
|
||||
"""
|
||||
for level, com in commands.items():
|
||||
for c in commands[level]:
|
||||
if not c in self.Irc.commands:
|
||||
self.Irc.commands_level[level].append(c)
|
||||
self.Irc.commands.append(c)
|
||||
|
||||
return None
|
||||
|
||||
def __create_tables(self) -> None:
|
||||
"""Methode qui va créer la base de donnée si elle n'existe pas.
|
||||
Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module
|
||||
Args:
|
||||
database_name (str): Nom de la base de données ( pas d'espace dans le nom )
|
||||
|
||||
Returns:
|
||||
None: Aucun retour n'es attendu
|
||||
"""
|
||||
|
||||
table_logs = '''CREATE TABLE IF NOT EXISTS test_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
datetime TEXT,
|
||||
server_msg TEXT
|
||||
)
|
||||
'''
|
||||
|
||||
self.Base.db_execute_query(table_logs)
|
||||
return None
|
||||
|
||||
def __load_module_configuration(self) -> None:
|
||||
"""### Load Module Configuration
|
||||
"""
|
||||
try:
|
||||
# Build the default configuration model (Mandatory)
|
||||
self.ModConfig = self.ModConfModel(param_exemple1='param value 1', param_exemple2=1)
|
||||
|
||||
# Sync the configuration with core configuration (Mandatory)
|
||||
self.Base.db_sync_core_config(self.module_name, self.ModConfig)
|
||||
|
||||
return None
|
||||
|
||||
except TypeError as te:
|
||||
self.Logs.critical(te)
|
||||
|
||||
def __update_configuration(self, param_key: str, param_value: str):
|
||||
"""Update the local and core configuration
|
||||
|
||||
Args:
|
||||
param_key (str): The parameter key
|
||||
param_value (str): The parameter value
|
||||
"""
|
||||
self.Base.db_update_core_config(self.module_name, self.ModConfig, param_key, param_value)
|
||||
|
||||
def unload(self) -> None:
|
||||
|
||||
return None
|
||||
|
||||
def cmd(self, data:list) -> None:
|
||||
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}")
|
||||
|
||||
def _hcmds(self, user:str, channel: any, cmd: list, fullcmd: list = []) -> None:
|
||||
|
||||
command = str(cmd[0]).lower()
|
||||
dnickname = self.Config.SERVICE_NICKNAME
|
||||
fromuser = user
|
||||
fromchannel = str(channel) if not channel is None else None
|
||||
|
||||
match command:
|
||||
|
||||
case 'test-command':
|
||||
try:
|
||||
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser, msg="This is a notice to the sender ...")
|
||||
self.Protocol.sendPrivMsg(nick_from=dnickname, msg=f"This is private message to the sender ...", nick_to=fromuser)
|
||||
|
||||
if not fromchannel is None:
|
||||
self.Protocol.sendPrivMsg(nick_from=dnickname, msg=f"This is private message to the sender ...", channel=fromchannel)
|
||||
|
||||
# How to update your module configuration
|
||||
self.__update_configuration('param_exemple2', 7)
|
||||
|
||||
# Log if you want the result
|
||||
self.Logs.debug(f"Test logs ready")
|
||||
|
||||
except Exception as err:
|
||||
self.Logs.error(f"Unknown Error: {err}")
|
||||
@@ -1,569 +0,0 @@
|
||||
from typing import TYPE_CHECKING
|
||||
import re
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from core.irc import Irc
|
||||
|
||||
# Activer le systeme sur un salon (activate #salon)
|
||||
# Le service devra se connecter au salon
|
||||
# Le service devra se mettre en op
|
||||
# Soumettre un nom de user (submit nickname)
|
||||
# voter pour un ban (vote_for)
|
||||
# voter contre un ban (vote_against)
|
||||
|
||||
|
||||
|
||||
class Votekick():
|
||||
|
||||
@dataclass
|
||||
class VoteChannelModel:
|
||||
channel_name: str
|
||||
target_user: str
|
||||
voter_users: list
|
||||
vote_for: int
|
||||
vote_against: int
|
||||
|
||||
VOTE_CHANNEL_DB:list[VoteChannelModel] = []
|
||||
|
||||
def __init__(self, ircInstance: 'Irc') -> None:
|
||||
|
||||
# Module name (Mandatory)
|
||||
self.module_name = 'mod_' + str(self.__class__.__name__).lower()
|
||||
|
||||
# Add Irc Object to the module
|
||||
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
|
||||
self.Config = ircInstance.Config
|
||||
|
||||
# Add Base object to the module
|
||||
self.Base = ircInstance.Base
|
||||
|
||||
# Add logs object to the module
|
||||
self.Logs = ircInstance.Base.logs
|
||||
|
||||
# Add User object to the module
|
||||
self.User = ircInstance.User
|
||||
|
||||
# Add Channel object to the module
|
||||
self.Channel = ircInstance.Channel
|
||||
|
||||
# Créer les nouvelles commandes du module
|
||||
self.commands_level = {
|
||||
0: ['vote']
|
||||
}
|
||||
|
||||
# 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.__set_commands(self.commands_level)
|
||||
self.__create_tables()
|
||||
self.join_saved_channels()
|
||||
|
||||
return None
|
||||
|
||||
def __set_commands(self, commands:dict[int, list[str]]) -> None:
|
||||
"""### Rajoute les commandes du module au programme principal
|
||||
|
||||
Args:
|
||||
commands (list): Liste des commandes du module
|
||||
"""
|
||||
for level, com in commands.items():
|
||||
for c in commands[level]:
|
||||
if not c in self.Irc.commands:
|
||||
self.Irc.commands_level[level].append(c)
|
||||
self.Irc.commands.append(c)
|
||||
|
||||
return None
|
||||
|
||||
def __create_tables(self) -> None:
|
||||
"""Methode qui va créer la base de donnée si elle n'existe pas.
|
||||
Une Session unique pour cette classe sera crée, qui sera utilisé dans cette classe / module
|
||||
Args:
|
||||
database_name (str): Nom de la base de données ( pas d'espace dans le nom )
|
||||
|
||||
Returns:
|
||||
None: Aucun retour n'es attendu
|
||||
"""
|
||||
|
||||
table_logs = '''CREATE TABLE IF NOT EXISTS votekick_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
datetime TEXT,
|
||||
server_msg TEXT
|
||||
)
|
||||
'''
|
||||
|
||||
table_vote = '''CREATE TABLE IF NOT EXISTS votekick_channel (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
datetime TEXT,
|
||||
channel TEXT
|
||||
)
|
||||
'''
|
||||
|
||||
self.Base.db_execute_query(table_logs)
|
||||
self.Base.db_execute_query(table_vote)
|
||||
return None
|
||||
|
||||
def unload(self) -> None:
|
||||
try:
|
||||
for chan in self.VOTE_CHANNEL_DB:
|
||||
self.Protocol.sendChanPart(uidornickname=self.Config.SERVICE_ID, channel=chan.channel_name)
|
||||
|
||||
self.VOTE_CHANNEL_DB = []
|
||||
self.Logs.debug(f'Delete memory DB VOTE_CHANNEL_DB: {self.VOTE_CHANNEL_DB}')
|
||||
|
||||
return None
|
||||
except UnboundLocalError as ne:
|
||||
self.Logs.error(f'{ne}')
|
||||
except NameError as ue:
|
||||
self.Logs.error(f'{ue}')
|
||||
except Exception as err:
|
||||
self.Logs.error(f'General Error: {err}')
|
||||
|
||||
def init_vote_system(self, channel: str) -> bool:
|
||||
|
||||
response = False
|
||||
for chan in self.VOTE_CHANNEL_DB:
|
||||
if chan.channel_name == channel:
|
||||
chan.target_user = ''
|
||||
chan.voter_users = []
|
||||
chan.vote_against = 0
|
||||
chan.vote_for = 0
|
||||
response = True
|
||||
|
||||
return response
|
||||
|
||||
def insert_vote_channel(self, ChannelObject: VoteChannelModel) -> bool:
|
||||
result = False
|
||||
found = False
|
||||
for chan in self.VOTE_CHANNEL_DB:
|
||||
if chan.channel_name == ChannelObject.channel_name:
|
||||
found = True
|
||||
|
||||
if not found:
|
||||
self.VOTE_CHANNEL_DB.append(ChannelObject)
|
||||
self.Logs.debug(f"The channel has been added {ChannelObject}")
|
||||
# self.db_add_vote_channel(ChannelObject.channel_name)
|
||||
|
||||
return result
|
||||
|
||||
def db_add_vote_channel(self, channel:str) -> bool:
|
||||
"""Cette fonction ajoute les salons ou seront autoriser les votes
|
||||
|
||||
Args:
|
||||
channel (str): le salon à enregistrer.
|
||||
"""
|
||||
current_datetime = self.Base.get_datetime()
|
||||
mes_donnees = {'channel': channel}
|
||||
|
||||
response = self.Base.db_execute_query("SELECT id FROM votekick_channel WHERE channel = :channel", mes_donnees)
|
||||
|
||||
isChannelExist = response.fetchone()
|
||||
|
||||
if isChannelExist is None:
|
||||
mes_donnees = {'datetime': current_datetime, 'channel': channel}
|
||||
insert = self.Base.db_execute_query(f"INSERT INTO votekick_channel (datetime, channel) VALUES (:datetime, :channel)", mes_donnees)
|
||||
if insert.rowcount > 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
def db_delete_vote_channel(self, channel: str) -> bool:
|
||||
"""Cette fonction supprime les salons de join de Defender
|
||||
|
||||
Args:
|
||||
channel (str): le salon à enregistrer.
|
||||
"""
|
||||
mes_donnes = {'channel': channel}
|
||||
response = self.Base.db_execute_query("DELETE FROM votekick_channel WHERE channel = :channel", mes_donnes)
|
||||
|
||||
affected_row = response.rowcount
|
||||
|
||||
if affected_row > 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def join_saved_channels(self) -> None:
|
||||
|
||||
param = {'module_name': self.module_name}
|
||||
result = self.Base.db_execute_query(f"SELECT id, channel_name FROM {self.Config.TABLE_CHANNEL} WHERE module_name = :module_name", param)
|
||||
|
||||
channels = result.fetchall()
|
||||
unixtime = self.Base.get_unixtime()
|
||||
|
||||
for channel in channels:
|
||||
id, chan = channel
|
||||
self.insert_vote_channel(self.VoteChannelModel(channel_name=chan, target_user='', voter_users=[], vote_for=0, vote_against=0))
|
||||
self.Protocol.sjoin(channel=chan)
|
||||
self.Protocol.send2socket(f":{self.Config.SERVICE_NICKNAME} SAMODE {chan} +o {self.Config.SERVICE_NICKNAME}")
|
||||
|
||||
return None
|
||||
|
||||
def is_vote_ongoing(self, channel: str) -> bool:
|
||||
|
||||
response = False
|
||||
for vote in self.VOTE_CHANNEL_DB:
|
||||
if vote.channel_name == channel:
|
||||
if vote.target_user:
|
||||
response = True
|
||||
|
||||
return response
|
||||
|
||||
def timer_vote_verdict(self, channel: str) -> None:
|
||||
|
||||
dnickname = self.Config.SERVICE_NICKNAME
|
||||
|
||||
if not self.is_vote_ongoing(channel):
|
||||
return None
|
||||
|
||||
for chan in self.VOTE_CHANNEL_DB:
|
||||
if chan.channel_name == channel:
|
||||
target_user = self.User.get_nickname(chan.target_user)
|
||||
if chan.vote_for > chan.vote_against:
|
||||
self.Protocol.sendPrivMsg(
|
||||
nick_from=dnickname,
|
||||
msg=f"User {self.Config.COLORS.bold}{target_user}{self.Config.COLORS.nogc} has {chan.vote_against} votes against and {chan.vote_for} votes for. For this reason, it'll be kicked from the channel",
|
||||
channel=channel
|
||||
)
|
||||
self.Protocol.send2socket(f":{dnickname} KICK {channel} {target_user} Following the vote, you are not welcome in {channel}")
|
||||
self.Channel.delete_user_from_channel(channel, self.User.get_uid(target_user))
|
||||
elif chan.vote_for <= chan.vote_against:
|
||||
self.Protocol.sendPrivMsg(
|
||||
nick_from=dnickname,
|
||||
msg=f"User {self.Config.COLORS.bold}{target_user}{self.Config.COLORS.nogc} has {chan.vote_against} votes against and {chan.vote_for} votes for. For this reason, it\'ll remain in the channel",
|
||||
channel=channel
|
||||
)
|
||||
|
||||
# Init the system
|
||||
if self.init_vote_system(channel):
|
||||
self.Protocol.sendPrivMsg(
|
||||
nick_from=dnickname,
|
||||
msg="System vote re initiated",
|
||||
channel=channel
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
def cmd(self, data:list) -> None:
|
||||
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}")
|
||||
|
||||
def _hcmds(self, user:str, channel: any, cmd: list, fullcmd: list = []) -> None:
|
||||
# cmd is the command starting from the user command
|
||||
# full cmd is sending the entire server response
|
||||
|
||||
command = str(cmd[0]).lower()
|
||||
dnickname = self.Config.SERVICE_NICKNAME
|
||||
fromuser = user
|
||||
fromchannel = channel
|
||||
|
||||
match command:
|
||||
|
||||
case 'vote':
|
||||
|
||||
if len(cmd) == 1:
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote activate #channel')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote deactivate #channel')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote +')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote -')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote cancel')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote status')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote submit nickname')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote verdict')
|
||||
return None
|
||||
|
||||
option = str(cmd[1]).lower()
|
||||
|
||||
match option:
|
||||
|
||||
case 'activate':
|
||||
try:
|
||||
# vote activate #channel
|
||||
if self.Admin.get_Admin(fromuser) is None:
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' :Your are not allowed to execute this command')
|
||||
return None
|
||||
|
||||
sentchannel = str(cmd[2]).lower() if self.Channel.Is_Channel(str(cmd[2]).lower()) else None
|
||||
if sentchannel is None:
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f" The correct command is {self.Config.SERVICE_PREFIX}{command} {option} #CHANNEL")
|
||||
|
||||
self.insert_vote_channel(
|
||||
self.VoteChannelModel(
|
||||
channel_name=sentchannel,
|
||||
target_user='',
|
||||
voter_users=[],
|
||||
vote_for=0,
|
||||
vote_against=0
|
||||
)
|
||||
)
|
||||
|
||||
self.Channel.db_query_channel('add', self.module_name, sentchannel)
|
||||
|
||||
self.Protocol.sendChanJoin(uidornickname=dnickname, channel=sentchannel)
|
||||
self.Protocol.send2socket(f":{dnickname} SAMODE {sentchannel} +o {dnickname}")
|
||||
self.Protocol.sendPrivMsg(nick_from=dnickname,
|
||||
msg="You can now use !submit <nickname> to decide if he will stay or not on this channel ",
|
||||
channel=sentchannel
|
||||
)
|
||||
except Exception as err:
|
||||
self.Logs.error(f'{err}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option} #channel')
|
||||
self.Protocol.sendNotice(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.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f" Your are not allowed to execute this command")
|
||||
return None
|
||||
|
||||
sentchannel = str(cmd[2]).lower() if self.Channel.Is_Channel(str(cmd[2]).lower()) else None
|
||||
if sentchannel is None:
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f" The correct command is {self.Config.SERVICE_PREFIX}{command} {option} #CHANNEL")
|
||||
|
||||
self.Protocol.send2socket(f":{dnickname} SAMODE {sentchannel} -o {dnickname}")
|
||||
self.Protocol.sendChanPart(uidornickname=dnickname, channel=sentchannel)
|
||||
|
||||
for chan in self.VOTE_CHANNEL_DB:
|
||||
if chan.channel_name == sentchannel:
|
||||
self.VOTE_CHANNEL_DB.remove(chan)
|
||||
self.Channel.db_query_channel('del', self.module_name, chan.channel_name)
|
||||
|
||||
self.Logs.debug(f"The Channel {sentchannel} has been deactivated from the vote system")
|
||||
except Exception as err:
|
||||
self.Logs.error(f'{err}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f" /msg {dnickname} {command} {option} #channel")
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f" Exemple /msg {dnickname} {command} {option} #welcome")
|
||||
|
||||
case '+':
|
||||
try:
|
||||
# vote +
|
||||
channel = fromchannel
|
||||
for chan in self.VOTE_CHANNEL_DB:
|
||||
if chan.channel_name == channel:
|
||||
if fromuser in chan.voter_users:
|
||||
self.Protocol.sendPrivMsg(nick_from=dnickname,
|
||||
msg="You already submitted a vote",
|
||||
channel=channel
|
||||
)
|
||||
else:
|
||||
chan.vote_for += 1
|
||||
chan.voter_users.append(fromuser)
|
||||
self.Protocol.sendPrivMsg(nick_from=dnickname,
|
||||
msg="Vote recorded, thank you",
|
||||
channel=channel
|
||||
)
|
||||
except Exception as err:
|
||||
self.Logs.error(f'{err}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' Exemple /msg {dnickname} {command} {option}')
|
||||
|
||||
case '-':
|
||||
try:
|
||||
# vote -
|
||||
channel = fromchannel
|
||||
for chan in self.VOTE_CHANNEL_DB:
|
||||
if chan.channel_name == channel:
|
||||
if fromuser in chan.voter_users:
|
||||
self.Protocol.sendPrivMsg(nick_from=dnickname,
|
||||
msg="You already submitted a vote",
|
||||
channel=channel
|
||||
)
|
||||
else:
|
||||
chan.vote_against += 1
|
||||
chan.voter_users.append(fromuser)
|
||||
self.Protocol.sendPrivMsg(nick_from=dnickname,
|
||||
msg="Vote recorded, thank you",
|
||||
channel=channel
|
||||
)
|
||||
except Exception as err:
|
||||
self.Logs.error(f'{err}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option}')
|
||||
self.Protocol.sendNotice(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.sendNotice(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.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' You need to specify the channel => /msg {dnickname} vote_cancel #channel')
|
||||
|
||||
for vote in self.VOTE_CHANNEL_DB:
|
||||
if vote.channel_name == channel:
|
||||
self.init_vote_system(channel)
|
||||
self.Protocol.sendPrivMsg(nick_from=dnickname,
|
||||
msg="Vote system re-initiated",
|
||||
channel=channel
|
||||
)
|
||||
|
||||
except Exception as err:
|
||||
self.Logs.error(f'{err}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' Exemple /msg {dnickname} {command} {option}')
|
||||
|
||||
case 'status':
|
||||
try:
|
||||
# vote status
|
||||
for chan in self.VOTE_CHANNEL_DB:
|
||||
if chan.channel_name == channel:
|
||||
self.Protocol.sendPrivMsg(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))}",
|
||||
channel=channel
|
||||
)
|
||||
except Exception as err:
|
||||
self.Logs.error(f'{err}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option}')
|
||||
self.Protocol.sendNotice(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.sendNotice(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)
|
||||
|
||||
# check if there is an ongoing vote
|
||||
if self.is_vote_ongoing(channel):
|
||||
for vote in self.VOTE_CHANNEL_DB:
|
||||
if vote.channel_name == channel:
|
||||
ongoing_user = self.User.get_nickname(vote.target_user)
|
||||
|
||||
self.Protocol.sendPrivMsg(nick_from=dnickname,
|
||||
msg=f"There is an ongoing vote on {ongoing_user}",
|
||||
channel=channel
|
||||
)
|
||||
return False
|
||||
|
||||
# check if the user exist
|
||||
if user_submitted is None:
|
||||
self.Protocol.sendPrivMsg(nick_from=dnickname,
|
||||
msg=f"This nickname <{nickname_submitted}> do not exist",
|
||||
channel=channel
|
||||
)
|
||||
return False
|
||||
|
||||
uid_cleaned = self.Base.clean_uid(uid_submitted)
|
||||
ChannelInfo = self.Channel.get_Channel(channel)
|
||||
if ChannelInfo is None:
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' This channel [{channel}] do not exist in the Channel Object')
|
||||
return False
|
||||
|
||||
clean_uids_in_channel: list = []
|
||||
for uid in ChannelInfo.uids:
|
||||
clean_uids_in_channel.append(self.Base.clean_uid(uid))
|
||||
|
||||
if not uid_cleaned in clean_uids_in_channel:
|
||||
self.Protocol.sendPrivMsg(nick_from=dnickname,
|
||||
msg=f"This nickname <{nickname_submitted}> is not available in this channel",
|
||||
channel=channel
|
||||
)
|
||||
return False
|
||||
|
||||
# check if Ircop or Service or Bot
|
||||
pattern = fr'[o|B|S]'
|
||||
operator_user = re.findall(pattern, user_submitted.umodes)
|
||||
if operator_user:
|
||||
self.Protocol.sendPrivMsg(nick_from=dnickname,
|
||||
msg="You cant vote for this user ! he/she is protected",
|
||||
channel=channel
|
||||
)
|
||||
return False
|
||||
|
||||
for chan in self.VOTE_CHANNEL_DB:
|
||||
if chan.channel_name == channel:
|
||||
chan.target_user = self.User.get_uid(nickname_submitted)
|
||||
|
||||
self.Protocol.sendPrivMsg(nick_from=dnickname,
|
||||
msg=f"{nickname_submitted} has been targeted for a vote",
|
||||
channel=channel
|
||||
)
|
||||
|
||||
self.Base.create_timer(60, self.timer_vote_verdict, (channel, ))
|
||||
self.Protocol.sendPrivMsg(nick_from=dnickname,
|
||||
msg="This vote will end after 60 secondes",
|
||||
channel=channel
|
||||
)
|
||||
|
||||
except Exception as err:
|
||||
self.Logs.error(f'{err}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option} nickname')
|
||||
self.Protocol.sendNotice(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.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f'Your are not allowed to execute this command')
|
||||
return None
|
||||
|
||||
for chan in self.VOTE_CHANNEL_DB:
|
||||
if chan.channel_name == channel:
|
||||
target_user = self.User.get_nickname(chan.target_user)
|
||||
if chan.vote_for > chan.vote_against:
|
||||
self.Protocol.sendPrivMsg(nick_from=dnickname,
|
||||
msg=f"User {self.Config.COLORS.bold}{target_user}{self.Config.COLORS.nogc} has {chan.vote_against} votes against and {chan.vote_for} votes for. For this reason, it\'ll be kicked from the channel",
|
||||
channel=channel
|
||||
)
|
||||
self.Protocol.send2socket(f":{dnickname} KICK {channel} {target_user} Following the vote, you are not welcome in {channel}")
|
||||
elif chan.vote_for <= chan.vote_against:
|
||||
self.Protocol.sendPrivMsg(
|
||||
nick_from=dnickname,
|
||||
msg=f"User {self.Config.COLORS.bold}{target_user}{self.Config.COLORS.nogc} has {chan.vote_against} votes against and {chan.vote_for} votes for. For this reason, it\'ll remain in the channel",
|
||||
channel=channel
|
||||
)
|
||||
|
||||
# Init the system
|
||||
if self.init_vote_system(channel):
|
||||
self.Protocol.sendPrivMsg(
|
||||
nick_from=dnickname,
|
||||
msg="System vote re initiated",
|
||||
channel=channel
|
||||
)
|
||||
except Exception as err:
|
||||
self.Logs.error(f'{err}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} {command} {option}')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' Exemple /msg {dnickname} {command} {option}')
|
||||
|
||||
case _:
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote activate #channel')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote deactivate #channel')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote +')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote -')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote cancel')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote status')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote submit nickname')
|
||||
self.Protocol.sendNotice(nick_from=dnickname, nick_to=fromuser,msg=f' /msg {dnickname} vote verdict')
|
||||
143
mods/test/mod_test.py
Normal file
143
mods/test/mod_test.py
Normal file
@@ -0,0 +1,143 @@
|
||||
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.loader import Loader
|
||||
|
||||
class Test(IModule):
|
||||
|
||||
@dataclass
|
||||
class ModConfModel:
|
||||
"""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
|
||||
|
||||
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)"""
|
||||
|
||||
@property
|
||||
def mod_config(self) -> ModConfModel:
|
||||
return self._mod_config
|
||||
|
||||
def __init__(self, uplink: 'Loader'):
|
||||
super().__init__(uplink)
|
||||
self._mod_config: Optional[Test.ModConfModel] = 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
|
||||
|
||||
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
|
||||
)
|
||||
'''
|
||||
|
||||
# await self.ctx.Base.db_execute_query(table_logs)
|
||||
return None
|
||||
|
||||
async def load(self) -> None:
|
||||
"""### Load Module Configuration (Mandatory)
|
||||
"""
|
||||
# Create tables if any (Mandatory)
|
||||
await self.create_tables()
|
||||
|
||||
# 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')
|
||||
|
||||
# Build the default configuration model (Mandatory)
|
||||
self._mod_config = self.ModConfModel(param_exemple1='str', param_exemple2=1)
|
||||
|
||||
# sync the database with local variable (Mandatory)
|
||||
await self.sync_db()
|
||||
|
||||
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)
|
||||
|
||||
def unload(self) -> None:
|
||||
"""### 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
|
||||
|
||||
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__}")
|
||||
|
||||
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:
|
||||
return None
|
||||
except Exception as err:
|
||||
self.ctx.Logs.error(f"General Error: {err}")
|
||||
|
||||
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.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)
|
||||
|
||||
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
|
||||
await self.update_configuration('param_exemple2', 7)
|
||||
await self.update_configuration('param_exemple1', 'my_value')
|
||||
|
||||
# Log if you want the result
|
||||
self.ctx.Logs.debug(f"Test logs ready")
|
||||
return None
|
||||
|
||||
except Exception as err:
|
||||
self.ctx.Logs.error(f"Unknown Error: {err}")
|
||||
return None
|
||||
|
||||
case _:
|
||||
return None
|
||||
419
mods/votekick/mod_votekick.py
Normal file
419
mods/votekick/mod_votekick.py
Normal file
@@ -0,0 +1,419 @@
|
||||
"""
|
||||
File : mod_votekick.py
|
||||
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-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.loader import Loader
|
||||
|
||||
class Votekick(IModule):
|
||||
|
||||
@dataclass
|
||||
class ModConfModel(schemas.VoteChannelModel):
|
||||
...
|
||||
|
||||
MOD_HEADER: dict[str, str] = {
|
||||
'name':'votekick',
|
||||
'version':'1.0.2',
|
||||
'description':'Channel Democraty',
|
||||
'author':'Defender Team',
|
||||
'core_version':'Defender-6'
|
||||
}
|
||||
|
||||
def __init__(self, context: 'Loader') -> None:
|
||||
super().__init__(context)
|
||||
self._mod_config: Optional[schemas.VoteChannelModel] = None
|
||||
|
||||
@property
|
||||
def mod_config(self) -> ModConfModel:
|
||||
return self._mod_config
|
||||
|
||||
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
|
||||
|
||||
Returns:
|
||||
None: Aucun retour n'es attendu
|
||||
"""
|
||||
|
||||
table_logs = '''CREATE TABLE IF NOT EXISTS votekick_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
datetime TEXT,
|
||||
server_msg TEXT
|
||||
)
|
||||
'''
|
||||
|
||||
table_vote = '''CREATE TABLE IF NOT EXISTS votekick_channel (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
datetime TEXT,
|
||||
channel TEXT
|
||||
)
|
||||
'''
|
||||
|
||||
await self.ctx.Base.db_execute_query(table_logs)
|
||||
await self.ctx.Base.db_execute_query(table_vote)
|
||||
return None
|
||||
|
||||
async def load(self) -> None:
|
||||
|
||||
# Create tables.
|
||||
await self.create_tables()
|
||||
|
||||
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.
|
||||
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:
|
||||
await self.ctx.Irc.Protocol.send_part_chan(uidornickname=self.ctx.Config.SERVICE_ID, channel=chan.channel_name)
|
||||
|
||||
self.VoteKickManager.VOTE_CHANNEL_DB = []
|
||||
self.ctx.Logs.debug(f'Delete memory DB VOTE_CHANNEL_DB: {self.VoteKickManager.VOTE_CHANNEL_DB}')
|
||||
|
||||
self.ctx.Commands.drop_command_by_module(self.module_name)
|
||||
|
||||
return None
|
||||
except UnboundLocalError as ne:
|
||||
self.ctx.Logs.error(f'{ne}')
|
||||
except NameError as ue:
|
||||
self.ctx.Logs.error(f'{ue}')
|
||||
except Exception as err:
|
||||
self.ctx.Logs.error(f'General Error: {err}')
|
||||
|
||||
def cmd(self, data: list) -> None:
|
||||
|
||||
if not data or len(data) < 2:
|
||||
return None
|
||||
|
||||
cmd = data.copy() if isinstance(data, list) else list(data).copy()
|
||||
index, command = self.ctx.Irc.Protocol.get_ircd_protocol_poisition(cmd)
|
||||
if index == -1:
|
||||
return None
|
||||
|
||||
try:
|
||||
|
||||
match command:
|
||||
|
||||
case 'PRIVMSG':
|
||||
return None
|
||||
|
||||
case 'QUIT':
|
||||
return None
|
||||
|
||||
case _:
|
||||
return None
|
||||
|
||||
except KeyError as ke:
|
||||
self.ctx.Logs.error(f"Key Error: {ke}")
|
||||
except IndexError as ie:
|
||||
self.ctx.Logs.error(f"{ie} / {cmd} / length {str(len(cmd))}")
|
||||
except Exception as err:
|
||||
self.ctx.Logs.error(f"General Error: {err}")
|
||||
|
||||
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.ctx.Config.SERVICE_NICKNAME
|
||||
fromuser = user
|
||||
fromchannel = channel
|
||||
|
||||
match command:
|
||||
|
||||
case 'vote':
|
||||
|
||||
if len(cmd) == 1:
|
||||
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()
|
||||
|
||||
match option:
|
||||
|
||||
case 'activate':
|
||||
try:
|
||||
# vote activate #channel
|
||||
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.ctx.Channel.is_valid_channel(str(cmd[2]).lower()) else None
|
||||
if sentchannel is None:
|
||||
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):
|
||||
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
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
except Exception as err:
|
||||
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.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.ctx.Channel.is_valid_channel(str(cmd[2]).lower()) else None
|
||||
if sentchannel is None:
|
||||
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")
|
||||
|
||||
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):
|
||||
await self.ctx.Channel.db_query_channel('del', self.module_name, sentchannel)
|
||||
return None
|
||||
|
||||
except Exception as err:
|
||||
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, '+'):
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg="Vote recorded, thank you",channel=channel)
|
||||
else:
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg="You already submitted a vote", channel=channel)
|
||||
|
||||
except Exception as err:
|
||||
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, '-'):
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg="Vote recorded, thank you",channel=channel)
|
||||
else:
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname, msg="You already submitted a vote", channel=channel)
|
||||
|
||||
except Exception as err:
|
||||
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.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.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):
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname,
|
||||
msg="Vote system re-initiated",
|
||||
channel=channel
|
||||
)
|
||||
|
||||
except Exception as err:
|
||||
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:
|
||||
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.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.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.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.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
|
||||
)
|
||||
return None
|
||||
|
||||
# check if the user exist
|
||||
if user_submitted is None:
|
||||
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.ctx.Utils.clean_uid(uid_submitted)
|
||||
channel_obj = self.ctx.Channel.get_channel(channel)
|
||||
if channel_obj is None:
|
||||
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.ctx.Utils.clean_uid(uid))
|
||||
|
||||
if not uid_cleaned in clean_uids_in_channel:
|
||||
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
|
||||
)
|
||||
return None
|
||||
|
||||
# check if Ircop or Service or Bot
|
||||
pattern = fr'[o|B|S]'
|
||||
operator_user = re.findall(pattern, user_submitted.umodes)
|
||||
if operator_user:
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname,
|
||||
msg="You cant vote for this user ! he/she is protected",
|
||||
channel=channel
|
||||
)
|
||||
return None
|
||||
|
||||
for chan in self.VoteKickManager.VOTE_CHANNEL_DB:
|
||||
if chan.channel_name == channel:
|
||||
chan.target_user = self.ctx.User.get_uid(nickname_submitted)
|
||||
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(nick_from=dnickname,
|
||||
msg=f"{nickname_submitted} has been targeted for a vote",
|
||||
channel=channel
|
||||
)
|
||||
|
||||
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.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.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.ctx.User.get_nickname(votec.target_user)
|
||||
if votec.vote_for >= votec.vote_against:
|
||||
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
|
||||
)
|
||||
await self.ctx.Irc.Protocol.send2socket(f":{dnickname} KICK {channel} {target_user} Following the vote, you are not welcome in {channel}")
|
||||
else:
|
||||
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 remain in the channel",
|
||||
channel=channel
|
||||
)
|
||||
|
||||
if self.VoteKickManager.init_vote_system(channel):
|
||||
await self.ctx.Irc.Protocol.send_priv_msg(
|
||||
nick_from=dnickname,
|
||||
msg="System vote re initiated",
|
||||
channel=channel
|
||||
)
|
||||
return None
|
||||
|
||||
except Exception as err:
|
||||
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 _:
|
||||
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 _:
|
||||
return None
|
||||
11
mods/votekick/schemas.py
Normal file
11
mods/votekick/schemas.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from typing import Optional
|
||||
from core.definition import MainModel
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
@dataclass
|
||||
class VoteChannelModel(MainModel):
|
||||
channel_name: Optional[str] = None
|
||||
target_user: Optional[str] = None
|
||||
voter_users: list = field(default_factory=list)
|
||||
vote_for: int = 0
|
||||
vote_against: int = 0
|
||||
43
mods/votekick/threads.py
Normal file
43
mods/votekick/threads.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import asyncio
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mods.votekick.mod_votekick import Votekick
|
||||
|
||||
async def timer_vote_verdict(uplink: 'Votekick', channel: str) -> None:
|
||||
|
||||
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.ctx.User.get_nickname(votec.target_user)
|
||||
|
||||
if votec.vote_for >= votec.vote_against and votec.vote_for != 0:
|
||||
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
|
||||
)
|
||||
await uplink.ctx.Irc.Protocol.send2socket(f":{dnickname} KICK {channel} {target_user} Following the vote, you are not welcome in {channel}")
|
||||
else:
|
||||
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 remain in the channel",
|
||||
channel=channel
|
||||
)
|
||||
|
||||
if uplink.VoteKickManager.init_vote_system(channel):
|
||||
await uplink.ctx.Irc.Protocol.send_priv_msg(
|
||||
nick_from=dnickname,
|
||||
msg="System vote re initiated",
|
||||
channel=channel
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
return None
|
||||
74
mods/votekick/utils.py
Normal file
74
mods/votekick/utils.py
Normal file
@@ -0,0 +1,74 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mods.votekick.mod_votekick import Votekick
|
||||
|
||||
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
|
||||
`votekick_channel` table. If not, it inserts a new entry with the current timestamp.
|
||||
|
||||
Args:
|
||||
uplink (Votekick): The main votekick system instance that provides access to utilities and database operations.
|
||||
channel (str): The name of the channel to be added to the database.
|
||||
|
||||
Returns:
|
||||
bool: True if the channel was successfully inserted into the database.
|
||||
False if the channel already exists or the insertion failed.
|
||||
"""
|
||||
current_datetime = uplink.ctx.Utils.get_sdatetime()
|
||||
mes_donnees = {'channel': channel}
|
||||
|
||||
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 = 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:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
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
|
||||
if it exists. It returns True if the deletion was successful.
|
||||
|
||||
Args:
|
||||
uplink (Votekick): The main votekick system instance used to execute the database operation.
|
||||
channel (str): The name of the channel to be removed from the database.
|
||||
|
||||
Returns:
|
||||
bool: True if the channel was successfully deleted, False if no rows were affected.
|
||||
"""
|
||||
mes_donnes = {'channel': channel}
|
||||
response = await uplink.ctx.Base.db_execute_query("DELETE FROM votekick_channel WHERE channel = :channel", mes_donnes)
|
||||
|
||||
affected_row = response.rowcount
|
||||
|
||||
if affected_row > 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
async def join_saved_channels(uplink: 'Votekick') -> None:
|
||||
|
||||
param = {'module_name': uplink.module_name}
|
||||
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)
|
||||
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
|
||||
163
mods/votekick/votekick_manager.py
Normal file
163
mods/votekick/votekick_manager.py
Normal file
@@ -0,0 +1,163 @@
|
||||
from typing import TYPE_CHECKING, Literal, Optional
|
||||
from mods.votekick.schemas import VoteChannelModel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mods.votekick.mod_votekick import Votekick
|
||||
|
||||
class VotekickManager:
|
||||
|
||||
VOTE_CHANNEL_DB:list[VoteChannelModel] = []
|
||||
|
||||
def __init__(self, uplink: 'Votekick'):
|
||||
self.uplink = uplink
|
||||
self.Logs = uplink.ctx.Logs
|
||||
self.Utils = uplink.ctx.Utils
|
||||
|
||||
def activate_new_channel(self, channel_name: str) -> bool:
|
||||
"""Activate a new channel in the votekick systeme
|
||||
|
||||
Args:
|
||||
channel_name (str): The channel name you want to activate
|
||||
|
||||
Returns:
|
||||
bool: True if it was activated
|
||||
"""
|
||||
votec = self.get_vote_channel_model(channel_name)
|
||||
|
||||
if votec is None:
|
||||
self.VOTE_CHANNEL_DB.append(
|
||||
VoteChannelModel(
|
||||
channel_name=channel_name,
|
||||
target_user='',
|
||||
voter_users=[],
|
||||
vote_for=0,
|
||||
vote_against=0
|
||||
)
|
||||
)
|
||||
self.Logs.debug(f"[VOTEKICK MANAGER] {channel_name} has been activated.")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def init_vote_system(self, channel_name: str) -> bool:
|
||||
"""Initializes or resets the votekick system for a given channel.
|
||||
|
||||
This method clears the current target, voter list, and vote counts
|
||||
in preparation for a new votekick session.
|
||||
|
||||
Args:
|
||||
channel_name (str): The name of the channel for which the votekick system should be initialized.
|
||||
|
||||
Returns:
|
||||
bool: True if the votekick system was successfully initialized, False if the channel is not found.
|
||||
"""
|
||||
votec = self.get_vote_channel_model(channel_name)
|
||||
|
||||
if votec is None:
|
||||
self.Logs.debug(f"[VOTEKICK MANAGER] The channel ({channel_name}) is not active!")
|
||||
return False
|
||||
|
||||
votec.target_user = ''
|
||||
votec.voter_users = []
|
||||
votec.vote_for = 0
|
||||
votec.vote_against = 0
|
||||
self.Logs.debug(f"[VOTEKICK MANAGER] The channel ({channel_name}) has been successfully initialized!")
|
||||
return True
|
||||
|
||||
def get_vote_channel_model(self, channel_name: str) -> Optional[VoteChannelModel]:
|
||||
"""Get Vote Channel Object model
|
||||
|
||||
Args:
|
||||
channel_name (str): The channel name you want to activate
|
||||
|
||||
Returns:
|
||||
(VoteChannelModel | None): The VoteChannelModel if exist
|
||||
"""
|
||||
for vote in self.VOTE_CHANNEL_DB:
|
||||
if vote.channel_name.lower() == channel_name.lower():
|
||||
self.Logs.debug(f"[VOTEKICK MANAGER] {channel_name} has been found in the VOTE_CHANNEL_DB")
|
||||
return vote
|
||||
|
||||
return None
|
||||
|
||||
def drop_vote_channel_model(self, channel_name: str) -> bool:
|
||||
"""Drop a channel from the votekick system.
|
||||
|
||||
Args:
|
||||
channel_name (str): The channel name you want to drop
|
||||
|
||||
Returns:
|
||||
bool: True if the channel has been droped.
|
||||
"""
|
||||
votec = self.get_vote_channel_model(channel_name)
|
||||
|
||||
if votec:
|
||||
self.VOTE_CHANNEL_DB.remove(votec)
|
||||
self.Logs.debug(f"[VOTEKICK MANAGER] {channel_name} has been removed from the VOTE_CHANNEL_DB")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def is_vote_ongoing(self, channel_name: str) -> bool:
|
||||
"""Check if there is an angoing vote on the channel provided
|
||||
|
||||
Args:
|
||||
channel_name (str): The channel name to check
|
||||
|
||||
Returns:
|
||||
bool: True if there is an ongoing vote on the channel provided.
|
||||
"""
|
||||
|
||||
votec = self.get_vote_channel_model(channel_name)
|
||||
|
||||
if votec is None:
|
||||
self.Logs.debug(f"[VOTEKICK MANAGER] {channel_name} is not activated!")
|
||||
return False
|
||||
|
||||
if votec.target_user:
|
||||
self.Logs.debug(f'[VOTEKICK MANAGER] A vote is ongoing on {channel_name}')
|
||||
return True
|
||||
|
||||
self.Logs.debug(f'[VOTEKICK MANAGER] {channel_name} is activated but there is no ongoing vote!')
|
||||
|
||||
return False
|
||||
|
||||
def action_vote(self, channel_name: str, nickname: str, action: Literal['+', '-']) -> bool:
|
||||
"""
|
||||
Registers a vote (for or against) in an active votekick session on a channel.
|
||||
|
||||
Args:
|
||||
channel_name (str): The name of the channel where the votekick session is active.
|
||||
nickname (str): The nickname of the user casting the vote.
|
||||
action (Literal['+', '-']): The vote action. Use '+' to vote for kicking, '-' to vote against.
|
||||
|
||||
Returns:
|
||||
bool: True if the vote was successfully registered, False otherwise.
|
||||
This can fail if:
|
||||
- The action is invalid (not '+' or '-')
|
||||
- The user has already voted
|
||||
- The channel has no active votekick session
|
||||
"""
|
||||
if action not in ['+', '-']:
|
||||
self.Logs.debug(f"[VOTEKICK MANAGER] The action must be + or - while you have provided ({action})")
|
||||
return False
|
||||
votec = self.get_vote_channel_model(channel_name)
|
||||
|
||||
if votec:
|
||||
client_obj = self.uplink.ctx.User.get_user(votec.target_user)
|
||||
client_to_punish = votec.target_user if client_obj is None else client_obj.nickname
|
||||
if nickname in votec.voter_users:
|
||||
self.Logs.debug(f"[VOTEKICK MANAGER] This nickname ({nickname}) has already voted for ({client_to_punish})")
|
||||
return False
|
||||
else:
|
||||
if action == '+':
|
||||
votec.vote_for += 1
|
||||
elif action == '-':
|
||||
votec.vote_against += 1
|
||||
|
||||
votec.voter_users.append(nickname)
|
||||
self.Logs.debug(f"[VOTEKICK MANAGER] The ({nickname}) has voted to ban ({client_to_punish})")
|
||||
return True
|
||||
else:
|
||||
self.Logs.debug(f"[VOTEKICK MANAGER] This channel {channel_name} is not active!")
|
||||
return False
|
||||
8
requirements.txt
Normal file
8
requirements.txt
Normal 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
|
||||
17
version.json
17
version.json
@@ -1,9 +1,12 @@
|
||||
{
|
||||
"version": "6.0.0",
|
||||
"version": "6.4.1",
|
||||
|
||||
"requests": "2.32.3",
|
||||
"psutil": "6.0.0",
|
||||
"unrealircd_rpc_py": "1.0.6",
|
||||
"sqlalchemy": "2.0.35",
|
||||
"faker": "30.1.0"
|
||||
}
|
||||
"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"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user