diff --git a/multisrc/overrides/wpmangastream/xcalibrscans/src/interceptor/MirrorImageInterceptor.kt b/multisrc/overrides/wpmangastream/xcalibrscans/src/interceptor/MirrorImageInterceptor.kt new file mode 100644 index 000000000..8e22d0d89 --- /dev/null +++ b/multisrc/overrides/wpmangastream/xcalibrscans/src/interceptor/MirrorImageInterceptor.kt @@ -0,0 +1,61 @@ +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/wpmangastream/xcalibrscans/src/interceptor/SplittedImageInterceptor.kt b/multisrc/overrides/wpmangastream/xcalibrscans/src/interceptor/SplittedImageInterceptor.kt new file mode 100644 index 000000000..be33c78d9 --- /dev/null +++ b/multisrc/overrides/wpmangastream/xcalibrscans/src/interceptor/SplittedImageInterceptor.kt @@ -0,0 +1,73 @@ +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/wpmangastream/xcalibrscans/src/xCaliBRScans.kt b/multisrc/overrides/wpmangastream/xcalibrscans/src/xCaliBRScans.kt index 790fba3b1..d9f6f43bb 100644 --- a/multisrc/overrides/wpmangastream/xcalibrscans/src/xCaliBRScans.kt +++ b/multisrc/overrides/wpmangastream/xcalibrscans/src/xCaliBRScans.kt @@ -1,18 +1,92 @@ 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 eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor import eu.kanade.tachiyomi.multisrc.wpmangastream.WPMangaStream +import eu.kanade.tachiyomi.source.model.Page +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonPrimitive +import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.OkHttpClient +import org.jsoup.nodes.Document +import uy.kohesive.injekt.injectLazy import java.util.concurrent.TimeUnit class xCaliBRScans : WPMangaStream("xCaliBR Scans", "https://xcalibrscans.com", "en") { - private val rateLimitInterceptor = RateLimitInterceptor(2) + private val json: Json by injectLazy() override val client: OkHttpClient = network.cloudflareClient.newBuilder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) - .addNetworkInterceptor(rateLimitInterceptor) + .addNetworkInterceptor(RateLimitInterceptor(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.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")) + } + } + } else { + val index = htmlPages.size + Page(index, "", el.select("img").attr("abs:src")) + } + } + + val docString = document.toString() + val imageListRegex = Regex("\\\"images.*?:.*?(\\[.*?\\])") + val imageListJson = imageListRegex.find(docString)!!.destructured.toList()[0] + + val imageList = json.parseToJsonElement(imageListJson).jsonArray + val baseResolver = baseUrl.toHttpUrl() + + val scriptPages = imageList.mapIndexed { i, jsonEl -> + val imageUrl = jsonEl.jsonPrimitive.content + Page(i, "", baseResolver.resolve(imageUrl).toString()) + } + + if (htmlPages.size < scriptPages.size) { + htmlPages += scriptPages + } + + countViews(document) + + return htmlPages.distinctBy { it.imageUrl } + } } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStreamGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStreamGenerator.kt index 8befd5ffd..9bc992265 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStreamGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/wpmangastream/WPMangaStreamGenerator.kt @@ -41,7 +41,7 @@ class WPMangaStreamGenerator : ThemeSourceGenerator { SingleLang("Mihentai", "https://mihentai.com", "en", isNsfw = true, overrideVersionCode = 1), SingleLang("Kuma Scans (Kuma Translation)", "https://kumascans.com", "en", className = "KumaScans", overrideVersionCode = 1), SingleLang("Tempest Manga", "https://manga.tempestfansub.com", "tr"), - SingleLang("xCaliBR Scans", "https://xcalibrscans.com", "en", overrideVersionCode = 2), + SingleLang("xCaliBR Scans", "https://xcalibrscans.com", "en", overrideVersionCode = 3), SingleLang("NoxSubs", "https://noxsubs.com", "tr"), SingleLang("The Apollo Team", "https://theapollo.team", "en"), SingleLang("Sekte Doujin", "https://sektedoujin.xyz", "id", isNsfw = true, overrideVersionCode = 2),