From ea4e8e790aaf01b0d6b796b6b344d997dcccf16b Mon Sep 17 00:00:00 2001 From: Flowseal Date: Sat, 30 May 2026 20:30:47 +0300 Subject: [PATCH] Possibility to pass few cfproxy and worker domains --- docs/BuildFromSource.md | 4 +- macos.py | 21 +++++--- proxy/__init__.py | 4 +- proxy/bridge.py | 60 +++++++++++----------- proxy/config.py | 30 +++++++++-- proxy/pool.py | 39 +++++++------- proxy/tg_ws_proxy.py | 26 +++++----- ui/ctk_tray_ui.py | 111 +++++++++++++++++++++++++++++++--------- utils/default_config.py | 4 +- utils/tray_common.py | 6 +-- 10 files changed, 202 insertions(+), 103 deletions(-) diff --git a/docs/BuildFromSource.md b/docs/BuildFromSource.md index 3f80747..221441e 100644 --- a/docs/BuildFromSource.md +++ b/docs/BuildFromSource.md @@ -47,8 +47,8 @@ tg-ws-proxy [--port PORT] [--host HOST] [--dc-ip DC:IP ...] [-v] | `--secret` | `random` | 32-значный hex-ключ для авторизации клиентов | | `--dc-ip` | `2:149.154.167.220`, `4:149.154.167.220` | Целевой IP для DC (параметр можно указывать несколько раз) | | `--no-cfproxy` | `false` | Отключить попытку [проксирования через Cloudflare](./CfProxy.md) | -| `--cfproxy-domain` | | Указать свой домен для проксирования через Cloudflare. [Подробнее](./CfProxy.md) | -| `--cfproxy-worker-domain` | | Домен Cloudflare Worker [Подробнее](./CfWorker.md) | +| `--cfproxy-domain` | | Указать свой домен для проксирования через Cloudflare [Подробнее](./CfProxy.md). Можно указать несколько через повторение аргумента. | +| `--cfproxy-worker-domain` | | Домен Cloudflare Worker [Подробнее](./CfWorker.md). Можно указать несколько через повторение аргумента. | | `--fake-tls-domain` | | Включить маскировку Fake TLS (ee-secret) с указанным SNI-доменом | | `--proxy-protocol` | выкл. | Принимать HAProxy PROXY protocol v1 (для работы за nginx/haproxy с `proxy_protocol on`) | | `--buf-kb` | `256` | Размер буфера в КБ | diff --git a/macos.py b/macos.py index b0232c1..896e77f 100644 --- a/macos.py +++ b/macos.py @@ -24,7 +24,7 @@ try: except ImportError: pyperclip = None -from proxy import __version__, get_link_host, parse_dc_ip_list, proxy_config +from proxy import __version__, get_link_host, parse_dc_ip_list, proxy_config, coerce_domain_list from proxy.tg_ws_proxy import _run from utils.tray_common import ( @@ -115,7 +115,7 @@ def _ask_cfworker_domain(default: str) -> Optional[str]: value = default while True: script = ( - f'set d to display dialog "{_esc("Cloudflare Worker домен (например, name.account.workers.dev):")}" ' + f'set d to display dialog "{_esc("Cloudflare Worker домены через запятую (например, name.account.workers.dev):")}" ' f'default answer "{_esc(value)}" ' f'with title "TG WS Proxy" ' f'buttons {{"Закрыть", "?", "OK"}} ' @@ -425,19 +425,24 @@ def _edit_config_dialog() -> None: return cfproxy_domain = _osascript_input( - "Свой CF-домен (оставьте пустым для автоматического выбора):\n" + "Свои CF-домены через запятую (оставьте пустым для автоматического выбора):\n" "DNS записи kws1-kws5,kws203 должны указывать на IP датацентров Telegram через Cloudflare.", - cfg.get("cfproxy_user_domain", DEFAULT_CONFIG.get("cfproxy_user_domain", "")), + ", ".join(coerce_domain_list( + cfg.get("cfproxy_user_domain", DEFAULT_CONFIG.get("cfproxy_user_domain", [])) + )), ) if cfproxy_domain is None: return - cfproxy_domain = cfproxy_domain.strip() + cfproxy_domains = coerce_domain_list(cfproxy_domain) cfworker_domain = _ask_cfworker_domain( - cfg.get("cfproxy_worker_domain", DEFAULT_CONFIG.get("cfproxy_worker_domain", "")) + ", ".join(coerce_domain_list( + cfg.get("cfproxy_worker_domain", DEFAULT_CONFIG.get("cfproxy_worker_domain", [])) + )) ) if cfworker_domain is None: return + cfworker_domains = coerce_domain_list(cfworker_domain) new_cfg = { "host": host, @@ -450,8 +455,8 @@ def _edit_config_dialog() -> None: "log_max_mb": adv.get("log_max_mb", cfg.get("log_max_mb", DEFAULT_CONFIG["log_max_mb"])), "check_updates": cfg.get("check_updates", True), "cfproxy": cfproxy, - "cfproxy_user_domain": cfproxy_domain, - "cfproxy_worker_domain": cfworker_domain, + "cfproxy_user_domain": cfproxy_domains, + "cfproxy_worker_domain": cfworker_domains, } save_config(new_cfg) log.info("Config saved: %s", new_cfg) diff --git a/proxy/__init__.py b/proxy/__init__.py index d779200..f8b2e8e 100644 --- a/proxy/__init__.py +++ b/proxy/__init__.py @@ -1,6 +1,6 @@ -from .config import parse_dc_ip_list, proxy_config +from .config import parse_dc_ip_list, proxy_config, coerce_domain_list from .utils import get_link_host, build_github_opener __version__ = "1.7.0" -__all__ = ["__version__", "get_link_host", "proxy_config", "parse_dc_ip_list", "build_github_opener"] \ No newline at end of file +__all__ = ["__version__", "get_link_host", "proxy_config", "parse_dc_ip_list", "build_github_opener", "coerce_domain_list"] \ No newline at end of file diff --git a/proxy/bridge.py b/proxy/bridge.py index 70e71e8..b038f54 100644 --- a/proxy/bridge.py +++ b/proxy/bridge.py @@ -133,11 +133,11 @@ async def do_fallback(reader, writer, relay_init, label, ctx: CryptoCtx, splitter=None): fallback_dst = DC_DEFAULT_IPS.get(dc) use_cf = proxy_config.fallback_cfproxy - worker_domain = proxy_config.cfproxy_worker_domain + worker_domains = proxy_config.cfproxy_worker_domains methods: List[str] = [] - if worker_domain and fallback_dst: + if worker_domains and fallback_dst: methods.append('cf_worker') if use_cf: methods.append('cf') @@ -176,38 +176,40 @@ async def _cfproxy_worker_fallback(reader, writer, relay_init, label, fallback_dst: str, splitter=None): media_tag = ' media' if is_media else '' - worker_domain = proxy_config.cfproxy_worker_domain - if not worker_domain: + worker_domains = proxy_config.cfproxy_worker_domains + if not worker_domains: return False - ws = await cf_worker_pool.get(dc, worker_domain, fallback_dst) - if ws: - log.info("[%s] DC%d%s -> CF worker pool hit for %s", - label, dc, media_tag, fallback_dst) - else: - query = urlencode({ - 'dst': fallback_dst, - 'dc': str(dc), - }) - path = f'/apiws?{query}' + for worker_domain in worker_domains: + ws = await cf_worker_pool.get(dc, worker_domain, fallback_dst) + if ws: + log.info("[%s] DC%d%s -> CF worker pool hit for %s", + label, dc, media_tag, fallback_dst) + else: + query = urlencode({ + 'dst': fallback_dst, + 'dc': str(dc), + }) + path = f'/apiws?{query}' - log.info("[%s] DC%d%s -> trying CF worker for %s", - label, dc, media_tag, fallback_dst) + log.info("[%s] DC%d%s -> trying CF worker %s for %s", + label, dc, media_tag, worker_domain, fallback_dst) - try: - ws = await RawWebSocket.connect(worker_domain, worker_domain, - timeout=10.0, path=path) - except Exception as exc: - log.warning("[%s] DC%d%s CF worker failed: %s", - label, dc, media_tag, repr(exc)) - return False + try: + ws = await RawWebSocket.connect(worker_domain, worker_domain, + timeout=10.0, path=path) + except Exception as exc: + log.warning("[%s] DC%d%s CF worker %s failed: %s", + label, dc, media_tag, worker_domain, repr(exc)) + continue - stats.connections_cfproxy += 1 - await ws.send(relay_init) - await bridge_ws_reencrypt(reader, writer, ws, label, ctx, - dc=dc, is_media=is_media, - splitter=splitter) - return True + stats.connections_cfproxy += 1 + await ws.send(relay_init) + await bridge_ws_reencrypt(reader, writer, ws, label, ctx, + dc=dc, is_media=is_media, + splitter=splitter) + return True + return False async def _cfproxy_fallback(reader, writer, relay_init, label, diff --git a/proxy/config.py b/proxy/config.py index 907a4b2..28afe92 100644 --- a/proxy/config.py +++ b/proxy/config.py @@ -58,8 +58,8 @@ class ProxyConfig: buffer_size: int = 256 * 1024 pool_size: int = 4 fallback_cfproxy: bool = True - cfproxy_user_domain: str = '' - cfproxy_worker_domain: str = '' + cfproxy_user_domains: List[str] = field(default_factory=list) + cfproxy_worker_domains: List[str] = field(default_factory=list) fake_tls_domain: str = '' proxy_protocol: bool = False @@ -67,6 +67,30 @@ class ProxyConfig: proxy_config = ProxyConfig() +def coerce_domain_list(value) -> List[str]: + if isinstance(value, str): + items = value.replace(',', ' ').replace(';', ' ').split() + elif isinstance(value, (list, tuple)): + items: List[str] = [] + for entry in value: + if isinstance(entry, str): + items.extend(entry.replace(',', ' ').replace(';', ' ').split()) + else: + return [] + seen = set() + result: List[str] = [] + for item in items: + item = item.strip() + if not item: + continue + key = item.lower() + if key in seen: + continue + seen.add(key) + result.append(item) + return result + + def _fetch_cfproxy_domain_list() -> List[str]: try: req = Request(CFPROXY_DOMAINS_URL + "?" + "".join(random.choices(string.ascii_letters, k=7)), @@ -120,7 +144,7 @@ def _normalize_domain_pool(domains: List[str]) -> List[str]: def refresh_cfproxy_domains() -> None: - if proxy_config.cfproxy_user_domain: + if proxy_config.cfproxy_user_domains: return fetched = _fetch_cfproxy_domain_list() diff --git a/proxy/pool.py b/proxy/pool.py index 20d3151..de2727f 100644 --- a/proxy/pool.py +++ b/proxy/pool.py @@ -114,16 +114,17 @@ class _CfWorkerPool: WS_POOL_MAX_AGE = 120.0 def __init__(self): - self._idle: Dict[int, deque] = {} - self._refilling: Set[int] = set() + self._idle: Dict[Tuple[int, str], deque] = {} + self._refilling: Set[Tuple[int, str]] = set() async def get(self, dc: int, worker_domain: str, fallback_dst: str) -> Optional[RawWebSocket]: now = time.monotonic() + key = (dc, worker_domain) - bucket = self._idle.get(dc) + bucket = self._idle.get(key) if bucket is None: bucket = deque() - self._idle[dc] = bucket + self._idle[key] = bucket while bucket: ws, created = bucket.popleft() age = now - created @@ -134,22 +135,23 @@ class _CfWorkerPool: stats.cf_pool_hits += 1 log.debug("CF worker pool hit DC%d (age=%.1fs, left=%d)", dc, age, len(bucket)) - self._schedule_refill(dc, worker_domain, fallback_dst) + self._schedule_refill(key, fallback_dst) return ws stats.cf_pool_misses += 1 - self._schedule_refill(dc, worker_domain, fallback_dst) + self._schedule_refill(key, fallback_dst) return None - def _schedule_refill(self, dc, worker_domain, fallback_dst): - if dc in self._refilling: + def _schedule_refill(self, key, fallback_dst): + if key in self._refilling: return - self._refilling.add(dc) - asyncio.create_task(self._refill(dc, worker_domain, fallback_dst)) + self._refilling.add(key) + asyncio.create_task(self._refill(key, fallback_dst)) - async def _refill(self, dc, worker_domain, fallback_dst): + async def _refill(self, key, fallback_dst): + dc, worker_domain = key try: - bucket = self._idle.setdefault(dc, deque()) + bucket = self._idle.setdefault(key, deque()) needed = proxy_config.pool_size - len(bucket) if needed <= 0: return @@ -166,7 +168,7 @@ class _CfWorkerPool: log.debug("CF worker pool refilled DC%d: %d ready", dc, len(bucket)) finally: - self._refilling.discard(dc) + self._refilling.discard(key) @staticmethod async def _connect_one(worker_domain, fallback_dst, dc) -> Optional[RawWebSocket]: @@ -194,12 +196,13 @@ class _CfWorkerPool: if dc not in proxy_config.dc_redirects } - if not cf_fallbacks or not proxy_config.cfproxy_worker_domain: + if not cf_fallbacks or not proxy_config.cfproxy_worker_domains: return - - for dc, fallback_dst in cf_fallbacks.items(): - self._schedule_refill(dc, proxy_config.cfproxy_worker_domain, fallback_dst) - + + for worker_domain in proxy_config.cfproxy_worker_domains: + for dc, fallback_dst in cf_fallbacks.items(): + self._schedule_refill((dc, worker_domain), fallback_dst) + log.info("CF worker pool warmup started for %d DC(s)", len(cf_fallbacks)) def reset(self): diff --git a/proxy/tg_ws_proxy.py b/proxy/tg_ws_proxy.py index ea72a2e..daee01e 100644 --- a/proxy/tg_ws_proxy.py +++ b/proxy/tg_ws_proxy.py @@ -22,7 +22,7 @@ if __name__ == '__main__' and (__package__ is None or __package__ == ''): from .utils import * from .stats import stats -from .config import proxy_config, parse_dc_ip_list, start_cfproxy_domain_refresh +from .config import proxy_config, parse_dc_ip_list, start_cfproxy_domain_refresh, coerce_domain_list from .bridge import MsgSplitter, CryptoCtx, do_fallback, bridge_ws_reencrypt from .raw_websocket import RawWebSocket, WsHandshakeError, set_sock_opts from .fake_tls import proxy_to_masking_domain, verify_client_hello, build_server_hello, FakeTlsStream, TLS_RECORD_HANDSHAKE @@ -439,9 +439,9 @@ async def _run(stop_event: Optional[asyncio.Event] = None): _client_tasks.clear() if proxy_config.fallback_cfproxy: - user = proxy_config.cfproxy_user_domain + user = proxy_config.cfproxy_user_domains if user: - balancer.update_domains_list([user]) + balancer.update_domains_list(user) else: start_cfproxy_domain_refresh() @@ -484,11 +484,11 @@ async def _run(stop_event: Optional[asyncio.Event] = None): ip = proxy_config.dc_redirects.get(dc) log.info(" DC%d: %s", dc, ip) if proxy_config.fallback_cfproxy: - user_domain = "user" if proxy_config.cfproxy_user_domain else "auto" + user_domain = "user" if proxy_config.cfproxy_user_domains else "auto" log.info(" CF proxy: enabled (%s)", user_domain) - if proxy_config.cfproxy_worker_domain: + if proxy_config.cfproxy_worker_domains: log.info(" CF worker: enabled (%s)", - proxy_config.cfproxy_worker_domain) + ", ".join(proxy_config.cfproxy_worker_domains)) log.info("=" * 60) log.info(" Connect:") if ftls: @@ -574,13 +574,15 @@ def main(): help='Socket send/recv buffer size in KB (default 256)') ap.add_argument('--pool-size', type=int, default=4, metavar='N', help='WS connection pool size per DC (default 4, min 0)') - ap.add_argument('--cfproxy-domain', type=str, default='', + ap.add_argument('--cfproxy-domain', action='append', default=None, metavar='DOMAIN', - help='User defined Cloudflare-proxied domain for WS fallback') - ap.add_argument('--cfproxy-worker-domain', type=str, default='', + help='User defined Cloudflare-proxied domain for WS fallback ' + '(repeatable for multiple domains)') + ap.add_argument('--cfproxy-worker-domain', action='append', default=None, metavar='DOMAIN', help='Cloudflare Worker domain for WS fallback ' - '(tried before other fallback methods)') + '(tried before other fallback methods, ' + 'repeatable for multiple domains)') ap.add_argument('--no-cfproxy', action='store_true', help='Disable Cloudflare proxy fallback') ap.add_argument('--fake-tls-domain', type=str, default='', @@ -622,8 +624,8 @@ def main(): proxy_config.buffer_size = max(4, args.buf_kb) * 1024 proxy_config.pool_size = max(0, args.pool_size) proxy_config.fallback_cfproxy = not args.no_cfproxy - proxy_config.cfproxy_user_domain = args.cfproxy_domain.strip() - proxy_config.cfproxy_worker_domain = args.cfproxy_worker_domain.strip() + proxy_config.cfproxy_user_domains = coerce_domain_list(args.cfproxy_domain) + proxy_config.cfproxy_worker_domains = coerce_domain_list(args.cfproxy_worker_domain) proxy_config.fake_tls_domain = args.fake_tls_domain.strip() proxy_config.proxy_protocol = args.proxy_protocol diff --git a/ui/ctk_tray_ui.py b/ui/ctk_tray_ui.py index 4b58b66..4df2e8e 100644 --- a/ui/ctk_tray_ui.py +++ b/ui/ctk_tray_ui.py @@ -6,7 +6,7 @@ import webbrowser from dataclasses import dataclass from typing import Any, Callable, Dict, List, Optional, Tuple, Union -from proxy import __version__, get_link_host, parse_dc_ip_list +from proxy import __version__, get_link_host, parse_dc_ip_list, coerce_domain_list from proxy.balancer import balancer from utils.update_check import RELEASES_PAGE_URL, get_status @@ -59,15 +59,17 @@ _TIP_CFPROXY = ( "Использовать Cloudflare прокси для недоступных датацентров" ) _TIP_CFPROXY_DOMAIN = ( - "Ваш собственный домен, проксируемый через Cloudflare, для WS-подключения.\n" - "Если не указан — выбирается автоматически из поддерживаемых доменов" + "Ваши собственные домены, проксируемые через Cloudflare, для WS-подключения.\n" + "Несколько доменов указывайте через запятую.\n" + "Если не указаны — выбираются автоматически из поддерживаемых доменов" ) _TIP_CFPROXY_USER_DOMAIN_CB = ( - "Указать свой домен вместо автоматического выбора" + "Указать свои домены вместо автоматического выбора" ) _TIP_CFWORKER_DOMAIN = ( - "Домен Cloudflare Worker (например, name.account.workers.dev).\n" - "Прокси передает через него подключение к Telegram DC по IP" + "Домены Cloudflare Worker (например, name.account.workers.dev).\n" + "Несколько доменов указывайте через запятую.\n" + "Прокси передает через них подключение к Telegram DC по IP" ) _TIP_SAVE = "Сохранить настройки" _TIP_CANCEL = "Закрыть окно без сохранения изменений" @@ -149,6 +151,14 @@ def _run_cfworker_connectivity_test(domain: str) -> dict: return _run_connectivity_test(cases) +def _run_cfproxy_multi_test(domains: list) -> dict: + return {domain: _run_cfproxy_connectivity_test(domain) for domain in domains} + + +def _run_cfworker_multi_test(domains: list) -> dict: + return {domain: _run_cfworker_connectivity_test(domain) for domain in domains} + + def _run_cfproxy_auto_test(domains: list) -> tuple: merged: dict = {} best_domain = None @@ -207,6 +217,52 @@ def _show_connectivity_results(title_base: str, results: dict, _mb.showinfo(title, msg, parent=root) root.destroy() + +def _show_multi_connectivity_results(title_base: str, per_domain: dict, + label_prefix: str = 'DC') -> None: + import tkinter as _tk + from tkinter import messagebox as _mb + + total = len(_CFPROXY_TEST_DCS) + all_ok = True + any_ok = False + blocks = [] + for domain, results in per_domain.items(): + ok = [dc for dc, v in results.items() if v is True] + fail = [(dc, v) for dc, v in results.items() if v is not True] + if len(ok) == total: + any_ok = True + blocks.append(f"\u2713 {domain}: все {total} серверов доступны") + elif not ok: + all_ok = False + blocks.append(f"\u2717 {domain}: недоступен") + else: + all_ok = False + any_ok = True + blocks.append( + f"~ {domain}: работают " + f"{', '.join(f'{label_prefix}{dc}' for dc in ok)}; " + f"недоступны " + f"{', '.join(f'{label_prefix}{dc}' for dc, _ in fail)}" + ) + + if all_ok: + title = f"{title_base}: всё работает" + elif any_ok: + title = f"{title_base}: частично работает" + else: + title = f"{title_base}: недоступен" + msg = "\n\n".join(blocks) + + root = _tk.Tk() + root.withdraw() + try: + root.attributes("-topmost", True) + except Exception: + pass + _mb.showinfo(title, msg, parent=root) + root.destroy() + _INNER_W = 396 _APPEARANCE_OPTIONS = ["Авто", "Светлая", "Тёмная"] @@ -450,20 +506,23 @@ def install_tray_config_form( _cf_test_btn = [None] def _on_cf_test(): - user_domain = cfproxy_user_domain_var.get().strip() if cf_custom_cb_var.get() else "" + user_domains = ( + coerce_domain_list(cfproxy_user_domain_var.get()) + if cf_custom_cb_var.get() else [] + ) btn = _cf_test_btn[0] if btn: btn.configure(text="...", state="disabled") import threading as _threading - if user_domain: + if user_domains: def _worker(): try: - res = _run_cfproxy_connectivity_test(user_domain) + per = _run_cfproxy_multi_test(user_domains) if btn: btn.after( 0, - lambda: _show_connectivity_results( - "CF-прокси", res, domain=user_domain, label_prefix='kws', + lambda: _show_multi_connectivity_results( + "CF-прокси", per, label_prefix='kws', ), ) except Exception as exc: @@ -508,8 +567,10 @@ def install_tray_config_form( cf_custom_row = ctk.CTkFrame(cf_inner, fg_color="transparent") cf_custom_row.pack(fill="x") - saved_user_domain = cfg.get("cfproxy_user_domain", default_config.get("cfproxy_user_domain", "")) - cf_custom_cb_var = ctk.BooleanVar(value=bool(saved_user_domain)) + saved_user_domains = coerce_domain_list( + cfg.get("cfproxy_user_domain", default_config.get("cfproxy_user_domain", "")) + ) + cf_custom_cb_var = ctk.BooleanVar(value=bool(saved_user_domains)) cf_custom_cb = _checkbox(ctk, cf_custom_row, theme, "Свой домен", cf_custom_cb_var) cf_custom_cb.pack(side="left", padx=(0, 10)) attach_ctk_tooltip(cf_custom_cb, _TIP_CFPROXY_USER_DOMAIN_CB) @@ -522,7 +583,7 @@ def install_tray_config_form( command=lambda: webbrowser.open(_CFPROXY_HELP_URL), ).pack(side="right") - cfproxy_user_domain_var = ctk.StringVar(value=saved_user_domain) + cfproxy_user_domain_var = ctk.StringVar(value=", ".join(saved_user_domains)) cf_domain_entry = _entry( ctk, cf_custom_row, theme, var=cfproxy_user_domain_var, height=32, radius=8, @@ -543,14 +604,16 @@ def install_tray_config_form( cf_worker_row = ctk.CTkFrame(cf_worker_inner, fg_color="transparent") cf_worker_row.pack(fill="x", pady=(0, 4)) - cf_worker_lbl = _label(ctk, cf_worker_row, theme, "Cloudflare Worker домен", size=11) + cf_worker_lbl = _label(ctk, cf_worker_row, theme, "Cloudflare Worker домены (через запятую)", size=11) cf_worker_lbl.pack(anchor="w", pady=(0, 2)) cf_worker_input = ctk.CTkFrame(cf_worker_inner, fg_color="transparent") cf_worker_input.pack(fill="x") cfproxy_worker_domain_var = ctk.StringVar( - value=cfg.get("cfproxy_worker_domain", default_config.get("cfproxy_worker_domain", "")) + value=", ".join(coerce_domain_list( + cfg.get("cfproxy_worker_domain", default_config.get("cfproxy_worker_domain", "")) + )) ) cf_worker_entry = _entry( ctk, cf_worker_input, theme, var=cfproxy_worker_domain_var, @@ -565,24 +628,24 @@ def install_tray_config_form( btn = _cfworker_test_btn[0] if btn is None: return - enabled = bool(cfproxy_worker_domain_var.get().strip()) + enabled = bool(coerce_domain_list(cfproxy_worker_domain_var.get())) btn.configure(state="normal" if enabled else "disabled") def _on_cfworker_test(): - domain = cfproxy_worker_domain_var.get().strip() + domains = coerce_domain_list(cfproxy_worker_domain_var.get()) btn = _cfworker_test_btn[0] - if not domain or btn is None: + if not domains or btn is None: return btn.configure(text="...", state="disabled") import threading as _threading def _worker(): try: - res = _run_cfworker_connectivity_test(domain) + per = _run_cfworker_multi_test(domains) btn.after( 0, - lambda: _show_connectivity_results( - "CF Worker", res, domain=domain, label_prefix='DC', + lambda: _show_multi_connectivity_results( + "CF Worker", per, label_prefix='DC', ), ) except Exception as exc: @@ -784,9 +847,9 @@ def validate_config_form( if widgets.cfproxy_var is not None: new_cfg["cfproxy"] = bool(widgets.cfproxy_var.get()) if widgets.cfproxy_user_domain_var is not None: - new_cfg["cfproxy_user_domain"] = widgets.cfproxy_user_domain_var.get().strip() + new_cfg["cfproxy_user_domain"] = coerce_domain_list(widgets.cfproxy_user_domain_var.get()) if widgets.cfproxy_worker_domain_var is not None: - new_cfg["cfproxy_worker_domain"] = widgets.cfproxy_worker_domain_var.get().strip() + new_cfg["cfproxy_worker_domain"] = coerce_domain_list(widgets.cfproxy_worker_domain_var.get()) if widgets.appearance_var is not None: new_cfg["appearance"] = _APPEARANCE_TO_CFG.get(widgets.appearance_var.get(), "auto") return new_cfg diff --git a/utils/default_config.py b/utils/default_config.py index fa6d427..097706a 100644 --- a/utils/default_config.py +++ b/utils/default_config.py @@ -18,8 +18,8 @@ _TRAY_DEFAULTS_COMMON: Dict[str, Any] = { "buf_kb": 256, "pool_size": 4, "cfproxy": True, - "cfproxy_user_domain": "", - "cfproxy_worker_domain": "", + "cfproxy_user_domain": [], + "cfproxy_worker_domain": [], } diff --git a/utils/tray_common.py b/utils/tray_common.py index 6595fb2..e553a35 100644 --- a/utils/tray_common.py +++ b/utils/tray_common.py @@ -14,7 +14,7 @@ from typing import Any, Callable, Dict, Optional, Tuple import psutil -from proxy import __version__, get_link_host, parse_dc_ip_list, proxy_config +from proxy import __version__, get_link_host, parse_dc_ip_list, proxy_config, coerce_domain_list from proxy.tg_ws_proxy import _run from utils.default_config import default_tray_config @@ -271,8 +271,8 @@ def apply_proxy_config(cfg: dict) -> bool: pc.buffer_size = max(4, cfg.get("buf_kb", DEFAULT_CONFIG["buf_kb"])) * 1024 pc.pool_size = max(0, cfg.get("pool_size", DEFAULT_CONFIG["pool_size"])) pc.fallback_cfproxy = cfg.get("cfproxy", DEFAULT_CONFIG["cfproxy"]) - pc.cfproxy_user_domain = cfg.get("cfproxy_user_domain", DEFAULT_CONFIG["cfproxy_user_domain"]) - pc.cfproxy_worker_domain = cfg.get("cfproxy_worker_domain", DEFAULT_CONFIG["cfproxy_worker_domain"]) + pc.cfproxy_user_domains = coerce_domain_list(cfg.get("cfproxy_user_domain", DEFAULT_CONFIG["cfproxy_user_domain"])) + pc.cfproxy_worker_domains = coerce_domain_list(cfg.get("cfproxy_worker_domain", DEFAULT_CONFIG["cfproxy_worker_domain"])) return True