diff --git a/src/pt/yugenmangas/build.gradle b/src/pt/yugenmangas/build.gradle index 5c46877a7..2513c2184 100644 --- a/src/pt/yugenmangas/build.gradle +++ b/src/pt/yugenmangas/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'Yugen Mangás' extClass = '.YugenMangas' - extVersionCode = 40 + extVersionCode = 41 } apply from: "$rootDir/common.gradle" diff --git a/src/pt/yugenmangas/src/eu/kanade/tachiyomi/extension/pt/yugenmangas/YugenMangas.kt b/src/pt/yugenmangas/src/eu/kanade/tachiyomi/extension/pt/yugenmangas/YugenMangas.kt index dfa0a84f0..ba4a81912 100644 --- a/src/pt/yugenmangas/src/eu/kanade/tachiyomi/extension/pt/yugenmangas/YugenMangas.kt +++ b/src/pt/yugenmangas/src/eu/kanade/tachiyomi/extension/pt/yugenmangas/YugenMangas.kt @@ -22,13 +22,15 @@ import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response import okio.Buffer import uy.kohesive.injekt.injectLazy +import java.text.SimpleDateFormat +import java.util.Locale import java.util.concurrent.TimeUnit class YugenMangas : HttpSource() { override val name = "Yugen Mangás" - override val baseUrl = "https://yugenapp.lat" + override val baseUrl = "https://yugenweb.com" override val lang = "pt-BR" @@ -38,6 +40,8 @@ class YugenMangas : HttpSource() { .rateLimit(1, 2, TimeUnit.SECONDS) .build() + override val versionId = 2 + private val json: Json by injectLazy() override fun headersBuilder(): Headers.Builder = Headers.Builder() @@ -49,80 +53,79 @@ class YugenMangas : HttpSource() { .add("Accept", "application/json, text/plain, */*") .add("Origin", baseUrl) .add("Sec-Fetch-Dest", "empty") - .add("Sec-Fetch-Mode", "cors") + .add("Sec-Fetch-Mode", "no-cors") .add("Sec-Fetch-Site", "same-site") override fun popularMangaRequest(page: Int): Request { - return GET("$API_BASE_URL/top_series_all/", apiHeaders) + val url = "$BASE_API/widgets/sort_and_filter/".toHttpUrl().newBuilder() + .addQueryParameter("page", "$page") + .addQueryParameter("sort", "views") + .addQueryParameter("order", "desc") + .build() + + return GET(url, apiHeaders) } override fun popularMangaParse(response: Response): MangasPage { - val result = response.parseAs>() - val mangaList = result.map { it.toSManga(API_HOST) } - return MangasPage(mangaList, hasNextPage = false) + val dto = response.parseAs>() + val mangaList = dto.results.map { it.toSManga() } + return MangasPage(mangaList, hasNextPage = dto.hasNext()) } override fun latestUpdatesRequest(page: Int): Request { - return GET("$API_BASE_URL/latest_updates/", apiHeaders) + return GET("$BASE_API/widgets/home/updates/", apiHeaders) } - override fun latestUpdatesParse(response: Response) = popularMangaParse(response) + override fun latestUpdatesParse(response: Response): MangasPage { + val dto = response.parseAs() + val mangaList = dto.series.map { it.toSManga() } + return MangasPage(mangaList, hasNextPage = false) + } override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val apiUrl = "$API_BASE_URL/series".toHttpUrl().newBuilder() - .addQueryParameter("search", query) - .build() - - return GET(apiUrl, apiHeaders) + val payload = json.encodeToString(SearchDto(query)).toRequestBody(JSON_MEDIA_TYPE) + return POST("$BASE_API/widgets/search/", apiHeaders, payload) } - override fun searchMangaParse(response: Response) = popularMangaParse(response) + override fun searchMangaParse(response: Response) = latestUpdatesParse(response) override fun mangaDetailsRequest(manga: SManga): Request { - val slug = manga.url.removePrefix("/series/") - return POST("$API_BASE_URL/serie/serie_details/$slug", apiHeaders) + val code = manga.url.substringAfterLast("/") + val payload = json.encodeToString(SeriesDto(code)).toRequestBody(JSON_MEDIA_TYPE) + return POST("$BASE_API/series/detail/series/", apiHeaders, payload) } override fun getMangaUrl(manga: SManga) = baseUrl + manga.url override fun mangaDetailsParse(response: Response): SManga { - return response.parseAs().toSManga(API_BASE_URL) + return response.parseAs().toSManga() } override fun chapterListRequest(manga: SManga): Request { - val slug = manga.url.removePrefix("/series/") - val body = YugenGetChaptersBySeriesDto(slug) - val payload = json.encodeToString(body).toRequestBody(JSON_MEDIA_TYPE) - - val newHeaders = apiHeadersBuilder() - .set("Content-Length", payload.contentLength().toString()) - .set("Content-Type", payload.contentType().toString()) - .build() - - return POST("$API_BASE_URL/chapters/get_chapters_by_serie/", newHeaders, payload) + val code = manga.url.substringAfterLast("/") + val payload = json.encodeToString(SeriesDto(code)).toRequestBody(JSON_MEDIA_TYPE) + return POST("$BASE_API/series/chapters/get-series-chapters/", apiHeaders, payload) } override fun pageListRequest(chapter: SChapter): Request { - val slug = chapter.url.removePrefix("/series/").substringBefore("/") - val chapterSlug = chapter.url.substringAfterLast("/") - - return POST("$API_BASE_URL/serie/$slug/chapter/$chapterSlug/images/imgs/get/", apiHeaders) + val code = chapter.url.substringAfterLast("/") + val payload = json.encodeToString(SeriesDto(code)).toRequestBody(JSON_MEDIA_TYPE) + return POST("$BASE_API/chapters/chapter-info/", apiHeaders, payload) } override fun chapterListParse(response: Response): List { - val (seriesSlug) = response.request.body!!.parseAs() - - return response.parseAs().chapters - .map { it.toSChapter(seriesSlug) } - .sortedByDescending(SChapter::chapter_number) + val series = response.request.body!!.parseAs() + return response.parseAs>() + .map { it.toSChapter(series.code) } + .reversed() } override fun getChapterUrl(chapter: SChapter) = baseUrl + chapter.url override fun pageListParse(response: Response): List { - val result = response.parseAs() - - return result.chapterImages.mapIndexed { index, url -> Page(index, baseUrl, "$API_HOST/$url") } + return response.parseAs().images.mapIndexed { index, imageUrl -> + Page(index, baseUrl, "$BASE_MEDIA/$imageUrl") + } } override fun imageUrlParse(response: Response) = "" @@ -145,8 +148,9 @@ class YugenMangas : HttpSource() { } companion object { - private const val API_HOST = "https://api.yugenapp.lat" - private const val API_BASE_URL = "$API_HOST/api" + private const val BASE_API = "https://api.yugenweb.com/api" + private const val BASE_MEDIA = "https://media.yugenweb.com" private val JSON_MEDIA_TYPE = "application/json".toMediaType() + val DATE_FORMAT = SimpleDateFormat("dd/MM/yyyy", Locale.ROOT) } } diff --git a/src/pt/yugenmangas/src/eu/kanade/tachiyomi/extension/pt/yugenmangas/YugenMangasDto.kt b/src/pt/yugenmangas/src/eu/kanade/tachiyomi/extension/pt/yugenmangas/YugenMangasDto.kt index 1ad6779f2..75b35bb0b 100644 --- a/src/pt/yugenmangas/src/eu/kanade/tachiyomi/extension/pt/yugenmangas/YugenMangasDto.kt +++ b/src/pt/yugenmangas/src/eu/kanade/tachiyomi/extension/pt/yugenmangas/YugenMangasDto.kt @@ -1,83 +1,112 @@ package eu.kanade.tachiyomi.extension.pt.yugenmangas +import eu.kanade.tachiyomi.extension.pt.yugenmangas.YugenMangas.Companion.DATE_FORMAT 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.JsonNames -import java.text.SimpleDateFormat -import java.util.Locale +import java.util.Calendar @Serializable -class YugenMangaDto( +class PageDto( + @SerialName("current_page") + val page: Int, + @SerialName("total_pages") + val totalPages: Int, + val results: List, +) { + fun hasNext() = page < totalPages +} + +@Serializable +class MangaDto( + @JsonNames("series_code") + val code: String, + @JsonNames("path_cover") + val cover: String, + @JsonNames("title", "series_name") val name: String, - @JsonNames("capa", "cover") val cover: String, - val slug: String, +) { + fun toSManga(): SManga = SManga.create().apply { + title = this@MangaDto.name + thumbnail_url = cover + url = "/series/$code" + } +} + +@Serializable +class LatestUpdatesDto( + val series: List, +) + +@Serializable +class MangaDetailsDto( + val title: String, + @SerialName("path_cover") + val cover: String, + val code: String, val author: String? = null, val artist: String? = null, val genres: List = emptyList(), val synopsis: String? = null, val status: String? = null, ) { - - fun toSManga(baseUrl: String): SManga = SManga.create().apply { - title = name - author = this@YugenMangaDto.author - artist = this@YugenMangaDto.artist + fun toSManga(): SManga = SManga.create().apply { + title = this@MangaDetailsDto.title + author = this@MangaDetailsDto.author + artist = this@MangaDetailsDto.artist description = synopsis - status = when (this@YugenMangaDto.status) { - "ongoing" -> SManga.ONGOING - "completed", "finished" -> SManga.COMPLETED + status = when (this@MangaDetailsDto.status) { + "Em Lançamento" -> SManga.ONGOING + "Hiato" -> SManga.ON_HIATUS + "Cancelado" -> SManga.CANCELLED + "Finalizado" -> SManga.COMPLETED else -> SManga.UNKNOWN } - - thumbnail_url = when { - cover.startsWith(listOf("/", "cover")) -> "$baseUrl/media/${cover.removePrefix("/")}" - else -> cover - } - url = "/series/$slug" + genre = genres.joinToString() + thumbnail_url = cover + url = "/series/$code" } - - private fun String.startsWith(group: List): Boolean = group.any(::startsWith) } @Serializable -class YugenChapterListDto(val chapters: List) - -@Serializable -class YugenChapterDto( +class ChapterDto( + val code: String, val name: String, - val season: Int, - @SerialName("upload_date") val uploadDate: String, - val slug: String, - val group: String, + @SerialName("upload_date") + val date: String, ) { - - fun toSChapter(mangaSlug: String): SChapter = SChapter.create().apply { - name = this@YugenChapterDto.name - date_upload = runCatching { DATE_FORMATTER.parse(uploadDate)?.time } - .getOrNull() ?: 0L - chapter_number = this@YugenChapterDto.name - .removePrefix("Capítulo ") - .substringBefore(" - ") - .toFloatOrNull() ?: -1f - scanlator = group.ifEmpty { null } - url = "/series/$mangaSlug/$slug" + fun toSChapter(mangaCode: String): SChapter = SChapter.create().apply { + name = this@ChapterDto.name + date_upload = try { parseDate() } catch (_: Exception) { 0L } + url = "/series/$mangaCode/$code" } - companion object { - private val DATE_FORMATTER by lazy { - SimpleDateFormat("dd/MM/yyyy", Locale("pt", "BR")) - } + private fun parseDate(): Long { + return try { + if ("dia" in date) { + val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0L + return Calendar.getInstance().let { + it.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis + } + } + return DATE_FORMAT.parse(date)!!.time + } catch (_: Exception) { 0L } } } @Serializable -data class YugenGetChaptersBySeriesDto( - @SerialName("serie_slug") val seriesSlug: String, +class SeriesDto( + val code: String, ) @Serializable -class YugenPageList( - @SerialName("chapter_images") val chapterImages: List, +class SearchDto( + val query: String, +) + +@Serializable +class PageListDto( + val images: List, )