platform: modularize api/gui, add docs-tests-web foundation, and refresh root config
This commit is contained in:
271
selective-vpn-gui/main_window/singbox/links_actions_mixin.py
Normal file
271
selective-vpn-gui/main_window/singbox/links_actions_mixin.py
Normal file
@@ -0,0 +1,271 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
|
||||
from PySide6.QtWidgets import QApplication, QInputDialog, QMenu
|
||||
|
||||
from main_window.constants import SINGBOX_EDITOR_PROTOCOL_OPTIONS
|
||||
|
||||
|
||||
class SingBoxLinksActionsMixin:
|
||||
def _apply_singbox_editor_values(self, values: dict[str, Any]) -> None:
|
||||
incoming = dict(values or {})
|
||||
target_protocol = str(incoming.get("protocol") or self._current_editor_protocol() or "vless").strip().lower() or "vless"
|
||||
payload = self._seed_editor_values_for_protocol(
|
||||
target_protocol,
|
||||
profile_name=str(incoming.get("profile_name") or "").strip(),
|
||||
)
|
||||
payload.update(incoming)
|
||||
self._singbox_editor_loading = True
|
||||
try:
|
||||
name = str(payload.get("profile_name") or "").strip()
|
||||
self.ent_singbox_proto_name.setText(name)
|
||||
self.chk_singbox_proto_enabled.setChecked(bool(payload.get("enabled", True)))
|
||||
protocol = str(payload.get("protocol") or "").strip().lower()
|
||||
if protocol:
|
||||
pidx = self.cmb_singbox_proto_protocol.findData(protocol)
|
||||
self.cmb_singbox_proto_protocol.setCurrentIndex(pidx if pidx >= 0 else self.cmb_singbox_proto_protocol.currentIndex())
|
||||
self.ent_singbox_vless_server.setText(str(payload.get("server") or "").strip())
|
||||
|
||||
try:
|
||||
self.spn_singbox_vless_port.setValue(int(payload.get("port") or 443))
|
||||
except Exception:
|
||||
self.spn_singbox_vless_port.setValue(443)
|
||||
|
||||
self.ent_singbox_vless_uuid.setText(str(payload.get("uuid") or "").strip())
|
||||
self.ent_singbox_proto_password.setText(str(payload.get("password") or "").strip())
|
||||
|
||||
flow_v = str(payload.get("flow") or "").strip()
|
||||
flow_idx = self.cmb_singbox_vless_flow.findData(flow_v)
|
||||
if flow_idx >= 0:
|
||||
self.cmb_singbox_vless_flow.setCurrentIndex(flow_idx)
|
||||
else:
|
||||
self.cmb_singbox_vless_flow.setEditText(flow_v)
|
||||
|
||||
packet_v = str(payload.get("packet_encoding") or "").strip().lower()
|
||||
if packet_v in ("none", "off", "false"):
|
||||
packet_v = ""
|
||||
packet_idx = self.cmb_singbox_vless_packet_encoding.findData(packet_v)
|
||||
self.cmb_singbox_vless_packet_encoding.setCurrentIndex(packet_idx if packet_idx >= 0 else 0)
|
||||
|
||||
transport_v = str(payload.get("transport") or "tcp").strip().lower()
|
||||
transport_idx = self.cmb_singbox_vless_transport.findData(transport_v)
|
||||
self.cmb_singbox_vless_transport.setCurrentIndex(transport_idx if transport_idx >= 0 else 0)
|
||||
|
||||
self.ent_singbox_vless_path.setText(str(payload.get("path") or "").strip())
|
||||
self.ent_singbox_vless_grpc_service.setText(str(payload.get("grpc_service") or "").strip())
|
||||
|
||||
sec_v = str(payload.get("security") or "none").strip().lower()
|
||||
sec_idx = self.cmb_singbox_vless_security.findData(sec_v)
|
||||
self.cmb_singbox_vless_security.setCurrentIndex(sec_idx if sec_idx >= 0 else 0)
|
||||
|
||||
self.ent_singbox_vless_sni.setText(str(payload.get("sni") or "").strip())
|
||||
fp_v = str(payload.get("utls_fp") or "").strip().lower()
|
||||
fp_idx = self.cmb_singbox_vless_utls_fp.findData(fp_v)
|
||||
self.cmb_singbox_vless_utls_fp.setCurrentIndex(fp_idx if fp_idx >= 0 else 0)
|
||||
self.ent_singbox_vless_reality_pk.setText(str(payload.get("reality_public_key") or "").strip())
|
||||
self.ent_singbox_vless_reality_sid.setText(str(payload.get("reality_short_id") or "").strip())
|
||||
self.chk_singbox_vless_insecure.setChecked(bool(payload.get("tls_insecure", False)))
|
||||
self.chk_singbox_vless_sniff.setChecked(bool(payload.get("sniff", True)))
|
||||
|
||||
ss_method = str(payload.get("ss_method") or "").strip().lower()
|
||||
if ss_method:
|
||||
idx = self.cmb_singbox_ss_method.findData(ss_method)
|
||||
if idx >= 0:
|
||||
self.cmb_singbox_ss_method.setCurrentIndex(idx)
|
||||
else:
|
||||
self.cmb_singbox_ss_method.setEditText(ss_method)
|
||||
else:
|
||||
self.cmb_singbox_ss_method.setCurrentIndex(0)
|
||||
self.ent_singbox_ss_plugin.setText(str(payload.get("ss_plugin") or "").strip())
|
||||
|
||||
try:
|
||||
self.spn_singbox_hy2_up_mbps.setValue(int(payload.get("hy2_up_mbps") or 0))
|
||||
except Exception:
|
||||
self.spn_singbox_hy2_up_mbps.setValue(0)
|
||||
try:
|
||||
self.spn_singbox_hy2_down_mbps.setValue(int(payload.get("hy2_down_mbps") or 0))
|
||||
except Exception:
|
||||
self.spn_singbox_hy2_down_mbps.setValue(0)
|
||||
self.ent_singbox_hy2_obfs.setText(str(payload.get("hy2_obfs") or "").strip())
|
||||
self.ent_singbox_hy2_obfs_password.setText(str(payload.get("hy2_obfs_password") or "").strip())
|
||||
|
||||
tuic_cc = str(payload.get("tuic_congestion") or "").strip()
|
||||
idx = self.cmb_singbox_tuic_congestion.findData(tuic_cc)
|
||||
self.cmb_singbox_tuic_congestion.setCurrentIndex(idx if idx >= 0 else 0)
|
||||
tuic_udp = str(payload.get("tuic_udp_mode") or "").strip()
|
||||
idx = self.cmb_singbox_tuic_udp_mode.findData(tuic_udp)
|
||||
self.cmb_singbox_tuic_udp_mode.setCurrentIndex(idx if idx >= 0 else 0)
|
||||
self.chk_singbox_tuic_zero_rtt.setChecked(bool(payload.get("tuic_zero_rtt", False)))
|
||||
|
||||
self.ent_singbox_wg_private_key.setText(str(payload.get("wg_private_key") or "").strip())
|
||||
self.ent_singbox_wg_peer_public_key.setText(str(payload.get("wg_peer_public_key") or "").strip())
|
||||
self.ent_singbox_wg_psk.setText(str(payload.get("wg_psk") or "").strip())
|
||||
wg_local = payload.get("wg_local_address") or []
|
||||
if isinstance(wg_local, list):
|
||||
self.ent_singbox_wg_local_address.setText(
|
||||
",".join([str(x).strip() for x in wg_local if str(x).strip()])
|
||||
)
|
||||
else:
|
||||
self.ent_singbox_wg_local_address.setText(str(wg_local or "").strip())
|
||||
wg_reserved = payload.get("wg_reserved") or []
|
||||
if isinstance(wg_reserved, list):
|
||||
self.ent_singbox_wg_reserved.setText(
|
||||
",".join([str(x).strip() for x in wg_reserved if str(x).strip()])
|
||||
)
|
||||
else:
|
||||
self.ent_singbox_wg_reserved.setText(str(wg_reserved or "").strip())
|
||||
try:
|
||||
self.spn_singbox_wg_mtu.setValue(int(payload.get("wg_mtu") or 0))
|
||||
except Exception:
|
||||
self.spn_singbox_wg_mtu.setValue(0)
|
||||
finally:
|
||||
self._singbox_editor_loading = False
|
||||
self.on_singbox_vless_editor_changed()
|
||||
|
||||
def _create_singbox_connection(
|
||||
self,
|
||||
*,
|
||||
profile_name: str,
|
||||
protocol: str = "vless",
|
||||
raw_config: dict[str, Any] | None = None,
|
||||
editor_values: dict[str, Any] | None = None,
|
||||
auto_save: bool = False,
|
||||
) -> str:
|
||||
name = str(profile_name or "").strip() or "SingBox connection"
|
||||
client_id = self._next_free_transport_client_id(name)
|
||||
proto = self._normalized_seed_protocol(protocol)
|
||||
config = self._default_new_singbox_client_config(client_id, protocol=proto)
|
||||
|
||||
created = self.ctrl.transport_client_create_action(
|
||||
client_id=client_id,
|
||||
kind="singbox",
|
||||
name=name,
|
||||
enabled=True,
|
||||
config=config,
|
||||
)
|
||||
line = (created.pretty_text or "").strip() or f"create {client_id}"
|
||||
self._append_transport_log(f"[engine] {line}")
|
||||
self.ctrl.log_gui(f"[transport-engine] {line}")
|
||||
if not created.ok:
|
||||
raise RuntimeError(line)
|
||||
|
||||
self.refresh_transport_engines(silent=True)
|
||||
if not self._select_transport_engine_by_id(client_id):
|
||||
raise RuntimeError(f"created client '{client_id}' was not found after refresh")
|
||||
|
||||
self._sync_selected_singbox_profile_link(silent=False)
|
||||
client, _eid, pid = self._selected_singbox_profile_context()
|
||||
|
||||
seed_raw = raw_config if isinstance(raw_config, dict) else self._seed_raw_config_for_protocol(proto)
|
||||
saved_seed = self.ctrl.singbox_profile_save_raw_for_client(
|
||||
client,
|
||||
profile_id=pid,
|
||||
name=name,
|
||||
enabled=True,
|
||||
protocol=proto,
|
||||
raw_config=seed_raw,
|
||||
)
|
||||
seed_line = (saved_seed.pretty_text or "").strip() or f"save profile {pid}"
|
||||
self._append_transport_log(f"[profile] {seed_line}")
|
||||
self.ctrl.log_gui(f"[singbox-profile] {seed_line}")
|
||||
self._load_singbox_editor_for_selected(silent=True)
|
||||
|
||||
if editor_values:
|
||||
payload = dict(editor_values)
|
||||
seeded = self._seed_editor_values_for_protocol(proto, profile_name=name)
|
||||
seeded.update(payload)
|
||||
payload = seeded
|
||||
if not str(payload.get("profile_name") or "").strip():
|
||||
payload["profile_name"] = name
|
||||
self._apply_singbox_editor_values(payload)
|
||||
if auto_save:
|
||||
saved = self._save_singbox_editor_draft(client, profile_id=pid)
|
||||
save_line = (saved.pretty_text or "").strip() or f"save profile {pid}"
|
||||
self._append_transport_log(f"[profile] {save_line}")
|
||||
self.ctrl.log_gui(f"[singbox-profile] {save_line}")
|
||||
return client_id
|
||||
|
||||
def on_singbox_create_connection_click(self) -> None:
|
||||
menu = QMenu(self)
|
||||
act_clip = menu.addAction("Create from clipboard")
|
||||
act_link = menu.addAction("Create from link...")
|
||||
act_manual = menu.addAction("Create manual")
|
||||
pos = self.btn_singbox_profile_create.mapToGlobal(
|
||||
self.btn_singbox_profile_create.rect().bottomLeft()
|
||||
)
|
||||
chosen = menu.exec(pos)
|
||||
if chosen is None:
|
||||
return
|
||||
|
||||
if chosen == act_clip:
|
||||
self._safe(self.on_singbox_create_connection_from_clipboard, title="Create connection error")
|
||||
return
|
||||
if chosen == act_link:
|
||||
self._safe(self.on_singbox_create_connection_from_link, title="Create connection error")
|
||||
return
|
||||
if chosen == act_manual:
|
||||
self._safe(self.on_singbox_create_connection_manual, title="Create connection error")
|
||||
|
||||
def on_singbox_create_connection_from_clipboard(self) -> None:
|
||||
raw = str(QApplication.clipboard().text() or "").strip()
|
||||
if not raw:
|
||||
raise RuntimeError("Clipboard is empty")
|
||||
payload = self._parse_connection_link_payload(raw)
|
||||
profile_name = str(payload.get("profile_name") or "").strip() or "SingBox Clipboard"
|
||||
cid = self._create_singbox_connection(
|
||||
profile_name=profile_name,
|
||||
protocol=str(payload.get("protocol") or "vless"),
|
||||
raw_config=payload.get("raw_config") if isinstance(payload.get("raw_config"), dict) else None,
|
||||
editor_values=payload.get("editor_values") if isinstance(payload.get("editor_values"), dict) else None,
|
||||
auto_save=True,
|
||||
)
|
||||
self.on_singbox_profile_edit_dialog(cid)
|
||||
|
||||
def on_singbox_create_connection_from_link(self) -> None:
|
||||
raw, ok = QInputDialog.getText(
|
||||
self,
|
||||
"Create connection from link",
|
||||
"Paste connection link (vless:// trojan:// ss:// hysteria2:// hy2:// tuic:// wireguard://):",
|
||||
)
|
||||
if not ok:
|
||||
return
|
||||
payload = self._parse_connection_link_payload(raw)
|
||||
profile_name = str(payload.get("profile_name") or "").strip() or "SingBox Link"
|
||||
cid = self._create_singbox_connection(
|
||||
profile_name=profile_name,
|
||||
protocol=str(payload.get("protocol") or "vless"),
|
||||
raw_config=payload.get("raw_config") if isinstance(payload.get("raw_config"), dict) else None,
|
||||
editor_values=payload.get("editor_values") if isinstance(payload.get("editor_values"), dict) else None,
|
||||
auto_save=True,
|
||||
)
|
||||
self.on_singbox_profile_edit_dialog(cid)
|
||||
|
||||
def on_singbox_create_connection_manual(self) -> None:
|
||||
name, ok = QInputDialog.getText(
|
||||
self,
|
||||
"Create manual connection",
|
||||
"Connection name:",
|
||||
)
|
||||
if not ok:
|
||||
return
|
||||
profile_name = str(name or "").strip() or "SingBox Manual"
|
||||
proto_title, ok = QInputDialog.getItem(
|
||||
self,
|
||||
"Create manual connection",
|
||||
"Protocol:",
|
||||
[label for label, _pid in SINGBOX_EDITOR_PROTOCOL_OPTIONS],
|
||||
0,
|
||||
False,
|
||||
)
|
||||
if not ok:
|
||||
return
|
||||
proto_map = {label.lower(): pid for label, pid in SINGBOX_EDITOR_PROTOCOL_OPTIONS}
|
||||
proto = self._normalized_seed_protocol(proto_map.get(str(proto_title or "").strip().lower(), "vless"))
|
||||
cid = self._create_singbox_connection(
|
||||
profile_name=profile_name,
|
||||
protocol=proto,
|
||||
editor_values=self._seed_editor_values_for_protocol(proto, profile_name=profile_name),
|
||||
auto_save=False,
|
||||
)
|
||||
self.on_singbox_profile_edit_dialog(cid)
|
||||
Reference in New Issue
Block a user