Baozimanhua: fix metadata, remove banner and add mirror preference (#11864)

* Baozimanhua: fix metadata and remove banner

* Baozimanhua: add preference to set mirror URL

* Baozimanhua: add changelog

* Baozimanhua: recycle banner bitmap

* Baozimanhua: update selector and URL
This commit is contained in:
kasperskier 2022-05-17 02:52:48 +08:00 committed by GitHub
parent f9560eff2b
commit e35dbfb57d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 166 additions and 11 deletions

View File

@ -0,0 +1,18 @@
## 1.2.4 (2022-05-14)
- 图源名改为中文
- 自动切除图片中的 banner
- 在设置中可以选择镜像网址
- 更新解析选择器和地址
## 1.2.3 (2022-01-27)
- 修复图片列表解析
## 1.2.2 (2021-12-06)
- (批量更改) 修复安卓 12 URL 跳转
## 1.2.1 (2021-08-31)
- 第一版

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -1,12 +1,11 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext { ext {
extName = 'Baozimanhua' extName = 'Baozimanhua'
pkgNameSuffix = 'zh.baozimanhua' pkgNameSuffix = 'zh.baozimanhua'
extClass = '.Baozimanhua' extClass = '.Baozimanhua'
extVersionCode = 3 extVersionCode = 4
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -0,0 +1,17 @@
from base64 import b64encode
with open('banner.jpg', 'rb') as f:
data = f.read()
head = b'''\
package eu.kanade.tachiyomi.extension.zh.baozimanhua
const val BANNER_BASE64 = "\
'''
tail = b'"\n'
with open('src/eu/kanade/tachiyomi/extension/zh/baozimanhua/BannerData.kt', 'wb') as f:
f.write(head)
f.write(b64encode(data))
f.write(tail)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 64 KiB

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,84 @@
package eu.kanade.tachiyomi.extension.zh.baozimanhua
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.util.Base64
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import java.io.ByteArrayOutputStream
import kotlin.math.abs
class BannerInterceptor : Interceptor {
private val banner: Bitmap by lazy {
val buffer = Base64.decode(BANNER_BASE64, Base64.DEFAULT)
BitmapFactory.decodeByteArray(buffer, 0, buffer.size)
}
private val w by lazy { banner.width }
private val h by lazy { banner.height }
private val size by lazy { w * h }
private val bannerBuffer by lazy {
val buffer = IntArray(size)
banner.getPixels(buffer, 0, w, 0, 0, w, h)
banner.recycle()
buffer
}
private val threshold by lazy { w * h * 3 / 2 } // 0.5 per pixel per channel
override fun intercept(chain: Interceptor.Chain): Response {
val url = chain.request().url.toString()
val response = chain.proceed(chain.request())
if (!url.endsWith(COMIC_IMAGE_SUFFIX)) return response
val body = response.body!!
val contentType = body.contentType()
val content = body.bytes()
val bitmap = BitmapFactory.decodeByteArray(content, 0, content.size)
val position = checkBanner(bitmap)
return if (position == null) {
response.newBuilder().body(content.toResponseBody(contentType)).build()
} else {
val result = Bitmap.createBitmap(
bitmap, 0,
when (position) {
BannerPosition.TOP -> h
BannerPosition.BOTTOM -> 0
},
bitmap.width, bitmap.height - h
)
val output = ByteArrayOutputStream()
result.compress(Bitmap.CompressFormat.JPEG, 90, output)
val responseBody = output.toByteArray().toResponseBody("image/jpeg".toMediaType())
response.newBuilder().body(responseBody).build()
}
}
private fun checkBanner(image: Bitmap): BannerPosition? {
if (image.width < w || image.height < h) return null
if ((image.width - w) % 2 != 0) return null
val pad = (image.width - w) / 2
val buf = IntArray(size)
image.getPixels(buf, 0, w, pad, 0, w, h) // top
if (isIdentical(bannerBuffer, buf)) return BannerPosition.TOP
image.getPixels(buf, 0, w, pad, image.height - h, w, h) // bottom
if (isIdentical(bannerBuffer, buf)) return BannerPosition.BOTTOM
return null
}
private fun isIdentical(a: IntArray, b: IntArray): Boolean {
var diff = 0
for (i in 0 until size) {
val pixel0 = a[i]
val pixel1 = b[i]
diff += abs((pixel0 and 0xFF) - (pixel1 and 0xFF))
diff += abs((pixel0 shr 8 and 0xFF) - (pixel1 shr 8 and 0xFF))
diff += abs((pixel0 shr 16 and 0xFF) - (pixel1 shr 16 and 0xFF))
if (diff > threshold) return false
}
return true
}
private enum class BannerPosition { TOP, BOTTOM }
}
const val COMIC_IMAGE_SUFFIX = "#baozi"

View File

@ -1,7 +1,11 @@
package eu.kanade.tachiyomi.extension.zh.baozimanhua package eu.kanade.tachiyomi.extension.zh.baozimanhua
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
@ -16,18 +20,27 @@ import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import rx.Observable import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class Baozimanhua : ParsedHttpSource() { class Baozimanhua : ParsedHttpSource(), ConfigurableSource {
override val name = "Baozimanhua" override val id = 5724751873601868259
override val baseUrl = "https://cn.baozimh.com" override val name = "包子漫画"
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override val baseUrl = "https://${preferences.getString(MIRROR_PREF, MIRRORS[0])}"
override val lang = "zh" override val lang = "zh"
override val supportsLatest = true override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.addNetworkInterceptor(BannerInterceptor()).build()
override fun chapterListSelector(): String = "div.pure-g[id^=chapter] > div" override fun chapterListSelector(): String = "div.pure-g[id^=chapter] > div"
@ -42,7 +55,7 @@ class Baozimanhua : ParsedHttpSource() {
override fun chapterFromElement(element: Element): SChapter { override fun chapterFromElement(element: Element): SChapter {
return SChapter.create().apply { return SChapter.create().apply {
url = element.select("a").attr("href").trim() setUrlWithoutDomain(element.select("a").attr("href").trim())
name = element.text() name = element.text()
} }
} }
@ -51,7 +64,7 @@ class Baozimanhua : ParsedHttpSource() {
override fun popularMangaFromElement(element: Element): SManga { override fun popularMangaFromElement(element: Element): SManga {
return SManga.create().apply { return SManga.create().apply {
url = element.attr("href")!!.trim() setUrlWithoutDomain(element.attr("href")!!.trim())
title = element.attr("title")!!.trim() title = element.attr("title")!!.trim()
thumbnail_url = element.select("> amp-img").attr("src")!!.trim() thumbnail_url = element.select("> amp-img").attr("src")!!.trim()
} }
@ -59,7 +72,7 @@ class Baozimanhua : ParsedHttpSource() {
override fun popularMangaNextPageSelector() = throw java.lang.UnsupportedOperationException("Not used.") override fun popularMangaNextPageSelector() = throw java.lang.UnsupportedOperationException("Not used.")
override fun popularMangaRequest(page: Int): Request = GET("https://www.baozimh.com/classify", headers) override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/classify", headers)
override fun popularMangaParse(response: Response): MangasPage { override fun popularMangaParse(response: Response): MangasPage {
val document = response.asJsoup() val document = response.asJsoup()
@ -104,8 +117,8 @@ class Baozimanhua : ParsedHttpSource() {
} }
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
val pages = document.select("section.comic-contain > amp-img").mapIndexed() { index, element -> val pages = document.select(".comic-contain > .chapter-img > img").mapIndexed { index, element ->
Page(index, imageUrl = element.attr("src").trim()) Page(index, imageUrl = element.attr("data-src").trim() + COMIC_IMAGE_SUFFIX)
} }
return pages return pages
} }
@ -301,7 +314,28 @@ class Baozimanhua : ParsedHttpSource() {
) )
) )
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val mirrorPref = androidx.preference.ListPreference(screen.context).apply {
key = MIRROR_PREF
title = MIRROR_PREF_TITLE
entries = MIRRORS
entryValues = MIRRORS
summary = MIRROR_PREF_SUMMARY
setDefaultValue(MIRRORS[0])
setOnPreferenceChangeListener { _, newValue ->
preferences.edit().putString(MIRROR_PREF, newValue as String).commit()
}
}
screen.addPreference(mirrorPref)
}
companion object { companion object {
const val ID_SEARCH_PREFIX = "id:" const val ID_SEARCH_PREFIX = "id:"
private const val MIRROR_PREF = "MIRROR"
private const val MIRROR_PREF_TITLE = "使用镜像网址"
private const val MIRROR_PREF_SUMMARY = "使用镜像网址。重启软件生效。"
private val MIRRORS = arrayOf("cn.baozimh.com", "cn.webmota.com")
} }
} }