Atsumaru: Use new API (#10705)
* update api * add manga type to genre * oops * show type first
This commit is contained in:
parent
d824aa0a17
commit
e70acec541
@ -1,7 +1,7 @@
|
||||
ext {
|
||||
extName = 'Atsumaru'
|
||||
extClass = '.Atsumaru'
|
||||
extVersionCode = 2
|
||||
extVersionCode = 3
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
@ -8,19 +8,18 @@ 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.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import keiyoushi.utils.parseAs
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class Atsumaru : HttpSource() {
|
||||
|
||||
override val versionId = 2
|
||||
|
||||
override val name = "Atsumaru"
|
||||
|
||||
override val baseUrl = "https://atsu.moe"
|
||||
private val apiUrl = "$baseUrl/api/v1"
|
||||
|
||||
override val lang = "en"
|
||||
|
||||
@ -32,29 +31,27 @@ class Atsumaru : HttpSource() {
|
||||
|
||||
private fun apiHeadersBuilder() = headersBuilder().apply {
|
||||
add("Accept", "*/*")
|
||||
add("Host", apiUrl.toHttpUrl().host)
|
||||
add("Host", "atsu.moe")
|
||||
}
|
||||
|
||||
private val apiHeaders by lazy { apiHeadersBuilder().build() }
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
// ============================== Popular ===============================
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
return GET("$apiUrl/layouts/s1/sliders/hotUpdates", apiHeaders)
|
||||
return GET("$baseUrl/api/infinite/trending?page=${page - 1}", apiHeaders)
|
||||
}
|
||||
|
||||
override fun popularMangaParse(response: Response): MangasPage {
|
||||
val data = response.parseAs<BrowseMangaDto>().items
|
||||
|
||||
return MangasPage(data.map { it.manga.toSManga() }, false)
|
||||
return MangasPage(data.map { it.toSManga(baseUrl) }, true)
|
||||
}
|
||||
|
||||
// =============================== Latest ===============================
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
return GET("$apiUrl/layouts/s1/latest-updates", apiHeaders)
|
||||
return GET("$baseUrl/api/infinite/recentlyUpdated?page=${page - 1}", apiHeaders)
|
||||
}
|
||||
|
||||
override fun latestUpdatesParse(response: Response): MangasPage {
|
||||
@ -64,31 +61,38 @@ class Atsumaru : HttpSource() {
|
||||
// =============================== Search ===============================
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val url = "$apiUrl/search".toHttpUrl().newBuilder()
|
||||
.addPathSegment(query)
|
||||
val url = "$baseUrl/collections/manga/documents/search".toHttpUrl().newBuilder()
|
||||
.addQueryParameter("q", query)
|
||||
.addQueryParameter("query_by", "title,englishTitle,otherNames")
|
||||
.addQueryParameter("limit", "24")
|
||||
.addQueryParameter("page", page.toString())
|
||||
.addQueryParameter("query_by_weights", "3,2,1")
|
||||
.addQueryParameter("include_fields", "id,title,englishTitle,poster")
|
||||
.addQueryParameter("num_typos", "4,3,2")
|
||||
.addQueryParameter("page", page.toString())
|
||||
.build()
|
||||
|
||||
return GET(url, apiHeaders)
|
||||
}
|
||||
|
||||
override fun searchMangaParse(response: Response): MangasPage {
|
||||
val data = response.parseAs<SearchResultsDto>().hits
|
||||
val data = response.parseAs<SearchResultsDto>()
|
||||
|
||||
return MangasPage(data.map { it.info.toSManga() }, false)
|
||||
return MangasPage(data.hits.map { it.document.toSManga(baseUrl) }, data.hasNextPage())
|
||||
}
|
||||
|
||||
// =========================== Manga Details ============================
|
||||
|
||||
override fun getMangaUrl(manga: SManga): String {
|
||||
return baseUrl + manga.url
|
||||
return "$baseUrl/manga/${manga.url}"
|
||||
}
|
||||
|
||||
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||
return GET(apiUrl + manga.url, apiHeaders)
|
||||
return GET("$baseUrl/api/manga/page?id=${manga.url}", apiHeaders)
|
||||
}
|
||||
|
||||
override fun mangaDetailsParse(response: Response): SManga {
|
||||
return response.parseAs<MangaObjectDto>().manga.toSManga()
|
||||
return response.parseAs<MangaObjectDto>().mangaPage.toSManga(baseUrl)
|
||||
}
|
||||
|
||||
// ============================== Chapters ==============================
|
||||
@ -98,38 +102,31 @@ class Atsumaru : HttpSource() {
|
||||
}
|
||||
|
||||
override fun chapterListParse(response: Response): List<SChapter> {
|
||||
val chapterList = response.parseAs<MangaObjectDto>().manga.chapters!!.map {
|
||||
return response.parseAs<MangaObjectDto>().mangaPage.chapters!!.map {
|
||||
it.toSChapter(response.request.url.pathSegments.last())
|
||||
}
|
||||
|
||||
return chapterList.sortedWith(
|
||||
compareBy(
|
||||
{ it.chapter_number },
|
||||
{ it.scanlator },
|
||||
),
|
||||
).reversed()
|
||||
}
|
||||
|
||||
override fun getChapterUrl(chapter: SChapter): String {
|
||||
val (slug, name) = chapter.url.split("/")
|
||||
return "$baseUrl/read/s1/$slug/$name/1"
|
||||
return "$baseUrl/read/$slug/$name"
|
||||
}
|
||||
|
||||
// =============================== Pages ================================
|
||||
|
||||
override fun pageListRequest(chapter: SChapter): Request {
|
||||
val (slug, name) = chapter.url.split("/")
|
||||
return GET("$apiUrl/manga/s1/$slug#$name", apiHeaders)
|
||||
val url = "$baseUrl/api/read/chapter".toHttpUrl().newBuilder()
|
||||
.addQueryParameter("mangaId", slug)
|
||||
.addQueryParameter("chapterId", name)
|
||||
|
||||
return GET(url.build(), apiHeaders)
|
||||
}
|
||||
|
||||
override fun pageListParse(response: Response): List<Page> {
|
||||
val chapter = response.parseAs<MangaObjectDto>().manga.chapters!!.first {
|
||||
it.name == response.request.url.fragment
|
||||
return response.parseAs<PageObjectDto>().readChapter.pages.mapIndexed { index, page ->
|
||||
Page(index, imageUrl = baseUrl + page.image)
|
||||
}
|
||||
|
||||
return chapter.pages.map { page ->
|
||||
Page(page.name.toInt(), imageUrl = page.pageURLs.first())
|
||||
}.sortedBy { it.index }
|
||||
}
|
||||
|
||||
override fun imageRequest(page: Page): Request {
|
||||
@ -144,10 +141,4 @@ class Atsumaru : HttpSource() {
|
||||
override fun imageUrlParse(response: Response): String {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
// ============================= Utilities ==============================
|
||||
|
||||
private inline fun <reified T> Response.parseAs(): T {
|
||||
return json.decodeFromString(body.string())
|
||||
}
|
||||
}
|
||||
|
@ -2,92 +2,116 @@ package eu.kanade.tachiyomi.extension.en.atsumaru
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonNames
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
@Serializable
|
||||
class BrowseMangaDto(
|
||||
val items: List<MangaObjectDto>,
|
||||
val items: List<MangaDto>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class MangaObjectDto(
|
||||
val manga: MangaDto,
|
||||
val mangaPage: MangaDto,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class SearchResultsDto(
|
||||
val page: Int,
|
||||
val found: Int,
|
||||
val hits: List<SearchMangaDto>,
|
||||
@SerialName("request_params") val requestParams: RequestParamsDto,
|
||||
) {
|
||||
fun hasNextPage(): Boolean {
|
||||
return page * requestParams.perPage < found
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class SearchMangaDto(
|
||||
val info: MangaDto,
|
||||
val document: MangaDto,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class RequestParamsDto(
|
||||
@SerialName("per_page") val perPage: Int,
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class MangaDto(
|
||||
// Common
|
||||
private val id: String,
|
||||
private val title: String,
|
||||
private val cover: String,
|
||||
private val slug: String,
|
||||
@JsonNames("poster", "image")
|
||||
private val imagePath: JsonElement,
|
||||
|
||||
// Details
|
||||
private val authors: List<String>? = null,
|
||||
private val description: String? = null,
|
||||
private val genres: List<String>? = null,
|
||||
private val statuses: List<String>? = null,
|
||||
private val authors: List<AuthorDto>? = null,
|
||||
private val synopsis: String? = null,
|
||||
private val tags: List<TagDto>? = null,
|
||||
private val status: String? = null,
|
||||
private val type: String? = null,
|
||||
|
||||
// Chapters
|
||||
val chapters: List<ChapterDto>? = null,
|
||||
) {
|
||||
fun toSManga(): SManga = SManga.create().apply {
|
||||
title = this@MangaDto.title
|
||||
thumbnail_url = cover
|
||||
url = "/manga/s1/$slug"
|
||||
private fun getImagePath(): String? = when (imagePath) {
|
||||
is JsonPrimitive -> imagePath.content
|
||||
is JsonObject -> imagePath["image"]?.jsonPrimitive?.content
|
||||
else -> null
|
||||
}
|
||||
|
||||
fun toSManga(baseUrl: String): SManga = SManga.create().apply {
|
||||
url = id
|
||||
title = this@MangaDto.title
|
||||
thumbnail_url = getImagePath().let { it -> baseUrl + it }
|
||||
description = synopsis
|
||||
genre = buildList {
|
||||
type?.let { add(it) }
|
||||
tags?.forEach { add(it.name) }
|
||||
}.joinToString()
|
||||
authors?.let {
|
||||
author = it.joinToString()
|
||||
author = it.joinToString { author -> author.name }
|
||||
}
|
||||
description = this@MangaDto.description
|
||||
genres?.let {
|
||||
genre = it.joinToString()
|
||||
}
|
||||
statuses?.let {
|
||||
status = when (it.first().lowercase().substringBefore(" ")) {
|
||||
this@MangaDto.status?.let {
|
||||
status = when (it.lowercase().trim()) {
|
||||
"ongoing" -> SManga.ONGOING
|
||||
"complete" -> SManga.COMPLETED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class TagDto(
|
||||
val name: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class AuthorDto(
|
||||
val name: String,
|
||||
)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class ChapterDto(
|
||||
val pages: List<PageDto>,
|
||||
val name: String,
|
||||
private val type: String,
|
||||
private val title: String? = null,
|
||||
private val date: String? = null,
|
||||
private val id: String,
|
||||
private val number: Float,
|
||||
private val title: String,
|
||||
@SerialName("createdAt") private val date: String? = null,
|
||||
) {
|
||||
fun toSChapter(slug: String): SChapter = SChapter.create().apply {
|
||||
val chapterNumber = this@ChapterDto.name.replace("_", ".")
|
||||
.filter { it.isDigit() || it == '.' }
|
||||
|
||||
name = buildString {
|
||||
append("Chapter ")
|
||||
append(chapterNumber)
|
||||
if (title != null) {
|
||||
append(" - ")
|
||||
append(title)
|
||||
}
|
||||
}
|
||||
url = "$slug/${this@ChapterDto.name}"
|
||||
chapter_number = chapterNumber.toFloat()
|
||||
scanlator = type.takeUnless { it == "Chapter" }
|
||||
url = "$slug/$id"
|
||||
chapter_number = number
|
||||
name = title
|
||||
date?.let {
|
||||
date_upload = parseDate(it)
|
||||
}
|
||||
@ -109,7 +133,16 @@ class ChapterDto(
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class PageDto(
|
||||
val pageURLs: List<String>,
|
||||
val name: String,
|
||||
class PageObjectDto(
|
||||
val readChapter: PageDto,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class PageDto(
|
||||
val pages: List<PageDataDto>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class PageDataDto(
|
||||
val image: String,
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user