MangaEsp: Update domain and add alternative names (#1470)

* Update domain

* Get comics from next data (api has old results)

* Add alternative names

* Change name creation logic

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

* Missing replace

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
This commit is contained in:
bapeey 2024-02-26 07:41:46 -05:00 committed by Draff
parent 942ca100d8
commit d6f01fea0b
3 changed files with 90 additions and 79 deletions

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'MangaEsp' extName = 'MangaEsp'
extClass = '.MangaEsp' extClass = '.MangaEsp'
extVersionCode = 1 extVersionCode = 2
isNsfw = false isNsfw = false
} }

View File

@ -29,9 +29,9 @@ class MangaEsp : HttpSource() {
override val name = "MangaEsp" override val name = "MangaEsp"
override val baseUrl = "https://mangaesp.co" override val baseUrl = "https://mangaesp.net"
private val apiBaseUrl = "https://apis.mangaesp.co" private val apiBaseUrl = "https://apis.mangaesp.net"
override val lang = "es" override val lang = "es"
@ -57,13 +57,7 @@ class MangaEsp : HttpSource() {
val topWeekly = responseData.response.topWeekly.flatten().map { it.data } val topWeekly = responseData.response.topWeekly.flatten().map { it.data }
val topMonthly = responseData.response.topMonthly.flatten().map { it.data } val topMonthly = responseData.response.topMonthly.flatten().map { it.data }
val mangas = (topDaily + topWeekly + topMonthly).distinctBy { it.slug }.map { series -> val mangas = (topDaily + topWeekly + topMonthly).distinctBy { it.slug }.map { it.toSManga() }
SManga.create().apply {
title = series.name
thumbnail_url = series.thumbnail
url = "/ver/${series.slug}"
}
}
return MangasPage(mangas, false) return MangasPage(mangas, false)
} }
@ -73,13 +67,7 @@ class MangaEsp : HttpSource() {
override fun latestUpdatesParse(response: Response): MangasPage { override fun latestUpdatesParse(response: Response): MangasPage {
val responseData = json.decodeFromString<LastUpdatesDto>(response.body.string()) val responseData = json.decodeFromString<LastUpdatesDto>(response.body.string())
val mangas = responseData.response.map { series -> val mangas = responseData.response.map { it.toSManga() }
SManga.create().apply {
title = series.name
thumbnail_url = series.thumbnail
url = "/ver/${series.slug}"
}
}
return MangasPage(mangas, false) return MangasPage(mangas, false)
} }
@ -100,13 +88,17 @@ class MangaEsp : HttpSource() {
} }
} }
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/api/comics", headers) override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/comics", headers)
override fun searchMangaParse(response: Response): MangasPage = throw UnsupportedOperationException() override fun searchMangaParse(response: Response): MangasPage = throw UnsupportedOperationException()
private fun searchMangaParse(response: Response, page: Int, query: String, filters: FilterList): MangasPage { private fun searchMangaParse(response: Response, page: Int, query: String, filters: FilterList): MangasPage {
val responseData = json.decodeFromString<ComicsDto>(response.body.string()) val document = response.asJsoup()
comicsList = responseData.response.toMutableList() val script = document.select("script:containsData(self.__next_f.push)").joinToString { it.data() }
val jsonString = MANGA_LIST_REGEX.find(script)?.groupValues?.get(1)
?: throw Exception("No se pudo encontrar la lista de comics")
val unescapedJson = jsonString.unescape()
comicsList = json.decodeFromString<List<SeriesDto>>(unescapedJson).toMutableList()
return parseComicsList(page, query, filters) return parseComicsList(page, query, filters)
} }
@ -118,7 +110,11 @@ class MangaEsp : HttpSource() {
if (query.isNotBlank()) { if (query.isNotBlank()) {
if (query.length < 2) throw Exception("La búsqueda debe tener al menos 2 caracteres") if (query.length < 2) throw Exception("La búsqueda debe tener al menos 2 caracteres")
filteredList.addAll(comicsList.filter { it.name.contains(query, ignoreCase = true) }) filteredList.addAll(
comicsList.filter {
it.name.contains(query, ignoreCase = true) || it.alternativeName?.contains(query, ignoreCase = true) == true
},
)
} else { } else {
filteredList.addAll(comicsList) filteredList.addAll(comicsList)
} }
@ -154,7 +150,7 @@ class MangaEsp : HttpSource() {
return MangasPage( return MangasPage(
filteredList.subList((page - 1) * MANGAS_PER_PAGE, min(page * MANGAS_PER_PAGE, filteredList.size)) filteredList.subList((page - 1) * MANGAS_PER_PAGE, min(page * MANGAS_PER_PAGE, filteredList.size))
.map { it.toSimpleSManga() }, .map { it.toSManga() },
hasNextPage, hasNextPage,
) )
} }
@ -163,42 +159,24 @@ class MangaEsp : HttpSource() {
val responseBody = response.body.string() val responseBody = response.body.string()
val mangaDetailsJson = MANGA_DETAILS_REGEX.find(responseBody)?.groupValues?.get(1) val mangaDetailsJson = MANGA_DETAILS_REGEX.find(responseBody)?.groupValues?.get(1)
?: throw Exception("No se pudo encontrar los detalles del manga") ?: throw Exception("No se pudo encontrar los detalles del manga")
val unescapedJson = mangaDetailsJson.replace("\\", "") val unescapedJson = mangaDetailsJson.unescape()
val series = json.decodeFromString<SeriesDto>(unescapedJson) return json.decodeFromString<SeriesDto>(unescapedJson).toSMangaDetails()
return SManga.create().apply {
title = series.name
thumbnail_url = series.thumbnail
description = series.synopsis
genre = series.genders.joinToString { it.gender.name }
author = series.authors.joinToString { it.author.name }
artist = series.artists.joinToString { it.artist.name }
}
} }
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
val responseBody = response.body.string() val responseBody = response.body.string()
val mangaDetailsJson = MANGA_DETAILS_REGEX.find(responseBody)?.groupValues?.get(1) val mangaDetailsJson = MANGA_DETAILS_REGEX.find(responseBody)?.groupValues?.get(1)
?: throw Exception("No se pudo encontrar la lista de capítulos") ?: throw Exception("No se pudo encontrar la lista de capítulos")
val unescapedJson = mangaDetailsJson.replace("\\", "") val unescapedJson = mangaDetailsJson.unescape()
val series = json.decodeFromString<SeriesDto>(unescapedJson) val series = json.decodeFromString<SeriesDto>(unescapedJson)
return series.chapters.map { chapter -> return series.chapters.map { it.toSChapter(series.slug, dateFormat) }
SChapter.create().apply {
name = if (chapter.name.isNullOrBlank()) {
"Capítulo ${chapter.number.toString().removeSuffix(".0")}"
} else {
"Capítulo ${chapter.number.toString().removeSuffix(".0")} - ${chapter.name}"
}
date_upload = runCatching { dateFormat.parse(chapter.date)?.time }.getOrNull() ?: 0L
url = "/ver/${series.slug}/${chapter.slug}"
}
}
} }
override fun pageListParse(response: Response): List<Page> { override fun pageListParse(response: Response): List<Page> {
val document = response.asJsoup() val document = response.asJsoup()
return document.select("main.contenedor.read img").mapIndexed { i, img -> return document.select("main.contenedor.read img").mapIndexed { i, img ->
Page(i, "", img.imgAttr()) Page(i, imageUrl = img.imgAttr())
} }
} }
@ -254,8 +232,14 @@ class MangaEsp : HttpSource() {
else -> attr("abs:src") else -> attr("abs:src")
} }
private fun String.unescape(): String {
return UNESCAPE_REGEX.replace(this, "$1")
}
companion object { companion object {
private val MANGA_DETAILS_REGEX = """self.__next_f.push\(.*data\\":(\{.*lastChapters.*\}).*numFollow""".toRegex() private val UNESCAPE_REGEX = """\\(.)""".toRegex()
private val MANGA_LIST_REGEX = """self\.__next_f\.push\(.*data\\":(\[.*trending.*])\}""".toRegex()
private val MANGA_DETAILS_REGEX = """self\.__next_f\.push\(.*data\\":(\{.*lastChapters.*\}).*numFollow""".toRegex()
private const val MANGAS_PER_PAGE = 15 private const val MANGAS_PER_PAGE = 15
} }
} }

