Add new source "MangaTigre" (#16444)
* Add MangaTigre * Keep title * Add more genres to manga * Use StringBuilder for create description * Minor changes
This commit is contained in:
parent
57c5e0c896
commit
3091cd6fdc
2
src/es/mangatigre/AndroidManifest.xml
Normal file
2
src/es/mangatigre/AndroidManifest.xml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest package="eu.kanade.tachiyomi.extension" />
|
13
src/es/mangatigre/build.gradle
Normal file
13
src/es/mangatigre/build.gradle
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlinx-serialization'
|
||||||
|
|
||||||
|
ext {
|
||||||
|
extName = 'MangaTigre'
|
||||||
|
pkgNameSuffix = 'es.mangatigre'
|
||||||
|
extClass = '.MangaTigre'
|
||||||
|
extVersionCode = 1
|
||||||
|
isNsfw = true
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
BIN
src/es/mangatigre/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
src/es/mangatigre/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
BIN
src/es/mangatigre/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
src/es/mangatigre/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
BIN
src/es/mangatigre/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
src/es/mangatigre/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.6 KiB |
BIN
src/es/mangatigre/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
src/es/mangatigre/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
src/es/mangatigre/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
src/es/mangatigre/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
BIN
src/es/mangatigre/res/web_hi_res_512.png
Normal file
BIN
src/es/mangatigre/res/web_hi_res_512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 122 KiB |
@ -0,0 +1,384 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.es.mangatigre
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.network.POST
|
||||||
|
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
|
||||||
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
|
import kotlinx.serialization.json.jsonObject
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import okhttp3.Response
|
||||||
|
import okio.Buffer
|
||||||
|
import org.jsoup.nodes.Document
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.util.Calendar
|
||||||
|
|
||||||
|
class MangaTigre : HttpSource() {
|
||||||
|
|
||||||
|
override val name = "MangaTigre"
|
||||||
|
|
||||||
|
override val baseUrl = "https://www.mangatigre.net"
|
||||||
|
|
||||||
|
override val lang = "es"
|
||||||
|
|
||||||
|
override val supportsLatest = true
|
||||||
|
|
||||||
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
|
private val imgCDNUrl = "https://i2.mtcdn.xyz"
|
||||||
|
|
||||||
|
private var mtToken = ""
|
||||||
|
|
||||||
|
override val client: OkHttpClient = network.client.newBuilder()
|
||||||
|
.addInterceptor { chain ->
|
||||||
|
val request = chain.request()
|
||||||
|
|
||||||
|
if (request.method == "POST") {
|
||||||
|
val response = chain.proceed(request)
|
||||||
|
|
||||||
|
if (response.code == 419) {
|
||||||
|
response.close()
|
||||||
|
getToken()
|
||||||
|
|
||||||
|
val newBody = json.parseToJsonElement(request.bodyString).jsonObject.toMutableMap().apply {
|
||||||
|
this["_token"] = JsonPrimitive(mtToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
val payload = Json.encodeToString(JsonObject(newBody)).toRequestBody(JSON_MEDIA_TYPE)
|
||||||
|
|
||||||
|
val apiHeaders = headersBuilder()
|
||||||
|
.add("Accept", ACCEPT_JSON)
|
||||||
|
.add("Content-Type", payload.contentType().toString())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val newRequest = request.newBuilder()
|
||||||
|
.headers(apiHeaders)
|
||||||
|
.method(request.method, payload)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return@addInterceptor chain.proceed(newRequest)
|
||||||
|
}
|
||||||
|
return@addInterceptor response
|
||||||
|
}
|
||||||
|
chain.proceed(request)
|
||||||
|
}
|
||||||
|
.rateLimitHost(baseUrl.toHttpUrl(), 1, 2)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
private fun getToken() {
|
||||||
|
val document = client.newCall(GET(baseUrl, headers)).execute().asJsoup()
|
||||||
|
mtToken = document.selectFirst("input.input-search[data-csrf]")!!.attr("data-csrf")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
|
val payloadObj = PayloadManga(
|
||||||
|
page = page,
|
||||||
|
token = mtToken,
|
||||||
|
)
|
||||||
|
|
||||||
|
val payload = json.encodeToString(payloadObj).toRequestBody(JSON_MEDIA_TYPE)
|
||||||
|
|
||||||
|
val apiHeaders = headersBuilder()
|
||||||
|
.add("Accept", ACCEPT_JSON)
|
||||||
|
.add("Content-Type", payload.contentType().toString())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return POST("$baseUrl/mangas?sort=views", apiHeaders, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
|
val jsonString = response.body.string()
|
||||||
|
|
||||||
|
val result = json.decodeFromString<MangasDto>(jsonString)
|
||||||
|
|
||||||
|
val mangas = result.mangas.map {
|
||||||
|
SManga.create().apply {
|
||||||
|
setUrlWithoutDomain("$baseUrl/manga/${it.slug}")
|
||||||
|
title = it.title
|
||||||
|
thumbnail_url = "$imgCDNUrl/mangas/${it.thumbnailFileName}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val hasNextPage = result.totalPages > result.page
|
||||||
|
|
||||||
|
return MangasPage(mangas, hasNextPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
|
val payloadObj = PayloadManga(
|
||||||
|
page = page,
|
||||||
|
token = mtToken,
|
||||||
|
)
|
||||||
|
|
||||||
|
val payload = json.encodeToString(payloadObj).toRequestBody(JSON_MEDIA_TYPE)
|
||||||
|
|
||||||
|
val apiHeaders = headersBuilder()
|
||||||
|
.add("Accept", ACCEPT_JSON)
|
||||||
|
.add("Content-Type", payload.contentType().toString())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return POST("$baseUrl/mangas?sort=date", apiHeaders, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesParse(response: Response) = popularMangaParse(response)
|
||||||
|
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
|
if (query.isNotEmpty()) {
|
||||||
|
if (query.length < 2) throw Exception("La cadena de búsqueda debe tener por lo menos 2 caracteres")
|
||||||
|
|
||||||
|
val payloadObj = PayloadSearch(
|
||||||
|
query = query,
|
||||||
|
token = mtToken,
|
||||||
|
)
|
||||||
|
val payload = json.encodeToString(payloadObj).toRequestBody(JSON_MEDIA_TYPE)
|
||||||
|
|
||||||
|
val apiHeaders = headersBuilder()
|
||||||
|
.add("Accept", ACCEPT_JSON)
|
||||||
|
.add("Content-Type", payload.contentType().toString())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return POST("$baseUrl/mangas/search#$query", apiHeaders, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
val url = "$baseUrl/mangas".toHttpUrlOrNull()!!.newBuilder()
|
||||||
|
|
||||||
|
filters.forEach { filter ->
|
||||||
|
when (filter) {
|
||||||
|
is OrderFilter -> {
|
||||||
|
url.addQueryParameter("sort", filter.toUriPart())
|
||||||
|
}
|
||||||
|
is TypeFilter -> {
|
||||||
|
filter.state.forEach { content ->
|
||||||
|
if (content.state) url.addQueryParameter("type[]", content.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is StatusFilter -> {
|
||||||
|
filter.state.forEach { content ->
|
||||||
|
if (content.state) url.addQueryParameter("status[]", content.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is DemographicFilter -> {
|
||||||
|
filter.state.forEach { content ->
|
||||||
|
if (content.state) url.addQueryParameter("demographic[]", content.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is ContentFilter -> {
|
||||||
|
filter.state.forEach { content ->
|
||||||
|
if (content.state) url.addQueryParameter("content[]", content.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is FormatFilter -> {
|
||||||
|
filter.state.forEach { content ->
|
||||||
|
if (content.state) url.addQueryParameter("format[]", content.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is GenreFilter -> {
|
||||||
|
filter.state.forEach { content ->
|
||||||
|
if (content.state) url.addQueryParameter("genre[]", content.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is ThemeFilter -> {
|
||||||
|
filter.state.forEach { content ->
|
||||||
|
if (content.state) url.addQueryParameter("theme[]", content.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val payloadObj = PayloadManga(
|
||||||
|
page = page,
|
||||||
|
token = mtToken,
|
||||||
|
)
|
||||||
|
val payload = json.encodeToString(payloadObj).toRequestBody(JSON_MEDIA_TYPE)
|
||||||
|
|
||||||
|
val apiHeaders = headersBuilder()
|
||||||
|
.add("Accept", ACCEPT_JSON)
|
||||||
|
.add("Content-Type", payload.contentType().toString())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return POST(url.build().toString(), apiHeaders, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaParse(response: Response): MangasPage {
|
||||||
|
val query = response.request.url.fragment
|
||||||
|
val jsonString = response.body.string()
|
||||||
|
|
||||||
|
if (!query.isNullOrEmpty()) {
|
||||||
|
val result = json.decodeFromString<SearchDto>(jsonString)
|
||||||
|
|
||||||
|
val mangas = result.result.map {
|
||||||
|
SManga.create().apply {
|
||||||
|
setUrlWithoutDomain("$baseUrl/manga/${it.slug}")
|
||||||
|
title = it.title
|
||||||
|
thumbnail_url = "$imgCDNUrl/mangas/${it.thumbnailFileName}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return MangasPage(mangas, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = json.decodeFromString<MangasDto>(jsonString)
|
||||||
|
|
||||||
|
val mangas = result.mangas.map {
|
||||||
|
SManga.create().apply {
|
||||||
|
setUrlWithoutDomain("$baseUrl/manga/${it.slug}")
|
||||||
|
title = it.title
|
||||||
|
thumbnail_url = "$imgCDNUrl/mangas/${it.thumbnailFileName}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val hasNextPage = result.totalPages > result.page
|
||||||
|
|
||||||
|
return MangasPage(mangas, hasNextPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(response: Response): SManga {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
return SManga.create().apply {
|
||||||
|
description = createDescription(document)
|
||||||
|
genre = createGenres(document)
|
||||||
|
thumbnail_url = document.selectFirst("div.manga-image > img")!!.attr("abs:data-src")
|
||||||
|
author = document.selectFirst("li.list-group-item:has(strong:contains(Autor)) > a")?.ownText()?.trim()
|
||||||
|
artist = document.selectFirst("li.list-group-item:has(strong:contains(Artista)) > a")?.ownText()?.trim()
|
||||||
|
status = document.selectFirst("li.list-group-item:has(strong:contains(Estado))")?.ownText()?.trim()!!.toStatus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createGenres(document: Document): String {
|
||||||
|
val demographic = document.select("li.list-group-item:has(strong:contains(Demografía)) a").joinToString { it.text() }
|
||||||
|
val genres = document.select("li.list-group-item:has(strong:contains(Géneros)) a").joinToString { it.text() }
|
||||||
|
val themes = document.select("li.list-group-item:has(strong:contains(Temas)) a").joinToString { it.text() }
|
||||||
|
val content = document.select("li.list-group-item:has(strong:contains(Contenido)) a").joinToString { it.text() }
|
||||||
|
return listOf(demographic, genres, themes, content).joinToString(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createDescription(document: Document): String {
|
||||||
|
val originalName = document.selectFirst("li.list-group-item:has(strong:contains(Original))")?.ownText()?.trim() ?: ""
|
||||||
|
val alternativeName = document.select("li.list-group-item:has(strong:contains(Alternativo)) span.alter-name").text()
|
||||||
|
val year = document.selectFirst("li.list-group-item:has(strong:contains(Año))")?.ownText()?.trim() ?: ""
|
||||||
|
val animeAdaptation = document.selectFirst("li.list-group-item:has(strong:contains(Anime))")?.ownText()?.trim() ?: ""
|
||||||
|
val country = document.selectFirst("li.list-group-item:has(strong:contains(País))")?.ownText()?.trim() ?: ""
|
||||||
|
val summary = document.selectFirst("div.synopsis > p")?.ownText()?.trim() ?: ""
|
||||||
|
return StringBuilder()
|
||||||
|
.appendLine("Nombre Original: $originalName")
|
||||||
|
.appendLine("Títulos Alternativos: $alternativeName")
|
||||||
|
.appendLine("Año: $year")
|
||||||
|
.appendLine("Adaptación al Anime: $animeAdaptation")
|
||||||
|
.appendLine("País: $country")
|
||||||
|
.appendLine()
|
||||||
|
.appendLine("Sinopsis: $summary")
|
||||||
|
.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chapterListRequest(manga: SManga): Request {
|
||||||
|
val payloadObj = PayloadChapter(
|
||||||
|
token = mtToken,
|
||||||
|
)
|
||||||
|
|
||||||
|
val payload = json.encodeToString(payloadObj).toRequestBody(JSON_MEDIA_TYPE)
|
||||||
|
|
||||||
|
val apiHeaders = headersBuilder()
|
||||||
|
.add("Accept", ACCEPT_JSON)
|
||||||
|
.add("Content-Type", payload.contentType().toString())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return POST("$baseUrl${manga.url}", apiHeaders, payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
|
return response.asJsoup().select("li").map {
|
||||||
|
SChapter.create().apply {
|
||||||
|
setUrlWithoutDomain(it.select("a").attr("href"))
|
||||||
|
name = it.selectFirst("a")!!.ownText().trim()
|
||||||
|
date_upload = parseRelativeDate(it.selectFirst("span")!!.ownText().trim())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
val script = document.selectFirst("script:containsData(window.chapter)")!!.data()
|
||||||
|
val jsonString = CHAPTERS_REGEX.find(script)!!.groupValues[1]
|
||||||
|
|
||||||
|
val result = json.decodeFromString<ChapterDto>(jsonString)
|
||||||
|
val slug = result.manga.slug
|
||||||
|
val number = result.number
|
||||||
|
|
||||||
|
return result.images.map {
|
||||||
|
val imageUrl = "$imgCDNUrl/chapters/$slug/$number/${it.value.name}.${it.value.format}"
|
||||||
|
Page(it.key.toInt(), "", imageUrl)
|
||||||
|
}.sortedBy { it.index }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFilterList() = FilterList(
|
||||||
|
Filter.Header("Los filtros serán ignorados si se realiza una búsqueda textual"),
|
||||||
|
Filter.Separator(),
|
||||||
|
OrderFilter(),
|
||||||
|
Filter.Separator(),
|
||||||
|
TypeFilter(getFilterTypeList()),
|
||||||
|
Filter.Separator(),
|
||||||
|
StatusFilter(getFilterStatusList()),
|
||||||
|
Filter.Separator(),
|
||||||
|
DemographicFilter(getFilterDemographicList()),
|
||||||
|
Filter.Separator(),
|
||||||
|
ContentFilter(getFilterContentList()),
|
||||||
|
Filter.Separator(),
|
||||||
|
FormatFilter(getFilterFormatList()),
|
||||||
|
Filter.Separator(),
|
||||||
|
GenreFilter(getFilterGenreList()),
|
||||||
|
Filter.Separator(),
|
||||||
|
ThemeFilter(getFilterThemeList()),
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("Not used.")
|
||||||
|
|
||||||
|
private fun parseRelativeDate(date: String): Long {
|
||||||
|
val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0
|
||||||
|
val cal = Calendar.getInstance()
|
||||||
|
|
||||||
|
return when {
|
||||||
|
WordSet("segundo").anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis
|
||||||
|
WordSet("minuto").anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
|
||||||
|
WordSet("hora").anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis
|
||||||
|
WordSet("día").anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
|
||||||
|
WordSet("semana").anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number * 7) }.timeInMillis
|
||||||
|
WordSet("mes").anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
|
||||||
|
WordSet("año").anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class WordSet(private vararg val words: String) {
|
||||||
|
fun anyWordIn(dateString: String): Boolean = words.any { dateString.contains(it, ignoreCase = true) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private val Request.bodyString: String
|
||||||
|
get() {
|
||||||
|
val requestCopy = newBuilder().build()
|
||||||
|
val buffer = Buffer()
|
||||||
|
|
||||||
|
return runCatching { buffer.apply { requestCopy.body!!.writeTo(this) }.readUtf8() }
|
||||||
|
.getOrNull() ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val ACCEPT_JSON = "application/json, text/plain, */*"
|
||||||
|
private val JSON_MEDIA_TYPE = "application/json".toMediaType()
|
||||||
|
|
||||||
|
private val CHAPTERS_REGEX = """window\.chapter\s*=\s*'(.+?)';""".toRegex()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.es.mangatigre
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PayloadManga(
|
||||||
|
val page: Int,
|
||||||
|
@SerialName("_token") val token: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PayloadChapter(
|
||||||
|
@SerialName("_token") val token: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PayloadSearch(
|
||||||
|
val query: String,
|
||||||
|
@SerialName("_token") val token: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class MangasDto(
|
||||||
|
@SerialName("current_page") val page: Int,
|
||||||
|
@SerialName("last_page") val totalPages: Int,
|
||||||
|
@SerialName("data") val mangas: List<MangasDataDto>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class MangasDataDto(
|
||||||
|
@SerialName("name") val title: String,
|
||||||
|
val slug: String,
|
||||||
|
@SerialName("image") val thumbnailFileName: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ChapterDto(
|
||||||
|
val manga: ChapterMangaInfoDto,
|
||||||
|
val number: Int,
|
||||||
|
val images: Map<String, ChapterImagesDto>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ChapterMangaInfoDto(
|
||||||
|
val slug: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ChapterImagesDto(
|
||||||
|
val name: String,
|
||||||
|
val format: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SearchDto(
|
||||||
|
val result: List<SearchDataDto>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SearchDataDto(
|
||||||
|
val id: Int,
|
||||||
|
@SerialName("name") val title: String,
|
||||||
|
val slug: String,
|
||||||
|
@SerialName("image")val thumbnailFileName: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun String.toStatus(): Int = when (this) {
|
||||||
|
"En Marcha" -> SManga.ONGOING
|
||||||
|
"Terminado" -> SManga.COMPLETED
|
||||||
|
"Detenido" -> SManga.ON_HIATUS
|
||||||
|
"Pausado" -> SManga.ON_HIATUS
|
||||||
|
else -> SManga.UNKNOWN
|
||||||
|
}
|
@ -0,0 +1,148 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.es.mangatigre
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
|
||||||
|
class Type(name: String, val id: String) : Filter.CheckBox(name)
|
||||||
|
class TypeFilter(values: List<Type>) : Filter.Group<Type>("Tipos", values)
|
||||||
|
|
||||||
|
class Status(name: String, val id: String) : Filter.CheckBox(name)
|
||||||
|
class StatusFilter(values: List<Status>) : Filter.Group<Status>("Estado", values)
|
||||||
|
|
||||||
|
class Demographic(name: String, val id: String) : Filter.CheckBox(name)
|
||||||
|
class DemographicFilter(values: List<Demographic>) : Filter.Group<Demographic>("Demografía", values)
|
||||||
|
|
||||||
|
class Content(name: String, val id: String) : Filter.CheckBox(name)
|
||||||
|
class ContentFilter(values: List<Content>) : Filter.Group<Content>("Contenido", values)
|
||||||
|
|
||||||
|
class Format(name: String, val id: String) : Filter.CheckBox(name)
|
||||||
|
class FormatFilter(values: List<Format>) : Filter.Group<Format>("Formato", values)
|
||||||
|
|
||||||
|
class Genre(name: String, val id: String) : Filter.CheckBox(name)
|
||||||
|
class GenreFilter(values: List<Genre>) : Filter.Group<Genre>("Géneros", values)
|
||||||
|
|
||||||
|
class Theme(name: String, val id: String) : Filter.CheckBox(name)
|
||||||
|
class ThemeFilter(values: List<Theme>) : Filter.Group<Theme>("Temas", values)
|
||||||
|
|
||||||
|
open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
|
||||||
|
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
|
||||||
|
fun toUriPart() = vals[state].second
|
||||||
|
}
|
||||||
|
|
||||||
|
class OrderFilter() : UriPartFilter(
|
||||||
|
"Ordenar por",
|
||||||
|
arrayOf(
|
||||||
|
Pair("Alfabético", "name"),
|
||||||
|
Pair("Vistas", "views"),
|
||||||
|
Pair("Fecha Estreno", "date"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getFilterTypeList() = listOf(
|
||||||
|
Type("Manga", "1"),
|
||||||
|
Type("Manhwa", "2"),
|
||||||
|
Type("Manhua", "3"),
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getFilterStatusList() = listOf(
|
||||||
|
Status("En Marcha", "1"),
|
||||||
|
Status("Terminado", "2"),
|
||||||
|
Status("Detenido", "3"),
|
||||||
|
Status("Pausado", "4"),
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getFilterDemographicList() = listOf(
|
||||||
|
Demographic("Shonen", "1"),
|
||||||
|
Demographic("Seinen", "2"),
|
||||||
|
Demographic("Shojo", "3"),
|
||||||
|
Demographic("Josei", "4"),
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getFilterContentList() = listOf(
|
||||||
|
Content("Ecchi", "1"),
|
||||||
|
Content("Gore", "2"),
|
||||||
|
Content("Smut", "3"),
|
||||||
|
Content("Violencia Sexual", "4"),
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getFilterFormatList() = listOf(
|
||||||
|
Format("Adaptación", "14"),
|
||||||
|
Format("Antalogía", "9"),
|
||||||
|
Format("Color Completo", "18"),
|
||||||
|
Format("Coloreado Oficial", "19"),
|
||||||
|
Format("Coloreado Por Fan", "15"),
|
||||||
|
Format("Creado Por Usuario", "20"),
|
||||||
|
Format("Delincuencia", "16"),
|
||||||
|
Format("Doujinshi", "10"),
|
||||||
|
Format("Galardonado", "13"),
|
||||||
|
Format("One Shot", "11"),
|
||||||
|
Format("Tira Larga", "17"),
|
||||||
|
Format("Webcomic", "12"),
|
||||||
|
Format("YonKoma", "8"),
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getFilterGenreList() = listOf(
|
||||||
|
Genre("Acción", "49"),
|
||||||
|
Genre("Aventura", "50"),
|
||||||
|
Genre("Boys Love", "75"),
|
||||||
|
Genre("Chicas Mágicas", "73"),
|
||||||
|
Genre("Ciencia-Ficción", "64"),
|
||||||
|
Genre("Comedia", "51"),
|
||||||
|
Genre("Crimen", "52"),
|
||||||
|
Genre("Deporte", "65"),
|
||||||
|
Genre("Drama", "53"),
|
||||||
|
Genre("Fantasía", "54"),
|
||||||
|
Genre("Filosófico", "61"),
|
||||||
|
Genre("Girls Love", "76"),
|
||||||
|
Genre("Guerra", "74"),
|
||||||
|
Genre("Histórico", "55"),
|
||||||
|
Genre("Horror", "56"),
|
||||||
|
Genre("Isekai", "57"),
|
||||||
|
Genre("Mecha", "58"),
|
||||||
|
Genre("Médica", "59"),
|
||||||
|
Genre("Misterio", "60"),
|
||||||
|
Genre("Psicológico", "62"),
|
||||||
|
Genre("Recuentos De La Vida", "72"),
|
||||||
|
Genre("Romance", "63"),
|
||||||
|
Genre("Superhéroe", "66"),
|
||||||
|
Genre("Thriller", "67"),
|
||||||
|
Genre("Tragedia", "68"),
|
||||||
|
Genre("Wuxia", "69"),
|
||||||
|
Genre("Yaoi", "70"),
|
||||||
|
Genre("Yuri", "71"),
|
||||||
|
)
|
||||||
|
|
||||||
|
fun getFilterThemeList() = listOf(
|
||||||
|
Theme("Animales", "52"),
|
||||||
|
Theme("Apocalíptico", "50"),
|
||||||
|
Theme("Artes Marciales", "60"),
|
||||||
|
Theme("Chicas Monstruo", "77"),
|
||||||
|
Theme("Cocinando", "53"),
|
||||||
|
Theme("Crossdressing", "79"),
|
||||||
|
Theme("Delincuencia", "78"),
|
||||||
|
Theme("Demonios", "54"),
|
||||||
|
Theme("Extranjeros", "51"),
|
||||||
|
Theme("Fantasma", "55"),
|
||||||
|
Theme("Género Bender", "81"),
|
||||||
|
Theme("Gyaru", "56"),
|
||||||
|
Theme("Harén", "57"),
|
||||||
|
Theme("Incesto", "58"),
|
||||||
|
Theme("Lolicon", "59"),
|
||||||
|
Theme("Mafia", "64"),
|
||||||
|
Theme("Magia", "65"),
|
||||||
|
Theme("Militar", "61"),
|
||||||
|
Theme("Monstruos", "62"),
|
||||||
|
Theme("Música", "63"),
|
||||||
|
Theme("Ninja", "66"),
|
||||||
|
Theme("Policía", "67"),
|
||||||
|
Theme("Realidad Virtual", "74"),
|
||||||
|
Theme("Reencarnación", "68"),
|
||||||
|
Theme("Samurái", "73"),
|
||||||
|
Theme("Shotacon", "71"),
|
||||||
|
Theme("Sobrenatural", "69"),
|
||||||
|
Theme("Superpoderes", "82"),
|
||||||
|
Theme("Supervivencia", "72"),
|
||||||
|
Theme("Vampiros", "75"),
|
||||||
|
Theme("Vida Escolar", "70"),
|
||||||
|
Theme("Videojuegos", "80"),
|
||||||
|
Theme("Zombis", "76"),
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user