Compare commits

...

3 Commits

Author SHA1 Message Date
Flowseal c02398fbc0 censoring domains 2026-09-05 16:23:24 +03:00
Flowseal 5f55245184 fix linux build 2026-09-05 15:39:45 +03:00
Flowseal 312d844eea removed old contribution link 2026-09-05 00:04:15 +03:00
12 changed files with 115 additions and 22 deletions
-1
View File
@@ -1 +0,0 @@
custom: ['https://nowpayments.io/donation/flowseal']
+2
View File
@@ -395,6 +395,8 @@ jobs:
python3-venv \
python3-dev \
python3-gi \
python3-gi-cairo \
gir1.2-appindicator3-0.1 \
gir1.2-ayatanaappindicator3-0.1 \
python3-tk
-1
View File
@@ -5,7 +5,6 @@
> **USDT (TRC20)**: `TXPnKs2Ww1RD8JN6nChFUVmi5r2hqrWjuu`
> **BTC**: `bc1qr8vd6jelkyyry3m4mq6z5txdx4pl856fu6ss0w`
> **ETH**: `0x1417878fdc5047E670a77748B34819b9A49C72F1`
> **Other coins**: https://nowpayments.io/donation/flowseal
The project is completely free for everyone.
However, its development and stable operation as the user base grows require investment.
-1
View File
@@ -20,7 +20,6 @@
> **USDT (TRC20)**: `TXPnKs2Ww1RD8JN6nChFUVmi5r2hqrWjuu`
> **BTC**: `bc1qr8vd6jelkyyry3m4mq6z5txdx4pl856fu6ss0w`
> **ETH**: `0x1417878fdc5047E670a77748B34819b9A49C72F1`
> **Other coins**: https://nowpayments.io/donation/flowseal
> [!CAUTION]
>
-1
View File
@@ -5,7 +5,6 @@
> **USDT (TRC20)**: `TXPnKs2Ww1RD8JN6nChFUVmi5r2hqrWjuu`
> **BTC**: `bc1qr8vd6jelkyyry3m4mq6z5txdx4pl856fu6ss0w`
> **ETH**: `0x1417878fdc5047E670a77748B34819b9A49C72F1`
> **Другие монеты**: https://nowpayments.io/donation/flowseal
Проект полностью бесплатен для всех.
Однако его развитие и стабильная работа при росте числа пользователей требуют вложений.
-1
View File
@@ -20,7 +20,6 @@
> **USDT (TRC20)**: `TXPnKs2Ww1RD8JN6nChFUVmi5r2hqrWjuu`
> **BTC**: `bc1qr8vd6jelkyyry3m4mq6z5txdx4pl856fu6ss0w`
> **ETH**: `0x1417878fdc5047E670a77748B34819b9A49C72F1`
> **Другие монеты**: https://nowpayments.io/donation/flowseal
> [!CAUTION]
>
-1
View File
@@ -5,7 +5,6 @@
> **USDT (TRC20)**: `TXPnKs2Ww1RD8JN6nChFUVmi5r2hqrWjuu`
> **BTC**: `bc1qr8vd6jelkyyry3m4mq6z5txdx4pl856fu6ss0w`
> **ETH**: `0x1417878fdc5047E670a77748B34819b9A49C72F1`
> **Другие монеты**: https://nowpayments.io/donation/flowseal
Проект полностью бесплатен для всех.
Однако его развитие и стабильная работа при росте числа пользователей требуют вложений.
+40
View File
@@ -310,7 +310,47 @@ def run_tray() -> None:
log.info("Tray app exited")
def _smoke_test() -> None:
"""Exercise the frozen GUI stack without starting the proxy or writing config."""
import gi
gi.require_version('AppIndicator3', '0.1')
gi.require_version('AyatanaAppIndicator3', '0.1')
from gi.repository import AppIndicator3, AyatanaAppIndicator3
# Namespace imports alone do not resolve app_indicator_new: call it too.
indicators = [
namespace.Indicator.new(
'tg-ws-proxy-smoke-test', '', namespace.IndicatorCategory.APPLICATION_STATUS,
)
for namespace in (AppIndicator3, AyatanaAppIndicator3)
]
icon = pystray.Icon('tg-ws-proxy-smoke-test', Image.new('RGB', (16, 16)))
root = ctk.CTk()
root.withdraw()
root.update_idletasks()
root.destroy()
# A successful constructor on the build host can hide missing bundled
# libraries. Check where the dynamic loader actually obtained them.
bundle_dir = os.path.realpath(sys._MEIPASS) + os.sep
prefixes = ('libglib-', 'libgobject-', 'libgio-', 'libgtk-',
'libappindicator', 'libayatana-', 'libdbusmenu-')
with open('/proc/self/maps', encoding='utf-8') as maps:
paths = {line.split(maxsplit=5)[-1].strip() for line in maps}
for path in sorted(paths):
if os.path.basename(path).startswith(prefixes):
if not os.path.realpath(path).startswith(bundle_dir):
raise RuntimeError('GUI library loaded outside the bundle: ' + path)
print(path)
assert icon is not None and all(indicators)
print('Linux bundle smoke test passed')
def main() -> None:
if sys.argv[1:] == ['--smoke-test']:
_smoke_test()
return
if not acquire_lock():
_show_info(t("dialog.already_running"), os.path.basename(sys.argv[0]))
return
+40 -16
View File
@@ -1,10 +1,9 @@
# -*- mode: python ; coding: utf-8 -*-
import sys
import os
import glob
from PyInstaller.utils.hooks import collect_submodules, collect_data_files
from PyInstaller.utils.hooks import collect_data_files
block_cipher = None
@@ -15,21 +14,18 @@ certifi_datas = collect_data_files('certifi')
_i18n_path = os.path.join(os.path.dirname(SPEC), os.pardir, 'ui', 'i18n')
# Collect gi (PyGObject) submodules and data so pystray._appindicator works
gi_hiddenimports = collect_submodules('gi')
gi_datas = collect_data_files('gi')
# Collect GObject typelib files from the system
typelib_dirs = glob.glob('/usr/lib/*/girepository-1.0')
typelib_datas = []
for d in typelib_dirs:
typelib_datas.append((d, 'gi_typelibs'))
appindicator_binaries = [
(path, '.')
for pattern in ('/usr/lib/*/libappindicator3.so.1',
'/usr/lib/libappindicator3.so.1', '/usr/lib64/libappindicator3.so.1')
for path in glob.glob(pattern)
]
a = Analysis(
[os.path.join(os.path.dirname(SPEC), os.pardir, 'linux.py')],
pathex=[],
binaries=[],
datas=[(ctk_path, 'customtkinter/'), (_i18n_path, 'ui/i18n')] + certifi_datas + gi_datas + typelib_datas,
binaries=appindicator_binaries,
datas=[(ctk_path, 'customtkinter/'), (_i18n_path, 'ui/i18n')] + certifi_datas,
hiddenimports=[
'pystray._appindicator',
'PIL._tkinter_finder',
@@ -39,15 +35,22 @@ a = Analysis(
'cryptography.hazmat.primitives.ciphers.modes',
'cryptography.hazmat.backends.openssl',
'gi',
'_gi',
'gi.repository.GLib',
'gi.repository.GObject',
'gi.repository.Gtk',
'gi.repository.Gdk',
'gi.repository.DBus',
'gi.repository.AppIndicator3',
'gi.repository.AyatanaAppIndicator3',
] + gi_hiddenimports,
],
hookspath=[],
hooksconfig={},
hooksconfig={
'gi': {
'icons': [],
'themes': [],
'languages': ['en', 'ru'],
},
},
runtime_hooks=[],
excludes=[
'PIL._avif',
@@ -58,6 +61,27 @@ a = Analysis(
cipher=block_cipher,
)
_required_libraries = {
'libglib-2.0.so.0', 'libgobject-2.0.so.0', 'libgio-2.0.so.0',
'libgtk-3.so.0', 'libappindicator3.so.1',
'libayatana-appindicator3.so.1',
}
_required_typelibs = {
'AppIndicator3-0.1.typelib', 'AyatanaAppIndicator3-0.1.typelib', 'DBus-1.0.typelib',
}
_bundled_libraries = {
os.path.basename(name)
for name, _, kind in a.binaries + a.datas
if kind in ('BINARY', 'SYMLINK')
}
_missing = (
_required_libraries - _bundled_libraries
) | (
_required_typelibs - {os.path.basename(name) for name, _, _ in a.datas}
)
if _missing:
raise RuntimeError('Incomplete Linux GI bundle: ' + ', '.join(sorted(_missing)))
_PIL_EXCLUDE_PYDS = {
'_avif', '_webp', '_imagingtk',
'FpxImagePlugin', 'MicImagePlugin',
+2
View File
@@ -731,6 +731,7 @@ def main():
console = logging.StreamHandler()
console.setFormatter(log_fmt)
console.addFilter(DomainCensorFilter())
root.addHandler(console)
if args.log_file:
@@ -741,6 +742,7 @@ def main():
backups=args.log_backups,
)
fh.setFormatter(log_fmt)
fh.addFilter(DomainCensorFilter())
root.addHandler(fh)
logging.getLogger('asyncio').setLevel(logging.WARNING)
+28
View File
@@ -2,6 +2,8 @@ import socket as _socket
import urllib.request
import http.client
import ssl
import logging
import re
import certifi
@@ -85,6 +87,32 @@ def get_link_host(host: str) -> Optional[str]:
return host
class DomainCensorFilter(logging.Filter):
domain_pattern = re.compile(
r'(?<![\w-])(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+'
r'[a-zA-Z]{2,}(?![\w-])'
)
def _censor_match(self, match):
domain = match.group()
normalized = domain.casefold().rstrip('.')
if normalized == 'telegram.org' or normalized.endswith('.telegram.org') or normalized.endswith('.log'):
return domain
parts = domain.split('.')
if len(parts) < 2:
return domain
return '.'.join(
part if i == len(parts) - 1 else
part[:len(part) // 2] + '*' * (len(part) - len(part) // 2)
for i, part in enumerate(parts)
)
def filter(self, record):
record.msg = self.domain_pattern.sub(self._censor_match, record.getMessage())
record.args = ()
return True
class _PinnedHTTPSHandler(urllib.request.HTTPSHandler):
def https_open(self, req: Request):
host = req.host.split(":")[0]
+3
View File
@@ -15,6 +15,7 @@ from typing import Any, Callable, Dict, Optional, Tuple
import psutil
from proxy import __version__, get_link_host, parse_dc_ip_list, proxy_config, coerce_domain_list
from proxy.utils import DomainCensorFilter
from proxy.tg_ws_proxy import _run
from utils.default_config import default_tray_config
from utils.diagnostics import diagnose_listen_error
@@ -237,12 +238,14 @@ def setup_logging(verbose: bool = False, log_max_mb: float = 5) -> None:
fh = build_log_handler(str(LOG_FILE), log_max_mb=log_max_mb, backups=1)
fh.setLevel(logging.DEBUG)
fh.setFormatter(logging.Formatter(_LOG_FMT_FILE, datefmt="%Y-%m-%d %H:%M:%S"))
fh.addFilter(DomainCensorFilter())
root.addHandler(fh)
if not IS_FROZEN:
ch = logging.StreamHandler(sys.stdout)
ch.setLevel(level)
ch.setFormatter(logging.Formatter(_LOG_FMT_CONSOLE, datefmt="%H:%M:%S"))
ch.addFilter(DomainCensorFilter())
root.addHandler(ch)