diff --git a/src/main/kotlin/suwayomi/tachidesk/launcher/LauncherViewModel.kt b/src/main/kotlin/suwayomi/tachidesk/launcher/LauncherViewModel.kt index 3ccf9a6..bbeadb9 100644 --- a/src/main/kotlin/suwayomi/tachidesk/launcher/LauncherViewModel.kt +++ b/src/main/kotlin/suwayomi/tachidesk/launcher/LauncherViewModel.kt @@ -102,6 +102,11 @@ class LauncherViewModel { // Local Source val localSourcePath = config.asStateFlow { it.localSourcePath } + // Cloudflare bypass + val flareSolverrEnabled = config.asStateFlow { it.flareSolverrEnabled } + val flareSolverrUrl = config.asStateFlow { it.flareSolverrUrl } + val flareSolverrTimeout = config.asStateFlow { it.flareSolverrTimeout } + val theme = settings.theme().asStateFlow(scope) fun launch(forceElectron: Boolean = false) { diff --git a/src/main/kotlin/suwayomi/tachidesk/launcher/Main.kt b/src/main/kotlin/suwayomi/tachidesk/launcher/Main.kt index e48f361..2854b63 100644 --- a/src/main/kotlin/suwayomi/tachidesk/launcher/Main.kt +++ b/src/main/kotlin/suwayomi/tachidesk/launcher/Main.kt @@ -24,6 +24,7 @@ import net.miginfocom.layout.LC import net.miginfocom.swing.MigLayout import suwayomi.tachidesk.launcher.ui.Backup import suwayomi.tachidesk.launcher.ui.BasicAuth +import suwayomi.tachidesk.launcher.ui.Cloudflare import suwayomi.tachidesk.launcher.ui.Downloader import suwayomi.tachidesk.launcher.ui.Extension import suwayomi.tachidesk.launcher.ui.LocalSource @@ -110,6 +111,7 @@ suspend fun main() { addTab("Local Source", LocalSource(vm, scope)) addTab("Requests", Requests(vm, scope)) addTab("Extension", Extension(vm, scope)) + addTab("Cloudflare", Cloudflare(vm, scope)) }.bind(CC().grow()) jpanel { jbutton("Launch") { diff --git a/src/main/kotlin/suwayomi/tachidesk/launcher/config/ServerConfig.kt b/src/main/kotlin/suwayomi/tachidesk/launcher/config/ServerConfig.kt index b3d2ddf..a0862f7 100644 --- a/src/main/kotlin/suwayomi/tachidesk/launcher/config/ServerConfig.kt +++ b/src/main/kotlin/suwayomi/tachidesk/launcher/config/ServerConfig.kt @@ -100,4 +100,9 @@ class ServerConfig( // local source val localSourcePath: MutableStateFlow by OverrideConfigValue(StringConfigAdapter) + + // cloudflare bypass + val flareSolverrEnabled: MutableStateFlow by OverrideConfigValue(BooleanConfigAdapter) + val flareSolverrUrl: MutableStateFlow by OverrideConfigValue(StringConfigAdapter) + val flareSolverrTimeout: MutableStateFlow by OverrideConfigValue(IntConfigAdapter) } diff --git a/src/main/kotlin/suwayomi/tachidesk/launcher/ui/Cloudflare.kt b/src/main/kotlin/suwayomi/tachidesk/launcher/ui/Cloudflare.kt new file mode 100644 index 0000000..096a2f2 --- /dev/null +++ b/src/main/kotlin/suwayomi/tachidesk/launcher/ui/Cloudflare.kt @@ -0,0 +1,81 @@ +package suwayomi.tachidesk.launcher.ui + +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import net.miginfocom.layout.CC +import net.miginfocom.layout.LC +import net.miginfocom.swing.MigLayout +import suwayomi.tachidesk.launcher.KeyListenerEvent +import suwayomi.tachidesk.launcher.LauncherViewModel +import suwayomi.tachidesk.launcher.actions +import suwayomi.tachidesk.launcher.bind +import suwayomi.tachidesk.launcher.changes +import suwayomi.tachidesk.launcher.jCheckBox +import suwayomi.tachidesk.launcher.jSpinner +import suwayomi.tachidesk.launcher.jTextArea +import suwayomi.tachidesk.launcher.jTextField +import suwayomi.tachidesk.launcher.jpanel +import suwayomi.tachidesk.launcher.keyListener +import java.net.URL +import javax.swing.SpinnerNumberModel + +fun Cloudflare(vm: LauncherViewModel, scope: CoroutineScope) = jpanel( + MigLayout( + LC().alignX("center").alignY("center") + ) +) { + jCheckBox("Use FlareSolverr", selected = vm.flareSolverrEnabled.value) { + toolTipText = "Use FlareSolverr instance to bypass Cloudflare." // todo improve + actions() + .onEach { + vm.flareSolverrEnabled.value = isSelected + } + .flowOn(Dispatchers.Default) + .launchIn(scope) + }.bind(CC().wrap()) + jTextArea("FlareSolverr URL") { + isEditable = false + }.bind() + jTextField(vm.flareSolverrUrl.value) { + // todo toolTipText = "" + keyListener() + .filterIsInstance() + .map { + text?.trim() + } + .onEach { + if (!it.isNullOrBlank() && runCatching { URL(it).toURI() }.isSuccess) { + vm.flareSolverrUrl.value = it + } + } + .flowOn(Dispatchers.Default) + .launchIn(scope) + columns = 10 + }.bind(CC().grow().spanX().wrap()) + + jTextArea("FlareSolverr timeout") { + isEditable = false + }.bind() + jSpinner(SpinnerNumberModel(vm.flareSolverrTimeout.value.coerceAtLeast(10), 10, Int.MAX_VALUE, 1)) { + toolTipText = "Time limit in seconds for FlareSolverr to run, will fail if it goes over" + changes() + .onEach { + vm.flareSolverrTimeout.value = (value as Int) + } + .flowOn(Dispatchers.Default) + .launchIn(scope) + }.bind(CC().grow().spanX().wrap()) +}