View File

@ -1,91 +1,118 @@
package eu.kanade.tachiyomi.extension.es.mangaesp package eu.kanade.tachiyomi.extension.es.mangaesp
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.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import java.text.SimpleDateFormat
@Serializable @Serializable
data class TopSeriesDto( class TopSeriesDto(
val response: TopSeriesResponseDto, val response: TopSeriesResponseDto,
) )
@Serializable @Serializable
data class LastUpdatesDto( class LastUpdatesDto(
val response: List<SeriesDto>, val response: List<SeriesDto>,
) )
@Serializable @Serializable
data class ComicsDto( class TopSeriesResponseDto(
val response: List<SeriesDto>,
)
@Serializable
data class TopSeriesResponseDto(
@SerialName("mensual") val topMonthly: List<List<PayloadSeriesDto>>, @SerialName("mensual") val topMonthly: List<List<PayloadSeriesDto>>,
@SerialName("semanal") val topWeekly: List<List<PayloadSeriesDto>>, @SerialName("semanal") val topWeekly: List<List<PayloadSeriesDto>>,
@SerialName("diario") val topDaily: List<List<PayloadSeriesDto>>, @SerialName("diario") val topDaily: List<List<PayloadSeriesDto>>,
) )
@Serializable @Serializable
data class PayloadSeriesDto( class PayloadSeriesDto(
@SerialName("project") val data: SeriesDto, @SerialName("project") val data: SeriesDto,
) )
@Serializable @Serializable
data class SeriesDto( class SeriesDto(
val name: String, val name: String,
val alternativeName: String? = null,
val slug: String, val slug: String,
@SerialName("sinopsis") val synopsis: String? = null, @SerialName("sinopsis") private val synopsis: String? = null,
@SerialName("urlImg") val thumbnail: String? = null, @SerialName("urlImg") private val thumbnail: String? = null,
val isVisible: Boolean,
@SerialName("actualizacionCap") val lastChapterDate: String? = null, @SerialName("actualizacionCap") val lastChapterDate: String? = null,
@SerialName("created_at") val createdAt: String? = null, @SerialName("created_at") val createdAt: String? = null,
@SerialName("state_id") val status: Int? = 0, @SerialName("state_id") val status: Int? = 0,
val genders: List<SeriesGenderDto> = emptyList(), private val genders: List<GenderDto> = emptyList(),
@SerialName("lastChapters") val chapters: List<SeriesChapterDto> = emptyList(), @SerialName("lastChapters") val chapters: List<ChapterDto> = emptyList(),
val trending: SeriesTrendingDto? = null, val trending: TrendingDto? = null,
@SerialName("autors") val authors: List<SeriesAuthorDto> = emptyList(), @SerialName("autors") private val authors: List<AuthorDto> = emptyList(),
val artists: List<SeriesArtistDto> = emptyList(), private val artists: List<ArtistDto> = emptyList(),
) { ) {
fun toSimpleSManga(): SManga { fun toSManga(): SManga {
return SManga.create().apply { return SManga.create().apply {
title = name title = name
thumbnail_url = thumbnail thumbnail_url = thumbnail
url = "/ver/$slug" url = "/ver/$slug"
} }
} }
fun toSMangaDetails(): SManga {
return SManga.create().apply {
title = name
thumbnail_url = thumbnail
description = synopsis
if (!alternativeName.isNullOrBlank()) {
if (!description.isNullOrBlank()) description += "\n\n"
description += "Nombres alternativos: $alternativeName"
}
genre = genders.joinToString { it.gender.name }
author = authors.joinToString { it.author.name }
artist = artists.joinToString { it.artist.name }
}
}
} }
@Serializable @Serializable
data class SeriesTrendingDto( class TrendingDto(
@SerialName("visitas") val views: Int? = 0, @SerialName("visitas") val views: Int? = 0,
) )
@Serializable @Serializable
data class SeriesGenderDto( class GenderDto(
val gender: SeriesDetailDataNameDto, val gender: DetailDataNameDto,
) )
@Serializable @Serializable
data class SeriesAuthorDto( class AuthorDto(
@SerialName("autor") val author: SeriesDetailDataNameDto, @SerialName("autor") val author: DetailDataNameDto,
) )
@Serializable @Serializable
data class SeriesArtistDto( class ArtistDto(
val artist: SeriesDetailDataNameDto, val artist: DetailDataNameDto,
) )
@Serializable @Serializable
data class SeriesDetailDataNameDto( class DetailDataNameDto(
val name: String, val name: String,
) )
@Serializable @Serializable
data class SeriesChapterDto( class ChapterDto(
@SerialName("num") val number: Float, @SerialName("num") private val number: Float,
val name: String? = null, private val name: String? = null,
val slug: String, private val slug: String,
@SerialName("created_at") val date: String, @SerialName("created_at") private val date: String,
) ) {
fun toSChapter(seriesSlug: String, dateFormat: SimpleDateFormat): SChapter {
return SChapter.create().apply {
name = "Capítulo ${number.toString().removeSuffix(".0")}"
if (!this@ChapterDto.name.isNullOrBlank()) {
name += " - ${this@ChapterDto.name}"
}
date_upload = try {
dateFormat.parse(date)?.time ?: 0L
} catch (e: Exception) {
0L
}
url = "/ver/$seriesSlug/$slug"
}
}
}