Readd search to LM (#19480)

Readd search to LM.
This commit is contained in:
Alessandro Jean 2023-12-29 17:52:31 -03:00 committed by GitHub
parent 5c71163ef6
commit 09458dcd86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 96 additions and 19 deletions

View File

@ -6,7 +6,7 @@ ext {
extName = 'Ler Mangá' extName = 'Ler Mangá'
pkgNameSuffix = 'pt.lermanga' pkgNameSuffix = 'pt.lermanga'
extClass = '.LerManga' extClass = '.LerManga'
extVersionCode = 3 extVersionCode = 4
isNsfw = true isNsfw = true
} }

View File

@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.extension.pt.lermanga
import android.util.Base64 import android.util.Base64
import eu.kanade.tachiyomi.network.GET 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.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page 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.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.lang.UnsupportedOperationException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.UnsupportedOperationException
class LerManga : ParsedHttpSource() { class LerManga : ParsedHttpSource() {
@ -34,7 +35,8 @@ class LerManga : ParsedHttpSource() {
override val supportsLatest = true override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient.newBuilder() 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() .build()
private val json: Json by injectLazy() private val json: Json by injectLazy()
@ -42,6 +44,11 @@ class LerManga : ParsedHttpSource() {
override fun headersBuilder(): Headers.Builder = Headers.Builder() override fun headersBuilder(): Headers.Builder = Headers.Builder()
.add("Referer", "$baseUrl/") .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 { override fun popularMangaRequest(page: Int): Request {
val path = if (page > 1) "page/$page/" else "" val path = if (page > 1) "page/$page/" else ""
return GET("$baseUrl/mangas/$path?orderby=views&order=desc", headers) return GET("$baseUrl/mangas/$path?orderby=views&order=desc", headers)
@ -70,39 +77,66 @@ class LerManga : ParsedHttpSource() {
override fun latestUpdatesNextPageSelector(): String? = null override fun latestUpdatesNextPageSelector(): String? = null
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
if (!query.startsWith(PREFIX_SLUG_SEARCH)) { if (query.startsWith(PREFIX_SLUG_SEARCH)) {
throw Exception(ERROR_NO_SEARCH_AVAILABLE)
}
val slug = query.removePrefix(PREFIX_SLUG_SEARCH) val slug = query.removePrefix(PREFIX_SLUG_SEARCH)
val tempManga = SManga.create().apply { url = "/mangas/$slug" } val tempManga = SManga.create().apply { url = "/mangas/$slug" }
return mangaDetailsRequest(tempManga) return mangaDetailsRequest(tempManga)
} }
override fun searchMangaParse(response: Response): MangasPage { val apiRequest = "$API_BASE_URL/mangas".toHttpUrl().newBuilder()
val manga = mangaDetailsParse(response) .addQueryParameter("page", page.toString())
.addQueryParameter("search", query)
.addQueryParameter("_fields", "title,slug")
.build()
return GET(apiRequest, apiHeaders)
}
override fun searchMangaParse(response: Response): MangasPage {
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<List<LmMangaDto>>()
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") override fun searchMangaSelector() = throw UnsupportedOperationException("Not used")
override fun searchMangaFromElement(element: Element) = throw UnsupportedOperationException("Not used") override fun searchMangaFromElement(element: Element) = throw UnsupportedOperationException("Not used")
override fun searchMangaNextPageSelector() = throw UnsupportedOperationException("Not used") override fun searchMangaNextPageSelector() = throw UnsupportedOperationException("Not used")
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { override fun getMangaUrl(manga: SManga): String = baseUrl + manga.url
val infoElement = document.selectFirst("div.capitulo_recente")!!
title = document.select("title").text().substringBeforeLast(" - ") override fun mangaDetailsRequest(manga: SManga): Request {
genre = infoElement.select("ul.genre-list li a") val slug = manga.url.removePrefix("/mangas/").removeSuffix("/")
.joinToString { it.text() }
description = infoElement.selectFirst("div.boxAnimeSobreLast p:last-child")!!.ownText() val apiRequest = "$API_BASE_URL/mangas".toHttpUrl().newBuilder()
thumbnail_url = infoElement.selectFirst("div.capaMangaInfo img")!!.absUrl("src") .addQueryParameter("slug", slug)
setUrlWithoutDomain(document.location()) .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<List<LmMangaDto>>().first().toSManga()
}
override fun mangaDetailsParse(document: Document): SManga = throw UnsupportedOperationException("Not used")
override fun chapterListSelector() = "div.manga-chapters div.single-chapter" override fun chapterListSelector() = "div.manga-chapters div.single-chapter"
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
@ -143,6 +177,10 @@ class LerManga : ParsedHttpSource() {
return GET(page.imageUrl!!, newHeaders) return GET(page.imageUrl!!, newHeaders)
} }
private inline fun <reified T> Response.parseAs(): T = use {
json.decodeFromString(it.body.string())
}
private fun Element.srcAttr(): String = when { private fun Element.srcAttr(): String = when {
hasAttr("data-src") -> absUrl("data-src") hasAttr("data-src") -> absUrl("data-src")
else -> absUrl("src") else -> absUrl("src")
@ -154,6 +192,8 @@ class LerManga : ParsedHttpSource() {
} }
companion object { 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 PAGES_VARIABLE_REGEX = "var imagens_cap\\s*=\\s*".toRegex()
private val DATE_FORMATTER by lazy { private val DATE_FORMATTER by lazy {
SimpleDateFormat("dd-MM-yyyy", Locale("pt", "BR")) SimpleDateFormat("dd-MM-yyyy", Locale("pt", "BR"))

View File

@ -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<List<LmTaxonomyDto>>)
@Serializable
data class LmTaxonomyDto(
val name: String,
val taxonomy: String,
)