From 88407d64af875d3142c06b666a45262f0052317c Mon Sep 17 00:00:00 2001 From: Hualiang <78242797+hualiong@users.noreply.github.com> Date: Tue, 19 Aug 2025 00:56:43 +0800 Subject: [PATCH] BiliManga: add more filter options (#10166) * add more filter options * fix pagination --- src/zh/bilimanga/build.gradle | 2 +- .../extension/zh/bilimanga/BiliManga.kt | 44 +++++-- .../extension/zh/bilimanga/Filters.kt | 116 ++++++++++++++---- 3 files changed, 122 insertions(+), 40 deletions(-) diff --git a/src/zh/bilimanga/build.gradle b/src/zh/bilimanga/build.gradle index 4ca8d0390..40fe25579 100644 --- a/src/zh/bilimanga/build.gradle +++ b/src/zh/bilimanga/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'BiliManga' extClass = '.BiliManga' - extVersionCode = 3 + extVersionCode = 4 isNsfw = true } diff --git a/src/zh/bilimanga/src/eu/kanade/tachiyomi/extension/zh/bilimanga/BiliManga.kt b/src/zh/bilimanga/src/eu/kanade/tachiyomi/extension/zh/bilimanga/BiliManga.kt index 3e43e9427..b4d1cb48d 100644 --- a/src/zh/bilimanga/src/eu/kanade/tachiyomi/extension/zh/bilimanga/BiliManga.kt +++ b/src/zh/bilimanga/src/eu/kanade/tachiyomi/extension/zh/bilimanga/BiliManga.kt @@ -16,6 +16,7 @@ import keiyoushi.utils.tryParse 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.Elements import java.text.SimpleDateFormat @@ -53,13 +54,29 @@ class BiliManga : HttpSource(), ConfigurableSource { } companion object { - const val PAGE_SIZE = 50 val META_REGEX = Regex("連載|完結|收藏|推薦|热度") val DATE_REGEX = Regex("\\d{4}-\\d{1,2}-\\d{1,2}") + val PAGE_REGEX = Regex("第(\\d+)/(\\d+)页") val MANGA_ID_REGEX = Regex("/detail/(\\d+)\\.html") val DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd", Locale.CHINESE) } + private fun hasNextPage(doc: Document, size: Int): Boolean { + val url = doc.location() + return when { + url.contains("filter") -> { + val total = doc.selectFirst("#pagelink > .last")!!.text().toInt() + val cur = doc.selectFirst("#pagelink > strong")!!.text().toInt() + cur < total + } + url.contains("search") -> { + val find = PAGE_REGEX.find(doc.selectFirst("#pagelink > span")!!.text())!! + find.groups[1]!!.value.toInt() < find.groups[1]!!.value.toInt() + } + else -> size == 50 + } + } + private fun getChapterUrlByContext(i: Int, els: Elements) = when (i) { 0 -> "${els[1].attr("href")}#prev" else -> "${els[i - 1].attr("href")}#next" @@ -72,8 +89,8 @@ class BiliManga : HttpSource(), ConfigurableSource { return GET(baseUrl + String.format(suffix, page), headers) } - override fun popularMangaParse(response: Response) = response.asJsoup().let { - val mangas = it.select(".book-layout").map { + override fun popularMangaParse(response: Response) = response.asJsoup().let { doc -> + val mangas = doc.select(".book-layout").map { SManga.create().apply { setUrlWithoutDomain(it.absUrl("href")) val img = it.selectFirst("img")!! @@ -81,12 +98,13 @@ class BiliManga : HttpSource(), ConfigurableSource { title = img.attr("alt") } } - MangasPage(mangas, mangas.size >= PAGE_SIZE) + MangasPage(mangas, hasNextPage(doc, mangas.size)) } // Latest Page - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/top/lastupdate/$page.html", headers) + override fun latestUpdatesRequest(page: Int) = + GET("$baseUrl/top/lastupdate/$page.html", headers) override fun latestUpdatesParse(response: Response) = popularMangaParse(response) @@ -94,13 +112,14 @@ class BiliManga : HttpSource(), ConfigurableSource { override fun getFilterList() = buildFilterList() + // https://www.bilimanga.net/filter/lastupdate_1_0_0_0_0_0_0_1_0.html override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { val url = baseUrl.toHttpUrl().newBuilder() if (query.isNotBlank()) { url.addPathSegment("search").addPathSegment("${query}_$page.html") } else { - url.addPathSegment("top").addPathSegment(filters[1].toString()) - .addPathSegment("$page.html") + url.addPathSegment("filter") + .addPathSegment("${filters[4]}_${filters[1]}_${filters[7]}_${filters[5]}_${filters[3]}_${filters[2]}_${filters[8]}_${filters[6]}_${page}_0.html") } return GET(url.build(), headers) } @@ -118,13 +137,11 @@ class BiliManga : HttpSource(), ConfigurableSource { val doc = response.asJsoup() val meta = doc.selectFirst(".book-meta")!!.text().split("|") val extra = meta.filterNot(META_REGEX::containsMatchIn) - val backupname = doc.selectFirst(".backupname")?.let { - "\n\n漫畫別名:\n• ${it.text().split("、").joinToString("\n• ")}" - } + val backupname = doc.selectFirst(".backupname")?.let { "【別名:${it.text()}】\n\n" } ?: "" setUrlWithoutDomain(doc.location()) title = doc.selectFirst(".book-title")!!.text() thumbnail_url = doc.selectFirst(".book-cover")!!.attr("src") - description = doc.selectFirst("#bookSummary")?.text() + backupname + description = backupname + doc.selectFirst("#bookSummary > content")?.wholeText()?.trim() artist = doc.selectFirst(".authorname")?.text() author = doc.selectFirst(".illname")?.text() ?: artist status = when (meta.firstOrNull()) { @@ -138,7 +155,8 @@ class BiliManga : HttpSource(), ConfigurableSource { // Catalog Page - override fun chapterListRequest(manga: SManga) = GET("$baseUrl/read/${manga.id}/catalog", headers) + override fun chapterListRequest(manga: SManga) = + GET("$baseUrl/read/${manga.id}/catalog", headers) override fun chapterListParse(response: Response) = response.asJsoup().let { val info = it.selectFirst(".chapter-sub-title")!!.text() @@ -162,7 +180,7 @@ class BiliManga : HttpSource(), ConfigurableSource { override fun pageListParse(response: Response) = response.asJsoup().let { val images = it.select(".imagecontent") - check(images.size > 0) { + check(images.isNotEmpty()) { it.selectFirst("#acontentz")?.let { e -> if ("電腦端" in e.text()) "不支持電腦端查看,請在高級設置中更換移動端UA標識" else "漫畫可能已下架或需要登錄查看" } ?: "章节鏈接错误" diff --git a/src/zh/bilimanga/src/eu/kanade/tachiyomi/extension/zh/bilimanga/Filters.kt b/src/zh/bilimanga/src/eu/kanade/tachiyomi/extension/zh/bilimanga/Filters.kt index ad216b5e5..232219943 100644 --- a/src/zh/bilimanga/src/eu/kanade/tachiyomi/extension/zh/bilimanga/Filters.kt +++ b/src/zh/bilimanga/src/eu/kanade/tachiyomi/extension/zh/bilimanga/Filters.kt @@ -4,39 +4,103 @@ import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList fun buildFilterList() = FilterList( - Filter.Header("篩選條件(搜尋時無效)"), - RankFilter(), + Filter.Header("篩選條件(搜尋關鍵字時無效)"), + ThemeFilter(), // 1 + TypeFilter(), // 5 + RegionFilter(), // 4 + SortFilter(), // 0 + AnimeFilter(), // 3 + NovelFilter(), // 7 + StatusFilter(), // 2 + TimeFilter(), // 6 ) -class RankFilter : Filter.Select( - "排行榜", +class ThemeFilter : Filter.Select( + "作品主題", arrayOf( - "月點擊榜", - "周點擊榜", - "月推薦榜", - "周推薦榜", - "月鮮花榜", - "周鮮花榜", - "月雞蛋榜", - "周雞蛋榜", - "最新入庫", - "收藏榜", - "新書榜", + "不限", "奇幻", "冒險", "異世界", "龍傲天", "魔法", + "仙俠", "戰爭", "熱血", "戰鬥", "競技", "懸疑", + "驚悚", "獵奇", "神鬼", "偵探", "校園", "日常", + "JK", "JC", "青梅竹馬", "妹妹", "大小姐", "女兒", + "愛情", "耽美", "百合", "NTR", "後宮", "職場", + "經營", "犯罪", "旅行", "群像", "女性視角", + "歷史", "武俠", "東方", "勵志", "宅系", "科幻", + "機戰", "遊戲", "異能", "腦洞", "病嬌", "人外", + "復仇", "鬥智", "惡役", "間諜", "治癒", "歡樂", + "萌系", "末日", "大逃殺", "音樂", "美食", "性轉", + "偽娘", "穿越", "童話", "轉生", "黑暗", "溫馨", + "超自然", ), ) { override fun toString(): String { return arrayOf( - "monthvisit", - "weekvisit", - "monthvote", - "weekvote", - "monthflower", - "weekflower", - "monthegg", - "weekegg", - "postdate", - "goodnum", - "newhot", + "0", "1", "2", "3", "4", "5", "6", "7", "8", + "9", "10", "11", "12", "13", "14", "15", "16", + "17", "18", "19", "20", "21", "22", "23", "24", + "25", "26", "27", "28", "29", "30", "31", "32", + "33", "34", "35", "36", "37", "38", "39", "40", + "41", "42", "43", "44", "45", "46", "47", "48", + "49", "50", "51", "52", "53", "54", "55", "56", + "57", "58", "59", "60", "61", "62", "63", "64", + "65", )[state] } } + +class TypeFilter : Filter.Select( + "作品分類", + arrayOf( + "全部", + "奇幻冒險", "戰鬥熱血", "懸疑驚悚", "校園青春", + "愛情浪漫", "職場都市", "歷史文化", "科幻未來", + "奇異幻想", "治癒溫馨", "末日生存", "其他分類", + ), +) { + override fun toString(): String { + return arrayOf( + "0", "1", "2", "3", "4", "5", "6", "7", "8", + "9", "10", "11", "12", + )[state] + } +} + +class RegionFilter : Filter.Select( + "作品地區", + arrayOf("不限", "日本", "韓國", "港台", "歐美", "大陸"), +) { + override fun toString() = arrayOf("0", "1", "2", "3", "4", "5")[state] +} + +class SortFilter : Filter.Select( + "排序方式", + arrayOf( + "最近更新", "月點擊", "周推薦", "月推薦", "周鮮花", + "月鮮花", "字數", "收藏數", "周點擊", "最新入庫", + ), +) { + override fun toString(): String { + return arrayOf( + "lastupdate", "monthvisit", "weekvote", "monthvote", "weekflower", + "monthflower", "words", "goodnum", "weekvisit", "postdate", + )[state] + } +} + +class AnimeFilter : Filter.Select("是否動畫", arrayOf("不限", "已動畫化", "未動畫化")) { + override fun toString() = arrayOf("0", "1", "2")[state] +} + +class NovelFilter : Filter.Select("是否輕改", arrayOf("不限", "輕改漫畫", "普通漫畫")) { + override fun toString() = arrayOf("0", "1", "2")[state] +} + +class StatusFilter : Filter.Select("連載狀態", arrayOf("不限", "連載", "完結")) { + override fun toString() = arrayOf("0", "1", "2")[state] +} + +class TimeFilter : Filter.Select( + "更新時間", + arrayOf("不限", "三日內", "七日內", "半月內", "一月內"), +) { + override fun toString() = arrayOf("0", "1", "2", "3", "4")[state] +}