|
|
|
@ -1,21 +1,23 @@
|
|
|
|
package org.koitharu.kotatsu.parsers.site.es
|
|
|
|
package org.koitharu.kotatsu.parsers.site.es
|
|
|
|
|
|
|
|
|
|
|
|
import kotlinx.coroutines.coroutineScope
|
|
|
|
import kotlinx.coroutines.coroutineScope
|
|
|
|
import org.jsoup.nodes.Document
|
|
|
|
|
|
|
|
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
|
|
|
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
|
|
|
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
|
|
|
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
|
|
|
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
|
|
|
import org.koitharu.kotatsu.parsers.SinglePageMangaParser
|
|
|
|
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
|
|
|
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
|
|
|
import org.koitharu.kotatsu.parsers.model.*
|
|
|
|
import org.koitharu.kotatsu.parsers.model.*
|
|
|
|
import org.koitharu.kotatsu.parsers.network.UserAgents
|
|
|
|
import org.koitharu.kotatsu.parsers.network.UserAgents
|
|
|
|
import org.koitharu.kotatsu.parsers.util.*
|
|
|
|
import org.koitharu.kotatsu.parsers.util.*
|
|
|
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.json.mapJSON
|
|
|
|
|
|
|
|
import java.text.DateFormat
|
|
|
|
|
|
|
|
import java.text.SimpleDateFormat
|
|
|
|
import java.util.*
|
|
|
|
import java.util.*
|
|
|
|
|
|
|
|
|
|
|
|
@MangaSourceParser("TEMPLESCANESP", "TempleScanEsp", "es", ContentType.HENTAI)
|
|
|
|
@MangaSourceParser("TEMPLESCANESP", "TempleScanEsp", "es", ContentType.HENTAI)
|
|
|
|
internal class TempleScanEsp(context: MangaLoaderContext) :
|
|
|
|
internal class TempleScanEsp(context: MangaLoaderContext) :
|
|
|
|
PagedMangaParser(context, MangaParserSource.TEMPLESCANESP, pageSize = 15) {
|
|
|
|
SinglePageMangaParser(context, MangaParserSource.TEMPLESCANESP) {
|
|
|
|
|
|
|
|
|
|
|
|
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.NEWEST, SortOrder.UPDATED)
|
|
|
|
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.NEWEST_ASC)
|
|
|
|
|
|
|
|
|
|
|
|
override val configKeyDomain = ConfigKey.Domain("templescanesp.net")
|
|
|
|
override val configKeyDomain = ConfigKey.Domain("templescanesp.net")
|
|
|
|
|
|
|
|
|
|
|
|
@ -31,32 +33,17 @@ internal class TempleScanEsp(context: MangaLoaderContext) :
|
|
|
|
|
|
|
|
|
|
|
|
override suspend fun getFilterOptions() = MangaListFilterOptions()
|
|
|
|
override suspend fun getFilterOptions() = MangaListFilterOptions()
|
|
|
|
|
|
|
|
|
|
|
|
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
|
|
|
|
override suspend fun getList(order: SortOrder, filter: MangaListFilter): List<Manga> {
|
|
|
|
val url = buildString {
|
|
|
|
val json = webClient.httpGet("https://apis.$domain/api/searchProject").parseJson().getJSONArray("response")
|
|
|
|
append("https://")
|
|
|
|
return json.mapJSON {
|
|
|
|
append(domain)
|
|
|
|
val href = "https://$domain/ver/${it.getString("slug")}"
|
|
|
|
if (order == SortOrder.NEWEST) {
|
|
|
|
|
|
|
|
append("/comics?page=")
|
|
|
|
|
|
|
|
append(page)
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
if (page > 1) {
|
|
|
|
|
|
|
|
return emptyList()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
val doc = webClient.httpGet(url).parseHtml()
|
|
|
|
|
|
|
|
return doc.select("div.grid figure").ifEmpty {
|
|
|
|
|
|
|
|
doc.requireElementById("projectsDiv").select("figure")
|
|
|
|
|
|
|
|
}.map { div ->
|
|
|
|
|
|
|
|
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
|
|
|
|
|
|
|
Manga(
|
|
|
|
Manga(
|
|
|
|
id = generateUid(href),
|
|
|
|
id = generateUid(href),
|
|
|
|
url = href,
|
|
|
|
url = href,
|
|
|
|
publicUrl = href.toAbsoluteUrl(div.host ?: domain),
|
|
|
|
publicUrl = href,
|
|
|
|
coverUrl = div.selectFirst("img")?.src().orEmpty(),
|
|
|
|
coverUrl = it.getString("urlImg").orEmpty(),
|
|
|
|
title = div.selectFirst("figcaption")?.text().orEmpty(),
|
|
|
|
title = it.getString("name").orEmpty(),
|
|
|
|
altTitle = null,
|
|
|
|
altTitle = it.getString("alternativeName").orEmpty(),
|
|
|
|
rating = RATING_UNKNOWN,
|
|
|
|
rating = RATING_UNKNOWN,
|
|
|
|
tags = emptySet(),
|
|
|
|
tags = emptySet(),
|
|
|
|
author = null,
|
|
|
|
author = null,
|
|
|
|
@ -70,36 +57,34 @@ internal class TempleScanEsp(context: MangaLoaderContext) :
|
|
|
|
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
|
|
|
|
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
|
|
|
|
val fullUrl = manga.url.toAbsoluteUrl(domain)
|
|
|
|
val fullUrl = manga.url.toAbsoluteUrl(domain)
|
|
|
|
val doc = webClient.httpGet(fullUrl).parseHtml()
|
|
|
|
val doc = webClient.httpGet(fullUrl).parseHtml()
|
|
|
|
val chaptersDeferred = getChapters(doc)
|
|
|
|
val dateFormat = SimpleDateFormat("dd/mm/yyyy", sourceLocale)
|
|
|
|
manga.copy(
|
|
|
|
manga.copy(
|
|
|
|
description = doc.requireElementById("section-sinopsis").html(),
|
|
|
|
description = doc.selectFirst(".infoProject_projectInfo__786qu")?.text().orEmpty(),
|
|
|
|
chapters = chaptersDeferred,
|
|
|
|
chapters = doc.body().select(".contenedor a")
|
|
|
|
|
|
|
|
.mapChapters(reversed = true) { i, a ->
|
|
|
|
|
|
|
|
val href = a.attrAsRelativeUrl("href")
|
|
|
|
|
|
|
|
MangaChapter(
|
|
|
|
|
|
|
|
id = generateUid(href),
|
|
|
|
|
|
|
|
name = a.selectFirst("span")?.text() ?: "Capítulo ${i + 1f}",
|
|
|
|
|
|
|
|
number = i + 1f,
|
|
|
|
|
|
|
|
volume = 0,
|
|
|
|
|
|
|
|
url = href,
|
|
|
|
|
|
|
|
uploadDate = parseChapterDate(
|
|
|
|
|
|
|
|
dateFormat,
|
|
|
|
|
|
|
|
a.selectFirst(".infoProject_dateChapter__BIuU7")?.text(),
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
source = source,
|
|
|
|
|
|
|
|
scanlator = null,
|
|
|
|
|
|
|
|
branch = null,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
},
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun getChapters(doc: Document): List<MangaChapter> {
|
|
|
|
|
|
|
|
return doc.body().select("div.grid-capitulos div.contenedor-capitulo-miniatura")
|
|
|
|
|
|
|
|
.mapChapters(reversed = true) { i, div ->
|
|
|
|
|
|
|
|
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
|
|
|
|
|
|
|
val date = parseUploadDate(div.selectFirstOrThrow("time").text())
|
|
|
|
|
|
|
|
MangaChapter(
|
|
|
|
|
|
|
|
id = generateUid(href),
|
|
|
|
|
|
|
|
name = div.requireElementById("name").text(),
|
|
|
|
|
|
|
|
number = i + 1f,
|
|
|
|
|
|
|
|
volume = 0,
|
|
|
|
|
|
|
|
url = href,
|
|
|
|
|
|
|
|
uploadDate = date,
|
|
|
|
|
|
|
|
source = source,
|
|
|
|
|
|
|
|
scanlator = null,
|
|
|
|
|
|
|
|
branch = null,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
|
|
|
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
|
|
|
val fullUrl = chapter.url.toAbsoluteUrl(domain)
|
|
|
|
val fullUrl = chapter.url.toAbsoluteUrl(domain)
|
|
|
|
val doc = webClient.httpGet(fullUrl).parseHtml()
|
|
|
|
val doc = webClient.httpGet(fullUrl).parseHtml()
|
|
|
|
return doc.select("main.contenedor-imagen img").map { url ->
|
|
|
|
return doc.select("main.contenedor img.readChapter_image__450v_").map { url ->
|
|
|
|
val img = url.src()?.toRelativeUrl(domain) ?: url.parseFailed("Image src not found")
|
|
|
|
val img = url.src()?.toRelativeUrl(domain) ?: url.parseFailed("Image src not found")
|
|
|
|
MangaPage(
|
|
|
|
MangaPage(
|
|
|
|
id = generateUid(img),
|
|
|
|
id = generateUid(img),
|
|
|
|
@ -110,23 +95,41 @@ internal class TempleScanEsp(context: MangaLoaderContext) :
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun parseUploadDate(timeStr: String?): Long {
|
|
|
|
private fun parseChapterDate(dateFormat: DateFormat, date: String?): Long {
|
|
|
|
timeStr ?: return 0
|
|
|
|
val d = date?.lowercase() ?: return 0
|
|
|
|
val timeWords = timeStr.split(' ')
|
|
|
|
return when {
|
|
|
|
if (timeWords.size != 3) return 0
|
|
|
|
|
|
|
|
val timeWord = timeWords[1]
|
|
|
|
WordSet("há ", "hace ").startsWith(d) -> {
|
|
|
|
val timeAmount = timeWords[0].toIntOrNull() ?: return 0
|
|
|
|
parseRelativeDate(d)
|
|
|
|
val timeUnit = when (timeWord) {
|
|
|
|
}
|
|
|
|
"minute", "minutes" -> Calendar.MINUTE
|
|
|
|
|
|
|
|
"hour", "hours" -> Calendar.HOUR
|
|
|
|
else -> dateFormat.tryParse(date)
|
|
|
|
"day", "days" -> Calendar.DAY_OF_YEAR
|
|
|
|
|
|
|
|
"week", "weeks" -> Calendar.WEEK_OF_YEAR
|
|
|
|
|
|
|
|
"month", "months" -> Calendar.MONTH
|
|
|
|
|
|
|
|
"year", "years" -> Calendar.YEAR
|
|
|
|
|
|
|
|
else -> return 0
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private fun parseRelativeDate(date: String): Long {
|
|
|
|
|
|
|
|
val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0
|
|
|
|
val cal = Calendar.getInstance()
|
|
|
|
val cal = Calendar.getInstance()
|
|
|
|
cal.add(timeUnit, -timeAmount)
|
|
|
|
return when {
|
|
|
|
return cal.time.time
|
|
|
|
WordSet("segundo", "second")
|
|
|
|
|
|
|
|
.anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
WordSet("minuto")
|
|
|
|
|
|
|
|
.anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
WordSet("hora", "horas")
|
|
|
|
|
|
|
|
.anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
WordSet("día", "días")
|
|
|
|
|
|
|
|
.anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
WordSet("meses", "mes")
|
|
|
|
|
|
|
|
.anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
WordSet("year")
|
|
|
|
|
|
|
|
.anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
else -> 0
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|