From 1ff1a22fff3c686516b9fe2d10b2462fd5ac0b82 Mon Sep 17 00:00:00 2001 From: mobi2002 <48650614+mobi2002@users.noreply.github.com> Date: Tue, 28 Feb 2023 06:26:16 +0500 Subject: [PATCH] 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 --- src/all/comickfun/build.gradle | 2 +- .../extension/all/comickfun/ComickFun.kt | 200 +++++++----------- .../extension/all/comickfun/ComickFunDto.kt | 10 +- .../all/comickfun/ComickFunFilters.kt | 11 +- 4 files changed, 90 insertions(+), 133 deletions(-) diff --git a/src/all/comickfun/build.gradle b/src/all/comickfun/build.gradle index 35e8081c8..db39a53cd 100644 --- a/src/all/comickfun/build.gradle +++ b/src/all/comickfun/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'Comick' pkgNameSuffix = 'all.comickfun' extClass = '.ComickFunFactory' - extVersionCode = 20 + extVersionCode = 21 isNsfw = true } diff --git a/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFun.kt b/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFun.kt index de77dfef0..b6b25e0f6 100644 --- a/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFun.kt +++ b/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFun.kt @@ -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>(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>(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 { - 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(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 { - val mangaData = json.decodeFromString(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(chapterData.body.string()) - return result.chapters.map { chapter -> + val chapterListResponse = json.decodeFromString(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(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 { @@ -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 - } } diff --git a/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFunDto.kt b/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFunDto.kt index b323a00a0..a3440c76f 100644 --- a/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFunDto.kt +++ b/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFunDto.kt @@ -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, + val chapters: MutableList, + val total: Int, ) @Serializable data class Chapter( - val hid: String = "", + val hid: String, val title: String = "", val created_at: String = "", val chap: String = "", diff --git a/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFunFilters.kt b/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFunFilters.kt index 833a2c2a0..1db8d7068 100644 --- a/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFunFilters.kt +++ b/src/all/comickfun/src/eu/kanade/tachiyomi/extension/all/comickfun/ComickFunFilters.kt @@ -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>) : - Select(name, sortList) +internal class SortFilter(name: String, sortList: Array>, state: Int = 0) : + Select(name, sortList, state) /** Generics **/ internal open class Group(name: String, values: List) : @@ -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>) : - Filter.Select(name, vals.map { it.first }.toTypedArray()) { +internal open class Select(name: String, private val vals: Array>, state: Int = 0) : + Filter.Select(name, vals.map { it.first }.toTypedArray(), state) { fun getValue() = vals[state].second } @@ -171,7 +171,8 @@ private val getCreatedAtList: Array> = arrayOf( Pair("1 year", "365"), ) -private val getSortsList: Array> = arrayOf( +internal val getSortsList: Array> = arrayOf( + Pair("Most popular", "follow"), Pair("Most follows", "user_follow_count"), Pair("Most views", "view"), Pair("High rating", "rating"),