Merge Qixi Manhua into 6Manhua and update URL (#14516)

* Merge Qixi Manhua into 6Manhua and update URL

* fix NPE
This commit is contained in:
stevenyomi 2022-12-13 11:46:08 +08:00 committed by GitHub
parent 1182b80a0e
commit b9950dd8da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 196 additions and 388 deletions

View File

@ -3,10 +3,10 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'QiXiManhua'
extName = 'QiXiManhua (Deprecated)'
pkgNameSuffix = 'zh.qiximh'
extClass = '.Qiximh'
extVersionCode = 5
extClass = '.QixiStub'
extVersionCode = 6
}
apply from: "$rootDir/common.gradle"

View File

@ -0,0 +1,33 @@
package eu.kanade.tachiyomi.extension.zh.qiximh
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import okhttp3.Response
class QixiStub : HttpSource() {
private val migratePrompt = Exception("请迁移到“6漫画”插件可以在该插件的设置中修改镜像站点")
override val id get() = 418374491144859437
override val name get() = "七夕漫画 (废弃,请使用6漫画)"
override val lang get() = "zh"
override val supportsLatest get() = false
override val baseUrl get() = ""
override fun popularMangaRequest(page: Int) = throw migratePrompt
override fun popularMangaParse(response: Response) = throw migratePrompt
override fun latestUpdatesRequest(page: Int) = throw migratePrompt
override fun latestUpdatesParse(response: Response) = throw migratePrompt
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw migratePrompt
override fun searchMangaParse(response: Response) = throw migratePrompt
override fun mangaDetailsRequest(manga: SManga) = throw migratePrompt
override fun mangaDetailsParse(response: Response) = throw migratePrompt
override fun chapterListRequest(manga: SManga) = throw migratePrompt
override fun chapterListParse(response: Response) = throw migratePrompt
override fun pageListRequest(chapter: SChapter) = throw migratePrompt
override fun pageListParse(response: Response) = throw migratePrompt
override fun imageUrlParse(response: Response) = throw migratePrompt
}

View File

