feat: fixed exit proxy (last hop only), optional pool-free single-hop mode, gitignore pycache
Made-with: Cursor
This commit is contained in:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user