mirror of
https://github.com/Z4nzu/hackingtool
synced 2026-05-24 09:28:43 +00:00
HackingToolsCollection.show_options(): - Add Description column back (was dropped during restructure) - Show first line of description; '—' for tools with no description - Switch to box.SIMPLE_HEAD with show_lines=True for row separators - Numbering changed from 0-based to 1-based (index 1..N) - Input validation updated: 1 <= choice <= len(active) with active[choice-1]
302 lines
10 KiB
Python
302 lines
10 KiB
Python
import os
|
|
import sys
|
|
import webbrowser
|
|
from collections.abc import Callable
|
|
from platform import system
|
|
|
|
from rich import box
|
|
from rich.console import Console
|
|
from rich.panel import Panel
|
|
from rich.prompt import Prompt
|
|
from rich.table import Table
|
|
from rich.theme import Theme
|
|
from rich.traceback import install
|
|
|
|
from constants import (
|
|
THEME_PRIMARY, THEME_BORDER, THEME_ACCENT,
|
|
THEME_SUCCESS, THEME_ERROR, THEME_WARNING,
|
|
THEME_DIM, THEME_ARCHIVED, THEME_URL,
|
|
)
|
|
|
|
# Enable rich tracebacks globally
|
|
install()
|
|
|
|
_theme = Theme({
|
|
"purple": "#7B61FF",
|
|
"success": THEME_SUCCESS,
|
|
"error": THEME_ERROR,
|
|
"warning": THEME_WARNING,
|
|
"archived": THEME_ARCHIVED,
|
|
"url": THEME_URL,
|
|
"dim": THEME_DIM,
|
|
})
|
|
|
|
# Single shared console — all tool files do: from core import console
|
|
console = Console(theme=_theme)
|
|
|
|
|
|
def clear_screen():
|
|
os.system("cls" if system() == "Windows" else "clear")
|
|
|
|
|
|
def validate_input(ip, val_range: list) -> int | None:
|
|
"""Return the integer if it is in val_range, else None."""
|
|
if not val_range:
|
|
return None
|
|
try:
|
|
ip = int(ip)
|
|
if ip in val_range:
|
|
return ip
|
|
except (TypeError, ValueError):
|
|
pass
|
|
return None
|
|
|
|
|
|
class HackingTool:
|
|
TITLE: str = ""
|
|
DESCRIPTION: str = ""
|
|
INSTALL_COMMANDS: list[str] = []
|
|
UNINSTALL_COMMANDS: list[str] = []
|
|
RUN_COMMANDS: list[str] = []
|
|
OPTIONS: list[tuple[str, Callable]] = []
|
|
PROJECT_URL: str = ""
|
|
|
|
# OS / capability metadata
|
|
SUPPORTED_OS: list[str] = ["linux", "macos"]
|
|
REQUIRES_ROOT: bool = False
|
|
REQUIRES_WIFI: bool = False
|
|
REQUIRES_GO: bool = False
|
|
REQUIRES_RUBY: bool = False
|
|
REQUIRES_JAVA: bool = False
|
|
REQUIRES_DOCKER: bool = False
|
|
|
|
# Archived tool flags
|
|
ARCHIVED: bool = False
|
|
ARCHIVED_REASON: str = ""
|
|
|
|
def __init__(self, options=None, installable=True, runnable=True):
|
|
options = options or []
|
|
if not isinstance(options, list):
|
|
raise TypeError("options must be a list of (option_name, option_fn) tuples")
|
|
self.OPTIONS = []
|
|
if installable:
|
|
self.OPTIONS.append(("Install", self.install))
|
|
if runnable:
|
|
self.OPTIONS.append(("Run", self.run))
|
|
self.OPTIONS.extend(options)
|
|
|
|
def show_info(self):
|
|
desc = f"[cyan]{self.DESCRIPTION}[/cyan]"
|
|
if self.PROJECT_URL:
|
|
desc += f"\n[url]🔗 {self.PROJECT_URL}[/url]"
|
|
if self.ARCHIVED:
|
|
desc += f"\n[archived]⚠ ARCHIVED: {self.ARCHIVED_REASON}[/archived]"
|
|
console.print(Panel(
|
|
desc,
|
|
title=f"[{THEME_PRIMARY}]{self.TITLE}[/{THEME_PRIMARY}]",
|
|
border_style="purple",
|
|
box=box.DOUBLE,
|
|
))
|
|
|
|
def show_options(self, parent=None):
|
|
"""Iterative menu loop — no recursion, no stack growth."""
|
|
while True:
|
|
clear_screen()
|
|
self.show_info()
|
|
|
|
table = Table(title="Options", box=box.SIMPLE_HEAVY)
|
|
table.add_column("No.", style="bold cyan", justify="center")
|
|
table.add_column("Action", style="bold yellow")
|
|
|
|
for index, option in enumerate(self.OPTIONS):
|
|
table.add_row(str(index + 1), option[0])
|
|
|
|
if self.PROJECT_URL:
|
|
table.add_row("98", "Open Project Page")
|
|
table.add_row("99", f"Back to {parent.TITLE if parent else 'Main Menu'}")
|
|
console.print(table)
|
|
|
|
raw = Prompt.ask("\n[bold cyan][?] Select an option[/bold cyan]", default="")
|
|
if not raw.strip():
|
|
continue
|
|
try:
|
|
choice = int(raw)
|
|
except ValueError:
|
|
console.print("[error]⚠ Please enter a number.[/error]")
|
|
Prompt.ask("[dim]Press Enter to continue[/dim]", default="")
|
|
continue
|
|
|
|
if choice == 99:
|
|
return
|
|
elif choice == 98 and self.PROJECT_URL:
|
|
self.show_project_page()
|
|
elif 1 <= choice <= len(self.OPTIONS):
|
|
try:
|
|
self.OPTIONS[choice - 1][1]()
|
|
except Exception:
|
|
console.print_exception(show_locals=True)
|
|
Prompt.ask("[dim]Press Enter to continue[/dim]", default="")
|
|
else:
|
|
console.print("[error]⚠ Invalid option.[/error]")
|
|
|
|
def before_install(self): pass
|
|
|
|
def install(self):
|
|
self.before_install()
|
|
if isinstance(self.INSTALL_COMMANDS, (list, tuple)):
|
|
for cmd in self.INSTALL_COMMANDS:
|
|
console.print(f"[warning]→ {cmd}[/warning]")
|
|
os.system(cmd)
|
|
self.after_install()
|
|
|
|
def after_install(self):
|
|
console.print("[success]✔ Successfully installed![/success]")
|
|
|
|
def before_uninstall(self) -> bool:
|
|
return True
|
|
|
|
def uninstall(self):
|
|
if self.before_uninstall():
|
|
if isinstance(self.UNINSTALL_COMMANDS, (list, tuple)):
|
|
for cmd in self.UNINSTALL_COMMANDS:
|
|
console.print(f"[error]→ {cmd}[/error]")
|
|
os.system(cmd)
|
|
self.after_uninstall()
|
|
|
|
def after_uninstall(self): pass
|
|
|
|
def before_run(self): pass
|
|
|
|
def run(self):
|
|
self.before_run()
|
|
if isinstance(self.RUN_COMMANDS, (list, tuple)):
|
|
for cmd in self.RUN_COMMANDS:
|
|
console.print(f"[cyan]⚙ Running:[/cyan] [bold]{cmd}[/bold]")
|
|
os.system(cmd)
|
|
self.after_run()
|
|
|
|
def after_run(self): pass
|
|
|
|
def show_project_page(self):
|
|
console.print(f"[url]🌐 Opening: {self.PROJECT_URL}[/url]")
|
|
webbrowser.open_new_tab(self.PROJECT_URL)
|
|
|
|
|
|
class HackingToolsCollection:
|
|
TITLE: str = ""
|
|
DESCRIPTION: str = ""
|
|
TOOLS: list = []
|
|
|
|
def __init__(self):
|
|
pass
|
|
|
|
def show_info(self):
|
|
console.rule(f"[{THEME_PRIMARY}]{self.TITLE}[/{THEME_PRIMARY}]", style="purple")
|
|
if self.DESCRIPTION:
|
|
console.print(f"[italic cyan]{self.DESCRIPTION}[/italic cyan]\n")
|
|
|
|
def _active_tools(self) -> list:
|
|
"""Return tools that are not archived and are OS-compatible."""
|
|
from os_detect import CURRENT_OS
|
|
return [
|
|
t for t in self.TOOLS
|
|
if not getattr(t, "ARCHIVED", False)
|
|
and CURRENT_OS.system in getattr(t, "SUPPORTED_OS", ["linux", "macos"])
|
|
]
|
|
|
|
def _archived_tools(self) -> list:
|
|
return [t for t in self.TOOLS if getattr(t, "ARCHIVED", False)]
|
|
|
|
def _incompatible_tools(self) -> list:
|
|
from os_detect import CURRENT_OS
|
|
return [
|
|
t for t in self.TOOLS
|
|
if not getattr(t, "ARCHIVED", False)
|
|
and CURRENT_OS.system not in getattr(t, "SUPPORTED_OS", ["linux", "macos"])
|
|
]
|
|
|
|
def _show_archived_tools(self):
|
|
"""Show archived tools sub-menu (option 98)."""
|
|
archived = self._archived_tools()
|
|
if not archived:
|
|
console.print("[dim]No archived tools in this category.[/dim]")
|
|
Prompt.ask("[dim]Press Enter to return[/dim]", default="")
|
|
return
|
|
|
|
while True:
|
|
clear_screen()
|
|
console.rule(f"[archived]Archived Tools — {self.TITLE}[/archived]", style="yellow")
|
|
|
|
table = Table(box=box.MINIMAL_DOUBLE_HEAD, show_lines=True)
|
|
table.add_column("No.", justify="center", style="bold yellow")
|
|
table.add_column("Tool", style="dim yellow")
|
|
table.add_column("Reason", style="dim white")
|
|
|
|
for i, tool in enumerate(archived):
|
|
reason = getattr(tool, "ARCHIVED_REASON", "No reason given")
|
|
table.add_row(str(i + 1), tool.TITLE, reason)
|
|
|
|
table.add_row("99", "Back", "")
|
|
console.print(table)
|
|
|
|
raw = Prompt.ask("[bold yellow][?] Select[/bold yellow]", default="99")
|
|
try:
|
|
choice = int(raw)
|
|
except ValueError:
|
|
continue
|
|
|
|
if choice == 99:
|
|
return
|
|
elif 1 <= choice <= len(archived):
|
|
archived[choice - 1].show_options(parent=self)
|
|
|
|
def show_options(self, parent=None):
|
|
"""Iterative menu loop — no recursion, no stack growth."""
|
|
while True:
|
|
clear_screen()
|
|
self.show_info()
|
|
|
|
active = self._active_tools()
|
|
incompatible = self._incompatible_tools()
|
|
archived = self._archived_tools()
|
|
|
|
table = Table(title="Available Tools", box=box.SIMPLE_HEAD, show_lines=True)
|
|
table.add_column("No.", justify="center", style="bold cyan", width=6)
|
|
table.add_column("Tool", style="bold yellow", min_width=24)
|
|
table.add_column("Description", style="white", overflow="fold")
|
|
|
|
for index, tool in enumerate(active, start=1):
|
|
desc = getattr(tool, "DESCRIPTION", "") or "—"
|
|
# Show only first line of description to keep rows compact
|
|
desc = desc.splitlines()[0] if desc != "—" else "—"
|
|
table.add_row(str(index), tool.TITLE, desc)
|
|
|
|
if archived:
|
|
table.add_row("[dim]98[/dim]", f"[archived]Archived tools ({len(archived)})[/archived]", "")
|
|
if incompatible:
|
|
console.print(f"[dim]({len(incompatible)} tools hidden — not supported on current OS)[/dim]")
|
|
|
|
table.add_row("99", f"Back to {parent.TITLE if parent else 'Main Menu'}", "")
|
|
console.print(table)
|
|
|
|
raw = Prompt.ask("\n[bold cyan][?] Choose a tool[/bold cyan]", default="")
|
|
if not raw.strip():
|
|
continue
|
|
try:
|
|
choice = int(raw)
|
|
except ValueError:
|
|
console.print("[error]⚠ Please enter a number.[/error]")
|
|
continue
|
|
|
|
if choice == 99:
|
|
return
|
|
elif choice == 98 and archived:
|
|
self._show_archived_tools()
|
|
elif 1 <= choice <= len(active):
|
|
try:
|
|
active[choice - 1].show_options(parent=self)
|
|
except Exception:
|
|
console.print_exception(show_locals=True)
|
|
Prompt.ask("[dim]Press Enter to continue[/dim]", default="")
|
|
else:
|
|
console.print("[error]⚠ Invalid option.[/error]")
|