MCCMS: update sources (#15843)
* MCCMS: update sources * Change cover placeholder * Refactor
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 7.0 KiB |
Before Width: | Height: | Size: 21 KiB |
@ -1,18 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.extension.zh.haoman6
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.mccms.MCCMSWeb
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
|
||||||
|
|
||||||
class Haoman6 : MCCMSWeb("好漫6", "https://www.haoman6.com") {
|
|
||||||
override fun SManga.cleanup() = apply {
|
|
||||||
description = description?.substringBefore(title)
|
|
||||||
title = title.removeSuffix("(最新在线)").removeSuffix("-")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun pageListRequest(chapter: SChapter) =
|
|
||||||
GET(baseUrl + chapter.url, headers)
|
|
||||||
|
|
||||||
override val lazyLoadImageAttr = "mob-ec"
|
|
||||||
}
|
|
@ -1,7 +1,3 @@
|
|||||||
package eu.kanade.tachiyomi.extension.zh.haoman8
|
package eu.kanade.tachiyomi.extension.zh.haoman8
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.mccms.MCCMS
|
class Haoman8 : MCCMSAcgn("好漫8", "https://www.haoman8.com", hasCategoryPage = false)
|
||||||
|
|
||||||
class Haoman8 : MCCMS("好漫8", "https://caiji.haoman8.com", hasCategoryPage = true) {
|
|
||||||
override val lazyLoadImageAttr = "data-original"
|
|
||||||
}
|
|
||||||
|
117
multisrc/overrides/mccms/haoman8/src/MCCMSAcgn.kt
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.zh.haoman8
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.multisrc.mccms.MCCMSWeb
|
||||||
|
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 kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import org.jsoup.nodes.Document
|
||||||
|
import org.jsoup.select.Evaluator
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
// TODO: Category page
|
||||||
|
open class MCCMSAcgn(
|
||||||
|
name: String,
|
||||||
|
baseUrl: String,
|
||||||
|
lang: String = "zh",
|
||||||
|
hasCategoryPage: Boolean = true,
|
||||||
|
) : MCCMSWeb(name, baseUrl, lang, hasCategoryPage) {
|
||||||
|
|
||||||
|
override fun parseListing(document: Document): MangasPage {
|
||||||
|
if (document.location().contains("search")) {
|
||||||
|
return searchMangaParse(document)
|
||||||
|
}
|
||||||
|
val list = document.selectFirst(Evaluator.Class("acgn-comic-list"))
|
||||||
|
?: return MangasPage(emptyList(), false)
|
||||||
|
val mangas = list.children().map {
|
||||||
|
SManga.create().apply {
|
||||||
|
val titleElement = it.selectFirst(Evaluator.Tag("h3"))!!.child(0)
|
||||||
|
url = titleElement.attr("href")
|
||||||
|
title = titleElement.ownText()
|
||||||
|
thumbnail_url = it.selectFirst(Evaluator.Tag("img"))!!
|
||||||
|
.attr("style").split("'")[1]
|
||||||
|
}.cleanup()
|
||||||
|
}
|
||||||
|
val hasNextPage = run { // default pagination
|
||||||
|
val pagination = document.selectFirst(Evaluator.Class("acgn-pages"))!!
|
||||||
|
pagination.children().last()!!.tagName() == "a"
|
||||||
|
}
|
||||||
|
return MangasPage(mangas, hasNextPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
|
val request = super.searchMangaRequest(page, query, filters)
|
||||||
|
return if (query.isNotBlank()) {
|
||||||
|
// TODO: Fix Captcha
|
||||||
|
throw Exception("暂不支持搜索,请等待后续插件更新")
|
||||||
|
// request.newBuilder().headers(headers).build()
|
||||||
|
} else {
|
||||||
|
request
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun searchMangaParse(document: Document): MangasPage {
|
||||||
|
val entries = document.select(Evaluator.Class("comic-list-item")).map {
|
||||||
|
SManga.create().apply {
|
||||||
|
val titleElement = it.selectFirst(Evaluator.Class("comic-name"))!!.child(0)
|
||||||
|
url = titleElement.attr("href")
|
||||||
|
title = titleElement.ownText()
|
||||||
|
author = it.selectFirst(Evaluator.Class("comic-author"))?.ownText()
|
||||||
|
genre = it.selectFirst(Evaluator.Class("comic-tags"))?.run {
|
||||||
|
children().joinToString { it.ownText() }
|
||||||
|
}
|
||||||
|
thumbnail_url = it.selectFirst(Evaluator.Tag("img"))!!.attr("src")
|
||||||
|
}.cleanup()
|
||||||
|
}
|
||||||
|
return MangasPage(entries, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(response: Response) = SManga.create().apply {
|
||||||
|
val document = response.asJsoup().selectFirst(Evaluator.Class("acgn-model-detail-frontcover"))!!
|
||||||
|
title = document.selectFirst(Evaluator.Tag("h1"))!!.ownText()
|
||||||
|
description = document.selectFirst(Evaluator.Class("desc-content"))?.ownText()
|
||||||
|
genre = document.select("ul.tags > a[href]").joinToString { it.ownText() }
|
||||||
|
thumbnail_url = document.selectFirst(Evaluator.Tag("img"))?.attr("src")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
val scripts = document.select(Evaluator.Tag("script"))
|
||||||
|
val js = scripts[scripts.size - 2].data()
|
||||||
|
val start = js.indexOf('[')
|
||||||
|
val end = js.lastIndexOf(']') + 1
|
||||||
|
val replaced = js.substring(start, end).replace('\'', '"')
|
||||||
|
val list: List<AcgnChapter> = json.decodeFromString(replaced)
|
||||||
|
val dateFormat = dateFormat
|
||||||
|
return list.asReversed().map { it.toSChapter(dateFormat) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override val lazyLoadImageAttr get() = "data-echo"
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class AcgnChapter(
|
||||||
|
private val name: String,
|
||||||
|
private val url: String,
|
||||||
|
private val time: String,
|
||||||
|
) {
|
||||||
|
fun toSChapter(dateFormat: SimpleDateFormat) = SChapter.create().apply {
|
||||||
|
url = this@AcgnChapter.url
|
||||||
|
name = this@AcgnChapter.name
|
||||||
|
date_upload = dateFormat.parse(time)?.time ?: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
|
private val dateFormat by lazy {
|
||||||
|
SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH)
|
||||||
|
}
|
||||||
|
}
|
7
multisrc/overrides/mccms/kuaikuai3/src/Kuaikuai3.kt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.zh.kuaikuai3
|
||||||
|
|
||||||
|
class Kuaikuai3 : MCCMSReduced("快快漫画3", "https://mobile3.manhuaorg.com") {
|
||||||
|
|
||||||
|
override fun headersBuilder() = super.headersBuilder()
|
||||||
|
.set("User-Agent", "okhttp/3.14.7")
|
||||||
|
}
|
105
multisrc/overrides/mccms/kuaikuai3/src/MCCMSReduced.kt
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.zh.kuaikuai3
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.multisrc.mccms.DecryptInterceptor
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
|
||||||
|
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.Companion.toHttpUrl
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import org.jsoup.select.Evaluator
|
||||||
|
|
||||||
|
open class MCCMSReduced(
|
||||||
|
override val name: String,
|
||||||
|
override val baseUrl: String,
|
||||||
|
) : HttpSource() {
|
||||||
|
override val lang = "zh"
|
||||||
|
override val supportsLatest get() = false
|
||||||
|
|
||||||
|
override val client by lazy {
|
||||||
|
network.client.newBuilder()
|
||||||
|
.rateLimitHost(baseUrl.toHttpUrl(), 2)
|
||||||
|
.addInterceptor(DecryptInterceptor)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun searchOnly(): Nothing = throw Exception("此图源只支持搜索")
|
||||||
|
private val noWebView = "https://stevenyomi.github.io/echo#本图源不支持网页查看"
|
||||||
|
|
||||||
|
override fun popularMangaRequest(page: Int) = searchOnly()
|
||||||
|
override fun popularMangaParse(response: Response) = searchOnly()
|
||||||
|
override fun latestUpdatesRequest(page: Int) = searchOnly()
|
||||||
|
override fun latestUpdatesParse(response: Response) = searchOnly()
|
||||||
|
|
||||||
|
override fun getMangaUrl(manga: SManga) = noWebView
|
||||||
|
override fun getChapterUrl(chapter: SChapter) = noWebView
|
||||||
|
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
|
val url = "$baseUrl/index.php/search".toHttpUrl().newBuilder()
|
||||||
|
.addQueryParameter("key", query)
|
||||||
|
.build()
|
||||||
|
return GET(url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaParse(response: Response): MangasPage {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
val placeholder = "$baseUrl/template/pc/default/images/bg_loadimg_3x4.png"
|
||||||
|
val entries = document.select(Evaluator.Tag("a")).map { link ->
|
||||||
|
SManga.create().apply {
|
||||||
|
url = link.attr("href")
|
||||||
|
title = link.ownText()
|
||||||
|
thumbnail_url = placeholder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return MangasPage(entries, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(response: Response): SManga {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
val metaProperties = HashMap<String, String>()
|
||||||
|
for (element in document.head().children()) {
|
||||||
|
if (element.tagName() == "meta" && element.hasAttr("property")) {
|
||||||
|
val key = element.attr("property").removePrefix("og:")
|
||||||
|
metaProperties[key] = element.attr("content")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return SManga.create().apply {
|
||||||
|
title = metaProperties["title"]!!
|
||||||
|
author = metaProperties["novel:author"]
|
||||||
|
description = metaProperties["description"]
|
||||||
|
val statusText = metaProperties["novel:status"]
|
||||||
|
status = when {
|
||||||
|
statusText == null -> SManga.UNKNOWN
|
||||||
|
'连' in statusText -> SManga.ONGOING
|
||||||
|
'完' in statusText -> SManga.COMPLETED
|
||||||
|
else -> SManga.UNKNOWN
|
||||||
|
}
|
||||||
|
thumbnail_url = metaProperties["image"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
return document.select(Evaluator.Class("j-chapter-link")).asReversed().map { link ->
|
||||||
|
SChapter.create().apply {
|
||||||
|
url = link.attr("href")
|
||||||
|
name = link.ownText()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
return document.select(Evaluator.Tag("img")).mapIndexed { index, img ->
|
||||||
|
Page(index, imageUrl = img.attr("data-original"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
|
||||||
|
}
|
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 7.8 KiB |
BIN
multisrc/overrides/mccms/manhuawu/res/web_hi_res_512.png
Normal file
After Width: | Height: | Size: 35 KiB |
11
multisrc/overrides/mccms/manhuawu/src/Manhuawu.kt
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.zh.manhuawu
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.multisrc.mccms.MCCMS
|
||||||
|
import eu.kanade.tachiyomi.multisrc.mccms.MangaDto
|
||||||
|
|
||||||
|
class Manhuawu : MCCMS("漫画屋", "https://www.mhua5.com", hasCategoryPage = true) {
|
||||||
|
|
||||||
|
override fun MangaDto.prepare() = copy(url = "/comic-$id.html")
|
||||||
|
|
||||||
|
override fun getMangaId(url: String) = url.substringAfterLast('-').substringBeforeLast('.')
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.multisrc.mccms
|
package eu.kanade.tachiyomi.multisrc.mccms
|
||||||
|
|
||||||
|
import android.util.Base64
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
@ -12,25 +13,38 @@ object DecryptInterceptor : Interceptor {
|
|||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
val request = chain.request()
|
val request = chain.request()
|
||||||
val response = chain.proceed(request)
|
val response = chain.proceed(request)
|
||||||
val key = when (request.url.topPrivateDomain()) {
|
val host = request.url.host
|
||||||
"bcebos.com" -> key1
|
val type = when {
|
||||||
null -> key2
|
host.endsWith("bcebos.com") -> 1
|
||||||
|
host.endsWith("mhrsrc.com") -> 2
|
||||||
else -> return response
|
else -> return response
|
||||||
}
|
}
|
||||||
val data = decrypt(response.body.bytes(), key)
|
val data = decrypt(response.body.bytes(), type)
|
||||||
val body = data.toResponseBody("image/jpeg".toMediaType())
|
val body = data.toResponseBody("image/jpeg".toMediaType())
|
||||||
return response.newBuilder().body(body).build()
|
return response.newBuilder().body(body).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
private fun decrypt(input: ByteArray, key: SecretKeySpec): ByteArray {
|
private fun decrypt(input: ByteArray, type: Int): ByteArray {
|
||||||
val cipher = cipher
|
val cipher = cipher
|
||||||
cipher.init(Cipher.DECRYPT_MODE, key, iv)
|
val decodedInput: ByteArray
|
||||||
return cipher.doFinal(input)
|
when (type) {
|
||||||
|
1 -> {
|
||||||
|
decodedInput = input
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, key1, iv)
|
||||||
|
}
|
||||||
|
2 -> {
|
||||||
|
decodedInput = Base64.decode(input, Base64.DEFAULT)
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, key2, iv2)
|
||||||
|
}
|
||||||
|
else -> return input
|
||||||
|
}
|
||||||
|
return cipher.doFinal(decodedInput)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val cipher by lazy(LazyThreadSafetyMode.NONE) { Cipher.getInstance("DESede/CBC/PKCS5Padding") }
|
private val cipher by lazy(LazyThreadSafetyMode.NONE) { Cipher.getInstance("DESede/CBC/PKCS5Padding") }
|
||||||
private val key1 by lazy(LazyThreadSafetyMode.NONE) { SecretKeySpec("OW84U8Eerdb99rtsTXWSILDO".toByteArray(), "DESede") }
|
private val key1 by lazy(LazyThreadSafetyMode.NONE) { SecretKeySpec("OW84U8Eerdb99rtsTXWSILDO".toByteArray(), "DESede") }
|
||||||
private val key2 by lazy(LazyThreadSafetyMode.NONE) { SecretKeySpec("OW84U8Eerdb99rtsTXWSILEC".toByteArray(), "DESede") }
|
private val key2 by lazy(LazyThreadSafetyMode.NONE) { SecretKeySpec("ys6n2GvmgEyB3rELDX1gaTBf".toByteArray(), "DESede") }
|
||||||
private val iv by lazy(LazyThreadSafetyMode.NONE) { IvParameterSpec("SK8bncVu".toByteArray()) }
|
private val iv by lazy(LazyThreadSafetyMode.NONE) { IvParameterSpec("SK8bncVu".toByteArray()) }
|
||||||
|
private val iv2 by lazy(LazyThreadSafetyMode.NONE) { IvParameterSpec("2pnB3NI2".toByteArray()) }
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
|
|||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Single
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
@ -49,13 +48,14 @@ open class MCCMS(
|
|||||||
.add("Referer", baseUrl)
|
.add("Referer", baseUrl)
|
||||||
|
|
||||||
protected open fun SManga.cleanup(): SManga = this
|
protected open fun SManga.cleanup(): SManga = this
|
||||||
|
protected open fun MangaDto.prepare(): MangaDto = this
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request =
|
override fun popularMangaRequest(page: Int): Request =
|
||||||
GET("$baseUrl/api/data/comic?page=$page&size=$PAGE_SIZE&order=hits", headers)
|
GET("$baseUrl/api/data/comic?page=$page&size=$PAGE_SIZE&order=hits", headers)
|
||||||
|
|
||||||
override fun popularMangaParse(response: Response): MangasPage {
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
val list: List<MangaDto> = response.parseAs()
|
val list: List<MangaDto> = response.parseAs()
|
||||||
return MangasPage(list.map { it.toSManga().cleanup() }, list.size >= PAGE_SIZE)
|
return MangasPage(list.map { it.prepare().toSManga().cleanup() }, list.size >= PAGE_SIZE)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int): Request =
|
override fun latestUpdatesRequest(page: Int): Request =
|
||||||
@ -91,14 +91,14 @@ open class MCCMS(
|
|||||||
.toString()
|
.toString()
|
||||||
return client.newCall(GET(url, headers))
|
return client.newCall(GET(url, headers))
|
||||||
.asObservableSuccess().map { response ->
|
.asObservableSuccess().map { response ->
|
||||||
val list: List<MangaDto> = response.parseAs()
|
val list = response.parseAs<List<MangaDto>>().map { it.prepare() }
|
||||||
list.find { it.url == manga.url }!!.toSManga().cleanup()
|
list.find { it.url == manga.url }!!.toSManga().cleanup()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mangaDetailsParse(response: Response) = throw UnsupportedOperationException("Not used.")
|
override fun mangaDetailsParse(response: Response): SManga = throw UnsupportedOperationException("Not used.")
|
||||||
|
|
||||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> = Single.create<List<SChapter>> { subscriber ->
|
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> = Observable.fromCallable {
|
||||||
val id = getMangaId(manga.url)
|
val id = getMangaId(manga.url)
|
||||||
val dataResponse = client.newCall(GET("$baseUrl/api/data/chapter?mid=$id", headers)).execute()
|
val dataResponse = client.newCall(GET("$baseUrl/api/data/chapter?mid=$id", headers)).execute()
|
||||||
val dataList: List<ChapterDataDto> = dataResponse.parseAs() // unordered
|
val dataList: List<ChapterDataDto> = dataResponse.parseAs() // unordered
|
||||||
@ -107,12 +107,12 @@ open class MCCMS(
|
|||||||
val response = client.newCall(GET("$baseUrl/api/comic/chapter?mid=$id", headers)).execute()
|
val response = client.newCall(GET("$baseUrl/api/comic/chapter?mid=$id", headers)).execute()
|
||||||
val list: List<ChapterDto> = response.parseAs()
|
val list: List<ChapterDto> = response.parseAs()
|
||||||
val result = list.map { it.toSChapter(date = dateMap[it.id.toInt()] ?: 0) }.asReversed()
|
val result = list.map { it.toSChapter(date = dateMap[it.id.toInt()] ?: 0) }.asReversed()
|
||||||
subscriber.onSuccess(result)
|
result
|
||||||
}.toObservable()
|
}
|
||||||
|
|
||||||
protected open fun getMangaId(url: String) = url.substringAfterLast('/')
|
protected open fun getMangaId(url: String) = url.substringAfterLast('/')
|
||||||
|
|
||||||
override fun chapterListParse(response: Response) = throw UnsupportedOperationException("Not used.")
|
override fun chapterListParse(response: Response): List<SChapter> = throw UnsupportedOperationException("Not used.")
|
||||||
|
|
||||||
override fun pageListRequest(chapter: SChapter): Request =
|
override fun pageListRequest(chapter: SChapter): Request =
|
||||||
GET(baseUrl + chapter.url, pcHeaders)
|
GET(baseUrl + chapter.url, pcHeaders)
|
||||||
|
@ -9,7 +9,8 @@ import java.util.Locale
|
|||||||
internal const val PAGE_SIZE = 30
|
internal const val PAGE_SIZE = 30
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class MangaDto(
|
data class MangaDto(
|
||||||
|
val id: String,
|
||||||
private val name: String,
|
private val name: String,
|
||||||
private val pic: String,
|
private val pic: String,
|
||||||
private val serialize: String,
|
private val serialize: String,
|
||||||
@ -28,8 +29,8 @@ class MangaDto(
|
|||||||
val date = dateFormat.parse(addtime)?.time ?: 0
|
val date = dateFormat.parse(addtime)?.time ?: 0
|
||||||
val isUpdating = System.currentTimeMillis() - date <= 30L * 24 * 3600 * 1000 // a month
|
val isUpdating = System.currentTimeMillis() - date <= 30L * 24 * 3600 * 1000 // a month
|
||||||
status = when {
|
status = when {
|
||||||
serialize.startsWith('连') || isUpdating -> SManga.ONGOING
|
'连' in serialize || isUpdating -> SManga.ONGOING
|
||||||
serialize.startsWith('完') -> SManga.COMPLETED
|
'完' in serialize -> SManga.COMPLETED
|
||||||
else -> SManga.UNKNOWN
|
else -> SManga.UNKNOWN
|
||||||
}
|
}
|
||||||
thumbnail_url = pic
|
thumbnail_url = pic
|
||||||
|
@ -6,24 +6,8 @@ import generator.ThemeSourceGenerator
|
|||||||
class MCCMSGenerator : ThemeSourceGenerator {
|
class MCCMSGenerator : ThemeSourceGenerator {
|
||||||
override val themeClass = "MCCMS"
|
override val themeClass = "MCCMS"
|
||||||
override val themePkg = "mccms"
|
override val themePkg = "mccms"
|
||||||
override val baseVersionCode = 5
|
override val baseVersionCode = 6
|
||||||
override val sources = listOf(
|
override val sources = listOf(
|
||||||
SingleLang(
|
|
||||||
name = "Haoman6",
|
|
||||||
baseUrl = "https://www.haoman6.com",
|
|
||||||
lang = "zh",
|
|
||||||
className = "Haoman6",
|
|
||||||
sourceName = "好漫6",
|
|
||||||
overrideVersionCode = 3,
|
|
||||||
),
|
|
||||||
SingleLang( // previously: app2.haoman6.com, app2.haomanwu.com
|
|
||||||
name = "Haomanwu",
|
|
||||||
baseUrl = "https://move.bookcomic.org",
|
|
||||||
lang = "zh",
|
|
||||||
className = "Haomanwu",
|
|
||||||
sourceName = "好漫屋",
|
|
||||||
overrideVersionCode = 3,
|
|
||||||
),
|
|
||||||
SingleLang( // same as: www.haoman6.cc
|
SingleLang( // same as: www.haoman6.cc
|
||||||
name = "Haoman6 (g-lens)",
|
name = "Haoman6 (g-lens)",
|
||||||
baseUrl = "https://www.g-lens.com",
|
baseUrl = "https://www.g-lens.com",
|
||||||
@ -32,58 +16,31 @@ class MCCMSGenerator : ThemeSourceGenerator {
|
|||||||
sourceName = "好漫6 (g-lens)",
|
sourceName = "好漫6 (g-lens)",
|
||||||
overrideVersionCode = 0,
|
overrideVersionCode = 0,
|
||||||
),
|
),
|
||||||
SingleLang( // same as: www.haoman8.com
|
SingleLang( // same as: caiji.haoman8.com
|
||||||
name = "Haoman8",
|
name = "Haoman8",
|
||||||
baseUrl = "https://caiji.haoman8.com",
|
baseUrl = "https://www.haoman8.com",
|
||||||
lang = "zh",
|
lang = "zh",
|
||||||
className = "Haoman8",
|
className = "Haoman8",
|
||||||
sourceName = "好漫8",
|
sourceName = "好漫8",
|
||||||
overrideVersionCode = 0,
|
overrideVersionCode = 0,
|
||||||
),
|
),
|
||||||
SingleLang(
|
SingleLang(
|
||||||
name = "Kuaikuai Manhua",
|
name = "Kuaikuai Manhua 3",
|
||||||
baseUrl = "https://mobile.manhuaorg.com",
|
baseUrl = "https://mobile3.manhuaorg.com",
|
||||||
lang = "zh",
|
lang = "zh",
|
||||||
className = "Kuaikuai",
|
className = "Kuaikuai3",
|
||||||
sourceName = "快快漫画",
|
sourceName = "快快漫画3",
|
||||||
overrideVersionCode = 0,
|
overrideVersionCode = 0,
|
||||||
),
|
),
|
||||||
SingleLang(
|
SingleLang(
|
||||||
name = "bz Manhua",
|
name = "Manhuawu",
|
||||||
baseUrl = "https://www2.pupumanhua.com",
|
baseUrl = "https://www.mhua5.com",
|
||||||
lang = "zh",
|
lang = "zh",
|
||||||
className = "bzManhua",
|
className = "Manhuawu",
|
||||||
sourceName = "包子漫画搬运",
|
sourceName = "漫画屋",
|
||||||
overrideVersionCode = 0,
|
overrideVersionCode = 0,
|
||||||
),
|
),
|
||||||
// The following sources are from https://www.yy123.cyou/ and are configured to use MCCMSNsfw
|
// The following sources are from https://www.yy123.cyou/ and are configured to use MCCMSNsfw
|
||||||
SingleLang( // 103=寄宿日记, same as: www.hanman.top (different URL format)
|
|
||||||
name = "Damao Manhua",
|
|
||||||
baseUrl = "https://www.hanman.cyou",
|
|
||||||
lang = "zh",
|
|
||||||
isNsfw = true,
|
|
||||||
className = "DamaoManhua",
|
|
||||||
sourceName = "大猫漫画",
|
|
||||||
overrideVersionCode = 0,
|
|
||||||
),
|
|
||||||
SingleLang( // 103=诡秘的姐妹
|
|
||||||
name = "Heihei Manhua",
|
|
||||||
baseUrl = "https://www.hhmh.cyou",
|
|
||||||
lang = "zh",
|
|
||||||
isNsfw = true,
|
|
||||||
className = "HHMH",
|
|
||||||
sourceName = "嘿嘿漫画",
|
|
||||||
overrideVersionCode = 0,
|
|
||||||
),
|
|
||||||
SingleLang( // 103=望月仙女傳說, same as: www.hanman.men
|
|
||||||
name = "Tudou Manhua",
|
|
||||||
baseUrl = "https://www.ptcomic.com",
|
|
||||||
lang = "zh",
|
|
||||||
isNsfw = true,
|
|
||||||
className = "PtComic",
|
|
||||||
sourceName = "土豆漫画",
|
|
||||||
overrideVersionCode = 0,
|
|
||||||
),
|
|
||||||
SingleLang( // 103=校园梦精记, same as: www.hmanwang.com, www.quanman8.com, www.lmmh.cc, www.xinmanba.com
|
SingleLang( // 103=校园梦精记, same as: www.hmanwang.com, www.quanman8.com, www.lmmh.cc, www.xinmanba.com
|
||||||
name = "Dida Manhua",
|
name = "Dida Manhua",
|
||||||
baseUrl = "https://www.didamanhua.com",
|
baseUrl = "https://www.didamanhua.com",
|
||||||
|
@ -21,7 +21,7 @@ open class MCCMSWeb(
|
|||||||
hasCategoryPage: Boolean = true,
|
hasCategoryPage: Boolean = true,
|
||||||
) : MCCMS(name, baseUrl, lang, hasCategoryPage) {
|
) : MCCMS(name, baseUrl, lang, hasCategoryPage) {
|
||||||
|
|
||||||
fun parseListing(document: Document): MangasPage {
|
protected open fun parseListing(document: Document): MangasPage {
|
||||||
val mangas = document.select(Evaluator.Class("common-comic-item")).map {
|
val mangas = document.select(Evaluator.Class("common-comic-item")).map {
|
||||||
SManga.create().apply {
|
SManga.create().apply {
|
||||||
val titleElement = it.selectFirst(Evaluator.Class("comic__title"))!!.child(0)
|
val titleElement = it.selectFirst(Evaluator.Class("comic__title"))!!.child(0)
|
||||||
@ -84,6 +84,12 @@ open class MCCMSWeb(
|
|||||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||||
if (manga.url == "/index.php/search") return Observable.just(manga)
|
if (manga.url == "/index.php/search") return Observable.just(manga)
|
||||||
return client.newCall(GET(baseUrl + manga.url, pcHeaders)).asObservableSuccess().map { response ->
|
return client.newCall(GET(baseUrl + manga.url, pcHeaders)).asObservableSuccess().map { response ->
|
||||||
|
mangaDetailsParse(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(response: Response): SManga {
|
||||||
|
return run {
|
||||||
SManga.create().apply {
|
SManga.create().apply {
|
||||||
val document = response.asJsoup().selectFirst(Evaluator.Class("de-info__box"))!!
|
val document = response.asJsoup().selectFirst(Evaluator.Class("de-info__box"))!!
|
||||||
title = document.selectFirst(Evaluator.Class("comic-title"))!!.ownText()
|
title = document.selectFirst(Evaluator.Class("comic-title"))!!.ownText()
|
||||||
@ -98,6 +104,12 @@ open class MCCMSWeb(
|
|||||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||||
if (manga.url == "/index.php/search") return Observable.just(emptyList())
|
if (manga.url == "/index.php/search") return Observable.just(emptyList())
|
||||||
return client.newCall(GET(baseUrl + manga.url, pcHeaders)).asObservableSuccess().map { response ->
|
return client.newCall(GET(baseUrl + manga.url, pcHeaders)).asObservableSuccess().map { response ->
|
||||||
|
chapterListParse(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
|
return run {
|
||||||
response.asJsoup().selectFirst(Evaluator.Class("chapter__list-box"))!!.children().map {
|
response.asJsoup().selectFirst(Evaluator.Class("chapter__list-box"))!!.children().map {
|
||||||
val link = it.child(0)
|
val link = it.child(0)
|
||||||
SChapter.create().apply {
|
SChapter.create().apply {
|
||||||
|