diff --git a/CODE/Logicytics.py b/CODE/Logicytics.py index e80dfb8..382d3f0 100644 --- a/CODE/Logicytics.py +++ b/CODE/Logicytics.py @@ -204,6 +204,7 @@ zip_values = Zip().and_hash(".", "CODE", action) if isinstance(zip_values, str): + # If error, log it log.error(zip_values) else: zip_loc, hash_loc = zip_values @@ -222,6 +223,7 @@ log.info("Rebooting...") subprocess.call("shutdown /r /t 3", shell=False) if sub_action == "webhook": + # Implement this in future log.warning("This feature is not fully implemented yet! Sorry") log.info("Exiting...") diff --git a/CODE/__lib_class.py b/CODE/__lib_class.py index 78c3423..3b7d976 100644 --- a/CODE/__lib_class.py +++ b/CODE/__lib_class.py @@ -13,7 +13,7 @@ class Actions: @staticmethod - def open_file(file: str) -> None: + def open_file(file: str): """ Opens a specified file using its default application in a cross-platform manner. Args: @@ -44,6 +44,18 @@ def run_command(command: str) -> str: @staticmethod def __parse_arguments() -> tuple[argparse.Namespace, argparse.ArgumentParser]: + """ + A static method used to parse command-line arguments for the Logicytics application. + + It defines various flags that can be used to customize the behavior of the application, + including options for running in default or minimal mode, unzipping extra files, + backing up or restoring data, updating from GitHub, and more. + + The method returns a tuple containing the parsed arguments and the argument parser object. + + Returns: + tuple[argparse.Namespace, argparse.ArgumentParser]: A tuple containing the parsed arguments and the argument parser object. + """ # Define the argument parser parser = argparse.ArgumentParser( description="Logicytics, The most powerful tool for system data analysis." @@ -128,6 +140,15 @@ def __parse_arguments() -> tuple[argparse.Namespace, argparse.ArgumentParser]: @staticmethod def __exclusivity(args: argparse.Namespace) -> bool: + """ + Checks if exclusive flags are used in the provided arguments. + + Args: + args (argparse.Namespace): The arguments to be checked. + + Returns: + bool: True if exclusive flags are used, False otherwise. + """ special_flag_used = False if args.reboot or args.shutdown or args.webhook: if not ( @@ -140,6 +161,15 @@ def __exclusivity(args: argparse.Namespace) -> bool: @staticmethod def __set_flags(args: argparse.Namespace) -> tuple[str, ...]: + """ + Sets flags based on the provided arguments. + + Args: + args (argparse.Namespace): The arguments to be checked for flags. + + Returns: + tuple[str, ...]: A tuple of flag names that are set to True. + """ Flags = {key: getattr(args, key) for key in vars(args)} true_keys = [] for key, value in Flags.items(): @@ -150,6 +180,11 @@ def __set_flags(args: argparse.Namespace) -> tuple[str, ...]: return tuple(true_keys) def flags(self) -> tuple[str, ...] | argparse.ArgumentParser: + """ + Handles the parsing and validation of command-line flags. + + Returns either a tuple of used flag names or an ArgumentParser instance. + """ args, parser = self.__parse_arguments() special_flag_used = self.__exclusivity(args) @@ -238,6 +273,12 @@ def check_current_files(directory: str) -> list: @staticmethod def mkdir(): + """ + Creates the necessary directories for storing logs, backups, and data. + + Returns: + None + """ os.makedirs("../ACCESS/LOGS/", exist_ok=True) os.makedirs("../ACCESS/LOGS/DEBUG", exist_ok=True) os.makedirs("../ACCESS/BACKUP/", exist_ok=True) @@ -284,6 +325,17 @@ def uac(self) -> bool: @staticmethod def sys_internal_zip(): + """ + Extracts the SysInternal_Suite zip file if it exists and is not ignored. + + This function checks if the SysInternal_Suite zip file exists and if it is not ignored. + If the zip file exists and is not ignored, + it extracts its contents to the SysInternal_Suite directory. + If the zip file is ignored, it prints a message indicating that it is skipping the extraction. + + Raises: + Exception: If there is an error during the extraction process. The error message is printed to the console and the program exits. + """ try: ignore_file = os.path.exists("SysInternal_Suite/.sys.ignore") zip_file = os.path.exists("SysInternal_Suite/SysInternal_Suite.zip") @@ -323,7 +375,7 @@ def get_files(directory: str, file_list: list) -> list: file_list.append(filename) return file_list - def file(self, execution_list: list, Index: int) -> None: + def file(self, execution_list: list, Index: int): # IT IS USED, DO NOT REMOVE """ Executes a file from the execution list at the specified index. @@ -336,7 +388,7 @@ def file(self, execution_list: list, Index: int) -> None: self.execute_script(execution_list[Index]) Log().info(f"{execution_list[Index]} executed") - def execute_script(self, script: str) -> None: + def execute_script(self, script: str): """ Executes a script file and handles its output based on the file extension. Parameters: @@ -353,7 +405,7 @@ def execute_script(self, script: str) -> None: self.__run_other_script(script) @staticmethod - def __unblock_ps1_script(script: str) -> None: + def __unblock_ps1_script(script: str): """ Unblocks and runs a PowerShell (.ps1) script. Parameters: @@ -369,7 +421,7 @@ def __unblock_ps1_script(script: str) -> None: Log().critical(f"Failed to unblock script: {err}", "_L", "G", "E") @staticmethod - def __run_python_script(script: str) -> None: + def __run_python_script(script: str): """ Runs a Python (.py) script. Parameters: @@ -383,7 +435,7 @@ def __run_python_script(script: str) -> None: print(result.decode()) @staticmethod - def __run_other_script(script: str) -> None: + def __run_other_script(script: str): """ Runs a script with other extensions and logs output based on its content. Parameters: diff --git a/CODE/__lib_log.py b/CODE/__lib_log.py index bd5b0b1..5a2eda7 100644 --- a/CODE/__lib_log.py +++ b/CODE/__lib_log.py @@ -207,7 +207,7 @@ def error(self, message): f"[{self.__timestamp()}] > ERROR: | {self.__pad_message(str(message))}\n" ) - def critical(self, message, FILECODE, ERRCODE, FUNCODE): + def critical(self, message, FILECODE: str, ERRCODE: str, FUNCODE: str): """ Logs a critical message to the error log File. diff --git a/CODE/__wrapper__.py b/CODE/__wrapper__.py index d66bb4b..d2ef06d 100644 --- a/CODE/__wrapper__.py +++ b/CODE/__wrapper__.py @@ -1,3 +1,6 @@ +# Optional wrapper that you can build manually, if you want to ignore the restrictions of python in the +# main Logicytics file. This wrapper is not compulsory, but it is recommended to use it to build the exe. + # Special wrapper that the exe Logicytics is made out of, not compulsory, just to ignore some restrictions of python. # If you modify please run this command: diff --git a/CODE/_debug.py b/CODE/_debug.py index f1edd42..4c261b3 100644 --- a/CODE/_debug.py +++ b/CODE/_debug.py @@ -17,7 +17,14 @@ class HealthCheck: - def get_config_data(self) -> bool | tuple[tuple[str, str, str], tuple[str, str, str]]: + def get_online_config(self) -> bool | tuple[tuple[str, str, str], tuple[str, str, str]]: + """ + Retrieves configuration data from a remote repository and compares it with the local configuration. + + Returns: + bool: False if a connection error occurs, otherwise a tuple containing version check and file check results. + tuple[tuple[str, str, str], tuple[str, str, str]]: A tuple containing version check and file check results. + """ try: url = "https://raw.githubusercontent.com/DefinetlyNotAI/Logicytics/main/CODE/config.json" config = json.loads(requests.get(url).text) @@ -30,7 +37,18 @@ def get_config_data(self) -> bool | tuple[tuple[str, str, str], tuple[str, str, return version_check, file_check @staticmethod - def __compare_versions(local_version, remote_version) -> tuple[str, str, str]: + def __compare_versions(local_version: str, remote_version: str) -> tuple[str, str, str]: + """ + Compares the local version with the remote version and returns a tuple containing a comparison result message, + a version information message, and a severity level. + + Args: + local_version (str): The version number of the local system. + remote_version (str): The version number of the remote repository. + + Returns: + tuple[str, str, str]: A tuple containing a comparison result message, a version information message, and a severity level. + """ if local_version == remote_version: return "Version is up to date.", f"Your Version: {local_version}", "INFO" elif local_version > remote_version: @@ -39,7 +57,18 @@ def __compare_versions(local_version, remote_version) -> tuple[str, str, str]: return "Version is behind the repository.", f"Your Version: {local_version}, Repository Version: {remote_version}", "ERROR" @staticmethod - def __check_files(local_files, remote_files) -> tuple[str, str, str]: + def __check_files(local_files: list, remote_files: list) -> tuple[str, str, str]: + """ + Check if all the files in the local_files list are present in the remote_files list. + + Args: + local_files (list): A list of files in the local repository. + remote_files (list): A list of files in the remote repository. + + Returns: + tuple[str, str, str]: A tuple containing the result message, a message detailing the files present or missing, + and the log level. + """ missing_files = set(remote_files) - set(local_files) if not missing_files: return "All files are present.", f"Your files: {local_files} contain all the files in the repository.", "INFO" @@ -49,7 +78,22 @@ def __check_files(local_files, remote_files) -> tuple[str, str, str]: class DebugCheck: @staticmethod - def SysInternal_Binaries(path): + def SysInternal_Binaries(path: str) -> tuple[str, str]: + """ + Checks the contents of the given path and determines the status of the SysInternal Binaries. + + Args: + path (str): The path to the directory containing the SysInternal Binaries. + + Returns: + tuple[str, str]: A tuple containing a status message and a severity level. + The status message indicates the result of the check. + The severity level is either "INFO", "WARNING", or "ERROR". + + Raises: + FileNotFoundError: If the given path does not exist. + Exception: If an unexpected error occurs during the check. + """ try: contents = os.listdir(path) log_debug.debug(contents) @@ -67,23 +111,55 @@ def SysInternal_Binaries(path): return f"An Unexpected error occurred: {e}", "ERROR" @staticmethod - def execution_policy(): + def execution_policy() -> bool: + """ + Checks the current PowerShell execution policy. + + Returns: + bool: True if the execution policy is unrestricted, False otherwise. + """ result = subprocess.run(['powershell', '-Command', 'Get-ExecutionPolicy'], capture_output=True, text=True) return result.stdout.strip().lower() == 'unrestricted' @staticmethod - def cpu_info(): + def cpu_info() -> tuple[str, str, str]: + """ + Retrieves information about the CPU. + + Returns: + tuple[str, str, str]: A tuple containing the CPU architecture, vendor ID, and model. + """ return 'CPU Architecture: ' + platform.machine(), 'CPU Vendor Id: ' + platform.system(), 'CPU Model: ' + f"{platform.release()} {platform.version()}" def debug(): + """ + Performs a series of system checks and logs the results. + + This function performs the following checks: + 1. Clears the debug log file. + 2. Checks the integrity of files by comparing local and remote configurations. + 3. Checks the status of SysInternal Binaries. + 4. Checks for admin privileges. + 5. Checks if User Account Control (UAC) is enabled. + 6. Logs the execution paths. + 7. Checks if the script is running in a virtual environment. + 8. Checks the PowerShell execution policy. + 9. Logs the Python version being used. + 10. Logs the repository path. + 11. Logs CPU information. + 12. Logs the debug configuration. + + Returns: + None + """ # Clear Debug Log if os.path.exists("../ACCESS/LOGS/DEBUG/DEBUG.LOG"): os.remove("../ACCESS/LOGS/DEBUG/DEBUG.LOG") # Check File integrity (Online) - if HealthCheck().get_config_data(): - version_tuple, file_tuple = HealthCheck().get_config_data() + if HealthCheck().get_online_config(): + version_tuple, file_tuple = HealthCheck().get_online_config() log_debug_funcs.get(version_tuple[2], log_debug.debug)("\n".join(version_tuple[0]).replace('\n', '')) log_debug_funcs.get(file_tuple[2], log_debug.debug)("\n".join(file_tuple[0]).replace('\n', '')) message, type = DebugCheck.SysInternal_Binaries("SysInternal_Suite") diff --git a/CODE/_dev.py b/CODE/_dev.py index 0e1a998..ffa155f 100644 --- a/CODE/_dev.py +++ b/CODE/_dev.py @@ -106,6 +106,17 @@ def __dev_checks(self) -> bool: return False def run_dev(self): + """ + Executes the development checks and runs the test files. + + This function performs the following steps: + 1. Creates necessary directories. + 2. Executes development checks to ensure guidelines and best practices are followed. + 3. Collects and runs all Python test files in the `../TESTS` directory, excluding `__init__.py` and `test.py`. + + Returns: + None + """ Actions().mkdir() if self.__dev_checks(): test_files = [] diff --git a/CODE/_extra.py b/CODE/_extra.py index da11087..91c9f70 100644 --- a/CODE/_extra.py +++ b/CODE/_extra.py @@ -10,7 +10,7 @@ } -def unzip(zip_path: str) -> None: +def unzip(zip_path: str): """ Unzips a given zip file to a new directory with the same name. @@ -31,7 +31,7 @@ def unzip(zip_path: str) -> None: z.extractall(path=str(output_dir)) -def menu() -> None: +def menu(): """ Displays a menu of available executable scripts in the '../EXTRA/EXTRA' directory, prompts the user to select a script, and runs the selected script using PowerShell. diff --git a/CODE/_zipper.py b/CODE/_zipper.py index 3a385df..eba27ef 100644 --- a/CODE/_zipper.py +++ b/CODE/_zipper.py @@ -7,8 +7,43 @@ class Zip: + """ + A class to handle zipping files, generating SHA256 hashes, and moving files. + + Methods: + __get_files_to_zip(path: str) -> list: + Returns a list of files to be zipped, excluding certain file types and names. + + __create_zip_file(path: str, files: list, filename: str): + Creates a zip file from the given list of files. + + __remove_files(path: str, files: list): + Removes the specified files from the given path. + + __generate_sha256_hash(filename: str) -> str: + Generates a SHA256 hash for the specified zip file. + + __write_hash_to_file(filename: str, sha256_hash: str): + Writes the SHA256 hash to a file. + + __move_files(filename: str): + Moves the zip file and its hash file to designated directories. + + and_hash(self, path: str, name: str, flag: str) -> tuple | str: + Zips files, generates a SHA256 hash, and moves the files. + """ + @staticmethod def __get_files_to_zip(path: str) -> list: + """ + Returns a list of files to be zipped, excluding certain file types and names. + + Args: + path (str): The directory path to search for files. + + Returns: + list: A list of file names to be zipped. + """ return [ f for f in os.listdir(path) @@ -18,12 +53,33 @@ def __get_files_to_zip(path: str) -> list: @staticmethod def __create_zip_file(path: str, files: list, filename: str): + """ + Creates a zip file from the given list of files. + + Args: + path (str): The directory path containing the files. + files (list): A list of file names to be zipped. + filename (str): The name of the output zip file. + + Returns: + None + """ with zipfile.ZipFile(f"{filename}.zip", "w") as zip_file: for file in files: zip_file.write(os.path.join(path, file)) @staticmethod def __remove_files(path: str, files: list): + """ + Removes the specified files from the given path. + + Args: + path (str): The directory path containing the files. + files (list): A list of file names to be removed. + + Returns: + None or str: Returns an error message if an exception occurs. + """ for file in files: try: shutil.rmtree(os.path.join(path, file)) @@ -34,21 +90,60 @@ def __remove_files(path: str, files: list): @staticmethod def __generate_sha256_hash(filename: str) -> str: + """ + Generates a SHA256 hash for the specified zip file. + + Args: + filename (str): The name of the zip file. + + Returns: + str: The SHA256 hash of the zip file. + """ with open(f"{filename}.zip", "rb") as zip_file: zip_data = zip_file.read() return hashlib.sha256(zip_data).hexdigest() @staticmethod def __write_hash_to_file(filename: str, sha256_hash: str): + """ + Writes the SHA256 hash to a file. + + Args: + filename (str): The name of the hash file. + sha256_hash (str): The SHA256 hash to be written. + + Returns: + None + """ with open(f"{filename}.hash", "w") as hash_file: hash_file.write(sha256_hash) @staticmethod def __move_files(filename: str): + """ + Moves the zip file and its hash file to designated directories. + + Args: + filename (str): The name of the files to be moved. + + Returns: + None + """ shutil.move(f"{filename}.zip", "../ACCESS/DATA/Zip") shutil.move(f"{filename}.hash", "../ACCESS/DATA/Hashes") def and_hash(self, path: str, name: str, flag: str) -> tuple | str: + """ + Zips files, generates a SHA256 hash, and moves the files. + + Args: + path (str): The directory path containing the files. + name (str): The base name for the output files. + flag (str): A flag to be included in the output file names. + + Returns: + tuple or str: A tuple containing success messages or an error message. + """ today = datetime.now() filename = f"Logicytics_{name}_{flag}_{today.strftime('%Y-%m-%d_%H-%M-%S')}" files_to_zip = self.__get_files_to_zip(path) diff --git a/CODE/driverquery+sysinfo.py b/CODE/driverquery+sysinfo.py index beacbdd..09f7ad6 100644 --- a/CODE/driverquery+sysinfo.py +++ b/CODE/driverquery+sysinfo.py @@ -11,6 +11,17 @@ def command(file: str, com: str, message: str): + """ + Executes a command and writes the output to a file. + + Args: + file (str): The name of the file to write the command output to. + com (str): The command to be executed. + message (str): A message to be logged. + + Returns: + None + """ try: output = Actions.run_command(com) open(file, "w").write(output) diff --git a/CODE/media_backup.py b/CODE/media_backup.py index 4cee8cf..d7d91ec 100644 --- a/CODE/media_backup.py +++ b/CODE/media_backup.py @@ -13,55 +13,56 @@ } -def get_default_paths(): - """Returns the default paths for photos and videos based on the Windows username.""" - username = getpass.getuser() - default_photo_path = os.path.expanduser(f"C:\\Users\\{username}\\Pictures") - default_video_path = os.path.expanduser(f"C:\\Users\\{username}\\Videos") - return [default_photo_path, default_video_path] +class Media: + @staticmethod + def __get_default_paths() -> list: + """Returns the default paths for photos and videos based on the Windows username.""" + username = getpass.getuser() + default_photo_path = os.path.expanduser(f"C:\\Users\\{username}\\Pictures") + default_video_path = os.path.expanduser(f"C:\\Users\\{username}\\Videos") + return [default_photo_path, default_video_path] + @staticmethod + def __ensure_backup_directory_exists(backup_directory: str): + """Ensures the backup directory exists; creates it if not.""" + if not os.path.exists(backup_directory): + os.makedirs(backup_directory) -def ensure_backup_directory_exists(backup_directory): - """Ensures the backup directory exists; creates it if not.""" - if not os.path.exists(backup_directory): - os.makedirs(backup_directory) + @staticmethod + def __collect_media_files(source_dirs: list) -> list: + """Collects all media files from the source directories.""" + media_files = [] + for source_dir in source_dirs: + for root, _, files in os.walk(source_dir): + for file in files: + if file.endswith((".jpg", ".jpeg", ".png", ".mp4")): + media_files.append(os.path.join(root, file)) + return media_files + @staticmethod + def __backup_files(media_files: list, backup_directory: str): + """Backs up media files to the backup directory.""" + for src_file in media_files: + dst_file = os.path.join( + backup_directory, + datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + + "_" + + os.path.basename(src_file), + ) + try: + shutil.copy2(str(src_file), str(dst_file)) + log.info(f"Copied {os.path.basename(src_file)} to {dst_file}") + except Exception as e: + log.error(f"Failed to copy {src_file}: {str(e)}") -def collect_media_files(source_dirs): - """Collects all media files from the source directories.""" - media_files = [] - for source_dir in source_dirs: - for root, _, files in os.walk(source_dir): - for file in files: - if file.endswith((".jpg", ".jpeg", ".png", ".mp4")): - media_files.append(os.path.join(root, file)) - return media_files + def backup(self): + """Backs up media files from the default Windows photo and video directories.""" + source_dirs = self.__get_default_paths() + backup_directory = "MediaBackup" + self.__ensure_backup_directory_exists(backup_directory) + media_files = self.__collect_media_files(source_dirs) + self.__backup_files(media_files, backup_directory) + log.info("Media backup script completed.") -def backup_files(media_files, backup_directory): - """Backs up media files to the backup directory.""" - for src_file in media_files: - dst_file = os.path.join( - backup_directory, - datetime.now().strftime("%Y-%m-%d_%H-%M-%S") - + "_" - + os.path.basename(src_file), - ) - try: - shutil.copy2(str(src_file), str(dst_file)) - log.info(f"Copied {os.path.basename(src_file)} to {dst_file}") - except Exception as e: - log.error(f"Failed to copy {src_file}: {str(e)}") - - -def backup_media(): - """Backs up media files from the default Windows photo and video directories.""" - source_dirs = get_default_paths() - backup_directory = "MediaBackup" - ensure_backup_directory_exists(backup_directory) - media_files = collect_media_files(source_dirs) - backup_files(media_files, backup_directory) - log.info("Media backup script completed.") - - -backup_media() +Media().backup() diff --git a/CODE/online_ip_scraper.py b/CODE/online_ip_scraper.py index 4e5bc82..912fcda 100644 --- a/CODE/online_ip_scraper.py +++ b/CODE/online_ip_scraper.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import requests from __lib_class import * @@ -11,9 +13,9 @@ } -class IP: +class Scrape: @staticmethod - def __get_my_ip(): + def __get_my_ip() -> str: """ Retrieves the user's current IP address from the ipify API. @@ -28,7 +30,7 @@ def __get_my_ip(): log.error(f"Failed to get IP: {e}") @staticmethod - def __get_location_data(ip, api_key=None): + def __get_location_data(ip: str, api_key=None) -> dict | None: """ Retrieves location data for a given IP address. @@ -50,7 +52,7 @@ def __get_location_data(ip, api_key=None): except Exception as e: log.error(f"Failed to get location data: {e}") - def scraper(self): + def ip(self): """ Scrapes the user's current IP address and its corresponding location data. @@ -67,4 +69,4 @@ def scraper(self): log.info("IP Scraper Executed") -IP().scraper() +Scrape().ip() diff --git a/CODE/sensitive_data_miner.py b/CODE/sensitive_data_miner.py index 70f3007..a969800 100644 --- a/CODE/sensitive_data_miner.py +++ b/CODE/sensitive_data_miner.py @@ -9,6 +9,7 @@ "CRITICAL": log.critical, None: log.debug, } + # List of allowed extensions allowed_extensions = [ ".png", @@ -23,12 +24,15 @@ ".text", ".docx", ".doc", + ".xls", + ".xlsx", + ".csv", ] -class Miner: +class Mine: @staticmethod - def __search_files_by_keyword(root, keyword): + def __search_files_by_keyword(root: Path, keyword: str) -> list: """ Searches for files containing the specified keyword in their names. Args: @@ -49,7 +53,7 @@ def __search_files_by_keyword(root, keyword): return matching_files @staticmethod - def __copy_file(src_file_path, dst_file_path): + def __copy_file(src_file_path: Path, dst_file_path: Path): """ Copies a file to the destination directory. Args: @@ -66,7 +70,7 @@ def __copy_file(src_file_path, dst_file_path): except Exception as e: log.error(f"Failed to copy file: {e}") - def __search_and_copy_files(self, keyword): + def __search_and_copy_files(self, keyword: str): """ Searches for files containing the specified keyword in their names and copies them to a destination directory. Args: @@ -105,4 +109,4 @@ def passwords(self): log.info("Sensitive Data Miner Completed") -Miner().passwords() +Mine().passwords() diff --git a/CODE/sys_internal.py b/CODE/sys_internal.py index 165a910..079b25c 100644 --- a/CODE/sys_internal.py +++ b/CODE/sys_internal.py @@ -7,6 +7,7 @@ "CRITICAL": log.critical, None: log.debug, } + sys_internal_executables = [ "psfile.exe", "PsGetsid.exe", diff --git a/CODE/todo b/CODE/todo new file mode 100644 index 0000000..06a8a3e --- /dev/null +++ b/CODE/todo @@ -0,0 +1,4 @@ +black . +--dev +Then update readme, wiki and contributing guidelines +Finally after all, create a pr, and a release, then the release should contain the commit history etc \ No newline at end of file