From db214620707ab2c932820a10682e66a8202949fe Mon Sep 17 00:00:00 2001 From: SummonHIM Date: Sat, 5 Oct 2024 12:33:43 +0800 Subject: [PATCH] Add Komiic source (#5224) * Init commit of the Komiic * komiic: set ext to nsfw * Komiic: Add fetchAPILimit * Komiic: Refactor entire project. * komiic: save date format as class val and wrap the parsing in try catch * Comiic: remove unnecessary private function rename some vars remove unnecessary SerialName remove unnecessary interface * komiic: add private val json payload use simple classes change some companion object to capital * Komiic: imports go ordered in lexicographic commet function fetchAPILimit * Komiic: restore previous import order * Komiic: optimize some variables --- src/zh/komiic/AndroidManifest.xml | 23 ++ src/zh/komiic/build.gradle | 8 + src/zh/komiic/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 2729 bytes src/zh/komiic/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1579 bytes .../komiic/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 3341 bytes .../komiic/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 5221 bytes .../komiic/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 7535 bytes .../tachiyomi/extension/zh/komiic/Komiic.kt | 285 ++++++++++++++++++ .../tachiyomi/extension/zh/komiic/Payload.kt | 49 +++ .../tachiyomi/extension/zh/komiic/Queries.kt | 187 ++++++++++++ .../tachiyomi/extension/zh/komiic/Response.kt | 160 ++++++++++ .../extension/zh/komiic/UrlActivity.kt | 34 +++ .../tachiyomi/extension/zh/komiic/Utils.kt | 15 + 13 files changed, 761 insertions(+) create mode 100644 src/zh/komiic/AndroidManifest.xml create mode 100644 src/zh/komiic/build.gradle create mode 100644 src/zh/komiic/res/mipmap-hdpi/ic_launcher.png create mode 100644 src/zh/komiic/res/mipmap-mdpi/ic_launcher.png create mode 100644 src/zh/komiic/res/mipmap-xhdpi/ic_launcher.png create mode 100644 src/zh/komiic/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 src/zh/komiic/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Komiic.kt create mode 100644 src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Payload.kt create mode 100644 src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Queries.kt create mode 100644 src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Response.kt create mode 100644 src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/UrlActivity.kt create mode 100644 src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Utils.kt diff --git a/src/zh/komiic/AndroidManifest.xml b/src/zh/komiic/AndroidManifest.xml new file mode 100644 index 000000000..5e9c2ef4d --- /dev/null +++ b/src/zh/komiic/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + diff --git a/src/zh/komiic/build.gradle b/src/zh/komiic/build.gradle new file mode 100644 index 000000000..ed7745b7e --- /dev/null +++ b/src/zh/komiic/build.gradle @@ -0,0 +1,8 @@ +ext { + extName = 'Komiic' + extClass = '.Komiic' + extVersionCode = 1 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/zh/komiic/res/mipmap-hdpi/ic_launcher.png b/src/zh/komiic/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..8b870210eb9adb1636966a13026325511d9d3a70 GIT binary patch literal 2729 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 literal 0 HcmV?d00001 diff --git a/src/zh/komiic/res/mipmap-xhdpi/ic_launcher.png b/src/zh/komiic/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..672fe14ec363065c8879a46b8b442f8ebf095cfb GIT binary patch literal 3341 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 X`}Ft14@Am(00000NkvXXu0mjf1@lx| literal 0 HcmV?d00001 diff --git a/src/zh/komiic/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/komiic/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..9fbeef0674b9483d0967718b02b664652c9ba65c GIT binary patch literal 5221 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 parseComicList(response: Response): MangasPage { + val res = response.parseAs>() + val comics = res.data.comics + + val entries = comics.map { comic -> + comic.toSManga() + } + + 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) + + /** + * 根據 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) + } + + /** + * 根據 ID 解析搜索來的漫畫 + * 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) + } + + // Search + 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) + } + + 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)) + .asObservableSuccess() + .map(::parseComicByID) + } else { + super.fetchSearchManga(page, query, filters) + } + } + + 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) + } + + // Comic details + override fun mangaDetailsRequest(manga: SManga) = comicByIDRequest(manga.url.substringAfterLast("/")) + + override fun mangaDetailsParse(response: Response): SManga { + val res = response.parseAs>() + val comic = res.data.comic.toSManga() + return comic + } + + override fun getMangaUrl(manga: SManga) = "$baseUrl${manga.url}" + + /** + * 解析日期 + * Parse date + */ + private fun parseDate(dateStr: String): Long { + return try { + DATE_FORMAT.parse(dateStr)?.time ?: 0L + } catch (e: ParseException) { + e.printStackTrace() + 0L + } + } + + // 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) + } + + 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 + } + + /** + * 檢查 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 + 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) + } + + 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}", + ) + } + } + + 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'") + .addHeader("referer", page.url) + .build() + } + + 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 new file mode 100644 index 000000000..f4c02454b --- /dev/null +++ b/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Payload.kt @@ -0,0 +1,49 @@ +package eu.kanade.tachiyomi.extension.zh.komiic + +import kotlinx.serialization.Serializable + +@Serializable +class Payload( + val operationName: String, + val variables: T, + val query: String, +) + +@Serializable +data class MangaListPagination( + val limit: Int, + val offset: Int, + val orderBy: String, + val status: String, + val asc: Boolean, +) + +@Serializable +data class HotComicsVariables( + val pagination: MangaListPagination, +) + +@Serializable +data class RecentUpdateVariables( + val pagination: MangaListPagination, +) + +@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, +) 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 new file mode 100644 index 000000000..a8a2704a7 --- /dev/null +++ b/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Queries.kt @@ -0,0 +1,187 @@ +package eu.kanade.tachiyomi.extension.zh.komiic + +private fun buildQuery(queryAction: () -> String): String { + return queryAction() + .trimIndent() + .replace("%", "$") +} + +val QUERY_HOT_COMICS: String = buildQuery { + """ + 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 + } + } + """ +} + +val QUERY_RECENT_UPDATE: String = buildQuery { + """ + 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 + } + } + """ +} + +val QUERY_SEARCH: String = buildQuery { + """ + 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 + } + } + """ +} + +val QUERY_CHAPTER: String = buildQuery { + """ + query chapterByComicId(%comicId: ID!) { + chaptersByComicId(comicId: %comicId) { + id + serial + type + dateCreated + dateUpdated + size + __typename + } + } + """ +} + +val QUERY_COMIC_BY_ID = 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 + } + } + """ +} + +val QUERY_PAGE_LIST = buildQuery { + """ + query imagesByChapterId(%chapterId: ID!) { + imagesByChapterId(chapterId: %chapterId) { + id + kid + height + width + __typename + } + } + """ +} + +val QUERY_API_LIMIT = buildQuery { + """ + query getImageLimit { + getImageLimit { + limit + usage + resetInSeconds + __typename + } + } + """ +} 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 new file mode 100644 index 000000000..14b33c015 --- /dev/null +++ b/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Response.kt @@ -0,0 +1,160 @@ +package eu.kanade.tachiyomi.extension.zh.komiic + +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 +} + +@Serializable +data class HotComicsResponse( + @SerialName("hotComics") override val comics: List, +) : ComicListResult + +@Serializable +data class RecentUpdateResponse( + @SerialName("recentUpdate") override val comics: List, +) : ComicListResult + +interface SearchResult { + val action: ComicsAndAuthors +} + +@Serializable +data class SearchResponse( + @SerialName("searchComicsAndAuthors") override val action: ComicsAndAuthors, +) : SearchResult + +@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 + +@Serializable +data class Comic( + val id: String, + val title: 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, +) { + 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") + } + status = parseStatus + initialized = true + } +} + +@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, +) + +@Serializable +data class Image( + val id: String, + 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/UrlActivity.kt b/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/UrlActivity.kt new file mode 100644 index 000000000..54be52071 --- /dev/null +++ b/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/UrlActivity.kt @@ -0,0 +1,34 @@ +package eu.kanade.tachiyomi.extension.zh.komiic + +import android.app.Activity +import android.content.ActivityNotFoundException +import android.content.Intent +import android.os.Bundle +import android.util.Log +import kotlin.system.exitProcess + +class UrlActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val pathSegments = intent?.data?.pathSegments + if (pathSegments != null && pathSegments.size > 1) { + val id = pathSegments[1] + val mainIntent = Intent().apply { + action = "eu.kanade.tachiyomi.SEARCH" + putExtra("query", "${Komiic.PREFIX_ID_SEARCH}$id") + putExtra("filter", packageName) + } + + try { + startActivity(mainIntent) + } catch (e: ActivityNotFoundException) { + Log.e("KomiicUrlActivity", e.toString()) + } + } else { + Log.e("KomiicUrlActivity", "could not parse uri from intent $intent") + } + + finish() + exitProcess(0) + } +} 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 new file mode 100644 index 000000000..5f5aff299 --- /dev/null +++ b/src/zh/komiic/src/eu/kanade/tachiyomi/extension/zh/komiic/Utils.kt @@ -0,0 +1,15 @@ +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}億" + } +}