Add Kouhai Scanlations (#9241)
* Add Kouhai Scanlations * Fix filters * Add chapter names
This commit is contained in:
parent
17c02bfec4
commit
b6fb13ec97
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest package="eu.kanade.tachiyomi.extension" />
|
|
@ -0,0 +1,12 @@
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlinx-serialization'
|
||||||
|
|
||||||
|
ext {
|
||||||
|
extName = 'Kouhai Scanlations'
|
||||||
|
pkgNameSuffix = 'en.kouhaiwork'
|
||||||
|
extClass = '.KouhaiWork'
|
||||||
|
extVersionCode = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 8.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
After Width: | Height: | Size: 131 KiB |
|
@ -0,0 +1,56 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.en.kouhaiwork
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
|
||||||
|
class Genre(val id: Int, name: String) : Filter.CheckBox(name)
|
||||||
|
|
||||||
|
private val genres: List<Genre>
|
||||||
|
get() = listOf(
|
||||||
|
Genre(1, "Romance"),
|
||||||
|
Genre(2, "Comedy"),
|
||||||
|
Genre(3, "Slice of Life"),
|
||||||
|
Genre(4, "Fantasy"),
|
||||||
|
Genre(5, "Sci-Fi"),
|
||||||
|
Genre(6, "Psychological"),
|
||||||
|
Genre(7, "Horror"),
|
||||||
|
Genre(8, "Mystery"),
|
||||||
|
Genre(9, "Girls' Love"),
|
||||||
|
Genre(10, "Drama"),
|
||||||
|
Genre(11, "Action"),
|
||||||
|
Genre(12, "Ecchi"),
|
||||||
|
Genre(13, "Adventure"),
|
||||||
|
)
|
||||||
|
|
||||||
|
class GenresFilter(values: List<Genre> = genres) :
|
||||||
|
Filter.Group<Genre>("Genres", values)
|
||||||
|
|
||||||
|
class Theme(val id: Int, name: String) : Filter.CheckBox(name)
|
||||||
|
|
||||||
|
private val themes: List<Theme>
|
||||||
|
get() = listOf(
|
||||||
|
Theme(1, "Office Workers"),
|
||||||
|
Theme(2, "Family"),
|
||||||
|
Theme(3, "Supernatural"),
|
||||||
|
Theme(4, "Demons"),
|
||||||
|
Theme(5, "Magic"),
|
||||||
|
Theme(6, "Aliens"),
|
||||||
|
Theme(7, "Suggestive"),
|
||||||
|
Theme(8, "Doujinshi"),
|
||||||
|
Theme(9, "School Life"),
|
||||||
|
Theme(10, "Police"),
|
||||||
|
)
|
||||||
|
|
||||||
|
class ThemesFilter(values: List<Theme> = themes) :
|
||||||
|
Filter.Group<Theme>("Themes", values)
|
||||||
|
|
||||||
|
private val demographics: Array<String>
|
||||||
|
get() = arrayOf("Any", "Shounen", "Shoujo", "Seinen")
|
||||||
|
|
||||||
|
class DemographicsFilter(values: Array<String> = demographics) :
|
||||||
|
Filter.Select<String>("Demographic", values)
|
||||||
|
|
||||||
|
private val statuses: Array<String>
|
||||||
|
get() = arrayOf("Any", "Ongoing", "Finished", "Axed/Dropped")
|
||||||
|
|
||||||
|
class StatusFilter(values: Array<String> = statuses) :
|
||||||
|
Filter.Select<String>("Status", values)
|
|
@ -0,0 +1,39 @@
|
||||||
|
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 artists: List<String>,
|
||||||
|
val authors: List<String>,
|
||||||
|
val alternative_titles: List<String>,
|
||||||
|
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?
|
||||||
|
)
|
|
@ -0,0 +1,151 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.en.kouhaiwork
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
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.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
|
||||||
|
|
||||||
|
class KouhaiWork : HttpSource() {
|
||||||
|
override val name = "Kouhai Scanlations"
|
||||||
|
|
||||||
|
override val baseUrl = "https://kouhai.work"
|
||||||
|
|
||||||
|
override val lang = "en"
|
||||||
|
|
||||||
|
override val supportsLatest = true
|
||||||
|
|
||||||
|
private val json by injectLazy<Json>()
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int) =
|
||||||
|
GET("$API_URL/manga/week", headers)
|
||||||
|
|
||||||
|
override fun latestUpdatesParse(response: Response) =
|
||||||
|
response.parse()["data"]?.jsonArray?.map {
|
||||||
|
val arr = it.jsonArray
|
||||||
|
SManga.create().apply {
|
||||||
|
url = arr[0].jsonPrimitive.content
|
||||||
|
title = arr[1].jsonPrimitive.content
|
||||||
|
thumbnail_url = arr.last().jsonPrimitive.content
|
||||||
|
}
|
||||||
|
}.let { MangasPage(it ?: emptyList(), false) }
|
||||||
|
|
||||||
|
override fun popularMangaRequest(page: Int) =
|
||||||
|
GET("$API_URL/manga/all", headers)
|
||||||
|
|
||||||
|
override fun popularMangaParse(response: Response) =
|
||||||
|
latestUpdatesParse(response)
|
||||||
|
|
||||||
|
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()) }
|
||||||
|
|
||||||
|
override fun searchMangaParse(response: Response) =
|
||||||
|
latestUpdatesParse(response)
|
||||||
|
|
||||||
|
// Request the actual manga URL for the webview
|
||||||
|
override fun mangaDetailsRequest(manga: SManga) =
|
||||||
|
GET("$baseUrl/series/${manga.url}", headers)
|
||||||
|
|
||||||
|
override fun fetchMangaDetails(manga: SManga) =
|
||||||
|
client.newCall(chapterListRequest(manga)).asObservableSuccess().map {
|
||||||
|
val series = it.data<KouhaiSeries>()
|
||||||
|
manga.description = series.synopsis
|
||||||
|
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.status = when (series.status) {
|
||||||
|
"ongoing" -> SManga.ONGOING
|
||||||
|
"finished" -> SManga.COMPLETED
|
||||||
|
else -> SManga.UNKNOWN
|
||||||
|
}
|
||||||
|
manga.initialized = true
|
||||||
|
return@map manga
|
||||||
|
}!!
|
||||||
|
|
||||||
|
override fun chapterListRequest(manga: SManga) =
|
||||||
|
GET("$API_URL/mangas/${manga.url}", headers)
|
||||||
|
|
||||||
|
override fun chapterListParse(response: Response) =
|
||||||
|
response.data<KouhaiSeries>().chapters.map {
|
||||||
|
SChapter.create().apply {
|
||||||
|
url = it.id.toString()
|
||||||
|
scanlator = it.group
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListRequest(chapter: SChapter) =
|
||||||
|
GET("$API_URL/chapters/${chapter.url}", headers)
|
||||||
|
|
||||||
|
override fun pageListParse(response: Response) =
|
||||||
|
response.parse()["chapter"]!!.jsonObject["pages"]!!
|
||||||
|
.jsonArray.mapIndexed { idx, obj ->
|
||||||
|
Page(idx, "", obj.jsonObject["media"]!!.jsonPrimitive.content)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFilterList() =
|
||||||
|
FilterList(GenresFilter(), ThemesFilter(), DemographicsFilter(), StatusFilter())
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(response: Response) =
|
||||||
|
throw UnsupportedOperationException("Not used")
|
||||||
|
|
||||||
|
override fun imageUrlParse(response: Response) =
|
||||||
|
throw UnsupportedOperationException("Not used")
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
private inline fun FilterList.json() =
|
||||||
|
json.encodeToJsonElement(
|
||||||
|
KouhaiTagList(
|
||||||
|
find<GenresFilter>()?.state?.filter { it.state }
|
||||||
|
?.map { KouhaiTag(it.id) } ?: emptyList(),
|
||||||
|
find<ThemesFilter>()?.state?.filter { it.state }
|
||||||
|
?.map { KouhaiTag(it.id) } ?: emptyList(),
|
||||||
|
find<DemographicsFilter>()?.state?.takeIf { it != 0 }
|
||||||
|
?.let { listOf(KouhaiTag(it)) } ?: emptyList(),
|
||||||
|
find<StatusFilter>()?.state?.takeIf { it != 0 }
|
||||||
|
?.let { KouhaiTag(it - 1) }
|
||||||
|
)
|
||||||
|
).toString()
|
||||||
|
|
||||||
|
@Suppress("NOTHING_TO_INLINE")
|
||||||
|
private inline fun Response.parse() =
|
||||||
|
json.parseToJsonElement(body!!.string()).jsonObject
|
||||||
|
|
||||||
|
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