From 2d0e57517e1d71f95c09e690876f79316b1ebaa8 Mon Sep 17 00:00:00 2001 From: Hualiang <78242797+hualiong@users.noreply.github.com> Date: Wed, 2 Jul 2025 16:09:36 +0800 Subject: [PATCH] Komiic: Add manga description and refactor some code (#9445) * add comic description * fix manga search results missing descriptions * clean unused variables * clean unused class * Add some config options and refactor some code * refactor some code * modify config option summary * apply comments * modify Queries.kt * small modification * Format code * replace parse method * optimize check API limit * modify config summary * add getChapterUrl() --- src/zh/komiic/build.gradle | 2 +- .../tachiyomi/extension/zh/komiic/Komiic.kt | 315 +++++++----------- .../tachiyomi/extension/zh/komiic/Payload.kt | 53 ++- .../extension/zh/komiic/Preferences.kt | 25 ++ .../tachiyomi/extension/zh/komiic/Queries.kt | 217 ++++-------- .../tachiyomi/extension/zh/komiic/Response.kt | 143 ++------ .../tachiyomi/extension/zh/komiic/Utils.kt | 15 - 7 files changed, 254 insertions(+), 516 deletions(-) create mode 100644 src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Preferences.kt delete mode 100644 src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Utils.kt diff --git a/src/zh/komiic/build.gradle b/src/zh/komiic/build.gradle index ed7745b7e..4370221d3 100644 --- a/src/zh/komiic/build.gradle +++ b/src/zh/komiic/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Komiic' extClass = '.Komiic' - extVersionCode = 1 + extVersionCode = 2 isNsfw = true } diff --git a/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Komiic.kt b/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Komiic.kt index 5f407ea64..42b9b15f6 100644 --- a/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Komiic.kt +++ b/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Komiic.kt @@ -1,109 +1,65 @@ package eu.kanade.tachiyomi.extension.zh.komiic +import androidx.preference.PreferenceScreen import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.source.ConfigurableSource 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 kotlinx.serialization.decodeFromString -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json +import keiyoushi.utils.getPreferences +import keiyoushi.utils.parseAs +import keiyoushi.utils.toJsonString +import keiyoushi.utils.tryParse import okhttp3.MediaType.Companion.toMediaTypeOrNull -import okhttp3.OkHttpClient import okhttp3.Request -import okhttp3.RequestBody import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response import rx.Observable -import uy.kohesive.injekt.injectLazy -import java.text.ParseException import java.text.SimpleDateFormat import java.util.Locale import java.util.TimeZone -class Komiic : HttpSource() { - // Override variables +class Komiic : HttpSource(), ConfigurableSource { override var name = "Komiic" override val baseUrl = "https://komiic.com" override val lang = "zh" override val supportsLatest = true + override val client = network.cloudflareClient - override val client: OkHttpClient = network.cloudflareClient + private val apiUrl = "$baseUrl/api/query" + private val preferences = getPreferences() - // Variables - private val queryAPIUrl = "$baseUrl/api/query" - private val json: Json by injectLazy() + override fun setupPreferenceScreen(screen: PreferenceScreen) { + preferencesInternal(screen.context).forEach(screen::addPreference) + } - /** - * 解析漫畫列表 - * Parse comic list - */ - private inline fun parseComicList(response: Response): MangasPage { - val res = response.parseAs>() - val comics = res.data.comics + // Customize =================================================================================== - val entries = comics.map { comic -> - comic.toSManga() + companion object { + const val PAGE_SIZE = 20 + const val PREFIX_ID_SEARCH = "id:" + private val JSON_MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull() + private val DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).apply { + timeZone = TimeZone.getTimeZone("UTC") } - - val hasNextPage = comics.size == PAGE_SIZE - return MangasPage(entries, hasNextPage) } - // Hot Comic - override fun popularMangaRequest(page: Int): Request { - val payload = Payload( - operationName = "hotComics", - variables = HotComicsVariables( - pagination = MangaListPagination( - PAGE_SIZE, - (page - 1) * PAGE_SIZE, - "MONTH_VIEWS", - "", - true, - ), - ), - query = QUERY_HOT_COMICS, - ).toJsonRequestBody() - return POST(queryAPIUrl, headers, payload) - } - - override fun popularMangaParse(response: Response) = parseComicList(response) - - // Recent update - override fun latestUpdatesRequest(page: Int): Request { - val payload = Payload( - operationName = "recentUpdate", - variables = RecentUpdateVariables( - pagination = MangaListPagination( - PAGE_SIZE, - (page - 1) * PAGE_SIZE, - "DATE_UPDATED", - "", - true, - ), - ), - query = QUERY_RECENT_UPDATE, - ).toJsonRequestBody() - return POST(queryAPIUrl, headers, payload) - } - - override fun latestUpdatesParse(response: Response) = parseComicList(response) + private val SManga.id get() = url.substringAfterLast("/") + private val SChapter.id get() = url.substringAfterLast("/") + private inline fun Payload.toRequestBody() = this.toJsonString().toRequestBody(JSON_MEDIA_TYPE) /** * 根據 ID 搜索漫畫 * Search the comic based on the ID. */ private fun comicByIDRequest(id: String): Request { - val payload = Payload( - operationName = "comicById", - variables = ComicByIdVariables(id), - query = QUERY_COMIC_BY_ID, - ).toJsonRequestBody() - return POST(queryAPIUrl, headers, payload) + val variables = Variables().set("comicId", id).build() + val payload = Payload("comicById", variables, QUERY_COMIC_BY_ID) + return POST(apiUrl, headers, payload.toRequestBody()) } /** @@ -111,32 +67,64 @@ class Komiic : HttpSource() { * Parse the comic based on the ID. */ private fun parseComicByID(response: Response): MangasPage { - val res = response.parseAs>() - val entries = mutableListOf() - val comic = res.data.comic.toSManga() - entries.add(comic) - val hasNextPage = entries.size == PAGE_SIZE - return MangasPage(entries, hasNextPage) + val res = response.parseAs>() + val entries = listOf(res.data.result.toSManga()) + return MangasPage(entries, false) } - // Search + /** + * 檢查 API 是否達到上限 + * Check if the API has reached its limit. + * But how to throw an exception message to notify user in reading page? + */ + // private fun checkAPILimit(): Observable { + // val payload = Payload("reachedImageLimit", Variables().build(), QUERY_API_LIMIT) + // val response = client.newCall(POST(queryAPIUrl, headers, payload.toRequestBody())) + // val limit = response.asObservableSuccess().map { it.parseAs>().data.result } + // return limit + // } + + // Popular Manga =============================================================================== + + override fun popularMangaRequest(page: Int): Request { + val variables = Variables().set( + "pagination", + Pagination((page - 1) * PAGE_SIZE, "MONTH_VIEWS"), + ).build() + val payload = Payload("hotComics", variables, QUERY_HOT_COMICS) + return POST(apiUrl, headers, payload.toRequestBody()) + } + + override fun popularMangaParse(response: Response): MangasPage { + val res = response.parseAs>>() + val comics = res.data.result + return MangasPage(comics.map(Comic::toSManga), comics.size == PAGE_SIZE) + } + + // Latest Updates ============================================================================== + + override fun latestUpdatesRequest(page: Int): Request { + val variables = Variables().set( + "pagination", + Pagination((page - 1) * PAGE_SIZE, "DATE_UPDATED"), + ).build() + val payload = Payload("recentUpdate", variables, QUERY_RECENT_UPDATE) + return POST(apiUrl, headers, payload.toRequestBody()) + } + + override fun latestUpdatesParse(response: Response) = popularMangaParse(response) + + // Search Manga ================================================================================ + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val payload = Payload( - operationName = "searchComicAndAuthorQuery", - variables = SearchVariables(query), - query = QUERY_SEARCH, - ).toJsonRequestBody() - return POST(queryAPIUrl, headers, payload) + val variables = Variables().set("keyword", query).build() + val payload = Payload("searchComicAndAuthorQuery", variables, QUERY_SEARCH) + return POST(apiUrl, headers, payload.toRequestBody()) } - override fun fetchSearchManga( - page: Int, - query: String, - filters: FilterList, - ): Observable { + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { return if (query.startsWith(PREFIX_ID_SEARCH)) { - val mangaId = query.substringAfter(PREFIX_ID_SEARCH) - client.newCall(comicByIDRequest(mangaId)) + client.newCall(comicByIDRequest(query.substringAfter(PREFIX_ID_SEARCH))) .asObservableSuccess() .map(::parseComicByID) } else { @@ -145,116 +133,68 @@ class Komiic : HttpSource() { } override fun searchMangaParse(response: Response): MangasPage { - val res = response.parseAs>() - val comics = res.data.action.comics - - val entries = comics.map { comic -> - comic.toSManga() - } - - val hasNextPage = comics.size == PAGE_SIZE - return MangasPage(entries, hasNextPage) + val res = response.parseAs>>>() + val comics = res.data.result.result + return MangasPage(comics.map(Comic::toSManga), comics.size == PAGE_SIZE) } - // Comic details - override fun mangaDetailsRequest(manga: SManga) = comicByIDRequest(manga.url.substringAfterLast("/")) + // Manga Details =============================================================================== + + override fun getMangaUrl(manga: SManga) = baseUrl + manga.url + + override fun mangaDetailsRequest(manga: SManga) = comicByIDRequest(manga.id) override fun mangaDetailsParse(response: Response): SManga { - val res = response.parseAs>() - val comic = res.data.comic.toSManga() - return comic + val res = response.parseAs>() + return res.data.result.toSManga() } - override fun getMangaUrl(manga: SManga) = "$baseUrl${manga.url}" + // Chapter List ================================================================================ - /** - * 解析日期 - * Parse date - */ - private fun parseDate(dateStr: String): Long { - return try { - DATE_FORMAT.parse(dateStr)?.time ?: 0L - } catch (e: ParseException) { - e.printStackTrace() - 0L - } - } + override fun getChapterUrl(chapter: SChapter) = baseUrl + chapter.url + "/images/all" - // Chapter list override fun chapterListRequest(manga: SManga): Request { - val payload = Payload( - operationName = "chapterByComicId", - variables = ChapterByComicIdVariables(manga.url.substringAfterLast("/")), - query = QUERY_CHAPTER, - ).toJsonRequestBody() - - return POST("$queryAPIUrl#${manga.url}", headers, payload) + val variables = Variables().set("comicId", manga.id).build() + val payload = Payload("chapterByComicId", variables, QUERY_CHAPTER) + return POST("$apiUrl#${manga.url}", headers, payload.toRequestBody()) } override fun chapterListParse(response: Response): List { - val res = response.parseAs>() - val comics = res.data.chapters - val comicUrl = response.request.url.fragment - - val tChapters = comics.filter { it.type == "chapter" } - val tBooks = comics.filter { it.type == "book" } - - val entries = (tChapters + tBooks).map { chapter -> - SChapter.create().apply { - url = "$comicUrl/chapter/${chapter.id}/page/1" - name = when (chapter.type) { - "chapter" -> "第 ${chapter.serial} 話" - "book" -> "第 ${chapter.serial} 卷" - else -> chapter.serial - } - date_upload = parseDate(chapter.dateCreated) - chapter_number = chapter.serial.toFloatOrNull() ?: -1f - } - }.reversed() - - return entries + val res = response.parseAs>>() + val comics = res.data.result.sortedWith( + compareByDescending { it.type } + .thenByDescending { it.serial.toFloatOrNull() }, + ) + val display = preferences.getString(CHAPTER_FILTER_PREF, "all") + val items = when (display) { + "chapter" -> comics.filter { it.type == "chapter" } + "book" -> comics.filter { it.type == "book" } + else -> comics + } + val comicUrl = response.request.url.fragment!! + return items.map { it.toSChapter(comicUrl, DATE_FORMAT::tryParse) } } - /** - * 檢查 API 是否達到上限 - * Check if the API has reached its limit. - * - * (Idk how to throw an exception in reading page) - */ - // private fun fetchAPILimit(): Boolean { - // val payload = Payload("getImageLimit", "", QUERY_API_LIMIT).toJsonRequestBody() - // val response = client.newCall(POST(queryAPIUrl, headers, payload)).execute() - // val limit = response.parseAs().getImageLimit - // return limit.limit <= limit.usage - // } + // Page List =================================================================================== - // Page list override fun pageListRequest(chapter: SChapter): Request { - val payload = Payload( - operationName = "imagesByChapterId", - variables = ImagesByChapterIdVariables( - chapter.url.substringAfter("/chapter/").substringBefore("/page/"), - ), - query = QUERY_PAGE_LIST, - ).toJsonRequestBody() - - return POST("$queryAPIUrl#${chapter.url}", headers, payload) + val variables = Variables().set("chapterId", chapter.id).build() + val payload = Payload("imagesByChapterId", variables, QUERY_PAGE_LIST) + return POST("$apiUrl#${chapter.url}", headers, payload.toRequestBody()) } override fun pageListParse(response: Response): List { - val res = response.parseAs>() - val pages = res.data.images - val chapterUrl = response.request.url.toString().split("#")[1] - - return pages.mapIndexed { index, image -> - Page( - index, - "${chapterUrl.substringBeforeLast("/")}/$index", - "$baseUrl/api/image/${image.kid}", - ) + val res = response.parseAs>>() + val check = preferences.getBoolean(CHECK_API_LIMIT_PREF, true) + require(!check || !res.data.result1) { "今日圖片讀取次數已達上限,請登录或明天再來!" } + val chapterUrl = response.request.url.fragment!! + return res.data.result2.mapIndexed { index, image -> + Page(index, "$chapterUrl/page/$index", "$baseUrl/api/image/${image.kid}") } } + // Image ======================================================================================= + override fun imageRequest(page: Page): Request { return super.imageRequest(page).newBuilder() .addHeader("accept", "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8'") @@ -263,23 +203,4 @@ class Komiic : HttpSource() { } override fun imageUrlParse(response: Response) = throw UnsupportedOperationException() - - private inline fun String.parseAs(): T = - json.decodeFromString(this) - - private inline fun Response.parseAs(): T = - use { body.string() }.parseAs() - - private inline fun T.toJsonRequestBody(): RequestBody = - json.encodeToString(this) - .toRequestBody(JSON_MEDIA_TYPE) - - companion object { - private const val PAGE_SIZE = 20 - const val PREFIX_ID_SEARCH = "id:" - private val JSON_MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull() - private val DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).apply { - timeZone = TimeZone.getTimeZone("UTC") - } - } } diff --git a/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Payload.kt b/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Payload.kt index f4c02454b..2d4e96e95 100644 --- a/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Payload.kt +++ b/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Payload.kt @@ -1,49 +1,38 @@ package eu.kanade.tachiyomi.extension.zh.komiic +import kotlinx.serialization.EncodeDefault import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.encodeToJsonElement @Serializable -class Payload( +data class Payload( val operationName: String, val variables: T, val query: String, ) @Serializable -data class MangaListPagination( - val limit: Int, +data class Pagination( val offset: Int, val orderBy: String, - val status: String, - val asc: Boolean, + @EncodeDefault + val limit: Int = Komiic.PAGE_SIZE, + @EncodeDefault + val status: String = "", + @EncodeDefault + val asc: Boolean = true, ) -@Serializable -data class HotComicsVariables( - val pagination: MangaListPagination, -) +class Variables { + val variableMap = mutableMapOf() -@Serializable -data class RecentUpdateVariables( - val pagination: MangaListPagination, -) + inline fun set(key: String, value: T): Variables { + variableMap[key] = Json.encodeToJsonElement(value) + return this + } -@Serializable -data class SearchVariables( - val keyword: String, -) - -@Serializable -data class ComicByIdVariables( - val comicId: String, -) - -@Serializable -data class ChapterByComicIdVariables( - val comicId: String, -) - -@Serializable -data class ImagesByChapterIdVariables( - val chapterId: String, -) + fun build() = JsonObject(variableMap) +} diff --git a/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Preferences.kt b/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Preferences.kt new file mode 100644 index 000000000..848487bdc --- /dev/null +++ b/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Preferences.kt @@ -0,0 +1,25 @@ +package eu.kanade.tachiyomi.extension.zh.komiic + +import android.content.Context +import androidx.preference.ListPreference +import androidx.preference.SwitchPreferenceCompat + +const val CHAPTER_FILTER_PREF = "CHAPTER_FILTER" +const val CHECK_API_LIMIT_PREF = "CHECK_API_LIMIT" + +fun preferencesInternal(context: Context) = arrayOf( + ListPreference(context).apply { + key = CHAPTER_FILTER_PREF + title = "章節列表顯示" + summary = "注:部分漫畫小概率會有章節缺失,僅顯示章節就沒法通過看卷的內容來補充了。建議不要僅顯示卷,會導致無法獲取及時章節更新" + entries = arrayOf("同時顯示卷和章節", "僅顯示章節", "僅顯示卷") + entryValues = arrayOf("all", "chapter", "book") + setDefaultValue("all") + }, + SwitchPreferenceCompat(context).apply { + key = CHECK_API_LIMIT_PREF + title = "自動檢查API受限" + summary = "點擊單個章節請求漫畫圖片時,自動檢查一次圖片API是否達到今日請求上限。若已達上限,則終止後續操作" + setDefaultValue(true) + }, +) diff --git a/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Queries.kt b/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Queries.kt index a8a2704a7..9bc699826 100644 --- a/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Queries.kt +++ b/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Queries.kt @@ -1,187 +1,90 @@ package eu.kanade.tachiyomi.extension.zh.komiic -private fun buildQuery(queryAction: () -> String): String { - return queryAction() - .trimIndent() +private fun buildQuery(body: String = "", queryAction: () -> String): String { + return queryAction().trimIndent() + .replace("#{body}", body.trimIndent()) .replace("%", "$") } -val QUERY_HOT_COMICS: String = buildQuery { +const val COMIC_BODY = """ - query hotComics(%pagination: Pagination!) { - hotComics(pagination: %pagination) { - id - title - status - year - imageUrl - authors { - id - name - __typename - } - categories { - id - name - __typename - } - dateUpdated - monthViews - views - favoriteCount - lastBookUpdate - lastChapterUpdate - __typename - } - } + { + id + title + description + status + imageUrl + authors { + id + name + } + categories { + id + name + } + } + """ + +val QUERY_HOT_COMICS = buildQuery(COMIC_BODY) { + """ + query hotComics(%pagination: Pagination!) { + result: hotComics(pagination: %pagination) #{body} + } """ } -val QUERY_RECENT_UPDATE: String = buildQuery { +val QUERY_RECENT_UPDATE = buildQuery(COMIC_BODY) { """ - query recentUpdate(%pagination: Pagination!) { - recentUpdate(pagination: %pagination) { - id - title - status - year - imageUrl - authors { - id - name - __typename - } - categories { - id - name - __typename - } - dateUpdated - monthViews - views - favoriteCount - lastBookUpdate - lastChapterUpdate - __typename - } - } + query recentUpdate(%pagination: Pagination!) { + result: recentUpdate(pagination: %pagination) #{body} + } """ } -val QUERY_SEARCH: String = buildQuery { +val QUERY_SEARCH = buildQuery(COMIC_BODY) { """ - query searchComicAndAuthorQuery(%keyword: String!) { - searchComicsAndAuthors(keyword: %keyword) { - comics { - id - title - status - year - imageUrl - authors { - id - name - __typename - } - categories { - id - name - __typename - } - dateUpdated - monthViews - views - favoriteCount - lastBookUpdate - lastChapterUpdate - __typename - } - authors { - id - name - chName - enName - wikiLink - comicCount - views - __typename - } - __typename - } - } + query searchComicAndAuthorQuery(%keyword: String!) { + result: searchComicsAndAuthors(keyword: %keyword) { + result: comics #{body} + } + } """ } -val QUERY_CHAPTER: String = buildQuery { +val QUERY_COMIC_BY_ID = buildQuery(COMIC_BODY) { """ - query chapterByComicId(%comicId: ID!) { - chaptersByComicId(comicId: %comicId) { - id - serial - type - dateCreated - dateUpdated - size - __typename - } - } + query comicById(%comicId: ID!) { + result: comicById(comicId: %comicId) #{body} + } """ } -val QUERY_COMIC_BY_ID = buildQuery { +val QUERY_CHAPTER = buildQuery { """ - query comicById(%comicId: ID!) { - comicById(comicId: %comicId) { - id - title - status - year - imageUrl - authors { - id - name - __typename - } - categories { - id - name - __typename - } - dateCreated - dateUpdated - views - favoriteCount - lastBookUpdate - lastChapterUpdate - __typename - } - } + query chapterByComicId(%comicId: ID!) { + result: chaptersByComicId(comicId: %comicId) { + id + serial + type + size + dateCreated + } + } """ } val QUERY_PAGE_LIST = buildQuery { """ - query imagesByChapterId(%chapterId: ID!) { - imagesByChapterId(chapterId: %chapterId) { - id - kid - height - width - __typename - } - } + query imagesByChapterId(%chapterId: ID!) { + result1: reachedImageLimit, + result2: imagesByChapterId(chapterId: %chapterId) { + id + kid + height + width + } + } """ } -val QUERY_API_LIMIT = buildQuery { - """ - query getImageLimit { - getImageLimit { - limit - usage - resetInSeconds - __typename - } - } - """ -} +// val QUERY_API_LIMIT = buildQuery { "query reachedImageLimit { result: reachedImageLimit }" } diff --git a/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Response.kt b/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Response.kt index 14b33c015..2dae12c65 100644 --- a/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Response.kt +++ b/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Response.kt @@ -1,141 +1,70 @@ package eu.kanade.tachiyomi.extension.zh.komiic +import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga -import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class Data(val data: T) - -interface ComicListResult { - val comics: List -} +class Data(val data: Result) @Serializable -data class HotComicsResponse( - @SerialName("hotComics") override val comics: List, -) : ComicListResult +class MultiData(val data: MultiResult) @Serializable -data class RecentUpdateResponse( - @SerialName("recentUpdate") override val comics: List, -) : ComicListResult - -interface SearchResult { - val action: ComicsAndAuthors -} +class Result(val result: T) @Serializable -data class SearchResponse( - @SerialName("searchComicsAndAuthors") override val action: ComicsAndAuthors, -) : SearchResult +class MultiResult(val result1: T, val result2: V) @Serializable -data class ComicsAndAuthors( - val comics: List, - val authors: List, - @SerialName("__typename") val typeName: String, -) - -interface ComicResult { - val comic: Comic -} - -@Serializable -data class ComicByIDResponse( - @SerialName("comicById") override val comic: Comic, -) : ComicResult +data class ComicItem(val id: String, val name: String) @Serializable data class Comic( val id: String, val title: String, + val description: String, val status: String, - val year: Int, val imageUrl: String, - var authors: List, - val categories: List, - val dateCreated: String = "", - val dateUpdated: String, - val monthViews: Int = 0, - val views: Int, - val favoriteCount: Int, - val lastBookUpdate: String, - val lastChapterUpdate: String, - @SerialName("__typename") val typeName: String, + var authors: List, + val categories: List, ) { - private val parseStatus = when (status) { - "ONGOING" -> SManga.ONGOING - "END" -> SManga.COMPLETED - else -> SManga.UNKNOWN - } - fun toSManga() = SManga.create().apply { url = "/comic/$id" title = this@Comic.title thumbnail_url = this@Comic.imageUrl author = this@Comic.authors.joinToString { it.name } genre = this@Comic.categories.joinToString { it.name } - description = buildString { - append("年份: $year | ") - append("點閱: ${simplifyNumber(views)} | ") - append("喜愛: ${simplifyNumber(favoriteCount)}\n") + description = this@Comic.description + status = when (this@Comic.status) { + "ONGOING" -> SManga.ONGOING + "END" -> SManga.COMPLETED + else -> SManga.UNKNOWN } - status = parseStatus - initialized = true + initialized = this@Comic.description.isNotEmpty() } } -@Serializable -data class ComicCategory( - val id: String, - val name: String, - @SerialName("__typename") val typeName: String, -) - -@Serializable -data class ComicAuthor( - val id: String, - val name: String, - @SerialName("__typename") val typeName: String, -) - -@Serializable -data class Author( - val id: String, - val name: String, - val chName: String, - val enName: String, - val wikiLink: String, - val comicCount: Int, - val views: Int, - @SerialName("__typename") val typeName: String, -) - -interface ChaptersResult { - val chapters: List -} - -@Serializable -data class ChaptersResponse( - @SerialName("chaptersByComicId") override val chapters: List, -) : ChaptersResult - @Serializable data class Chapter( val id: String, val serial: String, val type: String, - val dateCreated: String, - val dateUpdated: String, val size: Int, - @SerialName("__typename") val typeName: String, -) - -@Serializable -data class ImagesResponse( - @SerialName("imagesByChapterId") val images: List, -) + val dateCreated: String, +) { + fun toSChapter(comicUrl: String, parseDate: (String) -> Long) = SChapter.create().apply { + url = "$comicUrl/chapter/${this@Chapter.id}" + name = when (this@Chapter.type) { + "chapter" -> "第 ${this@Chapter.serial} 話" + "book" -> "第 ${this@Chapter.serial} 卷" + else -> this@Chapter.serial + } + scanlator = "${this@Chapter.size}P" + date_upload = parseDate(this@Chapter.dateCreated) + chapter_number = if (this@Chapter.type == "book") 0F else this@Chapter.serial.toFloatOrNull() ?: -1f + } +} @Serializable data class Image( @@ -143,18 +72,4 @@ data class Image( val kid: String, val height: Int, val width: Int, - @SerialName("__typename") val typeName: String, -) - -@Serializable -data class APILimitData( - @SerialName("getImageLimit") val getImageLimit: APILimit, -) - -@Serializable -data class APILimit( - val limit: Int, - val usage: Int, - val resetInSeconds: String, - @SerialName("__typename") val typeName: String, ) diff --git a/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Utils.kt b/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Utils.kt deleted file mode 100644 index 5f5aff299..000000000 --- a/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Utils.kt +++ /dev/null @@ -1,15 +0,0 @@ -package eu.kanade.tachiyomi.extension.zh.komiic - -import kotlin.math.abs - -/** - * 簡化數字顯示 - */ -fun simplifyNumber(num: Int): String { - return when { - abs(num) < 1000 -> "$num" - abs(num) < 10000 -> "${num / 1000}千" - abs(num) < 100000000 -> "${num / 10000}萬" - else -> "${num / 100000000}億" - } -}