Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .github/workflows/build-apk.yml
Original file line number Diff line number Diff line change
@@ -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
12 changes: 12 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Binary file added app/ci-release.keystore.jks
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
25 changes: 23 additions & 2 deletions app/src/main/java/com/vkturn/proxy/ui/SettingsScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
14 changes: 10 additions & 4 deletions app/src/main/java/com/vkturn/proxy/viewmodel/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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|<mode>|<url>|<token>" 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("Требуется авторизация", "Нажмите сюда, чтобы пройти капчу!")
}

Expand Down
Loading