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 {
|
ext {
|
||||||
extName = 'SpyFakku'
|
extName = 'SpyFakku'
|
||||||
extClass = '.SpyFakku'
|
extClass = '.SpyFakku'
|
||||||
extVersionCode = 11
|
extVersionCode = 12
|
||||||
isNsfw = true
|
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
|
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.GET
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
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.FilterList
|
||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
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.SManga
|
||||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
import keiyoushi.utils.getPreferences
|
||||||
|
import keiyoushi.utils.tryParse
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
@ -23,38 +29,95 @@ import okhttp3.Request
|
|||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
import javax.net.ssl.SSLContext
|
||||||
|
import javax.net.ssl.TrustManager
|
||||||
|
import javax.net.ssl.X509TrustManager
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
class SpyFakku : HttpSource() {
|
class SpyFakku : HttpSource(), ConfigurableSource {
|
||||||
|
|
||||||
override val name = "SpyFakku"
|
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 lang = "en"
|
||||||
|
|
||||||
override val supportsLatest = true
|
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()
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
override val client = network.cloudflareClient.newBuilder()
|
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)
|
.rateLimit(2, 1, TimeUnit.SECONDS)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
private val charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
private val charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
|
|
||||||
override fun headersBuilder() = super.headersBuilder()
|
|
||||||
.set("Referer", "$baseUrl/")
|
|
||||||
.set("Origin", baseUrl)
|
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request {
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
return GET("$baseApiUrl/library?sort=released_at&page=$page", headers)
|
return GET("$baseApiUrl/library?sort=released_at&page=$page", headers)
|
||||||
}
|
}
|
||||||
@ -287,11 +350,7 @@ class SpyFakku : HttpSource() {
|
|||||||
SChapter.create().apply {
|
SChapter.create().apply {
|
||||||
name = "Chapter"
|
name = "Chapter"
|
||||||
url = manga.url
|
url = manga.url
|
||||||
date_upload = try {
|
date_upload = releasedAtFormat.tryParse(add.released_at)
|
||||||
releasedAtFormat.parse(add.released_at)!!.time
|
|
||||||
} catch (e: Exception) {
|
|
||||||
0L
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -362,4 +421,24 @@ class SpyFakku : HttpSource() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
|
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