[mangaDex] Fix SortOrder and remove YearRange

[Bato] add OriginalLocale
[MangaPark] add OriginalLocale, SearchWithFilters
[Comick] add SearchWithFilters
[NineMangaParser] add SearchWithFilters
[AnimeBootstrap] add SearchWithFilters , ContentTypes
[madara] add Year, SearchWithFilters, SortOrder.RELEVANCE
[TeamXNovel] add ContentTypes , SearchWithFilters
This commit is contained in:
devi
2024-09-21 11:27:04 +02:00
parent f2354957e6
commit d1f9b0d829
22 changed files with 656 additions and 570 deletions

View File

@@ -49,10 +49,10 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
}
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.NEWEST,
SortOrder.UPDATED,
SortOrder.POPULARITY,
SortOrder.ALPHABETICAL,
SortOrder.UPDATED,
SortOrder.NEWEST,
SortOrder.POPULARITY,
SortOrder.POPULARITY_YEAR,
SortOrder.POPULARITY_MONTH,
SortOrder.POPULARITY_WEEK,
@@ -65,6 +65,7 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
isMultipleTagsSupported = true,
isTagsExclusionSupported = true,
isSearchSupported = true,
isOriginalLocaleSupported = true,
)
override suspend fun getFilterOptions() = MangaListFilterOptions(
@@ -165,6 +166,15 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
}
filter.originalLocale?.let {
append("&origs=")
if (it.language == "in") {
append("id")
} else {
append(it.language)
}
}
append("&genres=")
if (filter.tags.isNotEmpty()) {
appendAll(filter.tags, ",") { it.key }

View File

@@ -34,13 +34,12 @@ internal class ComickFunParser(context: MangaLoaderContext) :
SortOrder.NEWEST,
)
private val tagsArray = SuspendLazy(::loadTags)
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
isMultipleTagsSupported = true,
isTagsExclusionSupported = true,
isSearchSupported = true,
isSearchWithFiltersSupported = true,
isYearRangeSupported = true,
)
@@ -66,80 +65,77 @@ internal class ComickFunParser(context: MangaLoaderContext) :
.addQueryParameter("tachiyomi", "true")
.addQueryParameter("limit", pageSize.toString())
.addQueryParameter("page", page.toString())
when {
!filter.query.isNullOrEmpty() -> {
url.addQueryParameter("q", filter.query)
}
else -> {
filter.tags.forEach {
url.addQueryParameter("genres", it.key)
}
filter.tagsExclude.forEach {
url.addQueryParameter("excludes", it.key)
}
url.addQueryParameter(
"sort",
when (order) {
SortOrder.POPULARITY -> "view"
SortOrder.UPDATED -> "uploaded"
SortOrder.NEWEST -> "created_at"
SortOrder.RATING -> "rating"
else -> "uploaded"
},
)
filter.states.oneOrThrowIfMany()?.let {
url.addQueryParameter(
"status",
when (it) {
MangaState.ONGOING -> "1"
MangaState.FINISHED -> "2"
MangaState.ABANDONED -> "3"
MangaState.PAUSED -> "4"
else -> ""
},
)
}
if (filter.yearFrom != YEAR_UNKNOWN) {
url.addQueryParameter("from", filter.yearFrom.toString())
}
if (filter.yearTo != YEAR_UNKNOWN) {
url.addQueryParameter("to", filter.yearTo.toString())
}
filter.types.forEach {
url.addQueryParameter(
"country",
when (it) {
ContentType.MANGA -> "jp"
ContentType.MANHWA -> "kr"
ContentType.MANHUA -> "cn"
ContentType.OTHER -> "others"
else -> ""
},
)
}
filter.demographics.forEach {
url.addQueryParameter(
"demographic",
when (it) {
Demographic.SHOUNEN -> "1"
Demographic.SHOUJO -> "2"
Demographic.SEINEN -> "3"
Demographic.JOSEI -> "4"
Demographic.NONE -> "5"
},
)
}
}
filter.query?.let {
url.addQueryParameter("q", filter.query)
}
filter.tags.forEach {
url.addQueryParameter("genres", it.key)
}
filter.tagsExclude.forEach {
url.addQueryParameter("excludes", it.key)
}
url.addQueryParameter(
"sort",
when (order) {
SortOrder.NEWEST -> "created_at"
SortOrder.POPULARITY -> "view"
SortOrder.RATING -> "rating"
SortOrder.UPDATED -> "uploaded"
else -> "uploaded"
},
)
filter.states.oneOrThrowIfMany()?.let {
url.addQueryParameter(
"status",
when (it) {
MangaState.ONGOING -> "1"
MangaState.FINISHED -> "2"
MangaState.ABANDONED -> "3"
MangaState.PAUSED -> "4"
else -> ""
},
)
}
if (filter.yearFrom != YEAR_UNKNOWN) {
url.addQueryParameter("from", filter.yearFrom.toString())
}
if (filter.yearTo != YEAR_UNKNOWN) {
url.addQueryParameter("to", filter.yearTo.toString())
}
filter.types.forEach {
url.addQueryParameter(
"country",
when (it) {
ContentType.MANGA -> "jp"
ContentType.MANHWA -> "kr"
ContentType.MANHUA -> "cn"
ContentType.OTHER -> "others"
else -> ""
},
)
}
filter.demographics.forEach {
url.addQueryParameter(
"demographic",
when (it) {
Demographic.SHOUNEN -> "1"
Demographic.SHOUJO -> "2"
Demographic.SEINEN -> "3"
Demographic.JOSEI -> "4"
Demographic.NONE -> "5"
},
)
}
val ja = webClient.httpGet(url.build()).parseJsonArray()
val tagsMap = tagsArray.get()
return ja.mapJSON { jo ->
@@ -193,46 +189,6 @@ internal class ComickFunParser(context: MangaLoaderContext) :
)
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val jo = webClient.httpGet(
"https://api.${domain}/chapter/${chapter.url}?tachiyomi=true",
).parseJson().getJSONObject("chapter")
return jo.getJSONArray("images").mapJSON {
val url = it.getString("url")
MangaPage(
id = generateUid(url),
url = url,
preview = null,
source = source,
)
}
}
private suspend fun fetchAvailableTags(): Set<MangaTag> {
val sparseArray = tagsArray.get()
val set = ArraySet<MangaTag>(sparseArray.size())
for (i in 0 until sparseArray.size()) {
set.add(sparseArray.valueAt(i))
}
return set
}
private suspend fun loadTags(): SparseArrayCompat<MangaTag> {
val ja = webClient.httpGet("https://api.${domain}/genre").parseJsonArray()
val tags = SparseArrayCompat<MangaTag>(ja.length())
for (jo in ja.JSONIterator()) {
tags.append(
jo.getInt("id"),
MangaTag(
title = jo.getString("name").toTitleCase(Locale.ENGLISH),
key = jo.getString("slug"),
source = source,
),
)
}
return tags
}
private suspend fun getChapters(hid: String): List<MangaChapter> {
val ja = webClient.httpGet(
url = "https://api.${domain}/comic/$hid/chapters?limit=$CHAPTERS_LIMIT",
@@ -272,6 +228,48 @@ internal class ComickFunParser(context: MangaLoaderContext) :
}
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val jo = webClient.httpGet(
"https://api.${domain}/chapter/${chapter.url}?tachiyomi=true",
).parseJson().getJSONObject("chapter")
return jo.getJSONArray("images").mapJSON {
val url = it.getString("url")
MangaPage(
id = generateUid(url),
url = url,
preview = null,
source = source,
)
}
}
private val tagsArray = SuspendLazy(::loadTags)
private suspend fun fetchAvailableTags(): Set<MangaTag> {
val sparseArray = tagsArray.get()
val set = ArraySet<MangaTag>(sparseArray.size())
for (i in 0 until sparseArray.size()) {
set.add(sparseArray.valueAt(i))
}
return set
}
private suspend fun loadTags(): SparseArrayCompat<MangaTag> {
val ja = webClient.httpGet("https://api.${domain}/genre").parseJsonArray()
val tags = SparseArrayCompat<MangaTag>(ja.length())
for (jo in ja.JSONIterator()) {
tags.append(
jo.getInt("id"),
MangaTag(
title = jo.getString("name").toTitleCase(Locale.ENGLISH),
key = jo.getString("slug"),
source = source,
),
)
}
return tags
}
private fun JSONObject.selectGenres(tags: SparseArrayCompat<MangaTag>): Set<MangaTag> {
val array = optJSONArray("genres") ?: return emptySet()
val res = ArraySet<MangaTag>(array.length())

View File

@@ -44,6 +44,22 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
keys.add(preferredServerKey)
}
override val availableSortOrders: EnumSet<SortOrder> = EnumSet.of(
SortOrder.UPDATED,
SortOrder.UPDATED_ASC,
SortOrder.POPULARITY,
SortOrder.POPULARITY_ASC,
SortOrder.RATING,
SortOrder.RATING_ASC,
SortOrder.NEWEST,
SortOrder.NEWEST_ASC,
SortOrder.ALPHABETICAL,
SortOrder.ALPHABETICAL_DESC,
SortOrder.ADDED,
SortOrder.ADDED_ASC,
SortOrder.RELEVANCE,
)
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
isMultipleTagsSupported = true,
@@ -54,8 +70,6 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
isOriginalLocaleSupported = true,
)
override val availableSortOrders: EnumSet<SortOrder> = EnumSet.allOf(SortOrder::class.java)
override suspend fun getFilterOptions(): MangaListFilterOptions = coroutineScope {
val localesDeferred = async { fetchAvailableLocales() }
val tagsDeferred = async { fetchAvailableTags() }

View File

@@ -16,16 +16,23 @@ import java.util.*
internal class MangaPark(context: MangaLoaderContext) :
PagedMangaParser(context, MangaParserSource.MANGAPARK, pageSize = 36) {
override val configKeyDomain = ConfigKey.Domain("mangapark.net")
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys)
keys.add(userAgentKey)
}
override val availableSortOrders: Set<SortOrder> =
EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.NEWEST, SortOrder.ALPHABETICAL, SortOrder.RATING)
override val configKeyDomain = ConfigKey.Domain("mangapark.net")
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
isMultipleTagsSupported = true,
isTagsExclusionSupported = true,
isSearchSupported = true,
isSearchWithFiltersSupported = true,
isOriginalLocaleSupported = true,
)
override suspend fun getFilterOptions() = MangaListFilterOptions(
@@ -56,13 +63,6 @@ internal class MangaPark(context: MangaLoaderContext) :
),
)
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys)
keys.add(userAgentKey)
}
private val tagsMap = SuspendLazy(::parseTags)
init {
context.cookieJar.insertCookies(domain, "nsfw", "2")
}
@@ -73,67 +73,67 @@ internal class MangaPark(context: MangaLoaderContext) :
append(domain)
append("/search?page=")
append(page.toString())
when {
!filter.query.isNullOrEmpty() -> {
append("&word=")
append(filter.query.urlEncoded())
}
filter.query?.let {
append("&word=")
append(filter.query.urlEncoded())
}
else -> {
append("&genres=")
if (filter.tags.isNotEmpty()) {
appendAll(filter.tags, ",") { it.key }
}
append("&genres=")
if (filter.tags.isNotEmpty()) {
appendAll(filter.tags, ",") { it.key }
}
append("|")
if (filter.tagsExclude.isNotEmpty()) {
appendAll(filter.tagsExclude, ",") { it.key }
}
append("|")
if (filter.tagsExclude.isNotEmpty()) {
appendAll(filter.tagsExclude, ",") { it.key }
}
if (filter.contentRating.isNotEmpty()) {
filter.contentRating.oneOrThrowIfMany()?.let {
append(
when (it) {
ContentRating.SAFE -> append(",gore,bloody,violence,ecchi,adult,mature,smut,hentai")
else -> append("")
},
)
}
}
filter.states.oneOrThrowIfMany()?.let {
append("&status=")
append(
when (it) {
MangaState.ONGOING -> "ongoing"
MangaState.FINISHED -> "completed"
MangaState.PAUSED -> "hiatus"
MangaState.ABANDONED -> "cancelled"
MangaState.UPCOMING -> "pending"
},
)
}
append("&sortby=")
if (filter.contentRating.isNotEmpty()) {
filter.contentRating.oneOrThrowIfMany()?.let {
append(
when (order) {
SortOrder.POPULARITY -> "views_d000"
SortOrder.UPDATED -> "field_update"
SortOrder.NEWEST -> "field_create"
SortOrder.ALPHABETICAL -> "field_name"
SortOrder.RATING -> "field_score"
else -> ""
when (it) {
ContentRating.SAFE -> append(",gore,bloody,violence,ecchi,adult,mature,smut,hentai")
else -> append("")
},
)
filter.locale?.let {
append("&lang=")
append(it.language)
}
}
}
filter.states.oneOrThrowIfMany()?.let {
append("&status=")
append(
when (it) {
MangaState.ONGOING -> "ongoing"
MangaState.FINISHED -> "completed"
MangaState.PAUSED -> "hiatus"
MangaState.ABANDONED -> "cancelled"
MangaState.UPCOMING -> "pending"
},
)
}
append("&sortby=")
append(
when (order) {
SortOrder.POPULARITY -> "views_d000"
SortOrder.UPDATED -> "field_update"
SortOrder.NEWEST -> "field_create"
SortOrder.ALPHABETICAL -> "field_name"
SortOrder.RATING -> "field_score"
else -> ""
},
)
filter.locale?.let {
append("&lang=")
append(it.language)
}
filter.originalLocale?.let {
append("&orig=")
append(it.language)
}
}
val doc = webClient.httpGet(url).parseHtml()
@@ -156,6 +156,8 @@ internal class MangaPark(context: MangaLoaderContext) :
}
}
private val tagsMap = SuspendLazy(::parseTags)
private suspend fun parseTags(): Map<String, MangaTag> {
val tagElements = webClient.httpGet("https://$domain/search").parseHtml()
.select("div.flex-col:contains(Genres) div.whitespace-nowrap")
@@ -217,13 +219,18 @@ internal class MangaPark(context: MangaLoaderContext) :
private fun parseChapterDate(dateFormat: DateFormat, date: String?): Long {
val d = date?.lowercase() ?: return 0
return when {
d.endsWith(" ago") -> parseRelativeDate(date)
d.startsWith("just now") -> Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}.timeInMillis
WordSet(" ago").endsWith(d) -> {
parseRelativeDate(d)
}
WordSet("just now").startsWith(d) -> {
Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}.timeInMillis
}
else -> dateFormat.tryParse(date)
}

