diff --git a/src/en/koushoku/build.gradle b/src/en/koushoku/build.gradle index da76171da..1d29b84e0 100644 --- a/src/en/koushoku/build.gradle +++ b/src/en/koushoku/build.gradle @@ -5,7 +5,7 @@ ext { extName = 'Koushoku' pkgNameSuffix = 'en.koushoku' extClass = '.Koushoku' - extVersionCode = 14 + extVersionCode = 15 isNsfw = true } diff --git a/src/en/koushoku/src/eu/kanade/tachiyomi/extension/en/koushoku/GoogleTranslateInterceptor.kt b/src/en/koushoku/src/eu/kanade/tachiyomi/extension/en/koushoku/GoogleTranslateInterceptor.kt deleted file mode 100644 index 06513ba8e..000000000 --- a/src/en/koushoku/src/eu/kanade/tachiyomi/extension/en/koushoku/GoogleTranslateInterceptor.kt +++ /dev/null @@ -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() - } -} diff --git a/src/en/koushoku/src/eu/kanade/tachiyomi/extension/en/koushoku/GoogleTranslateNetworkInterceptor.kt b/src/en/koushoku/src/eu/kanade/tachiyomi/extension/en/koushoku/GoogleTranslateNetworkInterceptor.kt deleted file mode 100644 index 5c6b51038..000000000 --- a/src/en/koushoku/src/eu/kanade/tachiyomi/extension/en/koushoku/GoogleTranslateNetworkInterceptor.kt +++ /dev/null @@ -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() - } -} diff --git a/src/en/koushoku/src/eu/kanade/tachiyomi/extension/en/koushoku/Koushoku.kt b/src/en/koushoku/src/eu/kanade/tachiyomi/extension/en/koushoku/Koushoku.kt index a2af912c1..e785d2455 100644 --- a/src/en/koushoku/src/eu/kanade/tachiyomi/extension/en/koushoku/Koushoku.kt +++ b/src/en/koushoku/src/eu/kanade/tachiyomi/extension/en/koushoku/Koushoku.kt @@ -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 { - val totalPages = document.selectFirst(".total").text().toInt() + override fun pageListParse(response: Response): List { + 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 = + 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") } } diff --git a/src/en/koushoku/src/eu/kanade/tachiyomi/extension/en/koushoku/KoushokuWebViewInterceptor.kt b/src/en/koushoku/src/eu/kanade/tachiyomi/extension/en/koushoku/KoushokuWebViewInterceptor.kt new file mode 100644 index 000000000..6bcc34335 --- /dev/null +++ b/src/en/koushoku/src/eu/kanade/tachiyomi/extension/en/koushoku/KoushokuWebViewInterceptor.kt @@ -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()) + 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() + } +}