diff --git a/.github/workflows/build-apk.yml b/.github/workflows/build-apk.yml new file mode 100644 index 0000000..fe22526 --- /dev/null +++ b/.github/workflows/build-apk.yml @@ -0,0 +1,50 @@ +name: Build APK + +on: + push: + branches: + - main + - claude/main-bugs-review-nd8dax + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "17" + + - name: Set up Android SDK + uses: android-actions/setup-android@v3 + + - name: Install required SDK packages + run: sdkmanager "platforms;android-36" "build-tools;36.0.0" + + - name: Cache Gradle + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', 'gradle/wrapper/gradle-wrapper.properties') }} + restore-keys: | + gradle-${{ runner.os }}- + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build release APK + run: ./gradlew assembleRelease --stacktrace + + - name: Upload APK artifacts + uses: actions/upload-artifact@v4 + with: + name: TurnProxy-apk + path: app/build/outputs/apk/release/*.apk + if-no-files-found: error diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 681c037..26100e3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -23,6 +23,18 @@ android { } } + // Фиксированный тестовый keystore (не для Google Play): используется во всех + // сборках, включая CI, чтобы подпись APK не менялась от сборки к сборке — + // иначе обновление приложения поверх предыдущей версии ломается. + signingConfigs { + getByName("debug") { + storeFile = file("ci-release.keystore.jks") + storePassword = "vkturnproxy-ci" + keyAlias = "ci-release" + keyPassword = "vkturnproxy-ci" + } + } + // Красивое именование APK файлов applicationVariants.all { val variantName = name diff --git a/app/ci-release.keystore.jks b/app/ci-release.keystore.jks new file mode 100644 index 0000000..e45a830 Binary files /dev/null and b/app/ci-release.keystore.jks differ diff --git a/app/src/main/java/com/vkturn/proxy/ui/CaptchaWebViewDialog.kt b/app/src/main/java/com/vkturn/proxy/ui/CaptchaWebViewDialog.kt index 6401d65..1da9126 100644 --- a/app/src/main/java/com/vkturn/proxy/ui/CaptchaWebViewDialog.kt +++ b/app/src/main/java/com/vkturn/proxy/ui/CaptchaWebViewDialog.kt @@ -55,7 +55,7 @@ fun CaptchaWebViewDialog(captchaUrl: String, onDismiss: () -> Unit) { override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { val url = request.url.toString() // Allow all captcha-related domains to load inside WebView - val allowedDomains = listOf("localhost", "fapi.vk.com", "login.vk.com", "api.vk.com", "captcha.vk.com", "vk.com", "vk.ru") + val allowedDomains = listOf("localhost", "127.0.0.1", "fapi.vk.com", "login.vk.com", "id.vk.ru", "api.vk.com", "captcha.vk.com", "vk.com", "vk.ru") val isAllowed = allowedDomains.any { url.contains(it) } // Only dismiss if navigating to a completely unrelated page if (!isAllowed) { diff --git a/app/src/main/java/com/vkturn/proxy/ui/SettingsScreen.kt b/app/src/main/java/com/vkturn/proxy/ui/SettingsScreen.kt index a5d3f13..28bf8ec 100644 --- a/app/src/main/java/com/vkturn/proxy/ui/SettingsScreen.kt +++ b/app/src/main/java/com/vkturn/proxy/ui/SettingsScreen.kt @@ -351,9 +351,20 @@ fun SettingsScreen(viewModel: MainViewModel) { text = { Text("Отключение DTLS с большой вероятностью приведет к блокировке со стороны сервиса. Вы уверены?") }, confirmButton = { TextButton(onClick = { - currentFlags = currentFlags.map { - if (it.argument == "-no-dtls") it.copy(enabled = true) else it + val newFlags = currentFlags.map { + if (it.argument == "-no-dtls") it.copy(enabled = true) else it } + currentFlags = newFlags + viewModel.saveClientConfig(clientConfig.copy( + serverAddress = peer, + vkLink = link, + linkArgument = linkArg, + threads = threads.toInt(), + localPort = listen, + isRawMode = false, + rawCommand = rawCommand, + customFlags = newFlags + )) showDtlsWarning = false }) { Text("Продолжить", color = MaterialTheme.colorScheme.error) @@ -395,6 +406,16 @@ fun SettingsScreen(viewModel: MainViewModel) { } currentFlags = newFlags + viewModel.saveClientConfig(clientConfig.copy( + serverAddress = peer, + vkLink = link, + linkArgument = linkArg, + threads = threads.toInt(), + localPort = listen, + isRawMode = false, + rawCommand = rawCommand, + customFlags = newFlags + )) } }, modifier = Modifier.scale(0.8f) diff --git a/app/src/main/java/com/vkturn/proxy/viewmodel/MainViewModel.kt b/app/src/main/java/com/vkturn/proxy/viewmodel/MainViewModel.kt index aa57929..b24c150 100644 --- a/app/src/main/java/com/vkturn/proxy/viewmodel/MainViewModel.kt +++ b/app/src/main/java/com/vkturn/proxy/viewmodel/MainViewModel.kt @@ -93,10 +93,16 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { } private fun checkForCaptcha(log: String) { - val urlPattern = "(https?://(localhost:\\d+|login\\.vk\\.com)\\S*)".toRegex() - val match = urlPattern.find(log) - if (match != null) { - _proxyState.value = ProxyState.CaptchaRequired(match.value) + // Kernel emits "CAPTCHA_SOLVE|||" for the manual WebView flow. + // The url itself never contains "|", so stop the capture there instead of + // greedily eating the trailing token with it. + val solveMatch = "CAPTCHA_SOLVE\\|[^|]*\\|(https?://[^|\\s]+)".toRegex().find(log) + // Fallback for plain URLs in the log (localhost/127.0.0.1 local server, + // or VK's own auth domains, which have changed over time: login.vk.com -> id.vk.ru). + val fallbackMatch = "(https?://(localhost:\\d+|127\\.0\\.0\\.1:\\d+|id\\.vk\\.ru|login\\.vk\\.com)\\S*)".toRegex().find(log) + val captchaUrl = solveMatch?.groupValues?.get(1) ?: fallbackMatch?.value + if (captchaUrl != null) { + _proxyState.value = ProxyState.CaptchaRequired(captchaUrl) ProxyService.updateNotification("Требуется авторизация", "Нажмите сюда, чтобы пройти капчу!") }