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