From 7901080d6e65ced24b83dd5aaa242b535b7cb24b Mon Sep 17 00:00:00 2001 From: Vili Date: Wed, 3 Jun 2026 13:06:51 +0300 Subject: [PATCH] Added args to run tool(s) without the GUI Closes #35 --- README.md | 24 +++- h4xtools.py | 346 +++++++++++++++++++++++++++++++++++++++++++++- helper/handles.py | 63 ++++++--- helper/printer.py | 59 +++++++- helper/timer.py | 4 +- 5 files changed, 464 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 10944dd..44016d9 100644 --- a/README.md +++ b/README.md @@ -70,11 +70,31 @@ Dependencies can be installed manually with: pip install -r requirements.txt ``` -### Debug mode +### Command-line mode -Launch with the `--debug` flag to enable verbose output: +Run `python h4xtools.py --help` to list all direct-run options. If no tool flag is provided, H4X-Tools opens the interactive menu. + +Examples: + +```sh +python h4xtools.py --igscrape some_username --verbose +python h4xtools.py --username some_handle --debug +python h4xtools.py --ip example.com --whois example.com +python h4xtools.py --port-scanner 192.168.1.10 --port-range 1000 +``` + +Tool flags can usually be passed without a value to prompt only for the missing target: + +```sh +python h4xtools.py --igscrape --verbose +``` + +### Debug and verbose mode + +Launch with `-v` / `--verbose` for verbose output or `--debug` for debug output: ```sh +python h4xtools.py --verbose python h4xtools.py --debug ``` diff --git a/h4xtools.py b/h4xtools.py index 3f31455..5980463 100755 --- a/h4xtools.py +++ b/h4xtools.py @@ -17,14 +17,16 @@ along with this program. If not, see . """ +import argparse import socket import time +from typing import Any from colorama import Fore, Style from helper import config, handles, printer -VERSION = "26" +VERSION = "26.1" def _internet_check() -> None: @@ -217,13 +219,349 @@ def _print_menu() -> None: } -def main() -> None: +def _add_optional_tool_arg( + parser: Any, + *flags: str, + dest: str, + metavar: str, + help_text: str, +) -> None: + """ + Add a tool flag that may optionally receive a target value. + + If the user passes only the flag, the corresponding handler will prompt for + the missing value. Example: ``--igscrape`` prompts, while + ``--igscrape some_user`` uses ``some_user`` directly. + """ + parser.add_argument( + *flags, + dest=dest, + nargs="?", + const=True, + default=None, + metavar=metavar, + help=help_text, + ) + + +def _build_parser() -> argparse.ArgumentParser: + """ + Build the H4X-Tools command-line parser. + + :return: Configured argument parser. + """ + parser = argparse.ArgumentParser( + prog="h4xtools", + description="H4X-Tools - modular OSINT, reconnaissance, and scraping toolkit.", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + + parser.add_argument("--version", action="version", version=f"H4X-Tools v{VERSION}") + parser.add_argument( + "-v", + "--verbose", + action="count", + default=0, + help="Enable verbose output. Repeat for more verbosity.", + ) + parser.add_argument( + "--debug", + action="store_true", + help="Enable debug output. Implies verbose output.", + ) + parser.add_argument( + "--list-tools", + action="store_true", + help="Print the full tool help and exit.", + ) + parser.add_argument( + "--no-internet-check", + action="store_true", + help="Skip the startup internet connectivity check.", + ) + + tool_group = parser.add_argument_group( + "Tool shortcuts", + "Pass one or more tool flags to run them directly without opening the menu. " + "Flags with optional values will prompt if the value is omitted.", + ) + + _add_optional_tool_arg( + tool_group, + "--igscrape", + "--ig-scrape", + "--instagram", + "--ig", + dest="ig_scrape", + metavar="USERNAME", + help_text="Run Instagram scrape for USERNAME.", + ) + tool_group.add_argument( + "--webrecon", + "--web-recon", + "--web-reconnaissance", + action="store_true", + help="Run the interactive deep web search workflow.", + ) + _add_optional_tool_arg( + tool_group, + "--phone", + "--phone-lookup", + dest="phone_lookup", + metavar="NUMBER", + help_text="Run phone lookup for NUMBER.", + ) + _add_optional_tool_arg( + tool_group, + "--ip", + "--ip-lookup", + dest="ip_lookup", + metavar="IP_OR_DOMAIN", + help_text="Run IP/domain lookup.", + ) + _add_optional_tool_arg( + tool_group, + "--username", + "--username-search", + dest="username_search", + metavar="USERNAME", + help_text="Run Maigret username search.", + ) + _add_optional_tool_arg( + tool_group, + "--email", + "--email-search", + dest="email_search", + metavar="EMAIL", + help_text="Run email search.", + ) + _add_optional_tool_arg( + tool_group, + "--leak", + "--leak-search", + dest="leak_search", + metavar="TARGET", + help_text="Run leak search for an email, domain, or username.", + ) + _add_optional_tool_arg( + tool_group, + "--port", + "--port-scanner", + dest="port_scanner", + metavar="IP_OR_DOMAIN", + help_text="Run port scanner for IP_OR_DOMAIN.", + ) + tool_group.add_argument( + "--port-range", + type=int, + default=None, + help="Number of ports to scan when using --port/--port-scanner.", + ) + _add_optional_tool_arg( + tool_group, + "--whois", + "--whois-lookup", + dest="whois_lookup", + metavar="DOMAIN", + help_text="Run WHOIS lookup for DOMAIN.", + ) + tool_group.add_argument( + "--fake-info", + "--fake-info-generator", + action="store_true", + help="Generate fake identity information.", + ) + _add_optional_tool_arg( + tool_group, + "--webscrape", + "--web-scrape", + dest="web_scrape", + metavar="URL", + help_text="Run web scrape for URL.", + ) + tool_group.add_argument( + "--wifi-finder", + action="store_true", + help="Scan for nearby Wi-Fi networks.", + ) + tool_group.add_argument( + "--wifi-vault", + action="store_true", + help="Dump locally saved Wi-Fi passwords.", + ) + _add_optional_tool_arg( + tool_group, + "--dirbuster", + "--dir-buster", + dest="dir_buster", + metavar="DOMAIN", + help_text="Run directory buster for DOMAIN.", + ) + _add_optional_tool_arg( + tool_group, + "--bluetooth", + "--bluetooth-scanner", + dest="bluetooth_scanner", + metavar="SECONDS", + help_text="Run Bluetooth scanner for SECONDS.", + ) + tool_group.add_argument( + "--local-users", + action="store_true", + help="Enumerate local system users.", + ) + + return parser + + +def _value_or_prompt(value: object) -> str | None: + """ + Convert argparse optional-argument sentinels to handler values. + + :param value: ``None``, ``True`` when flag has no value, or a string value. + :return: ``None`` to make the handler prompt, otherwise the provided value. + """ + return None if value is True or value is None else str(value) + + +def _cli_tool_selected(args: argparse.Namespace) -> bool: + """ + Determine whether any direct-run tool flag was provided. + + :param args: Parsed CLI args. + :return: ``True`` if at least one tool should run directly. + """ + return any( + [ + args.ig_scrape is not None, + args.webrecon, + args.phone_lookup is not None, + args.ip_lookup is not None, + args.username_search is not None, + args.email_search is not None, + args.leak_search is not None, + args.port_scanner is not None, + args.whois_lookup is not None, + args.fake_info, + args.web_scrape is not None, + args.wifi_finder, + args.wifi_vault, + args.dir_buster is not None, + args.bluetooth_scanner is not None, + args.local_users, + ] + ) + + +def _run_cli_tools(args: argparse.Namespace) -> None: + """ + Execute tool flags directly and exit without opening the menu. + + Tools run in the fixed menu order when multiple flags are provided. + + :param args: Parsed CLI args. + """ + cli_tasks = [ + ( + args.ig_scrape is not None, + handles.handle_ig_scrape, + [_value_or_prompt(args.ig_scrape)], + ), + (args.webrecon, handles.handle_web_reconnaissance, []), + ( + args.phone_lookup is not None, + handles.handle_phone_lookup, + [_value_or_prompt(args.phone_lookup)], + ), + ( + args.ip_lookup is not None, + handles.handle_ip_lookup, + [_value_or_prompt(args.ip_lookup)], + ), + ( + args.username_search is not None, + handles.handle_username_search, + [_value_or_prompt(args.username_search)], + ), + ( + args.email_search is not None, + handles.handle_email_search, + [_value_or_prompt(args.email_search)], + ), + ( + args.leak_search is not None, + handles.handle_leak_search, + [_value_or_prompt(args.leak_search)], + ), + ( + args.port_scanner is not None, + handles.handle_port_scanner, + [_value_or_prompt(args.port_scanner), args.port_range], + ), + ( + args.whois_lookup is not None, + handles.handle_whois_lookup, + [_value_or_prompt(args.whois_lookup)], + ), + (args.fake_info, handles.handle_fake_info_generator, []), + ( + args.web_scrape is not None, + handles.handle_web_scrape, + [_value_or_prompt(args.web_scrape)], + ), + (args.wifi_finder, handles.handle_wifi_finder, []), + (args.wifi_vault, handles.handle_wifi_vault, []), + ( + args.dir_buster is not None, + handles.handle_dir_buster, + [_value_or_prompt(args.dir_buster)], + ), + ( + args.bluetooth_scanner is not None, + handles.handle_bluetooth_scanner, + [_value_or_prompt(args.bluetooth_scanner)], + ), + (args.local_users, handles.handle_local_users, []), + ] + + for selected, handler, handler_args in cli_tasks: + if not selected: + continue + + try: + printer.verbose( + f"Running {handler.__name__.replace('handle_', '').replace('_', ' ')}" + ) + handler(*handler_args) + except KeyboardInterrupt: + printer.warning("Cancelled..!") + break + + +def main(args: argparse.Namespace | None = None) -> None: + if args is None: + args = _build_parser().parse_args() + + printer.set_verbosity(verbose=args.verbose > 0, debug_enabled=args.debug) config.init_config() - _internet_check() - time.sleep(0.5) + + if args.list_tools: + _display_help() + return + + if not args.no_internet_check: + _internet_check() + time.sleep(0.5) printer.debug("DEBUG IS ON.") + if _cli_tool_selected(args): + printer.set_pause_after_tool(False) + _run_cli_tools(args) + return + + printer.set_pause_after_tool(True) + while True: _print_banner() _print_menu() diff --git a/helper/handles.py b/helper/handles.py index 591b186..a06875a 100644 --- a/helper/handles.py +++ b/helper/handles.py @@ -36,15 +36,21 @@ ) -def handle_bluetooth_scanner() -> None: +def handle_bluetooth_scanner(duration: int | str | None = None) -> None: """Handles the Bluetooth Scanner util.""" - scan_duration = int(printer.user_input("Enter a scan duration (seconds) : \t")) + scan_duration = int( + duration + if duration is not None + else printer.user_input("Enter a scan duration (seconds) : \t") + ) bluetooth_scanner.scan_nearby_bluetooth(duration=scan_duration) -def handle_ig_scrape() -> None: +def handle_ig_scrape(target: str | None = None) -> None: """Handles the IG Scrape util.""" - target = str(printer.user_input("Enter a target username : \t")).replace(" ", "_") + target = str(target or printer.user_input("Enter a target username : \t")).replace( + " ", "_" + ) ig_scrape.scrape(target=target) @@ -53,47 +59,58 @@ def handle_web_reconnaissance() -> None: web_reconnaissance.websearch() -def handle_phone_lookup() -> None: +def handle_phone_lookup(phone_number: str | None = None) -> None: """Handles the Phone number Lookup util.""" printer.info("Include the country code, e.g. +358501234567 or +12025550123") - no = str(printer.user_input("Enter a phone-number with country code : \t")) + no = str( + phone_number + or printer.user_input("Enter a phone-number with country code : \t") + ) phonenumber_lookup.lookup(phone_number=no) -def handle_ip_lookup() -> None: +def handle_ip_lookup(ip: str | None = None) -> None: """Handles the IP/Domain Lookup util.""" - ip = str(printer.user_input("Enter a IP address OR domain : \t")) + ip = str(ip or printer.user_input("Enter a IP address OR domain : \t")) ip_lookup.lookup(ip_address=ip) -def handle_username_search() -> None: +def handle_username_search(username: str | None = None) -> None: """Handles the Username Search util.""" printer.info( "Maigret will check the username with configurable scan options and optional TXT/CSV/JSON export." ) - username = str(printer.user_input("Enter a target username : \t")).replace(" ", "_") + username = str( + username or printer.user_input("Enter a target username : \t") + ).replace(" ", "_") search_username.search(username=username) -def handle_email_search() -> None: +def handle_email_search(email: str | None = None) -> None: """Handles the Email Search util.""" printer.info( "holehe will check the address against 100+ websites and show where it is registered." ) - email = str(printer.user_input("Enter an email address : \t")) + email = str(email or printer.user_input("Enter an email address : \t")) email_search.search(email=email) -def handle_port_scanner() -> None: +def handle_port_scanner( + ip: str | None = None, port_range: int | str | None = None +) -> None: """Handles the Port Scanner util.""" - ip = str(printer.user_input("Enter a IP address OR domain : \t")) - port_range = int(printer.user_input("Enter number of ports to scan : \t")) + ip = str(ip or printer.user_input("Enter a IP address OR domain : \t")) + port_range = int( + port_range + if port_range is not None + else printer.user_input("Enter number of ports to scan : \t") + ) port_scanner.scan(ip=ip, port_range=port_range) -def handle_whois_lookup() -> None: +def handle_whois_lookup(domain: str | None = None) -> None: """Handles the WhoIs Lookup util.""" - domain = str(printer.user_input("Enter a domain : \t")) + domain = str(domain or printer.user_input("Enter a domain : \t")) whois_lookup.check_whois(domain=domain) @@ -102,9 +119,9 @@ def handle_fake_info_generator() -> None: fake_info_generator.generate() -def handle_web_scrape() -> None: +def handle_web_scrape(url: str | None = None) -> None: """Handles the Web Scrape util.""" - url = str(printer.user_input("Enter a URL : \t")) + url = str(url or printer.user_input("Enter a URL : \t")) web_scrape.scrape(url=url) @@ -120,9 +137,9 @@ def handle_wifi_vault() -> None: wifi_vault.get_local_passwords() -def handle_dir_buster() -> None: +def handle_dir_buster(domain: str | None = None) -> None: """Handles the Dir Buster util.""" - domain = printer.user_input("Enter a domain : \t") + domain = domain or printer.user_input("Enter a domain : \t") dirbuster.bust(domain=domain) @@ -132,7 +149,7 @@ def handle_local_users() -> None: local_users.scan_for_local_users() -def handle_leak_search() -> None: +def handle_leak_search(target: str | None = None) -> None: """Handles the Cybercrime Intelligence util.""" - target = printer.user_input("Enter a target (email/domain) : \t") + target = target or printer.user_input("Enter a target (email/domain/username) : \t") leak_search.lookup(target=target) diff --git a/helper/printer.py b/helper/printer.py index 934d26d..c44967a 100644 --- a/helper/printer.py +++ b/helper/printer.py @@ -21,6 +21,9 @@ from colorama import Fore, Style ANSI_ESCAPE = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])") +VERBOSE = False +DEBUG = "--debug" in sys.argv +PAUSE_AFTER_TOOL = True def _print_colored(message: str, color: str, prefix: str, *args, **kwargs) -> None: @@ -52,8 +55,62 @@ def warning(message, *args, **kwargs) -> None: _print_colored(message, Fore.LIGHTYELLOW_EX, "[-]", *args, **kwargs) +def set_verbosity(verbose: bool = False, debug_enabled: bool = False) -> None: + """ + Configure global verbosity flags used by printer helpers. + + :param verbose: Enable verbose output. + :param debug_enabled: Enable debug output. Also implies verbose output. + """ + global VERBOSE, DEBUG + VERBOSE = verbose or debug_enabled + DEBUG = debug_enabled + + +def set_pause_after_tool(enabled: bool) -> None: + """ + Configure whether timed tools pause for Enter after completion. + + :param enabled: ``True`` for interactive menu pauses, ``False`` for direct CLI runs. + """ + global PAUSE_AFTER_TOOL + PAUSE_AFTER_TOOL = enabled + + +def should_pause_after_tool() -> bool: + """ + Return whether timed tools should pause for Enter after completion. + + :return: ``True`` if completion pauses are enabled. + """ + return PAUSE_AFTER_TOOL + + +def is_verbose() -> bool: + """ + Return whether verbose output is enabled. + + :return: ``True`` if verbose or debug output is enabled. + """ + return VERBOSE or DEBUG + + +def is_debug() -> bool: + """ + Return whether debug output is enabled. + + :return: ``True`` if debug output is enabled. + """ + return DEBUG + + +def verbose(message, *args, **kwargs) -> None: + if is_verbose(): + _print_colored(message, Fore.LIGHTMAGENTA_EX, "[>]", *args, **kwargs) + + def debug(message, *args, **kwargs) -> None: - if "--debug" in sys.argv: + if is_debug(): _print_colored(message, Fore.LIGHTMAGENTA_EX, "[>]", *args, **kwargs) diff --git a/helper/timer.py b/helper/timer.py index 047ef5e..05b5caa 100644 --- a/helper/timer.py +++ b/helper/timer.py @@ -42,8 +42,8 @@ def wrapper(*args, **kwargs) -> str: f"Completed in {elapsed_time:.4f} seconds." ) # Print the elapsed time - # Prompt the user for input after execution - if require_input: + # Prompt the user for input after execution in menu mode only. + if require_input and printer.should_pause_after_tool(): printer.user_input("Press Enter key to continue...") # Prompt for input return result # Return the result of the wrapped function