From f80dab6f73d83db3745e094d425a2f1ea7ee3fb8 Mon Sep 17 00:00:00 2001 From: AntsyLich <59261191+AntsyLich@users.noreply.github.com> Date: Wed, 31 Aug 2022 22:25:15 +0600 Subject: [PATCH] xCaliBR Scans: Update anti scrap logic (#13265) * xCaliBR Scans: Update anti scrap logic Also handles HTTP 103 through WebView Fixes #10528 * Remove unused import * Remove Http103Interceptor --- .../xcalibrscans/src/AntiScrapInterceptor.kt | 78 ++++++++++++++++ .../src/interceptor/MirrorImageInterceptor.kt | 61 ------------ .../interceptor/SplittedImageInterceptor.kt | 73 --------------- .../xcalibrscans/src/xCaliBRScans.kt | 93 +++++++------------ .../mangathemesia/MangaThemesiaGenerator.kt | 2 +- 5 files changed, 114 insertions(+), 193 deletions(-) create mode 100644 multisrc/overrides/mangathemesia/xcalibrscans/src/AntiScrapInterceptor.kt delete mode 100644 multisrc/overrides/mangathemesia/xcalibrscans/src/interceptor/MirrorImageInterceptor.kt delete mode 100644 multisrc/overrides/mangathemesia/xcalibrscans/src/interceptor/SplittedImageInterceptor.kt diff --git a/multisrc/overrides/mangathemesia/xcalibrscans/src/AntiScrapInterceptor.kt b/multisrc/overrides/mangathemesia/xcalibrscans/src/AntiScrapInterceptor.kt new file mode 100644 index 000000000..72f99c080 --- /dev/null +++ b/multisrc/overrides/mangathemesia/xcalibrscans/src/AntiScrapInterceptor.kt @@ -0,0 +1,78 @@ +package eu.kanade.tachiyomi.extension.en.xcalibrscans + +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.Rect +import okhttp3.Interceptor +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.Protocol +import okhttp3.Response +import okhttp3.ResponseBody.Companion.toResponseBody +import java.io.ByteArrayOutputStream + +class AntiScrapInterceptor : Interceptor { + + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + if (request.url.fragment != ANTI_SCRAP_FRAGMENT) { + return chain.proceed(request) + } + + val imageUrls = request.url + .queryParameter("urls").orEmpty() + .split(IMAGE_URLS_SEPARATOR) + + var width = 0 + var height = 0 + + val imageBitmaps = imageUrls.map { imageUrl -> + val newRequest = request.newBuilder().url(imageUrl).build() + val response = chain.proceed(newRequest) + + val bitmap = BitmapFactory.decodeStream(response.body!!.byteStream()) + response.close() + + width += bitmap.width + height = bitmap.height + + bitmap + } + + val mergedBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + Canvas(mergedBitmap).apply { + // Will mirror everything that are applied afterwards + scale(-1F, 1F, width / 2F, height / 2F) + + // Merge the bitmaps vertically + var left = 0 + imageBitmaps.forEach { bitmap -> + val srcRect = Rect(0, 0, bitmap.width, bitmap.height) + val dstRect = Rect(left, 0, left + bitmap.width, bitmap.height) + + drawBitmap(bitmap, srcRect, dstRect, null) + + left += bitmap.width + } + } + + val baos = ByteArrayOutputStream() + mergedBitmap.compress(Bitmap.CompressFormat.PNG, 100, baos) + + return Response.Builder() + .code(200) + .protocol(Protocol.HTTP_1_1) + .request(request) + .message("OK") + .body(baos.toByteArray().toResponseBody(pngMediaType)) + .build() + } + + companion object { + const val ANTI_SCRAP_FRAGMENT = "ANTI_SCRAP" + + const val IMAGE_URLS_SEPARATOR = "|" + + val pngMediaType = "image/png".toMediaType() + } +} diff --git a/multisrc/overrides/mangathemesia/xcalibrscans/src/interceptor/MirrorImageInterceptor.kt b/multisrc/overrides/mangathemesia/xcalibrscans/src/interceptor/MirrorImageInterceptor.kt deleted file mode 100644 index 8e22d0d89..000000000 --- a/multisrc/overrides/mangathemesia/xcalibrscans/src/interceptor/MirrorImageInterceptor.kt +++ /dev/null @@ -1,61 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.xcalibrscans.interceptor - -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.Matrix -import okhttp3.Interceptor -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.Protocol -import okhttp3.Response -import okhttp3.ResponseBody.Companion.toResponseBody -import java.io.ByteArrayOutputStream - -class MirrorImageInterceptor : Interceptor { - - override fun intercept(chain: Interceptor.Chain): Response { - if (!chain.request().url.toString().endsWith(MIRRORED_IMAGE_SUFFIX)) { - return chain.proceed(chain.request()) - } - - val imageUrl = chain.request().url.toString() - .removeSuffix(MIRRORED_IMAGE_SUFFIX) - - val request = chain.request().newBuilder().url(imageUrl).build() - val response = chain.proceed(request) - - val bitmap = BitmapFactory.decodeStream(response.body!!.byteStream()) - - val result = bitmap.flipHorizontally() - - val output = ByteArrayOutputStream() - result.compress(Bitmap.CompressFormat.PNG, 100, output) - - val responseBody = output.toByteArray().toResponseBody("image/png".toMediaType()) - - return Response.Builder() - .code(200) - .protocol(Protocol.HTTP_1_1) - .request(chain.request()) - .message("OK") - .body(responseBody) - .build() - } - - private fun Bitmap.flipHorizontally(): Bitmap { - val matrix = Matrix().apply { - postScale( - -1F, - 1F, - this@flipHorizontally.width / 2F, - this@flipHorizontally.height / 2F - ) - } - return Bitmap.createBitmap(this, 0, 0, this.width, this.height, matrix, true) - } -} - -const val MIRRORED_IMAGE_SUFFIX = "?mirrored" - -fun String.prepareMirrorImageForInterceptor(): String { - return "$this$MIRRORED_IMAGE_SUFFIX" -} diff --git a/multisrc/overrides/mangathemesia/xcalibrscans/src/interceptor/SplittedImageInterceptor.kt b/multisrc/overrides/mangathemesia/xcalibrscans/src/interceptor/SplittedImageInterceptor.kt deleted file mode 100644 index be33c78d9..000000000 --- a/multisrc/overrides/mangathemesia/xcalibrscans/src/interceptor/SplittedImageInterceptor.kt +++ /dev/null @@ -1,73 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.xcalibrscans.interceptor - -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.Canvas -import android.graphics.Rect -import okhttp3.Interceptor -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.Protocol -import okhttp3.Response -import okhttp3.ResponseBody.Companion.toResponseBody -import java.io.ByteArrayOutputStream - -class SplittedImageInterceptor : Interceptor { - - override fun intercept(chain: Interceptor.Chain): Response { - if (!chain.request().url.toString().endsWith(SPLITTED_IMAGE_SUFFIX)) { - return chain.proceed(chain.request()) - } - - val imageUrls = chain.request().url.toString() - .removeSuffix(SPLITTED_IMAGE_SUFFIX) - .split("%7C") - - var width = 0 - var height = 0 - - val imageBitmaps = imageUrls.map { imageUrl -> - val request = chain.request().newBuilder().url(imageUrl).build() - val response = chain.proceed(request) - - val bitmap = BitmapFactory.decodeStream(response.body!!.byteStream()) - - width += bitmap.width - height = bitmap.height - - bitmap - } - - val result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) - val canvas = Canvas(result) - - var left = 0 - - imageBitmaps.forEach { bitmap -> - val srcRect = Rect(0, 0, bitmap.width, bitmap.height) - val dstRect = Rect(left, 0, left + bitmap.width, bitmap.height) - - canvas.drawBitmap(bitmap, srcRect, dstRect, null) - - left += bitmap.width - } - - val output = ByteArrayOutputStream() - result.compress(Bitmap.CompressFormat.PNG, 100, output) - - val responseBody = output.toByteArray().toResponseBody("image/png".toMediaType()) - - return Response.Builder() - .code(200) - .protocol(Protocol.HTTP_1_1) - .request(chain.request()) - .message("OK") - .body(responseBody) - .build() - } -} - -const val SPLITTED_IMAGE_SUFFIX = "?splitted" - -fun List.prepareSplittedImageForInterceptor(): String { - return "${this.joinToString("|")}$SPLITTED_IMAGE_SUFFIX" -} diff --git a/multisrc/overrides/mangathemesia/xcalibrscans/src/xCaliBRScans.kt b/multisrc/overrides/mangathemesia/xcalibrscans/src/xCaliBRScans.kt index 3e9e2f508..cf1009134 100644 --- a/multisrc/overrides/mangathemesia/xcalibrscans/src/xCaliBRScans.kt +++ b/multisrc/overrides/mangathemesia/xcalibrscans/src/xCaliBRScans.kt @@ -1,17 +1,13 @@ package eu.kanade.tachiyomi.extension.en.xcalibrscans -import eu.kanade.tachiyomi.extension.en.xcalibrscans.interceptor.MirrorImageInterceptor -import eu.kanade.tachiyomi.extension.en.xcalibrscans.interceptor.SplittedImageInterceptor -import eu.kanade.tachiyomi.extension.en.xcalibrscans.interceptor.prepareMirrorImageForInterceptor -import eu.kanade.tachiyomi.extension.en.xcalibrscans.interceptor.prepareSplittedImageForInterceptor +import android.util.Log import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.network.interceptor.rateLimit import eu.kanade.tachiyomi.source.model.Page -import kotlinx.serialization.json.jsonArray -import kotlinx.serialization.json.jsonPrimitive +import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.OkHttpClient import org.jsoup.nodes.Document -import java.lang.IllegalArgumentException +import org.jsoup.nodes.Element import java.util.concurrent.TimeUnit class xCaliBRScans : MangaThemesia("xCaliBR Scans", "https://xcalibrscans.com", "en") { @@ -19,69 +15,50 @@ class xCaliBRScans : MangaThemesia("xCaliBR Scans", "https://xcalibrscans.com", override val client: OkHttpClient = network.cloudflareClient.newBuilder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) + .addInterceptor(AntiScrapInterceptor()) .rateLimit(2) - .addNetworkInterceptor(SplittedImageInterceptor()) - .addNetworkInterceptor(MirrorImageInterceptor()) .build() override val hasProjectPage = true - override val pageSelector = "div#readerarea > p, div#readerarea > div" - override fun pageListParse(document: Document): List { - val htmlPages = mutableListOf() + document.selectFirst("div#readerarea .sword_box") ?: return super.pageListParse(document) - document.select(pageSelector) - .filterNot { - it.select("img").all { imgEl -> - imgEl.attr("abs:src").isNullOrEmpty() - } - } - .map { el -> - if (el.tagName() == "div") { - when { - el.hasClass("kage") -> { - el.select("img").map { imgEl -> - val index = htmlPages.size - val imageUrl = - imgEl.attr("abs:src").prepareMirrorImageForInterceptor() - htmlPages.add(Page(index, "", imageUrl)) - } - } - el.hasClass("row") -> { - val index = htmlPages.size - val imageUrls = el.select("img").map { imgEl -> - imgEl.attr("abs:src") - }.prepareSplittedImageForInterceptor() - htmlPages.add(Page(index, "", imageUrls)) - } - else -> { - val index = htmlPages.size - Page(index, "", el.select("img").attr("abs:src")) - } + val imgUrls = mutableListOf() + + // Selects all direct descendant of "div#readerarea" + document.select("div#readerarea > *") + .forEach { element -> + when { + element.tagName() == "p" -> { + val imgUrl = element.selectFirst("img").imgAttr() + imgUrls.add(imgUrl) + } + element.tagName() == "div" && element.hasClass("kage") -> { + parseAntiScrapScramble(element, imgUrls) + } + else -> { + Log.d("xCaliBR Scans", "Unknown element for page parsing $element") } - } else { - val index = htmlPages.size - Page(index, "", el.select("img").attr("abs:src")) } } - countViews(document) + return imgUrls.mapIndexed { index, imageUrl -> Page(index, imageUrl = imageUrl) } + } - // Some sites also loads pages via javascript - if (htmlPages.isNotEmpty()) { return htmlPages } + private fun parseAntiScrapScramble(element: Element, destination: MutableList) { + element.select("div.sword") + .forEach { swordDiv -> + val imgUrls = swordDiv.select("img").map { it.imgAttr() } + val urls = imgUrls.joinToString(AntiScrapInterceptor.IMAGE_URLS_SEPARATOR) + val url = baseUrl.toHttpUrl() + .newBuilder() + .addQueryParameter("urls", urls) + .fragment(AntiScrapInterceptor.ANTI_SCRAP_FRAGMENT) + .build() + .toString() - val docString = document.toString() - val imageListJson = JSON_IMAGE_LIST_REGEX.find(docString)?.destructured?.toList()?.get(0).orEmpty() - val imageList = try { - json.parseToJsonElement(imageListJson).jsonArray - } catch (_: IllegalArgumentException) { - emptyList() - } - val scriptPages = imageList.mapIndexed { i, jsonEl -> - Page(i, "", jsonEl.jsonPrimitive.content) - } - - return scriptPages + destination.add(url) + } } } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesiaGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesiaGenerator.kt index 81b62a1ed..34705a525 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesiaGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangathemesia/MangaThemesiaGenerator.kt @@ -105,7 +105,7 @@ class MangaThemesiaGenerator : ThemeSourceGenerator { SingleLang("West Manga", "https://westmanga.info", "id", overrideVersionCode = 1), SingleLang("White Cloud Pavilion (New)", "https://www.whitecloudpavilion.com", "en", pkgName = "whitecloudpavilionnew", className = "WhiteCloudPavilion"), SingleLang("World Romance Translation", "https://wrt.my.id", "id", overrideVersionCode = 10), - SingleLang("xCaliBR Scans", "https://xcalibrscans.com", "en", overrideVersionCode = 3), + SingleLang("xCaliBR Scans", "https://xcalibrscans.com", "en", overrideVersionCode = 4), ) companion object {