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