feat(android): switch config and tg intent to mtproto model
This commit is contained in:
parent
1599b1126c
commit
810991ea18
|
|
@ -128,6 +128,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
private fun renderConfig(config: ProxyConfig) {
|
private fun renderConfig(config: ProxyConfig) {
|
||||||
binding.hostInput.setText(config.host)
|
binding.hostInput.setText(config.host)
|
||||||
binding.portInput.setText(config.portText)
|
binding.portInput.setText(config.portText)
|
||||||
|
binding.secretInput.setText(config.secretText)
|
||||||
binding.dcIpInput.setText(config.dcIpText)
|
binding.dcIpInput.setText(config.dcIpText)
|
||||||
binding.logMaxMbInput.setText(config.logMaxMbText)
|
binding.logMaxMbInput.setText(config.logMaxMbText)
|
||||||
binding.bufferKbInput.setText(config.bufferKbText)
|
binding.bufferKbInput.setText(config.bufferKbText)
|
||||||
|
|
@ -141,6 +142,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
return ProxyConfig(
|
return ProxyConfig(
|
||||||
host = binding.hostInput.text?.toString().orEmpty(),
|
host = binding.hostInput.text?.toString().orEmpty(),
|
||||||
portText = binding.portInput.text?.toString().orEmpty(),
|
portText = binding.portInput.text?.toString().orEmpty(),
|
||||||
|
secretText = binding.secretInput.text?.toString().orEmpty(),
|
||||||
dcIpText = binding.dcIpInput.text?.toString().orEmpty(),
|
dcIpText = binding.dcIpInput.text?.toString().orEmpty(),
|
||||||
logMaxMbText = binding.logMaxMbInput.text?.toString().orEmpty(),
|
logMaxMbText = binding.logMaxMbInput.text?.toString().orEmpty(),
|
||||||
bufferKbText = binding.bufferKbInput.text?.toString().orEmpty(),
|
bufferKbText = binding.bufferKbInput.text?.toString().orEmpty(),
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
package org.flowseal.tgwsproxy
|
package org.flowseal.tgwsproxy
|
||||||
|
|
||||||
|
import java.security.SecureRandom
|
||||||
|
|
||||||
data class ProxyConfig(
|
data class ProxyConfig(
|
||||||
val host: String = DEFAULT_HOST,
|
val host: String = DEFAULT_HOST,
|
||||||
val portText: String = DEFAULT_PORT.toString(),
|
val portText: String = DEFAULT_PORT.toString(),
|
||||||
|
val secretText: String = DEFAULT_SECRET,
|
||||||
val dcIpText: String = DEFAULT_DC_IP_LINES.joinToString("\n"),
|
val dcIpText: String = DEFAULT_DC_IP_LINES.joinToString("\n"),
|
||||||
val logMaxMbText: String = formatDecimal(DEFAULT_LOG_MAX_MB),
|
val logMaxMbText: String = formatDecimal(DEFAULT_LOG_MAX_MB),
|
||||||
val bufferKbText: String = DEFAULT_BUFFER_KB.toString(),
|
val bufferKbText: String = DEFAULT_BUFFER_KB.toString(),
|
||||||
|
|
@ -22,6 +25,13 @@ data class ProxyConfig(
|
||||||
return ValidationResult(errorMessage = "Порт должен быть в диапазоне 1-65535.")
|
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
|
val lines = dcIpText
|
||||||
.lineSequence()
|
.lineSequence()
|
||||||
.map { it.trim() }
|
.map { it.trim() }
|
||||||
|
|
@ -75,6 +85,7 @@ data class ProxyConfig(
|
||||||
normalized = NormalizedProxyConfig(
|
normalized = NormalizedProxyConfig(
|
||||||
host = hostValue,
|
host = hostValue,
|
||||||
port = portValue,
|
port = portValue,
|
||||||
|
secret = secretValue,
|
||||||
dcIpList = lines,
|
dcIpList = lines,
|
||||||
logMaxMb = logMaxMbValue,
|
logMaxMb = logMaxMbValue,
|
||||||
bufferKb = bufferKbValue,
|
bufferKb = bufferKbValue,
|
||||||
|
|
@ -87,10 +98,11 @@ data class ProxyConfig(
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val DEFAULT_HOST = "127.0.0.1"
|
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_LOG_MAX_MB = 5.0
|
||||||
const val DEFAULT_BUFFER_KB = 256
|
const val DEFAULT_BUFFER_KB = 256
|
||||||
const val DEFAULT_POOL_SIZE = 4
|
const val DEFAULT_POOL_SIZE = 4
|
||||||
|
val DEFAULT_SECRET = generateSecret()
|
||||||
val DEFAULT_DC_IP_LINES = listOf(
|
val DEFAULT_DC_IP_LINES = listOf(
|
||||||
"2:149.154.167.220",
|
"2:149.154.167.220",
|
||||||
"4: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 {
|
private fun isIpv4Address(value: String): Boolean {
|
||||||
val octets = value.split(".")
|
val octets = value.split(".")
|
||||||
if (octets.size != 4) {
|
if (octets.size != 4) {
|
||||||
|
|
@ -128,6 +146,7 @@ data class ValidationResult(
|
||||||
data class NormalizedProxyConfig(
|
data class NormalizedProxyConfig(
|
||||||
val host: String,
|
val host: String,
|
||||||
val port: Int,
|
val port: Int,
|
||||||
|
val secret: String,
|
||||||
val dcIpList: List<String>,
|
val dcIpList: List<String>,
|
||||||
val logMaxMb: Double,
|
val logMaxMb: Double,
|
||||||
val bufferKb: Int,
|
val bufferKb: Int,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ class ProxySettingsStore(context: Context) {
|
||||||
return ProxyConfig(
|
return ProxyConfig(
|
||||||
host = preferences.getString(KEY_HOST, ProxyConfig.DEFAULT_HOST).orEmpty(),
|
host = preferences.getString(KEY_HOST, ProxyConfig.DEFAULT_HOST).orEmpty(),
|
||||||
portText = preferences.getInt(KEY_PORT, ProxyConfig.DEFAULT_PORT).toString(),
|
portText = preferences.getInt(KEY_PORT, ProxyConfig.DEFAULT_PORT).toString(),
|
||||||
|
secretText = preferences.getString(KEY_SECRET, ProxyConfig.DEFAULT_SECRET).orEmpty(),
|
||||||
dcIpText = preferences.getString(
|
dcIpText = preferences.getString(
|
||||||
KEY_DC_IP_TEXT,
|
KEY_DC_IP_TEXT,
|
||||||
ProxyConfig.DEFAULT_DC_IP_LINES.joinToString("\n"),
|
ProxyConfig.DEFAULT_DC_IP_LINES.joinToString("\n"),
|
||||||
|
|
@ -36,6 +37,7 @@ class ProxySettingsStore(context: Context) {
|
||||||
preferences.edit()
|
preferences.edit()
|
||||||
.putString(KEY_HOST, config.host)
|
.putString(KEY_HOST, config.host)
|
||||||
.putInt(KEY_PORT, config.port)
|
.putInt(KEY_PORT, config.port)
|
||||||
|
.putString(KEY_SECRET, config.secret)
|
||||||
.putString(KEY_DC_IP_TEXT, config.dcIpList.joinToString("\n"))
|
.putString(KEY_DC_IP_TEXT, config.dcIpList.joinToString("\n"))
|
||||||
.putFloat(KEY_LOG_MAX_MB, config.logMaxMb.toFloat())
|
.putFloat(KEY_LOG_MAX_MB, config.logMaxMb.toFloat())
|
||||||
.putInt(KEY_BUFFER_KB, config.bufferKb)
|
.putInt(KEY_BUFFER_KB, config.bufferKb)
|
||||||
|
|
@ -49,6 +51,7 @@ class ProxySettingsStore(context: Context) {
|
||||||
private const val PREFS_NAME = "proxy_settings"
|
private const val PREFS_NAME = "proxy_settings"
|
||||||
private const val KEY_HOST = "host"
|
private const val KEY_HOST = "host"
|
||||||
private const val KEY_PORT = "port"
|
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_DC_IP_TEXT = "dc_ip_text"
|
||||||
private const val KEY_LOG_MAX_MB = "log_max_mb"
|
private const val KEY_LOG_MAX_MB = "log_max_mb"
|
||||||
private const val KEY_BUFFER_KB = "buf_kb"
|
private const val KEY_BUFFER_KB = "buf_kb"
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ object PythonProxyBridge {
|
||||||
File(context.filesDir, "tg-ws-proxy").absolutePath,
|
File(context.filesDir, "tg-ws-proxy").absolutePath,
|
||||||
config.host,
|
config.host,
|
||||||
config.port,
|
config.port,
|
||||||
|
config.secret,
|
||||||
config.dcIpList,
|
config.dcIpList,
|
||||||
config.logMaxMb,
|
config.logMaxMb,
|
||||||
config.bufferKb,
|
config.bufferKb,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import android.net.Uri
|
||||||
object TelegramProxyIntent {
|
object TelegramProxyIntent {
|
||||||
fun open(context: Context, config: NormalizedProxyConfig): Boolean {
|
fun open(context: Context, config: NormalizedProxyConfig): Boolean {
|
||||||
val uri = Uri.parse(
|
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)
|
val intent = Intent(Intent.ACTION_VIEW, uri)
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
|
|
||||||
|
|
@ -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()]
|
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,
|
dc_ip_list: Iterable[object], log_max_mb: float = 5.0,
|
||||||
buf_kb: int = 256, pool_size: int = 4,
|
buf_kb: int = 256, pool_size: int = 4,
|
||||||
verbose: bool = False) -> str:
|
verbose: bool = False) -> str:
|
||||||
|
|
@ -70,6 +70,7 @@ def start_proxy(app_dir: str, host: str, port: int,
|
||||||
config = {
|
config = {
|
||||||
"host": host,
|
"host": host,
|
||||||
"port": int(port),
|
"port": int(port),
|
||||||
|
"secret": str(secret).strip(),
|
||||||
"dc_ip": _normalize_dc_ip_list(dc_ip_list),
|
"dc_ip": _normalize_dc_ip_list(dc_ip_list),
|
||||||
"log_max_mb": float(log_max_mb),
|
"log_max_mb": float(log_max_mb),
|
||||||
"buf_kb": int(buf_kb),
|
"buf_kb": int(buf_kb),
|
||||||
|
|
|
||||||
|
|
@ -243,6 +243,20 @@
|
||||||
android:maxLines="1" />
|
android:maxLines="1" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:hint="@string/secret_hint">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/secretInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="textNoSuggestions"
|
||||||
|
android:maxLines="1" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">TG WS Proxy</string>
|
<string name="app_name">TG WS Proxy</string>
|
||||||
<string name="subtitle">Android app for the local Telegram SOCKS5 proxy.</string>
|
<string name="subtitle">Android app for the local Telegram MTProto proxy.</string>
|
||||||
<string name="status_label">Foreground service</string>
|
<string name="status_label">Foreground service</string>
|
||||||
<string name="status_starting">Starting</string>
|
<string name="status_starting">Starting</string>
|
||||||
<string name="status_running">Running</string>
|
<string name="status_running">Running</string>
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
<string name="updates_open_release_button">Open Release Page</string>
|
<string name="updates_open_release_button">Open Release Page</string>
|
||||||
<string name="host_hint">Proxy host</string>
|
<string name="host_hint">Proxy host</string>
|
||||||
<string name="port_hint">Proxy port</string>
|
<string name="port_hint">Proxy port</string>
|
||||||
|
<string name="secret_hint">MTProto secret (32 hex characters)</string>
|
||||||
<string name="dc_ip_hint">DC to IP mappings (one DC:IP per line)</string>
|
<string name="dc_ip_hint">DC to IP mappings (one DC:IP per line)</string>
|
||||||
<string name="log_max_mb_hint">Max log size before rotation (MB)</string>
|
<string name="log_max_mb_hint">Max log size before rotation (MB)</string>
|
||||||
<string name="buffer_kb_hint">Socket buffer size (KB)</string>
|
<string name="buffer_kb_hint">Socket buffer size (KB)</string>
|
||||||
|
|
@ -51,12 +52,12 @@
|
||||||
<string name="settings_saved">Settings saved</string>
|
<string name="settings_saved">Settings saved</string>
|
||||||
<string name="service_start_requested">Foreground service start requested</string>
|
<string name="service_start_requested">Foreground service start requested</string>
|
||||||
<string name="service_restart_requested">Foreground service restart requested</string>
|
<string name="service_restart_requested">Foreground service restart requested</string>
|
||||||
<string name="telegram_not_found">Telegram app was not found for tg://socks.</string>
|
<string name="telegram_not_found">Telegram app was not found for tg://proxy.</string>
|
||||||
<string name="notification_title">TG WS Proxy</string>
|
<string name="notification_title">TG WS Proxy</string>
|
||||||
<string name="notification_channel_name">Proxy service</string>
|
<string name="notification_channel_name">Proxy service</string>
|
||||||
<string name="notification_channel_description">Keeps the Telegram proxy service alive in the foreground.</string>
|
<string name="notification_channel_description">Keeps the Telegram proxy service alive in the foreground.</string>
|
||||||
<string name="notification_starting">SOCKS5 %1$s:%2$d • starting embedded Python</string>
|
<string name="notification_starting">MTProto %1$s:%2$d • starting embedded Python</string>
|
||||||
<string name="notification_running">SOCKS5 %1$s:%2$d • proxy active</string>
|
<string name="notification_running">MTProto %1$s:%2$d • proxy active</string>
|
||||||
<string name="notification_endpoint">%1$s:%2$d</string>
|
<string name="notification_endpoint">%1$s:%2$d</string>
|
||||||
<string name="notification_details">DC mappings: %1$d\nTraffic: ↑ %2$s/s ↓ %3$s/s\nTransferred: ↑ %4$s ↓ %5$s</string>
|
<string name="notification_details">DC mappings: %1$d\nTraffic: ↑ %2$s/s ↓ %3$s/s\nTransferred: ↑ %4$s ↓ %5$s</string>
|
||||||
<string name="notification_action_stop">Stop</string>
|
<string name="notification_action_stop">Stop</string>
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,8 @@ class AndroidProxyBridgeTests(unittest.TestCase):
|
||||||
log_path = android_proxy_bridge.start_proxy(
|
log_path = android_proxy_bridge.start_proxy(
|
||||||
"/tmp/app",
|
"/tmp/app",
|
||||||
"127.0.0.1",
|
"127.0.0.1",
|
||||||
1080,
|
1443,
|
||||||
|
"0123456789abcdef0123456789abcdef",
|
||||||
["2:149.154.167.220"],
|
["2:149.154.167.220"],
|
||||||
7.0,
|
7.0,
|
||||||
512,
|
512,
|
||||||
|
|
@ -118,6 +119,7 @@ class AndroidProxyBridgeTests(unittest.TestCase):
|
||||||
android_proxy_bridge.ProxyAppRuntime = original_runtime
|
android_proxy_bridge.ProxyAppRuntime = original_runtime
|
||||||
|
|
||||||
self.assertEqual(log_path, "/tmp/proxy.log")
|
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"]["log_max_mb"], 7.0)
|
||||||
self.assertEqual(captured["config"]["buf_kb"], 512)
|
self.assertEqual(captured["config"]["buf_kb"], 512)
|
||||||
self.assertEqual(captured["config"]["pool_size"], 6)
|
self.assertEqual(captured["config"]["pool_size"], 6)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue