diff --git a/src/zh/jinmantiantang/AndroidManifest.xml b/src/zh/jinmantiantang/AndroidManifest.xml index 4580065b5..363a1f0f8 100644 --- a/src/zh/jinmantiantang/AndroidManifest.xml +++ b/src/zh/jinmantiantang/AndroidManifest.xml @@ -23,15 +23,11 @@ android:pathPattern="/album/..*" android:scheme="https" /> - diff --git a/src/zh/jinmantiantang/build.gradle b/src/zh/jinmantiantang/build.gradle index 24ce0d1dc..e28e4f4c2 100644 --- a/src/zh/jinmantiantang/build.gradle +++ b/src/zh/jinmantiantang/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'Jinmantiantang' pkgNameSuffix = 'zh.jinmantiantang' extClass = '.Jinmantiantang' - extVersionCode = 24 + extVersionCode = 25 isNsfw = true } diff --git a/src/zh/jinmantiantang/res/mipmap-hdpi/ic_launcher.png b/src/zh/jinmantiantang/res/mipmap-hdpi/ic_launcher.png index 3ff5d3a60..15cc33e02 100644 Binary files a/src/zh/jinmantiantang/res/mipmap-hdpi/ic_launcher.png and b/src/zh/jinmantiantang/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/zh/jinmantiantang/res/mipmap-ldpi/ic_launcher.png b/src/zh/jinmantiantang/res/mipmap-ldpi/ic_launcher.png index d98d6fa7d..ee0d7eeb0 100644 Binary files a/src/zh/jinmantiantang/res/mipmap-ldpi/ic_launcher.png and b/src/zh/jinmantiantang/res/mipmap-ldpi/ic_launcher.png differ diff --git a/src/zh/jinmantiantang/res/mipmap-mdpi/ic_launcher.png b/src/zh/jinmantiantang/res/mipmap-mdpi/ic_launcher.png index 3f5fa94bb..96777aa47 100644 Binary files a/src/zh/jinmantiantang/res/mipmap-mdpi/ic_launcher.png and b/src/zh/jinmantiantang/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/zh/jinmantiantang/res/mipmap-xhdpi/ic_launcher.png b/src/zh/jinmantiantang/res/mipmap-xhdpi/ic_launcher.png index 65c831efa..cf0e4402f 100644 Binary files a/src/zh/jinmantiantang/res/mipmap-xhdpi/ic_launcher.png and b/src/zh/jinmantiantang/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/zh/jinmantiantang/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/jinmantiantang/res/mipmap-xxhdpi/ic_launcher.png index 94baa2ad1..b3e0cb094 100644 Binary files a/src/zh/jinmantiantang/res/mipmap-xxhdpi/ic_launcher.png and b/src/zh/jinmantiantang/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/zh/jinmantiantang/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/jinmantiantang/res/mipmap-xxxhdpi/ic_launcher.png index 7d8a73bb1..ddf39366f 100644 Binary files a/src/zh/jinmantiantang/res/mipmap-xxxhdpi/ic_launcher.png and b/src/zh/jinmantiantang/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/zh/jinmantiantang/res/web_hi_res_512.png b/src/zh/jinmantiantang/res/web_hi_res_512.png index 1e946efa7..54736f338 100644 Binary files a/src/zh/jinmantiantang/res/web_hi_res_512.png and b/src/zh/jinmantiantang/res/web_hi_res_512.png differ diff --git a/src/zh/jinmantiantang/src/eu/kanade/tachiyomi/extension/zh/jinmantiantang/Jinmantiantang.kt b/src/zh/jinmantiantang/src/eu/kanade/tachiyomi/extension/zh/jinmantiantang/Jinmantiantang.kt index 938b9b13f..c84a153b3 100644 --- a/src/zh/jinmantiantang/src/eu/kanade/tachiyomi/extension/zh/jinmantiantang/Jinmantiantang.kt +++ b/src/zh/jinmantiantang/src/eu/kanade/tachiyomi/extension/zh/jinmantiantang/Jinmantiantang.kt @@ -2,10 +2,6 @@ package eu.kanade.tachiyomi.extension.zh.jinmantiantang import android.app.Application import android.content.SharedPreferences -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.Canvas -import android.graphics.Rect import androidx.preference.EditTextPreference import androidx.preference.PreferenceScreen import eu.kanade.tachiyomi.lib.ratelimit.SpecificHostRateLimitInterceptor @@ -22,24 +18,17 @@ import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.util.asJsoup import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrlOrNull -import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response -import okhttp3.ResponseBody.Companion.toResponseBody import org.jsoup.nodes.Document import org.jsoup.nodes.Element import org.jsoup.select.Elements import rx.Observable import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.io.ByteArrayOutputStream -import java.io.InputStream -import java.math.BigInteger -import java.security.MessageDigest import java.text.SimpleDateFormat import java.util.Locale -import kotlin.math.floor class Jinmantiantang : ConfigurableSource, ParsedHttpSource() { @@ -58,82 +47,12 @@ class Jinmantiantang : ConfigurableSource, ParsedHttpSource() { // Add rate limit to fix manga thumbnail load failure private val mainSiteRateLimitInterceptor = SpecificHostRateLimitInterceptor(baseHttpUrl, preferences.getString(MAINSITE_RATELIMIT_PREF, "1")!!.toInt(), preferences.getString(MAINSITE_RATELIMIT_PERIOD, "3")!!.toLong()) - // 220980 - // 算法 html页面 1800 行左右 - // 图片开始分割的ID编号 - private val scrambleId = 220980 - // 处理URL请求 override val client: OkHttpClient = network.cloudflareClient .newBuilder() .addNetworkInterceptor(mainSiteRateLimitInterceptor) // .addNetworkInterceptor(RateLimitInterceptor(1, 3)) - .addInterceptor( - fun(chain): Response { - val url = chain.request().url.toString() - val response = chain.proceed(chain.request()) - if (!url.contains("media/photos", ignoreCase = true)) return response // 对非漫画图片连接直接放行 - if (url.substring(url.indexOf("photos/") + 7, url.lastIndexOf("/")).toInt() < scrambleId) return response // 对在漫画章节ID为220980之前的图片未进行图片分割,直接放行 -// 章节ID:220980(包含)之后的漫画(2020.10.27之后)图片进行了分割getRows倒序处理 - val aid = url.substring(url.indexOf("photos/") + 7, url.lastIndexOf("/")).toInt() - val imgIndex: String = url.substringAfterLast("/").substringBefore(".") - val res = response.body!!.byteStream().use { - decodeImage(it, getRows(aid, imgIndex)) - } - val mediaType = "image/avif,image/webp,image/apng,image/*,*/*".toMediaTypeOrNull() - val outputBytes = res.toResponseBody(mediaType) - return response.newBuilder().body(outputBytes).build() - } - ).build() - - private fun getRows(aid: Int, imgIndex: String): Int { - fun md5(input: String): String { - val md = MessageDigest.getInstance("MD5") - return BigInteger(1, md.digest(input.toByteArray())).toString(16).padStart(32, '0') - } - - return if (aid >= 268850) { - 2 * (md5(aid.toString() + imgIndex).last().toInt() % 10) + 2 - } else { - 10 - } - } - - // 对被分割的图片进行分割,排序处理 - private fun decodeImage(img: InputStream, rows: Int): ByteArray { - // 使用bitmap进行图片处理 - val input = BitmapFactory.decodeStream(img) - // 漫画高度 and width - val height = input.height - val width = input.width - // 未除尽像素 - val remainder = (height % rows) - // 创建新的图片对象 - val resultBitmap = Bitmap.createBitmap(input.width, input.height, Bitmap.Config.ARGB_8888) - val canvas = Canvas(resultBitmap) - // 分割图片 - for (x in 0 until rows) { - // 分割算法(详情见html源码页的方法"function scramble_image(img)") - var copyH = floor(height / rows.toDouble()).toInt() - var py = copyH * (x) - val y = height - (copyH * (x + 1)) - remainder - if (x == 0) { - copyH += remainder - } else { - py += remainder - } - // 要裁剪的区域 - val crop = Rect(0, y, width, y + copyH) - // 裁剪后应放置到新图片对象的区域 - val splic = Rect(0, py, width, py + copyH) - - canvas.drawBitmap(input, crop, splic, null) - } - // 创建输出流 - val output = ByteArrayOutputStream() - resultBitmap.compress(Bitmap.CompressFormat.JPEG, 100, output) - return output.toByteArray() - } + .addInterceptor(ScrambledImageInterceptor).build() // 点击量排序(人气) override fun popularMangaRequest(page: Int): Request { @@ -311,7 +230,7 @@ class Jinmantiantang : ConfigurableSource, ParsedHttpSource() { // 漫画图片信息 override fun pageListParse(document: Document): List { - fun internalParse(document: Document, pages: MutableList): List { + tailrec fun internalParse(document: Document, pages: MutableList): List { val elements = document.select("div[class=center scramble-page][id*=0]") for (element in elements) { pages.apply { @@ -322,9 +241,10 @@ class Jinmantiantang : ConfigurableSource, ParsedHttpSource() { } } } - return document.select("a.prevnext").firstOrNull() - ?.let { internalParse(client.newCall(GET(it.attr("abs:href"), headers)).execute().asJsoup(), pages) } - ?: pages + return when (val next = document.select("a.prevnext").firstOrNull()) { + null -> pages + else -> internalParse(client.newCall(GET(next.attr("abs:href"), headers)).execute().asJsoup(), pages) + } } return internalParse(document, mutableListOf()) @@ -555,14 +475,15 @@ class Jinmantiantang : ConfigurableSource, ParsedHttpSource() { private val PERIOD_ENTRIES_ARRAY = (1..60).map { i -> i.toString() }.toTypedArray() private val SITE_ENTRIES_ARRAY_DESCRIPTION = arrayOf( "主站", "海外分流", - "中国大陆总站", "中国大陆分流1", "中国大陆分流2" + "中国大陆总站", "中国大陆分流1", "中国大陆分流1" ) private val SITE_ENTRIES_ARRAY_VALUE = (0..4).map { i -> i.toString() }.toTypedArray() + // List is based on https://jmcomic.bet/ // Please also update AndroidManifest private val SITE_ENTRIES_ARRAY = arrayOf( DEFAULT_SITE, "18comic.org", - "jmcomic.mobi", "jmcomic1.mobi", "jmcomic1.mobi" + "jmcomic.asia", "jmcomic1.asia", "jmcomic1.asia" ) } } diff --git a/src/zh/jinmantiantang/src/eu/kanade/tachiyomi/extension/zh/jinmantiantang/ScrambledImageInterceptor.kt b/src/zh/jinmantiantang/src/eu/kanade/tachiyomi/extension/zh/jinmantiantang/ScrambledImageInterceptor.kt new file mode 100644 index 000000000..bb41fa99e --- /dev/null +++ b/src/zh/jinmantiantang/src/eu/kanade/tachiyomi/extension/zh/jinmantiantang/ScrambledImageInterceptor.kt @@ -0,0 +1,88 @@ +package eu.kanade.tachiyomi.extension.zh.jinmantiantang + +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.Response +import okhttp3.ResponseBody.Companion.toResponseBody +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.math.BigInteger +import java.security.MessageDigest +import kotlin.math.floor + +object ScrambledImageInterceptor : Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { + val url = chain.request().url.toString() + val response = chain.proceed(chain.request()) + if (!url.contains("media/photos", ignoreCase = true)) return response // 对非漫画图片连接直接放行 + if (url.substring(url.indexOf("photos/") + 7, url.lastIndexOf("/")).toInt() < scrambleId) return response // 对在漫画章节ID为220980之前的图片未进行图片分割,直接放行 +// 章节ID:220980(包含)之后的漫画(2020.10.27之后)图片进行了分割getRows倒序处理 + val aid = url.substring(url.indexOf("photos/") + 7, url.lastIndexOf("/")).toInt() + val imgIndex: String = url.substringAfterLast("/").substringBefore(".") + val res = response.body!!.byteStream().use { + decodeImage(it, getRows(aid, imgIndex)) + } + val outputBytes = res.toResponseBody(jpegMediaType) + return response.newBuilder().body(outputBytes).build() + } + + // 220980 + // 算法 html页面 1800 行左右 + // 图片开始分割的ID编号 + private const val scrambleId = 220980 + + private fun getRows(aid: Int, imgIndex: String): Int { + fun md5(input: String): String { + val md = MessageDigest.getInstance("MD5") + return BigInteger(1, md.digest(input.toByteArray())).toString(16).padStart(32, '0') + } + + return if (aid >= 268850) { + 2 * (md5(aid.toString() + imgIndex).last().toInt() % 10) + 2 + } else { + 10 + } + } + + // 对被分割的图片进行分割,排序处理 + private fun decodeImage(img: InputStream, rows: Int): ByteArray { + // 使用bitmap进行图片处理 + val input = BitmapFactory.decodeStream(img) + // 漫画高度 and width + val height = input.height + val width = input.width + // 未除尽像素 + val remainder = (height % rows) + // 创建新的图片对象 + val resultBitmap = Bitmap.createBitmap(input.width, input.height, Bitmap.Config.ARGB_8888) + val canvas = Canvas(resultBitmap) + // 分割图片 + for (x in 0 until rows) { + // 分割算法(详情见html源码页的方法"function scramble_image(img)") + var copyH = floor(height / rows.toDouble()).toInt() + var py = copyH * (x) + val y = height - (copyH * (x + 1)) - remainder + if (x == 0) { + copyH += remainder + } else { + py += remainder + } + // 要裁剪的区域 + val crop = Rect(0, y, width, y + copyH) + // 裁剪后应放置到新图片对象的区域 + val splic = Rect(0, py, width, py + copyH) + + canvas.drawBitmap(input, crop, splic, null) + } + // 创建输出流 + val output = ByteArrayOutputStream() + resultBitmap.compress(Bitmap.CompressFormat.JPEG, 90, output) + return output.toByteArray() + } + + private val jpegMediaType = "image/jpeg".toMediaType() +}