[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
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( override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.NEWEST, SortOrder.ALPHABETICAL,
SortOrder.UPDATED, SortOrder.UPDATED,
SortOrder.NEWEST,
SortOrder.POPULARITY, SortOrder.POPULARITY,
SortOrder.ALPHABETICAL,
SortOrder.POPULARITY_YEAR, SortOrder.POPULARITY_YEAR,
SortOrder.POPULARITY_MONTH, SortOrder.POPULARITY_MONTH,
SortOrder.POPULARITY_WEEK, SortOrder.POPULARITY_WEEK,
@ -65,6 +65,7 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
isMultipleTagsSupported = true, isMultipleTagsSupported = true,
isTagsExclusionSupported = true, isTagsExclusionSupported = true,
isSearchSupported = true, isSearchSupported = true,
isOriginalLocaleSupported = true,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( 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=") append("&genres=")
if (filter.tags.isNotEmpty()) { if (filter.tags.isNotEmpty()) {
appendAll(filter.tags, ",") { it.key } appendAll(filter.tags, ",") { it.key }

@ -34,13 +34,12 @@ internal class ComickFunParser(context: MangaLoaderContext) :
SortOrder.NEWEST, SortOrder.NEWEST,
) )
private val tagsArray = SuspendLazy(::loadTags)
override val filterCapabilities: MangaListFilterCapabilities override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities( get() = MangaListFilterCapabilities(
isMultipleTagsSupported = true, isMultipleTagsSupported = true,
isTagsExclusionSupported = true, isTagsExclusionSupported = true,
isSearchSupported = true, isSearchSupported = true,
isSearchWithFiltersSupported = true,
isYearRangeSupported = true, isYearRangeSupported = true,
) )
@ -66,80 +65,77 @@ internal class ComickFunParser(context: MangaLoaderContext) :
.addQueryParameter("tachiyomi", "true") .addQueryParameter("tachiyomi", "true")
.addQueryParameter("limit", pageSize.toString()) .addQueryParameter("limit", pageSize.toString())
.addQueryParameter("page", page.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 { filter.tags.forEach {
url.addQueryParameter("genres", it.key) url.addQueryParameter("genres", it.key)
} }
filter.tagsExclude.forEach { filter.tagsExclude.forEach {
url.addQueryParameter("excludes", it.key) url.addQueryParameter("excludes", it.key)
} }
url.addQueryParameter( url.addQueryParameter(
"sort", "sort",
when (order) { when (order) {
SortOrder.POPULARITY -> "view" SortOrder.NEWEST -> "created_at"
SortOrder.UPDATED -> "uploaded" SortOrder.POPULARITY -> "view"
SortOrder.NEWEST -> "created_at" SortOrder.RATING -> "rating"
SortOrder.RATING -> "rating" SortOrder.UPDATED -> "uploaded"
else -> "uploaded" else -> "uploaded"
}, },
) )
filter.states.oneOrThrowIfMany()?.let { filter.states.oneOrThrowIfMany()?.let {
url.addQueryParameter( url.addQueryParameter(
"status", "status",
when (it) { when (it) {
MangaState.ONGOING -> "1" MangaState.ONGOING -> "1"
MangaState.FINISHED -> "2" MangaState.FINISHED -> "2"
MangaState.ABANDONED -> "3" MangaState.ABANDONED -> "3"
MangaState.PAUSED -> "4" MangaState.PAUSED -> "4"
else -> "" else -> ""
}, },
) )
} }
if (filter.yearFrom != YEAR_UNKNOWN) { if (filter.yearFrom != YEAR_UNKNOWN) {
url.addQueryParameter("from", filter.yearFrom.toString()) url.addQueryParameter("from", filter.yearFrom.toString())
} }
if (filter.yearTo != YEAR_UNKNOWN) { if (filter.yearTo != YEAR_UNKNOWN) {
url.addQueryParameter("to", filter.yearTo.toString()) url.addQueryParameter("to", filter.yearTo.toString())
} }
filter.types.forEach { filter.types.forEach {
url.addQueryParameter( url.addQueryParameter(
"country", "country",
when (it) { when (it) {
ContentType.MANGA -> "jp" ContentType.MANGA -> "jp"
ContentType.MANHWA -> "kr" ContentType.MANHWA -> "kr"
ContentType.MANHUA -> "cn" ContentType.MANHUA -> "cn"
ContentType.OTHER -> "others" ContentType.OTHER -> "others"
else -> "" else -> ""
}, },
) )
} }
filter.demographics.forEach { filter.demographics.forEach {
url.addQueryParameter( url.addQueryParameter(
"demographic", "demographic",
when (it) { when (it) {
Demographic.SHOUNEN -> "1" Demographic.SHOUNEN -> "1"
Demographic.SHOUJO -> "2" Demographic.SHOUJO -> "2"
Demographic.SEINEN -> "3" Demographic.SEINEN -> "3"
Demographic.JOSEI -> "4" Demographic.JOSEI -> "4"
Demographic.NONE -> "5" Demographic.NONE -> "5"
}, },
) )
}
}
} }
val ja = webClient.httpGet(url.build()).parseJsonArray() val ja = webClient.httpGet(url.build()).parseJsonArray()
val tagsMap = tagsArray.get() val tagsMap = tagsArray.get()
return ja.mapJSON { jo -> 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> { override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val jo = webClient.httpGet( val jo = webClient.httpGet(
"https://api.${domain}/chapter/${chapter.url}?tachiyomi=true", "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> { private suspend fun fetchAvailableTags(): Set<MangaTag> {
val sparseArray = tagsArray.get() val sparseArray = tagsArray.get()
val set = ArraySet<MangaTag>(sparseArray.size()) val set = ArraySet<MangaTag>(sparseArray.size())
@ -233,45 +270,6 @@ internal class ComickFunParser(context: MangaLoaderContext) :
return tags 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> { private fun JSONObject.selectGenres(tags: SparseArrayCompat<MangaTag>): Set<MangaTag> {
val array = optJSONArray("genres") ?: return emptySet() val array = optJSONArray("genres") ?: return emptySet()
val res = ArraySet<MangaTag>(array.length()) val res = ArraySet<MangaTag>(array.length())

@ -44,6 +44,22 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
keys.add(preferredServerKey) 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 override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities( get() = MangaListFilterCapabilities(
isMultipleTagsSupported = true, isMultipleTagsSupported = true,
@ -54,8 +70,6 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
isOriginalLocaleSupported = true, isOriginalLocaleSupported = true,
) )
override val availableSortOrders: EnumSet<SortOrder> = EnumSet.allOf(SortOrder::class.java)
override suspend fun getFilterOptions(): MangaListFilterOptions = coroutineScope { override suspend fun getFilterOptions(): MangaListFilterOptions = coroutineScope {
val localesDeferred = async { fetchAvailableLocales() } val localesDeferred = async { fetchAvailableLocales() }
val tagsDeferred = async { fetchAvailableTags() } val tagsDeferred = async { fetchAvailableTags() }

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

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

@ -43,10 +43,16 @@ internal abstract class AnimeBootstrapParser(
override val filterCapabilities: MangaListFilterCapabilities override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities( get() = MangaListFilterCapabilities(
isSearchSupported = true, isSearchSupported = true,
isSearchWithFiltersSupported = true,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(), availableTags = fetchAvailableTags(),
availableContentTypes = EnumSet.of(
ContentType.MANGA,
ContentType.MANHWA,
ContentType.MANHUA,
),
) )
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
@ -58,30 +64,37 @@ internal abstract class AnimeBootstrapParser(
append(page.toString()) append(page.toString())
append("&type=all") append("&type=all")
when { filter.query?.let {
!filter.query.isNullOrEmpty() -> { append("&search=")
append("&search=") append(filter.query.urlEncoded())
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.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() val doc = webClient.httpGet(url).parseHtml()

@ -4,6 +4,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import org.koitharu.kotatsu.parsers.Broken
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.PagedMangaParser
@ -15,6 +16,7 @@ import org.koitharu.kotatsu.parsers.util.json.mapJSONIndexed
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@Broken
@MangaSourceParser("FLIXSCANS", "FlixScans.net", "ar") @MangaSourceParser("FLIXSCANS", "FlixScans.net", "ar")
internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.FLIXSCANS, 18) { 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 override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities( get() = MangaListFilterCapabilities(
isSearchSupported = true, isSearchSupported = true,
isSearchWithFiltersSupported = true,
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(), availableTags = fetchAvailableTags(),
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED), 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> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
val url = buildString { val url = buildString {
append("https://") append("https://")
append(domain) append(domain)
when {
!filter.query.isNullOrEmpty() -> { if (order == SortOrder.UPDATED) {
append("/?search=") 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()) append(filter.query.urlEncoded())
if (page > 1) {
append("&page=")
append(page.toString())
}
} }
else -> { filter.tags.oneOrThrowIfMany()?.let {
if (filter.tags.isNotEmpty()) { append("&genre=")
val tag = filter.tags.oneOrThrowIfMany() append(it.key)
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("?")
}
}
if (order == SortOrder.POPULARITY || filter.tags.isNotEmpty()) { filter.types.forEach {
filter.states.oneOrThrowIfMany()?.let { append("&type=")
append("status=") append(
append( when (it) {
when (it) { ContentType.MANGA -> "مانجا ياباني"
MangaState.ONGOING -> "مستمرة" ContentType.MANHWA -> "مانهوا كورية"
MangaState.FINISHED -> "مكتمل" ContentType.MANHUA -> "مانها صيني"
MangaState.ABANDONED -> "متوقف" else -> ""
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 val d = date?.lowercase() ?: return 0
return when { 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) -> { WordSet("today").startsWith(d) -> {
Calendar.getInstance().apply { Calendar.getInstance().apply {
@ -281,18 +283,25 @@ internal abstract class FmreaderParser(
return when { return when {
WordSet("second") WordSet("second")
.anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis .anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis
WordSet("min", "minute", "minutes", "minuto", "minutos") WordSet("min", "minute", "minutes", "minuto", "minutos")
.anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis .anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
WordSet("hour", "hours", "hora", "horas", "h") WordSet("hour", "hours", "hora", "horas", "h")
.anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis .anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis
WordSet("day", "days", "día", "dia") WordSet("day", "days", "día", "dia")
.anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis .anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
WordSet("week", "weeks", "semana", "semanas") WordSet("week", "weeks", "semana", "semanas")
.anyWordIn(date) -> cal.apply { add(Calendar.WEEK_OF_YEAR, -number) }.timeInMillis .anyWordIn(date) -> cal.apply { add(Calendar.WEEK_OF_YEAR, -number) }.timeInMillis
WordSet("month", "months", "mes", "meses") WordSet("month", "months", "mes", "meses")
.anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis .anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
WordSet("year", "año", "años") WordSet("year", "año", "años")
.anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis .anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis
else -> 0 else -> 0
} }
} }

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

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

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

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

@ -265,7 +265,9 @@ internal abstract class MangaboxParser(
protected fun parseChapterDate(dateFormat: DateFormat, date: String?): Long { protected fun parseChapterDate(dateFormat: DateFormat, date: String?): Long {
val d = date?.lowercase() ?: return 0 val d = date?.lowercase() ?: return 0
return when { return when {
WordSet(" ago", " h", " d").endsWith(d) -> { parseRelativeDate(d) } WordSet(" ago", " h", " d").endsWith(d) -> {
parseRelativeDate(d)
}
WordSet("today").startsWith(d) -> { WordSet("today").startsWith(d) -> {
Calendar.getInstance().apply { Calendar.getInstance().apply {
@ -294,16 +296,22 @@ internal abstract class MangaboxParser(
return when { return when {
WordSet("second") WordSet("second")
.anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis .anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis
WordSet("min", "minute", "minutes") WordSet("min", "minute", "minutes")
.anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis .anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
WordSet("hour", "hours", "h") WordSet("hour", "hours", "h")
.anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis .anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis
WordSet("day", "days") WordSet("day", "days")
.anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis .anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
WordSet("month", "months") WordSet("month", "months")
.anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis .anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
WordSet("year") WordSet("year")
.anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis .anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis
else -> 0 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", "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", "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", "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", "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", "مكتملة", "Завершено", "Finished", "Finalizado", "Completata", "One-Shot", "Bitti", "Tamat", "Completado", "Concluído",
"Concluido", "已完结", "Bitmiş", "Concluido", "已完结", "Bitmiş",
-> MangaState.FINISHED -> MangaState.FINISHED
"Canceled", "Cancelled", "Cancelado", "cancellato", "Cancelados", "Dropped", "Discontinued", "abandonné", "Abandonné", "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", "Hiatus", "On Hold", "Pausado", "En espera", "En pause", "En Pause", "En attente",
-> MangaState.PAUSED -> MangaState.PAUSED
else -> null else -> null
} }

@ -9,12 +9,12 @@ import java.util.*
@MangaSourceParser("ADUMANGA", "AduManga", "tr") @MangaSourceParser("ADUMANGA", "AduManga", "tr")
internal class AduManga(context: MangaLoaderContext) : 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 override val filterCapabilities: MangaListFilterCapabilities
get() = super.filterCapabilities.copy( get() = super.filterCapabilities.copy(
isTagsExclusionSupported = false, isTagsExclusionSupported = false,
) )
} }

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

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

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

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

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

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

Loading…
Cancel
Save