add Comicfuz (#3600)
* ComicFuz * points info * search and payed chapter indicator * save full urls * client side cache with search and latest * final cleanup and icons * image quality and isNsfw * tag search
This commit is contained in:
parent
47b60ed24d
commit
02ddcb00e6
|
@ -0,0 +1,8 @@
|
||||||
|
ext {
|
||||||
|
extName = 'COMIC FUZ'
|
||||||
|
extClass = '.ComicFuz'
|
||||||
|
extVersionCode = 1
|
||||||
|
isNsfw = true
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
Binary file not shown.
After Width: | Height: | Size: 3.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 8.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
|
@ -0,0 +1,222 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.ja.comicfuz
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.network.POST
|
||||||
|
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.decodeFromByteArray
|
||||||
|
import kotlinx.serialization.encodeToByteArray
|
||||||
|
import kotlinx.serialization.protobuf.ProtoBuf
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import okhttp3.Response
|
||||||
|
import okio.IOException
|
||||||
|
|
||||||
|
class ComicFuz : HttpSource() {
|
||||||
|
|
||||||
|
override val name = "COMIC FUZ"
|
||||||
|
|
||||||
|
private val domain = "comic-fuz.com"
|
||||||
|
override val baseUrl = "https://$domain"
|
||||||
|
private val apiUrl = "https://api.$domain/v1"
|
||||||
|
private val cdnUrl = "https://img.$domain"
|
||||||
|
|
||||||
|
override val lang = "ja"
|
||||||
|
|
||||||
|
override val supportsLatest = true
|
||||||
|
|
||||||
|
override val client = network.cloudflareClient.newBuilder()
|
||||||
|
.addInterceptor(ImageInterceptor)
|
||||||
|
.addNetworkInterceptor { chain ->
|
||||||
|
val response = chain.proceed(chain.request())
|
||||||
|
|
||||||
|
if (!response.isSuccessful) {
|
||||||
|
val exception = when (response.code) {
|
||||||
|
401 -> "Unauthorized"
|
||||||
|
402 -> "Payment Required"
|
||||||
|
else -> "HTTP error ${response.code}"
|
||||||
|
}
|
||||||
|
|
||||||
|
throw IOException(exception)
|
||||||
|
}
|
||||||
|
|
||||||
|
return@addNetworkInterceptor response
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
|
||||||
|
override fun headersBuilder() = super.headersBuilder()
|
||||||
|
.set("Referer", "$baseUrl/")
|
||||||
|
.set("Origin", baseUrl)
|
||||||
|
|
||||||
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
|
return searchMangaRequest(page, "", getFilterList())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
|
return searchMangaParse(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
|
val payload = DayOfWeekRequest(
|
||||||
|
deviceInfo = DeviceInfo(
|
||||||
|
deviceType = DeviceType.BROWSER,
|
||||||
|
),
|
||||||
|
dayOfWeek = DayOfWeek.today(),
|
||||||
|
).toRequestBody()
|
||||||
|
|
||||||
|
return POST("$apiUrl/mangas_by_day_of_week", headers, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesParse(response: Response): MangasPage {
|
||||||
|
val data = response.parseAs<MangaListResponse>()
|
||||||
|
val entries = data.mangas.map {
|
||||||
|
it.toSManga(cdnUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
return MangasPage(entries, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
|
val tag = filters.filterIsInstance<TagFilter>().first()
|
||||||
|
|
||||||
|
return if (query.isNotBlank() || tag.selected == null) {
|
||||||
|
val payload = SearchRequest(
|
||||||
|
deviceInfo = DeviceInfo(
|
||||||
|
deviceType = DeviceType.BROWSER,
|
||||||
|
),
|
||||||
|
query = query.trim(),
|
||||||
|
pageIndexOfMangas = page,
|
||||||
|
pageIndexOfBooks = 1,
|
||||||
|
).toRequestBody()
|
||||||
|
|
||||||
|
POST("$apiUrl/search#$page", headers, payload)
|
||||||
|
} else {
|
||||||
|
val payload = MangaListRequest(
|
||||||
|
deviceInfo = DeviceInfo(
|
||||||
|
deviceType = DeviceType.BROWSER,
|
||||||
|
),
|
||||||
|
tagId = tag.selected!!,
|
||||||
|
).toRequestBody()
|
||||||
|
|
||||||
|
POST("$apiUrl/manga_list", headers, payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaParse(response: Response): MangasPage {
|
||||||
|
return if (response.request.url.pathSegments.last() == "search") {
|
||||||
|
val data = response.parseAs<SearchResponse>()
|
||||||
|
val page = response.request.url.fragment!!.toInt()
|
||||||
|
val entries = data.mangas.map {
|
||||||
|
it.toSManga(cdnUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
MangasPage(entries, data.pageCountOfMangas > page)
|
||||||
|
} else {
|
||||||
|
val data = response.parseAs<MangaListResponse>()
|
||||||
|
val entries = data.mangas.map {
|
||||||
|
it.toSManga(cdnUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
return MangasPage(entries, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFilterList() = getFilters()
|
||||||
|
|
||||||
|
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||||
|
val payload = MangaDetailsRequest(
|
||||||
|
deviceInfo = DeviceInfo(
|
||||||
|
deviceType = DeviceType.BROWSER,
|
||||||
|
),
|
||||||
|
mangaId = manga.url.substringAfterLast("/").toInt(),
|
||||||
|
).toRequestBody()
|
||||||
|
|
||||||
|
return POST("$apiUrl/manga_detail", headers, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMangaUrl(manga: SManga): String {
|
||||||
|
return "$baseUrl${manga.url}"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(response: Response): SManga {
|
||||||
|
val data = response.parseAs<MangaDetailsResponse>()
|
||||||
|
|
||||||
|
return data.toSManga(cdnUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
|
||||||
|
|
||||||
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
|
val data = response.parseAs<MangaDetailsResponse>()
|
||||||
|
|
||||||
|
return data.chapterGroups.flatMap { group ->
|
||||||
|
group.chapters.map { chapter ->
|
||||||
|
chapter.toSChapter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getChapterUrl(chapter: SChapter): String {
|
||||||
|
return "$baseUrl${chapter.url}"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListRequest(chapter: SChapter): Request {
|
||||||
|
val payload = MangaViewerRequest(
|
||||||
|
deviceInfo = DeviceInfo(
|
||||||
|
deviceType = DeviceType.BROWSER,
|
||||||
|
),
|
||||||
|
chapterId = chapter.url.substringAfterLast("/").toInt(),
|
||||||
|
useTicket = false,
|
||||||
|
consumePoint = UserPoint(
|
||||||
|
event = 0,
|
||||||
|
paid = 0,
|
||||||
|
),
|
||||||
|
viewerMode = ViewerMode(
|
||||||
|
imageQuality = ImageQuality.HIGH,
|
||||||
|
),
|
||||||
|
).toRequestBody()
|
||||||
|
|
||||||
|
return POST("$apiUrl/manga_viewer", headers, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
|
val data = response.parseAs<MangaViewerResponse>()
|
||||||
|
|
||||||
|
val pages = data.pages
|
||||||
|
.filter { it.image?.isExtraPage == false }
|
||||||
|
.mapNotNull { it.image }
|
||||||
|
|
||||||
|
return pages.mapIndexed { idx, page ->
|
||||||
|
Page(
|
||||||
|
index = idx,
|
||||||
|
imageUrl = if (page.encryptionKey.isEmpty() && page.iv.isEmpty()) {
|
||||||
|
cdnUrl + page.imageUrl
|
||||||
|
} else {
|
||||||
|
"$cdnUrl${page.imageUrl}".toHttpUrl().newBuilder()
|
||||||
|
.addQueryParameter("key", page.encryptionKey)
|
||||||
|
.addQueryParameter("iv", page.iv)
|
||||||
|
.toString()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun imageUrlParse(response: Response): String {
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified T> Response.parseAs(): T {
|
||||||
|
return ProtoBuf.decodeFromByteArray(body.bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified T : Any> T.toRequestBody(): RequestBody {
|
||||||
|
return ProtoBuf.encodeToByteArray(this)
|
||||||
|
.toRequestBody("application/protobuf".toMediaType())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.ja.comicfuz
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.protobuf.ProtoNumber
|
||||||
|
import java.text.ParseException
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class MangaListResponse(
|
||||||
|
@ProtoNumber(1) val mangas: List<Manga>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class SearchResponse(
|
||||||
|
@ProtoNumber(2) val mangas: List<Manga>,
|
||||||
|
@ProtoNumber(6) val pageCountOfMangas: Int = 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Manga(
|
||||||
|
@ProtoNumber(1) private val id: Int,
|
||||||
|
@ProtoNumber(2) private val title: String,
|
||||||
|
@ProtoNumber(4) private val cover: String,
|
||||||
|
@ProtoNumber(14) private val description: String,
|
||||||
|
) {
|
||||||
|
fun toSManga(cdnUrl: String): SManga = SManga.create().apply {
|
||||||
|
url = "/manga/$id"
|
||||||
|
title = this@Manga.title
|
||||||
|
thumbnail_url = cdnUrl + cover
|
||||||
|
description = this@Manga.description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class MangaDetailsResponse(
|
||||||
|
@ProtoNumber(2) private val manga: Manga,
|
||||||
|
@ProtoNumber(3) val chapterGroups: List<ChapterGroup>,
|
||||||
|
@ProtoNumber(4) private val authors: List<Author>,
|
||||||
|
@ProtoNumber(7) private val tags: List<Name>,
|
||||||
|
) {
|
||||||
|
fun toSManga(cdnUrl: String) = manga.toSManga(cdnUrl).apply {
|
||||||
|
genre = tags.joinToString { it.name }
|
||||||
|
author = authors.joinToString { it.author.name }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Author(
|
||||||
|
@ProtoNumber(1) val author: Name,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Name(
|
||||||
|
@ProtoNumber(2) val name: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class ChapterGroup(
|
||||||
|
@ProtoNumber(2) val chapters: List<Chapter>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Chapter(
|
||||||
|
@ProtoNumber(1) private val id: Int,
|
||||||
|
@ProtoNumber(2) private val title: String,
|
||||||
|
@ProtoNumber(5) private val points: Point,
|
||||||
|
@ProtoNumber(8) private val date: String = "",
|
||||||
|
) {
|
||||||
|
fun toSChapter() = SChapter.create().apply {
|
||||||
|
url = "/manga/viewer/$id"
|
||||||
|
name = if (points.amount > 0) {
|
||||||
|
"\uD83D\uDD12 $title" // lock emoji
|
||||||
|
} else {
|
||||||
|
title
|
||||||
|
}
|
||||||
|
date_upload = try {
|
||||||
|
dateFormat.parse(date)!!.time
|
||||||
|
} catch (_: ParseException) {
|
||||||
|
0L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val dateFormat = SimpleDateFormat("yyyy/MM/dd", Locale.ENGLISH)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Point(
|
||||||
|
@ProtoNumber(2) val amount: Int = 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class MangaViewerResponse(
|
||||||
|
@ProtoNumber(3) val pages: List<ViewerPage>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class ViewerPage(
|
||||||
|
@ProtoNumber(1) val image: Image? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Image(
|
||||||
|
@ProtoNumber(1) val imageUrl: String,
|
||||||
|
@ProtoNumber(3) val iv: String = "",
|
||||||
|
@ProtoNumber(4) val encryptionKey: String = "",
|
||||||
|
@ProtoNumber(7) val isExtraPage: Boolean = false,
|
||||||
|
)
|
|
@ -0,0 +1,78 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.ja.comicfuz
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
|
||||||
|
fun getFilters() = FilterList(
|
||||||
|
TagFilter(),
|
||||||
|
Filter.Separator(),
|
||||||
|
Filter.Header("Doesn't work with text search"),
|
||||||
|
)
|
||||||
|
|
||||||
|
class TagFilter : Filter.Select<String>("Tags", tags.map { it.name }.toTypedArray()) {
|
||||||
|
val selected get() = when (state) {
|
||||||
|
0 -> null
|
||||||
|
else -> tags[state].id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Tag(
|
||||||
|
val id: Int,
|
||||||
|
val name: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val tags = listOf(
|
||||||
|
Tag(-1, ""),
|
||||||
|
Tag(7, "日曜日"),
|
||||||
|
Tag(12, "オリジナル"),
|
||||||
|
Tag(38, "グルメ"),
|
||||||
|
Tag(138, "FUZコミックス"),
|
||||||
|
Tag(288, "広告で人気の作品"),
|
||||||
|
Tag(462, "オリジナル作品の最新話が無料化!"),
|
||||||
|
Tag(540, "ギャグ・コメディ"),
|
||||||
|
Tag(552, "日常"),
|
||||||
|
Tag(23, "学園"),
|
||||||
|
Tag(26, "SF・ファンタジー"),
|
||||||
|
Tag(29, "恋愛"),
|
||||||
|
Tag(13, "男性向け"),
|
||||||
|
Tag(549, "百合"),
|
||||||
|
Tag(41, "お仕事・趣味"),
|
||||||
|
Tag(56, "週刊漫画TIMES"),
|
||||||
|
Tag(150, "芳文社コミックス"),
|
||||||
|
Tag(537, "スポーツ"),
|
||||||
|
Tag(68, "まんがタイムきららフォワード"),
|
||||||
|
Tag(141, "まんがタイムKRコミックス"),
|
||||||
|
Tag(291, "新規連載作品"),
|
||||||
|
Tag(204, "まんがタイムオリジナル"),
|
||||||
|
Tag(6, "土曜日"),
|
||||||
|
Tag(1274, "6/3発売 FUZオリジナル作品新刊"),
|
||||||
|
Tag(2, "火曜日"),
|
||||||
|
Tag(14, "女性向け"),
|
||||||
|
Tag(44, "バトル・アクション"),
|
||||||
|
Tag(47, "ミステリー・サスペンス"),
|
||||||
|
Tag(83, "BL"),
|
||||||
|
Tag(32, "メディア化"),
|
||||||
|
Tag(50, "歴史・時代"),
|
||||||
|
Tag(20, "4コマ"),
|
||||||
|
Tag(147, "まんがタイムコミックス"),
|
||||||
|
Tag(5, "金曜日"),
|
||||||
|
Tag(543, "異世界"),
|
||||||
|
Tag(35, "ヒューマンドラマ"),
|
||||||
|
Tag(65, "まんがタイムきららキャラット"),
|
||||||
|
Tag(4, "木曜日"),
|
||||||
|
Tag(59, "まんがタイムきらら"),
|
||||||
|
Tag(153, "ラバココミックス"),
|
||||||
|
Tag(201, "まんがタイム"),
|
||||||
|
Tag(3, "水曜日"),
|
||||||
|
Tag(62, "まんがタイムきららMAX"),
|
||||||
|
Tag(17, "読切"),
|
||||||
|
Tag(1, "月曜日"),
|
||||||
|
Tag(74, "ゆるキャン△"),
|
||||||
|
Tag(207, "コミックトレイル"),
|
||||||
|
Tag(77, "城下町のダンデライオン"),
|
||||||
|
Tag(156, "トレイルコミックス"),
|
||||||
|
Tag(198, "まんがホーム"),
|
||||||
|
Tag(71, "魔法少女まどか☆マギカ"),
|
||||||
|
Tag(177, "花音コミックス"),
|
||||||
|
Tag(1175, "価格改定対象作品"),
|
||||||
|
)
|
|
@ -0,0 +1,52 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.ja.comicfuz
|
||||||
|
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import okhttp3.Response
|
||||||
|
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
import javax.crypto.spec.IvParameterSpec
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
|
object ImageInterceptor : Interceptor {
|
||||||
|
private val mediaType = "image/jpeg".toMediaType()
|
||||||
|
|
||||||
|
private inline val AES: Cipher
|
||||||
|
get() = Cipher.getInstance("AES/CBC/PKCS7Padding")
|
||||||
|
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
val url = chain.request().url
|
||||||
|
val key = url.queryParameter("key")
|
||||||
|
?: return chain.proceed(chain.request())
|
||||||
|
val iv = url.queryParameter("iv")!!
|
||||||
|
|
||||||
|
val response = chain.proceed(
|
||||||
|
chain.request().newBuilder().url(
|
||||||
|
url.newBuilder()
|
||||||
|
.removeAllQueryParameters("key")
|
||||||
|
.removeAllQueryParameters("iv")
|
||||||
|
.build(),
|
||||||
|
).build(),
|
||||||
|
)
|
||||||
|
|
||||||
|
val body = response.body.bytes()
|
||||||
|
.decode(key.decodeHex(), iv.decodeHex())
|
||||||
|
|
||||||
|
return response.newBuilder()
|
||||||
|
.body(body)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ByteArray.decode(key: ByteArray, iv: ByteArray) = AES.let {
|
||||||
|
it.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
|
||||||
|
it.doFinal(this).toResponseBody(mediaType)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.decodeHex(): ByteArray {
|
||||||
|
check(length % 2 == 0) { "Must have an even length" }
|
||||||
|
|
||||||
|
return chunked(2)
|
||||||
|
.map { it.toInt(16).toByte() }
|
||||||
|
.toByteArray()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.ja.comicfuz
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.protobuf.ProtoNumber
|
||||||
|
import java.util.Calendar
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class DeviceInfo(
|
||||||
|
@ProtoNumber(3) private val deviceType: DeviceType,
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class DeviceType {
|
||||||
|
IOS,
|
||||||
|
ANDROID,
|
||||||
|
BROWSER,
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class DayOfWeekRequest(
|
||||||
|
@ProtoNumber(1) private val deviceInfo: DeviceInfo,
|
||||||
|
@ProtoNumber(2) private val dayOfWeek: DayOfWeek,
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class DayOfWeek(private val dayNum: Int) {
|
||||||
|
ALL(0),
|
||||||
|
MONDAY(1),
|
||||||
|
TUESDAY(2),
|
||||||
|
WEDNESDAY(3),
|
||||||
|
THURSDAY(4),
|
||||||
|
FRIDAY(5),
|
||||||
|
SATURDAY(6),
|
||||||
|
SUNDAY(7),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun today(): DayOfWeek {
|
||||||
|
val calendar = Calendar.getInstance()
|
||||||
|
val dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK)
|
||||||
|
val adjustedDayOfWeek = if (dayOfWeek == Calendar.SUNDAY) 7 else dayOfWeek - 1
|
||||||
|
|
||||||
|
return values().first { it.dayNum == adjustedDayOfWeek }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class SearchRequest(
|
||||||
|
@ProtoNumber(1) private val deviceInfo: DeviceInfo,
|
||||||
|
@ProtoNumber(2) private val query: String,
|
||||||
|
@ProtoNumber(3) private val pageIndexOfMangas: Int,
|
||||||
|
@ProtoNumber(4) private val pageIndexOfBooks: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class MangaListRequest(
|
||||||
|
@ProtoNumber(1) private val deviceInfo: DeviceInfo,
|
||||||
|
@ProtoNumber(2) private val tagId: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class MangaDetailsRequest(
|
||||||
|
@ProtoNumber(1) private val deviceInfo: DeviceInfo,
|
||||||
|
@ProtoNumber(2) private val mangaId: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class MangaViewerRequest(
|
||||||
|
@ProtoNumber(1) private val deviceInfo: DeviceInfo,
|
||||||
|
@ProtoNumber(2) private val chapterId: Int,
|
||||||
|
@ProtoNumber(3) private val useTicket: Boolean,
|
||||||
|
@ProtoNumber(4) private val consumePoint: UserPoint,
|
||||||
|
@ProtoNumber(5) private val viewerMode: ViewerMode,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class UserPoint(
|
||||||
|
@ProtoNumber(1) private val event: Int,
|
||||||
|
@ProtoNumber(2) private val paid: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class ViewerMode(
|
||||||
|
@ProtoNumber(1) private val imageQuality: ImageQuality,
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class ImageQuality {
|
||||||
|
NORMAL,
|
||||||
|
HIGH,
|
||||||
|
}
|
Loading…
Reference in New Issue