mirror of
https://github.com/Flowseal/tg-ws-proxy.git
synced 2026-06-13 10:11:42 +03:00
Possibility to pass few cfproxy and worker domains
This commit is contained in:
@@ -47,8 +47,8 @@ tg-ws-proxy [--port PORT] [--host HOST] [--dc-ip DC:IP ...] [-v]
|
|||||||
| `--secret` | `random` | 32-значный hex-ключ для авторизации клиентов |
|
| `--secret` | `random` | 32-значный hex-ключ для авторизации клиентов |
|
||||||
| `--dc-ip` | `2:149.154.167.220`, `4:149.154.167.220` | Целевой IP для DC (параметр можно указывать несколько раз) |
|
| `--dc-ip` | `2:149.154.167.220`, `4:149.154.167.220` | Целевой IP для DC (параметр можно указывать несколько раз) |
|
||||||
| `--no-cfproxy` | `false` | Отключить попытку [проксирования через Cloudflare](./CfProxy.md) |
|
| `--no-cfproxy` | `false` | Отключить попытку [проксирования через Cloudflare](./CfProxy.md) |
|
||||||
| `--cfproxy-domain` | | Указать свой домен для проксирования через Cloudflare. [Подробнее](./CfProxy.md) |
|
| `--cfproxy-domain` | | Указать свой домен для проксирования через Cloudflare [Подробнее](./CfProxy.md). Можно указать несколько через повторение аргумента. |
|
||||||
| `--cfproxy-worker-domain` | | Домен Cloudflare Worker [Подробнее](./CfWorker.md) |
|
| `--cfproxy-worker-domain` | | Домен Cloudflare Worker [Подробнее](./CfWorker.md). Можно указать несколько через повторение аргумента. |
|
||||||
| `--fake-tls-domain` | | Включить маскировку Fake TLS (ee-secret) с указанным SNI-доменом |
|
| `--fake-tls-domain` | | Включить маскировку Fake TLS (ee-secret) с указанным SNI-доменом |
|
||||||
| `--proxy-protocol` | выкл. | Принимать HAProxy PROXY protocol v1 (для работы за nginx/haproxy с `proxy_protocol on`) |
|
| `--proxy-protocol` | выкл. | Принимать HAProxy PROXY protocol v1 (для работы за nginx/haproxy с `proxy_protocol on`) |
|
||||||
| `--buf-kb` | `256` | Размер буфера в КБ |
|
| `--buf-kb` | `256` | Размер буфера в КБ |
|
||||||
|
|||||||
21
macos.py
21
macos.py
@@ -24,7 +24,7 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
pyperclip = None
|
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 proxy.tg_ws_proxy import _run
|
||||||
|
|
||||||
from utils.tray_common import (
|
from utils.tray_common import (
|
||||||
@@ -115,7 +115,7 @@ def _ask_cfworker_domain(default: str) -> Optional[str]:
|
|||||||
value = default
|
value = default
|
||||||
while True:
|
while True:
|
||||||
script = (
|
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'default answer "{_esc(value)}" '
|
||||||
f'with title "TG WS Proxy" '
|
f'with title "TG WS Proxy" '
|
||||||
f'buttons {{"Закрыть", "?", "OK"}} '
|
f'buttons {{"Закрыть", "?", "OK"}} '
|
||||||
@@ -425,19 +425,24 @@ def _edit_config_dialog() -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
cfproxy_domain = _osascript_input(
|
cfproxy_domain = _osascript_input(
|
||||||
"Свой CF-домен (оставьте пустым для автоматического выбора):\n"
|
"Свои CF-домены через запятую (оставьте пустым для автоматического выбора):\n"
|
||||||
"DNS записи kws1-kws5,kws203 должны указывать на IP датацентров Telegram через Cloudflare.",
|
"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:
|
if cfproxy_domain is None:
|
||||||
return
|
return
|
||||||
cfproxy_domain = cfproxy_domain.strip()
|
cfproxy_domains = coerce_domain_list(cfproxy_domain)
|
||||||
|
|
||||||
cfworker_domain = _ask_cfworker_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:
|
if cfworker_domain is None:
|
||||||
return
|
return
|
||||||
|
cfworker_domains = coerce_domain_list(cfworker_domain)
|
||||||
|
|
||||||
new_cfg = {
|
new_cfg = {
|
||||||
"host": host,
|
"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"])),
|
"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),
|
"check_updates": cfg.get("check_updates", True),
|
||||||
"cfproxy": cfproxy,
|
"cfproxy": cfproxy,
|
||||||
"cfproxy_user_domain": cfproxy_domain,
|
"cfproxy_user_domain": cfproxy_domains,
|
||||||
"cfproxy_worker_domain": cfworker_domain,
|
"cfproxy_worker_domain": cfworker_domains,
|
||||||
}
|
}
|
||||||
save_config(new_cfg)
|
save_config(new_cfg)
|
||||||
log.info("Config saved: %s", new_cfg)
|
log.info("Config saved: %s", new_cfg)
|
||||||
|
|||||||
@@ -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
|
from .utils import get_link_host, build_github_opener
|
||||||
|
|
||||||
__version__ = "1.7.0"
|
__version__ = "1.7.0"
|
||||||
|
|
||||||
__all__ = ["__version__", "get_link_host", "proxy_config", "parse_dc_ip_list", "build_github_opener"]
|
__all__ = ["__version__", "get_link_host", "proxy_config", "parse_dc_ip_list", "build_github_opener", "coerce_domain_list"]
|
||||||
@@ -133,11 +133,11 @@ async def do_fallback(reader, writer, relay_init, label,
|
|||||||
ctx: CryptoCtx, splitter=None):
|
ctx: CryptoCtx, splitter=None):
|
||||||
fallback_dst = DC_DEFAULT_IPS.get(dc)
|
fallback_dst = DC_DEFAULT_IPS.get(dc)
|
||||||
use_cf = proxy_config.fallback_cfproxy
|
use_cf = proxy_config.fallback_cfproxy
|
||||||
worker_domain = proxy_config.cfproxy_worker_domain
|
worker_domains = proxy_config.cfproxy_worker_domains
|
||||||
|
|
||||||
methods: List[str] = []
|
methods: List[str] = []
|
||||||
|
|
||||||
if worker_domain and fallback_dst:
|
if worker_domains and fallback_dst:
|
||||||
methods.append('cf_worker')
|
methods.append('cf_worker')
|
||||||
if use_cf:
|
if use_cf:
|
||||||
methods.append('cf')
|
methods.append('cf')
|
||||||
@@ -176,38 +176,40 @@ async def _cfproxy_worker_fallback(reader, writer, relay_init, label,
|
|||||||
fallback_dst: str,
|
fallback_dst: str,
|
||||||
splitter=None):
|
splitter=None):
|
||||||
media_tag = ' media' if is_media else ''
|
media_tag = ' media' if is_media else ''
|
||||||
worker_domain = proxy_config.cfproxy_worker_domain
|
worker_domains = proxy_config.cfproxy_worker_domains
|
||||||
if not worker_domain:
|
if not worker_domains:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
ws = await cf_worker_pool.get(dc, worker_domain, fallback_dst)
|
for worker_domain in worker_domains:
|
||||||
if ws:
|
ws = await cf_worker_pool.get(dc, worker_domain, fallback_dst)
|
||||||
log.info("[%s] DC%d%s -> CF worker pool hit for %s",
|
if ws:
|
||||||
label, dc, media_tag, fallback_dst)
|
log.info("[%s] DC%d%s -> CF worker pool hit for %s",
|
||||||
else:
|
label, dc, media_tag, fallback_dst)
|
||||||
query = urlencode({
|
else:
|
||||||
'dst': fallback_dst,
|
query = urlencode({
|
||||||
'dc': str(dc),
|
'dst': fallback_dst,
|
||||||
})
|
'dc': str(dc),
|
||||||
path = f'/apiws?{query}'
|
})
|
||||||
|
path = f'/apiws?{query}'
|
||||||
|
|
||||||
log.info("[%s] DC%d%s -> trying CF worker for %s",
|
log.info("[%s] DC%d%s -> trying CF worker %s for %s",
|
||||||
label, dc, media_tag, fallback_dst)
|
label, dc, media_tag, worker_domain, fallback_dst)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ws = await RawWebSocket.connect(worker_domain, worker_domain,
|
ws = await RawWebSocket.connect(worker_domain, worker_domain,
|
||||||
timeout=10.0, path=path)
|
timeout=10.0, path=path)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.warning("[%s] DC%d%s CF worker failed: %s",
|
log.warning("[%s] DC%d%s CF worker %s failed: %s",
|
||||||
label, dc, media_tag, repr(exc))
|
label, dc, media_tag, worker_domain, repr(exc))
|
||||||
return False
|
continue
|
||||||
|
|
||||||
stats.connections_cfproxy += 1
|
stats.connections_cfproxy += 1
|
||||||
await ws.send(relay_init)
|
await ws.send(relay_init)
|
||||||
await bridge_ws_reencrypt(reader, writer, ws, label, ctx,
|
await bridge_ws_reencrypt(reader, writer, ws, label, ctx,
|
||||||
dc=dc, is_media=is_media,
|
dc=dc, is_media=is_media,
|
||||||
splitter=splitter)
|
splitter=splitter)
|
||||||
return True
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
async def _cfproxy_fallback(reader, writer, relay_init, label,
|
async def _cfproxy_fallback(reader, writer, relay_init, label,
|
||||||
|
|||||||
@@ -58,8 +58,8 @@ class ProxyConfig:
|
|||||||
buffer_size: int = 256 * 1024
|
buffer_size: int = 256 * 1024
|
||||||
pool_size: int = 4
|
pool_size: int = 4
|
||||||
fallback_cfproxy: bool = True
|
fallback_cfproxy: bool = True
|
||||||
cfproxy_user_domain: str = ''
|
cfproxy_user_domains: List[str] = field(default_factory=list)
|
||||||
cfproxy_worker_domain: str = ''
|
cfproxy_worker_domains: List[str] = field(default_factory=list)
|
||||||
fake_tls_domain: str = ''
|
fake_tls_domain: str = ''
|
||||||
proxy_protocol: bool = False
|
proxy_protocol: bool = False
|
||||||
|
|
||||||
@@ -67,6 +67,30 @@ class ProxyConfig:
|
|||||||
proxy_config = 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]:
|
def _fetch_cfproxy_domain_list() -> List[str]:
|
||||||
try:
|
try:
|
||||||
req = Request(CFPROXY_DOMAINS_URL + "?" + "".join(random.choices(string.ascii_letters, k=7)),
|
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:
|
def refresh_cfproxy_domains() -> None:
|
||||||
if proxy_config.cfproxy_user_domain:
|
if proxy_config.cfproxy_user_domains:
|
||||||
return
|
return
|
||||||
|
|
||||||
fetched = _fetch_cfproxy_domain_list()
|
fetched = _fetch_cfproxy_domain_list()
|
||||||
|
|||||||
@@ -114,16 +114,17 @@ class _CfWorkerPool:
|
|||||||
WS_POOL_MAX_AGE = 120.0
|
WS_POOL_MAX_AGE = 120.0
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._idle: Dict[int, deque] = {}
|
self._idle: Dict[Tuple[int, str], deque] = {}
|
||||||
self._refilling: Set[int] = set()
|
self._refilling: Set[Tuple[int, str]] = set()
|
||||||
|
|
||||||
async def get(self, dc: int, worker_domain: str, fallback_dst: str) -> Optional[RawWebSocket]:
|
async def get(self, dc: int, worker_domain: str, fallback_dst: str) -> Optional[RawWebSocket]:
|
||||||
now = time.monotonic()
|
now = time.monotonic()
|
||||||
|
key = (dc, worker_domain)
|
||||||
|
|
||||||
bucket = self._idle.get(dc)
|
bucket = self._idle.get(key)
|
||||||
if bucket is None:
|
if bucket is None:
|
||||||
bucket = deque()
|
bucket = deque()
|
||||||
self._idle[dc] = bucket
|
self._idle[key] = bucket
|
||||||
while bucket:
|
while bucket:
|
||||||
ws, created = bucket.popleft()
|
ws, created = bucket.popleft()
|
||||||
age = now - created
|
age = now - created
|
||||||
@@ -134,22 +135,23 @@ class _CfWorkerPool:
|
|||||||
stats.cf_pool_hits += 1
|
stats.cf_pool_hits += 1
|
||||||
log.debug("CF worker pool hit DC%d (age=%.1fs, left=%d)",
|
log.debug("CF worker pool hit DC%d (age=%.1fs, left=%d)",
|
||||||
dc, age, len(bucket))
|
dc, age, len(bucket))
|
||||||
self._schedule_refill(dc, worker_domain, fallback_dst)
|
self._schedule_refill(key, fallback_dst)
|
||||||
return ws
|
return ws
|
||||||
|
|
||||||
stats.cf_pool_misses += 1
|
stats.cf_pool_misses += 1
|
||||||
self._schedule_refill(dc, worker_domain, fallback_dst)
|
self._schedule_refill(key, fallback_dst)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _schedule_refill(self, dc, worker_domain, fallback_dst):
|
def _schedule_refill(self, key, fallback_dst):
|
||||||
if dc in self._refilling:
|
if key in self._refilling:
|
||||||
return
|
return
|
||||||
self._refilling.add(dc)
|
self._refilling.add(key)
|
||||||
asyncio.create_task(self._refill(dc, worker_domain, fallback_dst))
|
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:
|
try:
|
||||||
bucket = self._idle.setdefault(dc, deque())
|
bucket = self._idle.setdefault(key, deque())
|
||||||
needed = proxy_config.pool_size - len(bucket)
|
needed = proxy_config.pool_size - len(bucket)
|
||||||
if needed <= 0:
|
if needed <= 0:
|
||||||
return
|
return
|
||||||
@@ -166,7 +168,7 @@ class _CfWorkerPool:
|
|||||||
log.debug("CF worker pool refilled DC%d: %d ready",
|
log.debug("CF worker pool refilled DC%d: %d ready",
|
||||||
dc, len(bucket))
|
dc, len(bucket))
|
||||||
finally:
|
finally:
|
||||||
self._refilling.discard(dc)
|
self._refilling.discard(key)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _connect_one(worker_domain, fallback_dst, dc) -> Optional[RawWebSocket]:
|
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 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
|
return
|
||||||
|
|
||||||
for dc, fallback_dst in cf_fallbacks.items():
|
for worker_domain in proxy_config.cfproxy_worker_domains:
|
||||||
self._schedule_refill(dc, proxy_config.cfproxy_worker_domain, fallback_dst)
|
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))
|
log.info("CF worker pool warmup started for %d DC(s)", len(cf_fallbacks))
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ if __name__ == '__main__' and (__package__ is None or __package__ == ''):
|
|||||||
|
|
||||||
from .utils import *
|
from .utils import *
|
||||||
from .stats import stats
|
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 .bridge import MsgSplitter, CryptoCtx, do_fallback, bridge_ws_reencrypt
|
||||||
from .raw_websocket import RawWebSocket, WsHandshakeError, set_sock_opts
|
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
|
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()
|
_client_tasks.clear()
|
||||||
|
|
||||||
if proxy_config.fallback_cfproxy:
|
if proxy_config.fallback_cfproxy:
|
||||||
user = proxy_config.cfproxy_user_domain
|
user = proxy_config.cfproxy_user_domains
|
||||||
if user:
|
if user:
|
||||||
balancer.update_domains_list([user])
|
balancer.update_domains_list(user)
|
||||||
else:
|
else:
|
||||||
start_cfproxy_domain_refresh()
|
start_cfproxy_domain_refresh()
|
||||||
|
|
||||||
@@ -484,11 +484,11 @@ async def _run(stop_event: Optional[asyncio.Event] = None):
|
|||||||
ip = proxy_config.dc_redirects.get(dc)
|
ip = proxy_config.dc_redirects.get(dc)
|
||||||
log.info(" DC%d: %s", dc, ip)
|
log.info(" DC%d: %s", dc, ip)
|
||||||
if proxy_config.fallback_cfproxy:
|
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)
|
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)",
|
log.info(" CF worker: enabled (%s)",
|
||||||
proxy_config.cfproxy_worker_domain)
|
", ".join(proxy_config.cfproxy_worker_domains))
|
||||||
log.info("=" * 60)
|
log.info("=" * 60)
|
||||||
log.info(" Connect:")
|
log.info(" Connect:")
|
||||||
if ftls:
|
if ftls:
|
||||||
@@ -574,13 +574,15 @@ def main():
|
|||||||
help='Socket send/recv buffer size in KB (default 256)')
|
help='Socket send/recv buffer size in KB (default 256)')
|
||||||
ap.add_argument('--pool-size', type=int, default=4, metavar='N',
|
ap.add_argument('--pool-size', type=int, default=4, metavar='N',
|
||||||
help='WS connection pool size per DC (default 4, min 0)')
|
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',
|
metavar='DOMAIN',
|
||||||
help='User defined Cloudflare-proxied domain for WS fallback')
|
help='User defined Cloudflare-proxied domain for WS fallback '
|
||||||
ap.add_argument('--cfproxy-worker-domain', type=str, default='',
|
'(repeatable for multiple domains)')
|
||||||
|
ap.add_argument('--cfproxy-worker-domain', action='append', default=None,
|
||||||
metavar='DOMAIN',
|
metavar='DOMAIN',
|
||||||
help='Cloudflare Worker domain for WS fallback '
|
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',
|
ap.add_argument('--no-cfproxy', action='store_true',
|
||||||
help='Disable Cloudflare proxy fallback')
|
help='Disable Cloudflare proxy fallback')
|
||||||
ap.add_argument('--fake-tls-domain', type=str, default='',
|
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.buffer_size = max(4, args.buf_kb) * 1024
|
||||||
proxy_config.pool_size = max(0, args.pool_size)
|
proxy_config.pool_size = max(0, args.pool_size)
|
||||||
proxy_config.fallback_cfproxy = not args.no_cfproxy
|
proxy_config.fallback_cfproxy = not args.no_cfproxy
|
||||||
proxy_config.cfproxy_user_domain = args.cfproxy_domain.strip()
|
proxy_config.cfproxy_user_domains = coerce_domain_list(args.cfproxy_domain)
|
||||||
proxy_config.cfproxy_worker_domain = args.cfproxy_worker_domain.strip()
|
proxy_config.cfproxy_worker_domains = coerce_domain_list(args.cfproxy_worker_domain)
|
||||||
proxy_config.fake_tls_domain = args.fake_tls_domain.strip()
|
proxy_config.fake_tls_domain = args.fake_tls_domain.strip()
|
||||||
proxy_config.proxy_protocol = args.proxy_protocol
|
proxy_config.proxy_protocol = args.proxy_protocol
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import webbrowser
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
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 proxy.balancer import balancer
|
||||||
from utils.update_check import RELEASES_PAGE_URL, get_status
|
from utils.update_check import RELEASES_PAGE_URL, get_status
|
||||||
|
|
||||||
@@ -59,15 +59,17 @@ _TIP_CFPROXY = (
|
|||||||
"Использовать Cloudflare прокси для недоступных датацентров"
|
"Использовать Cloudflare прокси для недоступных датацентров"
|
||||||
)
|
)
|
||||||
_TIP_CFPROXY_DOMAIN = (
|
_TIP_CFPROXY_DOMAIN = (
|
||||||
"Ваш собственный домен, проксируемый через Cloudflare, для WS-подключения.\n"
|
"Ваши собственные домены, проксируемые через Cloudflare, для WS-подключения.\n"
|
||||||
"Если не указан — выбирается автоматически из поддерживаемых доменов"
|
"Несколько доменов указывайте через запятую.\n"
|
||||||
|
"Если не указаны — выбираются автоматически из поддерживаемых доменов"
|
||||||
)
|
)
|
||||||
_TIP_CFPROXY_USER_DOMAIN_CB = (
|
_TIP_CFPROXY_USER_DOMAIN_CB = (
|
||||||
"Указать свой домен вместо автоматического выбора"
|
"Указать свои домены вместо автоматического выбора"
|
||||||
)
|
)
|
||||||
_TIP_CFWORKER_DOMAIN = (
|
_TIP_CFWORKER_DOMAIN = (
|
||||||
"Домен Cloudflare Worker (например, name.account.workers.dev).\n"
|
"Домены Cloudflare Worker (например, name.account.workers.dev).\n"
|
||||||
"Прокси передает через него подключение к Telegram DC по IP"
|
"Несколько доменов указывайте через запятую.\n"
|
||||||
|
"Прокси передает через них подключение к Telegram DC по IP"
|
||||||
)
|
)
|
||||||
_TIP_SAVE = "Сохранить настройки"
|
_TIP_SAVE = "Сохранить настройки"
|
||||||
_TIP_CANCEL = "Закрыть окно без сохранения изменений"
|
_TIP_CANCEL = "Закрыть окно без сохранения изменений"
|
||||||
@@ -149,6 +151,14 @@ def _run_cfworker_connectivity_test(domain: str) -> dict:
|
|||||||
return _run_connectivity_test(cases)
|
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:
|
def _run_cfproxy_auto_test(domains: list) -> tuple:
|
||||||
merged: dict = {}
|
merged: dict = {}
|
||||||
best_domain = None
|
best_domain = None
|
||||||
@@ -207,6 +217,52 @@ def _show_connectivity_results(title_base: str, results: dict,
|
|||||||
_mb.showinfo(title, msg, parent=root)
|
_mb.showinfo(title, msg, parent=root)
|
||||||
root.destroy()
|
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
|
_INNER_W = 396
|
||||||
|
|
||||||
_APPEARANCE_OPTIONS = ["Авто", "Светлая", "Тёмная"]
|
_APPEARANCE_OPTIONS = ["Авто", "Светлая", "Тёмная"]
|
||||||
@@ -450,20 +506,23 @@ def install_tray_config_form(
|
|||||||
_cf_test_btn = [None]
|
_cf_test_btn = [None]
|
||||||
|
|
||||||
def _on_cf_test():
|
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]
|
btn = _cf_test_btn[0]
|
||||||
if btn:
|
if btn:
|
||||||
btn.configure(text="...", state="disabled")
|
btn.configure(text="...", state="disabled")
|
||||||
import threading as _threading
|
import threading as _threading
|
||||||
if user_domain:
|
if user_domains:
|
||||||
def _worker():
|
def _worker():
|
||||||
try:
|
try:
|
||||||
res = _run_cfproxy_connectivity_test(user_domain)
|
per = _run_cfproxy_multi_test(user_domains)
|
||||||
if btn:
|
if btn:
|
||||||
btn.after(
|
btn.after(
|
||||||
0,
|
0,
|
||||||
lambda: _show_connectivity_results(
|
lambda: _show_multi_connectivity_results(
|
||||||
"CF-прокси", res, domain=user_domain, label_prefix='kws',
|
"CF-прокси", per, label_prefix='kws',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
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 = ctk.CTkFrame(cf_inner, fg_color="transparent")
|
||||||
cf_custom_row.pack(fill="x")
|
cf_custom_row.pack(fill="x")
|
||||||
|
|
||||||
saved_user_domain = cfg.get("cfproxy_user_domain", default_config.get("cfproxy_user_domain", ""))
|
saved_user_domains = coerce_domain_list(
|
||||||
cf_custom_cb_var = ctk.BooleanVar(value=bool(saved_user_domain))
|
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 = _checkbox(ctk, cf_custom_row, theme, "Свой домен", cf_custom_cb_var)
|
||||||
cf_custom_cb.pack(side="left", padx=(0, 10))
|
cf_custom_cb.pack(side="left", padx=(0, 10))
|
||||||
attach_ctk_tooltip(cf_custom_cb, _TIP_CFPROXY_USER_DOMAIN_CB)
|
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),
|
command=lambda: webbrowser.open(_CFPROXY_HELP_URL),
|
||||||
).pack(side="right")
|
).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(
|
cf_domain_entry = _entry(
|
||||||
ctk, cf_custom_row, theme, var=cfproxy_user_domain_var,
|
ctk, cf_custom_row, theme, var=cfproxy_user_domain_var,
|
||||||
height=32, radius=8,
|
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 = ctk.CTkFrame(cf_worker_inner, fg_color="transparent")
|
||||||
cf_worker_row.pack(fill="x", pady=(0, 4))
|
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_lbl.pack(anchor="w", pady=(0, 2))
|
||||||
|
|
||||||
cf_worker_input = ctk.CTkFrame(cf_worker_inner, fg_color="transparent")
|
cf_worker_input = ctk.CTkFrame(cf_worker_inner, fg_color="transparent")
|
||||||
cf_worker_input.pack(fill="x")
|
cf_worker_input.pack(fill="x")
|
||||||
|
|
||||||
cfproxy_worker_domain_var = ctk.StringVar(
|
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(
|
cf_worker_entry = _entry(
|
||||||
ctk, cf_worker_input, theme, var=cfproxy_worker_domain_var,
|
ctk, cf_worker_input, theme, var=cfproxy_worker_domain_var,
|
||||||
@@ -565,24 +628,24 @@ def install_tray_config_form(
|
|||||||
btn = _cfworker_test_btn[0]
|
btn = _cfworker_test_btn[0]
|
||||||
if btn is None:
|
if btn is None:
|
||||||
return
|
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")
|
btn.configure(state="normal" if enabled else "disabled")
|
||||||
|
|
||||||
def _on_cfworker_test():
|
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]
|
btn = _cfworker_test_btn[0]
|
||||||
if not domain or btn is None:
|
if not domains or btn is None:
|
||||||
return
|
return
|
||||||
btn.configure(text="...", state="disabled")
|
btn.configure(text="...", state="disabled")
|
||||||
import threading as _threading
|
import threading as _threading
|
||||||
|
|
||||||
def _worker():
|
def _worker():
|
||||||
try:
|
try:
|
||||||
res = _run_cfworker_connectivity_test(domain)
|
per = _run_cfworker_multi_test(domains)
|
||||||
btn.after(
|
btn.after(
|
||||||
0,
|
0,
|
||||||
lambda: _show_connectivity_results(
|
lambda: _show_multi_connectivity_results(
|
||||||
"CF Worker", res, domain=domain, label_prefix='DC',
|
"CF Worker", per, label_prefix='DC',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
@@ -784,9 +847,9 @@ def validate_config_form(
|
|||||||
if widgets.cfproxy_var is not None:
|
if widgets.cfproxy_var is not None:
|
||||||
new_cfg["cfproxy"] = bool(widgets.cfproxy_var.get())
|
new_cfg["cfproxy"] = bool(widgets.cfproxy_var.get())
|
||||||
if widgets.cfproxy_user_domain_var is not None:
|
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:
|
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:
|
if widgets.appearance_var is not None:
|
||||||
new_cfg["appearance"] = _APPEARANCE_TO_CFG.get(widgets.appearance_var.get(), "auto")
|
new_cfg["appearance"] = _APPEARANCE_TO_CFG.get(widgets.appearance_var.get(), "auto")
|
||||||
return new_cfg
|
return new_cfg
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ _TRAY_DEFAULTS_COMMON: Dict[str, Any] = {
|
|||||||
"buf_kb": 256,
|
"buf_kb": 256,
|
||||||
"pool_size": 4,
|
"pool_size": 4,
|
||||||
"cfproxy": True,
|
"cfproxy": True,
|
||||||
"cfproxy_user_domain": "",
|
"cfproxy_user_domain": [],
|
||||||
"cfproxy_worker_domain": "",
|
"cfproxy_worker_domain": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from typing import Any, Callable, Dict, Optional, Tuple
|
|||||||
|
|
||||||
import psutil
|
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 proxy.tg_ws_proxy import _run
|
||||||
from utils.default_config import default_tray_config
|
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.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.pool_size = max(0, cfg.get("pool_size", DEFAULT_CONFIG["pool_size"]))
|
||||||
pc.fallback_cfproxy = cfg.get("cfproxy", DEFAULT_CONFIG["cfproxy"])
|
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_user_domains = coerce_domain_list(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_worker_domains = coerce_domain_list(cfg.get("cfproxy_worker_domain", DEFAULT_CONFIG["cfproxy_worker_domain"]))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user