Added Qiximh (#4688)

This commit is contained in:
Ming Kit 2020-10-23 18:35:23 +08:00 committed by GitHub
parent 89cea3e023
commit 308cb375c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 318 additions and 0 deletions

View File

@ -0,0 +1,12 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
ext {
extName = 'QiXiManhua'
pkgNameSuffix = 'zh.qiximh'
extClass = '.Qiximh'
extVersionCode = 1
libVersion = '1.2'
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

View File

@ -0,0 +1,306 @@
package eu.kanade.tachiyomi.extension.zh.qiximh
import com.squareup.duktape.Duktape
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 okhttp3.FormBody
import okhttp3.Request
import okhttp3.Response
import org.json.JSONArray
import org.json.JSONObject
class Qiximh : HttpSource() {
override val lang = "zh"
override val supportsLatest = true
override val name = "七夕漫画"
override val baseUrl = "http://qiximh.com"
// This is hard limit by API
val maxPage = 5
// 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 = JSONArray(responseBody)
val mangaArr = mutableListOf<SManga>()
for (i in 0 until jsonData.length()) {
val targetObj = jsonData.getJSONObject(i)
mangaArr.add(
SManga.create().apply {
title = targetObj.get("name") as String
status = SManga.UNKNOWN
thumbnail_url = targetObj.get("imgurl") as String
url = "$baseUrl/${targetObj.get("id")}/"
}
)
}
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()
val mangaArr = mutableListOf<SManga>()
if (responseBody != null) {
val responseString = responseBody.string()
if (!responseString.isNullOrEmpty()) {
if (responseString.startsWith("[")) {
// This is to process filter
return commonDataProcess(response.request(), responseString)
} else {
val jsonData = JSONObject(responseString)
if (jsonData.get("msg") == "success") {
val jsonArr = jsonData.getJSONArray("search_data")
for (i in 0 until jsonArr.length()) {
val targetObj = jsonArr.getJSONObject(i)
mangaArr.add(
SManga.create().apply {
title = targetObj.get("name") as String
thumbnail_url = targetObj.get("imgs") as String
url = "$baseUrl/${targetObj.get("id")}/"
}
)
}
}
}
}
}
// Search does not have pagination
return MangasPage(mangaArr, 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()
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 htmlChapters = document.select(".catalog_list.row_catalog_list a").map {
SChapter.create().apply {
name = it.text()
url = "$baseUrl${it.attr("href")}"
}
}
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 = JSONArray(inlineResponse.body()!!.string())
val chapterArr = mutableListOf<SChapter>()
chapterArr.addAll(htmlChapters)
for (i in 0 until jsonData.length()) {
val targetObj = jsonData.getJSONObject(i)
chapterArr.add(
SChapter.create().apply {
name = targetObj.get("chaptername") as String
url = "$mangaUrl${targetObj.get("chapterid")}.html"
}
)
}
return chapterArr
}
// 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 duktape = Duktape.create()
val script = document.select("script:containsData(function(p,a,c,k,e,d))").html().removePrefix("eval")
val deobfuscatedScript = duktape.evaluate(script).toString()
val urls = deobfuscatedScript.substringAfter("newImgs=[\"").substringBefore("\"]").split("\",\"")
duktape.close()
return urls.mapIndexed { index, s -> Page(index, "", s) }
}
// Unused
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("Unused")
}