diff --git a/multisrc/overrides/mccms/haoman6_glens/src/Haoman6_glens.kt b/multisrc/overrides/mccms/haoman6_glens/src/Haoman6_glens.kt index 0f6175309..92bf423ec 100644 --- a/multisrc/overrides/mccms/haoman6_glens/src/Haoman6_glens.kt +++ b/multisrc/overrides/mccms/haoman6_glens/src/Haoman6_glens.kt @@ -1,8 +1,13 @@ package eu.kanade.tachiyomi.extension.zh.haoman6_glens import eu.kanade.tachiyomi.multisrc.mccms.MCCMS +import eu.kanade.tachiyomi.network.GET +import okhttp3.Response class Haoman6_glens : MCCMS("好漫6 (g-lens)", "https://www.g-lens.com") { override fun transformTitle(title: String) = title.removeSuffix("_").removeSuffix("-") override val lazyLoadImageAttr = "pc-ec" + + override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/category/order/addtime", headers) + override fun latestUpdatesParse(response: Response) = searchMangaParse(response) } diff --git a/multisrc/overrides/mccms/haoman8/src/Haoman8.kt b/multisrc/overrides/mccms/haoman8/src/Haoman8.kt index 2373ebcb7..f9611270c 100644 --- a/multisrc/overrides/mccms/haoman8/src/Haoman8.kt +++ b/multisrc/overrides/mccms/haoman8/src/Haoman8.kt @@ -2,13 +2,12 @@ package eu.kanade.tachiyomi.extension.zh.haoman8 import eu.kanade.tachiyomi.multisrc.mccms.MCCMS import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList class Haoman8 : MCCMS("好漫8", "https://caiji.haoman8.com") { // Search: 此站点nginx配置有问题,只能用以下格式搜索第一页 - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = + override fun textSearchRequest(page: Int, query: String) = GET("$baseUrl/index.php/search?key=$query", headers) override fun searchMangaNextPageSelector(): String? = null diff --git a/multisrc/overrides/mccms/haomanwu/src/Haomanwu.kt b/multisrc/overrides/mccms/haomanwu/src/Haomanwu.kt index afdcc0f70..57050d900 100644 --- a/multisrc/overrides/mccms/haomanwu/src/Haomanwu.kt +++ b/multisrc/overrides/mccms/haomanwu/src/Haomanwu.kt @@ -1,12 +1,13 @@ package eu.kanade.tachiyomi.extension.zh.haomanwu import eu.kanade.tachiyomi.multisrc.mccms.MCCMS +import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SManga import org.jsoup.nodes.Document import org.jsoup.nodes.Element -class Haomanwu : MCCMS("好漫屋", "https://app2.haomanwu.com") { +class Haomanwu : MCCMS("好漫屋", "https://app2.haoman6.com") { // Search @@ -24,4 +25,8 @@ class Haomanwu : MCCMS("好漫屋", "https://app2.haomanwu.com") { } return pages } + + // 分类页面缺失 + override fun fetchCategories() = Unit + override fun getFilterList() = FilterList(emptyList()) } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mccms/MCCMS.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mccms/MCCMS.kt index 4c861c9d6..d70bdd7b6 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mccms/MCCMS.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mccms/MCCMS.kt @@ -1,14 +1,19 @@ package eu.kanade.tachiyomi.multisrc.mccms +import android.util.Log import eu.kanade.tachiyomi.network.GET +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.ParsedHttpSource +import eu.kanade.tachiyomi.util.asJsoup import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element +import kotlin.concurrent.thread /** * 漫城CMS http://mccms.cn/ @@ -20,19 +25,15 @@ abstract class MCCMS( ) : ParsedHttpSource() { override val supportsLatest: Boolean = true - protected open val nextPageSelector = "div#Pagination a.next" - protected open val comicItemSelector = "div.common-comic-item" - protected open val comicItemTitleSelector = "p.comic__title > a" - protected open fun transformTitle(title: String) = title // Popular - override fun popularMangaRequest(page: Int) = GET("$baseUrl/category/order/hits/page/$page", headers) - override fun popularMangaNextPageSelector() = nextPageSelector - override fun popularMangaSelector() = comicItemSelector + override fun popularMangaRequest(page: Int) = GET("$baseUrl/custom/hot", headers) + override fun popularMangaNextPageSelector(): String? = null + override fun popularMangaSelector() = ".top-list__box-item" override fun popularMangaFromElement(element: Element) = SManga.create().apply { - val titleElement = element.select(comicItemTitleSelector) + val titleElement = element.select("p.comic__title > a") title = transformTitle(titleElement.text().trim()) setUrlWithoutDomain(titleElement.attr("abs:href")) thumbnail_url = element.select("img").attr("abs:data-original") @@ -40,20 +41,48 @@ abstract class MCCMS( // Latest - override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/category/order/addtime/page/$page", headers) - override fun latestUpdatesNextPageSelector() = nextPageSelector - override fun latestUpdatesSelector() = comicItemSelector + override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/custom/update", headers) + override fun latestUpdatesNextPageSelector(): String? = null + override fun latestUpdatesSelector() = "div.common-comic-item" override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) // Search - override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = + protected open fun textSearchRequest(page: Int, query: String) = GET("$baseUrl/search/$query/$page", headers) - override fun searchMangaNextPageSelector(): String? = nextPageSelector - override fun searchMangaSelector() = comicItemSelector + override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = + if (query.isNotBlank()) { + textSearchRequest(page, query) + } else { + val categories = filters.filterIsInstance() + .map { it.toUriPart() }.filter { it.isNotEmpty() }.joinToString("/") + GET("$baseUrl/category/$categories/page/$page", headers) + } + + override fun searchMangaNextPageSelector(): String? = "" // empty string means default pagination + override fun searchMangaSelector() = latestUpdatesSelector() override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) + override fun searchMangaParse(response: Response): MangasPage { + val document = response.asJsoup() + val isTextSearch = document.location().contains("search") + val mangas = if (isTextSearch) { + document.select(searchMangaSelector()).map { searchMangaFromElement(it) } + } else { + document.select(latestUpdatesSelector()).map { popularMangaFromElement(it) } + } + val hasNextPage = if (isTextSearch && searchMangaNextPageSelector() != "") { + searchMangaNextPageSelector()?.let { document.selectFirst(it) } != null + } else { // default pagination + val buttons = document.select("#Pagination a") + val count = buttons.size + // Next page != Last page + buttons[count - 1].attr("href") != buttons[count - 2].attr("href") + } + return MangasPage(mangas, hasNextPage) + } + // Details override fun mangaDetailsParse(document: Document) = SManga.create().apply { @@ -83,4 +112,56 @@ abstract class MCCMS( .mapIndexed { i, el -> Page(i, "", el.attr("abs:$lazyLoadImageAttr")) } override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used.") + + protected class UriPartFilter(name: String, values: Array, private val uriParts: Array) : + Filter.Select(name, values) { + fun toUriPart(): String = uriParts[state] + } + + protected data class Category(val name: String, val values: Array, val uriParts: Array) { + fun toUriPartFilter() = UriPartFilter(name, values, uriParts) + } + + private val sortCategory = Category("排序", arrayOf("热门人气", "更新时间"), arrayOf("order/hits", "order/addtime")) + private lateinit var categories: List + private var isFetchingCategories = false + + private fun tryFetchCategories() { + if (isFetchingCategories) return + isFetchingCategories = true + thread { + try { + fetchCategories() + } catch (e: Exception) { + Log.e("MCCMS/$name", "Failed to fetch categories ($e)") + } finally { + isFetchingCategories = false + } + } + } + + protected open fun fetchCategories() { + val document = client.newCall(GET("$baseUrl/category/", headers)).execute().asJsoup() + categories = document.select("div.cate-col").map { element -> + val name = element.select("p.cate-title").text().removeSuffix(":") + val tags = element.select("li.cate-item > a") + val values = tags.map { it.text() }.toTypedArray() + val uriParts = tags.map { it.attr("href").removePrefix("/category/") }.toTypedArray() + Category(name, values, uriParts) + } + } + + override fun getFilterList(): FilterList { + val result = mutableListOf( + Filter.Header("如果使用文本搜索,将会忽略分类筛选"), + sortCategory.toUriPartFilter(), + ) + if (::categories.isInitialized) { + categories.forEach { result.add(it.toUriPartFilter()) } + } else { + tryFetchCategories() + result.add(Filter.Header("其他分类正在获取,请返回上一页后重试")) + } + return FilterList(result) + } } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mccms/MCCMSGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mccms/MCCMSGenerator.kt index ed924842c..288ac803a 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mccms/MCCMSGenerator.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mccms/MCCMSGenerator.kt @@ -6,14 +6,14 @@ import generator.ThemeSourceGenerator class MCCMSGenerator : ThemeSourceGenerator { override val themeClass = "MCCMS" override val themePkg = "mccms" - override val baseVersionCode = 1 + override val baseVersionCode = 2 override val sources = listOf( SingleLang( name = "Haoman6", baseUrl = "https://www.haoman6.com", lang = "zh", className = "Haoman6", sourceName = "好漫6", overrideVersionCode = 2 ), SingleLang( - name = "Haomanwu", baseUrl = "https://app2.haomanwu.com", lang = "zh", + name = "Haomanwu", baseUrl = "https://app2.haoman6.com", lang = "zh", className = "Haomanwu", sourceName = "好漫屋", overrideVersionCode = 3 ), SingleLang(