remove unessery code

Remove old chapter manga and change last source for new manga chapter
Simplify parseChapterDate
Fix fetchAvailableTags on some source need to override
devi 2 years ago
parent 336c4a4d49
commit 600eab20a1

@ -38,28 +38,6 @@ public class MangaChapter(
@JvmField public val source: MangaSource, @JvmField public val source: MangaSource,
) { ) {
@Deprecated(message = "Consider using constructor with volume value")
internal constructor(
id: Long,
name: String,
number: Int,
url: String,
scanlator: String?,
uploadDate: Long,
branch: String?,
source: MangaSource,
) : this(
id = id,
name = name,
number = number.toFloat(),
volume = 0,
url = url,
scanlator = scanlator,
uploadDate = uploadDate,
branch = branch,
source = source,
)
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false

@ -5,7 +5,7 @@ import org.koitharu.kotatsu.parsers.InternalParsersApi
@InternalParsersApi @InternalParsersApi
public class WordSet(private vararg val words: String) { public class WordSet(private vararg val words: String) {
public fun anyWordIn(dateString: String): Boolean = words.any { public fun anyWordIn(dateString: String): Boolean = words.any { dateString.contains(it, ignoreCase = true) }
dateString.contains(it, ignoreCase = true) public fun startsWith(dateString: String): Boolean = words.any { dateString.startsWith(it, ignoreCase = true) }
} public fun endsWith(dateString: String): Boolean = words.any { dateString.endsWith(it, ignoreCase = true) }
} }

@ -71,8 +71,6 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
availableTags = fetchAvailableTags(), availableTags = fetchAvailableTags(),
availableStates = EnumSet.allOf(MangaState::class.java), availableStates = EnumSet.allOf(MangaState::class.java),
availableContentRating = EnumSet.of(ContentRating.SAFE), availableContentRating = EnumSet.of(ContentRating.SAFE),
availableContentTypes = emptySet(),
availableDemographics = emptySet(),
availableLocales = setOf( availableLocales = setOf(
Locale.CHINESE, Locale.ENGLISH, Locale.US, Locale.FRENCH, Locale.GERMAN, Locale.ITALIAN, Locale.JAPANESE, Locale.CHINESE, Locale.ENGLISH, Locale.US, Locale.FRENCH, Locale.GERMAN, Locale.ITALIAN, Locale.JAPANESE,
Locale("af"), Locale("ar"), Locale("az"), Locale("eu"), Locale("be"), Locale("af"), Locale("ar"), Locale("az"), Locale("eu"), Locale("be"),

@ -47,7 +47,6 @@ internal class ComickFunParser(context: MangaLoaderContext) :
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(), availableTags = fetchAvailableTags(),
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED), availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED),
availableContentRating = emptySet(),
availableContentTypes = EnumSet.of( availableContentTypes = EnumSet.of(
ContentType.MANGA, ContentType.MANGA,
ContentType.MANHWA, ContentType.MANHWA,
@ -55,7 +54,6 @@ internal class ComickFunParser(context: MangaLoaderContext) :
ContentType.OTHER, ContentType.OTHER,
), ),
availableDemographics = EnumSet.allOf(Demographic::class.java), availableDemographics = EnumSet.allOf(Demographic::class.java),
availableLocales = emptySet(),
) )
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {

@ -69,7 +69,6 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
MangaState.ABANDONED, MangaState.ABANDONED,
), ),
availableContentRating = EnumSet.allOf(ContentRating::class.java), availableContentRating = EnumSet.allOf(ContentRating::class.java),
availableContentTypes = emptySet(),
availableDemographics = EnumSet.allOf(Demographic::class.java), availableDemographics = EnumSet.allOf(Demographic::class.java),
availableLocales = localesDeferred.await(), availableLocales = localesDeferred.await(),
) )

