SpyFakku: Add mirror selection to preferences page (#11373)
* Add mirror selection to SpyFakku * Move this to resolve null pointer * mirror pref + trust certs for airdns domain + anibus bypass for airdns domain * inspector crash --------- Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
This commit is contained in:
parent
709609fb70
commit
fdc8e29671
@ -1,7 +1,7 @@
|
||||
ext {
|
||||
extName = 'SpyFakku'
|
||||
extClass = '.SpyFakku'
|
||||
extVersionCode = 11
|
||||
extVersionCode = 12
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,99 @@
|
||||
package eu.kanade.tachiyomi.extension.en.spyfakku
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.net.http.SslError
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.webkit.CookieManager
|
||||
import android.webkit.SslErrorHandler
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.Jsoup
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
object AnibusInterceptor : Interceptor {
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
val response = chain.proceed(request)
|
||||
|
||||
return if (request.url.host.contains("airdns")) {
|
||||
val document = Jsoup.parse(
|
||||
response.peekBody(Long.MAX_VALUE).string(),
|
||||
request.url.toString(),
|
||||
)
|
||||
if (document.selectFirst("script#anubis_challenge") != null) {
|
||||
response.close()
|
||||
if (!resolveInWebView(request)) {
|
||||
throw IOException("Failed to resolve challenge in WebView")
|
||||
} else {
|
||||
chain.proceed(request)
|
||||
}
|
||||
} else {
|
||||
response
|
||||
}
|
||||
} else {
|
||||
response
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
private fun resolveInWebView(request: Request): Boolean {
|
||||
val context = Injekt.get<Application>()
|
||||
val cookieManager = CookieManager.getInstance()
|
||||
val latch = CountDownLatch(1)
|
||||
var webView: WebView? = null
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
|
||||
handler.post {
|
||||
val webview = WebView(context)
|
||||
.also { webView = it }
|
||||
|
||||
with(webview.settings) {
|
||||
javaScriptEnabled = true
|
||||
domStorageEnabled = true
|
||||
databaseEnabled = true
|
||||
userAgentString = request.header("User-Agent")
|
||||
}
|
||||
webview.webViewClient = object : WebViewClient() {
|
||||
@SuppressLint("WebViewClientOnReceivedSslError")
|
||||
override fun onReceivedSslError(
|
||||
view: WebView,
|
||||
handler: SslErrorHandler,
|
||||
error: SslError,
|
||||
) {
|
||||
handler.proceed()
|
||||
}
|
||||
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
super.onPageFinished(view, url)
|
||||
val cookie = cookieManager.getCookie(url)
|
||||
.split("; ").map { it.split("=", limit = 2) }
|
||||
val auth = cookie.firstOrNull { it.first().contains("anubis-auth") && it.last().isNotBlank() }
|
||||
if (auth != null) {
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
webview.loadUrl(request.url.toString())
|
||||
}
|
||||
|
||||
latch.await(20, TimeUnit.SECONDS)
|
||||
|
||||
handler.post {
|
||||
webView?.stopLoading()
|
||||
webView?.destroy()
|
||||
}
|
||||
|
||||
return latch.count != 1L
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,11 @@
|
||||
package eu.kanade.tachiyomi.extension.en.spyfakku
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
@ -9,6 +13,8 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import keiyoushi.utils.getPreferences
|
||||
import keiyoushi.utils.tryParse
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
@ -23,38 +29,95 @@ import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.security.SecureRandom
|
||||
import java.security.cert.X509Certificate
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.TrustManager
|
||||
import javax.net.ssl.X509TrustManager
|
||||
import kotlin.random.Random
|
||||
|
||||
class SpyFakku : HttpSource() {
|
||||
class SpyFakku : HttpSource(), ConfigurableSource {
|
||||
|
||||
override val name = "SpyFakku"
|
||||
|
||||
override val baseUrl = "https://hentalk.pw"
|
||||
|
||||
private val baseImageUrl = "$baseUrl/image"
|
||||
|
||||
private val baseApiUrl = "$baseUrl/api"
|
||||
|
||||
override val lang = "en"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
private val preferences = getPreferences()
|
||||
|
||||
override val baseUrl: String
|
||||
get() {
|
||||
val index = preferences.getString(MIRROR_PREF_KEY, "0")!!.toInt()
|
||||
.coerceAtMost(mirrors.size - 1)
|
||||
|
||||
return mirrors[index]
|
||||
}
|
||||
|
||||
private val baseImageUrl = "$tmpCdnUrl/image"
|
||||
|
||||
private val baseApiUrl get() = "$baseUrl/api"
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
override val client = network.cloudflareClient.newBuilder()
|
||||
// add referer in interceptor due to domain change preference
|
||||
.addNetworkInterceptor { chain ->
|
||||
val request = chain.request().newBuilder()
|
||||
.header("Referer", "$baseUrl/")
|
||||
.header("Origin", baseUrl)
|
||||
.build()
|
||||
|
||||
chain.proceed(request)
|
||||
}
|
||||
// change domain of image urls
|
||||
.addInterceptor { chain ->
|
||||
val url = chain.request().url
|
||||
if (url.host == tmpCdnDomain) {
|
||||
val (host, port) = baseUrl.toHttpUrl().let { it.host to it.port }
|
||||
val newUrl = url.newBuilder()
|
||||
.scheme("https")
|
||||
.host(host)
|
||||
.port(port)
|
||||
.build()
|
||||
|
||||
val request = chain.request().newBuilder()
|
||||
.url(newUrl)
|
||||
.build()
|
||||
|
||||
chain.proceed(request)
|
||||
} else {
|
||||
chain.proceed(chain.request())
|
||||
}
|
||||
}
|
||||
// airdns domain is self-signed
|
||||
.apply {
|
||||
val naiveTrustManager = @SuppressLint("CustomX509TrustManager")
|
||||
object : X509TrustManager {
|
||||
override fun getAcceptedIssuers(): Array<X509Certificate?> = emptyArray()
|
||||
override fun checkClientTrusted(certs: Array<X509Certificate>, authType: String) = Unit
|
||||
override fun checkServerTrusted(certs: Array<X509Certificate>, authType: String) = Unit
|
||||
}
|
||||
|
||||
val insecureSocketFactory = SSLContext.getInstance("SSL").apply {
|
||||
val trustAllCerts = arrayOf<TrustManager>(naiveTrustManager)
|
||||
init(null, trustAllCerts, SecureRandom())
|
||||
}.socketFactory
|
||||
|
||||
sslSocketFactory(insecureSocketFactory, naiveTrustManager)
|
||||
hostnameVerifier { _, _ -> true }
|
||||
}
|
||||
// airdns domain is protected by Anibus
|
||||
.addInterceptor(AnibusInterceptor)
|
||||
.rateLimit(2, 1, TimeUnit.SECONDS)
|
||||
.build()
|
||||
|
||||
private val charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
|
||||
override fun headersBuilder() = super.headersBuilder()
|
||||
.set("Referer", "$baseUrl/")
|
||||
.set("Origin", baseUrl)
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
return GET("$baseApiUrl/library?sort=released_at&page=$page", headers)
|
||||
}
|
||||
@ -287,11 +350,7 @@ class SpyFakku : HttpSource() {
|
||||
SChapter.create().apply {
|
||||
name = "Chapter"
|
||||
url = manga.url
|
||||
date_upload = try {
|
||||
releasedAtFormat.parse(add.released_at)!!.time
|
||||
} catch (e: Exception) {
|
||||
0L
|
||||
}
|
||||
date_upload = releasedAtFormat.tryParse(add.released_at)
|
||||
},
|
||||
),
|
||||
)
|
||||
@ -362,4 +421,24 @@ class SpyFakku : HttpSource() {
|
||||
}
|
||||
|
||||
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
ListPreference(screen.context).apply {
|
||||
key = MIRROR_PREF_KEY
|
||||
title = "Preferred Mirror"
|
||||
entries = mirrors
|
||||
entryValues = Array(mirrors.size) { it.toString() }
|
||||
summary = "%s"
|
||||
setDefaultValue("0")
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
}
|
||||
|
||||
private const val MIRROR_PREF_KEY = "pref_mirror"
|
||||
private val mirrors = arrayOf(
|
||||
"https://hentalk.pw",
|
||||
"https://fakku.cc",
|
||||
"https://fakkuonion.airdns.org:4096",
|
||||
)
|
||||
private const val tmpCdnDomain = "127.0.0.1"
|
||||
private const val tmpCdnUrl = "http://$tmpCdnDomain"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user