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()
This commit is contained in:
Hualiang 2025-07-02 16:09:36 +08:00 committed by Draff
parent 3070ed4967
commit 2d0e57517e
Signed by: Draff
GPG Key ID: E8A89F3211677653
7 changed files with 254 additions and 516 deletions

View File

@ -1,7 +1,7 @@
ext {
extName = 'Komiic'
extClass = '.Komiic'
extVersionCode = 1
extVersionCode = 2
isNsfw = true
}

View File

@ -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 <reified T : ComicListResult> parseComicList(response: Response): MangasPage {
val res = response.parseAs<Data<T>>()
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<HotComicsResponse>(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<RecentUpdateResponse>(response)
private val SManga.id get() = url.substringAfterLast("/")
private val SChapter.id get() = url.substringAfterLast("/")
private inline fun <reified T> Payload<T>.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<Data<ComicByIDResponse>>()
val entries = mutableListOf<SManga>()
val comic = res.data.comic.toSManga()
entries.add(comic)
val hasNextPage = entries.size == PAGE_SIZE
return MangasPage(entries, hasNextPage)
val res = response.parseAs<Data<Comic>>()
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<Boolean> {
// 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<Boolean>>().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<Data<List<Comic>>>()
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<MangasPage> {
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
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<Data<SearchResponse>>()
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<Data<Result<List<Comic>>>>()
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<Data<ComicByIDResponse>>()
val comic = res.data.comic.toSManga()
return comic
val res = response.parseAs<Data<Comic>>()
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<SChapter> {
val res = response.parseAs<Data<ChaptersResponse>>()
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<Data<List<Chapter>>>()
val comics = res.data.result.sortedWith(
compareByDescending<Chapter> { 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<APILimitData>().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<Page> {
val res = response.parseAs<Data<ImagesResponse>>()
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<MultiData<Boolean, List<Image>>>()
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 <reified T> String.parseAs(): T =
json.decodeFromString(this)
private inline fun <reified T> Response.parseAs(): T =
use { body.string() }.parseAs()
private inline fun <reified T : Any> 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")
}
}
}

View File

@ -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<T>(
data class Payload<T>(
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<String, JsonElement>()
@Serializable
data class RecentUpdateVariables(
val pagination: MangaListPagination,
)
inline fun <reified T> 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)
}

View File

@ -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)
},
)

View File

@ -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 }" }

View File

@ -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<T>(val data: T)
interface ComicListResult {
val comics: List<Comic>
}
class Data<T>(val data: Result<T>)
@Serializable
data class HotComicsResponse(
@SerialName("hotComics") override val comics: List<Comic>,
) : ComicListResult
class MultiData<T, V>(val data: MultiResult<T, V>)
@Serializable
data class RecentUpdateResponse(
@SerialName("recentUpdate") override val comics: List<Comic>,
) : ComicListResult
interface SearchResult {
val action: ComicsAndAuthors
}
class Result<T>(val result: T)
@Serializable
data class SearchResponse(
@SerialName("searchComicsAndAuthors") override val action: ComicsAndAuthors,
) : SearchResult
class MultiResult<T, V>(val result1: T, val result2: V)
@Serializable
data class ComicsAndAuthors(
val comics: List<Comic>,
val authors: List<Author>,
@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<ComicAuthor>,
val categories: List<ComicCategory>,
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<ComicItem>,
val categories: List<ComicItem>,
) {
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<Chapter>
}
@Serializable
data class ChaptersResponse(
@SerialName("chaptersByComicId") override val chapters: List<Chapter>,
) : 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<Image>,
)
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,
)

View File

@ -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}"
}
}