First version to merge

This commit is contained in:
adator
2025-08-21 00:59:13 +02:00
parent 0a4e185fe8
commit 06fc6c4d82
8 changed files with 197 additions and 108 deletions

View File

@@ -452,6 +452,39 @@ class Base:
except AssertionError as ae: except AssertionError as ae:
self.logs.error(f'{ae}') self.logs.error(f'{ae}')
def is_thread_alive(self, thread_name: str) -> bool:
"""Check if the thread is still running! using the is_alive method of Threads.
Args:
thread_name (str): The thread name
Returns:
bool: True if is alive
"""
for thread in self.running_threads:
if thread.name.lower() == thread_name.lower():
if thread.is_alive():
return True
else:
return False
return False
def is_thread_exist(self, thread_name: str) -> bool:
"""Check if the thread exist in the local var (running_threads)
Args:
thread_name (str): The thread name
Returns:
bool: True if the thread exist
"""
for thread in self.running_threads:
if thread.name.lower() == thread_name.lower():
return True
return False
def thread_count(self, thread_name: str) -> int: def thread_count(self, thread_name: str) -> int:
"""This method return the number of existing threads """This method return the number of existing threads
currently running or not running currently running or not running

View File

@@ -1,6 +1,7 @@
''' '''
Main utils library. Main utils library.
''' '''
import gc
from pathlib import Path from pathlib import Path
from re import sub from re import sub
from typing import Literal, Optional, Any from typing import Literal, Optional, Any
@@ -50,6 +51,25 @@ def get_datetime() -> datetime:
""" """
return datetime.now() return datetime.now()
def run_python_garbage_collector() -> int:
"""Run Python garbage collector
Returns:
int: The number of unreachable objects is returned.
"""
return gc.collect()
def get_number_gc_objects(your_object_to_count: Optional[Any] = None) -> int:
"""Get The number of objects tracked by the collector (excluding the list returned).
Returns:
int: Number of tracked objects by the collector
"""
if your_object_to_count is None:
return len(gc.get_objects())
return sum(1 for obj in gc.get_objects() if isinstance(obj, your_object_to_count))
def generate_random_string(lenght: int) -> str: def generate_random_string(lenght: int) -> str:
"""Retourn une chaîne aléatoire en fonction de la longueur spécifiée. """Retourn une chaîne aléatoire en fonction de la longueur spécifiée.

View File

@@ -5,6 +5,7 @@ from core import installation
# Requierements : # # Requierements : #
# Python3.10 or higher # # Python3.10 or higher #
# SQLAlchemy, requests, psutil # # SQLAlchemy, requests, psutil #
# unrealircd-rpc-py #
# UnrealIRCD 6.2.2 or higher # # UnrealIRCD 6.2.2 or higher #
############################################# #############################################

View File

@@ -175,9 +175,9 @@ class Clone:
if len(cmd) == 1: if len(cmd) == 1:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone connect NUMBER GROUP_NAME INTERVAL") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone connect NUMBER GROUP_NAME INTERVAL")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone kill [all | nickname]") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone kill [all | group_name | nickname]")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone join [all | nickname] #channel") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone join [all | group_name | nickname] #channel")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone part [all | nickname] #channel") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone part [all | group_name | nickname] #channel")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone list") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"/msg {dnickname} clone list")
return None return None
@@ -296,11 +296,31 @@ class Clone:
case 'list': case 'list':
try: try:
clone_count = len(self.Clone.UID_CLONE_DB) # Syntax. /msg defender clone list <group_name>
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f">> Number of connected clones: {clone_count}") header = f" {'Nickname':<12}| {'Real name':<25}| {'Group name':<15}| {'Connected':<35}"
for clone_name in self.Clone.UID_CLONE_DB: line = "-"*67
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=header)
msg=f">> Nickname: {clone_name.nickname} | Username: {clone_name.username} | Realname: {clone_name.realname} | Vhost: {clone_name.vhost} | UID: {clone_name.uid} | Group: {clone_name.group} | Connected: {clone_name.connected}") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" {line}")
group_name = cmd[2] if len(cmd) > 2 else None
if group_name is None:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Number of connected clones: {len(self.Clone.UID_CLONE_DB)}")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" {line}")
for clone_name in self.Clone.UID_CLONE_DB:
self.Protocol.send_notice(
nick_from=dnickname,
nick_to=fromuser,
msg=f" {clone_name.nickname:<12}| {clone_name.realname:<25}| {clone_name.group:<15}| {clone_name.connected:<35}")
else:
if not self.Clone.group_exists(group_name):
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg="This Group name doesn't exist!")
return None
clones = self.Clone.get_clones_from_groupname(group_name)
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" Number of connected clones: {len(clones)}")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f" {line}")
for clone in clones:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser,
msg=f" {clone.nickname:<12}| {clone.realname:<25}| {clone.group:<15}| {clone.connected:<35}")
except Exception as err: except Exception as err:
self.Logs.error(f'{err}') self.Logs.error(f'{err}')

