From 09458dcd867220a50f226ce03403a93c500e1bff Mon Sep 17 00:00:00 2001 From: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com> Date: Fri, 29 Dec 2023 17:52:31 -0300 Subject: [PATCH] Readd search to LM (#19480) Readd search to LM. --- src/pt/lermanga/build.gradle | 2 +- .../extension/pt/lermanga/LerManga.kt | 76 ++++++++++++++----- .../extension/pt/lermanga/LerMangaDto.kt | 37 +++++++++ 3 files changed, 96 insertions(+), 19 deletions(-) create mode 100644 src/pt/lermanga/src/eu/kanade/tachiyomi/extension/pt/lermanga/LerMangaDto.kt diff --git a/src/pt/lermanga/build.gradle b/src/pt/lermanga/build.gradle index acaad243c..19616a3e0 100644 --- a/src/pt/lermanga/build.gradle +++ b/src/pt/lermanga/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'Ler Mangá' pkgNameSuffix = 'pt.lermanga' extClass = '.LerManga' - extVersionCode = 3 + extVersionCode = 4 isNsfw = true } diff --git a/src/pt/lermanga/src/eu/kanade/tachiyomi/extension/pt/lermanga/LerManga.kt b/src/pt/lermanga/src/eu/kanade/tachiyomi/extension/pt/lermanga/LerManga.kt index 699235228..92658904e 100644 --- a/src/pt/lermanga/src/eu/kanade/tachiyomi/extension/pt/lermanga/LerManga.kt +++ b/src/pt/lermanga/src/eu/kanade/tachiyomi/extension/pt/lermanga/LerManga.kt @@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.extension.pt.lermanga import android.util.Base64 import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.interceptor.rateLimit +import eu.kanade.tachiyomi.network.interceptor.rateLimitHost import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.Page @@ -12,16 +12,17 @@ import eu.kanade.tachiyomi.source.online.ParsedHttpSource import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import okhttp3.Headers +import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element import uy.kohesive.injekt.injectLazy -import java.lang.UnsupportedOperationException import java.text.SimpleDateFormat import java.util.Locale import java.util.concurrent.TimeUnit +import kotlin.UnsupportedOperationException class LerManga : ParsedHttpSource() { @@ -34,7 +35,8 @@ class LerManga : ParsedHttpSource() { override val supportsLatest = true override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .rateLimit(1, 2, TimeUnit.SECONDS) + .rateLimitHost(baseUrl.toHttpUrl(), 1, 1, TimeUnit.SECONDS) + .rateLimitHost(IMG_CDN_URL.toHttpUrl(), 1, 2, TimeUnit.SECONDS) .build() private val json: Json by injectLazy() @@ -42,6 +44,11 @@ class LerManga : ParsedHttpSource() { override fun headersBuilder(): Headers.Builder = Headers.Builder() .add("Referer", "$baseUrl/") + private fun apiHeadersBuilder(): Headers.Builder = headersBuilder() + .add("Accept", "application/json") + + private val apiHeaders by lazy { apiHeadersBuilder().build() } + override fun popularMangaRequest(page: Int): Request { val path = if (page > 1) "page/$page/" else "" return GET("$baseUrl/mangas/$path?orderby=views&order=desc", headers) @@ -70,20 +77,37 @@ class LerManga : ParsedHttpSource() { override fun latestUpdatesNextPageSelector(): String? = null override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - if (!query.startsWith(PREFIX_SLUG_SEARCH)) { - throw Exception(ERROR_NO_SEARCH_AVAILABLE) + if (query.startsWith(PREFIX_SLUG_SEARCH)) { + val slug = query.removePrefix(PREFIX_SLUG_SEARCH) + val tempManga = SManga.create().apply { url = "/mangas/$slug" } + + return mangaDetailsRequest(tempManga) } - val slug = query.removePrefix(PREFIX_SLUG_SEARCH) - val tempManga = SManga.create().apply { url = "/mangas/$slug" } + val apiRequest = "$API_BASE_URL/mangas".toHttpUrl().newBuilder() + .addQueryParameter("page", page.toString()) + .addQueryParameter("search", query) + .addQueryParameter("_fields", "title,slug") + .build() - return mangaDetailsRequest(tempManga) + return GET(apiRequest, apiHeaders) } override fun searchMangaParse(response: Response): MangasPage { - val manga = mangaDetailsParse(response) + if (response.request.url.queryParameter("slug") != null) { + val manga = mangaDetailsParse(response) + return MangasPage(listOf(manga), hasNextPage = false) + } - return MangasPage(listOf(manga), hasNextPage = false) + val result = response.parseAs>() + val mangaList = result.map(LmMangaDto::toSManga) + + val currentPage = response.request.url.queryParameter("page") + .orEmpty().toIntOrNull() ?: 1 + val lastPage = response.headers["X-Wp-TotalPages"]!!.toInt() + val hasNextPage = currentPage < lastPage + + return MangasPage(mangaList, hasNextPage) } override fun searchMangaSelector() = throw UnsupportedOperationException("Not used") @@ -92,17 +116,27 @@ class LerManga : ParsedHttpSource() { override fun searchMangaNextPageSelector() = throw UnsupportedOperationException("Not used") - override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { - val infoElement = document.selectFirst("div.capitulo_recente")!! + override fun getMangaUrl(manga: SManga): String = baseUrl + manga.url - title = document.select("title").text().substringBeforeLast(" - ") - genre = infoElement.select("ul.genre-list li a") - .joinToString { it.text() } - description = infoElement.selectFirst("div.boxAnimeSobreLast p:last-child")!!.ownText() - thumbnail_url = infoElement.selectFirst("div.capaMangaInfo img")!!.absUrl("src") - setUrlWithoutDomain(document.location()) + override fun mangaDetailsRequest(manga: SManga): Request { + val slug = manga.url.removePrefix("/mangas/").removeSuffix("/") + + val apiRequest = "$API_BASE_URL/mangas".toHttpUrl().newBuilder() + .addQueryParameter("slug", slug) + .addQueryParameter("limit", "1") + .addQueryParameter("_embed", "wp:term") + .addQueryParameter("_fields", "title,slug,content,_links.wp:term,_embedded.wp:term") + .build() + + return GET(apiRequest, apiHeaders) } + override fun mangaDetailsParse(response: Response): SManga { + return response.parseAs>().first().toSManga() + } + + override fun mangaDetailsParse(document: Document): SManga = throw UnsupportedOperationException("Not used") + override fun chapterListSelector() = "div.manga-chapters div.single-chapter" override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { @@ -143,6 +177,10 @@ class LerManga : ParsedHttpSource() { return GET(page.imageUrl!!, newHeaders) } + private inline fun Response.parseAs(): T = use { + json.decodeFromString(it.body.string()) + } + private fun Element.srcAttr(): String = when { hasAttr("data-src") -> absUrl("data-src") else -> absUrl("src") @@ -154,6 +192,8 @@ class LerManga : ParsedHttpSource() { } companion object { + const val API_BASE_URL = "https://lermanga.org/wp-json/wp/v2" + const val IMG_CDN_URL = "https://img.lermanga.org" private val PAGES_VARIABLE_REGEX = "var imagens_cap\\s*=\\s*".toRegex() private val DATE_FORMATTER by lazy { SimpleDateFormat("dd-MM-yyyy", Locale("pt", "BR")) diff --git a/src/pt/lermanga/src/eu/kanade/tachiyomi/extension/pt/lermanga/LerMangaDto.kt b/src/pt/lermanga/src/eu/kanade/tachiyomi/extension/pt/lermanga/LerMangaDto.kt new file mode 100644 index 000000000..a36f79784 --- /dev/null +++ b/src/pt/lermanga/src/eu/kanade/tachiyomi/extension/pt/lermanga/LerMangaDto.kt @@ -0,0 +1,37 @@ +package eu.kanade.tachiyomi.extension.pt.lermanga + +import eu.kanade.tachiyomi.source.model.SManga +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import org.jsoup.Jsoup + +@Serializable +data class LmMangaDto( + val slug: String, + val title: LmContentDto, + val content: LmContentDto? = null, + @SerialName("_embedded") val embedded: LmEmbedDto? = null, +) { + + fun toSManga(): SManga = SManga.create().apply { + title = this@LmMangaDto.title.rendered + thumbnail_url = "${LerManga.IMG_CDN_URL}/${slug.first().uppercase()}/$slug/capa.jpg" + description = content?.rendered?.let { Jsoup.parseBodyFragment(it) }?.text()?.trim() + genre = embedded?.wpTerm.orEmpty().flatten() + .filter { it.taxonomy == "generomanga" } + .joinToString { it.name } + url = "/mangas/$slug" + } +} + +@Serializable +data class LmContentDto(val rendered: String) + +@Serializable +data class LmEmbedDto(@SerialName("wp:term") val wpTerm: List>) + +@Serializable +data class LmTaxonomyDto( + val name: String, + val taxonomy: String, +)