Kouhai Work: update API (#9559)

This commit is contained in:
ObserverOfTime 2021-10-20 14:17:00 +03:00 committed by GitHub
parent 96b34410d6
commit 00f441e4df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 173 additions and 114 deletions

View File

@ -3,10 +3,10 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization' apply plugin: 'kotlinx-serialization'
ext { ext {
extName = 'Kouhai Scanlations' extName = 'Kouhai Work'
pkgNameSuffix = 'en.kouhaiwork' pkgNameSuffix = 'en.kouhaiwork'
extClass = '.KouhaiWork' extClass = '.KouhaiWork'
extVersionCode = 2 extVersionCode = 3
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -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<String>,
val artists: List<KouhaiTag>? = null,
val authors: List<KouhaiTag>? = null,
val genres: List<KouhaiTag>? = null,
val themes: List<KouhaiTag>? = null,
val demographics: List<KouhaiTag>? = null,
val chapters: List<KouhaiChapter>
) {
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<KouhaiTag>,
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<KouhaiTag>,
val themes: List<KouhaiTag>,
val demographics: List<KouhaiTag>,
val status: KouhaiTag?
)
@Serializable
data class KouhaiPages(
private val pages: List<KouhaiMedia>
) : Iterable<KouhaiMedia> by pages
@Serializable
data class KouhaiMedia(private val media: String) {
override fun toString() = STORAGE_URL + media
}
typealias KouhaiSearch = List<String>
inline val KouhaiSearch.url get() = this[0]
inline val KouhaiSearch.title get() = this[1].trim()
inline val KouhaiSearch.thumbnail get() = STORAGE_URL + last()

View File

@ -54,3 +54,5 @@ private val statuses: Array<String>
class StatusFilter(values: Array<String> = statuses) : class StatusFilter(values: Array<String> = statuses) :
Filter.Select<String>("Status", values) Filter.Select<String>("Status", values)
inline fun <reified T> List<Filter<*>>.find() = find { it is T } as? T

View File

@ -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<String>,
val artists: List<String>? = null,
val authors: List<String>? = null,
val genres: List<String>? = null,
val themes: List<String>? = null,
val demographics: List<String>? = null,
val chapters: List<KouhaiChapter>
)
@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<KouhaiTag>,
val themes: List<KouhaiTag>,
val demographics: List<KouhaiTag>,
val status: KouhaiTag?
)

View File

