Fix Baozimh.org (#14525)

This commit is contained in:
stevenyomi 2022-12-13 11:46:55 +08:00 committed by GitHub
parent b9950dd8da
commit bdf62a6107
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 261 additions and 154 deletions

View File

@ -1,153 +0,0 @@
package eu.kanade.tachiyomi.extension.zh.baozimhorg
import android.app.Application
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.select.Evaluator
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.text.SimpleDateFormat
import java.util.Locale
// Uses Elementor + Madara theme.
class BaozimhOrg : ConfigurableSource, Madara(
"包子漫画导航",
"",
"zh",
SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH)
) {
override val baseUrl: String
init {
val mirrors = MIRRORS
val mirrorIndex = Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
.getString(MIRROR_PREF, "0")!!.toInt().coerceAtMost(mirrors.size - 1)
baseUrl = "https://" + mirrors[mirrorIndex]
}
override val client = network.client
override val useLoadMoreSearch = false
override val sendViewCount = false
override fun popularMangaRequest(page: Int) = GET("$baseUrl/hots/$page/", headers)
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/dayup/$page/", headers)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
if (query.isNotEmpty()) {
val url = "$baseUrl/page/$page/".toHttpUrl().newBuilder()
.addQueryParameter("s", query)
return Request.Builder().url(url.build()).headers(headers).build()
}
for (filter in filters) {
if (filter is UriPartFilter) return GET(baseUrl + filter.toUriPart() + "page/$page/", headers)
}
return popularMangaRequest(page)
}
override val popularMangaUrlSelector = "h3 > a"
override fun latestUpdatesParse(response: Response) = popularMangaParse(response)
override fun searchMangaParse(response: Response) = popularMangaParse(response)
override fun popularMangaParse(response: Response): MangasPage {
val document = response.asJsoup().also(::parseGenresInternal)
val mangas = document.select(Evaluator.Tag("article")).map(::popularMangaFromElement)
val hasNextPage = document.selectFirst(Evaluator.Class("next"))?.tagName() == "a"
return MangasPage(mangas, hasNextPage)
}
override val mangaDetailsSelectorStatus = "none"
override val mangaDetailsSelectorDescription = "div.summary_content > div.post-content_item:last-child"
override val seriesTypeSelector = "none"
override val altNameSelector = "none"
override fun mangaDetailsParse(document: Document): SManga {
val manga = super.mangaDetailsParse(document)
val genre = manga.genre ?: return manga
val genreList = genre.split(", ") as ArrayList<String>
for ((index, tag) in genreList.withIndex()) {
val status = when (tag) {
"连载中" -> SManga.ONGOING
"已完结" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
if (status != SManga.UNKNOWN) {
manga.status = status
genreList.removeAt(index)
manga.genre = genreList.joinToString()
return manga
}
}
return manga
}
override fun chapterFromElement(element: Element) = SChapter.create().apply {
val link = element.selectFirst(Evaluator.Tag("a"))
url = link.attr("href").removePrefix(baseUrl)
name = link.ownText()
date_upload = parseChapterDate(link.child(0).text())
}
override fun parseRelativeDate(date: String) =
super.parseRelativeDate(date.replace("小时", "hour"))
// Jsoup won't ignore duplicates inside <noscript> tag
override val pageListParseSelector = ".text-left img.lazyload"
var genres: Array<Pair<String, String>> = emptyArray()
private fun parseGenresInternal(document: Document) {
if (genres.isNotEmpty()) return
val box = document.selectFirst("[data-elementor-type=header] + div nav > ul") ?: return
val items = box.select(Evaluator.Class("menu-item-type-custom"))
genres = buildList(items.size + 1) {
add(Pair("全部", "/allmanga/"))
items.mapTo(this) {
val link = it.child(0)
Pair(link.ownText(), link.attr("href"))
}
}.toTypedArray()
}
override fun getFilterList(): FilterList =
if (genres.isEmpty()) {
FilterList(listOf(Filter.Header("点击“重置”刷新分类")))
} else {
val list = listOf(
Filter.Header("分类(搜索文本时无效)"),
UriPartFilter("分类", genres),
)
FilterList(list)
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
val mirrors = MIRRORS
key = MIRROR_PREF
title = "镜像网址"
summary = "%s\n重启生效"
entries = mirrors
entryValues = Array(mirrors.size) { it.toString() }
setDefaultValue("0")
}.let(screen::addPreference)
}
companion object {
private const val MIRROR_PREF = "MIRROR"
private val MIRRORS get() = arrayOf("baozimh.org", "cn.godamanga.com")
}
}

View File

@ -52,7 +52,6 @@ class MadaraGenerator : ThemeSourceGenerator {
SingleLang("Azora", "https://azoraworlds.net", "ar", overrideVersionCode = 4),
SingleLang("Bakaman", "https://bakaman.net", "th", overrideVersionCode = 1),
SingleLang("Banana Cítrica", "https://dinastiacilly.com", "pt-BR", isNsfw = true, pkgName = "bananamecanica", className = "BananaCitrica", overrideVersionCode = 4),
SingleLang("Baozimh.org", "https://baozimh.org", "zh", className = "BaozimhOrg", sourceName = "包子漫画导航", overrideVersionCode = 1),
SingleLang("BestManga", "https://bestmanga.club", "ru", overrideVersionCode = 1),
SingleLang("BestManhua", "https://bestmanhua.com", "en", overrideVersionCode = 2),
SingleLang("Bichen Traduções", "https://bichentraducoes.com", "pt-BR", isNsfw = true, className = "BichenTraducoes"),

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="eu.kanade.tachiyomi.extension" />

View File

@ -0,0 +1,11 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
ext {
extName = 'Baozimh.org'
pkgNameSuffix = 'zh.baozimhorg'
extClass = '.BaozimhOrg'
extVersionCode = 28
}
apply from: "$rootDir/common.gradle"

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

View File

@ -0,0 +1,216 @@
package eu.kanade.tachiyomi.extension.zh.baozimhorg
import android.app.Application
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.select.Evaluator
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.text.SimpleDateFormat
import java.util.Locale
// Uses WPManga + GeneratePress/Blocksy Child
class BaozimhOrg : HttpSource(), ConfigurableSource {
override val name get() = "包子漫画导航"
override val lang get() = "zh"
override val supportsLatest get() = true
override val baseUrl: String
private val baseHttpUrl: HttpUrl
private val enableGenres: Boolean
init {
val mirrors = MIRRORS
val mirrorIndex = Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
.getString(MIRROR_PREF, "0")!!.toInt().coerceAtMost(mirrors.size - 1)
baseUrl = "https://" + mirrors[mirrorIndex]
baseHttpUrl = baseUrl.toHttpUrl()
enableGenres = mirrorIndex == 0
}
override val client = network.client.newBuilder()
.addInterceptor(UrlInterceptor)
.build()
private fun getKey(link: String): String {
val pathSegments = baseHttpUrl.resolve(link)!!.pathSegments
val fromIndex = if (pathSegments[0] == "manga") 1 else 0
val toIndex = if (pathSegments.last().isEmpty()) pathSegments.size - 1 else pathSegments.size
val list = pathSegments.subList(fromIndex, toIndex).toMutableList()
list[0] = list[0].split("-").take(2).joinToString("-")
return list.joinToString("/")
}
override fun popularMangaRequest(page: Int) = GET("$baseUrl/hots/page/$page/", headers)
override fun popularMangaParse(response: Response): MangasPage {
val document = response.asJsoup().also(::parseGenres)
val mangas = document.select("article.wp-manga").map { element ->
SManga.create().apply {
val link = element.selectFirst(Evaluator.Tag("h2")).child(0)
url = getKey(link.attr("href"))
title = link.ownText()
thumbnail_url = element.selectFirst(Evaluator.Tag("img")).imgSrc
}
}
val hasNextPage = document.selectFirst(Evaluator.Class("next"))?.tagName() == "a" ||
document.selectFirst(".gb-button[aria-label=Next page]") != null
return MangasPage(mangas, hasNextPage)
}
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/newss/page/$page/", headers)
override fun latestUpdatesParse(response: Response) = popularMangaParse(response)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
if (query.isNotEmpty()) {
val url = "$baseUrl/page/$page/".toHttpUrl().newBuilder()
.addQueryParameter("s", query)
return Request.Builder().url(url.build()).headers(headers).build()
}
for (filter in filters) {
if (filter is UriPartFilter) return GET(baseUrl + filter.toUriPart() + "page/$page/", headers)
}
return popularMangaRequest(page)
}
override fun searchMangaParse(response: Response) = popularMangaParse(response)
override fun mangaDetailsRequest(manga: SManga): Request {
val url = manga.url
if (url[0] == '/') throw Exception(MIGRATE)
return GET("$baseUrl/manga/$url/", headers)
}
override fun mangaDetailsParse(response: Response) = SManga.create().apply {
val document = response.asJsoup()
title = document.selectFirst(Evaluator.Tag("h1")).ownText()
author = document.selectFirst(Evaluator.Class("author-content")).children().joinToString { it.ownText() }
description = document.selectFirst(".descrip_manga_info, .wp-block-stackable-text").text()
thumbnail_url = document.selectFirst("img.wp-post-image").imgSrc
val genreList = document.selectFirst(Evaluator.Class("genres-content"))
.children().eachText().toMutableSet()
if ("连载中" in genreList) {
genreList.remove("连载中")
status = SManga.ONGOING
} else if ("已完结" in genreList) {
genreList.remove("已完结")
status = SManga.COMPLETED
}
genre = genreList.joinToString()
}
override fun chapterListRequest(manga: SManga): Request {
val url = manga.url
if (url[0] == '/') throw Exception(MIGRATE)
return GET("$baseUrl/chapterlist/$url/", headers)
}
override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup()
return document.selectFirst(Evaluator.Class("version-chaps")).children().map {
SChapter.create().apply {
url = getKey(it.attr("href"))
name = it.ownText()
date_upload = parseChapterDate(it.child(0).text())
}
}
}
override fun pageListRequest(chapter: SChapter): Request {
val url = chapter.url
if (url[0] == '/') throw Exception(MIGRATE)
return GET("$baseUrl/manga/$url/", headers)
}
override fun pageListParse(response: Response): List<Page> {
val document = response.asJsoup()
// Jsoup won't ignore duplicates inside <noscript> tag
document.select(Evaluator.Tag("noscript")).remove()
return document.select("img[decoding=async]").mapIndexed { index, element ->
Page(index, imageUrl = element.imgSrc)
}
}
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
var genres: Array<Pair<String, String>> = emptyArray()
private fun parseGenres(document: Document) {
if (!enableGenres || genres.isNotEmpty()) return
val box = document.selectFirst(Evaluator.Class("wp-block-navigation__container")) ?: return
val items = box.children()
genres = buildList(items.size + 1) {
add(Pair("全部", "/allmanga/"))
items.mapTo(this) {
val link = it.child(0)
Pair(link.text(), link.attr("href"))
}
}.toTypedArray()
}
override fun getFilterList(): FilterList =
if (!enableGenres) {
FilterList()
} else if (genres.isEmpty()) {
FilterList(listOf(Filter.Header("点击“重置”刷新分类")))
} else {
val list = listOf(
Filter.Header("分类(搜索文本时无效)"),
UriPartFilter("分类", genres),
)
FilterList(list)
}
class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>) :
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
val mirrors = MIRRORS
key = MIRROR_PREF
title = "镜像网址"
summary = "%s\n重启生效暂未适配GoDa漫画的分类筛选功能"
entries = mirrors
entryValues = Array(mirrors.size) { it.toString() }
setDefaultValue("0")
}.let(screen::addPreference)
}
companion object {
private const val MIRROR_PREF = "MIRROR"
private val MIRRORS get() = arrayOf("baozimh.org", "cn.godamanga.com")
const val MIGRATE = "请将此漫画重新迁移到本图源"
val Element.imgSrc: String get() = attr("data-src").ifEmpty { attr("src") }
private val dateFormat by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) }
fun parseChapterDate(text: String): Long = try {
dateFormat.parse(text)!!.time
} catch (_: Throwable) {
0
}
}
}

View File

@ -0,0 +1,32 @@
package eu.kanade.tachiyomi.extension.zh.baozimhorg
import okhttp3.Interceptor
import okhttp3.Response
// Temporary interceptor to handle URL redirections
object UrlInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val url = request.url
val (type, slug) = url.pathSegments
when (type) {
"manga", "chapterlist" -> {}
else -> return chain.proceed(request)
}
val mangaUrl = "/manga/$slug/"
val headRequest = request.newBuilder()
.head()
.url(url.resolve(mangaUrl)!!)
.build()
// might redirect multiple times
val headResponse = chain.proceed(headRequest)
if (headResponse.priorResponse == null) return chain.proceed(request)
val realSlug = headResponse.request.url.pathSegments[1]
val newUrl = url.newBuilder().setEncodedPathSegment(1, realSlug).build()
val newRequest = request.newBuilder().url(newUrl).build()
return chain.proceed(newRequest)
}
}