@ -81,10 +81,6 @@ internal abstract class MangaFireParser(
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = tags.get().values.toSet(), availableTags = tags.get().values.toSet(),
availableStates = EnumSet.allOf(MangaState::class.java), availableStates = EnumSet.allOf(MangaState::class.java),
availableContentRating = emptySet(),
availableContentTypes = emptySet(),
availableDemographics = emptySet(),
availableLocales = emptySet(),
) )
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {

@ -32,8 +32,6 @@ internal class MangaPark(context: MangaLoaderContext) :
availableTags = tagsMap.get().values.toSet(), availableTags = tagsMap.get().values.toSet(),
availableStates = EnumSet.allOf(MangaState::class.java), availableStates = EnumSet.allOf(MangaState::class.java),
availableContentRating = EnumSet.of(ContentRating.SAFE), availableContentRating = EnumSet.of(ContentRating.SAFE),
availableContentTypes = emptySet(),
availableDemographics = emptySet(),
availableLocales = setOf( availableLocales = setOf(
Locale("af"), Locale("sq"), Locale("am"), Locale("ar"), Locale("hy"), Locale("af"), Locale("sq"), Locale("am"), Locale("ar"), Locale("hy"),
Locale("az"), Locale("be"), Locale("bn"), Locale("zh_hk"), Locale("zh_tw"), Locale("az"), Locale("be"), Locale("bn"), Locale("zh_hk"), Locale("zh_tw"),

@ -77,10 +77,6 @@ internal class MangaReaderToParser(context: MangaLoaderContext) :
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = tags.get().values.toSet(), availableTags = tags.get().values.toSet(),
availableStates = EnumSet.allOf(MangaState::class.java), availableStates = EnumSet.allOf(MangaState::class.java),
availableContentRating = emptySet(),
availableContentTypes = emptySet(),
availableDemographics = emptySet(),
availableLocales = emptySet(),
) )
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {

@ -53,10 +53,6 @@ internal abstract class NineMangaParser(
MangaState.ONGOING, MangaState.ONGOING,
MangaState.FINISHED, MangaState.FINISHED,
), ),
availableContentRating = emptySet(),
availableContentTypes = emptySet(),
availableDemographics = emptySet(),
availableLocales = emptySet(),
) )
override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {

@ -31,9 +31,6 @@ internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context
availableTags = fetchAvailableTags(), availableTags = fetchAvailableTags(),
availableStates = EnumSet.allOf(MangaState::class.java), availableStates = EnumSet.allOf(MangaState::class.java),
availableContentRating = EnumSet.of(ContentRating.ADULT), availableContentRating = EnumSet.of(ContentRating.ADULT),
availableContentTypes = emptySet(),
availableDemographics = emptySet(),
availableLocales = emptySet(),
) )
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) { override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {

@ -33,10 +33,6 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex
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),
availableContentRating = emptySet(),
availableContentTypes = emptySet(),
availableDemographics = emptySet(),
availableLocales = emptySet(),
) )
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {

@ -35,10 +35,6 @@ internal abstract class CupFoxParser(
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(), availableTags = fetchAvailableTags(),
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED),
availableContentRating = emptySet(),
availableContentTypes = emptySet(),
availableDemographics = emptySet(),
availableLocales = emptySet(),
) )
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {

@ -36,10 +36,6 @@ internal class AsuraScansParser(context: MangaLoaderContext) :
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = getOrCreateTagMap().values.toSet(), availableTags = getOrCreateTagMap().values.toSet(),
availableStates = EnumSet.allOf(MangaState::class.java), availableStates = EnumSet.allOf(MangaState::class.java),
availableContentRating = emptySet(),
availableContentTypes = emptySet(),
availableDemographics = emptySet(),
availableLocales = emptySet(),
) )
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) { override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {

@ -29,7 +29,6 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(), availableTags = fetchAvailableTags(),
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED),
availableContentRating = emptySet(),
) )
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) { override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {

@ -39,7 +39,6 @@ internal class MangaTownParser(context: MangaLoaderContext) :
MangaState.ONGOING, MangaState.ONGOING,
MangaState.FINISHED, MangaState.FINISHED,
), ),
availableContentRating = emptySet(),
) )
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {

@ -39,10 +39,6 @@ internal class Mangaowl(context: MangaLoaderContext) :
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(), availableTags = fetchAvailableTags(),
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED),
availableContentRating = emptySet(),
availableContentTypes = emptySet(),
availableDemographics = emptySet(),
availableLocales = emptySet(),
) )
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) { override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {

@ -45,7 +45,6 @@ internal class Manhwa18Parser(context: MangaLoaderContext) :
MangaState.FINISHED, MangaState.FINISHED,
MangaState.PAUSED, MangaState.PAUSED,
), ),
availableContentRating = emptySet(),
) )
override suspend fun getFavicons(): Favicons { override suspend fun getFavicons(): Favicons {

@ -32,7 +32,6 @@ internal class ManhwasMen(context: MangaLoaderContext) :
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(), availableTags = fetchAvailableTags(),
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED),
availableContentRating = emptySet(),
) )
override suspend fun getListPage( override suspend fun getListPage(

@ -40,7 +40,6 @@ internal class VyManga(context: MangaLoaderContext) :
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(), availableTags = fetchAvailableTags(),
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED), availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED),
availableContentRating = emptySet(),
) )
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {

@ -78,10 +78,6 @@ internal abstract class FmreaderParser(
MangaState.FINISHED, MangaState.FINISHED,
MangaState.ABANDONED, MangaState.ABANDONED,
), ),
availableContentRating = emptySet(),
availableContentTypes = emptySet(),
availableDemographics = emptySet(),
availableLocales = emptySet(),
) )
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
@ -253,31 +249,19 @@ internal abstract class FmreaderParser(
} }
protected fun parseChapterDate(dateFormat: DateFormat, date: String?): Long { protected fun parseChapterDate(dateFormat: DateFormat, date: String?): Long {
// Clean date (e.g. 5th December 2019 to 5 December 2019) before parsing it
val d = date?.lowercase() ?: return 0 val d = date?.lowercase() ?: return 0
return when { return when {
d.endsWith(" ago") ||
d.endsWith(" atrás") ||
// short Hours
d.endsWith(" h") ||
// short Day
d.endsWith(" d") -> parseRelativeDate(date)
// Handle 'yesterday' and 'today', using midnight
d.startsWith("year") -> Calendar.getInstance().apply {
add(Calendar.DAY_OF_MONTH, -1) // yesterday
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}.timeInMillis
d.startsWith("today") -> Calendar.getInstance().apply { WordSet(" ago", " atrás", " h", " d").endsWith(d) -> { parseRelativeDate(d) }
WordSet("today").startsWith(d) -> {
Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, 0) set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0) set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0) set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0) set(Calendar.MILLISECOND, 0)
}.timeInMillis }.timeInMillis
}
date.contains(Regex("""\d(st|nd|rd|th)""")) -> date.split(" ").map { date.contains(Regex("""\d(st|nd|rd|th)""")) -> date.split(" ").map {
if (it.contains(Regex("""\d\D\D"""))) { if (it.contains(Regex("""\d\D\D"""))) {
@ -291,49 +275,24 @@ internal abstract class FmreaderParser(
} }
} }
// Parses dates in this form:
// 21 hours ago
private fun parseRelativeDate(date: String): Long { private fun parseRelativeDate(date: String): Long {
val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0 val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0
val cal = Calendar.getInstance() val cal = Calendar.getInstance()
return when { return when {
WordSet("second").anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis WordSet("second")
WordSet("min", "minute", "minutes", "minuto", "minutos").anyWordIn(date) -> cal.apply { .anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis
add( WordSet("min", "minute", "minutes", "minuto", "minutos")
Calendar.MINUTE, .anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
-number, WordSet("hour", "hours", "hora", "horas", "h")
) .anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis
}.timeInMillis WordSet("day", "days", "día", "dia")
.anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
WordSet("hour", "hours", "hora", "horas", "h").anyWordIn(date) -> cal.apply { WordSet("week", "weeks", "semana", "semanas")
add( .anyWordIn(date) -> cal.apply { add(Calendar.WEEK_OF_YEAR, -number) }.timeInMillis
Calendar.HOUR, WordSet("month", "months", "mes", "meses")
-number, .anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
) WordSet("year", "año", "años")
}.timeInMillis .anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -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 else -> 0
} }
} }

@ -52,7 +52,6 @@ internal class BentomangaParser(context: MangaLoaderContext) :
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(), availableTags = fetchAvailableTags(),
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED), availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED),
availableContentRating = emptySet(),
) )
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {

@ -28,7 +28,6 @@ internal class LegacyScansParser(context: MangaLoaderContext) :
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(), availableTags = fetchAvailableTags(),
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED, MangaState.PAUSED), availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED, MangaState.PAUSED),
availableContentRating = emptySet(),
) )
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) { override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {

@ -33,12 +33,7 @@ internal class LugnicaScans(context: MangaLoaderContext) :
) )
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = emptySet(),
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED), availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED),
availableContentRating = emptySet(),
availableContentTypes = emptySet(),
availableDemographics = emptySet(),
availableLocales = emptySet(),
) )
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) { override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {

@ -42,7 +42,6 @@ internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context
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),
availableContentRating = emptySet(),
) )
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) { override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {

@ -76,7 +76,6 @@ internal abstract class FuzzyDoodleParser(
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(), availableTags = fetchAvailableTags(),
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED), availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED),
availableContentRating = emptySet(),
) )
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {

@ -54,10 +54,6 @@ internal abstract class HeanCms(
override suspend fun getFilterOptions() = MangaListFilterOptions( override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = fetchAvailableTags(), availableTags = fetchAvailableTags(),
availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED), availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED),
availableContentRating = emptySet(),
availableContentTypes = emptySet(),
availableDemographics = emptySet(),
availableLocales = emptySet(),
) )
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> { override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {

@ -149,7 +149,7 @@ internal class NicovideoSeigaParser(context: MangaLoaderContext) :
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> { override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val fullUrl = chapter.url.toAbsoluteUrl(getDomain("seiga")) val fullUrl = chapter.url.toAbsoluteUrl(getDomain("seiga"))
val doc = webClient.httpGet(fullUrl).parseHtml() val doc = webClient.httpGet(fullUrl).parseHtml()
if (!doc.select("#login_manga").isEmpty) if (!doc.select("#login_manga").isEmpty())
throw AuthRequiredException(source) throw AuthRequiredException(source)
val root = doc.body().select("#page_contents > li") val root = doc.body().select("#page_contents > li")
return root.map { li -> return root.map { li ->

@ -266,22 +266,17 @@ internal abstract class KeyoappParser(
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 {
d.endsWith(" ago") -> parseRelativeDate(date)
d.startsWith("year") -> Calendar.getInstance().apply { WordSet(" ago").endsWith(d) -> { parseRelativeDate(d) }
add(Calendar.DAY_OF_MONTH, -1)
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}.timeInMillis
d.startsWith("today") -> Calendar.getInstance().apply { WordSet("today").startsWith(d) -> {
Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, 0) set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0) set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0) set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0) set(Calendar.MILLISECOND, 0)
}.timeInMillis }.timeInMillis
}
date.contains(Regex("""\d(st|nd|rd|th)""")) -> date.split(" ").map { date.contains(Regex("""\d(st|nd|rd|th)""")) -> date.split(" ").map {
if (it.contains(Regex("""\d\D\D"""))) { if (it.contains(Regex("""\d\D\D"""))) {
@ -299,17 +294,18 @@ internal abstract class KeyoappParser(
val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0 val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0
val cal = Calendar.getInstance() val cal = Calendar.getInstance()
return when { return when {
WordSet("second").anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis WordSet("second")
.anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis
WordSet("minute", "minutes").anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -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("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("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("month", "months")
.anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
WordSet("year").anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis WordSet("year")
.anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis
else -> 0 else -> 0
} }
} }

@ -746,56 +746,46 @@ internal abstract class MadaraParser(
} }
protected fun parseChapterDate(dateFormat: DateFormat, date: String?): Long { protected fun parseChapterDate(dateFormat: DateFormat, date: String?): Long {
// Clean date (e.g. 5th December 2019 to 5 December 2019) before parsing it
val d = date?.lowercase() ?: return 0 val d = date?.lowercase() ?: return 0
return when { return when {
d.endsWith(" ago") || d.endsWith(" atrás") || // Handle translated 'ago' in Portuguese.
d.startsWith("") || // other translated 'ago' in Portuguese. WordSet(" ago", "atrás", " hace", " publicado"," назад", " önce", " trước", "مضت",
d.endsWith(" hace") || // other translated 'ago' in Spanish " h", " d", " días", " jour", " horas", " heure", " mins", " minutos", " minute", " mois").endsWith(d) -> {
d.endsWith(" publicado") || parseRelativeDate(d)
d.endsWith(" назад") || // other translated 'ago' in Russian }
d.endsWith(" önce") || // Handle translated 'ago' in Turkish.
d.endsWith(" trước") || // Handle translated 'ago' in Viêt Nam. WordSet("", "منذ", "il y a" ).startsWith(d) -> {
d.endsWith("مضت") || // Handle translated 'ago' in Arabic parseRelativeDate(d)
d.startsWith("منذ") || }
d.startsWith("il y a") || // Handle translated 'ago' in French.
//If there is no ago but just a motion of time WordSet("yesterday", "يوم واحد").startsWith(d) -> {
// short Hours Calendar.getInstance().apply {
d.endsWith(" h") ||
// short Day
d.endsWith(" d") ||
// Day in Portuguese
d.endsWith(" días") || d.endsWith(" día") ||
// Day in French
d.endsWith(" jour") || d.endsWith(" jours") ||
// Hours in Portuguese
d.endsWith(" horas") || d.endsWith(" hora") ||
// Hours in french
d.endsWith(" heure") || d.endsWith(" heures") ||
// Minutes in English
d.endsWith(" mins") ||
// Minutes in Portuguese
d.endsWith(" minutos") || d.endsWith(" minuto") ||
//Minutes in French
d.endsWith(" minute") || d.endsWith(" minutes") ||
//month in French
d.endsWith(" mois") -> parseRelativeDate(date)
// Handle 'yesterday' and 'today', using midnight
d.startsWith("year") -> Calendar.getInstance().apply {
add(Calendar.DAY_OF_MONTH, -1) // yesterday add(Calendar.DAY_OF_MONTH, -1) // yesterday
set(Calendar.HOUR_OF_DAY, 0) set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0) set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0) set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0) set(Calendar.MILLISECOND, 0)
}.timeInMillis }.timeInMillis
}
WordSet("today").startsWith(d) -> {
Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}.timeInMillis
}
d.startsWith("today") -> Calendar.getInstance().apply { WordSet("يومين").startsWith(d) -> {
Calendar.getInstance().apply {
add(Calendar.DAY_OF_MONTH, -2) // day before yesterday
set(Calendar.HOUR_OF_DAY, 0) set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0) set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0) set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0) set(Calendar.MILLISECOND, 0)
}.timeInMillis }.timeInMillis
}
date.contains(Regex("""\d(st|nd|rd|th)""")) -> date.split(" ").map { date.contains(Regex("""\d(st|nd|rd|th)""")) -> date.split(" ").map {
if (it.contains(Regex("""\d\D\D"""))) { if (it.contains(Regex("""\d\D\D"""))) {
@ -812,27 +802,19 @@ internal abstract class MadaraParser(
private fun parseRelativeDate(date: String): Long { private fun parseRelativeDate(date: String): Long {
val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0 val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0
val cal = Calendar.getInstance() val cal = Calendar.getInstance()
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
} }
} }

@ -271,30 +271,19 @@ internal abstract class MadthemeParser(
} }
protected fun parseChapterDate(dateFormat: DateFormat, date: String?): Long { protected fun parseChapterDate(dateFormat: DateFormat, date: String?): Long {
// Clean date (e.g. 5th December 2019 to 5 December 2019) before parsing it
val d = date?.lowercase() ?: return 0 val d = date?.lowercase() ?: return 0
return when { return when {
d.endsWith(" ago") ||
// short Hours
d.endsWith(" h") ||
// short Day
d.endsWith(" d") -> parseRelativeDate(date)
// Handle 'yesterday' and 'today', using midnight
d.startsWith("year") -> Calendar.getInstance().apply {
add(Calendar.DAY_OF_MONTH, -1) // yesterday
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}.timeInMillis
d.startsWith("today") -> Calendar.getInstance().apply { WordSet(" ago", " h", " d").endsWith(d) -> { parseRelativeDate(d) }
WordSet("today").startsWith(d) -> {
Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, 0) set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0) set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0) set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0) set(Calendar.MILLISECOND, 0)
}.timeInMillis }.timeInMillis
}
date.contains(Regex("""\d(st|nd|rd|th)""")) -> date.split(" ").map { date.contains(Regex("""\d(st|nd|rd|th)""")) -> date.split(" ").map {
if (it.contains(Regex("""\d\D\D"""))) { if (it.contains(Regex("""\d\D\D"""))) {
@ -308,45 +297,22 @@ internal abstract class MadthemeParser(
} }
} }
// Parses dates in this form:
// 21 hours ago
private fun parseRelativeDate(date: String): Long { private fun parseRelativeDate(date: String): Long {
val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0 val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0
val cal = Calendar.getInstance() val cal = Calendar.getInstance()
return when { return when {
WordSet( WordSet("second")
"day", .anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis
"days", WordSet("min", "minute", "minutes")
).anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis .anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
WordSet("hour", "hours", "h")
WordSet("hour", "hours", "h").anyWordIn(date) -> cal.apply { .anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis
add( WordSet("day", "days")
Calendar.HOUR, .anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
-number, WordSet("month", "months")
) .anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
}.timeInMillis WordSet("year")
.anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis
WordSet(
"min",
"minute",
"minutes",
).anyWordIn(date) -> cal.apply {
add(
Calendar.MINUTE,
-number,
)
}.timeInMillis
WordSet("second").anyWordIn(date) -> cal.apply {
add(
Calendar.SECOND,
-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 else -> 0
} }
} }

@ -5,8 +5,8 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaListFilterOptions
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.site.manga18.Manga18Parser import org.koitharu.kotatsu.parsers.site.manga18.Manga18Parser
import org.koitharu.kotatsu.parsers.util.attrAsRelativeUrl import org.koitharu.kotatsu.parsers.util.attrAsRelativeUrl
import org.koitharu.kotatsu.parsers.util.generateUid import org.koitharu.kotatsu.parsers.util.generateUid
@ -17,6 +17,10 @@ import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow
internal class Hanman18(context: MangaLoaderContext) : internal class Hanman18(context: MangaLoaderContext) :
Manga18Parser(context, MangaParserSource.HANMAN18, "hanman18.com") { Manga18Parser(context, MangaParserSource.HANMAN18, "hanman18.com") {
override suspend fun getFilterOptions() = MangaListFilterOptions(
availableTags = emptySet()
)
override suspend fun getChapters(doc: Document): List<MangaChapter> { override suspend fun getChapters(doc: Document): List<MangaChapter> {
return doc.body().select(selectChapter).mapChapters(reversed = true) { i, li -> return doc.body().select(selectChapter).mapChapters(reversed = true) { i, li ->
val a = li.selectFirstOrThrow("a") val a = li.selectFirstOrThrow("a")
@ -34,6 +38,4 @@ internal class Hanman18(context: MangaLoaderContext) :
) )
} }
} }
private suspend fun fetchAvailableTags(): Set<MangaTag> = emptySet() // search by tag does not work
} }

@ -146,7 +146,7 @@ internal abstract class MangaboxParser(
protected open val selectTagMap = "div.panel-genres-list a:not(.genres-select)" protected open val selectTagMap = "div.panel-genres-list a:not(.genres-select)"
private suspend fun fetchAvailableTags(): Set<MangaTag> { protected open suspend fun fetchAvailableTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml() val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml()
val tags = doc.select(selectTagMap).drop(1) // remove all tags val tags = doc.select(selectTagMap).drop(1) // remove all tags
return tags.mapNotNullToSet { a -> return tags.mapNotNullToSet { a ->
@ -263,30 +263,18 @@ internal abstract class MangaboxParser(
} }
protected fun parseChapterDate(dateFormat: DateFormat, date: String?): Long { protected fun parseChapterDate(dateFormat: DateFormat, date: String?): Long {
// Clean date (e.g. 5th December 2019 to 5 December 2019) before parsing it
val d = date?.lowercase() ?: return 0 val d = date?.lowercase() ?: return 0
return when { return when {
d.endsWith(" ago") || WordSet(" ago", " h", " d").endsWith(d) -> { parseRelativeDate(d) }
// short Hours
d.endsWith(" h") ||
// short Day
d.endsWith(" d") -> parseRelativeDate(date)
// Handle 'yesterday' and 'today', using midnight
d.startsWith("year") -> Calendar.getInstance().apply {
add(Calendar.DAY_OF_MONTH, -1) // yesterday
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}.timeInMillis
d.startsWith("today") -> Calendar.getInstance().apply { WordSet("today").startsWith(d) -> {
Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, 0) set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0) set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0) set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0) set(Calendar.MILLISECOND, 0)
}.timeInMillis }.timeInMillis
}
date.contains(Regex("""\d(st|nd|rd|th)""")) -> date.split(" ").map { date.contains(Regex("""\d(st|nd|rd|th)""")) -> date.split(" ").map {
if (it.contains(Regex("""\d\D\D"""))) { if (it.contains(Regex("""\d\D\D"""))) {
@ -300,45 +288,22 @@ internal abstract class MangaboxParser(
} }
} }
// Parses dates in this form:
// 21 hours ago
private fun parseRelativeDate(date: String): Long { private fun parseRelativeDate(date: String): Long {
val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0 val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0
val cal = Calendar.getInstance() val cal = Calendar.getInstance()
return when { return when {
WordSet( WordSet("second")
"day", .anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis
"days", WordSet("min", "minute", "minutes")
).anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis .anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
WordSet("hour", "hours", "h")
WordSet("hour", "hours", "h").anyWordIn(date) -> cal.apply { .anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis
add( WordSet("day", "days")
Calendar.HOUR, .anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
-number, WordSet("month", "months")
) .anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
}.timeInMillis WordSet("year")
.anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis
WordSet(
"min",
"minute",
"minutes",
).anyWordIn(date) -> cal.apply {
add(
Calendar.MINUTE,
-number,
)
}.timeInMillis
WordSet("second").anyWordIn(date) -> cal.apply {
add(
Calendar.SECOND,
-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 else -> 0
} }
} }

@ -108,7 +108,7 @@ internal class Mangairo(context: MangaLoaderContext) :
} }
} }
private suspend fun fetchAvailableTags(): Set<MangaTag> { override suspend fun fetchAvailableTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain/$listUrl/type-latest/ctg-all/state-all/page-1").parseHtml() val doc = webClient.httpGet("https://$domain/$listUrl/type-latest/ctg-all/state-all/page-1").parseHtml()
return doc.select("div.panel_category a:not(.ctg_select)").mapNotNullToSet { a -> return doc.select("div.panel_category a:not(.ctg_select)").mapNotNullToSet { a ->
val key = a.attr("href").substringAfterLast("ctg-").substringBefore("/") val key = a.attr("href").substringAfterLast("ctg-").substringBefore("/")

@ -97,7 +97,7 @@ internal class Mangakakalot(context: MangaLoaderContext) :
} }
} }
private suspend fun fetchAvailableTags(): Set<MangaTag> { override suspend fun fetchAvailableTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml() val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml()
val tags = doc.select("ul.tag li a").drop(1) val tags = doc.select("ul.tag li a").drop(1)
return tags.mapNotNullToSet { a -> return tags.mapNotNullToSet { a ->

@ -128,7 +128,7 @@ internal class MangakakalotTv(context: MangaLoaderContext) :
override val selectTagMap = "ul.tag li a" override val selectTagMap = "ul.tag li a"
private suspend fun fetchAvailableTags(): Set<MangaTag> { override suspend fun fetchAvailableTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml() val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml()
return doc.select(selectTagMap).mapNotNullToSet { a -> return doc.select(selectTagMap).mapNotNullToSet { a ->
MangaTag( MangaTag(

@ -66,7 +66,6 @@ internal abstract class MmrcmsParser(
override val filterCapabilities: MangaListFilterCapabilities override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities( get() = MangaListFilterCapabilities(
isMultipleTagsSupported = false,
isSearchSupported = true, isSearchSupported = true,
) )

@ -241,61 +241,30 @@ internal abstract class OtakuSanctuaryParser(
} }
protected fun parseChapterDate(dateFormat: DateFormat, date: String?): Long { protected fun parseChapterDate(dateFormat: DateFormat, date: String?): Long {
// Clean date (e.g. 5th December 2019 to 5 December 2019) before parsing it
val d = date?.lowercase() ?: return 0 val d = date?.lowercase() ?: return 0
return when { return when {
d.endsWith(" ago") || d.endsWith(" atrás") || d.startsWith("cách đây ") -> parseRelativeDate(date) WordSet(" ago", " atrás").endsWith(d) -> { parseRelativeDate(d) }
WordSet("cách đây ").startsWith(d) -> { parseRelativeDate(d) }
else -> dateFormat.tryParse(date) else -> dateFormat.tryParse(date)
} }
} }
// Parses dates in this form:
// 21 hours ago
private fun parseRelativeDate(date: String): Long { private fun parseRelativeDate(date: String): Long {
val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0 val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0
val cal = Calendar.getInstance() val cal = Calendar.getInstance()
return when { return when {
WordSet( WordSet("second", "giây")
"day", .anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis
"days", WordSet("min", "minute", "minutes", "phút")
"d", .anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
"ngày", WordSet("tiếng", "hour", "hours")
).anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis .anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis
WordSet("day", "days", "d", "ngày")
WordSet( .anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
"tiếng", WordSet("month", "months")
"hour", .anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
"hours", WordSet("year")
).anyWordIn(date) -> cal.apply { .anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis
add(
Calendar.HOUR,
-number,
)
}.timeInMillis
WordSet(
"min",
"minute",
"minutes",
"phút",
).anyWordIn(date) -> cal.apply {
add(
Calendar.MINUTE,
-number,
)
}.timeInMillis
WordSet("second", "giây").anyWordIn(date) -> cal.apply {
add(
Calendar.SECOND,
-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 else -> 0
} }
} }

@ -71,7 +71,7 @@ internal class MangaWtfParser(
SortOrder.POPULARITY -> "viewsCount,desc" SortOrder.POPULARITY -> "viewsCount,desc"
SortOrder.RATING -> "likesCount,desc" SortOrder.RATING -> "likesCount,desc"
SortOrder.NEWEST -> "createdAt,desc" SortOrder.NEWEST -> "createdAt,desc"
else -> throw IllegalArgumentException("Unsupported ${order}") else -> throw IllegalArgumentException("Unsupported $order")
}, },
) )
if (filter.tags.isNotEmpty()) { if (filter.tags.isNotEmpty()) {

@ -174,7 +174,8 @@ internal class RemangaParser(
MangaChapter( MangaChapter(
id = generateUid(id), id = generateUid(id),
url = "/api/titles/chapters/$id/", url = "/api/titles/chapters/$id/",
number = jo.getIntOrDefault("index", chapters.size - i), number = jo.getIntOrDefault("index", chapters.size - i).toFloat(),
volume = 0,
name = buildString { name = buildString {
append("Том ") append("Том ")
append(jo.optString("tome", "0")) append(jo.optString("tome", "0"))

@ -12,8 +12,9 @@ internal class MangaFr(context: MangaLoaderContext) :
ScanParser(context, MangaParserSource.MANGAFR, "www.mangafr.org") { ScanParser(context, MangaParserSource.MANGAFR, "www.mangafr.org") {
override val listUrl = "/series" override val listUrl = "/series"
private suspend fun fetchAvailableTags(): Set<MangaTag> = emptySet() override suspend fun getFilterOptions() = MangaListFilterOptions(
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)

@ -124,7 +124,8 @@ internal class MangaInUaParser(context: MangaLoaderContext) : PagedMangaParser(
prevChapterName = name prevChapterName = name
name name
}, },
number = i, number = i.toFloat(),
volume = 0,
url = href, url = href,
scanlator = null, scanlator = null,
branch = if (isAlternative) { branch = if (isAlternative) {

@ -279,24 +279,17 @@ internal abstract class WpComicsParser(
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 {
d.endsWith(" ago") ||
d.endsWith(" trước")
-> parseRelativeDate(date)
d.startsWith("year") -> Calendar.getInstance().apply { WordSet(" ago", " trước").endsWith(d) -> { parseRelativeDate(d) }
add(Calendar.DAY_OF_MONTH, -1)
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}.timeInMillis
d.startsWith("today") -> Calendar.getInstance().apply { WordSet("today").startsWith(d) -> {
Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, 0) set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0) set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0) set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0) set(Calendar.MILLISECOND, 0)
}.timeInMillis }.timeInMillis
}
date.contains(Regex("""\d(st|nd|rd|th)""")) -> date.split(" ").map { date.contains(Regex("""\d(st|nd|rd|th)""")) -> date.split(" ").map {
if (it.contains(Regex("""\d\D\D"""))) { if (it.contains(Regex("""\d\D\D"""))) {
@ -314,28 +307,16 @@ internal abstract class WpComicsParser(
val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0 val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0
val cal = Calendar.getInstance() val cal = Calendar.getInstance()
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 { .anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
add(Calendar.MINUTE, -number) WordSet("jam", "saat", "heure", "hora", "horas", "hour", "hours", "h", "giờ")
}.timeInMillis .anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis
WordSet("day", "days", "d", "ngày")
WordSet("jam", "saat", "heure", "hora", "horas", "hour", "hours", "h", "giờ").anyWordIn(date) -> cal.apply { .anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
add(Calendar.HOUR, -number) WordSet("month", "months", "tháng")
}.timeInMillis .anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -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 WordSet("year", "năm").anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis
else -> 0 else -> 0
} }

@ -110,7 +110,7 @@ internal class XoxoComics(context: MangaLoaderContext) :
source = source, source = source,
) )
} }
val result = list.associateByTo(ArrayMap<String, MangaTag>(list.size)) { it.title } val result = list.associateByTo(ArrayMap(list.size)) { it.title }
tagCache = result tagCache = result
result result
} }

@ -250,30 +250,18 @@ internal abstract class ZMangaParser(
} }
protected fun parseChapterDate(dateFormat: DateFormat, date: String?): Long { protected fun parseChapterDate(dateFormat: DateFormat, date: String?): Long {
// Clean date (e.g. 5th December 2019 to 5 December 2019) before parsing it
val d = date?.lowercase() ?: return 0 val d = date?.lowercase() ?: return 0
return when { return when {
d.endsWith(" ago") || WordSet(" ago", " h", " d").endsWith(d) -> { parseRelativeDate(d) }
// short Hours
d.endsWith(" h") ||
// short Day
d.endsWith(" d") -> parseRelativeDate(date)
// Handle 'yesterday' and 'today', using midnight
d.startsWith("year") -> Calendar.getInstance().apply {
add(Calendar.DAY_OF_MONTH, -1) // yesterday
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}.timeInMillis
d.startsWith("today") -> Calendar.getInstance().apply { WordSet("today").startsWith(d) -> {
Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, 0) set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0) set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0) set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0) set(Calendar.MILLISECOND, 0)
}.timeInMillis }.timeInMillis
}
date.contains(Regex("""\d(st|nd|rd|th)""")) -> date.split(" ").map { date.contains(Regex("""\d(st|nd|rd|th)""")) -> date.split(" ").map {
if (it.contains(Regex("""\d\D\D"""))) { if (it.contains(Regex("""\d\D\D"""))) {
@ -287,45 +275,22 @@ internal abstract class ZMangaParser(
} }
} }
// Parses dates in this form:
// 21 hours ago
private fun parseRelativeDate(date: String): Long { private fun parseRelativeDate(date: String): Long {
val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0 val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0
val cal = Calendar.getInstance() val cal = Calendar.getInstance()
return when { return when {
WordSet( WordSet("second")
"day", .anyWordIn(date) -> cal.apply { add(Calendar.SECOND, -number) }.timeInMillis
"days", WordSet("min", "minute", "minutes")
).anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis .anyWordIn(date) -> cal.apply { add(Calendar.MINUTE, -number) }.timeInMillis
WordSet("hour", "hours", "h")
WordSet("hour", "hours", "h").anyWordIn(date) -> cal.apply { .anyWordIn(date) -> cal.apply { add(Calendar.HOUR, -number) }.timeInMillis
add( WordSet("day", "days").anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
Calendar.HOUR, WordSet("month", "months")
-number, .anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
) WordSet("year")
}.timeInMillis .anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis
WordSet(
"min",
"minute",
"minutes",
).anyWordIn(date) -> cal.apply {
add(
Calendar.MINUTE,
-number,
)
}.timeInMillis
WordSet("second").anyWordIn(date) -> cal.apply {
add(
Calendar.SECOND,
-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 else -> 0
} }
} }

Loading…
Cancel
Save