@ -12,18 +12,14 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromJsonElement import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.encodeToJsonElement import kotlinx.serialization.json.encodeToJsonElement
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.Response import okhttp3.Response
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.Injekt
import java.text.DecimalFormat import uy.kohesive.injekt.api.get
import java.text.SimpleDateFormat
import java.util.Locale
class KouhaiWork : HttpSource() { class KouhaiWork : HttpSource() {
override val name = "Kouhai Scanlations" override val name = "Kouhai Work"
override val baseUrl = "https://kouhai.work" override val baseUrl = "https://kouhai.work"
@ -31,20 +27,23 @@ class KouhaiWork : HttpSource() {
override val supportsLatest = true override val supportsLatest = true
private val json by injectLazy<Json>() override val id = 1273675602267580928L
private val json by lazy {
Json(Injekt.get()) { isLenient = true }
}
override fun latestUpdatesRequest(page: Int) = override fun latestUpdatesRequest(page: Int) =
GET("$API_URL/manga/week", headers) GET("$API_URL/manga/recent", headers)
override fun latestUpdatesParse(response: Response) = override fun latestUpdatesParse(response: Response) =
response.parse()["data"]?.jsonArray?.map { response.decode<List<KouhaiSeries>>().map {
val arr = it.jsonArray
SManga.create().apply { SManga.create().apply {
url = arr[0].jsonPrimitive.content url = it.url
title = arr[1].jsonPrimitive.content title = it.toString()
thumbnail_url = arr.last().jsonPrimitive.content thumbnail_url = it.thumbnail
} }
}.let { MangasPage(it ?: emptyList(), false) } }.let { MangasPage(it, false) }
override fun popularMangaRequest(page: Int) = override fun popularMangaRequest(page: Int) =
GET("$API_URL/manga/all", headers) GET("$API_URL/manga/all", headers)
@ -54,10 +53,16 @@ class KouhaiWork : HttpSource() {
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
FormBody.Builder().add("search", query).add("tags", filters.json()) 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) = override fun searchMangaParse(response: Response) =
latestUpdatesParse(response) response.decode<List<KouhaiSearch>>().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 // Request the actual manga URL for the webview
override fun mangaDetailsRequest(manga: SManga) = override fun mangaDetailsRequest(manga: SManga) =
@ -65,18 +70,11 @@ class KouhaiWork : HttpSource() {
override fun fetchMangaDetails(manga: SManga) = override fun fetchMangaDetails(manga: SManga) =
client.newCall(chapterListRequest(manga)).asObservableSuccess().map { client.newCall(chapterListRequest(manga)).asObservableSuccess().map {
val series = it.data<KouhaiSeries>() val series = it.decode<KouhaiSeriesDetails>()
manga.description = buildString { manga.description = series.toString()
append(series.synopsis)
append("\n\nAlternative Names:\n")
series.alternative_titles.joinTo(this, "\n")
}
manga.author = series.authors?.joinToString() manga.author = series.authors?.joinToString()
manga.artist = series.artists?.joinToString() manga.artist = series.artists?.joinToString()
manga.genre = series.genres.orEmpty() manga.genre = series.tags.joinToString()
.plus(series.themes.orEmpty())
.plus(series.demographics.orEmpty())
.joinToString()
manga.status = when (series.status) { manga.status = when (series.status) {
"ongoing" -> SManga.ONGOING "ongoing" -> SManga.ONGOING
"finished" -> SManga.COMPLETED "finished" -> SManga.COMPLETED
@ -87,31 +85,32 @@ class KouhaiWork : HttpSource() {
}!! }!!
override fun chapterListRequest(manga: SManga) = 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) = override fun chapterListParse(response: Response) =
response.data<KouhaiSeries>().chapters.map { response.decode<KouhaiSeriesDetails>().chapters.map {
SChapter.create().apply { SChapter.create().apply {
url = it.id.toString() url = it.url
scanlator = it.group name = it.toString()
chapter_number = it.number chapter_number = it.number
name = "Chapter ${decimalFormat.format(it.number)}" + date_upload = it.timestamp
if (it.name == null) "" else " - ${it.name}" scanlator = it.groups.joinToString()
date_upload = dateFormat.parse(it.updated_at)?.time ?: 0L
} }
} }
override fun pageListRequest(chapter: SChapter) = 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) = override fun pageListParse(response: Response) =
response.parse()["chapter"]!!.jsonObject["pages"]!! response.decode<KouhaiPages>("chapter")
.jsonArray.mapIndexed { idx, obj -> .mapIndexed { idx, img -> Page(idx, "", img.toString()) }
Page(idx, "", obj.jsonObject["media"]!!.jsonPrimitive.content)
}
override fun getFilterList() = override fun getFilterList() = FilterList(
FilterList(GenresFilter(), ThemesFilter(), DemographicsFilter(), StatusFilter()) GenresFilter(),
ThemesFilter(),
DemographicsFilter(),
StatusFilter()
)
override fun mangaDetailsParse(response: Response) = override fun mangaDetailsParse(response: Response) =
throw UnsupportedOperationException("Not used") throw UnsupportedOperationException("Not used")
@ -119,37 +118,25 @@ class KouhaiWork : HttpSource() {
override fun imageUrlParse(response: Response) = override fun imageUrlParse(response: Response) =
throw UnsupportedOperationException("Not used") throw UnsupportedOperationException("Not used")
@Suppress("NOTHING_TO_INLINE") private fun FilterList.json() = json.encodeToJsonElement(
private inline fun FilterList.json() = KouhaiTagList(
json.encodeToJsonElement( find<GenresFilter>()?.state?.filter { it.state }?.map {
KouhaiTagList( KouhaiTag(it.id, it.name)
find<GenresFilter>()?.state?.filter { it.state } } ?: emptyList(),
?.map { KouhaiTag(it.id) } ?: emptyList(), find<ThemesFilter>()?.state?.filter { it.state }?.map {
find<ThemesFilter>()?.state?.filter { it.state } KouhaiTag(it.id, it.name)
?.map { KouhaiTag(it.id) } ?: emptyList(), } ?: emptyList(),
find<DemographicsFilter>()?.state?.takeIf { it != 0 } find<DemographicsFilter>()?.takeIf { it.state != 0 }?.let {
?.let { listOf(KouhaiTag(it)) } ?: emptyList(), listOf(KouhaiTag(it.state, it.values[it.state]))
find<StatusFilter>()?.state?.takeIf { it != 0 } } ?: emptyList(),
?.let { KouhaiTag(it - 1) } find<StatusFilter>()?.takeIf { it.state != 0 }?.let {
) KouhaiTag(it.state - 1, it.values[it.state])
).toString() }
)
).toString()
@Suppress("NOTHING_TO_INLINE") private inline fun <reified T> Response.decode(key: String = "data") =
private inline fun Response.parse() = json.decodeFromJsonElement<T>(
json.parseToJsonElement(body!!.string()).jsonObject json.parseToJsonElement(body!!.string()).jsonObject[key]!!
)
private inline fun <reified T> Response.data() =
json.decodeFromJsonElement<T>(parse()["data"]!!)
private inline fun <reified T> 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("#.##")
}
} }