Merge pull request #98 from adator85/asyncio

Fix Asyncio update
This commit is contained in:
adator
2025-11-23 18:24:33 +01:00
committed by GitHub
31 changed files with 826 additions and 711 deletions

View File

@@ -2,17 +2,18 @@ import asyncio
import os import os
import re import re
import json import json
import time
import socket import socket
import threading import threading
import ipaddress import ipaddress
import ast import ast
import requests import requests
import concurrent.futures
from dataclasses import fields from dataclasses import fields
from typing import Any, Callable, Iterable, Optional, TYPE_CHECKING from typing import Any, Awaitable, Callable, Optional, TYPE_CHECKING, Union
from base64 import b64decode, b64encode from base64 import b64decode, b64encode
from sqlalchemy import create_engine, Engine, Connection, CursorResult from sqlalchemy import create_engine, Engine, Connection, CursorResult
from sqlalchemy.sql import text from sqlalchemy.sql import text
import core.definition as dfn
if TYPE_CHECKING: if TYPE_CHECKING:
from core.loader import Loader from core.loader import Loader
@@ -36,12 +37,15 @@ class Base:
# Liste des threads en cours # 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
# List of all async tasks
self.running_asynctasks: list[asyncio.Task] = self.Settings.RUNNING_ASYNCTASKS
# Les sockets ouvert # Les sockets ouvert
self.running_sockets: list[socket.socket] = self.Settings.RUNNING_SOCKETS 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 # Liste des fonctions en attentes
self.periodic_func: dict[object] = self.Settings.PERIODIC_FUNC self.periodic_func: dict[object] = self.Settings.PERIODIC_FUNC
@@ -71,32 +75,32 @@ class Base:
return None return None
def __get_latest_defender_version(self) -> None: def __get_latest_defender_version(self) -> None:
try: self.logs.debug(f'-- Looking for a new version available on Github')
self.logs.debug(f'-- Looking for a new version available on Github') token = ''
token = '' json_url = f'https://raw.githubusercontent.com/adator85/DEFENDER/main/version.json'
json_url = f'https://raw.githubusercontent.com/adator85/DEFENDER/main/version.json' headers = {
headers = { 'Authorization': f'token {token}',
'Authorization': f'token {token}', 'Accept': 'application/vnd.github.v3.raw' # Indique à GitHub que nous voulons le contenu brut du fichier
'Accept': 'application/vnd.github.v3.raw' # Indique à GitHub que nous voulons le contenu brut du fichier }
}
if token == '': with requests.Session() as sess:
response = requests.get(json_url, timeout=self.Config.API_TIMEOUT) try:
else: if token == '':
response = requests.get(json_url, headers=headers, timeout=self.Config.API_TIMEOUT) 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 response.raise_for_status() # Vérifie si la requête a réussi
json_response:dict = response.json() json_response:dict = response.json()
# self.LATEST_DEFENDER_VERSION = json_response["version"] self.Config.LATEST_VERSION = json_response.get('version', '')
self.Config.LATEST_VERSION = json_response['version'] return None
return None except requests.HTTPError as err:
except requests.HTTPError as err: self.logs.error(f'Github not available to fetch latest version: {err}')
self.logs.error(f'Github not available to fetch latest version: {err}') except:
except: self.logs.warning(f'Github not available to fetch latest version')
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 """Check if there is a new version available
Args: Args:
@@ -359,8 +363,8 @@ class Base:
except Exception as err: except Exception as err:
self.logs.error(err, exc_info=True) self.logs.error(err, exc_info=True)
def create_asynctask(self, func: Any, *, async_name: str = None, run_once: bool = False) -> asyncio.Task: 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_asynctasks variable """Create a new asynchrone and store it into running_iotasks variable
Args: Args:
func (Callable): The function you want to call in asynchrone way func (Callable): The function you want to call in asynchrone way
@@ -379,26 +383,71 @@ class Base:
task = asyncio.create_task(func, name=name) task = asyncio.create_task(func, name=name)
task.add_done_callback(self.asynctask_done) task.add_done_callback(self.asynctask_done)
self.running_asynctasks.append(task) self.running_iotasks.append(task)
self.logs.debug(f"++ New asynchrone task created as: {task.get_name()}") self.logs.debug(f"=== New IO task created as: {task.get_name()}")
return task return task
def asynctask_done(self, task: asyncio.Task): async def create_thread_io(self, func: Callable[..., Any], *args, run_once: bool = False, thread_flag: bool = False) -> Optional[Any]:
"""Run threads via asyncio.
Args:
func (Callable[..., Any]): The blocking IO function
run_once (bool, optional): If it should be run once.. Defaults to False.
thread_flag (bool, optional): If you are using a endless loop, use the threading Event object. Defaults to False.
Returns:
Any: The final result of the blocking IO function
"""
if run_once:
for iothread in self.running_iothreads:
if func.__name__.lower() == iothread.name.lower():
return None
with concurrent.futures.ThreadPoolExecutor(max_workers=1, thread_name_prefix=func.__name__) as executor:
loop = asyncio.get_event_loop()
largs = list(args)
thread_event: Optional[threading.Event] = None
if thread_flag:
thread_event = threading.Event()
thread_event.set()
largs.insert(0, thread_event)
future = loop.run_in_executor(executor, func, *tuple(largs))
future.add_done_callback(self.asynctask_done)
id_obj = self.Loader.Definition.MThread(
name=func.__name__,
thread_id=list(executor._threads)[0].native_id,
thread_event=thread_event,
thread_obj=list(executor._threads)[0],
executor=executor,
future=future)
self.running_iothreads.append(id_obj)
self.logs.debug(f"=== New thread started {func.__name__} with max workers set to: {executor._max_workers}")
result = await future
self.running_iothreads.remove(id_obj)
return result
def asynctask_done(self, task: Union[asyncio.Task, asyncio.Future]):
"""Log task when done """Log task when done
Args: Args:
task (asyncio.Task): The Asyncio Task callback task (asyncio.Task): The Asyncio Task callback
""" """
name = task.get_name() if isinstance(task, asyncio.Task) else "Thread"
task_or_future = "Task" if isinstance(task, asyncio.Task) else "Future"
try: try:
if task.exception(): if task.exception():
self.logs.error(f"[ASYNCIO] Task {task.get_name()} failed with exception: {task.exception()}") self.logs.error(f"[ASYNCIO] {task_or_future} {name} failed with exception: {task.exception()}")
else: else:
self.logs.debug(f"[ASYNCIO] Task {task.get_name()} completed successfully.") self.logs.debug(f"[ASYNCIO] {task_or_future} {name} completed successfully.")
except asyncio.CancelledError as ce: except asyncio.CancelledError as ce:
self.logs.debug(f"[ASYNCIO] Task {task.get_name()} terminated with cancelled error.") self.logs.debug(f"[ASYNCIO] {task_or_future} {name} terminated with cancelled error. {ce}")
except asyncio.InvalidStateError as ie: except asyncio.InvalidStateError as ie:
self.logs.debug(f"[ASYNCIO] Task {task.get_name()} terminated with invalid state error.") self.logs.debug(f"[ASYNCIO] {task_or_future} {name} terminated with invalid state error. {ie}")
def is_thread_alive(self, thread_name: str) -> bool: def is_thread_alive(self, thread_name: str) -> bool:
"""Check if the thread is still running! using the is_alive method of Threads. """Check if the thread is still running! using the is_alive method of Threads.
@@ -492,59 +541,6 @@ class Base:
self.running_sockets.remove(soc) self.running_sockets.remove(soc)
self.logs.debug(f"-- Socket ==> closed {str(soc.fileno())}") self.logs.debug(f"-- Socket ==> closed {str(soc.fileno())}")
async def shutdown(self) -> None:
"""Methode qui va préparer l'arrêt complêt du service
"""
# Stop RpcServer if running
await self.Loader.RpcServer.stop_server()
# unload modules.
self.logs.debug(f"=======> Unloading all modules!")
for module in self.Loader.ModuleUtils.model_get_loaded_modules().copy():
await self.Loader.ModuleUtils.unload_one_module(module.module_name)
self.logs.debug(f"=======> Closing all Coroutines!")
try:
await asyncio.wait_for(asyncio.gather(*self.running_asynctasks), timeout=5)
except asyncio.exceptions.TimeoutError as te:
self.logs.debug(f"Asyncio Timeout reached! {te}")
for task in self.running_asynctasks:
task.cancel()
except asyncio.exceptions.CancelledError as cerr:
self.logs.debug(f"Asyncio CancelledError reached! {cerr}")
# Nettoyage des timers
self.logs.debug(f"=======> Checking for Timers to stop")
for timer in self.running_timers:
while timer.is_alive():
self.logs.debug(f"> waiting for {timer.name} to close")
timer.cancel()
await asyncio.sleep(0.2)
self.running_timers.remove(timer)
self.logs.debug(f"> Cancelling {timer.name} {timer.native_id}")
self.logs.debug(f"=======> Checking for Threads to stop")
for thread in self.running_threads:
if thread.name == 'heartbeat' and thread.is_alive():
self.execute_periodic_action()
self.logs.debug(f"> Running the last periodic action")
self.running_threads.remove(thread)
self.logs.debug(f"> Cancelling {thread.name} {thread.native_id}")
self.logs.debug(f"=======> Checking for Sockets to stop")
for soc in self.running_sockets:
soc.close()
while soc.fileno() != -1:
soc.close()
self.running_sockets.remove(soc)
self.logs.debug(f"> Socket ==> closed {str(soc.fileno())}")
self.db_close()
return None
def db_init(self) -> tuple[Engine, Connection]: def db_init(self) -> tuple[Engine, Connection]:
db_directory = self.Config.DB_PATH db_directory = self.Config.DB_PATH

View File

@@ -12,12 +12,14 @@ class IRPC:
self.http_status_code = http_status_code self.http_status_code = http_status_code
self.response_model = { self.response_model = {
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": 'unknown',
"id": 123 "id": 123
} }
def reset(self): def reset(self):
self.response_model = { self.response_model = {
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": 'unknown',
"id": 123 "id": 123
} }

View File

@@ -14,12 +14,7 @@ class Admin:
Args: Args:
loader (Loader): The Loader Instance. loader (Loader): The Loader Instance.
""" """
self.Logs = loader.Logs self._ctx = loader
self.Base = loader.Base
self.Setting = loader.Settings
self.Config = loader.Config
self.User = loader.User
self.Definition = loader.Definition
def insert(self, new_admin: MAdmin) -> bool: def insert(self, new_admin: MAdmin) -> bool:
"""Insert a new admin object model """Insert a new admin object model
@@ -33,11 +28,11 @@ class Admin:
for record in self.UID_ADMIN_DB: for record in self.UID_ADMIN_DB:
if record.uid == new_admin.uid: if record.uid == new_admin.uid:
self.Logs.debug(f'{record.uid} already exist') self._ctx.Logs.debug(f'{record.uid} already exist')
return False return False
self.UID_ADMIN_DB.append(new_admin) self.UID_ADMIN_DB.append(new_admin)
self.Logs.debug(f'A new admin ({new_admin.nickname}) has been created') self._ctx.Logs.debug(f'A new admin ({new_admin.nickname}) has been created')
return True return True
def update_nickname(self, uid: str, new_admin_nickname: str) -> bool: def update_nickname(self, uid: str, new_admin_nickname: str) -> bool:
@@ -55,11 +50,11 @@ class Admin:
if record.uid == uid: if record.uid == uid:
# If the admin exist, update and do not go further # If the admin exist, update and do not go further
record.nickname = new_admin_nickname record.nickname = new_admin_nickname
self.Logs.debug(f'UID ({record.uid}) has been updated with new nickname {new_admin_nickname}') self._ctx.Logs.debug(f'UID ({record.uid}) has been updated with new nickname {new_admin_nickname}')
return True return True
self.Logs.debug(f'The new nickname {new_admin_nickname} was not updated, uid = {uid} - The Client is not an admin') self._ctx.Logs.debug(f'The new nickname {new_admin_nickname} was not updated, uid = {uid} - The Client is not an admin')
return False return False
def update_level(self, nickname: str, new_admin_level: int) -> bool: def update_level(self, nickname: str, new_admin_level: int) -> bool:
@@ -77,10 +72,10 @@ class Admin:
if record.nickname == nickname: if record.nickname == nickname:
# If the admin exist, update and do not go further # If the admin exist, update and do not go further
record.level = new_admin_level record.level = new_admin_level
self.Logs.debug(f'Admin ({record.nickname}) has been updated with new level {new_admin_level}') self._ctx.Logs.debug(f'Admin ({record.nickname}) has been updated with new level {new_admin_level}')
return True return True
self.Logs.debug(f'The new level {new_admin_level} was not updated, nickname = {nickname} - The Client is not an admin') self._ctx.Logs.debug(f'The new level {new_admin_level} was not updated, nickname = {nickname} - The Client is not an admin')
return False return False
@@ -96,10 +91,10 @@ class Admin:
admin_obj = self.get_admin(uidornickname) admin_obj = self.get_admin(uidornickname)
if admin_obj: if admin_obj:
self.UID_ADMIN_DB.remove(admin_obj) self.UID_ADMIN_DB.remove(admin_obj)
self.Logs.debug(f'UID ({admin_obj.uid}) has been deleted') self._ctx.Logs.debug(f'UID ({admin_obj.uid}) has been deleted')
return True return True
self.Logs.debug(f'The UID {uidornickname} was not deleted') self._ctx.Logs.debug(f'The UID {uidornickname} was not deleted')
return False return False
@@ -186,20 +181,20 @@ class Admin:
if fp is None: if fp is None:
return False return False
query = f"SELECT user, level, language FROM {self.Config.TABLE_ADMIN} WHERE fingerprint = :fp" query = f"SELECT user, level, language FROM {self._ctx.Config.TABLE_ADMIN} WHERE fingerprint = :fp"
data = {'fp': fp} data = {'fp': fp}
exe = await self.Base.db_execute_query(query, data) exe = await self._ctx.Base.db_execute_query(query, data)
result = exe.fetchone() result = exe.fetchone()
if result: if result:
account = result[0] account = result[0]
level = result[1] level = result[1]
language = result[2] language = result[2]
user_obj = self.User.get_user(uidornickname) user_obj = self._ctx.User.get_user(uidornickname)
if user_obj: if user_obj:
admin_obj = self.Definition.MAdmin(**user_obj.to_dict(), account=account, level=level, language=language) admin_obj = self._ctx.Definition.MAdmin(**user_obj.to_dict(), account=account, level=level, language=language)
if self.insert(admin_obj): if self.insert(admin_obj):
self.Setting.current_admin = admin_obj self._ctx.Settings.current_admin = admin_obj
self.Logs.debug(f"[Fingerprint login] {user_obj.nickname} ({admin_obj.account}) has been logged in successfully!") self._ctx.Logs.debug(f"[Fingerprint login] {user_obj.nickname} ({admin_obj.account}) has been logged in successfully!")
return True return True
return False return False
@@ -215,8 +210,8 @@ class Admin:
""" """
mes_donnees = {'admin': admin_nickname} mes_donnees = {'admin': admin_nickname}
query_search_user = f"SELECT id FROM {self.Config.TABLE_ADMIN} WHERE user = :admin" query_search_user = f"SELECT id FROM {self._ctx.Config.TABLE_ADMIN} WHERE user = :admin"
r = await self.Base.db_execute_query(query_search_user, mes_donnees) r = await self._ctx.Base.db_execute_query(query_search_user, mes_donnees)
exist_user = r.fetchone() exist_user = r.fetchone()
if exist_user: if exist_user:
return True return True

View File

@@ -15,8 +15,7 @@ class Client:
Args: Args:
loader (Loader): The Loader instance. loader (Loader): The Loader instance.
""" """
self.Logs = loader.Logs self._ctx = loader
self.Base = loader.Base
def insert(self, new_client: 'MClient') -> bool: def insert(self, new_client: 'MClient') -> bool:
"""Insert a new User object """Insert a new User object
@@ -28,7 +27,7 @@ class Client:
bool: True if inserted bool: True if inserted
""" """
client_obj = self.get_Client(new_client.uid) client_obj = self.get_client(new_client.uid)
if not client_obj is None: if not client_obj is None:
# User already created return False # User already created return False
@@ -48,7 +47,7 @@ class Client:
Returns: Returns:
bool: True if updated bool: True if updated
""" """
user_obj = self.get_Client(uidornickname=uid) user_obj = self.get_client(uidornickname=uid)
if user_obj is None: if user_obj is None:
return False return False
@@ -68,7 +67,7 @@ class Client:
bool: True if user mode has been updaed bool: True if user mode has been updaed
""" """
response = True response = True
user_obj = self.get_Client(uidornickname=uidornickname) user_obj = self.get_client(uidornickname=uidornickname)
if user_obj is None: if user_obj is None:
return False return False
@@ -93,7 +92,7 @@ class Client:
return False return False
liste_umodes = list(umodes) liste_umodes = list(umodes)
final_umodes_liste = [x for x in self.Base.Settings.PROTOCTL_USER_MODES if x in liste_umodes] final_umodes_liste = [x for x in self._ctx.Base.Settings.PROTOCTL_USER_MODES if x in liste_umodes]
final_umodes = ''.join(final_umodes_liste) final_umodes = ''.join(final_umodes_liste)
user_obj.umodes = f"+{final_umodes}" user_obj.umodes = f"+{final_umodes}"
@@ -110,7 +109,7 @@ class Client:
bool: True if deleted bool: True if deleted
""" """
user_obj = self.get_Client(uidornickname=uid) user_obj = self.get_client(uidornickname=uid)
if user_obj is None: if user_obj is None:
return False return False
@@ -119,7 +118,7 @@ class Client:
return True return True
def get_Client(self, uidornickname: str) -> Optional['MClient']: def get_client(self, uidornickname: str) -> Optional['MClient']:
"""Get The Client Object model """Get The Client Object model
Args: Args:
@@ -146,7 +145,7 @@ class Client:
str|None: Return the UID str|None: Return the UID
""" """
client_obj = self.get_Client(uidornickname=uidornickname) client_obj = self.get_client(uidornickname=uidornickname)
if client_obj is None: if client_obj is None:
return None return None
@@ -162,29 +161,13 @@ class Client:
Returns: Returns:
str|None: the nickname str|None: the nickname
""" """
client_obj = self.get_Client(uidornickname=uidornickname) client_obj = self.get_client(uidornickname=uidornickname)
if client_obj is None: if client_obj is None:
return None return None
return client_obj.nickname return client_obj.nickname
def get_client_asdict(self, uidornickname: str) -> Optional[dict[str, Any]]:
"""Transform User Object to a dictionary
Args:
uidornickname (str): The UID or The nickname
Returns:
Union[dict[str, any], None]: User Object as a dictionary or None
"""
client_obj = self.get_Client(uidornickname=uidornickname)
if client_obj is None:
return None
return client_obj.to_dict()
def is_exist(self, uidornickname: str) -> bool: def is_exist(self, uidornickname: str) -> bool:
"""Check if the UID or the nickname exist in the USER DB """Check if the UID or the nickname exist in the USER DB
@@ -194,7 +177,7 @@ class Client:
Returns: Returns:
bool: True if exist bool: True if exist
""" """
user_obj = self.get_Client(uidornickname=uidornickname) user_obj = self.get_client(uidornickname=uidornickname)
if user_obj is None: if user_obj is None:
return False return False
@@ -211,15 +194,15 @@ class Client:
bool: True if exist bool: True if exist
""" """
table_client = self.Base.Config.TABLE_CLIENT table_client = self._ctx.Base.Config.TABLE_CLIENT
account_to_check = {'account': account.lower()} account_to_check = {'account': account.lower()}
account_to_check_query = await self.Base.db_execute_query(f""" account_to_check_query = await self._ctx.Base.db_execute_query(f"""
SELECT id FROM {table_client} WHERE LOWER(account) = :account SELECT id FROM {table_client} WHERE LOWER(account) = :account
""", account_to_check) """, account_to_check)
account_to_check_result = account_to_check_query.fetchone() account_to_check_result = account_to_check_query.fetchone()
if account_to_check_result: if account_to_check_result:
self.Logs.error(f"Account ({account}) already exist") self._ctx.Logs.error(f"Account ({account}) already exist")
return True return True
return False return False

View File

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

View File

@@ -1,3 +1,4 @@
import asyncio
import importlib import importlib
import sys import sys
import time import time
@@ -26,7 +27,6 @@ REHASH_MODULES = [
'core.classes.protocols.inspircd' 'core.classes.protocols.inspircd'
] ]
async def restart_service(uplink: 'Loader', reason: str = "Restarting with no reason!") -> None: async def restart_service(uplink: 'Loader', reason: str = "Restarting with no reason!") -> None:
""" """
@@ -69,7 +69,7 @@ async def rehash_service(uplink: 'Loader', nickname: str) -> None:
need_a_restart = ["SERVEUR_ID"] need_a_restart = ["SERVEUR_ID"]
uplink.Settings.set_cache('db_commands', uplink.Commands.DB_COMMANDS) uplink.Settings.set_cache('db_commands', uplink.Commands.DB_COMMANDS)
await uplink.RpcServer.stop_server() await uplink.RpcServer.stop_rpc_server()
restart_flag = False restart_flag = False
config_model_bakcup = uplink.Config config_model_bakcup = uplink.Config
@@ -122,10 +122,68 @@ async def rehash_service(uplink: 'Loader', nickname: str) -> None:
uplink.Irc.Protocol.register_command() uplink.Irc.Protocol.register_command()
uplink.RpcServer = uplink.RpcServerModule.JSonRpcServer(uplink) uplink.RpcServer = uplink.RpcServerModule.JSonRpcServer(uplink)
uplink.Base.create_asynctask(uplink.RpcServer.start_server()) uplink.Base.create_asynctask(uplink.RpcServer.start_rpc_server())
# Reload Service modules # Reload Service modules
for module in uplink.ModuleUtils.model_get_loaded_modules().copy(): for module in uplink.ModuleUtils.model_get_loaded_modules().copy():
await uplink.ModuleUtils.reload_one_module(module.module_name, nickname) await uplink.ModuleUtils.reload_one_module(module.module_name, nickname)
return None return None
async def shutdown(uplink: 'Loader') -> None:
"""Methode qui va préparer l'arrêt complêt du service
"""
# Stop RpcServer if running
await uplink.RpcServer.stop_rpc_server()
# unload modules.
uplink.Logs.debug(f"=======> Unloading all modules!")
for module in uplink.ModuleUtils.model_get_loaded_modules().copy():
await uplink.ModuleUtils.unload_one_module(module.module_name)
# Nettoyage des timers
uplink.Logs.debug(f"=======> Closing all timers!")
for timer in uplink.Base.running_timers:
while timer.is_alive():
uplink.Logs.debug(f"> waiting for {timer.name} to close")
timer.cancel()
await asyncio.sleep(0.2)
uplink.Logs.debug(f"> Cancelling {timer.name} {timer.native_id}")
uplink.Logs.debug(f"=======> Closing all Threads!")
for thread in uplink.Base.running_threads:
if thread.name == 'heartbeat' and thread.is_alive():
uplink.Base.execute_periodic_action()
uplink.Logs.debug(f"> Running the last periodic action")
uplink.Logs.debug(f"> Cancelling {thread.name} {thread.native_id}")
uplink.Logs.debug(f"=======> Closing all IO Threads!")
[th.thread_event.clear() for th in uplink.Base.running_iothreads]
uplink.Logs.debug(f"=======> Closing all IO TASKS!")
try:
await asyncio.wait_for(asyncio.gather(*uplink.Base.running_iotasks), timeout=5)
except asyncio.exceptions.TimeoutError as te:
uplink.Logs.debug(f"Asyncio Timeout reached! {te}")
for task in uplink.Base.running_iotasks:
task.cancel()
except asyncio.exceptions.CancelledError as cerr:
uplink.Logs.debug(f"Asyncio CancelledError reached! {cerr}")
uplink.Logs.debug(f"=======> Closing all Sockets!")
for soc in uplink.Base.running_sockets:
soc.close()
while soc.fileno() != -1:
soc.close()
uplink.Base.running_sockets.remove(soc)
uplink.Logs.debug(f"> Socket ==> closed {str(soc.fileno())}")
uplink.Base.running_timers.clear()
uplink.Base.running_threads.clear()
uplink.Base.running_iotasks.clear()
uplink.Base.running_iothreads.clear()
uplink.Base.running_sockets.clear()
uplink.Base.db_close()
return None

