Add setting to use the flaresolverr response (#990)

This commit is contained in:
AeonLucid
2024-07-28 21:57:40 +02:00
committed by GitHub
parent 25a62e33a1
commit 9e006166a8
6 changed files with 73 additions and 30 deletions

View File

@@ -23,6 +23,7 @@ import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import suwayomi.tachidesk.server.serverConfig import suwayomi.tachidesk.server.serverConfig
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.IOException import java.io.IOException
@@ -56,11 +57,38 @@ class CloudflareInterceptor(
originalResponse.close() originalResponse.close()
// network.cookieStore.remove(originalRequest.url.toUri()) // network.cookieStore.remove(originalRequest.url.toUri())
val request = val flareResponseFallback = serverConfig.flareSolverrAsResponseFallback.value
val flareResponse =
runBlocking { runBlocking {
CFClearance.resolveWithFlareSolverr(setUserAgent, originalRequest) CFClearance.resolveWithFlareSolver(originalRequest, !flareResponseFallback)
} }
if (flareResponse.message.contains("not detected", ignoreCase = true)) {
logger.debug { "FlareSolverr failed to detect Cloudflare challenge" }
if (flareResponseFallback &&
flareResponse.solution.status in 200..299 &&
flareResponse.solution.response != null
) {
val isImage = flareResponse.solution.response.contains(CHROME_IMAGE_TEMPLATE_REGEX)
if (!isImage) {
logger.debug { "Falling back to FlareSolverr response" }
setUserAgent(flareResponse.solution.userAgent)
return originalResponse
.newBuilder()
.code(flareResponse.solution.status)
.body(flareResponse.solution.response.toResponseBody())
.build()
} else {
logger.debug { "FlareSolverr response is an image html template, not falling back" }
}
}
}
val request = CFClearance.requestWithFlareSolverr(flareResponse, setUserAgent, originalRequest)
chain.proceed(request) chain.proceed(request)
} catch (e: Exception) { } catch (e: Exception) {
// Because OkHttp's enqueue only handles IOExceptions, wrap the exception so that // Because OkHttp's enqueue only handles IOExceptions, wrap the exception so that
@@ -73,6 +101,7 @@ class CloudflareInterceptor(
private val ERROR_CODES = listOf(403, 503) private val ERROR_CODES = listOf(403, 503)
private val SERVER_CHECK = arrayOf("cloudflare-nginx", "cloudflare") private val SERVER_CHECK = arrayOf("cloudflare-nginx", "cloudflare")
private val COOKIE_NAMES = listOf("cf_clearance") private val COOKIE_NAMES = listOf("cf_clearance")
private val CHROME_IMAGE_TEMPLATE_REGEX = Regex("""<title>(.*?) \(\d+×\d+\)</title>""")
} }
} }
@@ -153,13 +182,13 @@ object CFClearance {
val version: String, val version: String,
) )
suspend fun resolveWithFlareSolverr( suspend fun resolveWithFlareSolver(
setUserAgent: (String) -> Unit,
originalRequest: Request, originalRequest: Request,
): Request { onlyCookies: Boolean,
): FlareSolverResponse {
val timeout = serverConfig.flareSolverrTimeout.value.seconds val timeout = serverConfig.flareSolverrTimeout.value.seconds
val flareSolverResponse =
with(json) { return with(json) {
mutex.withLock { mutex.withLock {
client.value.newCall( client.value.newCall(
POST( POST(
@@ -175,7 +204,7 @@ object CFClearance {
network.cookieStore.get(originalRequest.url).map { network.cookieStore.get(originalRequest.url).map {
FlareSolverCookie(it.name, it.value) FlareSolverCookie(it.name, it.value)
}, },
returnOnlyCookies = true, returnOnlyCookies = onlyCookies,
maxTimeout = timeout.inWholeMilliseconds.toInt(), maxTimeout = timeout.inWholeMilliseconds.toInt(),
), ),
).toRequestBody(jsonMediaType), ).toRequestBody(jsonMediaType),
@@ -183,7 +212,13 @@ object CFClearance {
).awaitSuccess().parseAs<FlareSolverResponse>() ).awaitSuccess().parseAs<FlareSolverResponse>()
} }
} }
}
fun requestWithFlareSolverr(
flareSolverResponse: FlareSolverResponse,
setUserAgent: (String) -> Unit,
originalRequest: Request,
): Request {
if (flareSolverResponse.solution.status in 200..299) { if (flareSolverResponse.solution.status in 200..299) {
setUserAgent(flareSolverResponse.solution.userAgent) setUserAgent(flareSolverResponse.solution.userAgent)
val cookies = val cookies =

View File

@@ -98,6 +98,7 @@ class SettingsMutation {
updateSetting(settings.flareSolverrTimeout, serverConfig.flareSolverrTimeout) updateSetting(settings.flareSolverrTimeout, serverConfig.flareSolverrTimeout)
updateSetting(settings.flareSolverrSessionName, serverConfig.flareSolverrSessionName) updateSetting(settings.flareSolverrSessionName, serverConfig.flareSolverrSessionName)
updateSetting(settings.flareSolverrSessionTtl, serverConfig.flareSolverrSessionTtl) updateSetting(settings.flareSolverrSessionTtl, serverConfig.flareSolverrSessionTtl)
updateSetting(settings.flareSolverrAsResponseFallback, serverConfig.flareSolverrAsResponseFallback)
} }
fun setSettings(input: SetSettingsInput): SetSettingsPayload { fun setSettings(input: SetSettingsInput): SetSettingsPayload {

View File

@@ -89,6 +89,7 @@ interface Settings : Node {
val flareSolverrTimeout: Int? val flareSolverrTimeout: Int?
val flareSolverrSessionName: String? val flareSolverrSessionName: String?
val flareSolverrSessionTtl: Int? val flareSolverrSessionTtl: Int?
val flareSolverrAsResponseFallback: Boolean?
} }
data class PartialSettingsType( data class PartialSettingsType(
@@ -151,6 +152,7 @@ data class PartialSettingsType(
override val flareSolverrTimeout: Int?, override val flareSolverrTimeout: Int?,
override val flareSolverrSessionName: String?, override val flareSolverrSessionName: String?,
override val flareSolverrSessionTtl: Int?, override val flareSolverrSessionTtl: Int?,
override val flareSolverrAsResponseFallback: Boolean?,
) : Settings ) : Settings
class SettingsType( class SettingsType(
@@ -213,6 +215,7 @@ class SettingsType(
override val flareSolverrTimeout: Int, override val flareSolverrTimeout: Int,
override val flareSolverrSessionName: String, override val flareSolverrSessionName: String,
override val flareSolverrSessionTtl: Int, override val flareSolverrSessionTtl: Int,
override val flareSolverrAsResponseFallback: Boolean,
) : Settings { ) : Settings {
constructor(config: ServerConfig = serverConfig) : this( constructor(config: ServerConfig = serverConfig) : this(
config.ip.value, config.ip.value,
@@ -270,5 +273,6 @@ class SettingsType(
config.flareSolverrTimeout.value, config.flareSolverrTimeout.value,
config.flareSolverrSessionName.value, config.flareSolverrSessionName.value,
config.flareSolverrSessionTtl.value, config.flareSolverrSessionTtl.value,
config.flareSolverrAsResponseFallback.value,
) )
} }

View File

@@ -141,6 +141,7 @@ class ServerConfig(getConfig: () -> Config, val moduleName: String = SERVER_CONF
val flareSolverrTimeout: MutableStateFlow<Int> by OverrideConfigValue(IntConfigAdapter) val flareSolverrTimeout: MutableStateFlow<Int> by OverrideConfigValue(IntConfigAdapter)
val flareSolverrSessionName: MutableStateFlow<String> by OverrideConfigValue(StringConfigAdapter) val flareSolverrSessionName: MutableStateFlow<String> by OverrideConfigValue(StringConfigAdapter)
val flareSolverrSessionTtl: MutableStateFlow<Int> by OverrideConfigValue(IntConfigAdapter) val flareSolverrSessionTtl: MutableStateFlow<Int> by OverrideConfigValue(IntConfigAdapter)
val flareSolverrAsResponseFallback: MutableStateFlow<Boolean> by OverrideConfigValue(BooleanConfigAdapter)
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
fun <T> subscribeTo( fun <T> subscribeTo(

View File

@@ -67,3 +67,4 @@ server.flareSolverrUrl = "http://localhost:8191"
server.flareSolverrTimeout = 60 # time in seconds server.flareSolverrTimeout = 60 # time in seconds
server.flareSolverrSessionName = "suwayomi" server.flareSolverrSessionName = "suwayomi"
server.flareSolverrSessionTtl = 15 # time in minutes server.flareSolverrSessionTtl = 15 # time in minutes
server.flareSolverrAsResponseFallback = false

View File

@@ -67,3 +67,4 @@ server.flareSolverrUrl = "http://localhost:8191"
server.flareSolverrTimeout = 60 # time in seconds server.flareSolverrTimeout = 60 # time in seconds
server.flareSolverrSessionName = "suwayomi" server.flareSolverrSessionName = "suwayomi"
server.flareSolverrSessionTtl = 15 # time in minutes server.flareSolverrSessionTtl = 15 # time in minutes
server.flareSolverrAsResponseFallback = false