[TuMangaOnline] add SearchWithFilters, TagsExclusion, Demographics, ContentTypes
[TempleScanEsp] fix Add KODOMO on Demographic Add ONE_SHOT on ContentType
This commit is contained in:
@@ -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("há ", "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 {
|
||||
|
||||
!filter.query.isNullOrEmpty() -> {
|
||||
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("&_pg=1&page=")
|
||||
append("/library?_pg=1&page=")
|
||||
append(page.toString())
|
||||
|
||||
filter.query?.let {
|
||||
append("&title=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
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 -> ""
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
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")
|
||||
|
||||
Reference in New Issue
Block a user