Added Qiximh (#4688)
This commit is contained in:
parent
89cea3e023
commit
308cb375c5
|
@ -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 |
|
@ -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")
|
||||
}
|
Loading…
Reference in New Issue