@ -1,314 +0,0 @@
package eu.kanade.tachiyomi.extension.zh.qiximh
import app.cash.quickjs.QuickJs
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
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 kotlinx.serialization.json.Json
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.FormBody
import okhttp3.Request
import okhttp3.Response
import uy.kohesive.injekt.injectLazy
class Qiximh : HttpSource() {
override val lang = "zh"
override val supportsLatest = true
override val name = "七夕漫画"
override val baseUrl = "http://qiximh1.com"
// This is hard limit by API
private val maxPage = 5
private val json: Json by injectLazy()
// Used in Rank API
private enum class RANKTYPE(val rankVal: Int) {
DAILY_HOT(1),
WEEKLY_HOT(2),
MONTHLY_HOT(3),
OVERALL_HOT(4),
LATEST(5),
NEW(6),
}
// Used in Sort API (although it looks like genre)
private enum class SORTTYPE(val sortVal: Int) {
ADVENTURE(1),
ACTION(2),
MAGIC_SCIFI(3),
THRILLER(4),
ROMANCE(5),
SLICE_OF_LIFE(6),
// These are not accurate, hence not included
// HIGH_QUALITY(11),
// ON_GOING(12),
// COMPLETED(13)
}
private open class PairIntFilter(displayName: String, val vals: Array<Pair<String, Int?>>) :
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun getVal() = vals[state].second
}
// Override
private fun FormBody.value(name: String): String {
return (0 until size)
.first { name(it) == name }
.let { value(it) }
}
private fun commonRankDataRequest(page: Int, rankTypeVal: Int): Request {
return POST(
"$baseUrl/rankdata.php",
headers,
FormBody.Builder()
.add("page_num", page.toString())
.add("type", rankTypeVal.toString())
.build()
)
}
private fun commonSortDataRequest(page: Int, sortTypeVal: Int): Request {
return POST(
"$baseUrl/sortdata.php",
headers,
FormBody.Builder()
.add("page_num", page.toString())
.add("type", sortTypeVal.toString())
.build()
)
}
private fun commonDataProcess(origRequest: Request, responseBody: String): MangasPage {
val jsonData = json.parseToJsonElement(responseBody).jsonArray
val mangaArr = jsonData.map {
val targetObj = it.jsonObject
SManga.create().apply {
title = targetObj["name"]!!.jsonPrimitive.content
status = SManga.UNKNOWN
thumbnail_url = targetObj["imgurl"]!!.jsonPrimitive.content
// Extension is wrongly adding the baseURL to the SManga.
// I kept it as it is to avoid user migrations.
url = "$baseUrl/${targetObj["id"]!!.jsonPrimitive.int}/"
}
}
val requestBody = origRequest.body as FormBody
val currentPage: Int = requestBody.value("page_num").toInt()
val hasNextPage = currentPage < maxPage
return MangasPage(mangaArr, hasNextPage)
}
private fun commonRankDataParse(response: Response): MangasPage {
return commonDataProcess(response.request, response.body!!.string())
}
// Popular Manga
override fun popularMangaRequest(page: Int) = commonRankDataRequest(page, RANKTYPE.DAILY_HOT.rankVal)
override fun popularMangaParse(response: Response) = commonRankDataParse(response)
// Latest Updates
override fun latestUpdatesRequest(page: Int) = commonRankDataRequest(page, RANKTYPE.LATEST.rankVal)
override fun latestUpdatesParse(response: Response) = commonRankDataParse(response)
// Search
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
if (query.isNotBlank()) {
return POST(
"$baseUrl/search.php",
headers,
FormBody.Builder()
.add("keyword", query)
.build()
)
} else {
filters.forEach { filter ->
when (filter) {
is RankFilter -> {
val filterVal = filter.getVal()
if (filterVal != null) {
return commonRankDataRequest(page, filterVal)
}
}
is SortFilter -> {
val filterVal = filter.getVal()
if (filterVal != null) {
return commonSortDataRequest(page, filterVal)
}
}
}
}
// Default if no filter set
return commonRankDataRequest(page, RANKTYPE.DAILY_HOT.rankVal)
}
}
override fun searchMangaParse(response: Response): MangasPage {
val responseBody = response.body
?: return MangasPage(emptyList(), false)
val responseString = responseBody.string()
if (responseString.isNotEmpty()) {
if (responseString.startsWith("[")) {
// This is to process filter
return commonDataProcess(response.request, responseString)
} else {
val jsonData = json.parseToJsonElement(responseString).jsonObject
if (jsonData["msg"]!!.jsonPrimitive.content == "success") {
val mangaArr = jsonData["search_data"]!!.jsonArray.map {
val targetObj = it.jsonObject
SManga.create().apply {
title = targetObj["name"]!!.jsonPrimitive.content
thumbnail_url = targetObj["imgs"]!!.jsonPrimitive.content
// Extension is wrongly adding the baseURL to the SManga.
// I kept it as it is to avoid user migrations.
url = "$baseUrl/${targetObj["id"]!!.jsonPrimitive.int}/"
}
}
return MangasPage(mangaArr, false)
}
}
}
// Search does not have pagination
return MangasPage(emptyList(), false)
}
// Filter
private class RankFilter : PairIntFilter(
"排行榜",
arrayOf(
Pair("全部", null),
Pair("日热门榜", RANKTYPE.DAILY_HOT.rankVal),
Pair("周热门榜", RANKTYPE.WEEKLY_HOT.rankVal),
Pair("月热门榜", RANKTYPE.MONTHLY_HOT.rankVal),
Pair("总热门榜", RANKTYPE.OVERALL_HOT.rankVal),
Pair("最近更新", RANKTYPE.LATEST.rankVal),
Pair("新漫入库", RANKTYPE.NEW.rankVal),
)
)
private class SortFilter : PairIntFilter(
"分类",
arrayOf(
Pair("全部", null),
Pair("冒险热血", SORTTYPE.ADVENTURE.sortVal),
Pair("武侠格斗", SORTTYPE.ACTION.sortVal),
Pair("玄幻科幻", SORTTYPE.MAGIC_SCIFI.sortVal),
Pair("侦探推理", SORTTYPE.THRILLER.sortVal),
Pair("耽美爱情", SORTTYPE.ROMANCE.sortVal),
Pair("生活漫画", SORTTYPE.SLICE_OF_LIFE.sortVal),
)
)
override fun getFilterList() = FilterList(
Filter.Header("注意: 文本搜索,排行榜和分类筛选,不可同时使用"),
Filter.Separator(),
RankFilter(),
SortFilter()
)
// Manga Details
override fun mangaDetailsRequest(manga: SManga) = GET(manga.url, headers)
override fun mangaDetailsParse(response: Response): SManga {
val document = response.asJsoup()
return SManga.create().apply {
title = document.select("h1.name").text()
thumbnail_url = document.select("div.comic_cover").attr("data-original")
author = document.select(".author_name").text()
description = arrayOf(
document.select("span.looking_chapter").text(),
document.select(".bold_fortime").text(),
document.select(".details").first().ownText(),
).filter(String::isNotBlank).joinToString("\n")
genre = arrayOf(
document.select(".comic_hot span:last-child").text(),
*(document.select(".tags.tags_last").text().split("|").toTypedArray())
).filter(String::isNotBlank).joinToString()
}
}
// Chapter
override fun chapterListRequest(manga: SManga) = GET(manga.url, headers)
override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup()
// API does not allow retrieve full chapter list, hence the need to parse
// the chapters from both HTML and API
val chapterList = document.select(".catalog_list.row_catalog_list a")
.map {
SChapter.create().apply {
name = it.text()
url = "$baseUrl${it.attr("href")}"
}
}
.toMutableList()
val mangaUrl = response.request.url.toString()
val request = POST(
"$baseUrl/bookchapter/",
headers,
FormBody.Builder()
.add("id", mangaUrl.split("/").toTypedArray().filter(String::isNotBlank).last())
.add("id2", "1")
.build()
)
val inlineResponse = client.newCall(request).execute()
val jsonData = json.parseToJsonElement(inlineResponse.body!!.string()).jsonArray
chapterList += jsonData.map {
val targetObj = it.jsonObject
SChapter.create().apply {
name = targetObj["chaptername"]!!.jsonPrimitive.content
url = "$mangaUrl${targetObj["chapterid"]!!.jsonPrimitive.int}.html"
}
}
return chapterList
}
// Page
override fun pageListRequest(chapter: SChapter) = GET(chapter.url, headers)
override fun pageListParse(response: Response): List<Page> {
val document = response.asJsoup()
// Special thanks to author who created Mangahere.kt
val script = document.select("script:containsData(function(p,a,c,k,e,d))").html().removePrefix("eval")
val deobfuscatedScript = QuickJs.create().use { it.evaluate(script).toString() }
val imageUrlListString = deobfuscatedScript.substringAfter("newImgs=").trim()
val imageUrlList = json.parseToJsonElement(imageUrlListString).jsonArray.map { it.jsonPrimitive.content }
return imageUrlList.mapIndexed { index, s -> Page(index, imageUrl = s) }
}
// Unused
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("Unused")
}

