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
This commit is contained in:
parent
ced338c8e2
commit
db21462070
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application>
|
||||
<activity
|
||||
android:name=".zh.komiic.UrlActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="komiic.com"
|
||||
android:pathPattern="/comic/..*"
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
|
@ -0,0 +1,8 @@
|
|||
ext {
|
||||
extName = 'Komiic'
|
||||
extClass = '.Komiic'
|
||||
extVersionCode = 1
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 7.4 KiB |
|
@ -0,0 +1,285 @@
|
|||
package eu.kanade.tachiyomi.extension.zh.komiic
|
||||
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
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 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
|
||||
override var name = "Komiic"
|
||||
override val baseUrl = "https://komiic.com"
|
||||
override val lang = "zh"
|
||||
override val supportsLatest = true
|
||||
|
||||
override val client: OkHttpClient = network.cloudflareClient
|
||||
|
||||
// Variables
|
||||
private val queryAPIUrl = "$baseUrl/api/query"
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
/**
|
||||
* 解析漫畫列表
|
||||
* Parse comic list
|
||||
*/
|
||||
private inline fun <reified T : ComicListResult> parseComicList(response: Response): MangasPage {
|
||||
val res = response.parseAs<Data<T>>()
|
||||
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<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)
|
||||
|
||||
/**
|
||||
* 根據 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<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)
|
||||
}
|
||||
|
||||
// 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<MangasPage> {
|
||||
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<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)
|
||||
}
|
||||
|
||||
// Comic details
|
||||
override fun mangaDetailsRequest(manga: SManga) = comicByIDRequest(manga.url.substringAfterLast("/"))
|
||||
|
||||
override fun mangaDetailsParse(response: Response): SManga {
|
||||
val res = response.parseAs<Data<ComicByIDResponse>>()
|
||||
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<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
|
||||
}
|
||||
|
||||
/**
|
||||
* 檢查 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
|
||||
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<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}",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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 <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")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package eu.kanade.tachiyomi.extension.zh.komiic
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class Payload<T>(
|
||||
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,
|
||||
)
|
|
@ -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
|
||||
}
|
||||
}
|
||||
"""
|
||||
}
|
|
@ -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<T>(val data: T)
|
||||
|
||||
interface ComicListResult {
|
||||
val comics: List<Comic>
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class HotComicsResponse(
|
||||
@SerialName("hotComics") override val comics: List<Comic>,
|
||||
) : ComicListResult
|
||||
|
||||
@Serializable
|
||||
data class RecentUpdateResponse(
|
||||
@SerialName("recentUpdate") override val comics: List<Comic>,
|
||||
) : ComicListResult
|
||||
|
||||
interface SearchResult {
|
||||
val action: ComicsAndAuthors
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class SearchResponse(
|
||||
@SerialName("searchComicsAndAuthors") override val action: ComicsAndAuthors,
|
||||
) : SearchResult
|
||||
|
||||
@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
|
||||
|
||||
@Serializable
|
||||
data class Comic(
|
||||
val id: String,
|
||||
val title: 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,
|
||||
) {
|
||||
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<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>,
|
||||
)
|
||||
|
||||
@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,
|
||||
)
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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}億"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue