diff --git a/hate_crack/api.py b/hate_crack/api.py index 5c5b1b0..2832566 100644 --- a/hate_crack/api.py +++ b/hate_crack/api.py @@ -10,6 +10,7 @@ from typing import Callable, Tuple import requests from bs4 import BeautifulSoup +from hate_crack.formatting import print_multicolumn_list _TORRENT_CLEANUP_REGISTERED = False def check_7z(): @@ -351,21 +352,15 @@ def weakpass_wordlist_menu(rank=-1): else: # Default: show all with rank > 4 filtered_wordlists = [wl for wl in all_wordlists if str(wl.get('rank', '')) > '4'] - col_width = 45 - cols = 3 print("\nEach entry shows: [number]. [wordlist name] [effectiveness score] [rank]") - print(f"Available Wordlists:") - rows = (len(filtered_wordlists) + cols - 1) // cols - lines = [''] * rows + entries = [] for idx, wl in enumerate(filtered_wordlists): - col = idx // rows - row = idx % rows effectiveness = wl.get('effectiveness', wl.get('downloads', '')) rank = wl.get('rank', '') - entry = f"{idx+1:3d}. {wl['name'][:25]:<25} {effectiveness:<8} {rank:<2}" - lines[row] += entry.ljust(col_width) - for line in lines: - print(line) + name = str(wl.get('name', ''))[:30] + entry = f"{idx+1:3d}. {name:<30} {effectiveness:<8} {rank:<2}" + entries.append(entry) + print_multicolumn_list("Available Wordlists", entries, min_col_width=36, max_col_width=70) def parse_indices(selection, max_index): indices = set() for part in selection.split(','): @@ -877,9 +872,16 @@ def download_hashmob_wordlist_list(): resp.raise_for_status() data = resp.json() wordlists = [r for r in data if r.get('type') == 'wordlist'] - print("Available Hashmob Wordlists:") + entries = [] for idx, wl in enumerate(wordlists): - print(f"{idx+1}. {wl.get('name', wl.get('file_name', ''))} - {wl.get('information', '')}") + name = wl.get('name', wl.get('file_name', '')) + info = wl.get('information', '') + if info: + entry = f"{idx+1}. {name} - {info}" + else: + entry = f"{idx+1}. {name}" + entries.append(entry) + print_multicolumn_list("Available Hashmob Wordlists", entries, min_col_width=30, max_col_width=80) return wordlists except Exception as e: print(f"Error fetching Hashmob wordlists: {e}") @@ -1033,9 +1035,8 @@ def list_official_wordlists(): resp.raise_for_status() try: data = resp.json() - print("Official Hashmob Wordlists (JSON):") - for idx, entry in enumerate(data): - print(f"{idx+1}. {entry}") + entries = [f"{idx+1}. {entry}" for idx, entry in enumerate(data)] + print_multicolumn_list("Official Hashmob Wordlists (JSON)", entries, min_col_width=30, max_col_width=80) return data except Exception: print("Official Hashmob Wordlists (raw text):") @@ -1057,11 +1058,12 @@ def list_and_download_official_wordlists(): print("Unexpected response format. Raw output:") print(data) return - print("Official Hashmob Wordlists:") + entries = [] for idx, entry in enumerate(data): name = entry.get('name', entry.get('file_name', str(entry))) file_name = entry.get('file_name', name) - print(f"{idx+1}. {name} ({file_name})") + entries.append(f"{idx+1}. {name} ({file_name})") + print_multicolumn_list("Official Hashmob Wordlists", entries, min_col_width=30, max_col_width=80) print("a. Download ALL files") def _safe_input(prompt): try: diff --git a/hate_crack/attacks.py b/hate_crack/attacks.py index b621382..f61ae59 100644 --- a/hate_crack/attacks.py +++ b/hate_crack/attacks.py @@ -3,6 +3,7 @@ import os import readline from typing import Any +from hate_crack.formatting import print_multicolumn_list def _configure_readline(completer): readline.set_completer_delims(' \t\n;') @@ -27,9 +28,8 @@ def quick_crack(ctx: Any) -> None: selected_hcatRules = [] wordlist_files = sorted(os.listdir(ctx.hcatWordlists)) - print("\nWordlists:") - for i, file in enumerate(wordlist_files, start=1): - print(f"{i}. {file}") + wordlist_entries = [f"{i}. {file}" for i, file in enumerate(wordlist_files, start=1)] + print_multicolumn_list("Wordlists", wordlist_entries, min_col_width=24, max_col_width=60) def path_completer(text, state): if not text: diff --git a/hate_crack/formatting.py b/hate_crack/formatting.py new file mode 100644 index 0000000..457706b --- /dev/null +++ b/hate_crack/formatting.py @@ -0,0 +1,46 @@ +import os + + +def _terminal_width(default: int = 120) -> int: + try: + return os.get_terminal_size().columns + except Exception: + return default + + +def print_multicolumn_list(title, entries, min_col_width=20, max_col_width=None): + if not entries: + if title: + print(f"\n{title}:\n (none)") + return + + terminal_width = _terminal_width() + max_len = max(len(entry) for entry in entries) + if max_col_width is None: + max_col_width = max_len + 2 + col_width = max(min_col_width, min(max_col_width, terminal_width)) + num_cols = max(1, terminal_width // col_width) + rows = (len(entries) + num_cols - 1) // num_cols + + if title: + print("\n" + "=" * terminal_width) + print(title) + print("=" * terminal_width) + + for row in range(rows): + line_parts = [] + for col in range(num_cols): + idx = row + col * rows + if idx < len(entries): + entry = entries[idx] + max_entry_len = max(1, col_width - 2) + if len(entry) > max_entry_len: + if max_entry_len > 3: + entry = entry[: max_entry_len - 3] + "..." + else: + entry = entry[:max_entry_len] + line_parts.append(entry.ljust(col_width)) + print("".join(line_parts).rstrip()) + + if title: + print("=" * terminal_width) diff --git a/hate_crack/main.py b/hate_crack/main.py index 4112a03..517135d 100755 --- a/hate_crack/main.py +++ b/hate_crack/main.py @@ -340,6 +340,13 @@ hcatExpanderBin = "expander.bin" hcatCombinatorBin = "combinator.bin" hcatPrinceBin = "pp64.bin" +def _make_abs_wordlist(base_dir, wordlist): + if not wordlist: + return wordlist + if os.path.isabs(wordlist): + return wordlist + return os.path.abspath(os.path.join(base_dir, wordlist)) + def get_rule_path(rule_name, fallback_dir=None): candidates = [] if rulesDirectory: @@ -464,6 +471,12 @@ if not SKIP_INIT: # Allow module to load even if initialization fails pass + # Normalize prince wordlist to an absolute path + wordlists_dir = hcatWordlists or os.path.join(hate_path, "wordlists") + if not os.path.isabs(wordlists_dir): + wordlists_dir = os.path.join(hate_path, wordlists_dir) + hcatPrinceBaseList = _make_abs_wordlist(wordlists_dir, hcatPrinceBaseList) + hcatHashCount = 0 hcatHashCracked = 0