View File

@ -3,10 +3,10 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = '6Manhua'
extName = '6Manhua / Qixi Manhua'
pkgNameSuffix = 'zh.sixmh'
extClass = '.SixMH'
extVersionCode = 4
extVersionCode = 5
}
apply from: "$rootDir/common.gradle"

View File

@ -0,0 +1,32 @@
package eu.kanade.tachiyomi.extension.zh.sixmh
import eu.kanade.tachiyomi.network.POST
import okhttp3.FormBody
import okhttp3.Headers
import okhttp3.Request
/** Documentation of unused APIs originally used in `zh.qiximh`. */
object Api {
fun getRankRequest(baseUrl: String, headers: Headers, page: Int, type: Int) =
getListingRequest("$baseUrl/rankdata.php", headers, page, type)
fun getSortRequest(baseUrl: String, headers: Headers, page: Int, type: Int) =
getListingRequest("$baseUrl/sortdata.php", headers, page, type)
/** @param page 1-5. Website allows 1-10 and contains more items per page. */
fun getListingRequest(url: String, headers: Headers, page: Int, type: Int): Request {
val body = FormBody.Builder()
.add("page_num", page.toString())
.add("type", type.toString())
.build()
return POST(url, headers, body)
}
fun getSearchRequest(baseUrl: String, headers: Headers, query: String): Request {
val body = FormBody.Builder()
.add("keyword", query)
.build()
return POST("$baseUrl/search.php", headers, body)
}
}

View File

@ -0,0 +1,18 @@
package eu.kanade.tachiyomi.extension.zh.sixmh
import eu.kanade.tachiyomi.source.model.SChapter
import kotlinx.serialization.Serializable
@Serializable
class QixiChapterDto(private val id: String, private val name: String) {
fun toSChapter(path: String) = SChapter.create().apply {
url = "$path$id.html"
name = this@QixiChapterDto.name
}
}
@Serializable
class QixiDataDto(val list: List<QixiChapterDto>)
@Serializable
class QixiResponseDto(val data: QixiDataDto)

View File

@ -1,90 +1,112 @@
package eu.kanade.tachiyomi.extension.zh.sixmh
import android.app.Application
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.AppInfo
import eu.kanade.tachiyomi.lib.unpacker.Unpacker
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.ConfigurableSource
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.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import okhttp3.FormBody
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 rx.Observable
import rx.Single
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Locale
class SixMH : ParsedHttpSource() {
class SixMH : HttpSource(), ConfigurableSource {
override val name = "6漫画"
override val lang = "zh"
override val baseUrl = PC_URL
override val supportsLatest = true
private val mirrorIndex: Int
private val pcUrl: String
override val baseUrl: String
init {
val preferences = Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
val mirrors = MIRRORS
val index = preferences.getString(MIRROR_PREF, "0")!!.toInt().coerceAtMost(mirrors.size - 1)
val domain = mirrors[index]
mirrorIndex = index
pcUrl = "http://www.$domain"
baseUrl = "http://$domain"
}
private val json: Json by injectLazy()
override val client = network.client.newBuilder()
.rateLimit(2)
.build()
override fun popularMangaRequest(page: Int) = GET("$PC_URL/rank/1-$page.html", headers)
override fun popularMangaNextPageSelector() = "li.thisclass:not(:last-of-type)"
override fun popularMangaSelector() = "div.cy_list_mh > ul"
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
with(element.child(1).child(0)) {
url = attr("href")
title = ownText()
override fun popularMangaRequest(page: Int) = GET("$pcUrl/rank/1-$page.html", headers)
override fun popularMangaParse(response: Response): MangasPage {
val document = response.asJsoup()
val imgSelector = Evaluator.Tag("img")
val items = document.selectFirst(Evaluator.Class("cy_list_mh")).children().map {
SManga.create().apply {
val link = it.child(1).child(0)
url = link.attr("href")
title = link.ownText()
thumbnail_url = it.selectFirst(imgSelector).attr("src")
}
}
thumbnail_url = element.selectFirst(Evaluator.Tag("img")).attr("src")
val hasNextPage = document.selectFirst(Evaluator.Class("thisclass"))?.nextElementSibling() != null
return MangasPage(items, hasNextPage)
}
override fun latestUpdatesRequest(page: Int) = GET("$PC_URL/rank/5-$page.html", headers)
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
override fun latestUpdatesSelector() = popularMangaSelector()
override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element)
override fun latestUpdatesRequest(page: Int) = GET("$pcUrl/rank/5-$page.html", headers)
override fun latestUpdatesParse(response: Response) = popularMangaParse(response)
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
override fun searchMangaSelector() = popularMangaSelector()
override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
if (query.isNotBlank()) {
return GET("$PC_URL/search.php?keyword=$query", headers)
val url = pcUrl.toHttpUrl().newBuilder()
.addEncodedPathSegment("search.php")
.addQueryParameter("keyword", query)
.toString()
return GET(url, headers)
} else {
filters.filterIsInstance<PageFilter>().firstOrNull()?.run {
return GET("$PC_URL$path$page.html", headers)
return GET("$pcUrl$path$page.html", headers)
}
return popularMangaRequest(page)
}
}
private fun pcRequest(manga: SManga) = GET("$PC_URL${manga.url}", headers)
private fun mobileRequest(manga: SManga) = GET("$MOBILE_URL${manga.url}", headers)
override fun searchMangaParse(response: Response) = popularMangaParse(response)
// for WebView
override fun mangaDetailsRequest(manga: SManga) = mobileRequest(manga)
override fun mangaDetailsParse(document: Document) = throw UnsupportedOperationException("Not used.")
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return client.newCall(GET(pcUrl + manga.url, headers))
.asObservableSuccess().map(::mangaDetailsParse)
}
// fetchMangaDetails fetches and parses PC page first, then mobile page
// fetchChapterList does in the opposite order, to make use of transparent cache
// in this way, the latter requests will be responded with 304 Not Modified (in most cases)
override fun fetchMangaDetails(manga: SManga): Observable<SManga> = Single.create<SManga> {
val document = client.newCall(pcRequest(manga)).execute().asJsoup()
override fun mangaDetailsParse(response: Response): SManga {
val document = response.asJsoup()
val result = SManga.create().apply {
val box = document.selectFirst(Evaluator.Id("intro_l"))
val box = document.selectFirst(Evaluator.Class("cy_info"))
val details = box.getElementsByTag("span")
author = details[0].text().removePrefix("作者:")
status = when (details[1].child(0).ownText()) {
status = when (details[1].text().removePrefix("状态:").trimStart()) {
"连载中" -> SManga.ONGOING
"已完结" -> SManga.COMPLETED
else -> SManga.UNKNOWN
@ -95,54 +117,57 @@ class SixMH : ParsedHttpSource() {
.filterTo(this) { it.isNotEmpty() }
}.joinToString()
description = box.selectFirst(Evaluator.Tag("p")).ownText()
thumbnail_url = box.selectFirst(Evaluator.Tag("img")).attr("src")
thumbnail_url = box.selectFirst(Evaluator.Tag("img")).run {
attr("data-src").ifEmpty { attr("src") }
}
}
val mobileDocument = client.newCall(mobileRequest(manga)).execute().asJsoup()
val details = mobileDocument.selectFirst(Evaluator.Class("author"))
.ownText().trim().split(Regex(""" +"""))
if (details.size >= 3) {
result.description = details[2] + '\n' + result.description
}
it.onSuccess(result)
}.toObservable()
return result
}
override fun chapterListSelector() = throw UnsupportedOperationException("Not used.")
override fun chapterFromElement(element: Element) = throw UnsupportedOperationException("Not used.")
override fun chapterListRequest(manga: SManga) = GET(pcUrl + manga.url, headers)
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> = Single.create<List<SChapter>> {
val document = client.newCall(mobileRequest(manga)).execute().asJsoup()
val list = mutableListOf<SChapter>()
override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup()
val tab = document.selectFirst(Evaluator.Class("cartoon-directory")).children()
if (tab.size >= 2) {
tab[1].children().mapTo(list) { element ->
val list = document.selectFirst(Evaluator.Class("cy_plist"))
.child(0).children().map {
val element = it.child(0)
SChapter.create().apply {
url = element.attr("href")
name = element.text()
}
}
if (tab.size >= 3) {
val element = tab[2]
val path = manga.url
as ArrayList
if (mirrorIndex == 0) { // 6Manhua
document.selectFirst(Evaluator.Id("zhankai"))?.let { element ->
val path = '/' + response.request.url.pathSegments[0] + '/'
val body = FormBody.Builder().apply {
addEncoded("id", element.attr("data-id"))
addEncoded("id2", element.attr("data-vid"))
}.build()
client.newCall(POST("$MOBILE_URL/bookchapter/", headers, body)).execute()
client.newCall(POST("$pcUrl/bookchapter/", headers, body)).execute()
.parseAs<List<ChapterDto>>().mapTo(list) { it.toSChapter(path) }
}
} else { // Qixi Manhua
if (document.selectFirst(Evaluator.Class("morechp")) != null) {
val id = response.request.url.pathSegments[0]
val path = "/$id/"
val body = FormBody.Builder().addEncoded("id", id).build()
client.newCall(POST("$pcUrl/chapterlist/", headers, body)).execute()
.parseAs<QixiResponseDto>().data.list.mapTo(list) { it.toSChapter(path) }
}
}
if (isNewDateLogic && list.isNotEmpty()) {
val pcDocument = client.newCall(pcRequest(manga)).execute().asJsoup()
pcDocument.selectFirst(".cy_zhangjie_top font")?.run {
document.selectFirst(".cy_zhangjie_top font")?.run {
list[0].date_upload = dateFormat.parse(ownText())?.time ?: 0
}
}
it.onSuccess(list)
}.toObservable()
return list
}
override fun pageListRequest(chapter: SChapter) = GET("$MOBILE_URL${chapter.url}", headers)
override fun pageListRequest(chapter: SChapter) = GET(baseUrl + chapter.url, headers)
override fun pageListParse(response: Response): List<Page> {
val result = Unpacker.unpack(response.body!!.string(), "[", "]")
@ -152,8 +177,7 @@ class SixMH : ParsedHttpSource() {
return result.mapIndexed { i, url -> Page(i, imageUrl = url) }
}
override fun pageListParse(document: Document) = throw UnsupportedOperationException("Not used.")
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used.")
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("Not used.")
private inline fun <reified T> Response.parseAs(): T = use {
json.decodeFromStream(body!!.byteStream())
@ -161,11 +185,26 @@ class SixMH : ParsedHttpSource() {
override fun getFilterList() = FilterList(listOf(PageFilter()))
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
val names = MIRROR_NAMES
key = MIRROR_PREF
title = "镜像站点(重启生效)"
summary = "%s"
entries = names
entryValues = Array(names.size, Int::toString)
setDefaultValue("0")
}.let(screen::addPreference)
}
companion object {
// redirect URL: http://www.6mh9.com/
private const val DOMAIN = "sixmh7.com"
private const val PC_URL = "http://www.$DOMAIN"
private const val MOBILE_URL = "http://m.$DOMAIN"
const val MIRROR_PREF = "MIRROR"
/** Note: mirror index affects [chapterListParse] */
val MIRRORS get() = arrayOf("6mh66.com", "qiximh3.com")
val MIRROR_NAMES get() = arrayOf("6漫画", "七夕漫画")
private val isNewDateLogic = AppInfo.getVersionCode() >= 81
private val dateFormat by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) }

View File

@ -5,7 +5,7 @@ import eu.kanade.tachiyomi.source.model.SChapter
import kotlinx.serialization.Serializable
@Serializable
class ChapterDto(val chapterid: String, val chaptername: String) {
class ChapterDto(private val chapterid: String, private val chaptername: String) {
fun toSChapter(path: String) = SChapter.create().apply {
url = "$path$chapterid.html"
name = chaptername