feat: fixed exit proxy (last hop only), optional pool-free single-hop mode, gitignore pycache

Made-with: Cursor
This commit is contained in:
Dr Jones
2026-04-14 19:28:50 -07:00
parent 45798464e4
commit 644229719f
17 changed files with 140 additions and 17 deletions

View File

@@ -18,6 +18,7 @@ from .config import (
OBFUSCATION_MODES,
Settings,
load_settings,
normalize_proxy_url,
save_settings,
)
from .firewall import (
@@ -176,6 +177,7 @@ def main() -> None:
obfuscation_mode=cur.obfuscation_mode,
use_pinned_chain=cur.use_pinned_chain,
pinned_chain=cur.pinned_chain,
manual_exit_proxy=cur.manual_exit_proxy,
health_check_seconds=cur.health_check_seconds,
full_refresh_seconds=cur.full_refresh_seconds,
validation_concurrency=cur.validation_concurrency,
@@ -273,7 +275,9 @@ def main() -> None:
d.configure(text_color=c)
pulse_job["id"] = root.after(380, lambda s=step: _pulse(s + 1)) # type: ignore[arg-type]
def _render_chain(hops: list[str], status: str, exit_ip: str | None) -> None:
def _render_chain(
hops: list[str], status: str, exit_ip: str | None, fixed_last: bool = False
) -> None:
_cancel_pulse()
for w in list(chain_canvas.winfo_children()):
w.destroy()
@@ -296,11 +300,15 @@ def main() -> None:
_node("NORD", PURPLE, bold=True)
_arr()
last_i = len(hops) - 1
for i, h in enumerate(hops):
dot = ctk.CTkLabel(chain_canvas, text="", font=(FONT, 12), text_color=hop_c)
is_fixed = fixed_last and i == last_i and last_i >= 0
lbl_c = ORANGE if is_fixed else hop_c
dot = ctk.CTkLabel(chain_canvas, text="", font=(FONT, 12), text_color=lbl_c)
dot.pack(side="left", padx=1)
hop_dot_labels.append(dot)
ctk.CTkLabel(chain_canvas, text=_short(h), font=(FONT, 10), text_color=hop_c).pack(side="left", padx=1)
label = _short(h) + ("" if is_fixed else "")
ctk.CTkLabel(chain_canvas, text=label, font=(FONT, 10), text_color=lbl_c).pack(side="left", padx=1)
if i < len(hops) - 1:
_arr()
@@ -448,7 +456,35 @@ def main() -> None:
cb_left, text="Elite proxies only (slower but more anonymous)",
variable=elite_var, font=(FONT, 11),
fg_color=ACCENT2, hover_color=ACCENT, text_color=TEXT,
).pack(anchor="w", padx=16, pady=(0, 12))
).pack(anchor="w", padx=16, pady=(0, 8))
exit_fix_frame = ctk.CTkFrame(cb_left, fg_color=PANEL, corner_radius=8)
exit_fix_frame.pack(fill="x", padx=12, pady=(0, 12))
ctk.CTkLabel(
exit_fix_frame,
text="Fixed exit — last hop only",
font=(FONT, 11, "bold"),
text_color=ORANGE,
).pack(anchor="w", padx=10, pady=(8, 2))
exit_proxy_entry = ctk.CTkEntry(
exit_fix_frame,
placeholder_text="http://host:port or socks5://host:port (optional)",
font=("Consolas", 10),
fg_color=BG,
border_color=ACCENT,
height=28,
)
exit_proxy_entry.pack(fill="x", padx=10, pady=(0, 4))
if s.manual_exit_proxy:
exit_proxy_entry.insert(0, s.manual_exit_proxy)
ctk.CTkLabel(
exit_fix_frame,
text="Earlier hops still rotate randomly. Empty = fully automatic exit.\n"
"Ignored while “Pin & use this chain” is enabled.",
font=(FONT, 9),
text_color=TEXT2,
justify="left",
).pack(anchor="w", padx=10, pady=(0, 8))
# Right — manual chain editor
cb_right = ctk.CTkFrame(cb_split, fg_color=CARD, corner_radius=8, width=400)
@@ -643,6 +679,7 @@ def main() -> None:
obfuscation_mode=mode_var.get(),
use_pinned_chain=bool(use_manual_var.get()),
pinned_chain=list(manual_chain),
manual_exit_proxy=normalize_proxy_url(exit_proxy_entry.get()),
health_check_seconds=max(10, int(entries["health"].get().strip())),
full_refresh_seconds=max(60, int(entries["refresh"].get().strip())),
validation_concurrency=max(1, int(entries["conc"].get().strip())),
@@ -751,7 +788,9 @@ def main() -> None:
hops = [str(x) for x in (m.get("hops") or [])]
status = str(m.get("status", "connecting"))
exit_ip = m.get("exit_ip")
_render_chain(hops, status, exit_ip)
fix = normalize_proxy_url(svc.settings.manual_exit_proxy)
fixed_last = bool(fix) and not svc.settings.use_pinned_chain
_render_chain(hops, status, exit_ip, fixed_last=fixed_last)
_refresh_sysproxy()
tray.set_state(
{"healthy": "green", "dead": "red", "connecting": "yellow"}.get(status, "yellow")
@@ -792,6 +831,7 @@ def main() -> None:
_log(f"Log file: {LOG_PATH}")
_log("Press START to fetch, validate and chain proxies.")
_log("Use +/ in top bar to change hop count instantly.")
_log("Chain Builder → Fixed exit: set the last hop (optional); rest still rotates.")
_log("" * 60)
_pump()
root.mainloop()