update rate limit, allow unknown sizes, update for site changes (#12922)
This commit is contained in:
parent
fc61a4e797
commit
415bcb5316
|
@ -5,7 +5,7 @@ ext {
|
|||
extName = 'Koushoku'
|
||||
pkgNameSuffix = 'en.koushoku'
|
||||
extClass = '.Koushoku'
|
||||
extVersionCode = 14
|
||||
extVersionCode = 15
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.en.koushoku
|
||||
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
||||
class GoogleTranslateInterceptor : Interceptor {
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
|
||||
return if (request.url.host == "koushoku.org") {
|
||||
val newUrl = googlify(request.url)
|
||||
val newRequest = request.newBuilder()
|
||||
.url(newUrl)
|
||||
.build()
|
||||
|
||||
chain.proceed(newRequest)
|
||||
} else {
|
||||
chain.proceed(request)
|
||||
}
|
||||
}
|
||||
|
||||
private fun googlify(url: HttpUrl): HttpUrl {
|
||||
val newHost = url.host
|
||||
.replace("-", "--")
|
||||
.replace('.', '-')
|
||||
.plus(".translate.goog")
|
||||
|
||||
return url.newBuilder()
|
||||
.host(newHost)
|
||||
// `_x_tr_sl` and `_x_tr_tl` must be different
|
||||
.addQueryParameter("_x_tr_sl", "en")
|
||||
.addQueryParameter("_x_tr_tl", "jp")
|
||||
.addQueryParameter("_x_tr_hl", "en")
|
||||
.build()
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.en.koushoku
|
||||
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
||||
class GoogleTranslateNetworkInterceptor : Interceptor {
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
val response = chain.proceed(request)
|
||||
|
||||
if (
|
||||
response.request.url.host == "koushoku-org.translate.goog" &&
|
||||
response.request.url.pathSegments.getOrNull(0) == "d" &&
|
||||
response.code in 300..399
|
||||
) {
|
||||
val newUrl = degooglify(response.headers["Location"]!!.toHttpUrl())
|
||||
|
||||
return response.newBuilder()
|
||||
.header("Location", newUrl.toString())
|
||||
.build()
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
private fun degooglify(url: HttpUrl): HttpUrl {
|
||||
val newHost = url.host
|
||||
.substringBeforeLast(".translate.goog")
|
||||
.replace('-', '.')
|
||||
.replace("..", "-")
|
||||
|
||||
return url.newBuilder()
|
||||
.host(newHost)
|
||||
.removeAllQueryParameters("_x_tr_sl")
|
||||
.removeAllQueryParameters("_x_tr_tl")
|
||||
.removeAllQueryParameters("_x_tr_hl")
|
||||
.build()
|
||||
}
|
||||
}
|
|
@ -21,6 +21,9 @@ import okhttp3.Response
|
|||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import rx.Observable
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import kotlin.random.Random
|
||||
|
||||
class Koushoku : ParsedHttpSource() {
|
||||
companion object {
|
||||
|
@ -30,6 +33,7 @@ class Koushoku : ParsedHttpSource() {
|
|||
const val magazinesSelector = ".metadata a[href^='/magazines/']"
|
||||
|
||||
private val PATTERN_IMAGES = "(.+/)(\\d+)(.*)".toRegex()
|
||||
private val DATE_FORMAT = SimpleDateFormat("E, d MMM yyy HH:mm:ss 'UTC'", Locale.US)
|
||||
}
|
||||
|
||||
override val baseUrl = "https://koushoku.org"
|
||||
|
@ -38,14 +42,25 @@ class Koushoku : ParsedHttpSource() {
|
|||
override val supportsLatest = true
|
||||
|
||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
||||
.addInterceptor(GoogleTranslateInterceptor())
|
||||
.addNetworkInterceptor(GoogleTranslateNetworkInterceptor())
|
||||
.rateLimitHost("https://ksk-h7glm2.xyz".toHttpUrl(), 1)
|
||||
.addInterceptor(KoushokuWebViewInterceptor())
|
||||
// Site: 40req per 1 minute
|
||||
// Here: 1req per 2 sec -> 30req per 1 minute
|
||||
// (somewhat lower due to caching)
|
||||
.rateLimitHost("https://koushoku.org".toHttpUrl(), 1, 2)
|
||||
.build()
|
||||
|
||||
override fun headersBuilder(): Headers.Builder = super.headersBuilder()
|
||||
.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36")
|
||||
.add("Referer", "$baseUrl/")
|
||||
override fun headersBuilder(): Headers.Builder {
|
||||
val chromeStableVersion = listOf("104.0.5112.69", "103.0.5060.71", "103.0.5060.70", "103.0.5060.53", "103.0.5060.129", "102.0.5005.99", "102.0.5005.98", "102.0.5005.78", "102.0.5005.125").random()
|
||||
val chromeCanaryVersion = listOf("106.0.5227.0", "106.0.5209.0", "106.0.5206.0", "106.0.5201.2", "106.0.5201.0", "106.0.5200.0", "106.0.5199.0", "106.0.5197.0", "106.0.5196.0", "105.0.5195.2", "105.0.5194.0", "105.0.5193.0", "105.0.5192.0", "105.0.5191.0", "105.0.5190.0", "105.0.5189.0", "105.0.5186.0", "105.0.5185.0", "105.0.5184.0", "105.0.5182.0", "105.0.5180.0", "105.0.5179.3", "105.0.5178.0", "105.0.5177.2", "105.0.5176.0", "105.0.5175.0", "105.0.5174.0", "105.0.5173.0", "105.0.5172.0", "105.0.5171.0").random()
|
||||
val chromeVersion = if (Random.nextFloat() > 0.2) chromeStableVersion else chromeCanaryVersion
|
||||
|
||||
val deviceInfo = if (Random.nextFloat() > 0.2) "" else "; " + listOf("SM-S908B", "SM-S908U", "SM-A536B", "SM-A536U", "SM-S901B", "SM-S901U", "SM-A736B", "SM-G973F", "SM-A528B", "SM-G975U", "SM-G990B", "SM-G990U").random()
|
||||
val androidVersion = IntRange(if (deviceInfo.isEmpty()) 9 else 11, 12).random()
|
||||
|
||||
return super.headersBuilder()
|
||||
.set("User-Agent", "Mozilla/5.0 (Linux; Android $androidVersion$deviceInfo) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/$chromeVersion Mobile Safari/537.36")
|
||||
.add("Referer", "$baseUrl/")
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/?page=$page", headers)
|
||||
override fun latestUpdatesSelector() = "#archives.feed .entries > .entry"
|
||||
|
@ -155,8 +170,13 @@ class Koushoku : ParsedHttpSource() {
|
|||
SChapter.create().apply {
|
||||
setUrlWithoutDomain(response.request.url.encodedPath)
|
||||
name = "Chapter"
|
||||
date_upload = document.select("tr > td:first-child:contains(Uploaded Date) + td")
|
||||
.attr("data-unix").toLong() * 1000
|
||||
|
||||
val dateText = document.select("tr > td:first-child:contains(Uploaded Date) + td")
|
||||
.text()
|
||||
date_upload = runCatching { DATE_FORMAT.parse(dateText) }
|
||||
.getOrNull()
|
||||
?.time
|
||||
?: 0
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -168,36 +188,27 @@ class Koushoku : ParsedHttpSource() {
|
|||
|
||||
override fun pageListRequest(chapter: SChapter) = GET("$baseUrl${chapter.url}/1", headers)
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
val totalPages = document.selectFirst(".total").text().toInt()
|
||||
override fun pageListParse(response: Response): List<Page> {
|
||||
val document = response.asJsoup()
|
||||
|
||||
val totalPages = document.selectFirst(".total")?.text()?.toInt() ?: 0
|
||||
if (totalPages == 0)
|
||||
throw UnsupportedOperationException("Error: Empty pages (try Webview)")
|
||||
|
||||
val url = document.selectFirst(".main img, main img").absUrl("src")
|
||||
val match = PATTERN_IMAGES.find(url)!!
|
||||
val match = PATTERN_IMAGES.find(response.request.url.toString())!!
|
||||
val prefix = match.groupValues[1]
|
||||
val suffix = match.groupValues[3]
|
||||
|
||||
return (1..totalPages).map {
|
||||
Page(it, "", "$prefix$it$suffix")
|
||||
Page(it, "$prefix$it$suffix")
|
||||
}
|
||||
}
|
||||
|
||||
override fun imageRequest(page: Page): Request {
|
||||
val url = page.imageUrl!!.toHttpUrl()
|
||||
override fun pageListParse(document: Document): List<Page> =
|
||||
throw UnsupportedOperationException("Not used")
|
||||
|
||||
val newHeaders = if (baseUrl.toHttpUrl().host != url.host) {
|
||||
headersBuilder()
|
||||
.add("Origin", baseUrl)
|
||||
.build()
|
||||
} else {
|
||||
headers
|
||||
}
|
||||
|
||||
return GET(page.imageUrl!!, newHeaders)
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used")
|
||||
override fun imageUrlParse(document: Document): String =
|
||||
document.selectFirst(".main img, main img").absUrl("src")
|
||||
|
||||
override fun getFilterList() = FilterList(
|
||||
SortFilter(
|
||||
|
@ -273,7 +284,7 @@ class Koushoku : ParsedHttpSource() {
|
|||
val pages = document.selectFirst("tr > td:first-child:contains(Pages) + td")
|
||||
append("Pages: ").append(pages.text()).append("\n")
|
||||
|
||||
val size = document.selectFirst("tr > td:first-child:contains(Size) + td")
|
||||
append("Size: ").append(size.text())
|
||||
val size: Element? = document.selectFirst("tr > td:first-child:contains(Size) + td")
|
||||
append("Size: ").append(size?.text() ?: "Unknown")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
package eu.kanade.tachiyomi.extension.en.koushoku
|
||||
|
||||
import android.app.Application
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import okhttp3.Interceptor
|
||||
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
|
||||
|
||||
class KoushokuWebViewInterceptor : Interceptor {
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
val response = chain.proceed(request)
|
||||
|
||||
if (response.headers("Content-Type").any { it.contains("text/html") }) {
|
||||
val responseBody = response.peekBody(1 * 1024 * 1024).string()
|
||||
if (response.code == 403) {
|
||||
val document = Jsoup.parse(responseBody)
|
||||
if (document.selectFirst("h1")?.text()?.contains(Regex("banned$")) == true) {
|
||||
throw IOException("You have been banned. Check WebView for details.")
|
||||
}
|
||||
}
|
||||
|
||||
if (response.networkResponse != null) {
|
||||
try {
|
||||
proceedWithWebView(response, responseBody)
|
||||
} catch (e: Exception) {
|
||||
throw IOException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
private fun proceedWithWebView(response: Response, responseBody: String) {
|
||||
val latch = CountDownLatch(1)
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
|
||||
handler.post {
|
||||
val webView = WebView(Injekt.get<Application>())
|
||||
with(webView.settings) {
|
||||
loadsImagesAutomatically = false
|
||||
userAgentString = response.request.header("User-Agent")
|
||||
}
|
||||
|
||||
webView.webViewClient = object : WebViewClient() {
|
||||
override fun onPageFinished(view: WebView, url: String) {
|
||||
webView.stopLoading()
|
||||
webView.destroy()
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
|
||||
webView.loadDataWithBaseURL(
|
||||
response.request.url.toString(),
|
||||
responseBody,
|
||||
"text/html",
|
||||
"utf-8",
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
latch.await()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue