[TuMangaOnline] add SearchWithFilters, TagsExclusion, Demographics, ContentTypes

[TempleScanEsp] fix
Add KODOMO on Demographic
Add ONE_SHOT on ContentType
devi 2 years ago
parent 42f2813e44
commit a55a4720fe

@ -27,4 +27,5 @@ public enum class ContentType {
* Use this type if no other suits your needs. For example, for an indie manga
*/
OTHER,
ONE_SHOT,
}

@ -5,5 +5,6 @@ public enum class Demographic {
SHOUJO,
SEINEN,
JOSEI,
KODOMO,
NONE,
}

@ -52,7 +52,13 @@ internal class ComickFunParser(context: MangaLoaderContext) :
ContentType.MANHUA,
ContentType.OTHER,
),
availableDemographics = EnumSet.allOf(Demographic::class.java),
availableDemographics = EnumSet.of(
Demographic.SHOUNEN,
Demographic.SHOUJO,
Demographic.SEINEN,
Demographic.JOSEI,
Demographic.NONE,
),
)
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
@ -132,6 +138,7 @@ internal class ComickFunParser(context: MangaLoaderContext) :
Demographic.SEINEN -> "3"
Demographic.JOSEI -> "4"
Demographic.NONE -> "5"
else -> ""
},
)
}

@ -82,7 +82,13 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
MangaState.ABANDONED,
),
availableContentRating = EnumSet.allOf(ContentRating::class.java),
availableDemographics = EnumSet.allOf(Demographic::class.java),
availableDemographics = EnumSet.of(
Demographic.SHOUNEN,
Demographic.SHOUJO,
Demographic.SEINEN,
Demographic.JOSEI,
Demographic.NONE,
),
availableLocales = localesDeferred.await(),
)
}
@ -164,6 +170,7 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
Demographic.SEINEN -> "seinen"
Demographic.JOSEI -> "josei"
Demographic.NONE -> "none"
else -> ""
},
)
}

@ -1,21 +1,23 @@
package org.koitharu.kotatsu.parsers.site.es
import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
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.model.*
import org.koitharu.kotatsu.parsers.network.UserAgents
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.*
@MangaSourceParser("TEMPLESCANESP", "TempleScanEsp", "es", ContentType.HENTAI)
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")
@ -31,32 +33,17 @@ internal class TempleScanEsp(context: MangaLoaderContext) :
override suspend fun getFilterOptions() = MangaListFilterOptions()
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
val url = buildString {
append("https://")
append(domain)
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")
override suspend fun getList(order: SortOrder, filter: MangaListFilter): List<Manga> {
val json = webClient.httpGet("https://apis.$domain/api/searchProject").parseJson().getJSONArray("response")
return json.mapJSON {
val href = "https://$domain/ver/${it.getString("slug")}"
Manga(
id = generateUid(href),
url = href,
publicUrl = href.toAbsoluteUrl(div.host ?: domain),
coverUrl = div.selectFirst("img")?.src().orEmpty(),
title = div.selectFirst("figcaption")?.text().orEmpty(),
altTitle = null,
publicUrl = href,
coverUrl = it.getString("urlImg").orEmpty(),
title = it.getString("name").orEmpty(),
altTitle = it.getString("alternativeName").orEmpty(),
rating = RATING_UNKNOWN,
tags = emptySet(),
author = null,
@ -70,36 +57,34 @@ internal class TempleScanEsp(context: MangaLoaderContext) :
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
val fullUrl = manga.url.toAbsoluteUrl(domain)
val doc = webClient.httpGet(fullUrl).parseHtml()
val chaptersDeferred = getChapters(doc)
val dateFormat = SimpleDateFormat("dd/mm/yyyy", sourceLocale)
manga.copy(
description = doc.requireElementById("section-sinopsis").html(),
chapters = chaptersDeferred,
description = doc.selectFirst(".infoProject_projectInfo__786qu")?.text().orEmpty(),
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> {
val fullUrl = chapter.url.toAbsoluteUrl(domain)
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")
MangaPage(
id = generateUid(img),
@ -110,23 +95,41 @@ internal class TempleScanEsp(context: MangaLoaderContext) :
}
}
private fun parseUploadDate(timeStr: String?): Long {
timeStr ?: return 0
val timeWords = timeStr.split(' ')
if (timeWords.size != 3) return 0
val timeWord = timeWords[1]
val timeAmount = timeWords[0].toIntOrNull() ?: return 0
val timeUnit = when (timeWord) {
"minute", "minutes" -> Calendar.MINUTE
"hour", "hours" -> Calendar.HOUR
"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 parseChapterDate(dateFormat: DateFormat, date: String?): Long {
val d = date?.lowercase() ?: return 0
return when {
WordSet("", "hace ").startsWith(d) -> {
parseRelativeDate(d)
}
else -> dateFormat.tryParse(date)
}
}
private fun parseRelativeDate(date: String): Long {
val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0
val cal = Calendar.getInstance()
cal.add(timeUnit, -timeAmount)
return cal.time.time
return when {
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
}
}
}

@ -40,66 +40,113 @@ internal class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaPars
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
isMultipleTagsSupported = true,
isSearchSupported = true,
isSearchWithFiltersSupported = true,
isTagsExclusionSupported = true,
isMultipleTagsSupported = true,
)
override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(),
availableContentRating = EnumSet.of(ContentRating.SAFE, ContentRating.ADULT),
availableDemographics = EnumSet.of(
Demographic.SHOUNEN,
Demographic.SHOUJO,
Demographic.SEINEN,
Demographic.JOSEI,
Demographic.KODOMO,
),
availableContentTypes = EnumSet.of(
ContentType.MANGA,
ContentType.MANHWA,
ContentType.MANHUA,
ContentType.NOVEL,
ContentType.ONE_SHOT,
ContentType.HENTAI,
ContentType.OTHER,
),
)
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
val url = buildString {
append("https://")
append(domain)
append("/library")
when {
append("/library?_pg=1&page=")
append(page.toString())
!filter.query.isNullOrEmpty() -> {
append("?title=")
append(filter.query.urlEncoded())
}
filter.query?.let {
append("&title=")
append(filter.query.urlEncoded())
}
else -> {
append("?order_item=")
append(
when (order) {
SortOrder.POPULARITY -> "likes_count&order_dir=desc"
SortOrder.POPULARITY_ASC -> "likes_count&order_dir=asc"
SortOrder.UPDATED -> "release_date&order_dir=desc"
SortOrder.UPDATED_ASC -> "release_date&order_dir=asc"
SortOrder.NEWEST -> "creation&order_dir=desc"
SortOrder.NEWEST_ASC -> "creation&order_dir=asc"
SortOrder.ALPHABETICAL -> "alphabetically&order_dir=asc"
SortOrder.ALPHABETICAL_DESC -> "alphabetically&order_dir=desc"
SortOrder.RATING -> "score&order_dir=desc"
SortOrder.RATING_ASC -> "score&order_dir=asc"
else -> "release_date&order_dir=desc"
},
)
append("&filter_by=title")
if (filter.tags.isNotEmpty()) {
for (tag in filter.tags) {
append("&genders[]=")
append(tag.key)
}
}
filter.contentRating.oneOrThrowIfMany()?.let {
append("&erotic=")
append(
when (it) {
ContentRating.SAFE -> "false"
ContentRating.ADULT -> "true"
else -> ""
},
)
}
}
append("&order_item=")
append(
when (order) {
SortOrder.POPULARITY -> "likes_count&order_dir=desc"
SortOrder.POPULARITY_ASC -> "likes_count&order_dir=asc"
SortOrder.UPDATED -> "release_date&order_dir=desc"
SortOrder.UPDATED_ASC -> "release_date&order_dir=asc"
SortOrder.NEWEST -> "creation&order_dir=desc"
SortOrder.NEWEST_ASC -> "creation&order_dir=asc"
SortOrder.ALPHABETICAL -> "alphabetically&order_dir=asc"
SortOrder.ALPHABETICAL_DESC -> "alphabetically&order_dir=desc"
SortOrder.RATING -> "score&order_dir=desc"
SortOrder.RATING_ASC -> "score&order_dir=asc"
else -> "release_date&order_dir=desc"
},
)
append("&filter_by=title")
filter.tags.forEach {
append("&genders[]=")
append(it.key)
}
filter.tagsExclude.forEach {
append("&exclude_genders[]=")
append(it.key)
}
append("&type=")
filter.types.forEach {
append(
when (it) {
ContentType.MANGA -> "manga"
ContentType.MANHWA -> "manhwa"
ContentType.MANHUA -> "manhua"
ContentType.NOVEL -> "novel"
ContentType.ONE_SHOT -> "one_shot"
ContentType.HENTAI -> "doujinshi"
ContentType.OTHER -> "oel"
else -> ""
},
)
}
filter.demographics.forEach {
append("&demography=")
append(
when (it) {
Demographic.SHOUNEN -> "shounen"
Demographic.SHOUJO -> "shoujo"
Demographic.SEINEN -> "seinen"
Demographic.JOSEI -> "josei"
Demographic.KODOMO -> "kodomo"
else -> ""
},
)
}
filter.contentRating.oneOrThrowIfMany()?.let {
append("&erotic=")
append(
when (it) {
ContentRating.SAFE -> "false"
ContentRating.ADULT -> "true"
else -> ""
},
)
}
append("&_pg=1&page=")
append(page.toString())
}
val doc = webClient.httpGet(url, getRequestHeaders()).parseHtml()
val items = doc.body().select("div.element")

@ -791,10 +791,10 @@ internal abstract class MadaraParser(
WordSet("jam", "saat", "heure", "hora", "horas", "hour", "hours", "h", "ساعات", "ساعة")
.anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis
WordSet("hari", "gün", "jour", "día", "dia", "day", "days", "d", "день")
WordSet("hari", "gün", "jour", "día", "dia", "day", "días", "days", "d", "день")
.anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
WordSet("month", "months", "أشهر", "mois")
WordSet("month", "months", "أشهر", "mois", "meses", "mes")
.anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
WordSet("year")

Loading…
Cancel
Save