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) {
|
||||
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(),
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
val logMaxMb: Double,
|
||||
val bufferKb: Int,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -243,6 +243,20 @@
|
|||
android:maxLines="1" />
|
||||
</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
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<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_starting">Starting</string>
|
||||
<string name="status_running">Running</string>
|
||||
|
|
@ -33,6 +33,7 @@
|
|||
<string name="updates_open_release_button">Open Release Page</string>
|
||||
<string name="host_hint">Proxy host</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="log_max_mb_hint">Max log size before rotation (MB)</string>
|
||||
<string name="buffer_kb_hint">Socket buffer size (KB)</string>
|
||||
|
|
@ -51,12 +52,12 @@
|
|||
<string name="settings_saved">Settings saved</string>
|
||||
<string name="service_start_requested">Foreground service start 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_channel_name">Proxy service</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_running">SOCKS5 %1$s:%2$d • proxy active</string>
|
||||
<string name="notification_starting">MTProto %1$s:%2$d • starting embedded Python</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_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>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue