SinMH: upload date logic, fixes, move YKMH source (#12189)
* SinMH: upload date logic and cleanup * Fix chapter and page list parse * SinMH: move, rename and fix Manhuadui -> YKMH * Fetch categories on popular/latest manga parse * Change SChapter List sorting * [skip ci] refine filter prompt
|
@ -0,0 +1,10 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.zh.gufengmh
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.multisrc.sinmh.SinMH
|
||||||
|
|
||||||
|
class Gufengmh : SinMH("古风漫画网", "https://www.gufengmh9.com") {
|
||||||
|
|
||||||
|
override val dateSelector = ".pic_zi:nth-of-type(4) > dd"
|
||||||
|
|
||||||
|
override fun chapterListSelector() = ".list li > a"
|
||||||
|
}
|
|
@ -1,23 +1,15 @@
|
||||||
package eu.kanade.tachiyomi.extension.zh.imitui
|
package eu.kanade.tachiyomi.extension.zh.imitui
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.sinmh.SinMH
|
import eu.kanade.tachiyomi.multisrc.sinmh.SinMH
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
|
|
||||||
class Imitui : SinMH("爱米推漫画", "https://www.imitui.com") {
|
class Imitui : SinMH("爱米推漫画", "https://www.imitui.com") {
|
||||||
private val mobileUrl = "https://m.imitui.com"
|
|
||||||
|
|
||||||
override fun chapterListRequest(manga: SManga) = GET(mobileUrl + manga.url, headers)
|
|
||||||
|
|
||||||
override fun pageListRequest(chapter: SChapter) = GET(mobileUrl + chapter.url, headers)
|
|
||||||
|
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
override fun pageListParse(document: Document): List<Page> {
|
||||||
val pageCount = document.select("div.image-content > p").text().removePrefix("1/").toInt()
|
val pageCount = document.select("div.image-content > p").text().removePrefix("1/").toInt()
|
||||||
val prefix = document.location().removeSuffix(".html")
|
val prefix = document.location().removeSuffix(".html")
|
||||||
return (0 until pageCount).map { Page(it, "$prefix-${it + 1}.html") }
|
return (0 until pageCount).map { Page(it, url = "$prefix-${it + 1}.html") }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun imageUrlParse(document: Document): String =
|
override fun imageUrlParse(document: Document): String =
|
||||||
|
|
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 6.3 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 100 KiB |
|
@ -0,0 +1,31 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.zh.manhuadui
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.multisrc.sinmh.SinMH
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import org.jsoup.nodes.Document
|
||||||
|
|
||||||
|
class YKMH : SinMH("优酷漫画", "http://www.ykmh.com") {
|
||||||
|
override val id = 1637952806167036168
|
||||||
|
override val mobileUrl = "http://wap.ykmh.com"
|
||||||
|
|
||||||
|
override val comicItemSelector = "li.list-comic"
|
||||||
|
override val comicItemTitleSelector = "h3 > a, p > a"
|
||||||
|
|
||||||
|
// DMZJ style
|
||||||
|
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
||||||
|
title = document.selectFirst("h1").text()
|
||||||
|
val details = document.selectFirst("ul.comic_deCon_liO").children()
|
||||||
|
author = details[0].selectFirst("a").text()
|
||||||
|
status = when (details[1].selectFirst("a").text()) {
|
||||||
|
"连载中" -> SManga.ONGOING
|
||||||
|
"已完结" -> SManga.COMPLETED
|
||||||
|
else -> SManga.UNKNOWN
|
||||||
|
}
|
||||||
|
genre = (details[2].select("a") + details[3].select("a")).joinToString(", ") { it.text() }
|
||||||
|
description = document.selectFirst("p.comic_deCon_d").text()
|
||||||
|
thumbnail_url = document.selectFirst("div.comic_i_img > img").attr("src")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun List<SChapter>.sortedDescending() = this
|
||||||
|
}
|
|
@ -1,9 +1,10 @@
|
||||||
package eu.kanade.tachiyomi.multisrc.sinmh
|
package eu.kanade.tachiyomi.multisrc.sinmh
|
||||||
|
|
||||||
import android.util.Log
|
import eu.kanade.tachiyomi.AppInfo
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
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.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
@ -13,7 +14,8 @@ import okhttp3.Headers
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import kotlin.concurrent.thread
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 圣樱漫画CMS https://gitee.com/shenl/SinMH-2.0-Guide
|
* 圣樱漫画CMS https://gitee.com/shenl/SinMH-2.0-Guide
|
||||||
|
@ -26,6 +28,7 @@ abstract class SinMH(
|
||||||
override val lang: String = "zh",
|
override val lang: String = "zh",
|
||||||
) : ParsedHttpSource() {
|
) : ParsedHttpSource() {
|
||||||
override val baseUrl = _baseUrl
|
override val baseUrl = _baseUrl
|
||||||
|
protected open val mobileUrl = _baseUrl.replace("www", "m")
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
override fun headersBuilder(): Headers.Builder = Headers.Builder()
|
override fun headersBuilder(): Headers.Builder = Headers.Builder()
|
||||||
|
@ -34,13 +37,12 @@ abstract class SinMH(
|
||||||
protected open val nextPageSelector = "ul.pagination > li.next:not(.disabled)"
|
protected open val nextPageSelector = "ul.pagination > li.next:not(.disabled)"
|
||||||
protected open val comicItemSelector = "#contList > li"
|
protected open val comicItemSelector = "#contList > li"
|
||||||
protected open val comicItemTitleSelector = "p > a"
|
protected open val comicItemTitleSelector = "p > a"
|
||||||
protected open fun mangaFromElement(element: Element) =
|
protected open fun mangaFromElement(element: Element) = SManga.create().apply {
|
||||||
SManga.create().apply {
|
val titleElement = element.selectFirst(comicItemTitleSelector)
|
||||||
val titleElement = element.select(comicItemTitleSelector)
|
title = titleElement.text()
|
||||||
title = titleElement.text()
|
setUrlWithoutDomain(titleElement.attr("abs:href"))
|
||||||
setUrlWithoutDomain(titleElement.attr("abs:href"))
|
thumbnail_url = element.selectFirst("img").attr("abs:src")
|
||||||
thumbnail_url = element.select("img").attr("abs:src")
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Popular
|
// Popular
|
||||||
|
|
||||||
|
@ -49,6 +51,14 @@ abstract class SinMH(
|
||||||
override fun popularMangaSelector() = comicItemSelector
|
override fun popularMangaSelector() = comicItemSelector
|
||||||
override fun popularMangaFromElement(element: Element) = mangaFromElement(element)
|
override fun popularMangaFromElement(element: Element) = mangaFromElement(element)
|
||||||
|
|
||||||
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
parseCategories(document)
|
||||||
|
val mangas = document.select(popularMangaSelector()).map(::popularMangaFromElement)
|
||||||
|
val hasNextPage = popularMangaNextPageSelector()?.let { document.selectFirst(it) } != null
|
||||||
|
return MangasPage(mangas, hasNextPage)
|
||||||
|
}
|
||||||
|
|
||||||
// Latest
|
// Latest
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/list/update/?page=$page", headers)
|
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/list/update/?page=$page", headers)
|
||||||
|
@ -56,6 +66,14 @@ abstract class SinMH(
|
||||||
override fun latestUpdatesSelector() = comicItemSelector
|
override fun latestUpdatesSelector() = comicItemSelector
|
||||||
override fun latestUpdatesFromElement(element: Element) = mangaFromElement(element)
|
override fun latestUpdatesFromElement(element: Element) = mangaFromElement(element)
|
||||||
|
|
||||||
|
override fun latestUpdatesParse(response: Response): MangasPage {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
parseCategories(document)
|
||||||
|
val mangas = document.select(latestUpdatesSelector()).map(::latestUpdatesFromElement)
|
||||||
|
val hasNextPage = latestUpdatesNextPageSelector()?.let { document.selectFirst(it) } != null
|
||||||
|
return MangasPage(mangas, hasNextPage)
|
||||||
|
}
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
|
|
||||||
override fun searchMangaNextPageSelector(): String? = nextPageSelector
|
override fun searchMangaNextPageSelector(): String? = nextPageSelector
|
||||||
|
@ -65,34 +83,48 @@ abstract class SinMH(
|
||||||
if (query.isNotBlank()) {
|
if (query.isNotBlank()) {
|
||||||
GET("$baseUrl/search/?keywords=$query&page=$page", headers)
|
GET("$baseUrl/search/?keywords=$query&page=$page", headers)
|
||||||
} else {
|
} else {
|
||||||
val categories = filters.filterIsInstance<UriPartFilter>()
|
val categories = filters.filterIsInstance<UriPartFilter>().map { it.toUriPart() }
|
||||||
.joinToString("-", transform = UriPartFilter::toUriPart) + "-"
|
.filter { it.isNotEmpty() }.joinToString("-") + "-"
|
||||||
GET("$baseUrl/list/$categories/", headers)
|
GET("$baseUrl/list/$categories/", headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Details
|
// Details
|
||||||
|
|
||||||
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
||||||
title = document.select(".book-title > h1 > span").text()
|
title = document.selectFirst(".book-title > h1 > span").text()
|
||||||
author = document.select(".detail-list strong:contains(作者) + a").text()
|
author = document.selectFirst(".detail-list strong:contains(作者) + a").text()
|
||||||
description = document.select("#intro-all").text().trim()
|
description = document.selectFirst("#intro-all").text().trim()
|
||||||
.removePrefix("漫画简介:").trim()
|
.removePrefix("漫画简介:").trim()
|
||||||
.removePrefix("漫画简介:").trim() // some sources have double prefix
|
.removePrefix("漫画简介:").trim() // some sources have double prefix
|
||||||
genre = document.select(".detail-list strong:contains(类型) + a").text() + ", " +
|
genre = document.selectFirst(".detail-list strong:contains(类型) + a").text() + ", " +
|
||||||
document.select(".breadcrumb-bar a[href*=/list/]").joinToString(", ") { it.text() }
|
document.select(".breadcrumb-bar a[href*=/list/]").joinToString(", ") { it.text() }
|
||||||
status = when (document.select(".detail-list strong:contains(状态) + a").text()) {
|
status = when (document.selectFirst(".detail-list strong:contains(状态) + a").text()) {
|
||||||
"连载中" -> SManga.ONGOING
|
"连载中" -> SManga.ONGOING
|
||||||
"已完结" -> SManga.COMPLETED
|
"已完结" -> SManga.COMPLETED
|
||||||
else -> SManga.UNKNOWN
|
else -> SManga.UNKNOWN
|
||||||
}
|
}
|
||||||
thumbnail_url = document.select("p.cover > img").attr("abs:src")
|
thumbnail_url = document.selectFirst("p.cover > img").attr("abs:src")
|
||||||
// TODO: can use 更新时间:2022-05-23 to set default upload date
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chapters
|
// Chapters
|
||||||
|
|
||||||
override fun chapterListSelector() = ".chapter-body li > a:not([href*=/comic/app/])"
|
override fun chapterListRequest(manga: SManga) = GET(mobileUrl + manga.url, headers)
|
||||||
override fun chapterListParse(response: Response) = super.chapterListParse(response).reversed()
|
|
||||||
|
protected open val dateSelector = ".date"
|
||||||
|
|
||||||
|
protected open fun List<SChapter>.sortedDescending() = this.asReversed()
|
||||||
|
|
||||||
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
return document.select(chapterListSelector()).map { chapterFromElement(it) }.sortedDescending().apply {
|
||||||
|
if (isNewDateLogic) {
|
||||||
|
val date = document.selectFirst(dateSelector).textNodes().last().text()
|
||||||
|
this[0].date_upload = DATE_FORMAT.parse(date)?.time ?: 0L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chapterListSelector() = ".chapter-body li > a:not([href^=/comic/app/])"
|
||||||
override fun chapterFromElement(element: Element) = SChapter.create().apply {
|
override fun chapterFromElement(element: Element) = SChapter.create().apply {
|
||||||
setUrlWithoutDomain(element.attr("abs:href"))
|
setUrlWithoutDomain(element.attr("abs:href"))
|
||||||
name = element.text()
|
name = element.text()
|
||||||
|
@ -100,13 +132,23 @@ abstract class SinMH(
|
||||||
|
|
||||||
// Pages
|
// Pages
|
||||||
|
|
||||||
|
override fun pageListRequest(chapter: SChapter) = GET(mobileUrl + chapter.url, headers)
|
||||||
|
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
override fun pageListParse(document: Document): List<Page> {
|
||||||
val script = document.select("script:containsData(chapterImages)").html()
|
val script = document.selectFirst("body > script").html()
|
||||||
val images = script.substringAfter("chapterImages = [\"").substringBefore("\"]").split("\",\"")
|
val images = script.substringAfter("chapterImages = [\"").substringBefore("\"]").split("\",\"")
|
||||||
val path = script.substringAfter("chapterPath = \"").substringBefore("\";")
|
val path = script.substringAfter("chapterPath = \"").substringBefore("\";")
|
||||||
// assume cover images are on the same server
|
// assume cover images are on the page image server
|
||||||
val server = script.substringAfter("pageImage = \"").substringBefore("/images/cover")
|
val server = script.substringAfter("pageImage = \"").substringBefore("/images/cover")
|
||||||
return images.mapIndexed { i, image -> Page(i, "", "$server/$path/$image") }
|
return images.mapIndexed { i, image ->
|
||||||
|
val unescapedImage = image.replace("""\/""", "/")
|
||||||
|
val imageUrl = if (unescapedImage.startsWith("/")) {
|
||||||
|
"$server$unescapedImage"
|
||||||
|
} else {
|
||||||
|
"$server/$path$unescapedImage"
|
||||||
|
}
|
||||||
|
Page(i, imageUrl = imageUrl)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used.")
|
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used.")
|
||||||
|
@ -121,26 +163,11 @@ abstract class SinMH(
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var categories: List<Category>
|
private lateinit var categories: List<Category>
|
||||||
private var isFetchingCategories = false
|
|
||||||
|
|
||||||
private fun tryFetchCategories() {
|
protected open fun parseCategories(document: Document) {
|
||||||
if (isFetchingCategories) return
|
if (::categories.isInitialized) return
|
||||||
isFetchingCategories = true
|
categories = document.selectFirst(".filter-nav").children().map { element ->
|
||||||
thread {
|
val name = element.selectFirst("label").text()
|
||||||
try {
|
|
||||||
fetchCategories()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("SinMH", "Failed to fetch categories ($e)")
|
|
||||||
} finally {
|
|
||||||
isFetchingCategories = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected open fun fetchCategories() {
|
|
||||||
val document = client.newCall(GET("$baseUrl/list/", headers)).execute().asJsoup()
|
|
||||||
categories = document.select(".page-main .filter-nav > .filter-item").map { element ->
|
|
||||||
val name = element.select("label").text()
|
|
||||||
val tags = element.select("a")
|
val tags = element.select("a")
|
||||||
val values = tags.map { it.text() }.toTypedArray()
|
val values = tags.map { it.text() }.toTypedArray()
|
||||||
val uriParts = tags.map { it.attr("href").removePrefix("/list/").removeSuffix("/") }.toTypedArray()
|
val uriParts = tags.map { it.attr("href").removePrefix("/list/").removeSuffix("/") }.toTypedArray()
|
||||||
|
@ -152,10 +179,17 @@ abstract class SinMH(
|
||||||
if (::categories.isInitialized) FilterList(
|
if (::categories.isInitialized) FilterList(
|
||||||
Filter.Header("如果使用文本搜索,将会忽略分类筛选"),
|
Filter.Header("如果使用文本搜索,将会忽略分类筛选"),
|
||||||
*categories.map(Category::toUriPartFilter).toTypedArray()
|
*categories.map(Category::toUriPartFilter).toTypedArray()
|
||||||
) else {
|
) else FilterList(
|
||||||
tryFetchCategories()
|
Filter.Header("点击“重置”即可刷新分类,如果失败,"),
|
||||||
FilterList(
|
Filter.Header("请尝试重新从图源列表点击进入图源"),
|
||||||
Filter.Header("分类尚未获取,请返回上一页后重试")
|
)
|
||||||
)
|
|
||||||
}
|
private val DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH)
|
||||||
|
private val isNewDateLogic = run {
|
||||||
|
val commitCount = AppInfo.getVersionName().substringAfter('-', "")
|
||||||
|
if (commitCount.isNotEmpty()) // Preview
|
||||||
|
commitCount.toInt() >= 4442
|
||||||
|
else // Stable
|
||||||
|
AppInfo.getVersionCode() >= 81
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import generator.ThemeSourceGenerator
|
||||||
class SinMHGenerator : ThemeSourceGenerator {
|
class SinMHGenerator : ThemeSourceGenerator {
|
||||||
override val themeClass = "SinMH"
|
override val themeClass = "SinMH"
|
||||||
override val themePkg = "sinmh"
|
override val themePkg = "sinmh"
|
||||||
override val baseVersionCode = 3
|
override val baseVersionCode = 4
|
||||||
override val sources = listOf(
|
override val sources = listOf(
|
||||||
SingleLang(
|
SingleLang(
|
||||||
name = "Gufeng Manhua", baseUrl = "https://www.gufengmh9.com", lang = "zh",
|
name = "Gufeng Manhua", baseUrl = "https://www.gufengmh9.com", lang = "zh",
|
||||||
|
@ -15,7 +15,11 @@ class SinMHGenerator : ThemeSourceGenerator {
|
||||||
SingleLang(
|
SingleLang(
|
||||||
name = "Imitui Manhua", baseUrl = "https://www.imitui.com", lang = "zh",
|
name = "Imitui Manhua", baseUrl = "https://www.imitui.com", lang = "zh",
|
||||||
className = "Imitui", sourceName = "爱米推漫画", overrideVersionCode = 2
|
className = "Imitui", sourceName = "爱米推漫画", overrideVersionCode = 2
|
||||||
)
|
),
|
||||||
|
SingleLang(
|
||||||
|
name = "YKMH", baseUrl = "http://www.ykmh.com", lang = "zh", className = "YKMH",
|
||||||
|
pkgName = "manhuadui", sourceName = "优酷漫画", overrideVersionCode = 17
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest package="eu.kanade.tachiyomi.extension" />
|
|
|
@ -1,11 +0,0 @@
|
||||||
apply plugin: 'com.android.application'
|
|
||||||
apply plugin: 'kotlin-android'
|
|
||||||
|
|
||||||
ext {
|
|
||||||
extName = 'Manhuadui'
|
|
||||||
pkgNameSuffix = 'zh.manhuadui'
|
|
||||||
extClass = '.Manhuadui'
|
|
||||||
extVersionCode = 17
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
Before Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 460 KiB |
|
@ -1,205 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.extension.zh.manhuadui
|
|
||||||
|
|
||||||
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.Page
|
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
|
||||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.Response
|
|
||||||
import org.jsoup.nodes.Document
|
|
||||||
import org.jsoup.nodes.Element
|
|
||||||
|
|
||||||
class Manhuadui : ParsedHttpSource() {
|
|
||||||
|
|
||||||
override val name = "漫画堆"
|
|
||||||
override val baseUrl = "https://ykmh.com"
|
|
||||||
override val lang = "zh"
|
|
||||||
override val supportsLatest = true
|
|
||||||
// Servers can be found from resHost in baseUrl/js/config.js
|
|
||||||
private val imageServer = arrayOf("https://js.tingliu.cc")
|
|
||||||
|
|
||||||
override val client: OkHttpClient = super.client.newBuilder()
|
|
||||||
.followRedirects(true)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
override fun popularMangaSelector() = "li.list-comic"
|
|
||||||
override fun searchMangaSelector() = popularMangaSelector()
|
|
||||||
override fun latestUpdatesSelector() = popularMangaSelector()
|
|
||||||
override fun chapterListSelector() = "ul[id^=chapter-list] > li a"
|
|
||||||
|
|
||||||
override fun searchMangaNextPageSelector() = "li.next a"
|
|
||||||
override fun popularMangaNextPageSelector() = searchMangaNextPageSelector()
|
|
||||||
override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector()
|
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int) = GET("$baseUrl/list_$page/", headers)
|
|
||||||
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/update/$page/", headers)
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
|
||||||
return if (query != "") {
|
|
||||||
val url = "$baseUrl/search/?keywords=$query&page=$page".toHttpUrlOrNull()?.newBuilder()
|
|
||||||
GET(url.toString(), headers)
|
|
||||||
} else {
|
|
||||||
val params = filters.map {
|
|
||||||
if (it is UriPartFilter) {
|
|
||||||
it.toUriPart()
|
|
||||||
} else ""
|
|
||||||
}.filter { it != "" }.joinToString("-")
|
|
||||||
val url = when {
|
|
||||||
params.isEmpty() -> "$baseUrl/list_$page/".toHttpUrlOrNull()?.newBuilder()
|
|
||||||
else -> "$baseUrl/list/$params/$page/".toHttpUrlOrNull()?.newBuilder()
|
|
||||||
}
|
|
||||||
GET(url.toString(), headers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun popularMangaFromElement(element: Element) = mangaFromElement(element)
|
|
||||||
override fun latestUpdatesFromElement(element: Element) = mangaFromElement(element)
|
|
||||||
private fun mangaFromElement(element: Element): SManga {
|
|
||||||
val manga = SManga.create()
|
|
||||||
element.select("a.comic_img").first().let {
|
|
||||||
manga.setUrlWithoutDomain(it.attr("href"))
|
|
||||||
manga.title = it.select("img").attr("alt").trim()
|
|
||||||
manga.thumbnail_url = if (it.select("img").attr("src").trim().indexOf("http") == -1)
|
|
||||||
"https:${it.select("img").attr("src").trim()}"
|
|
||||||
else it.select("img").attr("src").trim()
|
|
||||||
}
|
|
||||||
manga.author = element.select("span.comic_list_det > p").first()?.text()?.substring(3)
|
|
||||||
return manga
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchMangaFromElement(element: Element): SManga {
|
|
||||||
val manga = SManga.create()
|
|
||||||
val els = element.select("a.image-link")
|
|
||||||
if (els.size == 0) {
|
|
||||||
element.select("li.list-comic").first().let {
|
|
||||||
manga.setUrlWithoutDomain(it.select("a").attr("href"))
|
|
||||||
manga.title = it.select("span").attr("title").trim()
|
|
||||||
manga.thumbnail_url = it.select("a > img").attr("src").trim()
|
|
||||||
manga.author = it.select("span > p").first().text().split(":")[1].trim()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
element.select("a.image-link").first().let {
|
|
||||||
manga.setUrlWithoutDomain(it.attr("href"))
|
|
||||||
manga.title = it.attr("title").trim()
|
|
||||||
manga.thumbnail_url = it.select("img").attr("src").trim()
|
|
||||||
}
|
|
||||||
manga.author = element.select("p.auth").text().trim()
|
|
||||||
}
|
|
||||||
return manga
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun chapterFromElement(element: Element): SChapter {
|
|
||||||
val chapter = SChapter.create()
|
|
||||||
chapter.setUrlWithoutDomain(element.attr("href"))
|
|
||||||
chapter.name = element.select("span:nth-child(2)").text().trim()
|
|
||||||
return chapter
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun mangaDetailsParse(document: Document): SManga {
|
|
||||||
val manga = SManga.create()
|
|
||||||
manga.description = document.select("p.comic_deCon_d").text().trim()
|
|
||||||
manga.thumbnail_url = document.select("div.comic_i_img > img").attr("src")
|
|
||||||
manga.status = when (document.select("ul.comic_deCon_liO a")[1].attr("href")) {
|
|
||||||
"/list/lianzai/" -> SManga.ONGOING
|
|
||||||
"/list/wanjie/" -> SManga.COMPLETED
|
|
||||||
else -> SManga.UNKNOWN
|
|
||||||
}
|
|
||||||
manga.genre = document.select("ul.comic_deCon_liO li:nth-child(4) a").eachText().joinToString(", ")
|
|
||||||
return manga
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun chapterListRequest(manga: SManga) = GET(baseUrl.replace("www", "m") + manga.url)
|
|
||||||
override fun chapterListParse(response: Response): List<SChapter> {
|
|
||||||
return super.chapterListParse(response).asReversed()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val chapterImagesRegex = Regex("""var chapterImages =\s*\["(.*?)"\];""")
|
|
||||||
private val imgCodeCleanupRegex = Regex("""[\[\]"\\]""")
|
|
||||||
|
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
|
||||||
val html = document.html()
|
|
||||||
val imgCodeStr = chapterImagesRegex.find(html)?.groups?.get(1)?.value ?: throw Exception("imgCodeStr not found")
|
|
||||||
val imgCode = imgCodeStr
|
|
||||||
.replace(imgCodeCleanupRegex, "")
|
|
||||||
.replace("%", "%25")
|
|
||||||
return imgCode.split(",").mapIndexed { i, imgStr ->
|
|
||||||
// The way image urls are processed by the website can be found in
|
|
||||||
// getChapterImage() in baseUrl/js/common.js
|
|
||||||
if (imgStr.startsWith("http://images.dmzj.com")) {
|
|
||||||
Page(i, "", "${imageServer[0]}/showImage.php?url=$imgStr")
|
|
||||||
} else {
|
|
||||||
Page(i, "", if (imgStr.indexOf("http") == -1) "${imageServer[0]}$imgStr" else imgStr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used")
|
|
||||||
|
|
||||||
override fun getFilterList() = FilterList(
|
|
||||||
CategoryGroup(),
|
|
||||||
RegionGroup(),
|
|
||||||
GenreGroup(),
|
|
||||||
ProgressGroup()
|
|
||||||
)
|
|
||||||
|
|
||||||
private class CategoryGroup : UriPartFilter(
|
|
||||||
"按类型",
|
|
||||||
arrayOf(
|
|
||||||
Pair("全部", ""),
|
|
||||||
Pair("儿童漫画", "ertong"),
|
|
||||||
Pair("少年漫画", "shaonian"),
|
|
||||||
Pair("少女漫画", "shaonv"),
|
|
||||||
Pair("青年漫画", "qingnian")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
private class ProgressGroup : UriPartFilter(
|
|
||||||
"按进度",
|
|
||||||
arrayOf(
|
|
||||||
Pair("全部", ""),
|
|
||||||
Pair("已完结", "wanjie"),
|
|
||||||
Pair("连载中", "lianzai")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
private class RegionGroup : UriPartFilter(
|
|
||||||
"按地区",
|
|
||||||
arrayOf(
|
|
||||||
Pair("全部", ""),
|
|
||||||
Pair("日本", "riben"),
|
|
||||||
Pair("大陆", "dalu"),
|
|
||||||
Pair("香港", "hongkong"),
|
|
||||||
Pair("台湾", "taiwan"),
|
|
||||||
Pair("欧美", "oumei"),
|
|
||||||
Pair("韩国", "hanguo"),
|
|
||||||
Pair("其他", "qita")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
private class GenreGroup : UriPartFilter(
|
|
||||||
"按剧情",
|
|
||||||
arrayOf(
|
|
||||||
Pair("全部", ""),
|
|
||||||
Pair("热血", "rexue"),
|
|
||||||
Pair("冒险", "maoxian"),
|
|
||||||
Pair("玄幻", "xuanhuan"),
|
|
||||||
Pair("搞笑", "gaoxiao"),
|
|
||||||
Pair("恋爱", "lianai"),
|
|
||||||
Pair("宠物", "chongwu"),
|
|
||||||
Pair("新作", "xinzuo")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
private open class UriPartFilter(
|
|
||||||
displayName: String,
|
|
||||||
val vals: Array<Pair<String, String>>,
|
|
||||||
defaultValue: Int = 0
|
|
||||||
) :
|
|
||||||
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray(), defaultValue) {
|
|
||||||
open fun toUriPart() = vals[state].second
|
|
||||||
}
|
|
||||||
}
|
|