[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
master
devi 2 years ago
parent f2354957e6
commit d1f9b0d829

@ -49,10 +49,10 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
}
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.NEWEST,
SortOrder.ALPHABETICAL,
SortOrder.UPDATED,
SortOrder.NEWEST,
SortOrder.POPULARITY,
SortOrder.ALPHABETICAL,
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 }

@ -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.query?.let {
url.addQueryParameter("q", filter.query)
}
filter.tags.forEach {
url.addQueryParameter("genres", it.key)
}
filter.tags.forEach {
url.addQueryParameter("genres", it.key)
}
filter.tagsExclude.forEach {
url.addQueryParameter("excludes", 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"
},
)
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 -> ""
},
)
}
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.yearFrom != YEAR_UNKNOWN) {
url.addQueryParameter("from", filter.yearFrom.toString())
}
if (filter.yearTo != YEAR_UNKNOWN) {
url.addQueryParameter("to", filter.yearTo.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.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.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,6 +189,45 @@ internal class ComickFunParser(context: MangaLoaderContext) :
)
}
private suspend fun getChapters(hid: String): List<MangaChapter> {
val ja = webClient.httpGet(
url = "https://api.${domain}/comic/$hid/chapters?limit=$CHAPTERS_LIMIT",
).parseJson().getJSONArray("chapters")
val dateFormat = SimpleDateFormat("yyyy-MM-dd")
return ja.toJSONList().reversed().mapChapters { _, jo ->
val vol = jo.getIntOrDefault("vol", 0)
val chap = jo.getFloatOrDefault("chap", 0f)
val locale = Locale.forLanguageTag(jo.getString("lang"))
val group = jo.optJSONArray("group_name")?.joinToString(", ")
val branch = buildString {
append(locale.getDisplayName(locale).toTitleCase(locale))
if (!group.isNullOrEmpty()) {
append(" (")
append(group)
append(')')
}
}
MangaChapter(
id = generateUid(jo.getLong("id")),
name = buildString {
if (vol > 0) {
append("Vol ").append(vol).append(' ')
}
append("Chap ").append(chap)
jo.getStringOrNull("title")?.let { append(": ").append(it) }
},
number = chap,
volume = vol,
url = jo.getString("hid"),
scanlator = jo.optJSONArray("group_name")?.asIterable<String>()?.joinToString()
?.takeUnless { it.isBlank() },
uploadDate = dateFormat.tryParse(jo.getString("created_at").substringBefore('T')),
branch = branch,
source = source,
)
}
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val jo = webClient.httpGet(
"https://api.${domain}/chapter/${chapter.url}?tachiyomi=true",
@ -208,6 +243,8 @@ internal class ComickFunParser(context: MangaLoaderContext) :
}
}
private val tagsArray = SuspendLazy(::loadTags)
private suspend fun fetchAvailableTags(): Set<MangaTag> {
val sparseArray = tagsArray.get()
val set = ArraySet<MangaTag>(sparseArray.size())
@ -233,45 +270,6 @@ internal class ComickFunParser(context: MangaLoaderContext) :
return tags
}
private suspend fun getChapters(hid: String): List<MangaChapter> {
val ja = webClient.httpGet(
url = "https://api.${domain}/comic/$hid/chapters?limit=$CHAPTERS_LIMIT",
).parseJson().getJSONArray("chapters")
val dateFormat = SimpleDateFormat("yyyy-MM-dd")
return ja.toJSONList().reversed().mapChapters { _, jo ->
val vol = jo.getIntOrDefault("vol", 0)
val chap = jo.getFloatOrDefault("chap", 0f)
val locale = Locale.forLanguageTag(jo.getString("lang"))
val group = jo.optJSONArray("group_name")?.joinToString(", ")
val branch = buildString {
append(locale.getDisplayName(locale).toTitleCase(locale))
if (!group.isNullOrEmpty()) {
append(" (")
append(group)
append(')')
}
}
MangaChapter(
id = generateUid(jo.getLong("id")),
name = buildString {
if (vol > 0) {
append("Vol ").append(vol).append(' ')
}
append("Chap ").append(chap)
jo.getStringOrNull("title")?.let { append(": ").append(it) }
},
number = chap,
volume = vol,
url = jo.getString("hid"),
scanlator = jo.optJSONArray("group_name")?.asIterable<String>()?.joinToString()
?.takeUnless { it.isBlank() },
uploadDate = dateFormat.tryParse(jo.getString("created_at").substringBefore('T')),
branch = branch,
source = source,
)
}
}
private fun JSONObject.selectGenres(tags: SparseArrayCompat<MangaTag>): Set<MangaTag> {
val array = optJSONArray("genres") ?: return emptySet()
val res = ArraySet<MangaTag>(array.length())

@ -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() }

@ -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,66 +73,66 @@ 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("")
},
)
}
}
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"
},
)
}
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 -> ""
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.locale?.let {
append("&lang=")
append(it.language)
}
}
filter.originalLocale?.let {
append("&orig=")
append(it.language)
}
}
@ -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)
}

@ -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 -> {
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 })
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_")
append("&category_id=")
append(filter.tags.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.toString())
append(".html")
}
} else {
append("/category/index_")
append(page.toString())
}
}
val doc = webClient.httpGet(url).parseHtml()

@ -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()

@ -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) {

@ -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 -> "مستمرة"
},
)
}
}
}

@ -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
}
}

@ -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
}
}

@ -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=")
append(filter.query?.urlEncoded())
!filter.query.isNullOrEmpty() -> {
if (pages > 1) {
append("/page/")
append(pages.toString())
}
append("/?s=")
append(filter.query.urlEncoded())
append("&post_type=wp-manga")
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"
}
else -> {
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 query
// filter.query.let {
// payload["vars[s]"] = filter.query.urlEncoded()
// }
// 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"
}
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"
}
when (order) {
SortOrder.POPULARITY -> {
payload["vars[meta_key]"] = "_wp_manga_views"
payload["vars[orderby]"] = "meta_value_num"
payload["vars[order]"] = "desc"
}
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"
}
SortOrder.POPULARITY_ASC -> {
payload["vars[meta_key]"] = "_wp_manga_views"
payload["vars[orderby]"] = "meta_value_num"
payload["vars[order]"] = "asc"
}
// 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"
}
SortOrder.UPDATED -> {
payload["vars[meta_key]"] = "_latest_update"
payload["vars[orderby]"] = "meta_value_num"
payload["vars[order]"] = "desc"
}
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"
}
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"
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"
}
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 -> ""
}
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("", "منذ", "il y a" ).startsWith(d) -> {
WordSet("", "منذ", "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
}
}

@ -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
}
}

@ -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> {

@ -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
}
}

@ -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
}

@ -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,
)
}

@ -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,
)
},
)
}

@ -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
},

@ -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
}
}

@ -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)

@ -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
}

@ -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
}
}

Loading…
Cancel
Save