View File

@@ -14,9 +14,7 @@ class Reputation:
Args: Args:
loader (Loader): The Loader instance. loader (Loader): The Loader instance.
""" """
self._ctx = loader
self.Logs = loader.Logs
self.MReputation: Optional[MReputation] = None
def insert(self, new_reputation_user: MReputation) -> bool: def insert(self, new_reputation_user: MReputation) -> bool:
"""Insert a new Reputation User object """Insert a new Reputation User object
@@ -34,16 +32,16 @@ class Reputation:
if record.uid == new_reputation_user.uid: if record.uid == new_reputation_user.uid:
# If the user exist then return False and do not go further # If the user exist then return False and do not go further
exist = True exist = True
self.Logs.debug(f'{record.uid} already exist') self._ctx.Logs.debug(f'{record.uid} already exist')
return result return result
if not exist: if not exist:
self.UID_REPUTATION_DB.append(new_reputation_user) self.UID_REPUTATION_DB.append(new_reputation_user)
result = True result = True
self.Logs.debug(f'New Reputation User Captured: ({new_reputation_user})') self._ctx.Logs.debug(f'New Reputation User Captured: ({new_reputation_user})')
if not result: if not result:
self.Logs.critical(f'The Reputation User Object was not inserted {new_reputation_user}') self._ctx.Logs.critical(f'The Reputation User Object was not inserted {new_reputation_user}')
return result return result
@@ -86,11 +84,11 @@ class Reputation:
# If the user exist then remove and return True and do not go further # If the user exist then remove and return True and do not go further
self.UID_REPUTATION_DB.remove(record) self.UID_REPUTATION_DB.remove(record)
result = True 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 return result
if not 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 return result

View File

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

View File