View File

@@ -1,6 +1,7 @@
import gc
import logging import logging
import asyncio import asyncio
import mods.jsonrpc.utils as utils
import mods.jsonrpc.threads as thds
from time import sleep from time import sleep
from types import SimpleNamespace from types import SimpleNamespace
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
@@ -36,6 +37,9 @@ class Jsonrpc():
# Add Base object to the module (Mandatory) # Add Base object to the module (Mandatory)
self.Base = ircInstance.Base self.Base = ircInstance.Base
# Add Main Utils (Mandatory)
self.MainUtils = ircInstance.Utils
# Add logs object to the module (Mandatory) # Add logs object to the module (Mandatory)
self.Logs = ircInstance.Loader.Logs self.Logs = ircInstance.Loader.Logs
@@ -48,8 +52,14 @@ class Jsonrpc():
# Is RPC Active? # Is RPC Active?
self.is_streaming = False self.is_streaming = False
# Module Utils
self.Utils = utils
# Module threads
self.Threads = thds
# Run Garbage collector. # Run Garbage collector.
self.Base.create_timer(10, gc.collect) self.Base.create_timer(10, self.MainUtils.run_python_garbage_collector)
# Create module commands (Mandatory) # Create module commands (Mandatory)
self.Irc.build_command(1, self.module_name, 'jsonrpc', 'Activate the JSON RPC Live connection [ON|OFF]') self.Irc.build_command(1, self.module_name, 'jsonrpc', 'Activate the JSON RPC Live connection [ON|OFF]')
@@ -62,9 +72,6 @@ class Jsonrpc():
# Log the module # Log the module
self.Logs.debug(f'Module {self.module_name} loaded ...') self.Logs.debug(f'Module {self.module_name} loaded ...')
def compter_instances(self, cls) -> int:
return sum(1 for obj in gc.get_objects() if isinstance(obj, cls))
def __init_module(self) -> None: def __init_module(self) -> None:
logging.getLogger('websockets').setLevel(logging.WARNING) logging.getLogger('websockets').setLevel(logging.WARNING)
@@ -83,8 +90,7 @@ class Jsonrpc():
username=self.Config.JSONRPC_USER, username=self.Config.JSONRPC_USER,
password=self.Config.JSONRPC_PASSWORD, password=self.Config.JSONRPC_PASSWORD,
callback_object_instance=self, callback_object_instance=self,
callback_method_or_function_name='callback_sent_to_irc', callback_method_or_function_name='callback_sent_to_irc'
debug_level=10
) )
if self.UnrealIrcdRpcLive.get_error.code != 0: if self.UnrealIrcdRpcLive.get_error.code != 0:
@@ -113,7 +119,7 @@ class Jsonrpc():
raise Exception(f"[JSONRPC ERROR] {self.Rpc.get_error.message}") raise Exception(f"[JSONRPC ERROR] {self.Rpc.get_error.message}")
if self.ModConfig.jsonrpc == 1: if self.ModConfig.jsonrpc == 1:
self.Base.create_thread(self.thread_start_jsonrpc, run_once=True) self.Base.create_thread(func=self.Threads.thread_subscribe, func_args=(self, ), run_once=True)
return None return None
@@ -181,59 +187,6 @@ class Jsonrpc():
return None return None
def thread_start_jsonrpc(self):
response: dict[str, dict] = {}
if self.UnrealIrcdRpcLive.get_error.code == 0:
self.is_streaming = True
response = asyncio.run(self.UnrealIrcdRpcLive.subscribe(["all"]))
else:
self.Protocol.send_priv_msg(
nick_from=self.Config.SERVICE_NICKNAME,
msg=f"[{self.Config.COLORS.red}ERROR{self.Config.COLORS.nogc}] {self.UnrealIrcdRpcLive.get_error.message}",
channel=self.Config.SERVICE_CHANLOG
)
if response is None:
return
code = response.get('error', {}).get('code', 0)
message = response.get('error', {}).get('message', None)
if code == 0:
self.Protocol.send_priv_msg(
nick_from=self.Config.SERVICE_NICKNAME,
msg=f"[{self.Config.COLORS.green}JSONRPC{self.Config.COLORS.nogc}] Stream is OFF",
channel=self.Config.SERVICE_CHANLOG
)
else:
self.Protocol.send_priv_msg(
nick_from=self.Config.SERVICE_NICKNAME,
msg=f"[{self.Config.COLORS.red}JSONRPC{self.Config.COLORS.nogc}] Stream has crashed! {code} - {message}",
channel=self.Config.SERVICE_CHANLOG
)
def thread_stop_jsonrpc(self) -> None:
response: dict[str, dict] = asyncio.run(self.UnrealIrcdRpcLive.unsubscribe())
self.Logs.debug("[JSONRPC UNLOAD] Unsubscribe from the stream!")
self.is_streaming = False
self.__update_configuration('jsonrpc', 0)
if response is None:
print(f"... Response is None ?! {response}")
return None
code = response.get('error', {}).get('code', 0)
message = response.get('error', {}).get('message', None)
if code != 0:
self.Protocol.send_priv_msg(
nick_from=self.Config.SERVICE_NICKNAME,
msg=f"[{self.Config.COLORS.red}JSONRPC ERROR{self.Config.COLORS.nogc}] {message} ({code})",
channel=self.Config.SERVICE_CHANLOG
)
def __load_module_configuration(self) -> None: def __load_module_configuration(self) -> None:
"""### Load Module Configuration """### Load Module Configuration
""" """
@@ -249,7 +202,7 @@ class Jsonrpc():
except TypeError as te: except TypeError as te:
self.Logs.critical(te) self.Logs.critical(te)
def __update_configuration(self, param_key: str, param_value: str): def update_configuration(self, param_key: str, param_value: str) -> None:
"""Update the local and core configuration """Update the local and core configuration
Args: Args:
@@ -265,8 +218,8 @@ class Jsonrpc():
msg=f"[{self.Config.COLORS.green}JSONRPC INFO{self.Config.COLORS.nogc}] Shutting down RPC system!", msg=f"[{self.Config.COLORS.green}JSONRPC INFO{self.Config.COLORS.nogc}] Shutting down RPC system!",
channel=self.Config.SERVICE_CHANLOG channel=self.Config.SERVICE_CHANLOG
) )
self.Base.create_thread(func=self.thread_stop_jsonrpc, run_once=True) self.Base.create_thread(func=self.Threads.thread_unsubscribe, func_args=(self, ), run_once=True)
self.__update_configuration('jsonrpc', 0) self.update_configuration('jsonrpc', 0)
self.Logs.debug(f"Unloading {self.module_name}") self.Logs.debug(f"Unloading {self.module_name}")
return None return None
@@ -286,54 +239,42 @@ class Jsonrpc():
case 'jsonrpc': case 'jsonrpc':
try: try:
option = str(cmd[1]).lower() if len(cmd) < 2:
if len(command) == 1:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'/msg {dnickname} jsonrpc on') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'/msg {dnickname} jsonrpc on')
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'/msg {dnickname} jsonrpc off') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'/msg {dnickname} jsonrpc off')
return None
option = str(cmd[1]).lower()
match option: match option:
case 'on': case 'on':
thread_name = 'thread_subscribe'
if self.Base.is_thread_alive(thread_name):
self.Protocol.send_priv_msg(nick_from=dnickname, channel=dchannel, msg=f"The Subscription is running")
return None
elif self.Base.is_thread_exist(thread_name):
self.Protocol.send_priv_msg(
nick_from=dnickname, channel=dchannel,
msg=f"The subscription is not running, wait untill the process will be cleaned up"
)
return None
# for logger_name, logger in logging.root.manager.loggerDict.items(): self.Base.create_thread(func=self.Threads.thread_subscribe, func_args=(self, ), run_once=True)
# if isinstance(logger, logging.Logger): self.update_configuration('jsonrpc', 1)
# self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"{logger_name} - {logger.level}")
for thread in self.Base.running_threads:
if thread.name == 'thread_start_jsonrpc':
if thread.is_alive():
self.Protocol.send_priv_msg(
nick_from=self.Config.SERVICE_NICKNAME,
msg=f"Thread {thread.name} is running",
channel=dchannel
)
else:
self.Protocol.send_priv_msg(
nick_from=self.Config.SERVICE_NICKNAME,
msg=f"Thread {thread.name} is not running, wait untill the process will be cleaned up",
channel=dchannel
)
self.Base.create_thread(self.thread_start_jsonrpc, run_once=True)
self.__update_configuration('jsonrpc', 1)
case 'off': case 'off':
self.Base.create_thread(func=self.thread_stop_jsonrpc, run_once=True) self.Base.create_thread(func=self.Threads.thread_unsubscribe, func_args=(self, ), run_once=True)
self.__update_configuration('jsonrpc', 0) self.update_configuration('jsonrpc', 0)
except IndexError as ie: except IndexError as ie:
self.Logs.error(ie) self.Logs.error(ie)
case 'jruser': case 'jruser':
try: try:
option = str(cmd[1]).lower() if len(cmd) < 2:
if len(command) == 1:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'/msg {dnickname} jruser get nickname') self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f'/msg {dnickname} jruser get nickname')
option = str(cmd[1]).lower()
match option: match option:
case 'get': case 'get':
nickname = str(cmd[2]) nickname = str(cmd[2])
uid_to_get = self.User.get_uid(nickname) uid_to_get = self.User.get_uid(nickname)
@@ -378,10 +319,10 @@ class Jsonrpc():
case 'jrinstances': case 'jrinstances':
try: try:
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"GC Collect: {gc.collect()}") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"GC Collect: {self.MainUtils.run_python_garbage_collector()}")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance LiveWebsock: {self.compter_instances(LiveWebsocket)}") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance LiveWebsock: {self.MainUtils.get_number_gc_objects(LiveWebsocket)}")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance LiveUnixSocket: {self.compter_instances(LiveUnixSocket)}") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance LiveUnixSocket: {self.MainUtils.get_number_gc_objects(LiveUnixSocket)}")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance Loader: {self.compter_instances(Loader)}") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre d'instance Loader: {self.MainUtils.get_number_gc_objects(Loader)}")
self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre de toute les instances: {len(gc.get_objects())}") self.Protocol.send_notice(nick_from=dnickname, nick_to=fromuser, msg=f"Nombre de toute les instances: {self.MainUtils.get_number_gc_objects()}")
except Exception as err: except Exception as err:
self.Logs.error(f"Unknown Error: {err}") self.Logs.error(f"Unknown Error: {err}")

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

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

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

14
requirements.txt Normal file
View File

@@ -0,0 +1,14 @@
certifi==2024.12.14
charset-normalizer==3.4.1
Faker==33.1.2
greenlet==3.1.1
idna==3.10
psutil==6.1.1
python-dateutil==2.9.0.post0
requests==2.32.3
six==1.17.0
SQLAlchemy==2.0.36
typing_extensions==4.12.2
unrealircd-rpc-py==2.0.4
urllib3==2.3.0
websockets==14.1