Comick: hid refactor and add ext-lib 1.4 functions (#15487)

* comick: Refactor the hid changes and add ext-lib 1.4 methods

* bump

* Comick: migrate slugs to hid

* apply suggested changes
This commit is contained in:
mobi2002 2023-02-28 06:26:16 +05:00 committed by GitHub
parent 0c01024f76
commit 1ff1a22fff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 90 additions and 133 deletions

View File

@ -6,7 +6,7 @@ ext {
extName = 'Comick'
pkgNameSuffix = 'all.comickfun'
extClass = '.ComickFunFactory'
extVersionCode = 20
extVersionCode = 21
isNsfw = true
}

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.extension.all.comickfun
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
@ -11,26 +10,23 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import rx.Observable
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Locale
const val API_BASE = "https://api.comick.fun"
abstract class ComickFun(override val lang: String, private val comickFunLang: String) : HttpSource() {
override val name = "Comick"
override val baseUrl = "https://comick.app"
private val apiUrl = "https://api.comick.fun"
override val supportsLatest = true
private val json = Json {
@ -49,64 +45,32 @@ abstract class ComickFun(override val lang: String, private val comickFunLang: S
/** Popular Manga **/
override fun popularMangaRequest(page: Int): Request {
return GET(
API_BASE.toHttpUrl().newBuilder().apply {
addPathSegment("v1.0")
addPathSegment("search")
addQueryParameter("sort", "follow")
addQueryParameter("page", "$page")
addQueryParameter("tachiyomi", "true")
}.toString(),
headers,
)
}
override fun popularMangaParse(response: Response): MangasPage {
val result = json.decodeFromString<List<Manga>>(response.body.string())
return MangasPage(
result.map { data ->
SManga.create().apply {
url = "/comic/${data.slug}"
title = data.title
thumbnail_url = data.cover_url
}
},
hasNextPage = true,
return searchMangaRequest(
page = page,
query = "",
filters = FilterList(
SortFilter("", getSortsList, defaultPopularSort),
),
)
}
override fun popularMangaParse(response: Response) = searchMangaParse(response)
/** Latest Manga **/
override fun latestUpdatesRequest(page: Int): Request {
return GET(
API_BASE.toHttpUrl().newBuilder().apply {
addPathSegment("v1.0")
addPathSegment("search")
if (comickFunLang != "all") addQueryParameter("lang", comickFunLang)
addQueryParameter("sort", "uploaded")
addQueryParameter("page", "$page")
addQueryParameter("tachiyomi", "true")
}.toString(),
headers,
return searchMangaRequest(
page = page,
query = "",
filters = FilterList(
SortFilter("", getSortsList, defaultLatestSort),
),
)
}
override fun latestUpdatesParse(response: Response): MangasPage {
val result = json.decodeFromString<List<Manga>>(response.body.string())
return MangasPage(
result.map { data ->
SManga.create().apply {
url = "/comic/${data.slug}"
title = data.title
thumbnail_url = data.cover_url
}
},
hasNextPage = true,
)
}
override fun latestUpdatesParse(response: Response) = searchMangaParse(response)
/** Manga Search **/
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url: String = API_BASE.toHttpUrl().newBuilder().apply {
val url = apiUrl.toHttpUrl().newBuilder().apply {
addPathSegment("search")
if (query.isEmpty()) {
filters.forEach { it ->
@ -185,7 +149,7 @@ abstract class ComickFun(override val lang: String, private val comickFunLang: S
}
addQueryParameter("tachiyomi", "true")
addQueryParameter("page", "$page")
}.toString()
}.build()
return GET(url, headers)
}
@ -194,7 +158,8 @@ abstract class ComickFun(override val lang: String, private val comickFunLang: S
return MangasPage(
result.map { data ->
SManga.create().apply {
url = "/comic/${data.slug}"
// appennding # at end as part of migration from slug to hid
url = "/comic/${data.hid}#"
title = data.title
thumbnail_url = data.cover_url
}
@ -204,26 +169,21 @@ abstract class ComickFun(override val lang: String, private val comickFunLang: S
}
/** Manga Details **/
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return client.newCall(
GET(
"$API_BASE${manga.url}".toHttpUrl().newBuilder().apply {
addQueryParameter("tachiyomi", "true")
}.toString(),
headers,
),
).asObservableSuccess()
.map { response -> mangaDetailsParse(response).apply { initialized = true } }
}
override fun mangaDetailsRequest(manga: SManga): Request {
return GET("$baseUrl${manga.url}".toHttpUrl().toString())
// Migration from slug based urls to hid based ones
if (!manga.url.endsWith("#")) {
throw Exception("Migrate from Comick to Comick")
}
val mangaUrl = manga.url.removeSuffix("#")
return GET("$apiUrl$mangaUrl?tachiyomi=true", headers)
}
override fun mangaDetailsParse(response: Response): SManga {
val mangaData = json.decodeFromString<MangaDetails>(response.body.string())
return SManga.create().apply {
url = "$baseUrl/comic/${mangaData.comic.slug}"
// appennding # at end as part of migration from slug to hid
url = "/comic/${mangaData.comic.hid}#"
title = mangaData.comic.title
artist = mangaData.artists.joinToString { it.name.trim() }
author = mangaData.authors.joinToString { it.name.trim() }
@ -234,42 +194,60 @@ abstract class ComickFun(override val lang: String, private val comickFunLang: S
}
}
override fun getMangaUrl(manga: SManga): String {
return "$baseUrl${manga.url}"
}
/** Manga Chapter List **/
override fun chapterListRequest(manga: SManga): Request {
// Migration from slug based urls to hid based ones
if (!manga.url.endsWith("#")) {
throw Exception("Migrate from Comick to Comick")
}
return paginatedChapterListRequest(manga.url.removeSuffix("#"), 1)
}
private fun paginatedChapterListRequest(mangaUrl: String, page: Int): Request {
return GET(
"$API_BASE${manga.url}".toHttpUrl().newBuilder().apply {
"$apiUrl$mangaUrl".toHttpUrl().newBuilder().apply {
addPathSegment("chapters")
if (comickFunLang != "all") addQueryParameter("lang", comickFunLang)
addQueryParameter("tachiyomi", "true")
}.toString(),
addQueryParameter("page", "$page")
}.build(),
headers,
)
}
override fun chapterListParse(response: Response): List<SChapter> {
val mangaData = json.decodeFromString<MangaDetails>(response.body.string())
val mangaHid = findCurrentSlug(mangaData.comic.slug)
val chapterData = client.newCall(
GET(
API_BASE.toHttpUrl().newBuilder().apply {
addPathSegment("comic")
addPathSegments(mangaHid)
addPathSegments("chapters")
if (comickFunLang != "all") addQueryParameter("lang", comickFunLang)
addQueryParameter(
"limit",
mangaData.comic.chapter_count.toString(),
)
}.toString(),
headers,
),
).execute()
val result = json.decodeFromString<ChapterList>(chapterData.body.string())
return result.chapters.map { chapter ->
val chapterListResponse = json.decodeFromString<ChapterList>(response.body.string())
val mangaUrl = "/" + response.request.url.toString()
.substringBefore("/chapters")
.substringAfter("$apiUrl/")
var resultSize = chapterListResponse.chapters.size
var page = 2
while (chapterListResponse.total > resultSize) {
val newRequest = paginatedChapterListRequest(mangaUrl, page)
val newResponse = client.newCall(newRequest).execute()
val newChapterListResponse = json.decodeFromString<ChapterList>(newResponse.body.string())
chapterListResponse.chapters += newChapterListResponse.chapters
resultSize += newChapterListResponse.chapters.size
page += 1
}
return chapterListResponse.chapters.map { chapter ->
SChapter.create().apply {
url = "/comic/${mangaData.comic.slug}/${chapter.hid}-chapter-${chapter.chap}-$comickFunLang"
url = "$mangaUrl/${chapter.hid}-chapter-${chapter.chap}-$comickFunLang"
name = beautifyChapterName(chapter.vol, chapter.chap, chapter.title)
date_upload = chapter.created_at.let {
try {
DATE_FORMATTER.parse(it)?.time ?: 0L
dateFormat.parse(it)?.time ?: 0L
} catch (e: ParseException) {
0L
}
@ -279,21 +257,18 @@ abstract class ComickFun(override val lang: String, private val comickFunLang: S
}
}
private val DATE_FORMATTER by lazy {
private val dateFormat by lazy {
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ENGLISH)
}
override fun getChapterUrl(chapter: SChapter): String {
return "$baseUrl${chapter.url}"
}
/** Chapter Pages **/
override fun pageListRequest(chapter: SChapter): Request {
val chapterHid = chapter.url.substringAfterLast("/").substringBefore("-")
return GET(
API_BASE.toHttpUrl().newBuilder().apply {
addPathSegment("chapter")
addPathSegment(chapterHid)
addQueryParameter("tachiyomi", "true")
}.toString(),
headers,
)
return GET("$apiUrl/chapter/$chapterHid?tachiyomi=true", headers)
}
override fun pageListParse(response: Response): List<Page> {
@ -307,30 +282,13 @@ abstract class ComickFun(override val lang: String, private val comickFunLang: S
const val SLUG_SEARCH_PREFIX = "id:"
}
/** Don't touch this, Tachiyomi forces you to declare the following methods even I you don't use them **/
override fun imageUrlParse(response: Response): String {
return ""
throw UnsupportedOperationException("Not used")
}
protected open val defaultPopularSort: Int = 0
protected open val defaultLatestSort: Int = 4
override fun getFilterList() = FilterList(
getFilters(),
)
/** Map the slug to comic ID as slug might be changes by comic ID will not. **/
// TODO: Cleanup once ext-lib 1.4 is released.
private fun findCurrentSlug(oldSlug: String): String {
val response = client.newCall(
GET(
API_BASE.toHttpUrl().newBuilder().apply {
addPathSegment("tachiyomi")
addPathSegment("mapping")
addQueryParameter("slugs", oldSlug)
}.toString(),
headers,
),
).execute()
/** If the API does not contain the ID for the slug, return the slug back **/
return json.parseToJsonElement(response.body.string()).jsonObject[oldSlug]!!.jsonPrimitive.content
}
}

View File

@ -5,7 +5,6 @@ import kotlinx.serialization.Serializable
@Serializable
data class Manga(
val hid: String,
val slug: String,
val title: String,
val cover_url: String,
)
@ -20,12 +19,10 @@ data class MangaDetails(
@Serializable
data class Comic(
val id: Int,
val hid: String,
val title: String,
val slug: String,
val desc: String = "N/A",
val status: Int,
val chapter_count: Int?,
val cover_url: String,
)
@ -49,12 +46,13 @@ data class Genre(
@Serializable
data class ChapterList(
val chapters: Array<Chapter>,
val chapters: MutableList<Chapter>,
val total: Int,
)
@Serializable
data class Chapter(
val hid: String = "",
val hid: String,
val title: String = "",
val created_at: String = "",
val chap: String = "",

View File

@ -45,8 +45,8 @@ internal class FromYearFilter(name: String) : Text(name)
internal class ToYearFilter(name: String) : Text(name)
internal class SortFilter(name: String, sortList: Array<Pair<String, String>>) :
Select(name, sortList)
internal class SortFilter(name: String, sortList: Array<Pair<String, String>>, state: Int = 0) :
Select(name, sortList, state)
/** Generics **/
internal open class Group(name: String, values: List<Any>) :
@ -58,8 +58,8 @@ internal open class Text(name: String) : Filter.Text(name)
internal open class CheckBox(name: String, val value: String = "") : Filter.CheckBox(name)
internal open class Select(name: String, private val vals: Array<Pair<String, String>>) :
Filter.Select<String>(name, vals.map { it.first }.toTypedArray()) {
internal open class Select(name: String, private val vals: Array<Pair<String, String>>, state: Int = 0) :
Filter.Select<String>(name, vals.map { it.first }.toTypedArray(), state) {
fun getValue() = vals[state].second
}
@ -171,7 +171,8 @@ private val getCreatedAtList: Array<Pair<String, String>> = arrayOf(
Pair("1 year", "365"),
)
private val getSortsList: Array<Pair<String, String>> = arrayOf(
internal val getSortsList: Array<Pair<String, String>> = arrayOf(
Pair("Most popular", "follow"),
Pair("Most follows", "user_follow_count"),
Pair("Most views", "view"),
Pair("High rating", "rating"),