From 00f441e4df6a12bda5b5bcb2e7b878df4f5241f9 Mon Sep 17 00:00:00 2001 From: ObserverOfTime Date: Wed, 20 Oct 2021 14:17:00 +0300 Subject: [PATCH] Kouhai Work: update API (#9559) --- src/en/kouhaiwork/build.gradle | 4 +- .../extension/en/kouhaiwork/KouhaiAPI.kt | 109 ++++++++++++++ .../extension/en/kouhaiwork/KouhaiFilters.kt | 2 + .../extension/en/kouhaiwork/KouhaiModels.kt | 39 ----- .../extension/en/kouhaiwork/KouhaiWork.kt | 133 ++++++++---------- 5 files changed, 173 insertions(+), 114 deletions(-) create mode 100644 src/en/kouhaiwork/src/eu/kanade/tachiyomi/extension/en/kouhaiwork/KouhaiAPI.kt delete mode 100644 src/en/kouhaiwork/src/eu/kanade/tachiyomi/extension/en/kouhaiwork/KouhaiModels.kt diff --git a/src/en/kouhaiwork/build.gradle b/src/en/kouhaiwork/build.gradle index 7c629977b..e2a1a2c31 100644 --- a/src/en/kouhaiwork/build.gradle +++ b/src/en/kouhaiwork/build.gradle @@ -3,10 +3,10 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlinx-serialization' ext { - extName = 'Kouhai Scanlations' + extName = 'Kouhai Work' pkgNameSuffix = 'en.kouhaiwork' extClass = '.KouhaiWork' - extVersionCode = 2 + extVersionCode = 3 } apply from: "$rootDir/common.gradle" diff --git a/src/en/kouhaiwork/src/eu/kanade/tachiyomi/extension/en/kouhaiwork/KouhaiAPI.kt b/src/en/kouhaiwork/src/eu/kanade/tachiyomi/extension/en/kouhaiwork/KouhaiAPI.kt new file mode 100644 index 000000000..428ae6b81 --- /dev/null +++ b/src/en/kouhaiwork/src/eu/kanade/tachiyomi/extension/en/kouhaiwork/KouhaiAPI.kt @@ -0,0 +1,109 @@ +package eu.kanade.tachiyomi.extension.en.kouhaiwork + +import kotlinx.serialization.Serializable +import java.text.DecimalFormat +import java.text.SimpleDateFormat +import java.util.Locale + +const val API_URL = "https://api.kouhai.work/v3" + +const val STORAGE_URL = "https://api.kouhai.work/storage/" + +private const val ISO_DATE = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'" + +private val dateFormat = SimpleDateFormat(ISO_DATE, Locale.ROOT) + +private val decimalFormat = DecimalFormat("#.##") + +@Serializable +data class KouhaiSeries( + val id: Int, + val title: String, + val cover: String +) { + inline val url get() = id.toString() + + inline val thumbnail get() = STORAGE_URL + cover + + override fun toString() = title.trim() +} + +@Serializable +data class KouhaiSeriesDetails( + val synopsis: String, + val status: String, + val alternative_titles: List, + val artists: List? = null, + val authors: List? = null, + val genres: List? = null, + val themes: List? = null, + val demographics: List? = null, + val chapters: List +) { + val tags by lazy { + genres.orEmpty() + themes.orEmpty() + demographics.orEmpty() + } + + override fun toString() = buildString { + append(synopsis) + append("\n\nAlternative Names:\n") + alternative_titles.joinTo(this, "\n") + } +} + +@Serializable +data class KouhaiChapter( + val id: Int, + val volume: Int? = null, + val number: Float, + val name: String? = null, + val groups: List, + val updated_at: String +) { + inline val url get() = id.toString() + + val timestamp by lazy { + dateFormat.parse(updated_at)?.time ?: 0L + } + + override fun toString() = buildString { + volume?.let { append("[Vol. $it] ") } + append("Chapter ") + append(decimalFormat.format(number)) + name?.let { append(" - $it") } + } +} + +@Serializable +data class KouhaiTag( + private val id: Int, + private val name: String +) { + override fun toString() = name +} + +@Serializable +data class KouhaiTagList( + val genres: List, + val themes: List, + val demographics: List, + val status: KouhaiTag? +) + +@Serializable +data class KouhaiPages( + private val pages: List +) : Iterable by pages + +@Serializable +data class KouhaiMedia(private val media: String) { + override fun toString() = STORAGE_URL + media +} + +typealias KouhaiSearch = List + +inline val KouhaiSearch.url get() = this[0] + +inline val KouhaiSearch.title get() = this[1].trim() + +inline val KouhaiSearch.thumbnail get() = STORAGE_URL + last() diff --git a/src/en/kouhaiwork/src/eu/kanade/tachiyomi/extension/en/kouhaiwork/KouhaiFilters.kt b/src/en/kouhaiwork/src/eu/kanade/tachiyomi/extension/en/kouhaiwork/KouhaiFilters.kt index 01f52eeeb..f9b3e0988 100644 --- a/src/en/kouhaiwork/src/eu/kanade/tachiyomi/extension/en/kouhaiwork/KouhaiFilters.kt +++ b/src/en/kouhaiwork/src/eu/kanade/tachiyomi/extension/en/kouhaiwork/KouhaiFilters.kt @@ -54,3 +54,5 @@ private val statuses: Array class StatusFilter(values: Array = statuses) : Filter.Select("Status", values) + +inline fun List>.find() = find { it is T } as? T diff --git a/src/en/kouhaiwork/src/eu/kanade/tachiyomi/extension/en/kouhaiwork/KouhaiModels.kt b/src/en/kouhaiwork/src/eu/kanade/tachiyomi/extension/en/kouhaiwork/KouhaiModels.kt deleted file mode 100644 index 8363ae17e..000000000 --- a/src/en/kouhaiwork/src/eu/kanade/tachiyomi/extension/en/kouhaiwork/KouhaiModels.kt +++ /dev/null @@ -1,39 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.kouhaiwork - -import kotlinx.serialization.Serializable - -@Serializable -data class KouhaiSeries( - val id: Int, - val title: String, - val cover: String, - val synopsis: String, - val status: String, - val alternative_titles: List, - val artists: List? = null, - val authors: List? = null, - val genres: List? = null, - val themes: List? = null, - val demographics: List? = null, - val chapters: List -) - -@Serializable -data class KouhaiChapter( - val id: Int, - val group: String, - val number: Float, - val updated_at: String, - val name: String? = null -) - -@Serializable -data class KouhaiTag(val id: Int) - -@Serializable -data class KouhaiTagList( - val genres: List, - val themes: List, - val demographics: List, - val status: KouhaiTag? -) diff --git a/src/en/kouhaiwork/src/eu/kanade/tachiyomi/extension/en/kouhaiwork/KouhaiWork.kt b/src/en/kouhaiwork/src/eu/kanade/tachiyomi/extension/en/kouhaiwork/KouhaiWork.kt index b0f8487d6..e724f184d 100644 --- a/src/en/kouhaiwork/src/eu/kanade/tachiyomi/extension/en/kouhaiwork/KouhaiWork.kt +++ b/src/en/kouhaiwork/src/eu/kanade/tachiyomi/extension/en/kouhaiwork/KouhaiWork.kt @@ -12,18 +12,14 @@ import eu.kanade.tachiyomi.source.online.HttpSource import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromJsonElement import kotlinx.serialization.json.encodeToJsonElement -import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive import okhttp3.FormBody import okhttp3.Response -import uy.kohesive.injekt.injectLazy -import java.text.DecimalFormat -import java.text.SimpleDateFormat -import java.util.Locale +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get class KouhaiWork : HttpSource() { - override val name = "Kouhai Scanlations" + override val name = "Kouhai Work" override val baseUrl = "https://kouhai.work" @@ -31,20 +27,23 @@ class KouhaiWork : HttpSource() { override val supportsLatest = true - private val json by injectLazy() + override val id = 1273675602267580928L + + private val json by lazy { + Json(Injekt.get()) { isLenient = true } + } override fun latestUpdatesRequest(page: Int) = - GET("$API_URL/manga/week", headers) + GET("$API_URL/manga/recent", headers) override fun latestUpdatesParse(response: Response) = - response.parse()["data"]?.jsonArray?.map { - val arr = it.jsonArray + response.decode>().map { SManga.create().apply { - url = arr[0].jsonPrimitive.content - title = arr[1].jsonPrimitive.content - thumbnail_url = arr.last().jsonPrimitive.content + url = it.url + title = it.toString() + thumbnail_url = it.thumbnail } - }.let { MangasPage(it ?: emptyList(), false) } + }.let { MangasPage(it, false) } override fun popularMangaRequest(page: Int) = GET("$API_URL/manga/all", headers) @@ -54,10 +53,16 @@ class KouhaiWork : HttpSource() { override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = FormBody.Builder().add("search", query).add("tags", filters.json()) - .let { POST("$API_URL/manga/search", headers, it.build()) } + .let { POST("$API_URL/search/manga", headers, it.build()) } override fun searchMangaParse(response: Response) = - latestUpdatesParse(response) + response.decode>().map { + SManga.create().apply { + url = it.url + title = it.title + thumbnail_url = it.thumbnail + } + }.let { MangasPage(it, false) } // Request the actual manga URL for the webview override fun mangaDetailsRequest(manga: SManga) = @@ -65,18 +70,11 @@ class KouhaiWork : HttpSource() { override fun fetchMangaDetails(manga: SManga) = client.newCall(chapterListRequest(manga)).asObservableSuccess().map { - val series = it.data() - manga.description = buildString { - append(series.synopsis) - append("\n\nAlternative Names:\n") - series.alternative_titles.joinTo(this, "\n") - } + val series = it.decode() + manga.description = series.toString() manga.author = series.authors?.joinToString() manga.artist = series.artists?.joinToString() - manga.genre = series.genres.orEmpty() - .plus(series.themes.orEmpty()) - .plus(series.demographics.orEmpty()) - .joinToString() + manga.genre = series.tags.joinToString() manga.status = when (series.status) { "ongoing" -> SManga.ONGOING "finished" -> SManga.COMPLETED @@ -87,31 +85,32 @@ class KouhaiWork : HttpSource() { }!! override fun chapterListRequest(manga: SManga) = - GET("$API_URL/mangas/${manga.url}", headers) + GET("$API_URL/manga/get/${manga.url}", headers) override fun chapterListParse(response: Response) = - response.data().chapters.map { + response.decode().chapters.map { SChapter.create().apply { - url = it.id.toString() - scanlator = it.group + url = it.url + name = it.toString() chapter_number = it.number - name = "Chapter ${decimalFormat.format(it.number)}" + - if (it.name == null) "" else " - ${it.name}" - date_upload = dateFormat.parse(it.updated_at)?.time ?: 0L + date_upload = it.timestamp + scanlator = it.groups.joinToString() } } override fun pageListRequest(chapter: SChapter) = - GET("$API_URL/chapters/${chapter.url}", headers) + GET("$API_URL/chapters/get/${chapter.url}", headers) override fun pageListParse(response: Response) = - response.parse()["chapter"]!!.jsonObject["pages"]!! - .jsonArray.mapIndexed { idx, obj -> - Page(idx, "", obj.jsonObject["media"]!!.jsonPrimitive.content) - } + response.decode("chapter") + .mapIndexed { idx, img -> Page(idx, "", img.toString()) } - override fun getFilterList() = - FilterList(GenresFilter(), ThemesFilter(), DemographicsFilter(), StatusFilter()) + override fun getFilterList() = FilterList( + GenresFilter(), + ThemesFilter(), + DemographicsFilter(), + StatusFilter() + ) override fun mangaDetailsParse(response: Response) = throw UnsupportedOperationException("Not used") @@ -119,37 +118,25 @@ class KouhaiWork : HttpSource() { override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("Not used") - @Suppress("NOTHING_TO_INLINE") - private inline fun FilterList.json() = - json.encodeToJsonElement( - KouhaiTagList( - find()?.state?.filter { it.state } - ?.map { KouhaiTag(it.id) } ?: emptyList(), - find()?.state?.filter { it.state } - ?.map { KouhaiTag(it.id) } ?: emptyList(), - find()?.state?.takeIf { it != 0 } - ?.let { listOf(KouhaiTag(it)) } ?: emptyList(), - find()?.state?.takeIf { it != 0 } - ?.let { KouhaiTag(it - 1) } - ) - ).toString() + private fun FilterList.json() = json.encodeToJsonElement( + KouhaiTagList( + find()?.state?.filter { it.state }?.map { + KouhaiTag(it.id, it.name) + } ?: emptyList(), + find()?.state?.filter { it.state }?.map { + KouhaiTag(it.id, it.name) + } ?: emptyList(), + find()?.takeIf { it.state != 0 }?.let { + listOf(KouhaiTag(it.state, it.values[it.state])) + } ?: emptyList(), + find()?.takeIf { it.state != 0 }?.let { + KouhaiTag(it.state - 1, it.values[it.state]) + } + ) + ).toString() - @Suppress("NOTHING_TO_INLINE") - private inline fun Response.parse() = - json.parseToJsonElement(body!!.string()).jsonObject - - private inline fun Response.data() = - json.decodeFromJsonElement(parse()["data"]!!) - - private inline fun FilterList.find() = find { it is T } as? T - - companion object { - private const val API_URL = "https://api.kouhai.work/v2" - - private const val ISO_DATE = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'" - - private val dateFormat = SimpleDateFormat(ISO_DATE, Locale.ROOT) - - private val decimalFormat = DecimalFormat("#.##") - } + private inline fun Response.decode(key: String = "data") = + json.decodeFromJsonElement( + json.parseToJsonElement(body!!.string()).jsonObject[key]!! + ) }