Compare commits

...

9 Commits

Author SHA1 Message Date
Flowseal e81f0a973d fixes #1113 2026-07-08 00:43:13 +03:00
Flowseal 3143eb7147 docs upd 2026-07-07 21:57:06 +03:00
Danil c3309ed11b Поддержка тестового окружения Telegram (тестовые DC) (#1087) 2026-07-07 21:56:24 +03:00
Flowseal e9b74d7d7d optional art builds 2026-06-30 19:15:53 +03:00
Flowseal 050dcbce44 fixes #1072 #1060 2026-06-30 19:11:01 +03:00
Flowseal 4f9edf3072 Version bump 2026-06-27 01:22:19 +03:00
Flowseal 9ff95d1222 ip_timeout implementation 2026-06-27 01:19:27 +03:00
Flowseal 4c19a6cce4 release footer separator 2026-06-26 19:35:25 +03:00
Flowseal bb900e0c9e todo 2026-06-26 19:33:48 +03:00
17 changed files with 141 additions and 35 deletions
+38 -2
View File
@@ -12,6 +12,31 @@ on:
description: "Release version tag (e.g. v1.0.0)" description: "Release version tag (e.g. v1.0.0)"
required: false required: false
default: "v1.0.0" default: "v1.0.0"
build_windows_x64:
description: 'Build Windows x64?'
type: boolean
required: false
default: true
build_windows_arm64:
description: 'Build Windows ARM64?'
type: boolean
required: false
default: true
build_win7:
description: 'Build Windows 7 (x64 + x86)?'
type: boolean
required: false
default: true
build_macos:
description: 'Build macOS universal?'
type: boolean
required: false
default: true
build_linux:
description: 'Build Linux (amd64/.deb/.rpm)?'
type: boolean
required: false
default: true
permissions: permissions:
contents: write contents: write
@@ -19,6 +44,7 @@ permissions:
jobs: jobs:
build-windows-x64: build-windows-x64:
runs-on: windows-latest runs-on: windows-latest
if: ${{ github.event.inputs.build_windows_x64 == 'true' }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -78,6 +104,7 @@ jobs:
build-windows-arm64: build-windows-arm64:
runs-on: windows-11-arm runs-on: windows-11-arm
if: ${{ github.event.inputs.build_windows_arm64 == 'true' }}
env: env:
CRYPTOGRAPHY_VERSION: "46.0.5" CRYPTOGRAPHY_VERSION: "46.0.5"
ARM64_WHEELHOUSE: wheelhouse-arm64 ARM64_WHEELHOUSE: wheelhouse-arm64
@@ -154,6 +181,7 @@ jobs:
build-win7: build-win7:
runs-on: windows-latest runs-on: windows-latest
if: ${{ github.event.inputs.build_win7 == 'true' }}
strategy: strategy:
matrix: matrix:
include: include:
@@ -207,6 +235,7 @@ jobs:
build-macos: build-macos:
runs-on: macos-latest runs-on: macos-latest
if: ${{ github.event.inputs.build_macos == 'true' }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -345,6 +374,7 @@ jobs:
build-linux: build-linux:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.event.inputs.build_linux == 'true' }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -511,7 +541,13 @@ jobs:
release: release:
needs: [build-windows-x64, build-windows-arm64, build-win7, build-macos, build-linux] needs: [build-windows-x64, build-windows-arm64, build-win7, build-macos, build-linux]
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.event.inputs.make_release == 'true' }} if: >-
${{ github.event.inputs.make_release == 'true'
&& github.event.inputs.build_windows_x64 == 'true'
&& github.event.inputs.build_windows_arm64 == 'true'
&& github.event.inputs.build_win7 == 'true'
&& github.event.inputs.build_macos == 'true'
&& github.event.inputs.build_linux == 'true' }}
steps: steps:
- uses: actions/download-artifact@v8 - uses: actions/download-artifact@v8
with: with:
@@ -525,7 +561,7 @@ jobs:
tag_name: ${{ github.event.inputs.version }} tag_name: ${{ github.event.inputs.version }}
name: "TG WS Proxy ${{ github.event.inputs.version }}" name: "TG WS Proxy ${{ github.event.inputs.version }}"
body: | body: |
## ---
### [❤️ Поддержать развитие проекта](https://github.com/Flowseal/tg-ws-proxy/blob/main/docs/Funding.md) ### [❤️ Поддержать развитие проекта](https://github.com/Flowseal/tg-ws-proxy/blob/main/docs/Funding.md)
> [!TIP] > [!TIP]
+1
View File
@@ -46,6 +46,7 @@
- **[Docker](./README.docker.md)** - **[Docker](./README.docker.md)**
- [Настройка Cloudflare Worker'а (бесплатный аналог CF-прокси)](./CfWorker.md) - [Настройка Cloudflare Worker'а (бесплатный аналог CF-прокси)](./CfWorker.md)
- [Настройка Cloudflare-домена (CF-прокси)](./CfProxy.md) - [Настройка Cloudflare-домена (CF-прокси)](./CfProxy.md)
- [Тестовое окружение Telegram (тестовые DC)](./TestDc.md)
- [Fake TLS + upstream в Nginx](./FakeTlsNginx.md) - [Fake TLS + upstream в Nginx](./FakeTlsNginx.md)
- [Файлы конфигурации Tray-приложения](./TrayConfig.md) - [Файлы конфигурации Tray-приложения](./TrayConfig.md)
- [Установка из исходников](./BuildFromSource.md) - [Установка из исходников](./BuildFromSource.md)
+19
View File
@@ -0,0 +1,19 @@
# Тестовое окружение Telegram (тестовые DC)
Маршрутизация трафика к тестовым дата-центрам Telegram (test environment).
Нужна для разработки/тестирования ботов и клиентов в тестовой среде Telegram.
## Как включается
**Автоматически.** Telegram Desktop помечает тестовые DC сдвигом +10000
(1000110003). Прокси распознаёт сдвиг сам — включать ничего не нужно, можно
одновременно пользоваться продовым и тестовым аккаунтом в одном клиенте.
**Принудительно.** Для клиентов, которые сообщают тестовые DC как обычные 1-3
(Telethon, TDLib) — распознать их автоматически нельзя. Тогда весь трафик
принудительно направляется на тестовые DC (продовые аккаунты через этот прокси
работать перестанут). Для принудительной работы замените в конфиге force_test_dc на true или используйте флаг `--force-test-dc` в CLI
## Ограничения
Работает только для маршрутов **прямой DC → IP** и **Cloudflare Worker** (см. [Настройка Cloudflare Worker'а](./CfWorker.md)).
+1
View File
@@ -23,6 +23,7 @@ Tray-приложение хранит данные в:
"cfproxy": true, "cfproxy": true,
"cfproxy_user_domain": "", "cfproxy_user_domain": "",
"cfproxy_worker_domain": "", "cfproxy_worker_domain": "",
"force_test_dc": false,
"appearance": "auto" "appearance": "auto"
} }
``` ```
+2
View File
@@ -188,6 +188,8 @@ def _edit_config_dialog() -> None:
messagebox.showerror(t("app.error_title"), merged, parent=root) messagebox.showerror(t("app.error_title"), merged, parent=root)
return return
merged["force_test_dc"] = _config.get("force_test_dc", DEFAULT_CONFIG["force_test_dc"])
_ui_only_keys = {"appearance", "check_updates", "language"} _ui_only_keys = {"appearance", "check_updates", "language"}
config_changed = any(merged.get(k) != _config.get(k) for k in merged) config_changed = any(merged.get(k) != _config.get(k) for k in merged)
proxy_changed = any(merged.get(k) != _config.get(k) for k in merged if k not in _ui_only_keys) proxy_changed = any(merged.get(k) != _config.get(k) for k in merged if k not in _ui_only_keys)
+1
View File
@@ -475,6 +475,7 @@ def _edit_config_dialog() -> None:
"cfproxy": cfproxy, "cfproxy": cfproxy,
"cfproxy_user_domain": cfproxy_domains, "cfproxy_user_domain": cfproxy_domains,
"cfproxy_worker_domain": cfworker_domains, "cfproxy_worker_domain": cfworker_domains,
"force_test_dc": cfg.get("force_test_dc", DEFAULT_CONFIG["force_test_dc"]),
} }
save_config(new_cfg) save_config(new_cfg)
log.info("Config saved: %s", new_cfg) log.info("Config saved: %s", new_cfg)
+4 -4
View File
@@ -4,8 +4,8 @@
# http://msdn.microsoft.com/en-us/library/ms646997.aspx # http://msdn.microsoft.com/en-us/library/ms646997.aspx
VSVersionInfo( VSVersionInfo(
ffi=FixedFileInfo( ffi=FixedFileInfo(
filevers=(1, 8, 0, 0), filevers=(1, 8, 1, 0),
prodvers=(1, 8, 0, 0), prodvers=(1, 8, 1, 0),
mask=0x3f, mask=0x3f,
flags=0x0, flags=0x0,
OS=0x40004, OS=0x40004,
@@ -21,12 +21,12 @@ VSVersionInfo(
[ [
StringStruct(u'CompanyName', u'Flowseal'), StringStruct(u'CompanyName', u'Flowseal'),
StringStruct(u'FileDescription', u'Telegram Desktop WebSocket Bridge Proxy'), StringStruct(u'FileDescription', u'Telegram Desktop WebSocket Bridge Proxy'),
StringStruct(u'FileVersion', u'1.8.0.0'), StringStruct(u'FileVersion', u'1.8.1.0'),
StringStruct(u'InternalName', u'TgWsProxy'), StringStruct(u'InternalName', u'TgWsProxy'),
StringStruct(u'LegalCopyright', u'Copyright (c) Flowseal. MIT License.'), StringStruct(u'LegalCopyright', u'Copyright (c) Flowseal. MIT License.'),
StringStruct(u'OriginalFilename', u'TgWsProxy.exe'), StringStruct(u'OriginalFilename', u'TgWsProxy.exe'),
StringStruct(u'ProductName', u'TG WS Proxy'), StringStruct(u'ProductName', u'TG WS Proxy'),
StringStruct(u'ProductVersion', u'1.8.0.0'), StringStruct(u'ProductVersion', u'1.8.1.0'),
] ]
) )
] ]
+1 -1
View File
@@ -1,6 +1,6 @@
from .config import parse_dc_ip_list, proxy_config, coerce_domain_list 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.8.0" __version__ = "1.8.1"
__all__ = ["__version__", "get_link_host", "proxy_config", "parse_dc_ip_list", "build_github_opener", "coerce_domain_list"] __all__ = ["__version__", "get_link_host", "proxy_config", "parse_dc_ip_list", "build_github_opener", "coerce_domain_list"]
+8 -7
View File
@@ -130,10 +130,11 @@ class MsgSplitter:
async def do_fallback(reader, writer, relay_init, label, async def do_fallback(reader, writer, relay_init, label,
dc: int, is_media: bool, media_tag: str, dc: int, is_test_dc: bool, is_media: bool, media_tag: str,
ctx: CryptoCtx, splitter=None): ctx: CryptoCtx, splitter=None):
fallback_dst = DC_DEFAULT_IPS.get(dc) ip_table = DC_TEST_IPS if is_test_dc else DC_DEFAULT_IPS
use_cf = proxy_config.fallback_cfproxy fallback_dst = ip_table.get(dc)
use_cf = proxy_config.fallback_cfproxy and not is_test_dc
worker_domains = proxy_config.cfproxy_worker_domains worker_domains = proxy_config.cfproxy_worker_domains
methods: List[str] = [] methods: List[str] = []
@@ -149,8 +150,8 @@ async def do_fallback(reader, writer, relay_init, label,
if method == 'cf_worker' and fallback_dst: if method == 'cf_worker' and fallback_dst:
ok = await _cfproxy_worker_fallback( ok = await _cfproxy_worker_fallback(
reader, writer, relay_init, label, ctx, reader, writer, relay_init, label, ctx,
dc=dc, is_media=is_media, fallback_dst=fallback_dst, dc=dc, is_test_dc=is_test_dc, is_media=is_media,
splitter=splitter) fallback_dst=fallback_dst, splitter=splitter)
if ok: if ok:
return True return True
elif method == 'cf': elif method == 'cf':
@@ -173,7 +174,7 @@ async def do_fallback(reader, writer, relay_init, label,
async def _cfproxy_worker_fallback(reader, writer, relay_init, label, async def _cfproxy_worker_fallback(reader, writer, relay_init, label,
ctx: CryptoCtx, ctx: CryptoCtx,
dc: int, is_media: bool, dc: int, is_test_dc: bool, is_media: bool,
fallback_dst: str, fallback_dst: str,
splitter=None): splitter=None):
media_tag = ' media' if is_media else '' media_tag = ' media' if is_media else ''
@@ -184,7 +185,7 @@ async def _cfproxy_worker_fallback(reader, writer, relay_init, label,
random.shuffle(worker_domains) random.shuffle(worker_domains)
for worker_domain in worker_domains: for worker_domain in worker_domains:
ws = await cf_worker_pool.get(dc, worker_domain, fallback_dst) ws = None if is_test_dc else await cf_worker_pool.get(dc, worker_domain, fallback_dst)
if ws: if ws:
log.info("[%s] DC%d%s -> CF worker pool hit for %s", log.info("[%s] DC%d%s -> CF worker pool hit for %s",
label, dc, media_tag, fallback_dst) label, dc, media_tag, fallback_dst)
+1
View File
@@ -72,6 +72,7 @@ class ProxyConfig:
cfproxy_worker_domains: List[str] = field(default_factory=list) 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
force_test_dc: bool = False
proxy_config = ProxyConfig() proxy_config = ProxyConfig()
+43 -18
View File
@@ -33,13 +33,15 @@ from ._aes import Cipher, algorithms, modes
log = logging.getLogger('tg-mtproto-proxy') log = logging.getLogger('tg-mtproto-proxy')
DC_FAIL_COOLDOWN = 30.0 IP_FAIL_COOLDOWN = 3600.0
DC_FAIL_COOLDOWN = 60.0
WS_FAIL_TIMEOUT = 2.0 WS_FAIL_TIMEOUT = 2.0
FRONTING_COOLDOWN = 1800.0 FRONTING_COOLDOWN = 1800.0
LISTENER_CHECK_INTERVAL = 5.0 LISTENER_CHECK_INTERVAL = 5.0
LISTENER_RESTART_DELAY = 1.0 LISTENER_RESTART_DELAY = 1.0
ws_blacklist: Set[str] = set() ws_blacklist: Set[str] = set()
dc_fail_until: Dict[str, float] = {} dc_fail_until: Dict[str, float] = {}
ip_fail_until: Dict[str, float] = {}
fronting_until: float = 0.0 fronting_until: float = 0.0
@@ -278,6 +280,11 @@ async def _handle_client(reader, writer, secret: bytes):
dc, is_media, proto_tag, client_dec_prekey_iv = result dc, is_media, proto_tag, client_dec_prekey_iv = result
is_test_dc = proxy_config.force_test_dc or dc >= 10000
if dc >= 10000:
log.info("[%s] test DC%d -> DC%d", label, dc, dc - 10000)
dc -= 10000
if proto_tag == PROTO_TAG_ABRIDGED: if proto_tag == PROTO_TAG_ABRIDGED:
proto_int = PROTO_ABRIDGED_INT proto_int = PROTO_ABRIDGED_INT
elif proto_tag == PROTO_TAG_INTERMEDIATE: elif proto_tag == PROTO_TAG_INTERMEDIATE:
@@ -293,17 +300,27 @@ async def _handle_client(reader, writer, secret: bytes):
relay_init = _generate_relay_init(proto_tag, dc_idx) relay_init = _generate_relay_init(proto_tag, dc_idx)
ctx = _build_crypto_ctx(client_dec_prekey_iv, secret, relay_init) ctx = _build_crypto_ctx(client_dec_prekey_iv, secret, relay_init)
dc_key = f'{dc}{"m" if is_media else ""}' dc_key = f'{dc}{"t" if is_test_dc else ""}{"m" if is_media else ""}'
media_tag = " media" if is_media else "" media_tag = " media" if is_media else ""
now = time.monotonic()
ws_path = WS_PATH_TEST if is_test_dc else WS_PATH
target = proxy_config.dc_redirects.get(dc)
is_any_cf_fallback = proxy_config.fallback_cfproxy or proxy_config.cfproxy_worker_domains
# Fallback if DC not in config, if WS blacklisted for this DC/is_media or if connect to ip is timed out
if (dc not in proxy_config.dc_redirects
or dc_key in ws_blacklist
or now < ip_fail_until.get(target, 0) and is_any_cf_fallback):
# Fallback if DC not in config or WS blacklisted for this DC/is_media
if dc not in proxy_config.dc_redirects or dc_key in ws_blacklist:
if dc not in proxy_config.dc_redirects: if dc not in proxy_config.dc_redirects:
log.info("[%s] DC%d not in config -> fallback", log.info("[%s] DC%d not in config -> fallback",
label, dc) label, dc)
else: elif dc_key in ws_blacklist:
log.info("[%s] DC%d%s WS blacklisted -> fallback", log.info("[%s] DC%d%s WS blacklisted -> fallback",
label, dc, media_tag) label, dc, media_tag)
else:
log.info("[%s] DC%d%s WS connect to %s was timed out -> fallback",
label, dc, media_tag, target)
splitter = None splitter = None
try: try:
splitter = MsgSplitter(relay_init, proto_int) splitter = MsgSplitter(relay_init, proto_int)
@@ -311,26 +328,23 @@ async def _handle_client(reader, writer, secret: bytes):
pass pass
ok = await do_fallback( ok = await do_fallback(
clt_reader, clt_writer, relay_init, label, clt_reader, clt_writer, relay_init, label,
dc, is_media, media_tag, dc, is_test_dc, is_media, media_tag,
ctx, splitter=splitter) ctx, splitter=splitter)
if not ok: if not ok:
log.warning("[%s] DC%d%s no fallback available", log.warning("[%s] DC%d%s no fallback available",
label, dc, media_tag) label, dc, media_tag)
return return
now = time.monotonic() ws_timeout = WS_FAIL_TIMEOUT if now < dc_fail_until.get(dc_key, 0) else 5.0
fail_until = dc_fail_until.get(dc_key, 0)
ws_timeout = WS_FAIL_TIMEOUT if now < fail_until else 10.0
fronting_active = now < fronting_until fronting_active = now < fronting_until
domains = ws_domains(dc, is_media) domains = ws_domains(dc, is_media)
target = proxy_config.dc_redirects[dc]
ws = None ws = None
ws_failed_redirect = False ws_failed_redirect = False
ws_timed_out = False ws_timed_out = False
all_redirects = True all_redirects = True
ws = await ws_pool.get(dc, is_media, target, domains) ws = await ws_pool.get(dc, is_media, target, domains) if not is_test_dc else None
if ws: if ws:
log.info("[%s] DC%d%s -> pool hit via %s", log.info("[%s] DC%d%s -> pool hit via %s",
label, dc, media_tag, target) label, dc, media_tag, target)
@@ -340,7 +354,7 @@ async def _handle_client(reader, writer, secret: bytes):
label, dc, media_tag, domains[0]) label, dc, media_tag, domains[0])
try: try:
ws = await RawWebSocket.connect(target, domains[0], ws = await RawWebSocket.connect(target, domains[0],
timeout=10.0, timeout=5.0, path=ws_path,
sni="sprinthost.ru") sni="sprinthost.ru")
except Exception as exc: except Exception as exc:
stats.ws_errors += 1 stats.ws_errors += 1
@@ -355,12 +369,13 @@ async def _handle_client(reader, writer, secret: bytes):
ws_pool.fronting_until = 0.0 ws_pool.fronting_until = 0.0
else: else:
for domain in domains: for domain in domains:
url = f'wss://{domain}/apiws' url = f'wss://{domain}{ws_path}'
log.info("[%s] DC%d%s -> %s via %s", log.info("[%s] DC%d%s -> %s via %s",
label, dc, media_tag, url, target) label, dc, media_tag, url, target)
try: try:
ws = await RawWebSocket.connect(target, domain, ws = await RawWebSocket.connect(target, domain,
timeout=ws_timeout) timeout=ws_timeout,
path=ws_path)
all_redirects = False all_redirects = False
break break
except WsHandshakeError as exc: except WsHandshakeError as exc:
@@ -381,6 +396,7 @@ async def _handle_client(reader, writer, secret: bytes):
ws_timed_out = True ws_timed_out = True
log.warning("[%s] DC%d%s WS connect timed out via %s", log.warning("[%s] DC%d%s WS connect timed out via %s",
label, dc, media_tag, domain) label, dc, media_tag, domain)
break
except Exception as exc: except Exception as exc:
stats.ws_errors += 1 stats.ws_errors += 1
all_redirects = False all_redirects = False
@@ -395,7 +411,7 @@ async def _handle_client(reader, writer, secret: bytes):
label, dc, media_tag, domains[0]) label, dc, media_tag, domains[0])
try: try:
ws = await RawWebSocket.connect(target, domains[0], ws = await RawWebSocket.connect(target, domains[0],
timeout=10.0, timeout=5.0, path=ws_path,
sni="sprinthost.ru") sni="sprinthost.ru")
except Exception as exc: except Exception as exc:
stats.ws_errors += 1 stats.ws_errors += 1
@@ -410,6 +426,9 @@ async def _handle_client(reader, writer, secret: bytes):
# WS failed -> fallback # WS failed -> fallback
if ws is None: if ws is None:
if ws_timed_out:
ip_fail_until[target] = now + IP_FAIL_COOLDOWN
if ws_failed_redirect and all_redirects: if ws_failed_redirect and all_redirects:
ws_blacklist.add(dc_key) ws_blacklist.add(dc_key)
log.warning("[%s] DC%d%s blacklisted for WS (all 302)", log.warning("[%s] DC%d%s blacklisted for WS (all 302)",
@@ -418,8 +437,6 @@ async def _handle_client(reader, writer, secret: bytes):
dc_fail_until[dc_key] = now + DC_FAIL_COOLDOWN dc_fail_until[dc_key] = now + DC_FAIL_COOLDOWN
else: else:
dc_fail_until[dc_key] = now + DC_FAIL_COOLDOWN dc_fail_until[dc_key] = now + DC_FAIL_COOLDOWN
# TODO: may be don't try regular WS connection and do fallback instanstly
# instead of waiting for WS_FAIL_TIMEOUT and then fallback
log.info("[%s] DC%d%s WS cooldown for %ds", log.info("[%s] DC%d%s WS cooldown for %ds",
label, dc, media_tag, int(DC_FAIL_COOLDOWN)) label, dc, media_tag, int(DC_FAIL_COOLDOWN))
@@ -430,7 +447,7 @@ async def _handle_client(reader, writer, secret: bytes):
pass pass
ok = await do_fallback( ok = await do_fallback(
clt_reader, clt_writer, relay_init, label, clt_reader, clt_writer, relay_init, label,
dc, is_media, media_tag, dc, is_test_dc, is_media, media_tag,
ctx, splitter=splitter_fb) ctx, splitter=splitter_fb)
if ok: if ok:
log.info("[%s] DC%d%s fallback closed", log.info("[%s] DC%d%s fallback closed",
@@ -438,6 +455,7 @@ async def _handle_client(reader, writer, secret: bytes):
return return
dc_fail_until.pop(dc_key, None) dc_fail_until.pop(dc_key, None)
ip_fail_until.pop(target, None)
stats.connections_ws += 1 stats.connections_ws += 1
splitter = None splitter = None
@@ -491,6 +509,7 @@ async def _run(stop_event: Optional[asyncio.Event] = None):
cf_worker_pool.reset() cf_worker_pool.reset()
ws_blacklist.clear() ws_blacklist.clear()
dc_fail_until.clear() dc_fail_until.clear()
ip_fail_until.clear()
_client_tasks.clear() _client_tasks.clear()
fronting_until = 0.0 fronting_until = 0.0
@@ -691,6 +710,11 @@ def main():
metavar='DOMAIN', metavar='DOMAIN',
help='Enable Fake TLS (ee-secret) masking with the given ' help='Enable Fake TLS (ee-secret) masking with the given '
'SNI domain, e.g. example.com') 'SNI domain, e.g. example.com')
ap.add_argument('--force-test-dc', action='store_true',
help='Force ALL traffic to Telegram TEST datacenters. '
'Not needed for Telegram Desktop (test DCs 10001+ '
'are detected automatically); use for clients that '
'signal test DCs as plain 1-3')
ap.add_argument('--proxy-protocol', action='store_true', ap.add_argument('--proxy-protocol', action='store_true',
help='Accept PROXY protocol v1 header ' help='Accept PROXY protocol v1 header '
'(for use behind nginx/haproxy with proxy_protocol on)') '(for use behind nginx/haproxy with proxy_protocol on)')
@@ -730,6 +754,7 @@ def main():
proxy_config.cfproxy_worker_domains = coerce_domain_list(args.cfproxy_worker_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.fake_tls_domain = args.fake_tls_domain.strip()
proxy_config.proxy_protocol = args.proxy_protocol proxy_config.proxy_protocol = args.proxy_protocol
proxy_config.force_test_dc = args.force_test_dc
log_level = logging.DEBUG if args.verbose else logging.INFO log_level = logging.DEBUG if args.verbose else logging.INFO
log_fmt = logging.Formatter('%(asctime)s %(levelname)-5s %(message)s', log_fmt = logging.Formatter('%(asctime)s %(levelname)-5s %(message)s',
+9
View File
@@ -43,6 +43,15 @@ DC_DEFAULT_IPS: Dict[int, str] = {
203: '91.105.192.100' 203: '91.105.192.100'
} }
DC_TEST_IPS: Dict[int, str] = {
1: '149.154.175.10',
2: '149.154.167.40',
3: '149.154.175.117',
}
WS_PATH = '/apiws'
WS_PATH_TEST = WS_PATH + '_test'
def ws_domains(dc: int, is_media) -> List[str]: def ws_domains(dc: int, is_media) -> List[str]:
if dc == 203: if dc == 203:
+4 -1
View File
@@ -25,7 +25,10 @@ class LocaleEnum(str, Enum):
return _DEFAULT_LOCALE return _DEFAULT_LOCALE
_LOCALES_DIR = Path(__file__).resolve().parent try:
_LOCALES_DIR = Path(__file__).resolve(strict=False).parent
except OSError:
_LOCALES_DIR = Path(os.path.realpath(__file__)).parent
_DEFAULT_LOCALE = LocaleEnum.english _DEFAULT_LOCALE = LocaleEnum.english
_translations: Dict[str, str] = {} _translations: Dict[str, str] = {}
+1
View File
@@ -22,6 +22,7 @@ _TRAY_DEFAULTS_COMMON: Dict[str, Any] = {
"cfproxy": True, "cfproxy": True,
"cfproxy_user_domain": [], "cfproxy_user_domain": [],
"cfproxy_worker_domain": [], "cfproxy_worker_domain": [],
"force_test_dc": False,
"ws_keepalive_interval": 30, "ws_keepalive_interval": 30,
} }
+5 -1
View File
@@ -41,7 +41,10 @@ def _exe_dir() -> Optional[Path]:
return None return None
if not base: if not base:
return None return None
p = Path(base).resolve() try:
p = Path(base).resolve(strict=False)
except OSError:
p = Path(os.path.realpath(base))
return p.parent if p.is_file() else p return p.parent if p.is_file() else p
@@ -334,6 +337,7 @@ def apply_proxy_config(cfg: dict) -> bool:
pc.fallback_cfproxy = cfg.get("cfproxy", DEFAULT_CONFIG["cfproxy"]) pc.fallback_cfproxy = cfg.get("cfproxy", DEFAULT_CONFIG["cfproxy"])
pc.cfproxy_user_domains = coerce_domain_list(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_domains = coerce_domain_list(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"]))
pc.force_test_dc = cfg.get("force_test_dc", DEFAULT_CONFIG["force_test_dc"])
pc.ws_keepalive_interval = max(0, cfg.get("ws_keepalive_interval", DEFAULT_CONFIG["ws_keepalive_interval"])) pc.ws_keepalive_interval = max(0, cfg.get("ws_keepalive_interval", DEFAULT_CONFIG["ws_keepalive_interval"]))
return True return True
+2
View File
@@ -522,6 +522,8 @@ def _edit_config_dialog() -> None:
messagebox.showerror(t("app.error_title"), merged, parent=root) messagebox.showerror(t("app.error_title"), merged, parent=root)
return return
merged["force_test_dc"] = _config.get("force_test_dc", DEFAULT_CONFIG["force_test_dc"])
_ui_only_keys = {"appearance", "autostart", "check_updates", "language"} _ui_only_keys = {"appearance", "autostart", "check_updates", "language"}
config_changed = any(merged.get(k) != _config.get(k) for k in merged) config_changed = any(merged.get(k) != _config.get(k) for k in merged)
proxy_changed = any(merged.get(k) != _config.get(k) for k in merged if k not in _ui_only_keys) proxy_changed = any(merged.get(k) != _config.get(k) for k in merged if k not in _ui_only_keys)