3636# Ⓒ by https://github.com/flashnuke Ⓒ................................................................................
3737# --------------------------------------------------------------------------------------------------------------------
3838
39- def require_authentication (func , * args , ** kwargs ):
39+ # ============= WRAPPERS =============
40+
41+ def require_authentication (func , * _args , ** _kwargs ):
4042 async def _impl (self , update : Update , context : ContextTypes .DEFAULT_TYPE , * args , ** kwargs ):
4143 if context .user_data .get ("authenticated" , False ) or not SysTamer .should_authenticate ():
4244 # User is authenticated, proceed with the function
@@ -48,7 +50,7 @@ async def _impl(self, update: Update, context: ContextTypes.DEFAULT_TYPE, *args,
4850 return _impl
4951
5052
51- def log_action (func , * args , ** kwargs ):
53+ def log_action (func , * _args , ** _kwargs ):
5254 async def _impl (self , update : Update , context : ContextTypes .DEFAULT_TYPE , * args , ** kwargs ):
5355 message_text = update .message .text # This will contain the full command, e.g., "/start arg1 arg2"
5456 parts = message_text .split ()
@@ -61,6 +63,18 @@ async def _impl(self, update: Update, context: ContextTypes.DEFAULT_TYPE, *args,
6163 return _impl
6264
6365
66+ def check_for_permission (func , * _args , ** _kwargs ):
67+ async def _impl (self , update : Update , context : ContextTypes .DEFAULT_TYPE , * args , ** kwargs ):
68+ try :
69+ return await func (self , update , context , * args , ** kwargs )
70+ except PermissionError :
71+ if update .effective_message :
72+ await update .effective_message .reply_text ("No permissions for this action, try running as superuser." )
73+ if update .callback_query :
74+ await SysTamer .delete_message (update , context )
75+ return _impl
76+
77+
6478class SysTamer :
6579 _BROWSE_IGNORE_PATH = ".browseignore"
6680 _PASSWORD = str ()
@@ -86,6 +100,17 @@ def __init__(self, json_conf: dict):
86100 self ._browse_path_dict = dict ()
87101 self ._ignored_paths = SysTamer .load_ignore_paths ()
88102
103+ # ============= static method helpers =============
104+
105+ @staticmethod
106+ def build_navigate_keyboard (all_buttons : List [InlineKeyboardButton ]) -> List [List [InlineKeyboardButton ]]:
107+ # separate navigation buttons from path buttons...
108+ navigation_buttons = all_buttons [- 1 ] if isinstance (all_buttons [- 1 ], list ) else [all_buttons [- 1 ]]
109+ regular_buttons = all_buttons [:- 1 ] if isinstance (all_buttons [- 1 ], list ) else all_buttons
110+ keyboard = [regular_buttons [i :i + 2 ] for i in range (0 , len (regular_buttons ), 2 )]
111+ keyboard .append (navigation_buttons )
112+ return keyboard
113+
89114 @staticmethod
90115 async def delete_message (update : Update , context : ContextTypes .DEFAULT_TYPE ) -> None :
91116 try :
@@ -96,17 +121,6 @@ async def delete_message(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
96121 except telegram .error .BadRequest as e :
97122 print_error (f"Error deleting message: { e } " )
98123
99- def check_for_permission (func , * args , ** kwargs ):
100- async def _impl (self , update : Update , context : ContextTypes .DEFAULT_TYPE , * args , ** kwargs ):
101- try :
102- return await func (self , update , context , * args , ** kwargs )
103- except PermissionError :
104- if update .effective_message :
105- await update .effective_message .reply_text ("No permissions for this action, try running as superuser." )
106- if update .callback_query :
107- await SysTamer .delete_message (update , context )
108- return _impl
109-
110124 @staticmethod
111125 def get_update_username (update : Update ) -> str :
112126 return update .effective_user .username if update .effective_user .username else update .effective_user .id
@@ -171,7 +185,7 @@ def deauthenticate(context: ContextTypes.DEFAULT_TYPE):
171185
172186 @log_action
173187 @require_authentication
174- async def send_screenshot (self , update : Update , context : ContextTypes .DEFAULT_TYPE ):
188+ async def send_screenshot (self , update : Update , _context : ContextTypes .DEFAULT_TYPE ):
175189 with mss .mss () as sct :
176190 screenshot = sct .grab (sct .monitors [0 ]) # Capture the full screen
177191
@@ -191,13 +205,13 @@ async def reply_with_timeout(self, update: Update, async_reply_ptr: Callable[...
191205 try :
192206 await async_reply_ptr (* args , write_timeout = self ._timeout_duration ,
193207 connect_timeout = self ._timeout_duration , read_timeout = self ._timeout_duration , ** kwargs )
194- except telegram .error .TimedOut as exc :
208+ except telegram .error .TimedOut as _exc :
195209 await update .message .reply_text (f"Request timed out after { self ._timeout_duration } seconds." )
196210 except telegram .error .NetworkError as exc :
197211 await update .message .reply_text (f"Network error occurred: { exc } . Please try again later." )
198212
199213 @require_authentication
200- async def handle_file_upload (self , update : Update , context : ContextTypes .DEFAULT_TYPE ):
214+ async def handle_file_upload (self , update : Update , _context : ContextTypes .DEFAULT_TYPE ):
201215 if not os .path .exists (self ._uploads_dir ):
202216 os .makedirs (self ._uploads_dir )
203217 file_path = str ()
@@ -255,7 +269,7 @@ async def handle_file_upload(self, update: Update, context: ContextTypes.DEFAULT
255269
256270 @log_action
257271 @require_authentication
258- async def list_uploads (self , update : Update , context : ContextTypes .DEFAULT_TYPE ) -> None :
272+ async def list_uploads (self , update : Update , _context : ContextTypes .DEFAULT_TYPE ) -> None :
259273 try :
260274 if not os .path .exists (self ._uploads_dir ):
261275 await update .message .reply_text ("Upload directory does not exist." )
@@ -287,18 +301,13 @@ async def list_uploads(self, update: Update, context: ContextTypes.DEFAULT_TYPE)
287301
288302 @log_action
289303 @require_authentication
290- async def system_resource_monitoring (self , update : Update , context : ContextTypes .DEFAULT_TYPE ):
304+ async def system_resource_monitoring (self , update : Update , _context : ContextTypes .DEFAULT_TYPE ):
291305 cpu_usage = psutil .cpu_percent (interval = 1 )
292306 memory_info = psutil .virtual_memory ()
293307 disk_usage = psutil .disk_usage ('/' )
294308
295- response = (
296- f"CPU Usage: { cpu_usage } %\n "
297- f"Memory Usage: { memory_info .percent } % ({ memory_info .used / (1024 ** 3 ):.2f} GB used of { memory_info .total / (1024 ** 3 ):.2f} GB)\n "
298- f"Disk Usage: { disk_usage .percent } % ({ disk_usage .used / (1024 ** 3 ):.2f} GB used of { disk_usage .total / (1024 ** 3 ):.2f} GB)"
299- )
300-
301- await update .message .reply_text (response )
309+ await update .message .reply_text (generate_machine_stats_msg ("MachineStats" , cpu_usage , memory_info , disk_usage ),
310+ parse_mode = "MarkdownV2" )
302311
303312 @log_action
304313 @require_authentication
@@ -319,7 +328,8 @@ async def list_processes(self, update: Update, context: ContextTypes.DEFAULT_TYP
319328 except (psutil .NoSuchProcess , psutil .AccessDenied , psutil .ZombieProcess ):
320329 continue
321330
322- table_chunks = generate_proc_dict_msg (f"Processes:{ len (processes )} ,Filters:{ context .args if context .args else None } " , processes )
331+ table_chunks = generate_proc_stats_msg (f"Processes:{ len (processes )} ,"
332+ f"Filters:{ context .args if context .args else None } " , processes )
323333 for chunk in table_chunks :
324334 await update .message .reply_text (f"```{ chunk } ```" , parse_mode = "MarkdownV2" )
325335
@@ -339,16 +349,13 @@ async def kill_process(self, update: Update, context: ContextTypes.DEFAULT_TYPE)
339349 await update .message .reply_text ("Invalid process ID or process does not exist." )
340350
341351 @log_action
342- async def start (self , update : Update , context : ContextTypes .DEFAULT_TYPE ):
343- welcome_message = TG_BANNER + "\n \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\n " \
344- "by [@flashnuke](https://github.com/flashnuke/SysTamer)" \
345- "\n \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\n " \
346- + generate_cmd_dict_msg ("Commands" , COMMANDS_DICT )
352+ async def start (self , update : Update , _context : ContextTypes .DEFAULT_TYPE ):
353+ welcome_message = TG_BANNER + START_INTRO + generate_cmd_dict_msg ("Commands" , COMMANDS_DICT )
347354 await update .message .reply_text (welcome_message , parse_mode = 'MarkdownV2' , disable_web_page_preview = True )
348355
349356 @log_action
350357 @require_authentication
351- async def upload_info (self , update : Update , context : ContextTypes .DEFAULT_TYPE ):
358+ async def upload_info (self , update : Update , _context : ContextTypes .DEFAULT_TYPE ):
352359 upload_message = (
353360 f"Simply send a file, and it will be saved to -> { self ._uploads_dir } "
354361 )
@@ -367,29 +374,29 @@ def list_files_and_directories(self, path: str):
367374 entry_hashed = hashlib .md5 (full_path .encode ()).hexdigest ()
368375 self ._browse_path_dict [entry_hashed ] = full_path
369376
370- # Ensure that it's a directory or file and not something like NTUSER.DAT
371377 if os .path .isdir (full_path ):
372378 buttons .append (InlineKeyboardButton (entry + '/' , callback_data = f"cd { entry_hashed } " ))
373379 else :
374380 buttons .append (InlineKeyboardButton (entry , callback_data = f"file { entry_hashed } " ))
375381
376- # Add "Back" button to navigate to the parent directory if not at the root
377- # if path != str(Path.home()): # If we are not in the home directory
378- parent_directory = os .path .dirname (path ) # Get parent directory
379- if os .path .isdir (parent_directory ): # Ensure the parent directory is valid
382+ parent_directory = os .path .dirname (path )
383+ if os .path .isdir (parent_directory ):
380384 parent_hashed = hashlib .md5 (parent_directory .encode ()).hexdigest ()
381385 self ._browse_path_dict [parent_hashed ] = parent_directory
382- buttons .append (InlineKeyboardButton ("⬅️ Back" , callback_data = f"cd { parent_hashed } " ))
383- buttons .append (InlineKeyboardButton ("❌️ Close" , callback_data = f"action close" ))
386+ buttons .append ([InlineKeyboardButton ("⬅️ Back" , callback_data = f"cd { parent_hashed } " ),
387+ InlineKeyboardButton ("❌️ Close" , callback_data = f"action close" )])
388+ else :
389+ buttons .append ([InlineKeyboardButton ("❌️ Close" , callback_data = f"action close" )])
384390 return buttons
385391
386392 @log_action
387393 @check_for_permission
388394 @require_authentication
389- async def browse (self , update : Update , context : ContextTypes .DEFAULT_TYPE ):
390- path = str (Path .home ()) # Start from the home directory
391- buttons = self .list_files_and_directories (path )
392- keyboard = [buttons [i :i + 2 ] for i in range (0 , len (buttons ), 2 )] # Group buttons in rows
395+ async def browse (self , update : Update , _context : ContextTypes .DEFAULT_TYPE ):
396+ path = str (Path .home ())
397+ all_buttons = self .list_files_and_directories (path )
398+ keyboard = self .build_navigate_keyboard (all_buttons )
399+
393400 reply_markup = InlineKeyboardMarkup (keyboard )
394401 await update .message .reply_text ('Choose a directory or file:' , reply_markup = reply_markup )
395402
@@ -399,15 +406,18 @@ async def handle_navigation(self, update: Update, context: ContextTypes.DEFAULT_
399406 query = update .callback_query
400407 data = query .data .split (' ' , 1 )
401408 command = data [0 ] # The command is the first part (e.g., "cd", "file", "action")
402- print_cmd (f"user { SysTamer .get_update_username (update )} \t |\t handle_navigation received cmd -> { ' ' .join (data )} " + ('\t |\t (' + self ._browse_path_dict .get (data [1 ]) + ')' if len (data ) >= 1 and data [1 ] in self ._browse_path_dict else '' ))
409+ print_cmd (f"user { SysTamer .get_update_username (update )} \t |\t "
410+ f"handle_navigation received cmd -> { ' ' .join (data )} " +
411+ ('\t |\t (' + self ._browse_path_dict .get (data [1 ]) + ')'
412+ if len (data ) >= 1 and data [1 ] in self ._browse_path_dict else '' ))
403413
404414 if command == "cd" : # Handle directory navigation
405415 hashed_path = data [1 ]
406416 path = self ._browse_path_dict .get (hashed_path )
407417
408418 if path and os .path .isdir (path ): # Ensure the path is a valid directory
409- buttons = self .list_files_and_directories (path )
410- keyboard = [ buttons [ i : i + 2 ] for i in range ( 0 , len ( buttons ), 2 )]
419+ all_buttons = self .list_files_and_directories (path )
420+ keyboard = self . build_navigate_keyboard ( all_buttons )
411421 reply_markup = InlineKeyboardMarkup (keyboard )
412422 await query .edit_message_text (text = f'Navigating to: { path } ' , reply_markup = reply_markup )
413423 else :
@@ -496,7 +506,7 @@ def _build_app(self) -> telegram.ext.Application:
496506
497507 return application
498508
499- def _error_handler (self , update : object , context : telegram .ext .CallbackContext ):
509+ def _error_handler (self , _update : object , context : telegram .ext .CallbackContext ):
500510 try :
501511 raise context .error
502512 except Exception as exc :
0 commit comments