From 810991ea18d4b3901611a78550e4cfe761e99d88 Mon Sep 17 00:00:00 2001 From: Dark_Avery Date: Mon, 30 Mar 2026 16:14:54 +0300 Subject: [PATCH] feat(android): switch config and tg intent to mtproto model --- .../org/flowseal/tgwsproxy/MainActivity.kt | 2 ++ .../org/flowseal/tgwsproxy/ProxyConfig.kt | 21 ++++++++++++++++++- .../flowseal/tgwsproxy/ProxySettingsStore.kt | 3 +++ .../flowseal/tgwsproxy/PythonProxyBridge.kt | 1 + .../flowseal/tgwsproxy/TelegramProxyIntent.kt | 2 +- .../src/main/python/android_proxy_bridge.py | 3 ++- .../app/src/main/res/layout/activity_main.xml | 14 +++++++++++++ android/app/src/main/res/values/strings.xml | 9 ++++---- tests/test_android_proxy_bridge.py | 4 +++- 9 files changed, 51 insertions(+), 8 deletions(-) diff --git a/android/app/src/main/java/org/flowseal/tgwsproxy/MainActivity.kt b/android/app/src/main/java/org/flowseal/tgwsproxy/MainActivity.kt index 01243c9..15004ce 100644 --- a/android/app/src/main/java/org/flowseal/tgwsproxy/MainActivity.kt +++ b/android/app/src/main/java/org/flowseal/tgwsproxy/MainActivity.kt @@ -128,6 +128,7 @@ class MainActivity : AppCompatActivity() { private fun renderConfig(config: ProxyConfig) { binding.hostInput.setText(config.host) binding.portInput.setText(config.portText) + binding.secretInput.setText(config.secretText) binding.dcIpInput.setText(config.dcIpText) binding.logMaxMbInput.setText(config.logMaxMbText) binding.bufferKbInput.setText(config.bufferKbText) @@ -141,6 +142,7 @@ class MainActivity : AppCompatActivity() { return ProxyConfig( host = binding.hostInput.text?.toString().orEmpty(), portText = binding.portInput.text?.toString().orEmpty(), + secretText = binding.secretInput.text?.toString().orEmpty(), dcIpText = binding.dcIpInput.text?.toString().orEmpty(), logMaxMbText = binding.logMaxMbInput.text?.toString().orEmpty(), bufferKbText = binding.bufferKbInput.text?.toString().orEmpty(), diff --git a/android/app/src/main/java/org/flowseal/tgwsproxy/ProxyConfig.kt b/android/app/src/main/java/org/flowseal/tgwsproxy/ProxyConfig.kt index bc8075a..89628ea 100644 --- a/android/app/src/main/java/org/flowseal/tgwsproxy/ProxyConfig.kt +++ b/android/app/src/main/java/org/flowseal/tgwsproxy/ProxyConfig.kt @@ -1,8 +1,11 @@ package org.flowseal.tgwsproxy +import java.security.SecureRandom + data class ProxyConfig( val host: String = DEFAULT_HOST, val portText: String = DEFAULT_PORT.toString(), + val secretText: String = DEFAULT_SECRET, val dcIpText: String = DEFAULT_DC_IP_LINES.joinToString("\n"), val logMaxMbText: String = formatDecimal(DEFAULT_LOG_MAX_MB), val bufferKbText: String = DEFAULT_BUFFER_KB.toString(), @@ -22,6 +25,13 @@ data class ProxyConfig( return ValidationResult(errorMessage = "Порт должен быть в диапазоне 1-65535.") } + val secretValue = secretText.trim().lowercase() + if (secretValue.length != 32 || !secretValue.all { it in "0123456789abcdef" }) { + return ValidationResult( + errorMessage = "MTProto secret должен содержать ровно 32 hex-символа." + ) + } + val lines = dcIpText .lineSequence() .map { it.trim() } @@ -75,6 +85,7 @@ data class ProxyConfig( normalized = NormalizedProxyConfig( host = hostValue, port = portValue, + secret = secretValue, dcIpList = lines, logMaxMb = logMaxMbValue, bufferKb = bufferKbValue, @@ -87,10 +98,11 @@ data class ProxyConfig( companion object { const val DEFAULT_HOST = "127.0.0.1" - const val DEFAULT_PORT = 1080 + const val DEFAULT_PORT = 1443 const val DEFAULT_LOG_MAX_MB = 5.0 const val DEFAULT_BUFFER_KB = 256 const val DEFAULT_POOL_SIZE = 4 + val DEFAULT_SECRET = generateSecret() val DEFAULT_DC_IP_LINES = listOf( "2:149.154.167.220", "4:149.154.167.220", @@ -104,6 +116,12 @@ data class ProxyConfig( } } + private fun generateSecret(): String { + val bytes = ByteArray(16) + SecureRandom().nextBytes(bytes) + return bytes.joinToString(separator = "") { "%02x".format(it) } + } + private fun isIpv4Address(value: String): Boolean { val octets = value.split(".") if (octets.size != 4) { @@ -128,6 +146,7 @@ data class ValidationResult( data class NormalizedProxyConfig( val host: String, val port: Int, + val secret: String, val dcIpList: List, val logMaxMb: Double, val bufferKb: Int, diff --git a/android/app/src/main/java/org/flowseal/tgwsproxy/ProxySettingsStore.kt b/android/app/src/main/java/org/flowseal/tgwsproxy/ProxySettingsStore.kt index 6d726e9..70e51ac 100644 --- a/android/app/src/main/java/org/flowseal/tgwsproxy/ProxySettingsStore.kt +++ b/android/app/src/main/java/org/flowseal/tgwsproxy/ProxySettingsStore.kt @@ -9,6 +9,7 @@ class ProxySettingsStore(context: Context) { return ProxyConfig( host = preferences.getString(KEY_HOST, ProxyConfig.DEFAULT_HOST).orEmpty(), portText = preferences.getInt(KEY_PORT, ProxyConfig.DEFAULT_PORT).toString(), + secretText = preferences.getString(KEY_SECRET, ProxyConfig.DEFAULT_SECRET).orEmpty(), dcIpText = preferences.getString( KEY_DC_IP_TEXT, ProxyConfig.DEFAULT_DC_IP_LINES.joinToString("\n"), @@ -36,6 +37,7 @@ class ProxySettingsStore(context: Context) { preferences.edit() .putString(KEY_HOST, config.host) .putInt(KEY_PORT, config.port) + .putString(KEY_SECRET, config.secret) .putString(KEY_DC_IP_TEXT, config.dcIpList.joinToString("\n")) .putFloat(KEY_LOG_MAX_MB, config.logMaxMb.toFloat()) .putInt(KEY_BUFFER_KB, config.bufferKb) @@ -49,6 +51,7 @@ class ProxySettingsStore(context: Context) { private const val PREFS_NAME = "proxy_settings" private const val KEY_HOST = "host" private const val KEY_PORT = "port" + private const val KEY_SECRET = "secret" private const val KEY_DC_IP_TEXT = "dc_ip_text" private const val KEY_LOG_MAX_MB = "log_max_mb" private const val KEY_BUFFER_KB = "buf_kb" diff --git a/android/app/src/main/java/org/flowseal/tgwsproxy/PythonProxyBridge.kt b/android/app/src/main/java/org/flowseal/tgwsproxy/PythonProxyBridge.kt index 1923300..1042aa5 100644 --- a/android/app/src/main/java/org/flowseal/tgwsproxy/PythonProxyBridge.kt +++ b/android/app/src/main/java/org/flowseal/tgwsproxy/PythonProxyBridge.kt @@ -17,6 +17,7 @@ object PythonProxyBridge { File(context.filesDir, "tg-ws-proxy").absolutePath, config.host, config.port, + config.secret, config.dcIpList, config.logMaxMb, config.bufferKb, diff --git a/android/app/src/main/java/org/flowseal/tgwsproxy/TelegramProxyIntent.kt b/android/app/src/main/java/org/flowseal/tgwsproxy/TelegramProxyIntent.kt index 213126e..468663e 100644 --- a/android/app/src/main/java/org/flowseal/tgwsproxy/TelegramProxyIntent.kt +++ b/android/app/src/main/java/org/flowseal/tgwsproxy/TelegramProxyIntent.kt @@ -8,7 +8,7 @@ import android.net.Uri object TelegramProxyIntent { fun open(context: Context, config: NormalizedProxyConfig): Boolean { val uri = Uri.parse( - "tg://socks?server=${Uri.encode(config.host)}&port=${config.port}" + "tg://proxy?server=${Uri.encode(config.host)}&port=${config.port}&secret=dd${Uri.encode(config.secret)}" ) val intent = Intent(Intent.ACTION_VIEW, uri) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) diff --git a/android/app/src/main/python/android_proxy_bridge.py b/android/app/src/main/python/android_proxy_bridge.py index c911572..5a5aa85 100644 --- a/android/app/src/main/python/android_proxy_bridge.py +++ b/android/app/src/main/python/android_proxy_bridge.py @@ -44,7 +44,7 @@ def _normalize_dc_ip_list(dc_ip_list: Iterable[object]) -> list[str]: return [str(item).strip() for item in values if str(item).strip()] -def start_proxy(app_dir: str, host: str, port: int, +def start_proxy(app_dir: str, host: str, port: int, secret: str, dc_ip_list: Iterable[object], log_max_mb: float = 5.0, buf_kb: int = 256, pool_size: int = 4, verbose: bool = False) -> str: @@ -70,6 +70,7 @@ def start_proxy(app_dir: str, host: str, port: int, config = { "host": host, "port": int(port), + "secret": str(secret).strip(), "dc_ip": _normalize_dc_ip_list(dc_ip_list), "log_max_mb": float(log_max_mb), "buf_kb": int(buf_kb), diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml index ec2c29f..aac1a85 100644 --- a/android/app/src/main/res/layout/activity_main.xml +++ b/android/app/src/main/res/layout/activity_main.xml @@ -243,6 +243,20 @@ android:maxLines="1" /> + + + + + TG WS Proxy - Android app for the local Telegram SOCKS5 proxy. + Android app for the local Telegram MTProto proxy. Foreground service Starting Running @@ -33,6 +33,7 @@ Open Release Page Proxy host Proxy port + MTProto secret (32 hex characters) DC to IP mappings (one DC:IP per line) Max log size before rotation (MB) Socket buffer size (KB) @@ -51,12 +52,12 @@ Settings saved Foreground service start requested Foreground service restart requested - Telegram app was not found for tg://socks. + Telegram app was not found for tg://proxy. TG WS Proxy Proxy service Keeps the Telegram proxy service alive in the foreground. - SOCKS5 %1$s:%2$d • starting embedded Python - SOCKS5 %1$s:%2$d • proxy active + MTProto %1$s:%2$d • starting embedded Python + MTProto %1$s:%2$d • proxy active %1$s:%2$d DC mappings: %1$d\nTraffic: ↑ %2$s/s ↓ %3$s/s\nTransferred: ↑ %4$s ↓ %5$s Stop diff --git a/tests/test_android_proxy_bridge.py b/tests/test_android_proxy_bridge.py index 3ddadce..ab10293 100644 --- a/tests/test_android_proxy_bridge.py +++ b/tests/test_android_proxy_bridge.py @@ -107,7 +107,8 @@ class AndroidProxyBridgeTests(unittest.TestCase): log_path = android_proxy_bridge.start_proxy( "/tmp/app", "127.0.0.1", - 1080, + 1443, + "0123456789abcdef0123456789abcdef", ["2:149.154.167.220"], 7.0, 512, @@ -118,6 +119,7 @@ class AndroidProxyBridgeTests(unittest.TestCase): android_proxy_bridge.ProxyAppRuntime = original_runtime self.assertEqual(log_path, "/tmp/proxy.log") + self.assertEqual(captured["config"]["secret"], "0123456789abcdef0123456789abcdef") self.assertEqual(captured["config"]["log_max_mb"], 7.0) self.assertEqual(captured["config"]["buf_kb"], 512) self.assertEqual(captured["config"]["pool_size"], 6)