Merge branch 'KotatsuApp:master' into master

pull/421/head
Deivid Gabriel Pereira de Oliveira 2 years ago committed by GitHub
commit dd0c6ff620
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

3
.gitignore vendored

@ -29,6 +29,7 @@
.idea/compiler.xml
.idea/jarRepositories.xml
.idea/modules.xml
.idea/ktlint-plugin.xml
.idea/*.iml
.idea/modules
*.iml
@ -76,4 +77,4 @@ build/
.idea/codeStyles/
src/test/resources/cookies.txt
local.properties
local.properties

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KotlinJpsPluginSettings">
<option name="version" value="1.9.20" />
<option name="version" value="1.9.22" />
</component>
</project>

@ -2,8 +2,8 @@ import tasks.ReportGenerateTask
plugins {
id 'java-library'
id 'org.jetbrains.kotlin.jvm' version '1.9.20'
id 'com.google.devtools.ksp' version '1.9.20-1.0.14'
id 'org.jetbrains.kotlin.jvm' version '1.9.22'
id 'com.google.devtools.ksp' version '1.9.22-1.0.16'
id 'maven-publish'
}
@ -55,16 +55,16 @@ afterEvaluate {
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3'
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'com.squareup.okio:okio:3.6.0'
api 'org.jsoup:jsoup:1.16.2'
implementation 'com.squareup.okio:okio:3.7.0'
api 'org.jsoup:jsoup:1.17.2'
implementation 'org.json:json:20231013'
implementation 'androidx.collection:collection-ktx:1.3.0'
ksp project(':kotatsu-parsers-ksp')
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.0'
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.0'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.0'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.1'
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.1'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.1'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3'
testImplementation 'io.webfolder:quickjs:1.1.0'
}

@ -3,5 +3,5 @@ plugins {
}
dependencies {
implementation 'com.google.devtools.ksp:symbol-processing-api:1.9.20-1.0.14'
implementation 'com.google.devtools.ksp:symbol-processing-api:1.9.22-1.0.16'
}

@ -4,6 +4,8 @@ object ErrorMessages {
const val FILTER_MULTIPLE_STATES_NOT_SUPPORTED = "Multiple states are not supported by this source"
const val FILTER_MULTIPLE_GENRES_NOT_SUPPORTED = "Multiple genres are not supported by this source"
const val FILTER_MULTIPLE_CONTENT_RATING_NOT_SUPPORTED =
"Multiple Content Rating are not supported by this source"
const val FILTER_BOTH_LOCALE_GENRES_NOT_SUPPORTED =
"Filtering by both genres and locale is not supported by this source"
const val FILTER_BOTH_STATES_GENRES_NOT_SUPPORTED =

@ -33,11 +33,25 @@ abstract class MangaParser @InternalParsersApi constructor(
open val availableStates: Set<MangaState>
get() = emptySet()
open val availableContentRating: Set<ContentRating>
get() = emptySet()
/**
* Whether parser supports filtering by more than one tag
*/
open val isMultipleTagsSupported: Boolean = true
/**
* Whether parser supports tagsExclude field in filter
*/
open val isTagsExclusionSupported: Boolean = false
/**
* Whether parser supports searching by string query using [MangaListFilter.Search]
*/
open val isSearchSupported: Boolean = true
@Deprecated(
message = "Use availableSortOrders instead",
replaceWith = ReplaceWith("availableSortOrders"),
@ -95,6 +109,7 @@ abstract class MangaParser @InternalParsersApi constructor(
offset: Int,
query: String?,
tags: Set<MangaTag>?,
tagsExclude: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> = throw NotImplementedError("Please implement getList(offset, filter) instead")
@ -130,18 +145,51 @@ abstract class MangaParser @InternalParsersApi constructor(
"org.koitharu.kotatsu.parsers.model.MangaListFilter",
),
)
open suspend fun getList(offset: Int, tags: Set<MangaTag>?, sortOrder: SortOrder?): List<Manga> {
open suspend fun getList(
offset: Int,
tags: Set<MangaTag>?,
tagsExclude: Set<MangaTag>?,
sortOrder: SortOrder?,
): List<Manga> {
return getList(
offset,
MangaListFilter.Advanced(sortOrder ?: defaultSortOrder, tags.orEmpty(), null, emptySet()),
MangaListFilter.Advanced(
sortOrder = sortOrder ?: defaultSortOrder,
tags = tags.orEmpty(),
tagsExclude = tagsExclude.orEmpty(),
locale = null,
states = emptySet(),
contentRating = emptySet(),
),
)
}
@Suppress("DEPRECATION")
open suspend fun getList(offset: Int, filter: MangaListFilter?): List<Manga> {
return when (filter) {
is MangaListFilter.Advanced -> getList(offset, null, filter.tags, filter.sortOrder)
is MangaListFilter.Search -> getList(offset, filter.query, null, defaultSortOrder)
null -> getList(offset, null, null, defaultSortOrder)
is MangaListFilter.Advanced -> getList(
offset = offset,
query = null,
tags = filter.tags,
tagsExclude = filter.tagsExclude,
sortOrder = filter.sortOrder,
)
is MangaListFilter.Search -> getList(
offset = offset,
query = filter.query,
tags = null,
tagsExclude = null,
sortOrder = defaultSortOrder,
)
null -> getList(
offset = offset,
query = null,
tags = null,
tagsExclude = null,
sortOrder = defaultSortOrder,
)
}
}

@ -36,6 +36,7 @@ abstract class PagedMangaParser(
offset: Int,
query: String?,
tags: Set<MangaTag>?,
tagsExclude: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> = throw UnsupportedOperationException("You should use getListPage for PagedMangaParser")
@ -43,14 +44,35 @@ abstract class PagedMangaParser(
page: Int,
query: String?,
tags: Set<MangaTag>?,
tagsExclude: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> = throw NotImplementedError("Please implement getListPage(page, filter) instead")
open suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
return when (filter) {
is MangaListFilter.Advanced -> getListPage(page, null, filter.tags, filter.sortOrder)
is MangaListFilter.Search -> getListPage(page, filter.query, null, defaultSortOrder)
null -> getListPage(page, null, null, defaultSortOrder)
is MangaListFilter.Advanced -> getListPage(
page = page,
query = null,
tags = filter.tags,
tagsExclude = filter.tagsExclude,
sortOrder = filter.sortOrder,
)
is MangaListFilter.Search -> getListPage(
page = page,
query = filter.query,
tags = null,
tagsExclude = null,
sortOrder = defaultSortOrder,
)
null -> getListPage(
page = page,
query = null,
tags = null,
tagsExclude = null,
sortOrder = defaultSortOrder,
)
}
}

@ -0,0 +1,7 @@
package org.koitharu.kotatsu.parsers.model
enum class ContentRating {
SAFE,
SUGGESTIVE,
ADULT
}

@ -1,5 +1,6 @@
package org.koitharu.kotatsu.parsers.model
import org.koitharu.kotatsu.parsers.MangaParser
import java.util.*
sealed interface MangaListFilter {
@ -8,6 +9,16 @@ sealed interface MangaListFilter {
val sortOrder: SortOrder?
fun isValid(parser: MangaParser): Boolean = when (this) {
is Advanced -> (sortOrder in parser.availableSortOrders) &&
(tags.size <= 1 || parser.isMultipleTagsSupported) &&
(tagsExclude.isEmpty() || parser.isTagsExclusionSupported) &&
(contentRating.isEmpty() || parser.availableContentRating.containsAll(contentRating)) &&
(states.isEmpty() || parser.availableStates.containsAll(states))
is Search -> parser.isSearchSupported
}
data class Search(
@JvmField val query: String,
) : MangaListFilter {
@ -20,10 +31,63 @@ sealed interface MangaListFilter {
data class Advanced(
override val sortOrder: SortOrder,
@JvmField val tags: Set<MangaTag>,
@JvmField val tagsExclude: Set<MangaTag>,
@JvmField val locale: Locale?,
@JvmField val states: Set<MangaState>,
@JvmField val contentRating: Set<ContentRating>,
) : MangaListFilter {
override fun isEmpty(): Boolean = tags.isEmpty() && locale == null && states.isEmpty()
override fun isEmpty(): Boolean =
tags.isEmpty() && tagsExclude.isEmpty() && locale == null && states.isEmpty() && contentRating.isEmpty()
fun newBuilder() = Builder(sortOrder)
.tags(tags)
.tagsExclude(tagsExclude)
.locale(locale)
.states(states)
.contentRatings(contentRating)
class Builder(sortOrder: SortOrder) {
private var _sortOrder: SortOrder = sortOrder
private var _tags: Set<MangaTag>? = null
private var _tagsExclude: Set<MangaTag>? = null
private var _locale: Locale? = null
private var _states: Set<MangaState>? = null
private var _contentRating: Set<ContentRating>? = null
fun sortOrder(order: SortOrder) = apply {
_sortOrder = order
}
fun tags(tags: Set<MangaTag>?) = apply {
_tags = tags
}
fun tagsExclude(tags: Set<MangaTag>?) = apply {
_tagsExclude = tags
}
fun locale(locale: Locale?) = apply {
_locale = locale
}
fun states(states: Set<MangaState>?) = apply {
_states = states
}
fun contentRatings(rating: Set<ContentRating>?) = apply {
_contentRating = rating
}
fun build() = Advanced(
sortOrder = _sortOrder,
tags = _tags.orEmpty(),
tagsExclude = _tagsExclude.orEmpty(),
locale = _locale,
states = _states.orEmpty(),
contentRating = _contentRating.orEmpty(),
)
}
}
}

@ -1,5 +1,5 @@
package org.koitharu.kotatsu.parsers.model
enum class MangaState {
ONGOING, FINISHED, ABANDONED, PAUSED
ONGOING, FINISHED, ABANDONED, PAUSED, UPCOMING
}

@ -5,5 +5,6 @@ enum class SortOrder {
POPULARITY,
RATING,
NEWEST,
ALPHABETICAL
}
ALPHABETICAL,
ALPHABETICAL_DESC
}

@ -35,6 +35,10 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
override val isTagsExclusionSupported = true
override val availableContentRating: Set<ContentRating> = EnumSet.of(ContentRating.SAFE)
override val configKeyDomain = ConfigKey.Domain(
"bato.to",
"batocomic.com",
@ -93,6 +97,7 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
MangaState.FINISHED -> "completed"
MangaState.ABANDONED -> "cancelled"
MangaState.PAUSED -> "hiatus"
MangaState.UPCOMING -> "pending"
},
)
}
@ -102,13 +107,29 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
append(it.language)
}
append("&genres=")
if (filter.tags.isNotEmpty()) {
append("&genres=")
appendAll(filter.tags, ",") { it.key }
}
append("|")
if (filter.tagsExclude.isNotEmpty()) {
appendAll(filter.tagsExclude, ",") { it.key }
}
if (filter.contentRating.isNotEmpty()) {
filter.contentRating.oneOrThrowIfMany()?.let {
append(
when (it) {
ContentRating.SAFE -> append(",gore,bloody,violence,ecchi,adult,mature,smut,hentai")
else -> append("")
},
)
}
}
append("&page=")
append(page)
append(page.toString())
}
return parseList(url, page)
@ -120,7 +141,7 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
append(domain)
append("/browse?sort=update.za")
append("&page=")
append(page)
append(page.toString())
}
return parseList(url, page)
}

