From 0fc23e3cb00d6e4a9fbd2eff58dea583d3dcd5bc Mon Sep 17 00:00:00 2001 From: Chopper <156493704+choppeh@users.noreply.github.com> Date: Mon, 11 Nov 2024 05:34:52 -0300 Subject: [PATCH] Taiyo: Fix popularManga, search and chapterList (#5963) * Fix popularManga, search and chapterList * Typo * Change limit per page * Remove useless code * Cleanup * Fix chapter date_upload --- src/pt/taiyo/AndroidManifest.xml | 2 +- src/pt/taiyo/build.gradle | 2 +- .../tachiyomi/extension/pt/taiyo/Taiyo.kt | 100 ++++++++++++------ .../extension/pt/taiyo/dto/TaiyoDto.kt | 26 +++-- 4 files changed, 80 insertions(+), 50 deletions(-) diff --git a/src/pt/taiyo/AndroidManifest.xml b/src/pt/taiyo/AndroidManifest.xml index 26b60d15a..0b5cebddd 100644 --- a/src/pt/taiyo/AndroidManifest.xml +++ b/src/pt/taiyo/AndroidManifest.xml @@ -13,7 +13,7 @@ diff --git a/src/pt/taiyo/build.gradle b/src/pt/taiyo/build.gradle index 2e28fafce..eafbd5d83 100644 --- a/src/pt/taiyo/build.gradle +++ b/src/pt/taiyo/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Taiyō' extClass = '.Taiyo' - extVersionCode = 1 + extVersionCode = 2 isNsfw = true } diff --git a/src/pt/taiyo/src/eu/kanade/tachiyomi/extension/pt/taiyo/Taiyo.kt b/src/pt/taiyo/src/eu/kanade/tachiyomi/extension/pt/taiyo/Taiyo.kt index 593432ca4..c62157b20 100644 --- a/src/pt/taiyo/src/eu/kanade/tachiyomi/extension/pt/taiyo/Taiyo.kt +++ b/src/pt/taiyo/src/eu/kanade/tachiyomi/extension/pt/taiyo/Taiyo.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.extension.pt.taiyo import eu.kanade.tachiyomi.extension.pt.taiyo.dto.AdditionalInfoDto import eu.kanade.tachiyomi.extension.pt.taiyo.dto.ChapterListDto import eu.kanade.tachiyomi.extension.pt.taiyo.dto.MediaChapterDto -import eu.kanade.tachiyomi.extension.pt.taiyo.dto.ResponseDto import eu.kanade.tachiyomi.extension.pt.taiyo.dto.SearchResultDto import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST @@ -19,6 +18,8 @@ import eu.kanade.tachiyomi.util.asJsoup import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import kotlinx.serialization.json.add +import kotlinx.serialization.json.buildJsonArray import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.decodeFromStream import kotlinx.serialization.json.put @@ -39,11 +40,12 @@ class Taiyo : ParsedHttpSource() { override val name = "Taiyō" - override val baseUrl = "https://www.taiyo.moe" + override val baseUrl = "https://taiyo.moe" override val lang = "pt-BR" - override val supportsLatest = true + // The source doesn't show the title on the home page + override val supportsLatest = false override val client = network.client.newBuilder() .rateLimitHost(baseUrl.toHttpUrl(), 2) @@ -53,34 +55,41 @@ class Taiyo : ParsedHttpSource() { private val json: Json by injectLazy() // ============================== Popular =============================== - override fun popularMangaRequest(page: Int) = GET(baseUrl, headers) + var bearerToken = "" - override fun popularMangaSelector() = "main > div.flex > div.overflow-hidden div.flex > a" - - override fun popularMangaFromElement(element: Element) = SManga.create().apply { - setUrlWithoutDomain(element.attr("href")) - thumbnail_url = element.selectFirst("div.overflow-hidden > img")?.getImageUrl() - title = element.selectFirst("p")!!.text() + override fun popularMangaRequest(page: Int): Request { + if (bearerToken.isBlank()) { + getBearerToken() + } + return searchMangaRequest(page, "", FilterList()) } + override fun popularMangaParse(response: Response) = searchMangaParse(response) + override fun popularMangaSelector() = throw UnsupportedOperationException() + override fun popularMangaFromElement(element: Element) = throw UnsupportedOperationException() override fun popularMangaNextPageSelector() = null - // =============================== Latest =============================== - override fun latestUpdatesRequest(page: Int) = GET(baseUrl, headers) + private fun getBearerToken() { + val scriptUrl = client.newCall(GET(baseUrl, headers)) + .execute().asJsoup() + .selectFirst("script[src*=ee07d8437723d9f5]") + ?.attr("src") ?: throw Exception("Não foi possivel localizar o token") - override fun latestUpdatesSelector() = "main div.grow div.flex:has(div.grow)" + val script = client.newCall(GET("$baseUrl$scriptUrl", headers)) + .execute().body.string() - override fun latestUpdatesFromElement(element: Element) = SManga.create().apply { - with(element.selectFirst("a.line-clamp-1")!!) { - setUrlWithoutDomain(attr("href")) - title = text() - } - thumbnail_url = element.selectFirst("img")?.getImageUrl()?.replace("&w=128", "&w=256") + bearerToken = TOKEN_REGEX.find(script)?.groups?.get("token")?.value + ?: throw Exception("Não foi possivel extrair o token") } + // =============================== Latest =============================== + override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException() + override fun latestUpdatesSelector() = throw UnsupportedOperationException() + override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException() override fun latestUpdatesNextPageSelector() = null // =============================== Search =============================== + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler val id = query.removePrefix(PREFIX_SEARCH) @@ -98,31 +107,48 @@ class Taiyo : ParsedHttpSource() { } override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val limit = 21 + val jsonObj = buildJsonObject { - putJsonObject("0") { - putJsonObject("json") { - put("title", query) - } - } + put( + "queries", + buildJsonArray { + add( + buildJsonObject { + put("indexUid", "medias") + put("q", query) + put("filter", buildJsonArray { add("deletedAt IS NULL") }) + put("limit", limit) + put("offset", limit * (page - 1)) + }, + ) + }, + ) } val requestBody = json.encodeToString(jsonObj).toRequestBody(MEDIA_TYPE) - return POST("$baseUrl/api/trpc/medias.search?batch=1", headers, requestBody) + val apiHeaders = headers.newBuilder() + .set("Authorization", "Bearer $bearerToken") + .build() + + return POST("https://meilisearch.${baseUrl.substringAfterLast("/")}/multi-search", apiHeaders, requestBody) } override fun searchMangaParse(response: Response): MangasPage { - val obj = response.parseAs>>>().first() - val mangas = obj.data.map { item -> + val obj = response.parseAs() + val mangas = obj.mangas.map { item -> SManga.create().apply { url = "/media/${item.id}" - title = item.title + title = item.titles.firstOrNull { it.language.contains("en") }?.title + ?: item.titles.maxByOrNull { it.priority }!!.title + thumbnail_url = item.coverId?.let { "$baseUrl/_next/image?url=$IMG_CDN/${item.id}/covers/$it.jpg&w=256&q=75" } } } - return MangasPage(mangas, false) + return MangasPage(mangas, mangas.isNotEmpty()) } override fun searchMangaSelector(): String { @@ -173,7 +199,7 @@ class Taiyo : ParsedHttpSource() { override fun fetchChapterList(manga: SManga): Observable> { val id = manga.url.substringAfter("/media/").trimEnd('/') var page = 1 - val apiUrl = "$baseUrl/api/trpc/mediaChapters.getByMediaId?batch=1".toHttpUrl() + val apiUrl = "$baseUrl/api/trpc/chapters.getByMediaId?batch=1".toHttpUrl() val chapters = buildList { do { val input = buildJsonObject { @@ -192,13 +218,17 @@ class Taiyo : ParsedHttpSource() { .addQueryParameter("input", json.encodeToString(input)) .build() - val res = client.newCall(GET(pageUrl, headers)).execute() - val parsed = res.parseAs>>().first().data + val chapters = client.newCall(GET(pageUrl, headers)).execute().let { + CHAPTER_REGEX.find(it.body.string())?.groups?.get("chapters")?.value + } + + val parsed = json.decodeFromString(chapters!!) + addAll( parsed.chapters.map { SChapter.create().apply { chapter_number = it.number - name = it.title ?: "Capítulo ${it.number}".replace(".0", "") + name = it.title?.takeIf(String::isNotBlank) ?: "Capítulo ${it.number}".replace(".0", "") url = "/chapter/${it.id}/1" date_upload = it.createdAt.orEmpty().toDate() } @@ -262,13 +292,15 @@ class Taiyo : ParsedHttpSource() { companion object { const val PREFIX_SEARCH = "id:" + val CHAPTER_REGEX = """(?\{"chapters".+"totalPages":\d+\})""".toRegex() + val TOKEN_REGEX = """NEXT_PUBLIC_MEILISEARCH_PUBLIC_KEY:(\s+)?"(?[^"]+)""".toRegex() private const val IMG_CDN = "https://cdn.taiyo.moe/medias" private val MEDIA_TYPE = "application/json; charset=utf-8".toMediaType() private val DATE_FORMATTER by lazy { - SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.ENGLISH) + SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH) } } } diff --git a/src/pt/taiyo/src/eu/kanade/tachiyomi/extension/pt/taiyo/dto/TaiyoDto.kt b/src/pt/taiyo/src/eu/kanade/tachiyomi/extension/pt/taiyo/dto/TaiyoDto.kt index d92017129..ab7ed1c49 100644 --- a/src/pt/taiyo/src/eu/kanade/tachiyomi/extension/pt/taiyo/dto/TaiyoDto.kt +++ b/src/pt/taiyo/src/eu/kanade/tachiyomi/extension/pt/taiyo/dto/TaiyoDto.kt @@ -1,31 +1,29 @@ package eu.kanade.tachiyomi.extension.pt.taiyo.dto +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class ResponseDto(val result: ResultDto) { - val data: T = result.data.json +data class SearchResultDto( + val results: List, +) { + val mangas: List get() = results.firstOrNull()?.hits ?: emptyList() } @Serializable -data class ResultDto(val data: DataDto) - -@Serializable -data class DataDto(val json: T) - -@Serializable -data class SearchResultDto( - val id: String, - val title: String, - val coverId: String? = null, +data class SearchWrapper( + val hits: List, ) @Serializable data class AdditionalInfoDto( + val id: String, val synopsis: String? = null, val status: String? = null, val genres: List? = null, - val titles: List? = null, + @SerialName("mainCoverId") + val coverId: String, + val titles: List, ) enum class Genre(val portugueseName: String) { @@ -51,7 +49,7 @@ enum class Genre(val portugueseName: String) { } @Serializable -data class TitleDto(val title: String, val language: String) +data class TitleDto(val title: String, val language: String, val priority: Int) @Serializable data class ChapterListDto(val chapters: List, val totalPages: Int)