View File

@@ -44,6 +44,7 @@ internal abstract class NineMangaParser(
get() = MangaListFilterCapabilities(
isMultipleTagsSupported = true,
isTagsExclusionSupported = true,
isSearchWithFiltersSupported = true,
isSearchSupported = true,
)
@@ -69,39 +70,35 @@ internal abstract class NineMangaParser(
val url = buildString {
append("https://")
append(domain)
when {
!filter.query.isNullOrEmpty() -> {
append("/search/?name_sel=&wd=")
if (filter.tags.isNotEmpty() || filter.tagsExclude.isNotEmpty() || filter.states.isNotEmpty() || !filter.query.isNullOrEmpty()) {
append("/search/")
append("?page=")
append(page.toString())
filter.query?.let {
append("&name_sel=contain&wd=")
append(filter.query.urlEncoded())
append("&page=")
append(page)
append(".html")
}
else -> {
append("&category_id=")
append(filter.tags.joinToString(separator = ",") { it.key })
if (filter.tags.isNotEmpty() || filter.tagsExclude.isNotEmpty() || filter.states.isNotEmpty()) {
append("/search/?category_id=")
append(filter.tags.joinToString(separator = ",") { it.key })
append("&out_category_id=")
append(filter.tagsExclude.joinToString(separator = ",") { it.key })
append("&out_category_id=")
append(filter.tagsExclude.joinToString(separator = ",") { it.key })
filter.states.oneOrThrowIfMany()?.let {
append("&completed_series=")
when (it) {
MangaState.ONGOING -> append("no")
MangaState.FINISHED -> append("yes")
else -> append("either")
}
}
append("&page=")
} else {
append("/category/index_")
filter.states.oneOrThrowIfMany()?.let {
append("&completed_series=")
when (it) {
MangaState.ONGOING -> append("no")
MangaState.FINISHED -> append("yes")
else -> append("either")
}
append(page.toString())
append(".html")
}
} else {
append("/category/index_")
append(page.toString())
}
}
val doc = webClient.httpGet(url).parseHtml()

View File

@@ -43,10 +43,16 @@ internal abstract class AnimeBootstrapParser(
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
isSearchSupported = true,
isSearchWithFiltersSupported = true,
)
override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(),
availableContentTypes = EnumSet.of(
ContentType.MANGA,
ContentType.MANHWA,
ContentType.MANHUA,
),
)
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
@@ -58,30 +64,37 @@ internal abstract class AnimeBootstrapParser(
append(page.toString())
append("&type=all")
when {
!filter.query.isNullOrEmpty() -> {
append("&search=")
append(filter.query.urlEncoded())
}
else -> {
filter.tags.oneOrThrowIfMany()?.let {
append("&categorie=")
append(it.key)
}
append("&sort=")
when (order) {
SortOrder.POPULARITY -> append("view")
SortOrder.UPDATED -> append("updated")
SortOrder.ALPHABETICAL -> append("default")
SortOrder.NEWEST -> append("published")
else -> append("updated")
}
}
filter.query?.let {
append("&search=")
append(filter.query.urlEncoded())
}
filter.tags.oneOrThrowIfMany()?.let {
append("&categorie=")
append(it.key)
}
filter.types.oneOrThrowIfMany()?.let {
append("&type=")
append(
when (it) {
ContentType.MANGA -> "manga"
ContentType.MANHWA -> "manhwa"
ContentType.MANHUA -> "manhua"
else -> "all"
},
)
}
append("&sort=")
when (order) {
SortOrder.POPULARITY -> append("view")
SortOrder.UPDATED -> append("updated")
SortOrder.ALPHABETICAL -> append("default")
SortOrder.NEWEST -> append("published")
else -> append("updated")
}
}
val doc = webClient.httpGet(url).parseHtml()

View File

@@ -4,6 +4,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser
@@ -15,6 +16,7 @@ import org.koitharu.kotatsu.parsers.util.json.mapJSONIndexed
import java.text.SimpleDateFormat
import java.util.*
@Broken
@MangaSourceParser("FLIXSCANS", "FlixScans.net", "ar")
internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.FLIXSCANS, 18) {

View File

@@ -28,66 +28,66 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
isSearchSupported = true,
isSearchWithFiltersSupported = true,
)
override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(),
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED),
availableContentTypes = EnumSet.of(
ContentType.MANGA,
ContentType.MANHWA,
ContentType.MANHUA,
),
)
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
val url = buildString {
append("https://")
append(domain)
when {
!filter.query.isNullOrEmpty() -> {
append("/?search=")
if (order == SortOrder.UPDATED) {
if (filter.tags.isNotEmpty() || filter.demographics.isNotEmpty()) {
throw IllegalArgumentException("Updated sorting does not support other sorting filters")
}
append("/?page=")
append(page.toString())
} else {
append("/series?page=")
append(page.toString())
filter.query?.let {
append("&search=")
append(filter.query.urlEncoded())
if (page > 1) {
append("&page=")
append(page.toString())
}
}
else -> {
if (filter.tags.isNotEmpty()) {
val tag = filter.tags.oneOrThrowIfMany()
append("/series?genre=")
append(tag?.key.orEmpty())
if (page > 1) {
append("&page=")
append(page.toString())
}
append("&")
} else {
when (order) {
SortOrder.POPULARITY -> append("/series")
SortOrder.UPDATED -> append("/")
else -> append("/")
}
if (page > 1) {
append("?page=")
append(page.toString())
append("&")
} else {
append("?")
}
}
filter.tags.oneOrThrowIfMany()?.let {
append("&genre=")
append(it.key)
}
if (order == SortOrder.POPULARITY || filter.tags.isNotEmpty()) {
filter.states.oneOrThrowIfMany()?.let {
append("status=")
append(
when (it) {
MangaState.ONGOING -> "مستمرة"
MangaState.FINISHED -> "مكتمل"
MangaState.ABANDONED -> "متوقف"
else -> "مستمرة"
},
)
}
}
filter.types.forEach {
append("&type=")
append(
when (it) {
ContentType.MANGA -> "مانجا ياباني"
ContentType.MANHWA -> "مانهوا كورية"
ContentType.MANHUA -> "مانها صيني"
else -> ""
},
)
}
filter.states.oneOrThrowIfMany()?.let {
append("status=")
append(
when (it) {
MangaState.ONGOING -> "مستمرة"
MangaState.FINISHED -> "مكتمل"
MangaState.ABANDONED -> "متوقف"
else -> "مستمرة"
},
)
}
}
}

View File

@@ -252,7 +252,9 @@ internal abstract class FmreaderParser(
val d = date?.lowercase() ?: return 0
return when {
WordSet(" ago", " atrás", " h", " d").endsWith(d) -> { parseRelativeDate(d) }
WordSet(" ago", " atrás", " h", " d").endsWith(d) -> {
parseRelativeDate(d)
}
WordSet("today").startsWith(d) -> {
Calendar.getInstance().apply {
@@ -281,18 +283,25 @@ internal abstract class FmreaderParser(
return when {
WordSet("second")
.anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis
WordSet("min", "minute", "minutes", "minuto", "minutos")
.anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
WordSet("hour", "hours", "hora", "horas", "h")
.anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis
WordSet("day", "days", "día", "dia")
.anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
WordSet("week", "weeks", "semana", "semanas")
.anyWordIn(date) -> cal.apply { add(Calendar.WEEK_OF_YEAR, -number) }.timeInMillis
WordSet("month", "months", "mes", "meses")
.anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
WordSet("year", "año", "años")
.anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis
else -> 0
}
}

View File

@@ -267,7 +267,9 @@ internal abstract class KeyoappParser(
val d = date?.lowercase() ?: return 0
return when {
WordSet(" ago").endsWith(d) -> { parseRelativeDate(d) }
WordSet(" ago").endsWith(d) -> {
parseRelativeDate(d)
}
WordSet("today").startsWith(d) -> {
Calendar.getInstance().apply {
@@ -296,16 +298,22 @@ internal abstract class KeyoappParser(
return when {
WordSet("second")
.anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis
WordSet("minute", "minutes")
.anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
WordSet("hour", "hours")
.anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis
WordSet("day", "days")
.anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
WordSet("month", "months")
.anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
WordSet("year")
.anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis
else -> 0
}
}

View File

@@ -40,6 +40,8 @@ internal abstract class MadaraParser(
isMultipleTagsSupported = true,
isTagsExclusionSupported = !withoutAjax,
isSearchSupported = true,
isSearchWithFiltersSupported = true,
isYearSupported = true,
)
override suspend fun getFilterOptions() = MangaListFilterOptions(
@@ -51,8 +53,7 @@ internal abstract class MadaraParser(
override val availableSortOrders: Set<SortOrder> = setupAvailableSortOrders()
private fun setupAvailableSortOrders(): Set<SortOrder> {
return if(!withoutAjax)
{
return if (!withoutAjax) {
EnumSet.of(
SortOrder.UPDATED,
SortOrder.UPDATED_ASC,
@@ -64,10 +65,17 @@ internal abstract class MadaraParser(
SortOrder.ALPHABETICAL_DESC,
SortOrder.RATING,
SortOrder.RATING_ASC,
SortOrder.RELEVANCE,
)
} else {
EnumSet.of(
SortOrder.UPDATED,
SortOrder.POPULARITY,
SortOrder.NEWEST,
SortOrder.ALPHABETICAL,
SortOrder.RATING,
SortOrder.RELEVANCE,
)
}else
{
EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST, SortOrder.ALPHABETICAL, SortOrder.RATING)
}
}
@@ -219,91 +227,74 @@ internal abstract class MadaraParser(
append("https://")
append(domain)
when {
if (pages > 1) {
append("/page/")
append(pages.toString())
}
append("/?s=")
!filter.query.isNullOrEmpty() -> {
if (pages > 1) {
append("/page/")
append(pages.toString())
}
append("/?s=")
append(filter.query.urlEncoded())
append("&post_type=wp-manga")
append(filter.query?.urlEncoded())
append("&post_type=wp-manga")
// Known bug: in some cases, if there are no manga with the associated tags, the source returns the full list of manga
if (filter.tags.isNotEmpty()) {
filter.tags.forEach {
append("&genre[]=")
append(it.key)
}
}
else -> {
if (pages > 1) {
append("/page/")
append(pages.toString())
}
append("/?s=")
//Support query
//append(filter.query.urlEncoded())
append("&post_type=wp-manga")
// Known bug: in some cases, if there are no manga with the associated tags, the source returns the full list of manga
if (filter.tags.isNotEmpty()) {
filter.tags.forEach {
append("&genre[]=")
append(it.key)
}
}
filter.states.forEach {
append("&status[]=")
when (it) {
MangaState.ONGOING -> append("on-going")
MangaState.FINISHED -> append("end")
MangaState.ABANDONED -> append("canceled")
MangaState.PAUSED -> append("on-hold")
MangaState.UPCOMING -> append("upcoming")
}
}
filter.contentRating.oneOrThrowIfMany()?.let {
append("&adult=")
append(
when (it) {
ContentRating.SAFE -> "0"
ContentRating.ADULT -> "1"
else -> ""
},
)
}
// Support year
//filter.year?.let {
// append("&release=")
// append(filter.year)
//}
// Support author
//filter.author?.let {
// append("&author=")
// append(filter.author)
//}
// Support artist
//filter.artist?.let {
// append("&artist=")
// append(filter.artist)
//}
append("&m_orderby=")
when (order) {
SortOrder.POPULARITY -> append("views")
SortOrder.UPDATED -> append("latest")
SortOrder.NEWEST -> append("new-manga")
SortOrder.ALPHABETICAL -> append("alphabet")
SortOrder.RATING -> append("rating")
// SortOrder.RELEVANCE -> {}
else -> append("latest")
}
filter.states.forEach {
append("&status[]=")
when (it) {
MangaState.ONGOING -> append("on-going")
MangaState.FINISHED -> append("end")
MangaState.ABANDONED -> append("canceled")
MangaState.PAUSED -> append("on-hold")
MangaState.UPCOMING -> append("upcoming")
}
}
filter.contentRating.oneOrThrowIfMany()?.let {
append("&adult=")
append(
when (it) {
ContentRating.SAFE -> "0"
ContentRating.ADULT -> "1"
else -> ""
},
)
}
if (filter.year != 0) {
append("&release=")
append(filter.year.toString())
}
// Support author
//filter.author?.let {
// append("&author=")
// append(filter.author)
//}
// Support artist
//filter.artist?.let {
// append("&artist=")
// append(filter.artist)
//}
append("&m_orderby=")
when (order) {
SortOrder.POPULARITY -> append("views")
SortOrder.UPDATED -> append("latest")
SortOrder.NEWEST -> append("new-manga")
SortOrder.ALPHABETICAL -> append("alphabet")
SortOrder.RATING -> append("rating")
SortOrder.RELEVANCE -> {}
else -> {}
}
}
return parseMangaList(webClient.httpGet(url).parseHtml())
} else {
@@ -312,158 +303,144 @@ internal abstract class MadaraParser(
payload["page"] = page.toString()
when {
filter.query?.let {
payload["vars[s]"] = filter.query.urlEncoded()
}
!filter.query.isNullOrEmpty() -> {
payload["vars[s]"] = filter.query.urlEncoded()
if (filter.tags.isNotEmpty()) {
payload["vars[tax_query][0][taxonomy]"] = "wp-manga-genre"
payload["vars[tax_query][0][field]"] = "slug"
filter.tags.forEachIndexed { i, it ->
payload["vars[tax_query][0][terms][$i]"] = it.key
}
payload["vars[tax_query][0][operator]"] = "IN"
}
if (filter.tagsExclude.isNotEmpty()) {
payload["vars[tax_query][1][taxonomy]"] = "wp-manga-genre"
payload["vars[tax_query][1][field]"] = "slug"
filter.tagsExclude.forEachIndexed { i, it ->
payload["vars[tax_query][1][terms][$i]"] = it.key
}
payload["vars[tax_query][1][operator]"] = "NOT IN"
}
if (filter.year != 0) {
payload["vars[tax_query][2][taxonomy]"] = "wp-manga-release"
payload["vars[tax_query][2][field]"] = "slug"
payload["vars[tax_query][2][terms][]"] = filter.year.toString()
}
// Support author
// filter.author.let {
// payload["vars[tax_query][3][taxonomy]"] = "wp-manga-author"
// payload["vars[tax_query][3][field]"] = "name"
// payload["vars[tax_query][3][terms][0]"] = filter.author
// payload["vars[tax_query][3][operator]"] = "IN"
//}
// Support artist
// filter.artist.let {
// payload["vars[tax_query][4][taxonomy]"] = "wp-manga-artist"
// payload["vars[tax_query][4][field]"] = "name"
// payload["vars[tax_query][4][terms][0]"] = filter.artist
// payload["vars[tax_query][4][operator]"] = "IN"
//}
if (filter.tags.isNotEmpty() || filter.tagsExclude.isNotEmpty() || filter.year != 0) {
payload["vars[tax_query][relation]"] = "AND"
}
when (order) {
SortOrder.POPULARITY -> {
payload["vars[meta_key]"] = "_wp_manga_views"
payload["vars[orderby]"] = "meta_value_num"
payload["vars[order]"] = "desc"
}
else -> {
// Support query
// filter.query.let {
// payload["vars[s]"] = filter.query.urlEncoded()
// }
if (filter.tags.isNotEmpty()) {
payload["vars[tax_query][0][taxonomy]"] = "wp-manga-genre"
payload["vars[tax_query][0][field]"] = "slug"
filter.tags.forEachIndexed { i, it ->
payload["vars[tax_query][0][terms][$i]"] = it.key
}
payload["vars[tax_query][0][operator]"] = "IN"
}
if (filter.tagsExclude.isNotEmpty()) {
payload["vars[tax_query][1][taxonomy]"] = "wp-manga-genre"
payload["vars[tax_query][1][field]"] = "slug"
filter.tagsExclude.forEachIndexed { i, it ->
payload["vars[tax_query][1][terms][$i]"] = it.key
}
payload["vars[tax_query][1][operator]"] = "NOT IN"
}
// Support year
//filter.year?.let {
// payload["vars[tax_query][2][taxonomy]"] = wp-manga-release
// payload["vars[tax_query][2][field]"] = slug
// payload["vars[tax_query][2][terms][]"] = filter.year
//}
// Support author
// filter.author.let {
// payload["vars[tax_query][3][taxonomy]"] = "wp-manga-author"
// payload["vars[tax_query][3][field]"] = "name"
// payload["vars[tax_query][3][terms][0]"] = filter.author
// payload["vars[tax_query][3][operator]"] = "IN"
//}
// Support artist
// filter.artist.let {
// payload["vars[tax_query][4][taxonomy]"] = "wp-manga-artist"
// payload["vars[tax_query][4][field]"] = "name"
// payload["vars[tax_query][4][terms][0]"] = filter.artist
// payload["vars[tax_query][4][operator]"] = "IN"
//}
/// for add filter.year need to add || filter.year
if (filter.tags.isNotEmpty() || filter.tagsExclude.isNotEmpty()) {
payload["vars[tax_query][relation]"] = "AND"
}
when (order) {
SortOrder.POPULARITY -> {
payload["vars[meta_key]"] = "_wp_manga_views"
payload["vars[orderby]"] = "meta_value_num"
payload["vars[order]"] = "desc"
}
SortOrder.POPULARITY_ASC -> {
payload["vars[meta_key]"] = "_wp_manga_views"
payload["vars[orderby]"] = "meta_value_num"
payload["vars[order]"] = "asc"
}
SortOrder.UPDATED -> {
payload["vars[meta_key]"] = "_latest_update"
payload["vars[orderby]"] = "meta_value_num"
payload["vars[order]"] = "desc"
}
SortOrder.UPDATED_ASC -> {
payload["vars[meta_key]"] = "_latest_update"
payload["vars[orderby]"] = "meta_value_num"
payload["vars[order]"] = "asc"
}
SortOrder.NEWEST -> {
payload["vars[orderby]"] = "date"
payload["vars[order]"] = "desc"
}
SortOrder.NEWEST_ASC -> {
payload["vars[orderby]"] = "date"
payload["vars[order]"] = "asc"
}
SortOrder.ALPHABETICAL -> {
payload["vars[orderby]"] = "post_title"
payload["vars[order]"] = "asc"
}
SortOrder.ALPHABETICAL_DESC -> {
payload["vars[orderby]"] = "post_title"
payload["vars[order]"] = "desc"
}
SortOrder.RATING -> {
payload["vars[meta_query][0][query_avarage_reviews][key]"] = "_manga_avarage_reviews"
payload["vars[meta_query][0][query_total_reviews][key]"] = "_manga_total_votes"
payload["vars[orderby][query_avarage_reviews]"] = "DESC"
payload["vars[orderby][query_total_reviews]"] = "DESC"
}
SortOrder.RATING_ASC -> {
payload["vars[meta_query][0][query_avarage_reviews][key]"] = "_manga_avarage_reviews"
payload["vars[meta_query][0][query_total_reviews][key]"] = "_manga_total_votes"
payload["vars[orderby][query_avarage_reviews]"] = "ASC"
payload["vars[orderby][query_total_reviews]"] = "ASC"
}
// SortOrder.RELEVANCE -> {
// payload["vars[orderby]"] = ""
// }
else -> payload["vars[meta_key]"] = "_latest_update"
}
filter.states.forEach {
payload["vars[meta_query][0][0][key]"] = "_wp_manga_status"
payload["vars[meta_query][0][0][compare]"] = "IN"
payload["vars[meta_query][0][0][value][]"] =
when (it) {
MangaState.ONGOING -> "on-going"
MangaState.FINISHED -> "end"
MangaState.ABANDONED -> "canceled"
MangaState.PAUSED -> "on-hold"
MangaState.UPCOMING -> "upcoming"
}
}
filter.contentRating.oneOrThrowIfMany()?.let {
payload["vars[meta_query][0][1][key]"] = "manga_adult_content"
payload["vars[meta_query][0][1][value]"] =
when (it) {
ContentRating.SAFE -> ""
ContentRating.ADULT -> "a%3A1%3A%7Bi%3A0%3Bs%3A3%3A%22yes%22%3B%7D"
else -> ""
}
}
SortOrder.POPULARITY_ASC -> {
payload["vars[meta_key]"] = "_wp_manga_views"
payload["vars[orderby]"] = "meta_value_num"
payload["vars[order]"] = "asc"
}
SortOrder.UPDATED -> {
payload["vars[meta_key]"] = "_latest_update"
payload["vars[orderby]"] = "meta_value_num"
payload["vars[order]"] = "desc"
}
SortOrder.UPDATED_ASC -> {
payload["vars[meta_key]"] = "_latest_update"
payload["vars[orderby]"] = "meta_value_num"
payload["vars[order]"] = "asc"
}
SortOrder.NEWEST -> {
payload["vars[orderby]"] = "date"
payload["vars[order]"] = "desc"
}
SortOrder.NEWEST_ASC -> {
payload["vars[orderby]"] = "date"
payload["vars[order]"] = "asc"
}
SortOrder.ALPHABETICAL -> {
payload["vars[orderby]"] = "post_title"
payload["vars[order]"] = "asc"
}
SortOrder.ALPHABETICAL_DESC -> {
payload["vars[orderby]"] = "post_title"
payload["vars[order]"] = "desc"
}
SortOrder.RATING -> {
payload["vars[meta_query][0][query_avarage_reviews][key]"] = "_manga_avarage_reviews"
payload["vars[meta_query][0][query_total_reviews][key]"] = "_manga_total_votes"
payload["vars[orderby][query_avarage_reviews]"] = "DESC"
payload["vars[orderby][query_total_reviews]"] = "DESC"
}
SortOrder.RATING_ASC -> {
payload["vars[meta_query][0][query_avarage_reviews][key]"] = "_manga_avarage_reviews"
payload["vars[meta_query][0][query_total_reviews][key]"] = "_manga_total_votes"
payload["vars[orderby][query_avarage_reviews]"] = "ASC"
payload["vars[orderby][query_total_reviews]"] = "ASC"
}
SortOrder.RELEVANCE -> {
payload["vars[orderby]"] = ""
}
else -> payload["vars[orderby]"] = ""
}
filter.states.forEach {
payload["vars[meta_query][0][0][key]"] = "_wp_manga_status"
payload["vars[meta_query][0][0][compare]"] = "IN"
payload["vars[meta_query][0][0][value][]"] =
when (it) {
MangaState.ONGOING -> "on-going"
MangaState.FINISHED -> "end"
MangaState.ABANDONED -> "canceled"
MangaState.PAUSED -> "on-hold"
MangaState.UPCOMING -> "upcoming"
}
}
filter.contentRating.oneOrThrowIfMany()?.let {
payload["vars[meta_query][0][1][key]"] = "manga_adult_content"
payload["vars[meta_query][0][1][value]"] =
when (it) {
ContentRating.SAFE -> ""
ContentRating.ADULT -> "a%3A1%3A%7Bi%3A0%3Bs%3A3%3A%22yes%22%3B%7D"
else -> ""
}
}
return parseMangaList(
@@ -749,12 +726,14 @@ internal abstract class MadaraParser(
val d = date?.lowercase() ?: return 0
return when {
WordSet(" ago", "atrás", " hace", " publicado"," назад", " önce", " trước", "مضت",
" h", " d", " días", " jour", " horas", " heure", " mins", " minutos", " minute", " mois").endsWith(d) -> {
WordSet(
" ago", "atrás", " hace", " publicado", " назад", " önce", " trước", "مضت",
" h", " d", " días", " jour", " horas", " heure", " mins", " minutos", " minute", " mois",
).endsWith(d) -> {
parseRelativeDate(d)
}
WordSet("há ", "منذ", "il y a" ).startsWith(d) -> {
WordSet("há ", "منذ", "il y a").startsWith(d) -> {
parseRelativeDate(d)
}
@@ -805,16 +784,22 @@ internal abstract class MadaraParser(
return when {
WordSet("detik", "segundo", "second", "ثوان")
.anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis
WordSet("menit", "dakika", "min", "minute", "minutes", "minuto", "mins", "phút", "минут", "دقيقة")
.anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
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", "день")
.anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
WordSet("month", "months", "أشهر", "mois")
.anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
WordSet("year")
.anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis
else -> 0
}
}

View File

@@ -274,7 +274,9 @@ internal abstract class MadthemeParser(
val d = date?.lowercase() ?: return 0
return when {
WordSet(" ago", " h", " d").endsWith(d) -> { parseRelativeDate(d) }
WordSet(" ago", " h", " d").endsWith(d) -> {
parseRelativeDate(d)
}
WordSet("today").startsWith(d) -> {
Calendar.getInstance().apply {
@@ -303,16 +305,22 @@ internal abstract class MadthemeParser(
return when {
WordSet("second")
.anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis
WordSet("min", "minute", "minutes")
.anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
WordSet("hour", "hours", "h")
.anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis
WordSet("day", "days")
.anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
WordSet("month", "months")
.anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
WordSet("year")
.anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis
else -> 0
}
}

View File

@@ -18,7 +18,7 @@ internal class Hanman18(context: MangaLoaderContext) :
Manga18Parser(context, MangaParserSource.HANMAN18, "hanman18.com") {
override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = emptySet()
availableTags = emptySet(),
)
override suspend fun getChapters(doc: Document): List<MangaChapter> {

View File

@@ -265,7 +265,9 @@ internal abstract class MangaboxParser(
protected fun parseChapterDate(dateFormat: DateFormat, date: String?): Long {
val d = date?.lowercase() ?: return 0
return when {
WordSet(" ago", " h", " d").endsWith(d) -> { parseRelativeDate(d) }
WordSet(" ago", " h", " d").endsWith(d) -> {
parseRelativeDate(d)
}
WordSet("today").startsWith(d) -> {
Calendar.getInstance().apply {
@@ -294,16 +296,22 @@ internal abstract class MangaboxParser(
return when {
WordSet("second")
.anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis
WordSet("min", "minute", "minutes")
.anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
WordSet("hour", "hours", "h")
.anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis
WordSet("day", "days")
.anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
WordSet("month", "months")
.anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
WordSet("year")
.anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis
else -> 0
}
}

View File

@@ -245,18 +245,18 @@ internal abstract class MangaReaderParser(
"En cours de publication", "Đang tiến hành", "Em lançamento", "em lançamento", "Em Lançamento", "Онгоінг", "Publishing",
"Devam Ediyor", "Em Andamento", "In Corso", "Güncel", "Berjalan", "Продолжается", "Updating", "Lançando", "In Arrivo", "Emision",
"En emision", "مستمر", "Curso", "En marcha", "Publicandose", "Publicando", "连载中", "Devam ediyor", "Devam Etmekte",
-> MangaState.ONGOING
-> MangaState.ONGOING
"Completed", "Completo", "Complété", "Fini", "Achevé", "Terminé", "Terminé ⚫", "Tamamlandı", "Đã hoàn thành", "Hoàn Thành",
"مكتملة", "Завершено", "Finished", "Finalizado", "Completata", "One-Shot", "Bitti", "Tamat", "Completado", "Concluído",
"Concluido", "已完结", "Bitmiş",
-> MangaState.FINISHED
-> MangaState.FINISHED
"Canceled", "Cancelled", "Cancelado", "cancellato", "Cancelados", "Dropped", "Discontinued", "abandonné", "Abandonné",
-> MangaState.ABANDONED
-> MangaState.ABANDONED
"Hiatus", "On Hold", "Pausado", "En espera", "En pause", "En Pause", "En attente",
-> MangaState.PAUSED
-> MangaState.PAUSED
else -> null
}

View File

@@ -9,12 +9,12 @@ import java.util.*
@MangaSourceParser("ADUMANGA", "AduManga", "tr")
internal class AduManga(context: MangaLoaderContext) :
MangaReaderParser(context, MangaParserSource.ADUMANGA, "adumanga.com", pageSize = 20, searchPageSize = 10) {
MangaReaderParser(context, MangaParserSource.ADUMANGA, "adumanga.com", pageSize = 20, searchPageSize = 10) {
override val sourceLocale: Locale = Locale.ENGLISH
override val sourceLocale: Locale = Locale.ENGLISH
override val filterCapabilities: MangaListFilterCapabilities
get() = super.filterCapabilities.copy(
isTagsExclusionSupported = false,
)
override val filterCapabilities: MangaListFilterCapabilities
get() = super.filterCapabilities.copy(
isTagsExclusionSupported = false,
)
}

View File

@@ -104,13 +104,13 @@ internal abstract class MangaWorldParser(
tags = tags,
author = div.selectFirst(".author a")?.text(),
state =
when (div.selectFirst(".status a")?.text()) {
"In corso" -> MangaState.ONGOING
"Finito" -> MangaState.FINISHED
"Droppato" -> MangaState.ABANDONED
"In pausa" -> MangaState.PAUSED
else -> null
},
when (div.selectFirst(".status a")?.text()) {
"In corso" -> MangaState.ONGOING
"Finito" -> MangaState.FINISHED
"Droppato" -> MangaState.ABANDONED
"In pausa" -> MangaState.PAUSED
else -> null
},
source = source,
isNsfw = isNsfwSource,
)
@@ -143,30 +143,30 @@ internal abstract class MangaWorldParser(
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
return manga.copy(
altTitle =
doc.selectFirst(".meta-data .font-weight-bold:contains(Titoli alternativi:)")
?.parent()
?.ownText()
?.substringAfter(": ")
?.trim(),
doc.selectFirst(".meta-data .font-weight-bold:contains(Titoli alternativi:)")
?.parent()
?.ownText()
?.substringAfter(": ")
?.trim(),
description = doc.getElementById("noidungm")?.text().orEmpty(),
chapters =
doc.select(".chapters-wrapper .chapter a").mapChapters(reversed = true) { i, a ->
val url = a.attrAsRelativeUrl("href").toAbsoluteUrl(domain)
MangaChapter(
id = generateUid(url),
name = a.selectFirstOrThrow("span.d-inline-block").text(),
number = i + 1f,
volume = 0,
url = "$url?style=list",
scanlator = null,
uploadDate =
SimpleDateFormat("dd MMMM yyyy", Locale.ITALIAN).tryParse(
a.selectFirst(".chap-date")?.text(),
),
branch = null,
source = source,
)
},
doc.select(".chapters-wrapper .chapter a").mapChapters(reversed = true) { i, a ->
val url = a.attrAsRelativeUrl("href").toAbsoluteUrl(domain)
MangaChapter(
id = generateUid(url),
name = a.selectFirstOrThrow("span.d-inline-block").text(),
number = i + 1f,
volume = 0,
url = "$url?style=list",
scanlator = null,
uploadDate =
SimpleDateFormat("dd MMMM yyyy", Locale.ITALIAN).tryParse(
a.selectFirst(".chap-date")?.text(),
),
branch = null,
source = source,
)
},
)
}

View File

@@ -186,17 +186,17 @@ internal abstract class NepnepParser(
altTitle = null,
state = when (doc.selectFirstOrThrow(".list-group-item:contains(Status:) a").text()) {
"Ongoing (Scan)", "Ongoing (Publish)",
-> MangaState.ONGOING
-> MangaState.ONGOING
"Complete (Scan)", "Complete (Publish)",
-> MangaState.FINISHED
-> MangaState.FINISHED
"Cancelled (Scan)", "Cancelled (Publish)",
"Discontinued (Scan)", "Discontinued (Publish)",
-> MangaState.ABANDONED
-> MangaState.ABANDONED
"Hiatus (Scan)", "Hiatus (Publish)",
-> MangaState.PAUSED
-> MangaState.PAUSED
else -> null
},

View File

@@ -243,8 +243,14 @@ internal abstract class OtakuSanctuaryParser(
protected fun parseChapterDate(dateFormat: DateFormat, date: String?): Long {
val d = date?.lowercase() ?: return 0
return when {
WordSet(" ago", " atrás").endsWith(d) -> { parseRelativeDate(d) }
WordSet("cách đây ").startsWith(d) -> { parseRelativeDate(d) }
WordSet(" ago", " atrás").endsWith(d) -> {
parseRelativeDate(d)
}
WordSet("cách đây ").startsWith(d) -> {
parseRelativeDate(d)
}
else -> dateFormat.tryParse(date)
}
}
@@ -255,16 +261,22 @@ internal abstract class OtakuSanctuaryParser(
return when {
WordSet("second", "giây")
.anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis
WordSet("min", "minute", "minutes", "phút")
.anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
WordSet("tiếng", "hour", "hours")
.anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis
WordSet("day", "days", "d", "ngày")
.anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
WordSet("month", "months")
.anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
WordSet("year")
.anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis
else -> 0
}
}

View File

@@ -13,8 +13,9 @@ internal class MangaFr(context: MangaLoaderContext) :
override val listUrl = "/series"
override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = emptySet()
availableTags = emptySet(),
)
override suspend fun getDetails(manga: Manga): Manga {
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val dateFormat = SimpleDateFormat("MM-dd-yyyy", sourceLocale)

View File

@@ -280,7 +280,9 @@ internal abstract class WpComicsParser(
val d = date?.lowercase() ?: return 0
return when {
WordSet(" ago", " trước").endsWith(d) -> { parseRelativeDate(d) }
WordSet(" ago", " trước").endsWith(d) -> {
parseRelativeDate(d)
}
WordSet("today").startsWith(d) -> {
Calendar.getInstance().apply {
@@ -309,14 +311,19 @@ internal abstract class WpComicsParser(
return when {
WordSet("second", "giây")
.anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis
WordSet("min", "minute", "minutes", "mins", "phút")
.anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
WordSet("jam", "saat", "heure", "hora", "horas", "hour", "hours", "h", "giờ")
.anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis
WordSet("day", "days", "d", "ngày")
.anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
WordSet("month", "months", "tháng")
.anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
WordSet("year", "năm").anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis
else -> 0
}

View File

@@ -252,7 +252,9 @@ internal abstract class ZMangaParser(
protected fun parseChapterDate(dateFormat: DateFormat, date: String?): Long {
val d = date?.lowercase() ?: return 0
return when {
WordSet(" ago", " h", " d").endsWith(d) -> { parseRelativeDate(d) }
WordSet(" ago", " h", " d").endsWith(d) -> {
parseRelativeDate(d)
}
WordSet("today").startsWith(d) -> {
Calendar.getInstance().apply {
@@ -282,15 +284,20 @@ internal abstract class ZMangaParser(
return when {
WordSet("second")
.anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis
WordSet("min", "minute", "minutes")
.anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
WordSet("hour", "hours", "h")
.anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis
WordSet("day", "days").anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
WordSet("month", "months")
.anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
WordSet("year")
.anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis
else -> 0
}
}