@ -31,7 +31,8 @@ internal class ComickFunParser(context: MangaLoaderContext) : PagedMangaParser(c
SortOrder.RATING,
)
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
override val availableStates: Set<MangaState> =
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED)
@Volatile
private var cachedTags: SparseArrayCompat<MangaTag>? = null
@ -75,6 +76,7 @@ internal class ComickFunParser(context: MangaLoaderContext) : PagedMangaParser(c
MangaState.FINISHED -> "2"
MangaState.ABANDONED -> "3"
MangaState.PAUSED -> "4"
else -> ""
},
)
}
@ -117,8 +119,7 @@ internal class ComickFunParser(context: MangaLoaderContext) : PagedMangaParser(c
var alt = ""
comic.getJSONArray("md_titles").mapJSON { alt += it.getString("title") + " - " }
return manga.copy(
title = comic.getString("title"),
altTitle = alt,
altTitle = alt.ifEmpty { comic.getStringOrNull("title") },
isNsfw = jo.getBoolean("matureContent") || comic.getBoolean("hentai"),
description = comic.getStringOrNull("parsed") ?: comic.getStringOrNull("desc"),
tags = manga.tags + comic.getJSONArray("md_comic_md_genres").mapJSONToSet {
@ -186,7 +187,7 @@ internal class ComickFunParser(context: MangaLoaderContext) : PagedMangaParser(c
val chap = jo.getStringOrNull("chap")
val locale = Locale.forLanguageTag(jo.getString("lang"))
val group = jo.optJSONArray("group_name")?.joinToString(", ")
val branch = locale.getDisplayName(locale).toTitleCase(locale) + group
val branch = locale.getDisplayName(locale).toTitleCase(locale) + " (" + group + ")"
MangaChapter(
id = generateUid(jo.getLong("id")),
name = buildString {

@ -1,11 +1,11 @@
package org.koitharu.kotatsu.parsers.site.all
import androidx.collection.ArrayMap
import androidx.collection.ArraySet
import androidx.collection.SparseArrayCompat
import androidx.collection.set
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.jsoup.internal.StringUtil
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
@ -26,9 +26,8 @@ internal class ExHentaiParser(
context: MangaLoaderContext,
) : PagedMangaParser(context, MangaSource.EXHENTAI, pageSize = 25), MangaParserAuthProvider {
override val availableSortOrders: Set<SortOrder> = Collections.singleton(
SortOrder.NEWEST,
)
override val availableSortOrders: Set<SortOrder> = setOf(SortOrder.NEWEST)
override val isTagsExclusionSupported: Boolean = true
override val configKeyDomain: ConfigKey.Domain
get() = ConfigKey.Domain(
@ -44,6 +43,7 @@ internal class ExHentaiParser(
private var updateDm = false
private val nextPages = SparseArrayCompat<Long>()
private val suspiciousContentKey = ConfigKey.ShowSuspiciousContent(false)
private val tagsMap = SuspendLazy(::fetchTags)
override val isAuthorized: Boolean
get() {
@ -93,30 +93,25 @@ internal class ExHentaiParser(
is MangaListFilter.Advanced -> {
append("&f_search=")
var fCats = 0
if (filter.tags.isNotEmpty()) {
filter.tags.forEach {
if (it.title.startsWith("- ")) {
it.key.toIntOrNull()?.let { fCats = fCats or it } ?: run {
search += it.key + " "
}
} else {
append(" tag:".urlEncoded())
append(it.key)
}
}
filter.toSearchQuery()?.let { sq ->
append("&f_search=")
append(sq.urlEncoded())
}
if (filter.locale != null) {
append(" language:".urlEncoded())
append(filter.locale.toLanguagePath())
val catsOn = filter.tags.mapNotNullToSet { it.key.toIntOrNull() }
val catsOff = filter.tagsExclude.mapNotNullToSet { it.key.toIntOrNull() }
if (catsOff.size >= 10) {
return emptyList()
}
var fCats = catsOn.fold(0, Int::or)
if (fCats != 0) {
fCats = 1023 - fCats
}
fCats = catsOff.fold(fCats, Int::or)
if (fCats != 0) {
append("&f_cats=")
append(1023 - fCats)
append(fCats)
}
}
@ -182,15 +177,14 @@ internal class ExHentaiParser(
val title = root.getElementById("gd2")
val tagList = root.getElementById("taglist")
val tabs = doc.body().selectFirst("table.ptt")?.selectFirst("tr")
val lang =
root.getElementById("gd3")?.selectFirst("tr:contains(Language)")?.selectFirst(".gdt2")?.text() ?: "Unknown"
val lang = root.getElementById("gd3")
?.selectFirst("tr:contains(Language)")
?.selectFirst(".gdt2")?.ownTextOrNull()
val tagMap = getOrCreateTagMap()
val tagF =
tagList?.selectFirst("tr:contains(female:)")?.select("a")?.mapNotNullToSet { tagMap[it.text()] }.orEmpty()
val tagM =
tagList?.selectFirst("tr:contains(male:)")?.select("a")?.mapNotNullToSet { tagMap[it.text()] }.orEmpty()
val tags = tagF + tagM
val tagMap = tagsMap.get()
val tags = ArraySet<MangaTag>()
tagList?.selectFirst("tr:contains(female:)")?.select("a")?.mapNotNullTo(tags) { tagMap[it.text()] }
tagList?.selectFirst("tr:contains(male:)")?.select("a")?.mapNotNullTo(tags) { tagMap[it.text()] }
return manga.copy(
title = title?.getElementById("gn")?.text()?.cleanupTitle() ?: manga.title,
@ -265,21 +259,17 @@ internal class ExHentaiParser(
"unusual pupils,urination,vore,vtuber,widow,wings,witch,wolf girl,x-ray,yuri,zombie,sole male,males only,yaoi," +
"tomgirl,tall man,oni,shotacon,prostate massage,policeman,males only,huge penis,fox boy,feminization,dog boy,dickgirl on male,big penis"
private var tagCache: ArrayMap<String, MangaTag>? = null
private val mutex = Mutex()
override suspend fun getAvailableTags(): Set<MangaTag> {
return getOrCreateTagMap().values.toSet()
return tagsMap.get().values.toSet()
}
protected suspend fun getOrCreateTagMap(): Map<String, MangaTag> = mutex.withLock {
tagCache?.let { return@withLock it }
private suspend fun fetchTags(): Map<String, MangaTag> {
val tagMap = ArrayMap<String, MangaTag>()
val tagElements = tags.split(",")
for (el in tagElements) {
if (el.isEmpty()) continue
tagMap[el] = MangaTag(
title = el,
title = el.toTitleCase(Locale.ENGLISH),
key = el,
source = source,
)
@ -289,16 +279,14 @@ internal class ExHentaiParser(
val root = doc.body().requireElementById("searchbox").selectFirstOrThrow("table")
root.select("div.cs").mapNotNullToSet { div ->
val id = div.id().substringAfterLast('_').toIntOrNull() ?: return@mapNotNullToSet null
val name = "- " + div.text().toTitleCase()
val name = div.text().toTitleCase(Locale.ENGLISH)
tagMap[name] = MangaTag(
title = name,
title = "Kind: $name",
key = id.toString(),
source = source,
)
}
tagCache = tagMap
return@withLock tagMap
return tagMap
}
override suspend fun getAvailableLocales(): Set<Locale> = setOf(
@ -403,4 +391,32 @@ internal class ExHentaiParser(
?.queryParameter("next")
?.toLongOrNull() ?: 1
}
private fun MangaListFilter.Advanced.toSearchQuery(): String? {
val joiner = StringUtil.StringJoiner(" ")
for (tag in tags) {
if (tag.key.isNumeric()) {
continue
}
joiner.add("tag:\"")
joiner.append(tag.key)
joiner.append("\"$")
}
for (tag in tagsExclude) {
if (tag.key.isNumeric()) {
continue
}
joiner.add("-tag:\"")
joiner.append(tag.key)
joiner.append("\"$")
}
locale?.let { lc ->
joiner.add("language:\"")
joiner.append(lc.toLanguagePath())
joiner.append("\"$")
}
return joiner.complete().takeUnless { it.isEmpty() }
}
private fun String.isNumeric() = all { c -> c.isDigit() }
}

@ -21,8 +21,6 @@ private const val CHAPTERS_FIRST_PAGE_SIZE = 120
private const val CHAPTERS_MAX_PAGE_SIZE = 500
private const val CHAPTERS_PARALLELISM = 3
private const val CHAPTERS_MAX_COUNT = 10_000 // strange api behavior, looks like a bug
private const val CONTENT_RATING =
"contentRating[]=safe&contentRating[]=suggestive&contentRating[]=erotica&contentRating[]=pornographic"
private const val LOCALE_FALLBACK = "en"
@MangaSourceParser("MANGADEX", "MangaDex")
@ -32,7 +30,12 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
override val availableSortOrders: EnumSet<SortOrder> = EnumSet.allOf(SortOrder::class.java)
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
override val availableContentRating: Set<ContentRating> = EnumSet.allOf(ContentRating::class.java)
override val availableStates: Set<MangaState> =
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED)
override val isTagsExclusionSupported = true
override suspend fun getList(offset: Int, filter: MangaListFilter?): List<Manga> {
@ -44,28 +47,41 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
append(PAGE_SIZE)
append("&offset=")
append(offset)
append("&includes[]=cover_art&includes[]=author&includes[]=artist&")
append("&includes[]=cover_art&includes[]=author&includes[]=artist")
when (filter) {
is MangaListFilter.Search -> {
append("title=")
append("&title=")
append(filter.query)
append('&')
}
is MangaListFilter.Advanced -> {
filter.tags.forEach { tag ->
append("includedTags[]=")
append(tag.key)
append('&')
filter.tags.forEach {
append("&includedTags[]=")
append(it.key)
}
filter.tagsExclude.forEach {
append("&excludedTags[]=")
append(it.key)
}
if (filter.contentRating.isNotEmpty()) {
filter.contentRating.forEach {
when (it) {
ContentRating.SAFE -> append("&contentRating[]=safe")
ContentRating.SUGGESTIVE -> append("&contentRating[]=suggestive&contentRating[]=erotica")
ContentRating.ADULT -> append("&contentRating[]=pornographic")
}
}
}
append(CONTENT_RATING)
append("&order")
append(
when (filter.sortOrder) {
SortOrder.UPDATED -> "[latestUploadedChapter]=desc"
SortOrder.RATING -> "[rating]=desc"
SortOrder.ALPHABETICAL -> "[title]=asc"
SortOrder.ALPHABETICAL_DESC -> "[title]=desc"
SortOrder.NEWEST -> "[createdAt]=desc"
SortOrder.POPULARITY -> "[followedCount]=desc"
},
@ -77,6 +93,7 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
MangaState.FINISHED -> append("completed")
MangaState.ABANDONED -> append("cancelled")
MangaState.PAUSED -> append("hiatus")
else -> append("")
}
}
filter.locale?.let {
@ -248,8 +265,7 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
append(limitedLimit)
append("&includes[]=scanlation_group&order[volume]=asc&order[chapter]=asc&offset=")
append(offset)
append('&')
append(CONTENT_RATING)
append("&contentRating[]=safe&contentRating[]=suggestive&contentRating[]=erotica&contentRating[]=pornographic")
}
val json = webClient.httpGet(url).parseJson()
if (json.getString("result") == "ok") {

@ -20,6 +20,10 @@ internal class MangaPark(context: MangaLoaderContext) :
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
override val availableContentRating: Set<ContentRating> = EnumSet.of(ContentRating.SAFE)
override val isTagsExclusionSupported = true
override val configKeyDomain = ConfigKey.Domain("mangapark.net")
private val tagsMap = SuspendLazy(::parseTags)
@ -42,9 +46,25 @@ internal class MangaPark(context: MangaLoaderContext) :
is MangaListFilter.Advanced -> {
append("&genres=")
if (filter.tags.isNotEmpty()) {
append("&genres=")
append(filter.tags.joinToString(",") { it.key })
appendAll(filter.tags, ",") { it.key }
}
append("|")
if (filter.tagsExclude.isNotEmpty()) {
appendAll(filter.tagsExclude, ",") { it.key }
}
if (filter.contentRating.isNotEmpty()) {
filter.contentRating.oneOrThrowIfMany()?.let {
append(
when (it) {
ContentRating.SAFE -> append(",gore,bloody,violence,ecchi,adult,mature,smut,hentai")
else -> append("")
},
)
}
}
filter.states.oneOrThrowIfMany()?.let {
@ -55,6 +75,7 @@ internal class MangaPark(context: MangaLoaderContext) :
MangaState.FINISHED -> "completed"
MangaState.PAUSED -> "hiatus"
MangaState.ABANDONED -> "cancelled"
MangaState.UPCOMING -> "pending"
},
)
}
@ -67,6 +88,7 @@ internal class MangaPark(context: MangaLoaderContext) :
SortOrder.NEWEST -> "field_create"
SortOrder.ALPHABETICAL -> "field_name"
SortOrder.RATING -> "field_score"
else -> ""
},
)

@ -9,30 +9,14 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.PagedMangaParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.SuspendLazy
import org.koitharu.kotatsu.parsers.util.domain
import org.koitharu.kotatsu.parsers.util.generateUid
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.*
import org.koitharu.kotatsu.parsers.util.json.getStringOrNull
import org.koitharu.kotatsu.parsers.util.json.mapJSON
import org.koitharu.kotatsu.parsers.util.json.mapJSONToSet
import org.koitharu.kotatsu.parsers.util.json.toJSONList
import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.oneOrThrowIfMany
import org.koitharu.kotatsu.parsers.util.parseHtml
import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl
import org.koitharu.kotatsu.parsers.util.toCamelCase
import java.text.SimpleDateFormat
import java.util.EnumSet
import java.util.Locale
import java.util.*
@MangaSourceParser("NINENINENINEHENTAI", "999Hentai", type = ContentType.HENTAI)
internal class NineNineNineHentaiParser(context: MangaLoaderContext) :

@ -25,6 +25,7 @@ internal class PapScan(context: MangaLoaderContext) :
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.POPULARITY,
SortOrder.ALPHABETICAL,
SortOrder.ALPHABETICAL_DESC,
)
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
@ -50,7 +51,8 @@ internal class PapScan(context: MangaLoaderContext) :
append("&sortBy=")
when (filter.sortOrder) {
SortOrder.POPULARITY -> append("views")
SortOrder.ALPHABETICAL -> append("name")
SortOrder.ALPHABETICAL_DESC -> append("name&asc=false")
SortOrder.ALPHABETICAL -> append("name&asc=true")
else -> append("updated")
}

@ -7,4 +7,4 @@ import org.koitharu.kotatsu.parsers.site.animebootstrap.AnimeBootstrapParser
@MangaSourceParser("KOMIKZOID", "KomikzoId", "id")
internal class KomikzoId(context: MangaLoaderContext) :
AnimeBootstrapParser(context, MangaSource.KOMIKZOID, "komikzoid.xyz")
AnimeBootstrapParser(context, MangaSource.KOMIKZOID, "komikzoid.id")

@ -59,6 +59,7 @@ internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context
MangaState.FINISHED -> "completed"
MangaState.ABANDONED -> "droped"
MangaState.PAUSED -> "onhold"
MangaState.UPCOMING -> "soon"
},
)
}

@ -20,7 +20,7 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex
override val availableStates: Set<MangaState> =
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED)
override val configKeyDomain = ConfigKey.Domain("team11x11.com")
override val configKeyDomain = ConfigKey.Domain("team11x11.fun")
override val isMultipleTagsSupported = false
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {

@ -31,6 +31,7 @@ internal class AnibelParser(context: MangaLoaderContext) : MangaParser(context,
offset: Int,
query: String?,
tags: Set<MangaTag>?,
tagsExclude: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
if (!query.isNullOrEmpty()) {

@ -19,6 +19,7 @@ internal class FlixScansOrg(context: MangaLoaderContext) : PagedMangaParser(cont
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
override val configKeyDomain = ConfigKey.Domain("flixscans.org")
override val isSearchSupported = false
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {

@ -68,6 +68,7 @@ class Manhwa18Parser(context: MangaLoaderContext) :
append(
when (filter.sortOrder) {
SortOrder.ALPHABETICAL -> "az"
SortOrder.ALPHABETICAL_DESC -> "za"
SortOrder.POPULARITY -> "top"
SortOrder.UPDATED -> "update"
SortOrder.NEWEST -> "new"

@ -20,7 +20,9 @@ internal class ReaperComics(context: MangaLoaderContext) :
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.ALPHABETICAL)
override val configKeyDomain = ConfigKey.Domain("reapercomics.com")
override val configKeyDomain = ConfigKey.Domain("reaperscans.com")
override val isSearchSupported = false
override val headers: Headers = Headers.Builder()
.add("User-Agent", UserAgents.CHROME_DESKTOP)

@ -21,6 +21,8 @@ internal class TempleScanEsp(context: MangaLoaderContext) :
override val configKeyDomain = ConfigKey.Domain("templescanesp.net")
override val isSearchSupported = false
override val headers: Headers = Headers.Builder()
.add("User-Agent", UserAgents.CHROME_DESKTOP)
.build()

@ -26,6 +26,7 @@ class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser(
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.ALPHABETICAL,
SortOrder.ALPHABETICAL_DESC,
SortOrder.UPDATED,
SortOrder.NEWEST,
SortOrder.POPULARITY,
@ -48,15 +49,14 @@ class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser(
append("?order_item=")
append(
when (filter.sortOrder) {
SortOrder.POPULARITY -> "likes_count"
SortOrder.UPDATED -> "release_date"
SortOrder.NEWEST -> "creation"
SortOrder.ALPHABETICAL -> "alphabetically"
SortOrder.RATING -> "score"
SortOrder.POPULARITY -> "likes_count&order_dir=desc"
SortOrder.UPDATED -> "release_date&order_dir=desc"
SortOrder.NEWEST -> "creation&order_dir=desc"
SortOrder.ALPHABETICAL -> "alphabetically&order_dir=asc"
SortOrder.ALPHABETICAL_DESC -> "alphabetically&order_dir=desc"
SortOrder.RATING -> "score&order_dir=desc"
},
)
append("&order_dir=desc")
append("&filter_by=title")
if (filter.tags.isNotEmpty()) {
for (tag in filter.tags) {

@ -25,6 +25,7 @@ internal abstract class FmreaderParser(
SortOrder.UPDATED,
SortOrder.POPULARITY,
SortOrder.ALPHABETICAL,
SortOrder.ALPHABETICAL_DESC,
)
override val availableStates: Set<MangaState> = EnumSet.of(
@ -85,7 +86,8 @@ internal abstract class FmreaderParser(
when (filter.sortOrder) {
SortOrder.POPULARITY -> append("views")
SortOrder.UPDATED -> append("last_update")
SortOrder.ALPHABETICAL -> append("name")
SortOrder.ALPHABETICAL -> append("name&sort_type=ASC")
SortOrder.ALPHABETICAL_DESC -> append("name&sort_type=DESC")
else -> append("last_update")
}

@ -48,6 +48,7 @@ internal class Manhwa18Com(context: MangaLoaderContext) :
append(
when (filter.sortOrder) {
SortOrder.ALPHABETICAL -> "az"
SortOrder.ALPHABETICAL_DESC -> "za"
SortOrder.POPULARITY -> "top"
SortOrder.UPDATED -> "update"
SortOrder.NEWEST -> "new"

@ -44,7 +44,8 @@ internal class OlimpoScans(context: MangaLoaderContext) :
when (filter.sortOrder) {
SortOrder.POPULARITY -> append("views")
SortOrder.UPDATED -> append("last_update")
SortOrder.ALPHABETICAL -> append("name")
SortOrder.ALPHABETICAL -> append("name&sort_type=ASC")
SortOrder.ALPHABETICAL_DESC -> append("name&sort_type=DESC")
else -> append("last_update")
}
}

@ -24,11 +24,15 @@ internal class BentomangaParser(context: MangaLoaderContext) : PagedMangaParser(
SortOrder.RATING,
SortOrder.NEWEST,
SortOrder.ALPHABETICAL,
SortOrder.ALPHABETICAL_DESC,
)
override val configKeyDomain = ConfigKey.Domain("bentomanga.com", "www.bentomanga.com")
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
override val availableStates: Set<MangaState> =
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED)
override val isTagsExclusionSupported = true
init {
paginator.firstPage = 0
@ -47,21 +51,34 @@ internal class BentomangaParser(context: MangaLoaderContext) : PagedMangaParser(
is MangaListFilter.Advanced -> {
url.addQueryParameter(
"order_by",
when (filter.sortOrder) {
SortOrder.UPDATED -> "update"
SortOrder.POPULARITY -> "views"
SortOrder.RATING -> "top"
SortOrder.NEWEST -> "create"
SortOrder.ALPHABETICAL -> "name"
},
)
when (filter.sortOrder) {
SortOrder.UPDATED -> url.addQueryParameter("order_by", "update")
.addQueryParameter("order", "desc")
SortOrder.POPULARITY -> url.addQueryParameter("order_by", "views")
.addQueryParameter("order", "desc")
SortOrder.RATING -> url.addQueryParameter("order_by", "top")
.addQueryParameter("order", "desc")
SortOrder.NEWEST -> url.addQueryParameter("order_by", "create")
.addQueryParameter("order", "desc")
SortOrder.ALPHABETICAL -> url.addQueryParameter("order_by", "name")
.addQueryParameter("order", "asc")
SortOrder.ALPHABETICAL_DESC -> url.addQueryParameter("order_by", "name")
.addQueryParameter("order", "desc")
}
if (filter.tags.isNotEmpty()) {
url.addQueryParameter("withCategories", filter.tags.joinToString(",") { it.key })
}
if (filter.tagsExclude.isNotEmpty()) {
url.addQueryParameter("withoutCategories", filter.tagsExclude.joinToString(",") { it.key })
}
filter.states.oneOrThrowIfMany()?.let {
url.addQueryParameter(
"state",
@ -70,6 +87,7 @@ internal class BentomangaParser(context: MangaLoaderContext) : PagedMangaParser(
MangaState.FINISHED -> "2"
MangaState.PAUSED -> "3"
MangaState.ABANDONED -> "5"
else -> "1"
},
)
}

@ -23,6 +23,8 @@ internal class FuryoSociety(context: MangaLoaderContext) :
override val configKeyDomain = ConfigKey.Domain("furyosociety.com")
override val isSearchSupported = false
override val headers: Headers = Headers.Builder()
.add("User-Agent", UserAgents.CHROME_DESKTOP)
.build()

@ -22,7 +22,10 @@ internal class LugnicaScans(context: MangaLoaderContext) : PagedMangaParser(cont
SortOrder.UPDATED,
)
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
override val availableStates: Set<MangaState> =
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED)
override val isSearchSupported = false
override val configKeyDomain = ConfigKey.Domain("lugnica-scans.com")
@ -68,6 +71,7 @@ internal class LugnicaScans(context: MangaLoaderContext) : PagedMangaParser(cont
MangaState.FINISHED -> append("1")
MangaState.PAUSED -> append("4")
MangaState.ABANDONED -> append("3")
else -> append("")
}
}

@ -182,8 +182,7 @@ internal class ScantradUnion(context: MangaLoaderContext) : PagedMangaParser(con
override suspend fun getAvailableTags(): Set<MangaTag> {
val doc = webClient.httpGet("https://$domain/").parseHtml()
val body = doc.body()
val root = body.select(".asp_gochosen")[1]
val list = root?.select("option").orEmpty()
val list = body.select(".asp_gochosen")[1].select("option").orEmpty()
return list.mapToSet { li ->
MangaTag(
key = li.text().lowercase().replace(" ", "-"),

@ -1,88 +0,0 @@
package org.koitharu.kotatsu.parsers.site.galleryadults.all
import org.koitharu.kotatsu.parsers.ErrorMessages
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.galleryadults.GalleryAdultsParser
import org.koitharu.kotatsu.parsers.util.*
import java.util.*
@MangaSourceParser("NHENTAIUK", "NHentai.uk", type = ContentType.HENTAI)
internal class NHentaiUk(context: MangaLoaderContext) :
GalleryAdultsParser(context, MangaSource.NHENTAIUK, "nhentai.uk", pageSize = 50) {
override val selectGallery = ".gallery"
override val selectGalleryLink = "a"
override val selectGalleryTitle = ".caption"
override val pathTagUrl = "/tags/popular?p="
override val selectTags = "#tag-container"
override val selectTag = "div.tag-container:contains(Tags:) span.tags"
override val selectAuthor = "div.tag-container:contains(Artists:) a"
override val selectLanguageChapter = "div.tag-container:contains(Languages:) a"
override val idImg = "image-container"
override suspend fun getAvailableLocales(): Set<Locale> = setOf(
Locale.ENGLISH,
Locale.FRENCH,
Locale.JAPANESE,
Locale.CHINESE,
Locale("es"),
Locale("ru"),
Locale("ko"),
Locale.GERMAN,
Locale("pt"),
Locale.ITALIAN,
Locale("tr"),
)
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
val url = buildString {
append("https://")
append(domain)
when (filter) {
is MangaListFilter.Search -> {
throw IllegalArgumentException(ErrorMessages.SEARCH_NOT_SUPPORTED)
}
is MangaListFilter.Advanced -> {
when {
filter.locale != null && filter.tags.isNotEmpty() -> {
throw IllegalArgumentException(ErrorMessages.FILTER_BOTH_LOCALE_GENRES_NOT_SUPPORTED)
}
filter.locale != null -> {
append("/language")
append(filter.locale.toLanguagePath())
append("/?p=")
}
filter.tags.isNotEmpty() -> {
filter.tags.oneOrThrowIfMany()?.let {
append("/tag/")
append(it.key)
}
append("/?p=")
}
else -> {
append("/home?p=")
}
}
}
null -> append("/?")
}
append(page.toString())
}
return parseMangaList(webClient.httpGet(url).parseHtml())
}
override suspend fun getPageUrl(page: MangaPage): String {
val doc = webClient.httpGet(page.url.toAbsoluteUrl(domain)).parseHtml()
val root = doc.body()
return root.requireElementById(idImg).selectFirstOrThrow("img").src() ?: root.parseFailed("Image src not found")
}
}

@ -27,7 +27,8 @@ internal abstract class HeanCms(
SortOrder.POPULARITY,
)
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
override val availableStates: Set<MangaState> =
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED)
override val headers: Headers = Headers.Builder()
.add("User-Agent", UserAgents.CHROME_DESKTOP)
@ -56,18 +57,19 @@ internal abstract class HeanCms(
MangaState.FINISHED -> "Completed"
MangaState.ABANDONED -> "Dropped"
MangaState.PAUSED -> "Hiatus"
else -> ""
},
)
}
append("&order=desc")
append("&orderBy=")
when (filter.sortOrder) {
SortOrder.POPULARITY -> append("total_views")
SortOrder.UPDATED -> append("latest")
SortOrder.NEWEST -> append("created_at")
SortOrder.ALPHABETICAL -> append("title")
else -> append("latest")
SortOrder.POPULARITY -> append("total_views&order=desc")
SortOrder.UPDATED -> append("latest&order=desc")
SortOrder.NEWEST -> append("created_at&order=desc")
SortOrder.ALPHABETICAL -> append("title&order=desc")
SortOrder.ALPHABETICAL_DESC -> append("title&order=asc")
else -> append("latest&order=desc")
}
append("&series_type=Comic&perPage=12")
append("&tags_ids=")

@ -33,18 +33,19 @@ internal class YugenMangasEs(context: MangaLoaderContext) :
MangaState.FINISHED -> "Completed"
MangaState.ABANDONED -> "Dropped"
MangaState.PAUSED -> "Hiatus"
else -> ""
},
)
}
append("&order=desc")
append("&orderBy=")
when (filter.sortOrder) {
SortOrder.POPULARITY -> append("total_views")
SortOrder.UPDATED -> append("latest")
SortOrder.NEWEST -> append("created_at")
SortOrder.ALPHABETICAL -> append("title")
else -> append("latest")
SortOrder.POPULARITY -> append("total_views&order=desc")
SortOrder.UPDATED -> append("latest&order=desc")
SortOrder.NEWEST -> append("created_at&order=desc")
SortOrder.ALPHABETICAL -> append("title&order=desc")
SortOrder.ALPHABETICAL_DESC -> append("title&order=asc")
else -> append("latest&order=desc")
}
append("&series_type=Comic&perPage=12")
append("&tags_ids=")

@ -23,6 +23,8 @@ internal abstract class HeanCmsAlt(
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
override val isSearchSupported = false
protected open val listUrl = "/comics"
protected open val datePattern = "MMMM d, yyyy"

@ -151,6 +151,15 @@ internal abstract class MadaraParser(
"En attente",
)
@JvmField
protected val upcoming: Set<String> = hashSetOf(
"Upcoming",
"upcoming",
"لم تُنشَر بعد",
"Prochainement",
"À venir",
)
// Change these values only if the site does not support manga listings via ajax
protected open val withoutAjax = false
@ -203,6 +212,7 @@ internal abstract class MadaraParser(
MangaState.FINISHED -> append("end")
MangaState.ABANDONED -> append("canceled")
MangaState.PAUSED -> append("on-hold")
MangaState.UPCOMING -> append("upcoming")
}
}
append("&")
@ -215,6 +225,7 @@ internal abstract class MadaraParser(
SortOrder.NEWEST -> append("new-manga")
SortOrder.ALPHABETICAL -> append("alphabet")
SortOrder.RATING -> append("rating")
else -> append("latest")
}
}
@ -255,6 +266,7 @@ internal abstract class MadaraParser(
}
SortOrder.RATING -> {}
else -> payload["vars[meta_key]"] = "_latest_update"
}
filter.states.forEach {
@ -264,6 +276,7 @@ internal abstract class MadaraParser(
MangaState.FINISHED -> "end"
MangaState.ABANDONED -> "canceled"
MangaState.PAUSED -> "on-hold"
MangaState.UPCOMING -> "upcoming"
}
}
}
@ -310,6 +323,7 @@ internal abstract class MadaraParser(
in finished -> MangaState.FINISHED
in abandoned -> MangaState.ABANDONED
in paused -> MangaState.PAUSED
in upcoming -> MangaState.UPCOMING
else -> null
},
source = source,

@ -1,13 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.ar
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
import java.util.Locale
@MangaSourceParser("AKUMANGA", "AkuManga", "ar")
internal class AkuManga(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.AKUMANGA, "akumanga.com") {
override val sourceLocale: Locale = Locale.ENGLISH
}

@ -1,12 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.ar
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("FALCONMANGA", "FalconManga", "ar")
internal class FalconManga(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.FALCONMANGA, "falconmanga.com") {
override val datePattern = "d MMMM، yyyy"
}

@ -1,13 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.ar
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("MANGALEKS", "MangaLeks", "ar")
internal class MangaLeks(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGALEKS, "mangaleks.com") {
override val datePattern = "yyyy/MM/dd"
override val postReq = true
}

@ -5,8 +5,8 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("SHADOWXMANGA", "Shadow X Manga", "ar")
@MangaSourceParser("SHADOWXMANGA", "ShadowXManga", "ar")
internal class ShadowxManga(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.SHADOWXMANGA, "shadowxmanga.com") {
MadaraParser(context, MangaSource.SHADOWXMANGA, "www.shadowxmanga.com") {
override val datePattern = "yyyy/MM/dd"
}

@ -0,0 +1,10 @@
package org.koitharu.kotatsu.parsers.site.madara.de
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("MANGALESEN", "MangaLesen", "de")
internal class MangaLesen(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGALESEN, "mangalesen.net")

@ -1,9 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("CM_READER", "CmReader", "en")
internal class CmReader(context: MangaLoaderContext) : MadaraParser(context, MangaSource.CM_READER, "cmreader.info")

@ -75,7 +75,7 @@ internal class FireScans(context: MangaLoaderContext) :
}
}
fun String.decodeHex(): ByteArray {
private fun String.decodeHex(): ByteArray {
check(length % 2 == 0) { "Must have an even length" }
return chunked(2).map { it.toInt(16).toByte() }.toByteArray()

@ -68,6 +68,7 @@ internal class Hentai4Free(context: MangaLoaderContext) :
SortOrder.NEWEST -> append("new-manga")
SortOrder.ALPHABETICAL -> append("alphabet")
SortOrder.RATING -> append("rating")
else -> append("latest")
}
}

@ -61,6 +61,7 @@ internal class IsekaiScanEuParser(context: MangaLoaderContext) :
MangaState.FINISHED -> append("end")
MangaState.ABANDONED -> append("canceled")
MangaState.PAUSED -> append("on-hold")
MangaState.UPCOMING -> append("upcoming")
}
}
append("&")
@ -73,6 +74,7 @@ internal class IsekaiScanEuParser(context: MangaLoaderContext) :
SortOrder.NEWEST -> append("new-manga")
SortOrder.ALPHABETICAL -> append("alphabet")
SortOrder.RATING -> append("rating")
else -> append("latest")
}
}

@ -1,10 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("JAIMINISBOX", "JaiminisBox", "en")
internal class Jaiminisbox(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.JAIMINISBOX, "jaiminisbox.net")

@ -5,6 +5,6 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("JIMANGA", "JiManga", "en")
@MangaSourceParser("JIMANGA", "S2Manga.io", "en")
internal class JiManga(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.JIMANGA, "jimanga.com")
MadaraParser(context, MangaSource.JIMANGA, "s2manga.io")

@ -1,12 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("MANGA_3S", "Manga3s", "en")
internal class Manga3s(context: MangaLoaderContext) : MadaraParser(context, MangaSource.MANGA_3S, "manga3s.com") {
override val tagPrefix = "manhwa-genre/"
override val datePattern = "MMMM dd, yyyy"
}

@ -1,12 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("MANGACV", "MangaCv", "en")
internal class MangaCv(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGACV, "mangacv.com", pageSize = 10) {
override val datePattern = "MMMM dd, yyyy"
}

@ -62,6 +62,7 @@ internal class MangaDass(context: MangaLoaderContext) :
SortOrder.NEWEST -> append("new-manga")
SortOrder.ALPHABETICAL -> append("alphabet")
SortOrder.RATING -> append("rating")
else -> append("latest")
}
}

@ -1,10 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("MANGA_ONLINE", "MangaOnline.team", "en")
internal class MangaOnline(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGA_ONLINE, "mangaonline.team", 18)

@ -5,8 +5,8 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("MANGAROSIE", "MangaRosie", "en")
@MangaSourceParser("MANGAROSIE", "Toon69", "en")
internal class MangaRosie(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGAROSIE, "mangarosie.in", pageSize = 16) {
MadaraParser(context, MangaSource.MANGAROSIE, "toon69.com", pageSize = 16) {
override val datePattern = "MMMM dd, yyyy"
}

@ -1,12 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("MANGATX", "MangaTx", "en")
internal class MangaTx(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGATX, "mangatx.com") {
override val datePattern = "MMMM dd, yyyy"
}

@ -5,6 +5,6 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("MANGATXUNOFFICIAL", "Manga-Tx.com", "en")
@MangaSourceParser("MANGATXUNOFFICIAL", "MangaEmpress", "en")
internal class MangaTxUnofficial(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGATXUNOFFICIAL, "manga-tx.com")
MadaraParser(context, MangaSource.MANGATXUNOFFICIAL, "mangaempress.com")

@ -1,11 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("MANGAGOYAOI", "MangaGoYaoi", "en", ContentType.HENTAI)
internal class Mangagoyaoi(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGAGOYAOI, "mangagoyaoi.com")

@ -1,12 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("MANGANELO", "MangaNelo.biz", "en")
internal class Manganelo(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGANELO, "manganelo.biz", 10) {
override val postReq = true
}

@ -1,10 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("MANGASTIC", "Mangastic", "en")
internal class Mangastic(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGASTIC, "mangastic.cc", 20)

@ -1,10 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("MANHUADEX", "ManhuaDex", "en")
internal class ManhuaDex(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANHUADEX, "manhuadex.com")

@ -1,13 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("MANHUAMIX", "ManhuaMix", "en")
internal class Manhuamix(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANHUAMIX, "manhuamix.com", 20) {
override val tagPrefix = "manhua-genre/"
override val listUrl = "manhua/"
}

@ -1,10 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("MANHWA2READ", "Manhwa2read", "en")
internal class Manhwa2Read(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANHWA2READ, "manhwa2read.com")

@ -1,10 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("STMANHWA", "1stManhwa", "en")
internal class StManhwa(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.STMANHWA, "1stmanhwa.com")

@ -1,12 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("STKISSMANGA_TV", "1stKissManga.tv", "en")
internal class StkissMangaTv(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.STKISSMANGA_TV, "1stkissmanga.tv", 20) {
override val postReq = true
}

@ -1,10 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("TOONILYNET", "Toonily.net", "en")
internal class ToonilyNet(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.TOONILYNET, "toonily.net")

@ -7,7 +7,7 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("TOPMANHUA", "TopManhua", "en")
internal class TopManhua(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.TOPMANHUA, "www.topmanhua.com") {
MadaraParser(context, MangaSource.TOPMANHUA, "topmanhua.com") {
override val tagPrefix = "manhua-genre/"
override val datePattern = "MM/dd/yyyy"
}

@ -5,6 +5,6 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("CONSEJODEMATONES", "ConsejoDeMatones", "es")
internal class ConsejoDeMatones(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.CONSEJODEMATONES, "www.consejodematones.xyz")
@MangaSourceParser("KENHUAV2SCANK", "Kenhuav2Scan", "es")
internal class Kenhuav2Scan(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.KENHUAV2SCANK, "kenhuav2scan.com")

@ -1,13 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.es
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("UNITOONOFICIAL", "UniToonOficial", "es")
internal class UniToonOficial(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.UNITOONOFICIAL, "unitoonoficial.com") {
override val datePattern = "dd/MM/yyyy"
override val tagPrefix = "generos/"
}

@ -7,6 +7,6 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("ASTRALMANGA", "AstralManga", "fr")
internal class AstralManga(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.ASTRALMANGA, "astral-manga.fr", pageSize = 12) {
MadaraParser(context, MangaSource.ASTRALMANGA, "astral-manga.fr") {
override val datePattern = "dd/MM/yyyy"
}

@ -1,14 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.fr
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("KARATCAMSCANS", "KaratcamScans", "fr")
internal class KaratcamScans(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.KARATCAMSCANS, "karatcam-scans.fr") {
override val tagPrefix = "webtoon-genre/"
override val listUrl = "webtoon/"
override val datePattern = "dd/MM/yyyy"
}

@ -1,15 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.id
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
import java.util.Locale
@MangaSourceParser("HWAGO", "Hwago", "id")
internal class Hwago(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.HWAGO, "hwago.id", 10) {
override val listUrl = "komik/"
override val tagPrefix = "genre/"
override val sourceLocale: Locale = Locale.ENGLISH
}

@ -1,14 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.id
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
import java.util.Locale
@MangaSourceParser("IMMORTALUPDATESID", "ImmortalUpdates", "id")
internal class ImmortalUpdatesId(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.IMMORTALUPDATESID, "immortalupdates.id") {
override val datePattern = "d MMMM yyyy"
override val sourceLocale: Locale = Locale.ENGLISH
}

@ -1,16 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.id
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
import java.util.Locale
@MangaSourceParser("KOMIKSA", "KomikSay", "id")
internal class Komiksay(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.KOMIKSA, "komiksay.site") {
override val tagPrefix = "komik-genre/"
override val listUrl = "komik/"
override val datePattern = "MMMM d"
override val sourceLocale: Locale = Locale.ENGLISH
}

@ -2,14 +2,24 @@ package org.koitharu.kotatsu.parsers.site.madara.id
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.network.UserAgents
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
import java.util.Locale
import java.util.*
@MangaSourceParser("SHINIGAMI", "Shinigami", "id")
internal class Shinigami(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.SHINIGAMI, "shinigami.moe", 10) {
private val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_MOBILE)
override val tagPrefix = "genre/"
override val listUrl = "series/"
override val sourceLocale: Locale = Locale.ENGLISH
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
super.onCreateConfig(keys)
keys.add(userAgentKey)
}
}

@ -5,9 +5,8 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("ZEROSCAN", "ZeroScan", "pt")
internal class ZeroScan(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.ZEROSCAN, "zeroscan.com.br") {
override val postReq = true
override val datePattern: String = "dd/MM/yyyy"
@MangaSourceParser("BURNINGSCANS", "BurningScans", "pt")
internal class BurningScans(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.BURNINGSCANS, "burningscans.com") {
override val datePattern = "dd/MM/yyyy"
}

@ -1,12 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.pt
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("LADYESTELARSCAN", "Lady Estelar Scan", "pt")
internal class LadyestelarScan(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.LADYESTELARSCAN, "ladyestelarscan.com.br", 10) {
override val datePattern: String = "dd/MM/yyyy"
}

@ -1,12 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.pt
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("VORTCESCAN", "VortceScan", "pt")
internal class VortceScan(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.VORTCESCAN, "vortcescan.com.br", pageSize = 10) {
override val datePattern: String = "d 'de' MMMMM 'de' yyyy"
}

@ -1,15 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.tr
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("CIZGIROMANARSIVI", "Cizgiromanarsivi", "tr")
internal class Cizgiromanarsivi(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.CIZGIROMANARSIVI, "cizgiromanarsivi.com", 24) {
override val stylePage = ""
override val tagPrefix = "kategori/"
override val listUrl = "seri/"
override val datePattern = "dd/MM/yyyy"
}

@ -7,7 +7,7 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("DOMALFANSB", "DomalFansub", "tr")
internal class DomalFansb(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.DOMALFANSB, "domalfansb.com") {
MadaraParser(context, MangaSource.DOMALFANSB, "domalfansub.com.tr") {
override val datePattern = "d MMMM yyyy"
override val tagPrefix = "manga-turleri/"
}

@ -7,5 +7,5 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("GHOSTFANSUB", "GhostFansub", "tr")
internal class GhostFansub(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.GHOSTFANSUB, "ghostfansub.online", 18)
MadaraParser(context, MangaSource.GHOSTFANSUB, "ghostfansub.co", 18)
// you now need to log in to access content

@ -1,10 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.tr
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("MANGADIYARI", "MangaDiyari", "tr")
internal class MangaDiyari(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGADIYARI, "manga-diyari.com", 10)

@ -7,6 +7,6 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("MANGAWOW", "MangaWow", "tr")
internal class MangaWow(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MANGAWOW, "mangawow.com", 18) {
MadaraParser(context, MangaSource.MANGAWOW, "mangawow.org", 18) {
override val datePattern = "d MMMM yyyy"
}

@ -6,6 +6,8 @@ import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("TONIZUTOON", "TonizuToon", "tr", ContentType.HENTAI)
@MangaSourceParser("TONIZUTOON", "ToniZu.com", "tr", ContentType.HENTAI)
internal class Tonizutoon(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.TONIZUTOON, "tonizutoon.com")
MadaraParser(context, MangaSource.TONIZUTOON, "tonizu.com") {
override val datePattern = "dd/mm/yyyy"
}

@ -1,10 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.tr
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("WEBTOONEVRENI", "WebtoonEvreni", "tr")
internal class Webtoonevreni(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.WEBTOONEVRENI, "webtoonevreni.net", 10)

@ -1,14 +0,0 @@
package org.koitharu.kotatsu.parsers.site.madara.vi
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("MI2MANGA", "Mi2Manga", "vi")
internal class Mi2Manga(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.MI2MANGA, "www.mi2manga2.com") {
override val listUrl = "truyen-tranh/"
override val tagPrefix = "the-loai/"
override val datePattern = "d MMMM, yyyy"
}

@ -9,9 +9,9 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
import org.koitharu.kotatsu.parsers.util.*
import java.util.EnumSet
@MangaSourceParser("SAYTRUYENHAY", "Saytruyenhay", "vi")
@MangaSourceParser("SAYTRUYENHAY", "PheTruyen", "vi")
internal class Saytruyenhay(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.SAYTRUYENHAY, "saytruyenhay.com", 40) {
MadaraParser(context, MangaSource.SAYTRUYENHAY, "phetruyen.pro", 40) {
override val tagPrefix = "genre/"
override val withoutAjax = true

@ -76,6 +76,7 @@ internal abstract class MadthemeParser(
SortOrder.ALPHABETICAL -> append("name") // On some sites without tags or searches, the alphabetical option is empty.
SortOrder.NEWEST -> append("created_at")
SortOrder.RATING -> append("rating")
else -> append("updated_at")
}
if (filter.tags.isNotEmpty()) {
filter.tags.forEach {
@ -196,7 +197,10 @@ internal abstract class MadthemeParser(
protected open suspend fun getChapters(doc: Document): List<MangaChapter> {
val dateFormat = SimpleDateFormat(datePattern, sourceLocale)
return doc.body().select(selectChapter).mapChapters(reversed = true) { i, li ->
val slug = doc.selectFirstOrThrow("script:containsData(bookSlug)").data().substringAfter("bookSlug = \"")
.substringBefore("\";")
val docChapter = webClient.httpGet("https://$domain/api/manga/$slug/chapters?source=detail").parseHtml()
return docChapter.select(selectChapter).mapChapters(reversed = true) { i, li ->
val a = li.selectFirstOrThrow("a")
val href = a.attrAsRelativeUrl("href")
val dateText = li.selectFirst(selectDate)?.text()

@ -1,22 +1,51 @@
package org.koitharu.kotatsu.parsers.site.madtheme.en
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.madtheme.MadthemeParser
import org.koitharu.kotatsu.parsers.util.attrAsRelativeUrl
import org.koitharu.kotatsu.parsers.util.domain
import org.koitharu.kotatsu.parsers.util.generateUid
import org.koitharu.kotatsu.parsers.util.mapChapters
import org.koitharu.kotatsu.parsers.util.parseHtml
import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow
import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl
import java.text.SimpleDateFormat
@MangaSourceParser("MANGAJINX", "MangaJinx", "en")
internal class MangaJinx(context: MangaLoaderContext) :
MadthemeParser(context, MangaSource.MANGAJINX, "mangajinx.com") {
override val listUrl = "search"
override suspend fun getChapters(doc: Document): List<MangaChapter> {
val dateFormat = SimpleDateFormat(datePattern, sourceLocale)
val id = doc.selectFirstOrThrow("script:containsData(bookId)").data().substringAfter("bookId = ")
.substringBefore(";")
val docChapter = webClient.httpGet("https://$domain/service/backend/chaplist/?manga_id=$id").parseHtml()
return docChapter.select(selectChapter).mapChapters(reversed = true) { i, li ->
val a = li.selectFirstOrThrow("a")
val href = a.attrAsRelativeUrl("href")
val dateText = li.selectFirst(selectDate)?.text()
MangaChapter(
id = generateUid(href),
name = li.selectFirstOrThrow(".chapter-title").text(),
number = i + 1,
url = href,
uploadDate = parseChapterDate(
dateFormat,
dateText,
),
source = source,
scanlator = null,
branch = null,
)
}
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val chapterUrl = chapter.url.toAbsoluteUrl(domain)
val docs = webClient.httpGet(chapterUrl).parseHtml()

@ -1,10 +1,12 @@
package org.koitharu.kotatsu.parsers.site.madtheme.en
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.madtheme.MadthemeParser
import org.koitharu.kotatsu.parsers.util.*
import java.text.SimpleDateFormat
import java.util.Locale
@MangaSourceParser("MANHUASCAN", "ManhuaScan.io", "en")
@ -35,6 +37,7 @@ internal class ManhuaScan(context: MangaLoaderContext) :
SortOrder.ALPHABETICAL -> append("name")
SortOrder.NEWEST -> append("created_at")
SortOrder.RATING -> append("rating")
else -> append("updated_at")
}
if (filter.tags.isNotEmpty()) {
filter.tags.forEach {
@ -93,6 +96,31 @@ internal class ManhuaScan(context: MangaLoaderContext) :
}
}
override suspend fun getChapters(doc: Document): List<MangaChapter> {
val dateFormat = SimpleDateFormat(datePattern, sourceLocale)
val id = doc.selectFirstOrThrow("script:containsData(bookId)").data().substringAfter("bookId = ")
.substringBefore(";")
val docChapter = webClient.httpGet("https://$domain/service/backend/chaplist/?manga_id=$id").parseHtml()
return docChapter.select(selectChapter).mapChapters(reversed = true) { i, li ->
val a = li.selectFirstOrThrow("a")
val href = a.attrAsRelativeUrl("href")
val dateText = li.selectFirst(selectDate)?.text()
MangaChapter(
id = generateUid(href),
name = li.selectFirstOrThrow(".chapter-title").text(),
number = i + 1,
url = href,
uploadDate = parseChapterDate(
dateFormat,
dateText,
),
source = source,
scanlator = null,
branch = null,
)
}
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val chapterUrl = chapter.url.toAbsoluteUrl(domain)
val docs = webClient.httpGet(chapterUrl).parseHtml()

@ -14,7 +14,7 @@ import java.util.EnumSet
internal class MangakakalotTv(context: MangaLoaderContext) :
MangaboxParser(context, MangaSource.MANGAKAKALOTTV) {
override val configKeyDomain = ConfigKey.Domain("ww6.mangakakalot.tv")
override val configKeyDomain = ConfigKey.Domain("ww7.mangakakalot.tv")
override val searchUrl = "/search/"
override val listUrl = "/manga_list"
override val availableSortOrders: Set<SortOrder> = EnumSet.of(

@ -30,7 +30,13 @@ internal abstract class MangaReaderParser(
override val configKeyDomain = ConfigKey.Domain(domain)
override val availableSortOrders: Set<SortOrder>
get() = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.ALPHABETICAL, SortOrder.NEWEST)
get() = EnumSet.of(
SortOrder.UPDATED,
SortOrder.POPULARITY,
SortOrder.ALPHABETICAL,
SortOrder.ALPHABETICAL_DESC,
SortOrder.NEWEST,
)
override val availableStates: Set<MangaState>
get() = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED)
@ -63,6 +69,7 @@ internal abstract class MangaReaderParser(
append(
when (filter.sortOrder) {
SortOrder.ALPHABETICAL -> "title"
SortOrder.ALPHABETICAL_DESC -> "titlereverse"
SortOrder.NEWEST -> "latest"
SortOrder.POPULARITY -> "popular"
SortOrder.UPDATED -> "update"

@ -1,10 +0,0 @@
package org.koitharu.kotatsu.parsers.site.mangareader.ar
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser
@MangaSourceParser("GALAXYACTION", "GalaxyAction", "ar")
internal class GalaxyAction(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.GALAXYACTION, "galaxyaction.site", pageSize = 20, searchPageSize = 10)

@ -1,10 +0,0 @@
package org.koitharu.kotatsu.parsers.site.mangareader.ar
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser
@MangaSourceParser("MANGAPROTM", "MangaPro", "ar")
internal class MangaProtm(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.MANGAPROTM, "mangapro.co", pageSize = 20, searchPageSize = 20)

@ -1,89 +0,0 @@
package org.koitharu.kotatsu.parsers.site.mangareader.ar
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.WordSet
import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser
import org.koitharu.kotatsu.parsers.util.*
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
@MangaSourceParser("OXAPK", "Oxapk", "ar")
internal class Oxapk(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.OXAPK, "oxapk.com", pageSize = 24, searchPageSize = 10) {
override val sourceLocale: Locale = Locale.ENGLISH
override suspend fun getDetails(manga: Manga): Manga {
val docs = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
val dateFormat = SimpleDateFormat(datePattern, sourceLocale)
val chapters = docs.select("#chapterlist > ul > li").mapChapters(reversed = true) { index, element ->
val url = element.selectFirst("a")?.attrAsRelativeUrl("href") ?: return@mapChapters null
MangaChapter(
id = generateUid(url),
name = docs.selectFirst("a.chapter-link-item")?.ownText().orEmpty(),
url = url,
number = index + 1,
scanlator = null,
uploadDate = parseChapterDate(
dateFormat,
element.selectFirst("div.chapter-link-time")?.text(),
),
branch = null,
source = source,
)
}
return parseInfo(docs, manga, chapters)
}
private fun parseChapterDate(dateFormat: DateFormat, date: String?): Long {
date ?: return 0
return when {
date.endsWith("منذ ", ignoreCase = true) -> {
parseRelativeDate(date)
}
else -> dateFormat.tryParse(date)
}
}
private fun parseRelativeDate(date: String): Long {
val number = Regex("""(\d+)""").find(date)?.value?.toIntOrNull() ?: return 0
val cal = Calendar.getInstance()
return when {
WordSet("أيام").anyWordIn(date) -> cal.apply { add(Calendar.DAY_OF_MONTH, -number) }.timeInMillis
WordSet("hour", "hours").anyWordIn(date) -> cal.apply {
add(
Calendar.HOUR,
-number,
)
}.timeInMillis
WordSet(
"mins",
).anyWordIn(date) -> cal.apply {
add(
Calendar.MINUTE,
-number,
)
}.timeInMillis
WordSet("second").anyWordIn(date) -> cal.apply {
add(
Calendar.SECOND,
-number,
)
}.timeInMillis
WordSet("أشهر").anyWordIn(date) -> cal.apply { add(Calendar.MONTH, -number) }.timeInMillis
WordSet("year").anyWordIn(date) -> cal.apply { add(Calendar.YEAR, -number) }.timeInMillis
else -> 0
}
}
}

@ -1,10 +0,0 @@
package org.koitharu.kotatsu.parsers.site.mangareader.ar
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser
@MangaSourceParser("OZULSHOJO", "OzulShojo", "ar")
internal class OzulShojo(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.OZULSHOJO, "ozulshojo.com", pageSize = 20, searchPageSize = 10)

@ -1,10 +0,0 @@
package org.koitharu.kotatsu.parsers.site.mangareader.ar
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser
@MangaSourceParser("SPIDERSCANS", "SpiderScans", "ar")
internal class SpiderScans(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.SPIDERSCANS, "spiderscans.com", pageSize = 20, searchPageSize = 10)

@ -36,6 +36,7 @@ internal class SwaTeam(context: MangaLoaderContext) :
append(
when (filter.sortOrder) {
SortOrder.ALPHABETICAL -> "a-z"
SortOrder.ALPHABETICAL_DESC -> "z-a"
SortOrder.NEWEST -> "added"
SortOrder.POPULARITY -> "popular"
SortOrder.UPDATED -> "update"

@ -1,10 +0,0 @@
package org.koitharu.kotatsu.parsers.site.mangareader.en
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser
@MangaSourceParser("AQUAMANGA_LIVE", "AquaManga.live", "en")
internal class AquaManga(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.AQUAMANGA_LIVE, "aquamanga.live", pageSize = 30, searchPageSize = 10)

@ -7,6 +7,6 @@ import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser
@MangaSourceParser("ARVENSCANS", "ArvenScans", "en")
internal class ArvenScans(context: MangaLoaderContext) :
MangaReaderParser(context, MangaSource.ARVENSCANS, "arvenscans.com", pageSize = 20, searchPageSize = 10) {
MangaReaderParser(context, MangaSource.ARVENSCANS, "arvenscans.org", pageSize = 20, searchPageSize = 10) {
override val listUrl = "/series"
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save