Kouhai Work: update API (#9559)
This commit is contained in:
parent
96b34410d6
commit
00f441e4df
|
@ -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"
|
||||||
|
|
|
@ -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()
|
|
@ -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
|
||||||
|
|
|
@ -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?
|
|
||||||
)
|
|
|
@ -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() =
|
|
||||||
json.encodeToJsonElement(
|
|
||||||
KouhaiTagList(
|
KouhaiTagList(
|
||||||
find<GenresFilter>()?.state?.filter { it.state }
|
find<GenresFilter>()?.state?.filter { it.state }?.map {
|
||||||
?.map { KouhaiTag(it.id) } ?: emptyList(),
|
KouhaiTag(it.id, it.name)
|
||||||
find<ThemesFilter>()?.state?.filter { it.state }
|
} ?: emptyList(),
|
||||||
?.map { KouhaiTag(it.id) } ?: emptyList(),
|
find<ThemesFilter>()?.state?.filter { it.state }?.map {
|
||||||
find<DemographicsFilter>()?.state?.takeIf { it != 0 }
|
KouhaiTag(it.id, it.name)
|
||||||
?.let { listOf(KouhaiTag(it)) } ?: emptyList(),
|
} ?: emptyList(),
|
||||||
find<StatusFilter>()?.state?.takeIf { it != 0 }
|
find<DemographicsFilter>()?.takeIf { it.state != 0 }?.let {
|
||||||
?.let { KouhaiTag(it - 1) }
|
listOf(KouhaiTag(it.state, it.values[it.state]))
|
||||||
|
} ?: emptyList(),
|
||||||
|
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("#.##")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue