mirror of
https://github.com/Flowseal/tg-ws-proxy.git
synced 2026-08-01 08:30:36 +03:00
Compare commits
5 Commits
8ac52f69b3
...
v1.9.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 21aaeb3aba | |||
| e0230ebda7 | |||
| 47b8db18d9 | |||
| a0545cca64 | |||
| 129a760996 |
@@ -236,6 +236,8 @@ jobs:
|
|||||||
build-macos:
|
build-macos:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
if: ${{ github.event.inputs.build_macos == 'true' }}
|
if: ${{ github.event.inputs.build_macos == 'true' }}
|
||||||
|
env:
|
||||||
|
CFFI_VERSION: "2.0.0"
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
@@ -261,7 +263,7 @@ jobs:
|
|||||||
--python-version 3.12 \
|
--python-version 3.12 \
|
||||||
--implementation cp \
|
--implementation cp \
|
||||||
-d wheelhouse/arm64 \
|
-d wheelhouse/arm64 \
|
||||||
'cffi>=2.0.0' \
|
"cffi==$CFFI_VERSION" \
|
||||||
Pillow==12.1.0 \
|
Pillow==12.1.0 \
|
||||||
psutil==7.0.0
|
psutil==7.0.0
|
||||||
|
|
||||||
@@ -271,7 +273,7 @@ jobs:
|
|||||||
--python-version 3.12 \
|
--python-version 3.12 \
|
||||||
--implementation cp \
|
--implementation cp \
|
||||||
-d wheelhouse/x86_64 \
|
-d wheelhouse/x86_64 \
|
||||||
'cffi>=2.0.0' \
|
"cffi==$CFFI_VERSION" \
|
||||||
Pillow==12.1.0
|
Pillow==12.1.0
|
||||||
|
|
||||||
python3.12 -m pip download \
|
python3.12 -m pip download \
|
||||||
|
|||||||
@@ -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, 1, 0),
|
filevers=(1, 9, 0, 0),
|
||||||
prodvers=(1, 8, 1, 0),
|
prodvers=(1, 9, 0, 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.1.0'),
|
StringStruct(u'FileVersion', u'1.9.0.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.1.0'),
|
StringStruct(u'ProductVersion', u'1.9.0.0'),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|||||||
+1
-1
@@ -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.1"
|
__version__ = "1.9.0"
|
||||||
|
|
||||||
__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"]
|
||||||
+38
-4
@@ -16,15 +16,20 @@ log = logging.getLogger('tg-mtproto-proxy')
|
|||||||
class _WsPool:
|
class _WsPool:
|
||||||
WS_POOL_MAX_AGE = 120.0
|
WS_POOL_MAX_AGE = 120.0
|
||||||
WS_POOL_CHECK_INTERVAL = 5.0
|
WS_POOL_CHECK_INTERVAL = 5.0
|
||||||
|
REFILL_BACKOFF_INITIAL = 60.0
|
||||||
|
REFILL_BACKOFF_MAX = 3600.0
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._idle: Dict[Tuple[int, bool], deque] = {}
|
self._idle: Dict[Tuple[int, bool], deque] = {}
|
||||||
self._refilling: Set[Tuple[int, bool]] = set()
|
self._refilling: Set[Tuple[int, bool]] = set()
|
||||||
self._rotating: Dict[Tuple[int, bool], asyncio.Task] = {}
|
self._rotating: Dict[Tuple[int, bool], asyncio.Task] = {}
|
||||||
|
self._refill_failures: Dict[Tuple[int, bool], int] = {}
|
||||||
|
self._refill_after: Dict[Tuple[int, bool], float] = {}
|
||||||
self.try_fronting_first = False
|
self.try_fronting_first = False
|
||||||
|
|
||||||
async def get(self, dc: int, is_media: bool,
|
async def get(self, dc: int, is_media: bool,
|
||||||
target_ip: str, domains: List[str]
|
target_ip: str, domains: List[str],
|
||||||
|
*, allow_refill: bool = True
|
||||||
) -> Optional[RawWebSocket]:
|
) -> Optional[RawWebSocket]:
|
||||||
key = (dc, is_media)
|
key = (dc, is_media)
|
||||||
now = time.monotonic()
|
now = time.monotonic()
|
||||||
@@ -43,19 +48,28 @@ class _WsPool:
|
|||||||
stats.pool_hits += 1
|
stats.pool_hits += 1
|
||||||
log.debug("WS pool hit DC%d%s (age=%.1fs, left=%d)",
|
log.debug("WS pool hit DC%d%s (age=%.1fs, left=%d)",
|
||||||
dc, 'm' if is_media else '', age, len(bucket))
|
dc, 'm' if is_media else '', age, len(bucket))
|
||||||
self._schedule_refill(key, target_ip, domains)
|
self.report_success(dc, is_media)
|
||||||
|
if allow_refill:
|
||||||
|
self._schedule_refill(key, target_ip, domains)
|
||||||
return ws
|
return ws
|
||||||
|
|
||||||
stats.pool_misses += 1
|
stats.pool_misses += 1
|
||||||
self._schedule_refill(key, target_ip, domains)
|
if allow_refill:
|
||||||
|
self._schedule_refill(key, target_ip, domains)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _schedule_refill(self, key, target_ip, domains):
|
def _schedule_refill(self, key, target_ip, domains):
|
||||||
if key in self._refilling:
|
if (key in self._refilling
|
||||||
|
or time.monotonic() < self._refill_after.get(key, 0)):
|
||||||
return
|
return
|
||||||
self._refilling.add(key)
|
self._refilling.add(key)
|
||||||
asyncio.create_task(self._refill(key, target_ip, domains))
|
asyncio.create_task(self._refill(key, target_ip, domains))
|
||||||
|
|
||||||
|
def report_success(self, dc: int, is_media: bool) -> None:
|
||||||
|
key = (dc, is_media)
|
||||||
|
self._refill_failures.pop(key, None)
|
||||||
|
self._refill_after.pop(key, None)
|
||||||
|
|
||||||
async def _refill(self, key, target_ip, domains):
|
async def _refill(self, key, target_ip, domains):
|
||||||
dc, is_media = key
|
dc, is_media = key
|
||||||
try:
|
try:
|
||||||
@@ -63,6 +77,7 @@ class _WsPool:
|
|||||||
needed = proxy_config.pool_size - len(bucket)
|
needed = proxy_config.pool_size - len(bucket)
|
||||||
if needed <= 0:
|
if needed <= 0:
|
||||||
return
|
return
|
||||||
|
connected = 0
|
||||||
tasks = [asyncio.create_task(
|
tasks = [asyncio.create_task(
|
||||||
self._connect_one(target_ip, domains))
|
self._connect_one(target_ip, domains))
|
||||||
for _ in range(needed)]
|
for _ in range(needed)]
|
||||||
@@ -71,9 +86,24 @@ class _WsPool:
|
|||||||
ws = await t
|
ws = await t
|
||||||
if ws:
|
if ws:
|
||||||
bucket.append((ws, time.monotonic()))
|
bucket.append((ws, time.monotonic()))
|
||||||
|
connected += 1
|
||||||
self._schedule_rotation(key, target_ip, domains)
|
self._schedule_rotation(key, target_ip, domains)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
if connected:
|
||||||
|
self.report_success(dc, is_media)
|
||||||
|
else:
|
||||||
|
failures = self._refill_failures.get(key, 0) + 1
|
||||||
|
self._refill_failures[key] = failures
|
||||||
|
delay = min(
|
||||||
|
self.REFILL_BACKOFF_INITIAL
|
||||||
|
* (2 ** min(failures - 1, 6)),
|
||||||
|
self.REFILL_BACKOFF_MAX,
|
||||||
|
)
|
||||||
|
self._refill_after[key] = time.monotonic() + delay
|
||||||
|
log.info(
|
||||||
|
"WS pool refill failed for DC%d%s, retry in %.0fs",
|
||||||
|
dc, 'm' if is_media else '', delay)
|
||||||
log.debug("WS pool refilled DC%d%s: %d ready",
|
log.debug("WS pool refilled DC%d%s: %d ready",
|
||||||
dc, 'm' if is_media else '', len(bucket))
|
dc, 'm' if is_media else '', len(bucket))
|
||||||
finally:
|
finally:
|
||||||
@@ -136,6 +166,8 @@ class _WsPool:
|
|||||||
self.try_fronting_first = False
|
self.try_fronting_first = False
|
||||||
return ws
|
return ws
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
|
if self.try_fronting_first:
|
||||||
|
return None
|
||||||
return await self._connect_fronted(target_ip, domain)
|
return await self._connect_fronted(target_ip, domain)
|
||||||
except WsHandshakeError as exc:
|
except WsHandshakeError as exc:
|
||||||
if exc.is_redirect:
|
if exc.is_redirect:
|
||||||
@@ -179,6 +211,8 @@ class _WsPool:
|
|||||||
self._idle.clear()
|
self._idle.clear()
|
||||||
self._refilling.clear()
|
self._refilling.clear()
|
||||||
self._rotating.clear()
|
self._rotating.clear()
|
||||||
|
self._refill_failures.clear()
|
||||||
|
self._refill_after.clear()
|
||||||
self.try_fronting_first = False
|
self.try_fronting_first = False
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -339,7 +339,11 @@ async def _handle_client(reader, writer, secret: bytes):
|
|||||||
ws_timed_out = False
|
ws_timed_out = False
|
||||||
all_redirects = True
|
all_redirects = True
|
||||||
|
|
||||||
ws = await ws_pool.get(dc, is_media, target, domains) if not is_test_dc else None
|
allow_pool_refill = now >= ip_fail_until.get(target, 0)
|
||||||
|
ws = await ws_pool.get(
|
||||||
|
dc, is_media, target, domains,
|
||||||
|
allow_refill=allow_pool_refill,
|
||||||
|
) 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)
|
||||||
@@ -413,6 +417,7 @@ async def _handle_client(reader, writer, secret: bytes):
|
|||||||
|
|
||||||
dc_fail_until.pop(dc_key, None)
|
dc_fail_until.pop(dc_key, None)
|
||||||
ip_fail_until.pop(target, None)
|
ip_fail_until.pop(target, None)
|
||||||
|
ws_pool.report_success(dc, is_media)
|
||||||
stats.connections_ws += 1
|
stats.connections_ws += 1
|
||||||
|
|
||||||
splitter = None
|
splitter = None
|
||||||
|
|||||||
+1
-1
@@ -525,7 +525,7 @@ def _edit_config_dialog() -> None:
|
|||||||
merged["force_test_dc"] = _config.get("force_test_dc", DEFAULT_CONFIG["force_test_dc"])
|
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) != cfg.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)
|
||||||
|
|
||||||
if not config_changed:
|
if not config_changed:
|
||||||
|
|||||||
Reference in New Issue
Block a user