Fixed Kuaikanmanhua extension (#6783)

* Kkmh (#1)

* Fixed fetching popular and latest manga for KKMH

* Changed Searching and filtering to use the API for KKMH

* Chapter lists are now properly called and now show upload date

* Pages are properly called. Code cleanup. Extension properly works

Co-authored-by: Raymond Wang <rywang@email.wm.edu>

* Converted to HttpSource. More code cleanup.

Co-authored-by: Raymond Wang <rywang@email.wm.edu>
This commit is contained in:
ShadesOfRay 2021-05-01 17:58:15 -04:00 committed by GitHub
parent 5e7c11371b
commit 16c4320b7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 91 additions and 103 deletions

View File

@ -5,7 +5,7 @@ ext {
extName = 'Kuaikanmanhua' extName = 'Kuaikanmanhua'
pkgNameSuffix = 'zh.kuaikanmanhua' pkgNameSuffix = 'zh.kuaikanmanhua'
extClass = '.Kuaikanmanhua' extClass = '.Kuaikanmanhua'
extVersionCode = 2 extVersionCode = 3
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -1,9 +1,5 @@
package eu.kanade.tachiyomi.extension.zh.kuaikanmanhua package eu.kanade.tachiyomi.extension.zh.kuaikanmanhua
import com.github.salomonbrys.kotson.fromJson
import com.google.gson.Gson
import com.google.gson.JsonArray
import com.google.gson.JsonObject
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
@ -11,15 +7,15 @@ 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
import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.json.JSONArray
import org.jsoup.nodes.Element import org.json.JSONObject
import rx.Observable
class Kuaikanmanhua : ParsedHttpSource() { class Kuaikanmanhua : HttpSource() {
override val name = "Kuaikanmanhua" override val name = "Kuaikanmanhua"
@ -31,65 +27,50 @@ class Kuaikanmanhua : ParsedHttpSource() {
override val client: OkHttpClient = network.cloudflareClient override val client: OkHttpClient = network.cloudflareClient
private val gson = Gson() private val apiUrl = "https://api.kkmh.com"
// Popular // Popular
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/tag/0?state=1&page=$page", headers) return GET("$apiUrl/v1/topic_new/lists/get_by_tag?tag=0&since=${(page - 1) * 10}", headers)
} }
override fun popularMangaParse(response: Response): MangasPage { override fun popularMangaParse(response: Response): MangasPage {
return parseMangaDocument(response.asJsoup()) val body = response.body!!.string()
val jsonList = JSONObject(body).getJSONObject("data").getJSONArray("topics")
return parseMangaJsonArray(jsonList)
} }
private fun parseMangaDocument(document: Document): MangasPage { private fun parseMangaJsonArray(jsonList: JSONArray, isSearch: Boolean = false): MangasPage {
val mangas = mutableListOf<SManga>() val mangaList = mutableListOf<SManga>()
gson.fromJson<JsonArray>( for (i in 0 until jsonList.length()) {
document.select("script:containsData(datalist)").first().data() val obj = jsonList.getJSONObject(i)
.substringAfter("dataList:").substringBefore("}],error") mangaList.add(
SManga.create().apply {
title = obj.getString("title")
thumbnail_url = obj.getString("vertical_image_url")
url = "/web/topic/" + obj.getInt("id")
}
) )
.forEach { mangas.add(mangaFromJson(it.asJsonObject)) }
return MangasPage(mangas, document.select(popularMangaNextPageSelector()).isNotEmpty())
} }
// KKMH does not have pages when you search
private fun mangaFromJson(jsonObject: JsonObject): SManga { return MangasPage(mangaList, mangaList.size > 9 && !isSearch)
val manga = SManga.create()
manga.url = "/web/topic/" + jsonObject["id"].asString
manga.title = jsonObject["title"].asString
manga.thumbnail_url = jsonObject["cover_image_url"].asString
return manga
} }
override fun popularMangaSelector() = throw UnsupportedOperationException("Not used")
override fun popularMangaFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used")
override fun popularMangaNextPageSelector() = "li:not(.disabled) b.right"
// Latest // Latest
override fun latestUpdatesRequest(page: Int): Request { override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/tag/19?state=1&page=$page", headers) return GET("$apiUrl/v1/topic_new/lists/get_by_tag?tag=19&since=${(page - 1) * 10}", headers)
} }
override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response) override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response)
override fun latestUpdatesSelector() = throw UnsupportedOperationException("Not used")
override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException("Not used")
override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("Not used")
// Search // Search
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
return if (query.isNotEmpty()) { return if (query.isNotEmpty()) {
GET("$baseUrl/s/result/$query", headers) GET("$apiUrl/v1/search/topic?q=$query&size=18", headers)
} else { } else {
lateinit var genre: String lateinit var genre: String
lateinit var status: String lateinit var status: String
@ -103,94 +84,97 @@ class Kuaikanmanhua : ParsedHttpSource() {
} }
} }
} }
GET("$baseUrl/tag/$genre?state=$status&page=$page", headers) GET("$apiUrl/v1/search/by_tag?since=${(page - 1) * 10}&tag=$genre&sort=1&query_category=%7B%22update_status%22:$status%7D")
} }
} }
override fun searchMangaParse(response: Response): MangasPage { override fun searchMangaParse(response: Response): MangasPage {
val mangas = mutableListOf<SManga>() val body = response.body!!.string()
val document = response.asJsoup() val jsonObj = JSONObject(body).getJSONObject("data")
if (jsonObj.has("hit")) {
document.select("script:containsData(resultList)").let { return parseMangaJsonArray(jsonObj.getJSONArray("hit"), true)
return if (it.isNotEmpty()) {
// for search by query
gson.fromJson<JsonArray>(
it.first().data()
.substringAfter("resultList:").substringBefore(",noResult")
)
.forEach { result -> mangas.add(searchMangaFromJson(result.asJsonObject)) }
MangasPage(mangas, document.select(searchMangaNextPageSelector()).isNotEmpty())
} else {
// for search by genre, status
parseMangaDocument(document)
}
}
} }
private fun searchMangaFromJson(jsonObject: JsonObject): SManga { return parseMangaJsonArray(jsonObj.getJSONArray("topics"), false)
val manga = SManga.create()
manga.url = jsonObject["url"].asString
manga.title = jsonObject["title"].asString
manga.thumbnail_url = jsonObject["image_url"].asString
return manga
} }
override fun searchMangaSelector() = throw UnsupportedOperationException("Not used")
override fun searchMangaFromElement(element: Element) = throw UnsupportedOperationException("Not used")
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
// Details // Details
override fun mangaDetailsParse(document: Document): SManga { override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
val infoElement = document.select("div.TopicHeader").first() // Convert the stored url to one that works with the api
val newUrl = apiUrl + "/v1/topics/" + manga.url.trimEnd('/').substringAfterLast("/")
val response = client.newCall(GET(newUrl)).execute()
val sManga = mangaDetailsParse(response).apply { initialized = true }
return Observable.just(sManga)
}
override fun mangaDetailsParse(response: Response): SManga {
val data = JSONObject(response.body!!.string()).getJSONObject("data")
val manga = SManga.create() val manga = SManga.create()
manga.title = infoElement.select("h3").first().text() manga.title = data.getString("title")
manga.author = infoElement.select("div.nickname").text() manga.author = data.getJSONObject("user").getString("nickname")
manga.description = infoElement.select("div.detailsBox p").text() manga.description = data.getString("description")
manga.status = data.getInt("update_status_code")
return manga return manga
} }
// Chapters & Pages // Chapters & Pages
override fun chapterListSelector() = "div.TopicItem" override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
val newUrl = apiUrl + "/v1/topics/" + manga.url.trimEnd('/').substringAfterLast("/")
override fun chapterFromElement(element: Element): SChapter { val response = client.newCall(GET(newUrl)).execute()
val chapter = SChapter.create() val chapters = chapterListParse(response)
return Observable.just(chapters)
element.select("div.title a").let {
chapter.url = it.attr("href")
chapter.name = it.text() + if (element.select("i.lockedIcon").isNotEmpty()) { " \uD83D\uDD12" } else { "" }
} }
return chapter
override fun chapterListParse(response: Response): List<SChapter> {
val data = JSONObject(response.body!!.string()).getJSONObject("data")
val chaptersJson = data.getJSONArray("comics")
val chapters = mutableListOf<SChapter>()
for (i in 0 until chaptersJson.length()) {
val obj = chaptersJson.getJSONObject(i)
chapters.add(
SChapter.create().apply {
url = "/v2/comic/" + obj.getString("id")
name = obj.getString("title") +
if (!obj.getBoolean("can_view")) {
" \uD83D\uDD12"
} else {
""
}
date_upload = obj.getLong("created_at") * 1000
}
)
}
return chapters
}
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
val request = client.newCall(pageListRequest(chapter)).execute()
return Observable.just(pageListParse(request))
} }
override fun pageListRequest(chapter: SChapter): Request { override fun pageListRequest(chapter: SChapter): Request {
if (chapter.url == "javascript:void(0);") { if (chapter.name.endsWith("🔒")) {
throw Exception("[此章节为付费内容]") throw Exception("[此章节为付费内容]")
} }
return super.pageListRequest(chapter) return GET(apiUrl + chapter.url)
} }
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(response: Response): List<Page> {
val pages = mutableListOf<Page>() val pages = ArrayList<Page>()
val data = JSONObject(response.body!!.string()).getJSONObject("data")
gson.fromJson<JsonArray>( val pagesJson = data.getJSONArray("images")
document.select("script:containsData(comicImages)").first().data()
.substringAfter("comicImages:").substringBefore("},nextComicInfo")
)
.forEachIndexed { i, json -> pages.add(Page(i, "", json.asJsonObject["url"].asString)) }
for (i in 0 until pagesJson.length()) {
pages.add(Page(i, pagesJson.getString(i), pagesJson.getString(i)))
}
return pages return pages
} }
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
// Filters // Filters
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
@ -199,6 +183,10 @@ class Kuaikanmanhua : ParsedHttpSource() {
GenreFilter() GenreFilter()
) )
override fun imageUrlParse(response: Response): String {
throw Exception("Not used")
}
private class GenreFilter : UriPartFilter( private class GenreFilter : UriPartFilter(
"题材", "题材",
arrayOf( arrayOf(