@@ -17,11 +17,11 @@ if TYPE_CHECKING:
class JSonRpcServer: class JSonRpcServer:
def __init__(self, context: 'Loader', *, hostname: str = 'localhost', port: int = 5000): def __init__(self, context: 'Loader'):
self._ctx = context self._ctx = context
self.live: bool = False self.live: bool = False
self.host = hostname self.host = context.Config.RPC_HOST
self.port = port self.port = context.Config.RPC_PORT
self.routes: list[Route] = [] self.routes: list[Route] = []
self.server: Optional[uvicorn.Server] = None self.server: Optional[uvicorn.Server] = None
@@ -34,12 +34,12 @@ class JSonRpcServer:
'command.get.by.module': RPCCommand(context).command_get_by_module 'command.get.by.module': RPCCommand(context).command_get_by_module
} }
async def start_server(self): async def start_rpc_server(self):
if not self.live: if not self.live:
self.routes = [Route('/api', self.request_handler, methods=['POST'])] self.routes = [Route('/api', self.request_handler, methods=['POST'])]
self.app_jsonrpc = Starlette(debug=False, routes=self.routes) 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) 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.server = uvicorn.Server(config)
self.live = True self.live = True
await self._ctx.Irc.Protocol.send_priv_msg( await self._ctx.Irc.Protocol.send_priv_msg(
@@ -52,7 +52,7 @@ class JSonRpcServer:
else: else:
self._ctx.Logs.debug("Server already running") self._ctx.Logs.debug("Server already running")
async def stop_server(self): async def stop_rpc_server(self):
if self.server: if self.server:
self.server.should_exit = True self.server.should_exit = True
@@ -77,10 +77,10 @@ class JSonRpcServer:
response_data = { response_data = {
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": method,
"id": request_data.get('id', 123) "id": request_data.get('id', 123)
} }
response_data['method'] = method
rip = request.client.host rip = request.client.host
rport = request.client.port rport = request.client.port
http_code = http_status_code.HTTP_200_OK http_code = http_status_code.HTTP_200_OK
@@ -89,6 +89,7 @@ class JSonRpcServer:
r: JSONResponse = self.methods[method](**params) r: JSONResponse = self.methods[method](**params)
resp = json.loads(r.body) resp = json.loads(r.body)
resp['id'] = request_data.get('id', 123) resp['id'] = request_data.get('id', 123)
resp['method'] = method
return JSONResponse(resp, r.status_code) return JSONResponse(resp, r.status_code)
response_data['error'] = rpcerr.create_error_response(rpcerr.JSONRPCErrorCode.METHOD_NOT_FOUND) response_data['error'] = rpcerr.create_error_response(rpcerr.JSONRPCErrorCode.METHOD_NOT_FOUND)

View File

@@ -1,5 +1,4 @@
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from starlette.responses import JSONResponse from starlette.responses import JSONResponse
from core.classes.interfaces.irpc_endpoint import IRPC from core.classes.interfaces.irpc_endpoint import IRPC
from core.classes.modules.rpc.rpc_errors import JSONRPCErrorCode from core.classes.modules.rpc.rpc_errors import JSONRPCErrorCode

View File

@@ -42,4 +42,4 @@ class RPCUser(IRPC):
return JSONResponse(self.response_model) return JSONResponse(self.response_model)
self.response_model['result'] = 'User not found!' self.response_model['result'] = 'User not found!'
return JSONResponse(self.response_model, self.http_status_code.HTTP_204_NO_CONTENT) return JSONResponse(self.response_model, self.http_status_code.HTTP_404_NOT_FOUND)

View File

@@ -6,7 +6,7 @@ from threading import Timer, Thread, RLock
from asyncio.locks import Lock from asyncio.locks import Lock
from socket import socket from socket import socket
from typing import Any, Optional, TYPE_CHECKING from typing import Any, Optional, TYPE_CHECKING
from core.definition import MSModule, MAdmin from core.definition import MSModule, MAdmin, MThread
if TYPE_CHECKING: if TYPE_CHECKING:
from core.classes.modules.user import User from core.classes.modules.user import User
@@ -19,10 +19,12 @@ class Settings:
RUNNING_TIMERS: list[Timer] = [] RUNNING_TIMERS: list[Timer] = []
RUNNING_THREADS: list[Thread] = [] RUNNING_THREADS: list[Thread] = []
RUNNING_ASYNCTASKS: list[asyncio.Task] = []
RUNNING_SOCKETS: list[socket] = [] RUNNING_SOCKETS: list[socket] = []
RUNNING_ASYNC_TASKS: list[asyncio.Task] = []
RUNNING_ASYNC_THREADS: list[MThread] = []
PERIODIC_FUNC: dict[str, Any] = {} PERIODIC_FUNC: dict[str, Any] = {}
LOCK: RLock = RLock()
THLOCK: RLock = RLock()
AILOCK: Lock = Lock() AILOCK: Lock = Lock()
CONSOLE: bool = False CONSOLE: bool = False

View File

@@ -93,4 +93,4 @@ class Translation:
except Exception as err: except Exception as err:
self.Logs.error(f'General Error: {err}') self.Logs.error(f'General Error: {err}')
return {} return dict()

View File

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

View File

@@ -4,7 +4,7 @@ from datetime import datetime
from typing import TYPE_CHECKING, Any, Optional from typing import TYPE_CHECKING, Any, Optional
from ssl import SSLEOFError, SSLError from ssl import SSLEOFError, SSLError
from core.classes.interfaces.iprotocol import IProtocol from core.classes.interfaces.iprotocol import IProtocol
from core.utils import tr from core.utils import is_coroutinefunction, tr
if TYPE_CHECKING: if TYPE_CHECKING:
from core.definition import MClient, MSasl, MUser, MChannel from core.definition import MClient, MSasl, MUser, MChannel
@@ -258,6 +258,7 @@ class Unrealircd6(IProtocol):
async def send_set_mode(self, modes: str, *, nickname: Optional[str] = None, channel_name: Optional[str] = None, params: Optional[str] = None) -> None: 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 """Set a mode to channel or to a nickname or for a user in a channel
This method will always send as the command as Defender's nickname (service_id)
Args: Args:
modes (str): The selected mode modes (str): The selected mode
@@ -478,7 +479,7 @@ class Unrealircd6(IProtocol):
c_uid = client_obj.uid c_uid = client_obj.uid
c_nickname = client_obj.nickname c_nickname = client_obj.nickname
await self.send2socket(f":{self._ctx.Config.SERVEUR_LINK} SVSLOGIN {self._ctx.Settings.MAIN_SERVER_HOSTNAME} {c_uid} 0") await self.send2socket(f":{self._ctx.Config.SERVEUR_LINK} SVSLOGIN {self._ctx.Settings.MAIN_SERVER_HOSTNAME} {c_uid} 0")
self.send_svs2mode(c_nickname, '-r') await self.send_svs2mode(c_nickname, '-r')
except Exception as err: except Exception as err:
self._ctx.Logs.error(f'General Error: {err}') self._ctx.Logs.error(f'General Error: {err}')
@@ -1020,7 +1021,7 @@ class Unrealircd6(IProtocol):
# Send EOF to other modules # Send EOF to other modules
for module in self._ctx.ModuleUtils.model_get_loaded_modules().copy(): for module in self._ctx.ModuleUtils.model_get_loaded_modules().copy():
module.class_instance.cmd(server_msg_copy) await module.class_instance.cmd(server_msg_copy) if self._ctx.Utils.is_coroutinefunction(module.class_instance.cmd) else module.class_instance.cmd(server_msg_copy)
# Join saved channels & load existing modules # Join saved channels & load existing modules
await self._ctx.Channel.db_join_saved_channels() await self._ctx.Channel.db_join_saved_channels()

View File

@@ -1,3 +1,7 @@
import asyncio
import concurrent
import concurrent.futures
import threading
from datetime import datetime from datetime import datetime
from json import dumps from json import dumps
from dataclasses import dataclass, field, asdict, fields, replace from dataclasses import dataclass, field, asdict, fields, replace
@@ -210,6 +214,12 @@ class MConfig(MainModel):
PASSWORD: str = "password" PASSWORD: str = "password"
"""The password of the admin of the service""" """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) RPC_USERS: list[dict] = field(default_factory=list)
"""The Defender rpc users""" """The Defender rpc users"""
@@ -344,6 +354,15 @@ class MConfig(MainModel):
self.SERVEUR_CHARSET: list = ["utf-8", "iso-8859-1"] self.SERVEUR_CHARSET: list = ["utf-8", "iso-8859-1"]
"""0: utf-8 | 1: iso-8859-1""" """0: utf-8 | 1: iso-8859-1"""
@dataclass
class MThread(MainModel):
name: str
thread_id: Optional[int]
thread_event: Optional[threading.Event]
thread_obj: threading.Thread
executor: concurrent.futures.ThreadPoolExecutor
future: asyncio.Future
@dataclass @dataclass
class MCommand(MainModel): class MCommand(MainModel):
module_name: str = None module_name: str = None

View File

@@ -1,8 +1,7 @@
import asyncio import asyncio
import socket
import re import re
import time import ssl
from ssl import SSLSocket import threading
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import TYPE_CHECKING, Any, Optional, Union from typing import TYPE_CHECKING, Any, Optional, Union
from core.classes.modules import rehash from core.classes.modules import rehash
@@ -30,6 +29,10 @@ class Irc:
# Load Context class (Loader) # Load Context class (Loader)
self.ctx = loader self.ctx = loader
# Define Reader and Writer
self.reader: Optional[asyncio.StreamReader] = None
self.writer: Optional[asyncio.StreamWriter] = None
# Date et heure de la premiere connexion de Defender # Date et heure de la premiere connexion de Defender
self.defender_connexion_datetime = self.ctx.Config.DEFENDER_CONNEXION_DATETIME self.defender_connexion_datetime = self.ctx.Config.DEFENDER_CONNEXION_DATETIME
@@ -58,50 +61,56 @@ class Irc:
# Load Commands Utils # Load Commands Utils
# self.Commands = self.Loader.Commands # self.Commands = self.Loader.Commands
"""Command utils""" """Command utils"""
self.ctx.Commands.build_command(0, 'core', 'help', 'This provide the help')
self.ctx.Commands.build_command(0, 'core', 'auth', 'Login to the IRC Service')
self.ctx.Commands.build_command(0, 'core', 'copyright', 'Give some information about the IRC Service')
self.ctx.Commands.build_command(0, 'core', 'uptime', 'Give you since when the service is connected')
self.ctx.Commands.build_command(0, 'core', 'firstauth', 'First authentication of the Service')
self.ctx.Commands.build_command(0, 'core', 'register', f'Register your nickname /msg {self.ctx.Config.SERVICE_NICKNAME} REGISTER <password> <email>')
self.ctx.Commands.build_command(0, 'core', 'identify', f'Identify yourself with your password /msg {self.ctx.Config.SERVICE_NICKNAME} IDENTIFY <account> <password>')
self.ctx.Commands.build_command(0, 'core', 'logout', 'Reverse the effect of the identify command')
self.ctx.Commands.build_command(1, 'core', 'load', 'Load an existing module')
self.ctx.Commands.build_command(1, 'core', 'unload', 'Unload a module')
self.ctx.Commands.build_command(1, 'core', 'reload', 'Reload a module')
self.ctx.Commands.build_command(1, 'core', 'deauth', 'Deauth from the irc service')
self.ctx.Commands.build_command(1, 'core', 'checkversion', 'Check the version of the irc service')
self.ctx.Commands.build_command(2, 'core', 'show_modules', 'Display a list of loaded modules')
self.ctx.Commands.build_command(2, 'core', 'show_timers', 'Display active timers')
self.ctx.Commands.build_command(2, 'core', 'show_threads', 'Display active threads in the system')
self.ctx.Commands.build_command(2, 'core', 'show_asyncio', 'Display active asyncio')
self.ctx.Commands.build_command(2, 'core', 'show_channels', 'Display a list of active channels')
self.ctx.Commands.build_command(2, 'core', 'show_users', 'Display a list of connected users')
self.ctx.Commands.build_command(2, 'core', 'show_clients', 'Display a list of connected clients')
self.ctx.Commands.build_command(2, 'core', 'show_admins', 'Display a list of administrators')
self.ctx.Commands.build_command(2, 'core', 'show_configuration', 'Display the current configuration settings')
self.ctx.Commands.build_command(2, 'core', 'show_cache', 'Display the current cache')
self.ctx.Commands.build_command(2, 'core', 'clear_cache', 'Clear the cache!')
self.ctx.Commands.build_command(3, 'core', 'quit', 'Disconnect the bot or user from the server.')
self.ctx.Commands.build_command(3, 'core', 'restart', 'Restart the bot or service.')
self.ctx.Commands.build_command(3, 'core', 'addaccess', 'Add a user or entity to an access list with specific permissions.')
self.ctx.Commands.build_command(3, 'core', 'editaccess', 'Modify permissions for an existing user or entity in the access list.')
self.ctx.Commands.build_command(3, 'core', 'delaccess', 'Remove a user or entity from the access list.')
self.ctx.Commands.build_command(3, 'core', 'cert', 'Append your new fingerprint to your account!')
self.ctx.Commands.build_command(4, 'core', 'rehash', 'Reload the configuration file without restarting')
self.ctx.Commands.build_command(4, 'core', 'raw', 'Send a raw command directly to the IRC server')
self.ctx.Commands.build_command(4, 'core', 'print_vars', 'Print users in a file.')
self.ctx.Commands.build_command(4, 'core', 'start_rpc', 'Start defender jsonrpc server')
self.ctx.Commands.build_command(4, 'core', 'stop_rpc', 'Stop defender jsonrpc server')
self.build_command(0, 'core', 'help', 'This provide the help') ##############################################
self.build_command(0, 'core', 'auth', 'Login to the IRC Service') # CONNEXION IRC #
self.build_command(0, 'core', 'copyright', 'Give some information about the IRC Service') ##############################################
self.build_command(0, 'core', 'uptime', 'Give you since when the service is connected')
self.build_command(0, 'core', 'firstauth', 'First authentication of the Service')
self.build_command(0, 'core', 'register', f'Register your nickname /msg {self.ctx.Config.SERVICE_NICKNAME} REGISTER <password> <email>')
self.build_command(0, 'core', 'identify', f'Identify yourself with your password /msg {self.ctx.Config.SERVICE_NICKNAME} IDENTIFY <account> <password>')
self.build_command(0, 'core', 'logout', 'Reverse the effect of the identify command')
self.build_command(1, 'core', 'load', 'Load an existing module')
self.build_command(1, 'core', 'unload', 'Unload a module')
self.build_command(1, 'core', 'reload', 'Reload a module')
self.build_command(1, 'core', 'deauth', 'Deauth from the irc service')
self.build_command(1, 'core', 'checkversion', 'Check the version of the irc service')
self.build_command(2, 'core', 'show_modules', 'Display a list of loaded modules')
self.build_command(2, 'core', 'show_timers', 'Display active timers')
self.build_command(2, 'core', 'show_threads', 'Display active threads in the system')
self.build_command(2, 'core', 'show_asyncio', 'Display active asyncio')
self.build_command(2, 'core', 'show_channels', 'Display a list of active channels')
self.build_command(2, 'core', 'show_users', 'Display a list of connected users')
self.build_command(2, 'core', 'show_clients', 'Display a list of connected clients')
self.build_command(2, 'core', 'show_admins', 'Display a list of administrators')
self.build_command(2, 'core', 'show_configuration', 'Display the current configuration settings')
self.build_command(2, 'core', 'show_cache', 'Display the current cache')
self.build_command(2, 'core', 'clear_cache', 'Clear the cache!')
self.build_command(3, 'core', 'quit', 'Disconnect the bot or user from the server.')
self.build_command(3, 'core', 'restart', 'Restart the bot or service.')
self.build_command(3, 'core', 'addaccess', 'Add a user or entity to an access list with specific permissions.')
self.build_command(3, 'core', 'editaccess', 'Modify permissions for an existing user or entity in the access list.')
self.build_command(3, 'core', 'delaccess', 'Remove a user or entity from the access list.')
self.build_command(3, 'core', 'cert', 'Append your new fingerprint to your account!')
self.build_command(4, 'core', 'rehash', 'Reload the configuration file without restarting')
self.build_command(4, 'core', 'raw', 'Send a raw command directly to the IRC server')
self.build_command(4, 'core', 'print_vars', 'Print users in a file.')
self.build_command(4, 'core', 'start_rpc', 'Start defender jsonrpc server')
self.build_command(4, 'core', 'stop_rpc', 'Stop defender jsonrpc server')
# Define the IrcSocket object async def run(self):
self.IrcSocket: Optional[Union[socket.socket, SSLSocket]] = None try:
await self.connect()
self.reader: Optional[asyncio.StreamReader] = None await self.listen()
self.writer: Optional[asyncio.StreamWriter] = None except asyncio.exceptions.IncompleteReadError as ie:
# When IRCd server is down
self.ctx.Base.create_asynctask(self.heartbeat(self.beat)) # asyncio.exceptions.IncompleteReadError: 0 bytes read on a total of undefined expected bytes
self.ctx.Logs.critical(f"The IRCd server is no more connected! {ie}")
except asyncio.exceptions.CancelledError as cerr:
self.ctx.Logs.debug(f"Asyncio CancelledError reached! {cerr}")
async def connect(self): async def connect(self):
@@ -116,24 +125,36 @@ class Irc:
await self.Protocol.send_link() await self.Protocol.send_link()
async def listen(self): async def listen(self):
self.ctx.Base.create_asynctask(
self.ctx.Base.create_thread_io(
self.ctx.Utils.heartbeat,
self.ctx, self.beat,
run_once=True, thread_flag=True
)
)
while self.signal: while self.signal:
data = await self.reader.readuntil(b'\r\n') data = await self.reader.readuntil(b'\r\n')
await self.send_response(data.splitlines()) await self.send_response(data.splitlines())
async def run(self): async def send_response(self, responses:list[bytes]) -> None:
try: try:
await self.connect() for data in responses:
await self.listen() response = data.decode(self.CHARSET[0]).split()
except asyncio.exceptions.IncompleteReadError as ie: await self.cmd(response)
# When IRCd server is down
# asyncio.exceptions.IncompleteReadError: 0 bytes read on a total of undefined expected bytes
self.ctx.Logs.critical(f"The IRCd server is no more connected! {ie}")
except asyncio.exceptions.CancelledError as cerr:
self.ctx.Logs.debug(f"Asyncio CancelledError reached! {cerr}")
############################################## except (UnicodeEncodeError, UnicodeDecodeError) as ue:
# CONNEXION IRC # for data in responses:
############################################## response = data.decode(self.CHARSET[1], 'replace').split()
await self.cmd(response)
self.ctx.Logs.error(f'UnicodeEncodeError: {ue}')
self.ctx.Logs.error(responses)
except AssertionError as ae:
self.ctx.Logs.error(f"Assertion error : {ae}")
# --------------------------------------------
# FIN CONNEXION IRC #
# --------------------------------------------
def init_service_user(self) -> None: def init_service_user(self) -> None:
@@ -157,51 +178,6 @@ class Irc:
chan = chan_name[0] chan = chan_name[0]
await self.Protocol.send_sjoin(channel=chan) await self.Protocol.send_sjoin(channel=chan)
async def send_response(self, responses:list[bytes]) -> None:
try:
for data in responses:
response = data.decode(self.CHARSET[0]).split()
await self.cmd(response)
except UnicodeEncodeError as ue:
for data in responses:
response = data.decode(self.CHARSET[1],'replace').split()
await self.cmd(response)
self.ctx.Logs.error(f'UnicodeEncodeError: {ue}')
self.ctx.Logs.error(response)
except UnicodeDecodeError as ud:
for data in responses:
response = data.decode(self.CHARSET[1],'replace').split()
await self.cmd(response)
self.ctx.Logs.error(f'UnicodeDecodeError: {ud}')
self.ctx.Logs.error(response)
except AssertionError as ae:
self.ctx.Logs.error(f"Assertion error : {ae}")
def unload(self) -> None:
# This is only to reference the method
return None
# --------------------------------------------
# FIN CONNEXION IRC #
# --------------------------------------------
def build_command(self, level: int, module_name: str, command_name: str, command_description: str) -> None:
"""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.
self.ctx.Commands.build(self.ctx.Definition.MCommand(module_name, command_name, command_description, level))
return None
async def generate_help_menu(self, nickname: str, module: Optional[str] = None) -> None: async def generate_help_menu(self, nickname: str, module: Optional[str] = None) -> None:
# Check if the nickname is an admin # Check if the nickname is an admin
@@ -289,17 +265,6 @@ class Irc:
return uptime return uptime
async def heartbeat(self, 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 self.hb_active:
await asyncio.sleep(beat)
self.ctx.Base.execute_periodic_action()
def insert_db_admin(self, uid: str, account: str, level: int, language: str) -> None: def insert_db_admin(self, uid: str, account: str, level: int, language: str) -> None:
user_obj = self.ctx.User.get_user(uid) user_obj = self.ctx.User.get_user(uid)
@@ -382,8 +347,13 @@ class Irc:
async def thread_check_for_new_version(self, fromuser: str) -> None: async def thread_check_for_new_version(self, fromuser: str) -> None:
dnickname = self.ctx.Config.SERVICE_NICKNAME dnickname = self.ctx.Config.SERVICE_NICKNAME
response = self.ctx.Base.create_asynctask(
self.ctx.Base.create_thread_io(
self.ctx.Base.check_for_new_version, True
)
)
if self.ctx.Base.check_for_new_version(True): if response:
await self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" New Version available : {self.ctx.Config.CURRENT_VERSION} >>> {self.ctx.Config.LATEST_VERSION}") await self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" New Version available : {self.ctx.Config.CURRENT_VERSION} >>> {self.ctx.Config.LATEST_VERSION}")
await self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=" Please run (git pull origin main) in the current folder") await self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=" Please run (git pull origin main) in the current folder")
else: else:
@@ -413,12 +383,6 @@ class Irc:
for module in modules: for module in modules:
await module.class_instance.cmd(original_response) if self.ctx.Utils.is_coroutinefunction(module.class_instance.cmd) else module.class_instance.cmd(original_response) await module.class_instance.cmd(original_response) if self.ctx.Utils.is_coroutinefunction(module.class_instance.cmd) else module.class_instance.cmd(original_response)
# if len(original_response) > 2:
# if original_response[2] != 'UID':
# # Envoyer la commande aux classes dynamiquement chargées
# for module in self.ctx.ModuleUtils.model_get_loaded_modules().copy():
# module.class_instance.cmd(original_response)
except IndexError as ie: except IndexError as ie:
self.ctx.Logs.error(f"IndexError: {ie}") self.ctx.Logs.error(f"IndexError: {ie}")
except Exception as err: except Exception as err:
@@ -441,7 +405,7 @@ class Irc:
if u is None: if u is None:
return None return None
c = self.ctx.Client.get_Client(u.uid) c = self.ctx.Client.get_client(u.uid)
"""The Client Object""" """The Client Object"""
fromuser = u.nickname fromuser = u.nickname
@@ -627,7 +591,7 @@ class Irc:
level = self.ctx.Base.int_if_possible(cmd[2]) level = self.ctx.Base.int_if_possible(cmd[2])
password = str(cmd[3]) password = str(cmd[3])
self.create_defender_user(fromuser, new_admin, level, password) await self.create_defender_user(fromuser, new_admin, level, password)
return None return None
except IndexError as ie: except IndexError as ie:
@@ -982,11 +946,10 @@ class Irc:
try: try:
final_reason = ' '.join(cmd[1:]) final_reason = ' '.join(cmd[1:])
self.hb_active = False self.hb_active = False
await self.ctx.Base.shutdown() await rehash.shutdown(self.ctx)
self.ctx.Base.execute_periodic_action() self.ctx.Base.execute_periodic_action()
for chan_name in self.ctx.Channel.UID_CHANNEL_DB: for chan_name in self.ctx.Channel.UID_CHANNEL_DB:
# self.Protocol.send_mode_chan(chan_name.name, '-l')
await self.Protocol.send_set_mode('-l', channel_name=chan_name.name) await self.Protocol.send_set_mode('-l', channel_name=chan_name.name)
for client in self.ctx.Client.CLIENT_DB: for client in self.ctx.Client.CLIENT_DB:
@@ -1002,7 +965,6 @@ class Irc:
self.ctx.Logs.info(f'Arrêt du server {dnickname}') self.ctx.Logs.info(f'Arrêt du server {dnickname}')
self.ctx.Config.DEFENDER_RESTART = 0 self.ctx.Config.DEFENDER_RESTART = 0
await self.writer.drain()
self.writer.close() self.writer.close()
await self.writer.wait_closed() await self.writer.wait_closed()
@@ -1011,6 +973,8 @@ class Irc:
except ConnectionResetError: except ConnectionResetError:
if self.writer.is_closing(): if self.writer.is_closing():
self.ctx.Logs.debug(f"Defender stopped properly!") self.ctx.Logs.debug(f"Defender stopped properly!")
except ssl.SSLError as serr:
self.ctx.Logs.error(f"Defender has ended with an SSL Error! - {serr}")
case 'restart': case 'restart':
final_reason = ' '.join(cmd[1:]) final_reason = ' '.join(cmd[1:])
@@ -1080,6 +1044,13 @@ class Irc:
msg=f">> {thread.name} ({thread.is_alive()})" msg=f">> {thread.name} ({thread.is_alive()})"
) )
for thread in threading.enumerate():
await self.Protocol.send_notice(
nick_from=dnickname,
nick_to=fromuser,
msg=f">> Thread name: {thread.name} - Is alive: {thread.is_alive()} - Daemon: {thread.daemon}"
)
return None return None
case 'show_asyncio': case 'show_asyncio':
@@ -1209,10 +1180,10 @@ class Irc:
return None return None
case 'start_rpc': case 'start_rpc':
self.ctx.Base.create_asynctask(self.ctx.RpcServer.start_server()) self.ctx.Base.create_asynctask(self.ctx.RpcServer.start_rpc_server())
case 'stop_rpc': case 'stop_rpc':
self.ctx.Base.create_asynctask(self.ctx.RpcServer.stop_server()) self.ctx.Base.create_asynctask(self.ctx.RpcServer.stop_rpc_server())
case _: case _:
pass pass

View File

@@ -81,7 +81,7 @@ class Loader:
self.PFactory: factory.ProtocolFactorty = factory.ProtocolFactorty(self) self.PFactory: factory.ProtocolFactorty = factory.ProtocolFactorty(self)
self.RpcServer: rpc_mod.JSonRpcServer = rpc_mod.JSonRpcServer(self, hostname='0.0.0.0') self.RpcServer: rpc_mod.JSonRpcServer = rpc_mod.JSonRpcServer(self)
self.Logs.debug(self.Utils.tr("Loader %s success", __name__)) self.Logs.debug(self.Utils.tr("Loader %s success", __name__))

View File

@@ -3,20 +3,20 @@ Main utils library.
""" """
import gc import gc
import ssl import ssl
import socket
import sys
from pathlib import Path from pathlib import Path
from re import match, sub from re import match, sub
import threading
from typing import Literal, Optional, Any, TYPE_CHECKING from typing import Literal, Optional, Any, TYPE_CHECKING
from datetime import datetime from datetime import datetime
from time import time from time import time, sleep
from random import choice from random import choice
from hashlib import md5, sha3_512 from hashlib import md5, sha3_512
from core.classes.modules.settings import global_settings from core.classes.modules.settings import global_settings
from asyncio import iscoroutinefunction from asyncio import iscoroutinefunction
if TYPE_CHECKING: if TYPE_CHECKING:
from core.irc import Irc from threading import Event
from core.loader import Loader
def tr(message: str, *args) -> str: def tr(message: str, *args) -> str:
"""Translation Engine system """Translation Engine system
@@ -115,39 +115,6 @@ def get_ssl_context() -> ssl.SSLContext:
ctx.verify_mode = ssl.CERT_NONE ctx.verify_mode = ssl.CERT_NONE
return ctx return ctx
def create_socket(uplink: 'Irc') -> None:
"""Create a socket to connect SSL or Normal connection
"""
try:
soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM or socket.SOCK_NONBLOCK)
connexion_information = (uplink.Config.SERVEUR_IP, uplink.Config.SERVEUR_PORT)
if uplink.Config.SERVEUR_SSL:
# Create SSL Context object
ssl_context = get_ssl_context()
ssl_connexion = ssl_context.wrap_socket(soc, server_hostname=uplink.Config.SERVEUR_HOSTNAME)
ssl_connexion.connect(connexion_information)
uplink.IrcSocket = ssl_connexion
uplink.Config.SSL_VERSION = uplink.IrcSocket.version()
uplink.Logs.info(f"-- Connected using SSL : Version = {uplink.Config.SSL_VERSION}")
else:
soc.connect(connexion_information)
uplink.IrcSocket = soc
uplink.Logs.info("-- Connected in a normal mode!")
return None
except (ssl.SSLEOFError, ssl.SSLError) as soe:
uplink.Logs.critical(f"[SSL ERROR]: {soe}")
except OSError as oe:
uplink.Logs.critical(f"[OS Error]: {oe}")
if 'connection refused' in str(oe).lower():
sys.exit(oe.__str__())
if oe.errno == 10053:
sys.exit(oe.__str__())
except AttributeError as ae:
uplink.Logs.critical(f"AttributeError: {ae}")
def run_python_garbage_collector() -> int: def run_python_garbage_collector() -> int:
"""Run Python garbage collector """Run Python garbage collector
@@ -167,6 +134,21 @@ def get_number_gc_objects(your_object_to_count: Optional[Any] = None) -> int:
return sum(1 for obj in gc.get_objects() if isinstance(obj, your_object_to_count)) 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: def generate_random_string(lenght: int) -> str:
"""Retourn une chaîne aléatoire en fonction de la longueur spécifiée. """Retourn une chaîne aléatoire en fonction de la longueur spécifiée.

View File

@@ -1,6 +1,5 @@
import asyncio import asyncio
from core import install from core import install
############################################# #############################################
# @Version : 6.4 # # @Version : 6.4 #
# Requierements : # # Requierements : #
@@ -19,4 +18,4 @@ async def main():
await loader.Irc.run() await loader.Irc.run()
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(main()) asyncio.run(main(), debug=False)

View File

@@ -73,7 +73,7 @@ class Clone(IModule):
self.ctx.Logs.debug(f"Cache Size = {self.ctx.Settings.get_cache_size()}") self.ctx.Logs.debug(f"Cache Size = {self.ctx.Settings.get_cache_size()}")
# Créer les nouvelles commandes du module # Créer les nouvelles commandes du module
self.ctx.Irc.build_command(1, self.module_name, 'clone', 'Connect, join, part, kill and say clones') 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.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_sjoin(self.ctx.Config.CLONE_CHANNEL)

View File

@@ -65,56 +65,56 @@ class Command(IModule):
for c in new_cmds: for c in new_cmds:
self.ctx.Irc.Protocol.known_protocol.add(c) self.ctx.Irc.Protocol.known_protocol.add(c)
self.ctx.Irc.build_command(2, self.module_name, 'join', 'Join a channel') self.ctx.Commands.build_command(2, self.module_name, 'join', 'Join a channel')
self.ctx.Irc.build_command(2, self.module_name, 'assign', 'Assign a user to a role or task') self.ctx.Commands.build_command(2, self.module_name, 'assign', 'Assign a user to a role or task')
self.ctx.Irc.build_command(2, self.module_name, 'part', 'Leave a channel') self.ctx.Commands.build_command(2, self.module_name, 'part', 'Leave a channel')
self.ctx.Irc.build_command(2, self.module_name, 'unassign', 'Remove a user from a role or task') self.ctx.Commands.build_command(2, self.module_name, 'unassign', 'Remove a user from a role or task')
self.ctx.Irc.build_command(2, self.module_name, 'owner', 'Give channel ownership to a user') self.ctx.Commands.build_command(2, self.module_name, 'owner', 'Give channel ownership to a user')
self.ctx.Irc.build_command(2, self.module_name, 'deowner', 'Remove channel ownership from a user') self.ctx.Commands.build_command(2, self.module_name, 'deowner', 'Remove channel ownership from a user')
self.ctx.Irc.build_command(2, self.module_name, 'protect', 'Protect a user from being kicked') self.ctx.Commands.build_command(2, self.module_name, 'protect', 'Protect a user from being kicked')
self.ctx.Irc.build_command(2, self.module_name, 'deprotect', 'Remove protection from a user') self.ctx.Commands.build_command(2, self.module_name, 'deprotect', 'Remove protection from a user')
self.ctx.Irc.build_command(2, self.module_name, 'op', 'Grant operator privileges to a user') self.ctx.Commands.build_command(2, self.module_name, 'op', 'Grant operator privileges to a user')
self.ctx.Irc.build_command(2, self.module_name, 'deop', 'Remove operator privileges from a user') self.ctx.Commands.build_command(2, self.module_name, 'deop', 'Remove operator privileges from a user')
self.ctx.Irc.build_command(1, self.module_name, 'halfop', 'Grant half-operator privileges to a user') self.ctx.Commands.build_command(1, self.module_name, 'halfop', 'Grant half-operator privileges to a user')
self.ctx.Irc.build_command(1, self.module_name, 'dehalfop', 'Remove half-operator privileges from a user') self.ctx.Commands.build_command(1, self.module_name, 'dehalfop', 'Remove half-operator privileges from a user')
self.ctx.Irc.build_command(1, self.module_name, 'voice', 'Grant voice privileges to a user') self.ctx.Commands.build_command(1, self.module_name, 'voice', 'Grant voice privileges to a user')
self.ctx.Irc.build_command(1, self.module_name, 'devoice', 'Remove voice privileges from a user') self.ctx.Commands.build_command(1, self.module_name, 'devoice', 'Remove voice privileges from a user')
self.ctx.Irc.build_command(1, self.module_name, 'topic', 'Change the topic of a channel') self.ctx.Commands.build_command(1, self.module_name, 'topic', 'Change the topic of a channel')
self.ctx.Irc.build_command(2, self.module_name, 'opall', 'Grant operator privileges to all users') self.ctx.Commands.build_command(2, self.module_name, 'opall', 'Grant operator privileges to all users')
self.ctx.Irc.build_command(2, self.module_name, 'deopall', 'Remove operator privileges from all users') self.ctx.Commands.build_command(2, self.module_name, 'deopall', 'Remove operator privileges from all users')
self.ctx.Irc.build_command(2, self.module_name, 'devoiceall', 'Remove voice privileges from all users') self.ctx.Commands.build_command(2, self.module_name, 'devoiceall', 'Remove voice privileges from all users')
self.ctx.Irc.build_command(2, self.module_name, 'voiceall', 'Grant voice privileges to all users') self.ctx.Commands.build_command(2, self.module_name, 'voiceall', 'Grant voice privileges to all users')
self.ctx.Irc.build_command(2, self.module_name, 'ban', 'Ban a user from a channel') self.ctx.Commands.build_command(2, self.module_name, 'ban', 'Ban a user from a channel')
self.ctx.Irc.build_command(2, self.module_name, 'automode', 'Automatically set user modes upon join') self.ctx.Commands.build_command(2, self.module_name, 'automode', 'Automatically set user modes upon join')
self.ctx.Irc.build_command(2, self.module_name, 'unban', 'Remove a ban from a user') self.ctx.Commands.build_command(2, self.module_name, 'unban', 'Remove a ban from a user')
self.ctx.Irc.build_command(2, self.module_name, 'kick', 'Kick a user from a channel') self.ctx.Commands.build_command(2, self.module_name, 'kick', 'Kick a user from a channel')
self.ctx.Irc.build_command(2, self.module_name, 'kickban', 'Kick and ban 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.Irc.build_command(2, self.module_name, 'umode', 'Set user mode') self.ctx.Commands.build_command(2, self.module_name, 'umode', 'Set user mode')
self.ctx.Irc.build_command(2, self.module_name, 'mode', 'Set channel mode') self.ctx.Commands.build_command(2, self.module_name, 'mode', 'Set channel mode')
self.ctx.Irc.build_command(2, self.module_name, 'get_mode', 'Retrieve current channel mode') self.ctx.Commands.build_command(2, self.module_name, 'get_mode', 'Retrieve current channel mode')
self.ctx.Irc.build_command(2, self.module_name, 'svsjoin', 'Force a user to join a channel') self.ctx.Commands.build_command(2, self.module_name, 'svsjoin', 'Force a user to join a channel')
self.ctx.Irc.build_command(2, self.module_name, 'svspart', 'Force a user to leave a channel') self.ctx.Commands.build_command(2, self.module_name, 'svspart', 'Force a user to leave a channel')
self.ctx.Irc.build_command(2, self.module_name, 'svsnick', 'Force a user to change their nickname') self.ctx.Commands.build_command(2, self.module_name, 'svsnick', 'Force a user to change their nickname')
self.ctx.Irc.build_command(2, self.module_name, 'wallops', 'Send a message to all operators') self.ctx.Commands.build_command(2, self.module_name, 'wallops', 'Send a message to all operators')
self.ctx.Irc.build_command(2, self.module_name, 'globops', 'Send a global operator message') self.ctx.Commands.build_command(2, self.module_name, 'globops', 'Send a global operator message')
self.ctx.Irc.build_command(2, self.module_name, 'gnotice', 'Send a global notice') self.ctx.Commands.build_command(2, self.module_name, 'gnotice', 'Send a global notice')
self.ctx.Irc.build_command(2, self.module_name, 'whois', 'Get information about a user') self.ctx.Commands.build_command(2, self.module_name, 'whois', 'Get information about a user')
self.ctx.Irc.build_command(2, self.module_name, 'names', 'List users in a channel') self.ctx.Commands.build_command(2, self.module_name, 'names', 'List users in a channel')
self.ctx.Irc.build_command(2, self.module_name, 'invite', 'Invite a user to a channel') self.ctx.Commands.build_command(2, self.module_name, 'invite', 'Invite a user to a channel')
self.ctx.Irc.build_command(2, self.module_name, 'inviteme', 'Invite yourself to a channel') self.ctx.Commands.build_command(2, self.module_name, 'inviteme', 'Invite yourself to a channel')
self.ctx.Irc.build_command(2, self.module_name, 'sajoin', 'Force yourself into a channel') self.ctx.Commands.build_command(2, self.module_name, 'sajoin', 'Force yourself into a channel')
self.ctx.Irc.build_command(2, self.module_name, 'sapart', 'Force yourself to leave a channel') self.ctx.Commands.build_command(2, self.module_name, 'sapart', 'Force yourself to leave a channel')
self.ctx.Irc.build_command(2, self.module_name, 'kill', 'Disconnect a user from the server') self.ctx.Commands.build_command(2, self.module_name, 'kill', 'Disconnect a user from the server')
self.ctx.Irc.build_command(2, self.module_name, 'gline', 'Ban a user from the entire server') self.ctx.Commands.build_command(2, self.module_name, 'gline', 'Ban a user from the entire server')
self.ctx.Irc.build_command(2, self.module_name, 'ungline', 'Remove a global server ban') self.ctx.Commands.build_command(2, self.module_name, 'ungline', 'Remove a global server ban')
self.ctx.Irc.build_command(2, self.module_name, 'kline', 'Ban a user based on their hostname') self.ctx.Commands.build_command(2, self.module_name, 'kline', 'Ban a user based on their hostname')
self.ctx.Irc.build_command(2, self.module_name, 'unkline', 'Remove a K-line ban') self.ctx.Commands.build_command(2, self.module_name, 'unkline', 'Remove a K-line ban')
self.ctx.Irc.build_command(2, self.module_name, 'shun', 'Prevent a user from sending messages') self.ctx.Commands.build_command(2, self.module_name, 'shun', 'Prevent a user from sending messages')
self.ctx.Irc.build_command(2, self.module_name, 'unshun', 'Remove a shun from a user') self.ctx.Commands.build_command(2, self.module_name, 'unshun', 'Remove a shun from a user')
self.ctx.Irc.build_command(2, self.module_name, 'glinelist', 'List all global bans') self.ctx.Commands.build_command(2, self.module_name, 'glinelist', 'List all global bans')
self.ctx.Irc.build_command(2, self.module_name, 'shunlist', 'List all shunned users') self.ctx.Commands.build_command(2, self.module_name, 'shunlist', 'List all shunned users')
self.ctx.Irc.build_command(2, self.module_name, 'klinelist', 'List all K-line bans') self.ctx.Commands.build_command(2, self.module_name, 'klinelist', 'List all K-line bans')
self.ctx.Irc.build_command(3, self.module_name, 'map', 'Show the server network map') self.ctx.Commands.build_command(3, self.module_name, 'map', 'Show the server network map')
def unload(self) -> None: def unload(self) -> None:
self.ctx.Commands.drop_command_by_module(self.module_name) self.ctx.Commands.drop_command_by_module(self.module_name)
@@ -214,7 +214,7 @@ class Command(IModule):
user_uid = self.ctx.User.clean_uid(cmd[5]) user_uid = self.ctx.User.clean_uid(cmd[5])
userObj: MUser = self.ctx.User.get_user(user_uid) userObj: MUser = self.ctx.User.get_user(user_uid)
channel_name = cmd[4] if self.ctx.Channel.is_valid_channel(cmd[4]) else None channel_name = cmd[4] if self.ctx.Channel.is_valid_channel(cmd[4]) else None
client_obj = self.ctx.Client.get_Client(user_uid) client_obj = self.ctx.Client.get_client(user_uid)
nickname = userObj.nickname if userObj is not None else None nickname = userObj.nickname if userObj is not None else None
if client_obj is not None: if client_obj is not None:

View File

@@ -1,4 +1,5 @@
from dataclasses import dataclass from dataclasses import dataclass
import logging
from typing import Any, TYPE_CHECKING, Optional from typing import Any, TYPE_CHECKING, Optional
from core.classes.interfaces.imodule import IModule from core.classes.interfaces.imodule import IModule
import mods.defender.schemas as schemas import mods.defender.schemas as schemas
@@ -26,6 +27,8 @@ class Defender(IModule):
def __init__(self, context: 'Loader') -> None: def __init__(self, context: 'Loader') -> None:
super().__init__(context) super().__init__(context)
self._mod_config: Optional[schemas.ModConfModel] = None self._mod_config: Optional[schemas.ModConfModel] = None
self.Schemas = schemas.RepDB()
self.Threads = thds
@property @property
def mod_config(self) -> ModConfModel: def mod_config(self) -> ModConfModel:
@@ -55,40 +58,43 @@ class Defender(IModule):
return None return None
async def load(self): async def load(self):
# Variable qui va contenir les options de configuration du module Defender # Variable qui va contenir les options de configuration du module Defender
self._mod_config: schemas.ModConfModel = self.ModConfModel() self._mod_config: schemas.ModConfModel = self.ModConfModel()
# sync the database with local variable (Mandatory) # sync the database with local variable (Mandatory)
await self.sync_db() await self.sync_db()
# Add module schemas
self.Schemas = schemas
# Add module utils functions # Add module utils functions
self.mod_utils = utils self.mod_utils = utils
# Create module commands (Mandatory) # Create module commands (Mandatory)
self.ctx.Irc.build_command(0, self.module_name, 'code', 'Display the code or key for access') self.ctx.Commands.build_command(0, self.module_name, 'code', 'Display the code or key for access')
self.ctx.Irc.build_command(1, self.module_name, 'info', 'Provide information about the channel or server') self.ctx.Commands.build_command(1, self.module_name, 'info', 'Provide information about the channel or server')
self.ctx.Irc.build_command(1, self.module_name, 'autolimit', 'Automatically set channel user limits') self.ctx.Commands.build_command(1, self.module_name, 'autolimit', 'Automatically set channel user limits')
self.ctx.Irc.build_command(3, self.module_name, 'reputation', 'Check or manage user reputation') self.ctx.Commands.build_command(3, self.module_name, 'reputation', 'Check or manage user reputation')
self.ctx.Irc.build_command(3, self.module_name, 'proxy_scan', 'Scan users for proxy connections') self.ctx.Commands.build_command(3, self.module_name, 'proxy_scan', 'Scan users for proxy connections')
self.ctx.Irc.build_command(3, self.module_name, 'flood', 'Handle flood detection and mitigation') self.ctx.Commands.build_command(3, self.module_name, 'flood', 'Handle flood detection and mitigation')
self.ctx.Irc.build_command(3, self.module_name, 'status', 'Check the status of the server or bot') self.ctx.Commands.build_command(3, self.module_name, 'status', 'Check the status of the server or bot')
self.ctx.Irc.build_command(3, self.module_name, 'show_reputation', 'Display reputation information') self.ctx.Commands.build_command(3, self.module_name, 'show_reputation', 'Display reputation information')
self.ctx.Irc.build_command(3, self.module_name, 'sentinel', 'Monitor and guard the channel or server') self.ctx.Commands.build_command(3, self.module_name, 'sentinel', 'Monitor and guard the channel or server')
self.timeout = self.ctx.Config.API_TIMEOUT self.timeout = self.ctx.Config.API_TIMEOUT
# Listes qui vont contenir les ip a scanner avec les différentes API # 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_ABUSEIPDB_USERS = []
self.Schemas.DB_PSUTIL_USERS = self.Schemas.DB_LOCALSCAN_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 # Variables qui indique que les threads sont en cours d'éxecutions
self.abuseipdb_isRunning = self.freeipapi_isRunning = self.cloudfilt_isRunning = True self.abuseipdb_isRunning = True if self.mod_config.abuseipdb_scan == 1 else False
self.psutil_isRunning = self.localscan_isRunning = self.reputationTimer_isRunning = True self.freeipapi_isRunning = True if self.mod_config.freeipapi_scan == 1 else False
self.autolimit_isRunning = True 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 # Variable qui va contenir les users
self.flood_system = {} self.flood_system = {}
@@ -101,19 +107,21 @@ class Defender(IModule):
self.cloudfilt_key = 'r1gEtjtfgRQjtNBDMxsg' self.cloudfilt_key = 'r1gEtjtfgRQjtNBDMxsg'
# Démarrer les threads pour démarrer les api # Démarrer les threads pour démarrer les api
self.ctx.Base.create_asynctask(thds.coro_freeipapi_scan(self)) 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(thds.coro_cloudfilt_scan(self)) 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(thds.coro_abuseipdb_scan(self)) 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(thds.coro_local_scan(self)) 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(thds.coro_psutil_scan(self)) 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(thds.coro_apply_reputation_sanctions(self)) 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.autolimit == 1:
self.ctx.Base.create_asynctask(thds.coro_autolimit(self))
if self.mod_config.reputation == 1: if self.mod_config.reputation == 1:
await self.ctx.Irc.Protocol.send_sjoin(self.ctx.Config.SALON_JAIL) 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}") 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): def __onload(self):
@@ -138,7 +146,7 @@ class Defender(IModule):
if localscan: if localscan:
self.Schemas.DB_LOCALSCAN_USERS = localscan self.Schemas.DB_LOCALSCAN_USERS = localscan
def unload(self) -> None: async def unload(self) -> None:
"""Cette methode sera executée a chaque désactivation ou """Cette methode sera executée a chaque désactivation ou
rechargement de module rechargement de module
""" """
@@ -158,6 +166,13 @@ class Defender(IModule):
self.ctx.Commands.drop_command_by_module(self.module_name) 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 return None
async def insert_db_trusted(self, uid: str, nickname:str) -> None: async def insert_db_trusted(self, uid: str, nickname:str) -> None:
@@ -411,22 +426,26 @@ class Defender(IModule):
if self.mod_config.reputation == 1: 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) 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 False return None
# self.update_db_configuration('reputation', 1)
await self.update_configuration(key, 1) 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_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.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.send2socket(f":{service_id} SAMODE {jail_chan} +{dumodes} {dnickname}")
await self.ctx.Irc.Protocol.send2socket(f":{service_id} MODE {jail_chan} +{jail_chan_mode}") await self.ctx.Irc.Protocol.send_set_mode(f'+{jail_chan_mode}', channel_name=jail_chan)
if self.mod_config.reputation_sg == 1: if self.mod_config.reputation_sg == 1:
for chan in self.ctx.Channel.UID_CHANNEL_DB: for chan in self.ctx.Channel.UID_CHANNEL_DB:
if chan.name != jail_chan: if chan.name != jail_chan:
await self.ctx.Irc.Protocol.send2socket(f":{service_id} MODE {chan.name} +b ~security-group:unknown-users") await self.ctx.Irc.Protocol.send_set_mode('+b', channel_name=chan.name, params='~security-group:unknown-users')
await self.ctx.Irc.Protocol.send2socket(f":{service_id} MODE {chan.name} +eee ~security-group:webirc-users ~security-group:known-users ~security-group:websocket-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) await self.ctx.Channel.db_query_channel('add', self.module_name, jail_chan)
@@ -441,20 +460,26 @@ class Defender(IModule):
return False return False
await self.update_configuration(key, 0) await self.update_configuration(key, 0)
self.reputationTimer_isRunning = False
await self.ctx.Irc.Protocol.send_priv_msg( await self.ctx.Irc.Protocol.send_priv_msg(
nick_from=dnickname, nick_from=dnickname,
msg=f"[ {self.ctx.Config.COLORS.red}REPUTATION{self.ctx.Config.COLORS.black} ] : Deactivated by {fromuser}", msg=f"[ {self.ctx.Config.COLORS.red}REPUTATION{self.ctx.Config.COLORS.black} ] : Deactivated by {fromuser}",
channel=dchanlog channel=dchanlog
) )
await self.ctx.Irc.Protocol.send2socket(f":{service_id} SAMODE {jail_chan} -{dumodes} {dnickname}") 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} -sS") await self.ctx.Irc.Protocol.send_set_mode('-sS', channel_name=jail_chan)
await self.ctx.Irc.Protocol.send2socket(f":{service_id} PART {jail_chan}") await self.ctx.Irc.Protocol.send_part_chan(service_id, jail_chan)
for chan in self.ctx.Channel.UID_CHANNEL_DB: for chan in self.ctx.Channel.UID_CHANNEL_DB:
if chan.name != jail_chan: if chan.name != jail_chan:
await self.ctx.Irc.Protocol.send2socket(f":{service_id} MODE {chan.name} -b ~security-group:unknown-users") await self.ctx.Irc.Protocol.send_set_mode('-b', channel_name=chan.name, params='~security-group:unknown-users')
await self.ctx.Irc.Protocol.send2socket(f":{service_id} MODE {chan.name} -eee ~security-group:webirc-users ~security-group:known-users ~security-group:websocket-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) await self.ctx.Channel.db_query_channel('del', self.module_name, jail_chan)
@@ -464,12 +489,17 @@ class Defender(IModule):
match get_options: match get_options:
case 'release': case 'release':
# .reputation release [nick] # .reputation release [nick]
p = await self.ctx.Irc.Protocol
link = self.ctx.Config.SERVEUR_LINK link = self.ctx.Config.SERVEUR_LINK
jailed_salon = self.ctx.Config.SALON_JAIL jailed_salon = self.ctx.Config.SALON_JAIL
welcome_salon = self.ctx.Config.SALON_LIBERER welcome_salon = self.ctx.Config.SALON_LIBERER
client_obj = self.ctx.User.get_user(str(cmd[2])) 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: if client_obj is None:
await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname, await self.ctx.Irc.Protocol.send_notice(nick_from=dnickname,
nick_to=fromuser, nick_to=fromuser,
@@ -658,6 +688,7 @@ class Defender(IModule):
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) 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 return None
self.ctx.Base.create_asynctask(self.Threads.coro_local_scan(self))
await self.update_configuration(option, 1) 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) 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)
@@ -667,6 +698,7 @@ class Defender(IModule):
return None return None
await self.update_configuration(option, 0) 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) 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)
@@ -676,6 +708,7 @@ class Defender(IModule):
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) 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 return None
self.ctx.Base.create_asynctask(self.Threads.coro_psutil_scan(self))
await self.update_configuration(option, 1) 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) 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)
@@ -685,6 +718,7 @@ class Defender(IModule):
return None return None
await self.update_configuration(option, 0) 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) 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)
@@ -694,6 +728,7 @@ class Defender(IModule):
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) 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 return None
self.ctx.Base.create_asynctask(self.Threads.coro_abuseipdb_scan(self))
await self.update_configuration(option, 1) 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) 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)
@@ -703,6 +738,7 @@ class Defender(IModule):
return None return None
await self.update_configuration(option, 0) 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) 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)
@@ -712,6 +748,7 @@ class Defender(IModule):
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) 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 return None
self.ctx.Base.create_asynctask(self.Threads.coro_freeipapi_scan(self))
await self.update_configuration(option, 1) 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) 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)
@@ -721,6 +758,7 @@ class Defender(IModule):
return None return None
await self.update_configuration(option, 0) 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) 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)
@@ -730,6 +768,7 @@ class Defender(IModule):
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) 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 return None
self.ctx.Base.create_asynctask(self.Threads.coro_cloudfilt_scan(self))
await self.update_configuration(option, 1) 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) 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)
@@ -739,6 +778,7 @@ class Defender(IModule):
return None return None
await self.update_configuration(option, 0) 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) 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)
@@ -915,3 +955,6 @@ class Defender(IModule):
await self.join_saved_channels() await self.join_saved_channels()
return None return None
case _:
pass

View File

@@ -28,9 +28,11 @@ class FloodUser(MainModel):
nbr_msg: int = 0 nbr_msg: int = 0
first_msg_time: int = 0 first_msg_time: int = 0
DB_FLOOD_USERS: list[FloodUser] = []
DB_ABUSEIPDB_USERS: list[MUser] = [] class RepDB:
DB_FREEIPAPI_USERS: list[MUser] = [] DB_FLOOD_USERS: list[FloodUser] = []
DB_CLOUDFILT_USERS: list[MUser] = [] DB_ABUSEIPDB_USERS: list[MUser] = []
DB_PSUTIL_USERS: list[MUser] = [] DB_FREEIPAPI_USERS: list[MUser] = []
DB_LOCALSCAN_USERS: list[MUser] = [] DB_CLOUDFILT_USERS: list[MUser] = []
DB_PSUTIL_USERS: list[MUser] = []
DB_LOCALSCAN_USERS: list[MUser] = []

View File

@@ -1,89 +1,251 @@
import asyncio import asyncio
from typing import TYPE_CHECKING from typing import TYPE_CHECKING, Optional
from time import sleep
if TYPE_CHECKING: if TYPE_CHECKING:
from mods.defender.mod_defender import Defender from mods.defender.mod_defender import Defender
async def coro_apply_reputation_sanctions(uplink: 'Defender'): async def coro_apply_reputation_sanctions(uplink: 'Defender'):
uplink.reputationTimer_isRunning = True
while uplink.reputationTimer_isRunning: while uplink.reputationTimer_isRunning:
await uplink.mod_utils.action_apply_reputation_santions(uplink) await uplink.mod_utils.action_apply_reputation_santions(uplink)
await asyncio.sleep(5) await asyncio.sleep(5)
async def coro_cloudfilt_scan(uplink: 'Defender'): async def coro_cloudfilt_scan(uplink: 'Defender'):
uplink.cloudfilt_isRunning = True
service_id = uplink.ctx.Config.SERVICE_ID
service_chanlog = uplink.ctx.Config.SERVICE_CHANLOG
color_red = uplink.ctx.Config.COLORS.red
nogc = uplink.ctx.Config.COLORS.nogc
nogc = uplink.ctx.Config.COLORS.nogc
p = uplink.ctx.Irc.Protocol
while uplink.cloudfilt_isRunning: while uplink.cloudfilt_isRunning:
list_to_remove:list = [] try:
for user in uplink.Schemas.DB_CLOUDFILT_USERS: list_to_remove:list = []
await uplink.mod_utils.action_scan_client_with_cloudfilt(uplink, user) for user in uplink.Schemas.DB_CLOUDFILT_USERS:
list_to_remove.append(user) if user.remote_ip not in uplink.ctx.Config.WHITELISTED_IP:
await asyncio.sleep(1) result: Optional[dict] = await uplink.ctx.Base.create_thread_io(
uplink.mod_utils.action_scan_client_with_cloudfilt,
uplink, user
)
list_to_remove.append(user)
for user_model in list_to_remove: if not result:
uplink.Schemas.DB_CLOUDFILT_USERS.remove(user_model) continue
await asyncio.sleep(1) 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'): 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: while uplink.freeipapi_isRunning:
try:
list_to_remove: list = []
for user in uplink.Schemas.DB_FREEIPAPI_USERS:
if user.remote_ip not in uplink.ctx.Config.WHITELISTED_IP:
result: Optional[dict] = await uplink.ctx.Base.create_thread_io(
uplink.mod_utils.action_scan_client_with_freeipapi,
uplink, user
)
list_to_remove: list = [] if not result:
for user in uplink.Schemas.DB_FREEIPAPI_USERS: continue
await uplink.mod_utils.action_scan_client_with_freeipapi(uplink, user)
list_to_remove.append(user)
await asyncio.sleep(1)
for user_model in list_to_remove: # pseudo!ident@host
uplink.Schemas.DB_FREEIPAPI_USERS.remove(user_model) remote_ip = user.remote_ip
fullname = f'{user.nickname}!{user.username}@{user.hostname}'
await asyncio.sleep(1) await p.send_priv_msg(
nick_from=service_id,
msg=f"[ {color_red}FREEIPAPI_SCAN{nogc} ] : Connexion de {fullname} ({remote_ip}) ==> Proxy: {str(result['isProxy'])} | Country : {str(result['countryCode'])}",
channel=service_chanlog)
uplink.ctx.Logs.debug(f"[FREEIPAPI SCAN] ({fullname}) connected from ({result['countryCode']}), Proxy: {result['isProxy']}")
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'): 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: while uplink.abuseipdb_isRunning:
try:
list_to_remove: list = []
for user in uplink.Schemas.DB_ABUSEIPDB_USERS:
if user.remote_ip not in uplink.ctx.Config.WHITELISTED_IP:
list_to_remove: list = [] result: Optional[dict] = await uplink.ctx.Base.create_thread_io(
print(uplink.Schemas.DB_ABUSEIPDB_USERS) uplink.mod_utils.action_scan_client_with_abuseipdb,
for user in uplink.Schemas.DB_ABUSEIPDB_USERS: uplink, user
await uplink.mod_utils.action_scan_client_with_abuseipdb(uplink, user) )
list_to_remove.append(user) list_to_remove.append(user)
await asyncio.sleep(1)
print(list_to_remove) if not result:
for user_model in list_to_remove: continue
uplink.Schemas.DB_ABUSEIPDB_USERS.remove(user_model)
await asyncio.sleep(1) 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'): 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: while uplink.localscan_isRunning:
list_to_remove:list = [] try:
for user in uplink.Schemas.DB_LOCALSCAN_USERS: list_to_remove:list = []
await uplink.mod_utils.action_scan_client_with_local_socket(uplink, user) for user in uplink.Schemas.DB_LOCALSCAN_USERS:
list_to_remove.append(user) if user.remote_ip not in uplink.ctx.Config.WHITELISTED_IP:
await asyncio.sleep(1) list_to_remove.append(user)
result = await uplink.ctx.Base.create_thread_io(
uplink.mod_utils.action_scan_client_with_local_socket,
uplink, user
)
for user_model in list_to_remove: if not result:
uplink.Schemas.DB_LOCALSCAN_USERS.remove(user_model) continue
await asyncio.sleep(1) 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'): 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: while uplink.psutil_isRunning:
try:
list_to_remove:list = [] list_to_remove:list = []
for user in uplink.Schemas.DB_PSUTIL_USERS: for user in uplink.Schemas.DB_PSUTIL_USERS:
await uplink.mod_utils.action_scan_client_with_psutil(uplink, user) result = await uplink.ctx.Base.create_thread_io(uplink.mod_utils.action_scan_client_with_psutil, uplink, user)
list_to_remove.append(user) 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) await asyncio.sleep(1)
for user_model in list_to_remove: for user_model in list_to_remove:
uplink.Schemas.DB_PSUTIL_USERS.remove(user_model) uplink.Schemas.DB_PSUTIL_USERS.remove(user_model)
await asyncio.sleep(1) await asyncio.sleep(1.5)
except ValueError as ve:
uplink.ctx.Logs.debug(f"The value to remove is not in the list. {ve}")
except TimeoutError as te:
uplink.ctx.Logs.debug(f"Timeout Error {te}")
async def coro_autolimit(uplink: 'Defender'): async def coro_autolimit(uplink: 'Defender'):
@@ -104,7 +266,6 @@ async def coro_autolimit(uplink: 'Defender'):
chan_list: list[str] = [c.name 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: while uplink.autolimit_isRunning:
if uplink.mod_config.autolimit == 0: if uplink.mod_config.autolimit == 0:
uplink.ctx.Logs.debug("autolimit deactivated ... stopping the current thread") uplink.ctx.Logs.debug("autolimit deactivated ... stopping the current thread")
break break
@@ -112,7 +273,7 @@ async def coro_autolimit(uplink: 'Defender'):
for chan in uplink.ctx.Channel.UID_CHANNEL_DB: for chan in uplink.ctx.Channel.UID_CHANNEL_DB:
for chan_copy in chanObj_copy: for chan_copy in chanObj_copy:
if chan_copy["name"] == chan.name and len(chan.uids) != chan_copy["uids_count"]: if chan_copy["name"] == chan.name and len(chan.uids) != chan_copy["uids_count"]:
await p.send2socket(f":{uplink.ctx.Config.SERVICE_ID} MODE {chan.name} +l {len(chan.uids) + uplink.mod_config.autolimit_amount}") await p.send_set_mode('+l', channel_name=chan.name, params=len(chan.uids) + uplink.mod_config.autolimit_amount)
chan_copy["uids_count"] = len(chan.uids) chan_copy["uids_count"] = len(chan.uids)
if chan.name not in chan_list: if chan.name not in chan_list:
@@ -128,13 +289,13 @@ async def coro_autolimit(uplink: 'Defender'):
# Si c'est la premiere execution # Si c'est la premiere execution
if INIT == 1: if INIT == 1:
for chan in uplink.ctx.Channel.UID_CHANNEL_DB: for chan in uplink.ctx.Channel.UID_CHANNEL_DB:
await p.send2socket(f":{uplink.ctx.Config.SERVICE_ID} MODE {chan.name} +l {len(chan.uids) + uplink.mod_config.autolimit_amount}") 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 # Si le nouveau amount est différent de l'initial
if init_amount != uplink.mod_config.autolimit_amount: if init_amount != uplink.mod_config.autolimit_amount:
init_amount = uplink.mod_config.autolimit_amount init_amount = uplink.mod_config.autolimit_amount
for chan in uplink.ctx.Channel.UID_CHANNEL_DB: for chan in uplink.ctx.Channel.UID_CHANNEL_DB:
await p.send2socket(f":{uplink.ctx.Config.SERVICE_ID} MODE {chan.name} +l {len(chan.uids) + uplink.mod_config.autolimit_amount}") await p.send_set_mode('+l', channel_name=chan.name, params=len(chan.uids) + uplink.mod_config.autolimit_amount)
INIT = 0 INIT = 0
@@ -142,7 +303,6 @@ async def coro_autolimit(uplink: 'Defender'):
await asyncio.sleep(uplink.mod_config.autolimit_interval) await asyncio.sleep(uplink.mod_config.autolimit_interval)
for chan in uplink.ctx.Channel.UID_CHANNEL_DB: for chan in uplink.ctx.Channel.UID_CHANNEL_DB:
# await p.send2socket(f":{uplink.ctx.Config.SERVICE_ID} MODE {chan.name} -l")
await p.send_set_mode('-l', channel_name=chan.name) await p.send_set_mode('-l', channel_name=chan.name)
uplink.ctx.Irc.autolimit_started = False uplink.ctx.Irc.autolimit_started = False
@@ -158,7 +318,6 @@ async def coro_release_mode_mute(uplink: 'Defender', action: str, channel: str):
channel (str): The related channel channel (str): The related channel
""" """
service_id = uplink.ctx.Config.SERVICE_ID
timeout = uplink.mod_config.flood_timer timeout = uplink.mod_config.flood_timer
await asyncio.sleep(timeout) await asyncio.sleep(timeout)
@@ -169,6 +328,6 @@ async def coro_release_mode_mute(uplink: 'Defender', action: str, channel: str):
match action: match action:
case 'mode-m': case 'mode-m':
# Action -m sur le salon # Action -m sur le salon
await uplink.ctx.Irc.Protocol.send2socket(f":{service_id} MODE {channel} -m") await uplink.ctx.Irc.Protocol.send_set_mode('-m', channel_name=channel)
case _: case _:
pass pass

View File

@@ -267,6 +267,7 @@ async def handle_on_uid(uplink: 'Defender', srvmsg: list[str]):
#################### ####################
# [:<sid>] UID <uid> <ts> <nick> <real-host> <displayed-host> <real-user> <ip> <signon> <modes> [<mode-parameters>]+ :<real> # [:<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 # [:<sid>] UID nickname hopcount timestamp username hostname uid servicestamp umodes virthost cloakedhost ip :gecos
async def action_on_flood(uplink: 'Defender', srvmsg: list[str]): async def action_on_flood(uplink: 'Defender', srvmsg: list[str]):
confmodel = uplink.mod_config confmodel = uplink.mod_config
@@ -316,17 +317,16 @@ async def action_on_flood(uplink: 'Defender', srvmsg: list[str]):
fu.nbr_msg = 0 fu.nbr_msg = 0
get_diff_secondes = unixtime - fu.first_msg_time get_diff_secondes = unixtime - fu.first_msg_time
elif fu.nbr_msg > flood_message: elif fu.nbr_msg > flood_message:
uplink.ctx.Logs.info('system de flood detecté') await p.send_set_mode('+m', channel_name=channel)
await p.send_priv_msg( await p.send_priv_msg(
nick_from=dnickname, nick_from=dnickname,
msg=f"{color_red} {color_bold} Flood detected. Apply the +m mode (Ô_o)", msg=f"{color_red} {color_bold} Flood detected. Apply the +m mode (Ô_o)",
channel=channel channel=channel
) )
await p.send2socket(f":{service_id} MODE {channel} +m") uplink.ctx.Logs.debug(f'[FLOOD] {get_detected_nickname} triggered +m mode on the channel {channel}')
uplink.ctx.Logs.info(f'FLOOD Détecté sur {get_detected_nickname} mode +m appliqué sur le salon {channel}')
fu.nbr_msg = 0 fu.nbr_msg = 0
fu.first_msg_time = unixtime fu.first_msg_time = unixtime
uplink.ctx.Base.create_asynctask(dthreads.coro_release_mode_mute(uplink, 'mode-m', channel)) 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 ): async def action_add_reputation_sanctions(uplink: 'Defender', jailed_uid: str ):
@@ -336,10 +336,14 @@ async def action_add_reputation_sanctions(uplink: 'Defender', jailed_uid: str ):
confmodel = uplink.mod_config confmodel = uplink.mod_config
get_reputation = uplink.ctx.Reputation.get_reputation(jailed_uid) get_reputation = uplink.ctx.Reputation.get_reputation(jailed_uid)
if get_reputation is None: if get_reputation is None:
uplink.ctx.Logs.warning(f'UID {jailed_uid} has not been found') uplink.ctx.Logs.warning(f'UID {jailed_uid} has not been found')
return 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_logs = gconfig.SERVICE_CHANLOG
salon_jail = gconfig.SALON_JAIL salon_jail = gconfig.SALON_JAIL
@@ -360,7 +364,7 @@ async def action_add_reputation_sanctions(uplink: 'Defender', jailed_uid: str ):
# Si le user ne vient pas de webIrc # Si le user ne vient pas de webIrc
await p.send_sajoin(nick_to_sajoin=jailed_nickname, channel_name=salon_jail) await p.send_sajoin(nick_to_sajoin=jailed_nickname, channel_name=salon_jail)
await p.send_priv_msg(nick_from=gconfig.SERVICE_NICKNAME, await p.send_priv_msg(nick_from=gconfig.SERVICE_NICKNAME,
msg=f" [{color_red} REPUTATION {nogc}] : Connexion de {jailed_nickname} ({jailed_score}) ==> {salon_jail}", 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 channel=salon_logs
) )
await p.send_notice( await p.send_notice(
@@ -371,7 +375,7 @@ async def action_add_reputation_sanctions(uplink: 'Defender', jailed_uid: str ):
if reputation_ban_all_chan == 1: if reputation_ban_all_chan == 1:
for chan in uplink.ctx.Channel.UID_CHANNEL_DB: for chan in uplink.ctx.Channel.UID_CHANNEL_DB:
if chan.name != salon_jail: if chan.name != salon_jail:
await p.send2socket(f":{service_id} MODE {chan.name} +b {jailed_nickname}!*@*") await p.send_set_mode('+b', channel_name=chan.name, params=f'{jailed_nickname}!*@*')
await p.send2socket(f":{service_id} KICK {chan.name} {jailed_nickname}") 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})") uplink.ctx.Logs.info(f"[REPUTATION] {jailed_nickname} jailed (UID: {jailed_uid}, score: {jailed_score})")
@@ -419,14 +423,14 @@ async def action_apply_reputation_santions(uplink: 'Defender') -> None:
for chan in uplink.ctx.Channel.UID_CHANNEL_DB: for chan in uplink.ctx.Channel.UID_CHANNEL_DB:
if chan.name != salon_jail and ban_all_chan == 1: if chan.name != salon_jail and ban_all_chan == 1:
get_user_reputation = uplink.ctx.Reputation.get_reputation(uid) get_user_reputation = uplink.ctx.Reputation.get_reputation(uid)
await p.send2socket(f":{service_id} MODE {chan.name} -b {get_user_reputation.nickname}!*@*") 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}. # Lorsqu'un utilisateur quitte, il doit être supprimé de {UID_DB}.
uplink.ctx.Channel.delete_user_from_all_channel(uid) uplink.ctx.Channel.delete_user_from_all_channel(uid)
uplink.ctx.Reputation.delete(uid) uplink.ctx.Reputation.delete(uid)
uplink.ctx.User.delete(uid) uplink.ctx.User.delete(uid)
async def action_scan_client_with_cloudfilt(uplink: 'Defender', user_model: 'MUser') -> Optional[dict[str, str]]: def action_scan_client_with_cloudfilt(uplink: 'Defender', user_model: 'MUser') -> Optional[dict[str, str]]:
"""Analyse l'ip avec cloudfilt """Analyse l'ip avec cloudfilt
Cette methode devra etre lancer toujours via un thread ou un timer. Cette methode devra etre lancer toujours via un thread ou un timer.
Args: Args:
@@ -438,11 +442,6 @@ async def action_scan_client_with_cloudfilt(uplink: 'Defender', user_model: 'MUs
""" """
remote_ip = user_model.remote_ip remote_ip = user_model.remote_ip
username = user_model.username
hostname = user_model.hostname
nickname = user_model.nickname
p = uplink.ctx.Irc.Protocol
if remote_ip in uplink.ctx.Config.WHITELISTED_IP: if remote_ip in uplink.ctx.Config.WHITELISTED_IP:
return None return None
if uplink.mod_config.cloudfilt_scan == 0: if uplink.mod_config.cloudfilt_scan == 0:
@@ -450,52 +449,28 @@ async def action_scan_client_with_cloudfilt(uplink: 'Defender', user_model: 'MUs
if uplink.cloudfilt_key == '': if uplink.cloudfilt_key == '':
return None return None
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
url = "https://developers18334.cloudfilt.com/" url = "https://developers18334.cloudfilt.com/"
data = {'ip': remote_ip, 'key': uplink.cloudfilt_key}
with requests.Session() as sess:
response = sess.post(url=url, data=data)
data = { # Formatted output
'ip': remote_ip, decoded_response: dict = loads(response.text)
'key': uplink.cloudfilt_key status_code = response.status_code
} if status_code != 200:
uplink.ctx.Logs.warning(f'Error connecting to cloudfilt API | Code: {str(status_code)}')
return
response = requests.post(url=url, data=data) result = {
# Formatted output 'countryiso': decoded_response.get('countryiso', None),
decoded_response: dict = loads(response.text) 'listed': decoded_response.get('listed', False),
status_code = response.status_code 'listed_by': decoded_response.get('listed_by', None),
if status_code != 200: 'host': decoded_response.get('host', None)
uplink.ctx.Logs.warning(f'Error connecting to cloudfilt API | Code: {str(status_code)}') }
return
result = { return result
'countryiso': decoded_response.get('countryiso', None),
'listed': decoded_response.get('listed', None),
'listed_by': decoded_response.get('listed_by', None),
'host': decoded_response.get('host', None)
}
# pseudo!ident@host def action_scan_client_with_freeipapi(uplink: 'Defender', user_model: 'MUser') -> Optional[dict[str, str]]:
fullname = f'{nickname}!{username}@{hostname}'
await p.send_priv_msg(
nick_from=service_id,
msg=f"[ {color_red}CLOUDFILT_SCAN{nogc} ] : Connexion de {fullname} ({remote_ip}) ==> Host: {str(result['host'])} | country: {str(result['countryiso'])} | listed: {str(result['listed'])} | listed by : {str(result['listed_by'])}",
channel=service_chanlog)
uplink.ctx.Logs.debug(f"[CLOUDFILT SCAN] ({fullname}) connected from ({result['countryiso']}), Listed: {result['listed']}, by: {result['listed_by']}")
if result['listed']:
await p.send2socket(f":{service_id} GLINE +*@{remote_ip} {uplink.ctx.Config.GLINE_DURATION} Your connexion is listed as dangerous {str(result['listed'])} {str(result['listed_by'])} - detected by cloudfilt")
uplink.ctx.Logs.debug(f"[CLOUDFILT SCAN GLINE] Dangerous connection ({fullname}) from ({result['countryiso']}) Listed: {result['listed']}, by: {result['listed_by']}")
response.close()
return result
async def action_scan_client_with_freeipapi(uplink: 'Defender', user_model: 'MUser') -> Optional[dict[str, str]]:
"""Analyse l'ip avec Freeipapi """Analyse l'ip avec Freeipapi
Cette methode devra etre lancer toujours via un thread ou un timer. Cette methode devra etre lancer toujours via un thread ou un timer.
Args: Args:
@@ -505,64 +480,35 @@ async def action_scan_client_with_freeipapi(uplink: 'Defender', user_model: 'MUs
dict[str, any] | None: les informations du provider dict[str, any] | None: les informations du provider
keys : 'countryCode', 'isProxy' keys : 'countryCode', 'isProxy'
""" """
p = uplink.ctx.Irc.Protocol
remote_ip = user_model.remote_ip remote_ip = user_model.remote_ip
username = user_model.username
hostname = user_model.hostname
nickname = user_model.nickname
if remote_ip in uplink.ctx.Config.WHITELISTED_IP: if remote_ip in uplink.ctx.Config.WHITELISTED_IP:
return None return None
if uplink.mod_config.freeipapi_scan == 0: if uplink.mod_config.freeipapi_scan == 0:
return None return None
service_id = uplink.ctx.Config.SERVICE_ID with requests.Session() as sess:
service_chanlog = uplink.ctx.Config.SERVICE_CHANLOG url = f'https://freeipapi.com/api/json/{remote_ip}'
color_red = uplink.ctx.Config.COLORS.red headers = {'Accept': 'application/json'}
nogc = uplink.ctx.Config.COLORS.nogc response = sess.request(method='GET', url=url, headers=headers, timeout=uplink.timeout)
url = f'https://freeipapi.com/api/json/{remote_ip}' # Formatted output
decoded_response: dict = loads(response.text)
headers = { status_code = response.status_code
'Accept': 'application/json', if status_code == 429:
} uplink.ctx.Logs.warning('Too Many Requests - The rate limit for the API has been exceeded.')
return None
elif status_code != 200:
uplink.ctx.Logs.warning(f'status code = {str(status_code)}')
return None
response = requests.request(method='GET', url=url, headers=headers, timeout=uplink.timeout) result = {
'countryCode': decoded_response.get('countryCode', None),
'isProxy': decoded_response.get('isProxy', None)
}
return result
# Formatted output def action_scan_client_with_abuseipdb(uplink: 'Defender', user_model: 'MUser') -> Optional[dict[str, str]]:
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)
}
# pseudo!ident@host
fullname = f'{nickname}!{username}@{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']}")
response.close()
return result
async def action_scan_client_with_abuseipdb(uplink: 'Defender', user_model: 'MUser') -> Optional[dict[str, str]]:
"""Analyse l'ip avec AbuseIpDB """Analyse l'ip avec AbuseIpDB
Cette methode devra etre lancer toujours via un thread ou un timer. Cette methode devra etre lancer toujours via un thread ou un timer.
Args: Args:
@@ -572,121 +518,81 @@ async def action_scan_client_with_abuseipdb(uplink: 'Defender', user_model: 'MUs
Returns: Returns:
dict[str, str] | None: les informations du provider dict[str, str] | None: les informations du provider
""" """
p = uplink.ctx.Irc.Protocol
remote_ip = user_model.remote_ip remote_ip = user_model.remote_ip
username = user_model.username
hostname = user_model.hostname
nickname = user_model.nickname
if remote_ip in uplink.ctx.Config.WHITELISTED_IP: if remote_ip in uplink.ctx.Config.WHITELISTED_IP:
return None return None
if uplink.mod_config.abuseipdb_scan == 0: if uplink.mod_config.abuseipdb_scan == 0:
return None return None
if uplink.abuseipdb_key == '': if uplink.abuseipdb_key == '':
return None return None
url = 'https://api.abuseipdb.com/api/v2/check' with requests.Session() as sess:
querystring = { url = 'https://api.abuseipdb.com/api/v2/check'
'ipAddress': remote_ip, querystring = {'ipAddress': remote_ip, 'maxAgeInDays': '90'}
'maxAgeInDays': '90' headers = {
} 'Accept': 'application/json',
'Key': uplink.abuseipdb_key
}
headers = { response = sess.request(method='GET', url=url, headers=headers, params=querystring, timeout=uplink.timeout)
'Accept': 'application/json',
'Key': uplink.abuseipdb_key
}
response = requests.request(method='GET', url=url, headers=headers, params=querystring, timeout=uplink.timeout) if response.status_code != 200:
uplink.ctx.Logs.warning(f'status code = {str(response.status_code)}')
return None
# Formatted output # Formatted output
decoded_response: dict[str, dict] = loads(response.text) decoded_response: dict[str, dict] = loads(response.text)
if 'data' not in decoded_response: if 'data' not in decoded_response:
return None return None
result = { result = {
'score': decoded_response.get('data', {}).get('abuseConfidenceScore', 0), 'score': decoded_response.get('data', {}).get('abuseConfidenceScore', 0),
'country': decoded_response.get('data', {}).get('countryCode', None), 'country': decoded_response.get('data', {}).get('countryCode', None),
'isTor': decoded_response.get('data', {}).get('isTor', None), 'isTor': decoded_response.get('data', {}).get('isTor', None),
'totalReports': decoded_response.get('data', {}).get('totalReports', 0) 'totalReports': decoded_response.get('data', {}).get('totalReports', 0)
} }
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
# pseudo!ident@host
fullname = f'{nickname}!{username}@{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']}")
response.close()
return result return result
async def action_scan_client_with_local_socket(uplink: 'Defender', user_model: 'MUser'): def action_scan_client_with_local_socket(uplink: 'Defender', user_model: 'MUser') -> Optional[dict[str, str]]:
"""local_scan """local_scan
Args: Args:
uplink (Defender): Defender instance object uplink (Defender): Defender instance object
user_model (MUser): l'objet User qui contient l'ip user_model (MUser): l'objet User qui contient l'ip
""" """
p = uplink.ctx.Irc.Protocol
remote_ip = user_model.remote_ip remote_ip = user_model.remote_ip
username = user_model.username
hostname = user_model.hostname
nickname = user_model.nickname
fullname = f'{nickname}!{username}@{hostname}'
if remote_ip in uplink.ctx.Config.WHITELISTED_IP: if remote_ip in uplink.ctx.Config.WHITELISTED_IP:
return None return None
result = {'opened_ports': [], 'closed_ports': []}
for port in uplink.ctx.Config.PORTS_TO_SCAN: for port in uplink.ctx.Config.PORTS_TO_SCAN:
try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM or socket.SOCK_NONBLOCK) as sock:
newSocket = '' try:
newSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM or socket.SOCK_NONBLOCK) sock.settimeout(0.5)
newSocket.settimeout(0.5) connection = (remote_ip, uplink.ctx.Base.int_if_possible(port))
sock.connect(connection)
connection = (remote_ip, uplink.ctx.Base.int_if_possible(port)) result['opened_ports'].append(port)
newSocket.connect(connection) uplink.ctx.Base.running_sockets.append(sock)
sock.shutdown(socket.SHUT_RDWR)
uplink.ctx.Base.running_sockets.remove(sock)
return result
await p.send_priv_msg( except (socket.timeout, ConnectionRefusedError):
nick_from=uplink.ctx.Config.SERVICE_NICKNAME, uplink.ctx.Logs.debug(f"[LOCAL SCAN] Port {remote_ip}:{str(port)} is close.")
msg=f"[ {uplink.ctx.Config.COLORS.red}PROXY_SCAN{uplink.ctx.Config.COLORS.nogc} ] {fullname} ({remote_ip}) : Port [{str(port)}] ouvert sur l'adresse ip [{remote_ip}]", result['closed_ports'].append(port)
channel=uplink.ctx.Config.SERVICE_CHANLOG except AttributeError as ae:
) uplink.ctx.Logs.warning(f"AttributeError ({remote_ip}): {ae}")
# print(f"=======> Le port {str(port)} est ouvert !!") except socket.gaierror as err:
uplink.ctx.Base.running_sockets.append(newSocket) uplink.ctx.Logs.warning(f"Address Info Error ({remote_ip}): {err}")
# print(newSocket)
newSocket.shutdown(socket.SHUT_RDWR)
newSocket.close()
except (socket.timeout, ConnectionRefusedError): return result
uplink.ctx.Logs.info(f"Le port {remote_ip}:{str(port)} est fermé")
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}")
finally:
# newSocket.shutdown(socket.SHUT_RDWR)
newSocket.close()
uplink.ctx.Logs.info('=======> Fermeture de la socket')
async def action_scan_client_with_psutil(uplink: 'Defender', user_model: 'MUser') -> list[int]: 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) """psutil_scan for Linux (should be run on the same location as the unrealircd server)
Args: Args:
@@ -695,28 +601,16 @@ async def action_scan_client_with_psutil(uplink: 'Defender', user_model: 'MUser'
Returns: Returns:
list[int]: list of ports list[int]: list of ports
""" """
p = uplink.ctx.Irc.Protocol
remote_ip = user_model.remote_ip remote_ip = user_model.remote_ip
username = user_model.username
hostname = user_model.hostname
nickname = user_model.nickname
if remote_ip in uplink.ctx.Config.WHITELISTED_IP: if remote_ip in uplink.ctx.Config.WHITELISTED_IP:
return None return None
if uplink.mod_config.psutil_scan == 0:
return None
try: try:
connections = psutil.net_connections(kind='inet') connections = psutil.net_connections(kind='inet')
fullname = f'{nickname}!{username}@{hostname}'
matching_ports = [conn.raddr.port for conn in connections if conn.raddr and conn.raddr.ip == remote_ip] matching_ports = [conn.raddr.port for conn in connections if conn.raddr and conn.raddr.ip == remote_ip]
uplink.ctx.Logs.info(f"Connexion of {fullname} ({remote_ip}) using ports : {str(matching_ports)}") uplink.ctx.Logs.debug(f"Connexion of ({remote_ip}) using ports : {str(matching_ports)}")
if matching_ports:
await p.send_priv_msg(
nick_from=uplink.ctx.Config.SERVICE_NICKNAME,
msg=f"[ {uplink.ctx.Config.COLORS.red}PSUTIL_SCAN{uplink.ctx.Config.COLORS.black} ] {fullname} ({remote_ip}) : is using ports {matching_ports}",
channel=uplink.ctx.Config.SERVICE_CHANLOG
)
return matching_ports return matching_ports

View File

@@ -91,9 +91,9 @@ class Jsonrpc(IModule):
self.is_streaming = False self.is_streaming = False
# Create module commands (Mandatory) # Create module commands (Mandatory)
self.ctx.Irc.build_command(1, self.module_name, 'jsonrpc', 'Activate the JSON RPC Live connection [ON|OFF]') self.ctx.Commands.build_command(1, self.module_name, 'jsonrpc', 'Activate the JSON RPC Live connection [ON|OFF]')
self.ctx.Irc.build_command(1, self.module_name, 'jruser', 'Get Information about a user using JSON RPC') self.ctx.Commands.build_command(1, self.module_name, 'jruser', 'Get Information about a user using JSON RPC')
self.ctx.Irc.build_command(1, self.module_name, 'jrinstances', 'Get number of instances') self.ctx.Commands.build_command(1, self.module_name, 'jrinstances', 'Get number of instances')
try: try:
self.Rpc = ConnectionFactory(self.ctx.Config.DEBUG_LEVEL).get(self.ctx.Config.JSONRPC_METHOD) self.Rpc = ConnectionFactory(self.ctx.Config.DEBUG_LEVEL).get(self.ctx.Config.JSONRPC_METHOD)
@@ -138,7 +138,6 @@ class Jsonrpc(IModule):
channel=self.ctx.Config.SERVICE_CHANLOG channel=self.ctx.Config.SERVICE_CHANLOG
) )
self.ctx.Base.create_asynctask(thds.thread_unsubscribe(self)) self.ctx.Base.create_asynctask(thds.thread_unsubscribe(self))
# await self.update_configuration('jsonrpc', 0)
self.ctx.Commands.drop_command_by_module(self.module_name) self.ctx.Commands.drop_command_by_module(self.module_name)
self.ctx.Logs.debug(f"Unloading {self.module_name}") self.ctx.Logs.debug(f"Unloading {self.module_name}")
return None return None

View File

@@ -1,4 +1,3 @@
import asyncio
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:

View File

@@ -53,11 +53,11 @@ class Test(IModule):
""" """
# Create module commands (Mandatory) # Create module commands (Mandatory)
self.ctx.Irc.build_command(0, self.module_name, 'test-command', 'Execute a test command') self.ctx.Commands.build_command(0, self.module_name, 'test-command', 'Execute a test command')
self.ctx.Irc.build_command(0, self.module_name, 'asyncio', 'Create a new asynchron task!') self.ctx.Commands.build_command(0, self.module_name, 'asyncio', 'Create a new asynchron task!')
self.ctx.Irc.build_command(1, self.module_name, 'test_level_1', 'Execute a level 1 test command') self.ctx.Commands.build_command(1, self.module_name, 'test_level_1', 'Execute a level 1 test command')
self.ctx.Irc.build_command(2, self.module_name, 'test_level_2', 'Execute a level 2 test command') self.ctx.Commands.build_command(2, self.module_name, 'test_level_2', 'Execute a level 2 test command')
self.ctx.Irc.build_command(3, self.module_name, 'test_level_3', 'Execute a level 3 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) # Build the default configuration model (Mandatory)
self._mod_config = self.ModConfModel(param_exemple1='str', param_exemple2=1) self._mod_config = self.ModConfModel(param_exemple1='str', param_exemple2=1)

View File

@@ -88,7 +88,7 @@ class Votekick(IModule):
self.VoteKickManager.VOTE_CHANNEL_DB = metadata self.VoteKickManager.VOTE_CHANNEL_DB = metadata
# Créer les nouvelles commandes du module # Créer les nouvelles commandes du module
self.ctx.Irc.build_command(1, self.module_name, 'vote', 'The kick vote module') self.ctx.Commands.build_command(1, self.module_name, 'vote', 'The kick vote module')
async def unload(self) -> None: async def unload(self) -> None:
try: try:

View File

@@ -12,7 +12,7 @@ async def timer_vote_verdict(uplink: 'Votekick', channel: str) -> None:
if not uplink.VoteKickManager.is_vote_ongoing(channel): if not uplink.VoteKickManager.is_vote_ongoing(channel):
return None return None
asyncio.sleep(60) await asyncio.sleep(60)
votec = uplink.VoteKickManager.get_vote_channel_model(channel) votec = uplink.VoteKickManager.get_vote_channel_model(channel)
if votec: if votec: