@@ -33,6 +33,8 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
|
||||
SortOrder.ALPHABETICAL,
|
||||
)
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain(
|
||||
"bato.to",
|
||||
"batocomic.com",
|
||||
@@ -61,37 +63,64 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
|
||||
"zbato.org",
|
||||
)
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
if (!query.isNullOrEmpty()) {
|
||||
return search(page, query)
|
||||
}
|
||||
@Suppress("NON_EXHAUSTIVE_WHEN_STATEMENT")
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/browse?sort=")
|
||||
when (sortOrder) {
|
||||
SortOrder.UPDATED,
|
||||
-> append("update.za")
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
SortOrder.POPULARITY -> append("views_a.za")
|
||||
SortOrder.NEWEST -> append("create.za")
|
||||
SortOrder.ALPHABETICAL -> append("title.az")
|
||||
SortOrder.RATING -> Unit
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
return search(page, filter.query)
|
||||
}
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
append("&genres=")
|
||||
appendAll(tags, ",") { it.key }
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
|
||||
append("/browse?sort=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.UPDATED -> append("update.za")
|
||||
SortOrder.POPULARITY -> append("views_a.za")
|
||||
SortOrder.NEWEST -> append("create.za")
|
||||
SortOrder.ALPHABETICAL -> append("title.az")
|
||||
else -> append("update.za")
|
||||
}
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&release=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "ongoing"
|
||||
MangaState.FINISHED -> "completed"
|
||||
MangaState.ABANDONED -> "cancelled"
|
||||
MangaState.PAUSED -> "hiatus"
|
||||
},
|
||||
)
|
||||
}
|
||||
// langs= en ...
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("&genres=")
|
||||
appendAll(filter.tags, ",") { it.key }
|
||||
}
|
||||
|
||||
append("&page=")
|
||||
append(page)
|
||||
}
|
||||
|
||||
return parseList(url, page)
|
||||
}
|
||||
|
||||
null -> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/browse?sort=update.za")
|
||||
append("&page=")
|
||||
append(page)
|
||||
}
|
||||
return parseList(url, page)
|
||||
}
|
||||
append("&page=")
|
||||
append(page)
|
||||
}
|
||||
return parseList(url, page)
|
||||
}
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
@@ -109,9 +138,11 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser(
|
||||
?.selectFirst(".limit-html")
|
||||
?.html(),
|
||||
tags = manga.tags + attrs["Genres:"]?.parseTags().orEmpty(),
|
||||
state = when (attrs["Release status:"]?.text()) {
|
||||
state = when (attrs["Original work:"]?.text()) {
|
||||
"Ongoing" -> MangaState.ONGOING
|
||||
"Completed" -> MangaState.FINISHED
|
||||
"Cancelled" -> MangaState.ABANDONED
|
||||
"Hiatus" -> MangaState.PAUSED
|
||||
else -> manga.state
|
||||
},
|
||||
author = attrs["Authors:"]?.text()?.trim() ?: manga.author,
|
||||
|
||||
@@ -114,9 +114,11 @@ internal class ComickFunParser(context: MangaLoaderContext) : PagedMangaParser(c
|
||||
val url = "https://api.$domain/comic/${manga.url}?tachiyomi=true"
|
||||
val jo = webClient.httpGet(url).parseJson()
|
||||
val comic = jo.getJSONObject("comic")
|
||||
var alt = ""
|
||||
comic.getJSONArray("md_titles").mapJSON { alt += it.getString("title") + " - " }
|
||||
return manga.copy(
|
||||
title = comic.getString("title"),
|
||||
altTitle = null, // TODO
|
||||
altTitle = alt,
|
||||
isNsfw = jo.getBoolean("matureContent") || comic.getBoolean("hentai"),
|
||||
description = comic.getStringOrNull("parsed") ?: comic.getStringOrNull("desc"),
|
||||
tags = manga.tags + comic.getJSONArray("md_comic_md_genres").mapJSONToSet {
|
||||
|
||||
@@ -24,43 +24,50 @@ internal class ImHentai(context: MangaLoaderContext) :
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append("/search/?key=")
|
||||
append(query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page)
|
||||
} else if (!tags.isNullOrEmpty()) {
|
||||
append("/tag/")
|
||||
append(tag?.key.orEmpty())
|
||||
append("/")
|
||||
when (sortOrder) {
|
||||
SortOrder.UPDATED -> append("")
|
||||
SortOrder.POPULARITY -> append("popular/")
|
||||
else -> append("")
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/search/?key=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page)
|
||||
}
|
||||
append("?page=")
|
||||
append(page)
|
||||
} else {
|
||||
append("/search/?page=")
|
||||
append(page)
|
||||
when (sortOrder) {
|
||||
SortOrder.UPDATED -> append("<=1&pp=0")
|
||||
SortOrder.POPULARITY -> append("<=0&pp=1")
|
||||
SortOrder.RATING -> append("<=0&pp=0")
|
||||
else -> append("<=1&pp=0")
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("/tag/")
|
||||
append(tag?.key.orEmpty())
|
||||
append("/")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.UPDATED -> append("")
|
||||
SortOrder.POPULARITY -> append("popular/")
|
||||
else -> append("")
|
||||
}
|
||||
append("?page=")
|
||||
append(page)
|
||||
} else {
|
||||
append("/search/?page=")
|
||||
append(page)
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.UPDATED -> append("<=1&pp=0")
|
||||
SortOrder.POPULARITY -> append("<=0&pp=1")
|
||||
SortOrder.RATING -> append("<=0&pp=0")
|
||||
else -> append("<=1&pp=0")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/search/?lt=1&pp=0&page=")
|
||||
append(page)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
return doc.select("div.galleries div.thumb").map { div ->
|
||||
|
||||
@@ -132,83 +132,117 @@ internal abstract class LineWebtoonsParser(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getList(
|
||||
offset: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val genre = tags.oneOrThrowIfMany()?.key ?: "ALL"
|
||||
override suspend fun getList(offset: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val manga =
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
makeRequest("/lineWebtoon/webtoon/searchChallenge?query=${filter.query.urlEncoded()}&startIndex=${offset + 1}&pageSize=20")
|
||||
.getJSONObject("challengeSearch")
|
||||
.getJSONArray("titleList")
|
||||
.mapJSON { jo ->
|
||||
val titleNo = jo.getLong("titleNo")
|
||||
|
||||
val sortOrderStr = when (sortOrder) {
|
||||
SortOrder.UPDATED -> "UPDATE"
|
||||
SortOrder.POPULARITY -> "READ_COUNT"
|
||||
SortOrder.RATING -> "LIKEIT"
|
||||
else -> throw IllegalArgumentException("Unsupported sort order: $sortOrder")
|
||||
}
|
||||
Manga(
|
||||
id = generateUid(titleNo),
|
||||
title = jo.getString("title"),
|
||||
altTitle = null,
|
||||
url = titleNo.toString(),
|
||||
publicUrl = "https://$domain/$languageCode/canvas/a/list?title_no=$titleNo",
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = isNsfwSource,
|
||||
coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain),
|
||||
largeCoverUrl = null,
|
||||
tags = emptySet(),
|
||||
author = jo.getStringOrNull("writingAuthorName"),
|
||||
description = null,
|
||||
state = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val manga = if (query != null) {
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
throw IllegalArgumentException("This source does not support search with tags")
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
val genre = filter.tags.oneOrThrowIfMany()?.key ?: "ALL"
|
||||
|
||||
val sortOrderStr = when (filter.sortOrder) {
|
||||
SortOrder.UPDATED -> "UPDATE"
|
||||
SortOrder.POPULARITY -> "READ_COUNT"
|
||||
SortOrder.RATING -> "LIKEIT"
|
||||
else -> throw IllegalArgumentException("Unsupported sort order: ${filter.sortOrder}")
|
||||
}
|
||||
|
||||
val result =
|
||||
makeRequest("/lineWebtoon/webtoon/challengeGenreTitleList.json?genre=$genre&sortOrder=$sortOrderStr&startIndex=${offset + 1}&pageSize=20")
|
||||
|
||||
val genres = result.getJSONObject("genreList")
|
||||
.getJSONArray("challengeGenres")
|
||||
.mapJSON { jo -> parseTag(jo) }
|
||||
.associateBy { tag -> tag.key }
|
||||
|
||||
result
|
||||
.getJSONObject("titleList")
|
||||
.getJSONArray("titles")
|
||||
.mapJSON { jo ->
|
||||
val titleNo = jo.getLong("titleNo")
|
||||
|
||||
Manga(
|
||||
id = generateUid(titleNo),
|
||||
title = jo.getString("title"),
|
||||
altTitle = null,
|
||||
url = titleNo.toString(),
|
||||
publicUrl = "https://$domain/$languageCode/canvas/a/list?title_no=$titleNo",
|
||||
rating = jo.getFloatOrDefault("starScoreAverage", -10f) / 10f,
|
||||
isNsfw = jo.getBooleanOrDefault("ageGradeNotice", isNsfwSource),
|
||||
coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain),
|
||||
largeCoverUrl = jo.getStringOrNull("thumbnailVertical")?.toAbsoluteUrl(staticDomain),
|
||||
tags = setOfNotNull(genres[jo.getString("representGenre")]),
|
||||
author = jo.getStringOrNull("writingAuthorName"),
|
||||
description = jo.getString("synopsis"),
|
||||
// I don't think the API provides this info
|
||||
state = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
|
||||
val result =
|
||||
makeRequest("/lineWebtoon/webtoon/challengeGenreTitleList.json?genre=ALL&sortOrder=UPDATE&startIndex=${offset + 1}&pageSize=20")
|
||||
|
||||
val genres = result.getJSONObject("genreList")
|
||||
.getJSONArray("challengeGenres")
|
||||
.mapJSON { jo -> parseTag(jo) }
|
||||
.associateBy { tag -> tag.key }
|
||||
|
||||
result
|
||||
.getJSONObject("titleList")
|
||||
.getJSONArray("titles")
|
||||
.mapJSON { jo ->
|
||||
val titleNo = jo.getLong("titleNo")
|
||||
|
||||
Manga(
|
||||
id = generateUid(titleNo),
|
||||
title = jo.getString("title"),
|
||||
altTitle = null,
|
||||
url = titleNo.toString(),
|
||||
publicUrl = "https://$domain/$languageCode/canvas/a/list?title_no=$titleNo",
|
||||
rating = jo.getFloatOrDefault("starScoreAverage", -10f) / 10f,
|
||||
isNsfw = jo.getBooleanOrDefault("ageGradeNotice", isNsfwSource),
|
||||
coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain),
|
||||
largeCoverUrl = jo.getStringOrNull("thumbnailVertical")?.toAbsoluteUrl(staticDomain),
|
||||
tags = setOfNotNull(genres[jo.getString("representGenre")]),
|
||||
author = jo.getStringOrNull("writingAuthorName"),
|
||||
description = jo.getString("synopsis"),
|
||||
// I don't think the API provides this info
|
||||
state = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
makeRequest("/lineWebtoon/webtoon/searchChallenge?query=${query.urlEncoded()}&startIndex=${offset + 1}&pageSize=20")
|
||||
.getJSONObject("challengeSearch")
|
||||
.getJSONArray("titleList")
|
||||
.mapJSON { jo ->
|
||||
val titleNo = jo.getLong("titleNo")
|
||||
|
||||
Manga(
|
||||
id = generateUid(titleNo),
|
||||
title = jo.getString("title"),
|
||||
altTitle = null,
|
||||
url = titleNo.toString(),
|
||||
publicUrl = "https://$domain/$languageCode/canvas/a/list?title_no=$titleNo",
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = isNsfwSource,
|
||||
coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain),
|
||||
largeCoverUrl = null,
|
||||
tags = emptySet(),
|
||||
author = jo.getStringOrNull("writingAuthorName"),
|
||||
description = null,
|
||||
state = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val result =
|
||||
makeRequest("/lineWebtoon/webtoon/challengeGenreTitleList.json?genre=$genre&sortOrder=$sortOrderStr&startIndex=${offset + 1}&pageSize=20")
|
||||
|
||||
val genres = result.getJSONObject("genreList")
|
||||
.getJSONArray("challengeGenres")
|
||||
.mapJSON { jo -> parseTag(jo) }
|
||||
.associateBy { tag -> tag.key }
|
||||
|
||||
result
|
||||
.getJSONObject("titleList")
|
||||
.getJSONArray("titles")
|
||||
.mapJSON { jo ->
|
||||
val titleNo = jo.getLong("titleNo")
|
||||
|
||||
Manga(
|
||||
id = generateUid(titleNo),
|
||||
title = jo.getString("title"),
|
||||
altTitle = null,
|
||||
url = titleNo.toString(),
|
||||
publicUrl = "https://$domain/$languageCode/canvas/a/list?title_no=$titleNo",
|
||||
rating = jo.getFloatOrDefault("starScoreAverage", -10f) / 10f,
|
||||
isNsfw = jo.getBooleanOrDefault("ageGradeNotice", isNsfwSource),
|
||||
coverUrl = jo.getString("thumbnail").toAbsoluteUrl(staticDomain),
|
||||
largeCoverUrl = jo.getStringOrNull("thumbnailVertical")?.toAbsoluteUrl(staticDomain),
|
||||
tags = setOfNotNull(genres[jo.getString("representGenre")]),
|
||||
author = jo.getStringOrNull("writingAuthorName"),
|
||||
description = jo.getString("synopsis"),
|
||||
// I don't think the API provides this info
|
||||
state = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return manga
|
||||
}
|
||||
@@ -309,10 +343,10 @@ internal abstract class LineWebtoonsParser(
|
||||
}
|
||||
|
||||
fun makeEncryptUrl(urlBuilder: HttpUrl.Builder) {
|
||||
val msgpad = Calendar.getInstance().timeInMillis.toString()
|
||||
val digest = getMessageDigest(getMessage(urlBuilder.build().toString(), msgpad))
|
||||
val msgPad = Calendar.getInstance().timeInMillis.toString()
|
||||
val digest = getMessageDigest(getMessage(urlBuilder.build().toString(), msgPad))
|
||||
urlBuilder
|
||||
.addQueryParameter("msgpad", msgpad)
|
||||
.addQueryParameter("msgpad", msgPad)
|
||||
.addQueryParameter("md", digest)
|
||||
// .addEncodedQueryParameter("md", digest.urlEncoded())
|
||||
}
|
||||
|
||||
@@ -30,19 +30,12 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("mangadex.org")
|
||||
|
||||
override val availableSortOrders: EnumSet<SortOrder> = EnumSet.of(
|
||||
SortOrder.UPDATED,
|
||||
SortOrder.ALPHABETICAL,
|
||||
SortOrder.NEWEST,
|
||||
SortOrder.POPULARITY,
|
||||
)
|
||||
override val availableSortOrders: EnumSet<SortOrder> = EnumSet.allOf(SortOrder::class.java)
|
||||
|
||||
override suspend fun getList(
|
||||
offset: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
|
||||
|
||||
|
||||
override suspend fun getList(offset: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val domain = domain
|
||||
val url = buildString {
|
||||
append("https://api.")
|
||||
@@ -52,29 +45,46 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
|
||||
append("&offset=")
|
||||
append(offset)
|
||||
append("&includes[]=cover_art&includes[]=author&includes[]=artist&")
|
||||
tags?.forEach { tag ->
|
||||
append("includedTags[]=")
|
||||
append(tag.key)
|
||||
append('&')
|
||||
}
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append("title=")
|
||||
append(query.urlEncoded())
|
||||
append('&')
|
||||
}
|
||||
append(CONTENT_RATING)
|
||||
append("&order")
|
||||
append(
|
||||
when (sortOrder) {
|
||||
SortOrder.UPDATED,
|
||||
-> "[latestUploadedChapter]=desc"
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("title=")
|
||||
append(filter.query)
|
||||
append('&')
|
||||
}
|
||||
|
||||
SortOrder.ALPHABETICAL -> "[title]=asc"
|
||||
SortOrder.NEWEST -> "[createdAt]=desc"
|
||||
SortOrder.POPULARITY -> "[followedCount]=desc"
|
||||
else -> "[followedCount]=desc"
|
||||
},
|
||||
)
|
||||
is MangaListFilter.Advanced -> {
|
||||
filter.tags.forEach { tag ->
|
||||
append("includedTags[]=")
|
||||
append(tag.key)
|
||||
append('&')
|
||||
}
|
||||
|
||||
append(CONTENT_RATING)
|
||||
append("&order")
|
||||
append(
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.UPDATED -> "[latestUploadedChapter]=desc"
|
||||
SortOrder.RATING -> "[rating]=desc"
|
||||
SortOrder.ALPHABETICAL -> "[title]=asc"
|
||||
SortOrder.NEWEST -> "[createdAt]=desc"
|
||||
SortOrder.POPULARITY -> "[followedCount]=desc"
|
||||
},
|
||||
)
|
||||
filter.states.forEach {
|
||||
append("&status[]=")
|
||||
when (it) {
|
||||
MangaState.ONGOING -> append("ongoing")
|
||||
MangaState.FINISHED -> append("completed")
|
||||
MangaState.ABANDONED -> append("cancelled")
|
||||
MangaState.PAUSED -> append("hiatus")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("&order[latestUploadedChapter]=desc")
|
||||
}
|
||||
}
|
||||
}
|
||||
val json = webClient.httpGet(url).parseJson().getJSONArray("data")
|
||||
return json.mapJSON { jo ->
|
||||
@@ -110,9 +120,11 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context
|
||||
source = source,
|
||||
)
|
||||
},
|
||||
state = when (jo.getStringOrNull("status")) {
|
||||
state = when (attrs.getStringOrNull("status")) {
|
||||
"ongoing" -> MangaState.ONGOING
|
||||
"completed" -> MangaState.FINISHED
|
||||
"hiatus" -> MangaState.PAUSED
|
||||
"cancelled" -> MangaState.ABANDONED
|
||||
else -> null
|
||||
},
|
||||
author = (relations["author"] ?: relations["artist"])
|
||||
|
||||
@@ -35,6 +35,11 @@ internal abstract class NineMangaParser(
|
||||
SortOrder.POPULARITY,
|
||||
)
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.of(
|
||||
MangaState.ONGOING,
|
||||
MangaState.FINISHED,
|
||||
)
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
val newRequest = if (request.url.host == domain) {
|
||||
@@ -45,37 +50,60 @@ internal abstract class NineMangaParser(
|
||||
return chain.proceed(newRequest)
|
||||
}
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/search/?name_sel=&wd=")
|
||||
append(query.urlEncoded())
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page)
|
||||
append(".html")
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/search/?category_id=")
|
||||
for (tag in tags) {
|
||||
append(tag.key)
|
||||
append(',')
|
||||
is MangaListFilter.Advanced -> {
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("/search/?category_id=")
|
||||
for (tag in filter.tags) {
|
||||
append(tag.key)
|
||||
append(',')
|
||||
}
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&completed_series=")
|
||||
when (it) {
|
||||
MangaState.ONGOING -> append("no")
|
||||
MangaState.FINISHED -> append("yes")
|
||||
else -> append("either")
|
||||
}
|
||||
}
|
||||
append("&page=")
|
||||
} else {
|
||||
append("/category/")
|
||||
if (filter.states.isNotEmpty()) {
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
when (it) {
|
||||
MangaState.ONGOING -> append("updated_")
|
||||
MangaState.FINISHED -> append("completed_")
|
||||
else -> append("either")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
append("index_")
|
||||
}
|
||||
}
|
||||
append("&page=")
|
||||
append(page)
|
||||
append(".html")
|
||||
}
|
||||
|
||||
else -> {
|
||||
null -> {
|
||||
append("/category/index_")
|
||||
append(page)
|
||||
append(".html")
|
||||
}
|
||||
}
|
||||
append(page)
|
||||
append(".html")
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
val root = doc.body().selectFirst("ul.direlist") ?: doc.parseFailed("Cannot find root")
|
||||
|
||||
@@ -11,8 +11,6 @@ import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.util.*
|
||||
|
||||
// see https://themewagon.com/themes/free-bootstrap-4-html5-gaming-anime-website-template-anime/
|
||||
|
||||
internal abstract class AnimeBootstrapParser(
|
||||
context: MangaLoaderContext,
|
||||
source: MangaSource,
|
||||
@@ -40,13 +38,7 @@ internal abstract class AnimeBootstrapParser(
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
@@ -55,23 +47,31 @@ internal abstract class AnimeBootstrapParser(
|
||||
append(page.toString())
|
||||
append("&type=all")
|
||||
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append("&search=")
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("&search=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
append("&categorie=")
|
||||
append(tag?.key.orEmpty())
|
||||
}
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
append("&sort=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("view")
|
||||
SortOrder.UPDATED -> append("updated")
|
||||
SortOrder.ALPHABETICAL -> append("default")
|
||||
SortOrder.NEWEST -> append("published")
|
||||
else -> append("updated")
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("&categorie=")
|
||||
append(it.key)
|
||||
}
|
||||
|
||||
append("&sort=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("view")
|
||||
SortOrder.UPDATED -> append("updated")
|
||||
SortOrder.ALPHABETICAL -> append("default")
|
||||
SortOrder.NEWEST -> append("published")
|
||||
else -> append("updated")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
null -> append("&sort=updated")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
@@ -115,11 +115,8 @@ internal abstract class AnimeBootstrapParser(
|
||||
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
|
||||
val fullUrl = manga.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||
|
||||
val chaptersDeferred = async { getChapters(doc) }
|
||||
|
||||
val desc = doc.selectFirstOrThrow(selectDesc).html()
|
||||
|
||||
val state = if (doc.select(selectState).isNullOrEmpty()) {
|
||||
MangaState.FINISHED
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.koitharu.kotatsu.parsers.site.animebootstrap.fr
|
||||
|
||||
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import org.jsoup.nodes.Document
|
||||
@@ -13,20 +12,14 @@ import java.text.SimpleDateFormat
|
||||
import java.util.EnumSet
|
||||
import java.util.Locale
|
||||
|
||||
|
||||
@MangaSourceParser("PAPSCAN", "PapScan", "fr")
|
||||
internal class PapScan(context: MangaLoaderContext) :
|
||||
AnimeBootstrapParser(context, MangaSource.PAPSCAN, "papscan.com") {
|
||||
|
||||
override val sourceLocale: Locale = Locale.ENGLISH
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override val listUrl = "/liste-manga"
|
||||
|
||||
override val selectState = "div.anime__details__widget li:contains(En cours)"
|
||||
override val selectTag = "div.anime__details__widget li:contains(Genre) a"
|
||||
|
||||
override val selectChapter = "ul.chapters li"
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||
@@ -34,40 +27,39 @@ internal class PapScan(context: MangaLoaderContext) :
|
||||
SortOrder.ALPHABETICAL,
|
||||
)
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/filterList")
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("&alpha=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append("&alpha=")
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
append("&cat=")
|
||||
append(tag?.key.orEmpty())
|
||||
}
|
||||
append("&sortBy=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.ALPHABETICAL -> append("name")
|
||||
else -> append("updated")
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("&cat=")
|
||||
append(it.key)
|
||||
}
|
||||
|
||||
append("&sortBy=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.ALPHABETICAL -> append("name")
|
||||
else -> append("updated")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
null -> append("&sortBy=updated")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
return doc.select("div.product__item").map { div ->
|
||||
val href = div.selectFirstOrThrow("h5 a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
@@ -103,17 +95,13 @@ internal class PapScan(context: MangaLoaderContext) :
|
||||
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
|
||||
val fullUrl = manga.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||
|
||||
val chaptersDeferred = async { getChapters(doc) }
|
||||
|
||||
val desc = doc.selectFirstOrThrow(selectDesc).html()
|
||||
|
||||
val state = if (doc.select(selectState).isNullOrEmpty()) {
|
||||
MangaState.FINISHED
|
||||
} else {
|
||||
MangaState.ONGOING
|
||||
}
|
||||
|
||||
manga.copy(
|
||||
tags = doc.body().select(selectTag).mapNotNullToSet { a ->
|
||||
MangaTag(
|
||||
@@ -145,5 +133,4 @@ internal class PapScan(context: MangaLoaderContext) :
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,6 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.site.animebootstrap.AnimeBootstrapParser
|
||||
|
||||
@MangaSourceParser("KOMIKZOID", "Komikzo Id", "id")
|
||||
@MangaSourceParser("KOMIKZOID", "KomikzoId", "id")
|
||||
internal class KomikzoId(context: MangaLoaderContext) :
|
||||
AnimeBootstrapParser(context, MangaSource.KOMIKZOID, "komikzoid.xyz")
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package org.koitharu.kotatsu.parsers.site.animebootstrap.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.animebootstrap.AnimeBootstrapParser
|
||||
|
||||
|
||||
@MangaSourceParser("NEUMANGA", "NeuManga", "id")
|
||||
@MangaSourceParser("NEUMANGA", "NeuManga.xyz", "id")
|
||||
internal class NeuManga(context: MangaLoaderContext) :
|
||||
AnimeBootstrapParser(context, MangaSource.NEUMANGA, "neumanga.xyz")
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.koitharu.kotatsu.parsers.site.animebootstrap.id
|
||||
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
|
||||
@@ -19,32 +19,66 @@ import java.util.*
|
||||
internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.FLIXSCANS, 18) {
|
||||
|
||||
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.com")
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val json = if (!query.isNullOrEmpty()) {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
val json = when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
val url = "https://api.$domain/api/v1/search/serie"
|
||||
val body = JSONObject()
|
||||
body.put("title", filter.query.urlEncoded())
|
||||
webClient.httpPost(url, body).parseJson().getJSONArray("data")
|
||||
}
|
||||
val url = "https://api.$domain/api/v1/search/serie"
|
||||
val body = JSONObject()
|
||||
body.put("title", query.urlEncoded())
|
||||
webClient.httpPost(url, body).parseJson().getJSONArray("data")
|
||||
} else if (!tags.isNullOrEmpty()) {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
val url = buildString {
|
||||
append("https://api.")
|
||||
append(domain)
|
||||
append("/api/v1/")
|
||||
|
||||
if (filter.tags.isNotEmpty() || filter.states.isNotEmpty()) {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
append("search/advance?=")
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
val tagQuery = filter.tags.joinToString(separator = ",") { it.key }
|
||||
append("&genres=")
|
||||
append(tagQuery)
|
||||
}
|
||||
if (filter.states.isNotEmpty()) {
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&status=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "ongoing"
|
||||
MangaState.FINISHED -> "completed"
|
||||
MangaState.ABANDONED -> "droped"
|
||||
MangaState.PAUSED -> "onhold"
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
append("&serie_type=webtoon")
|
||||
|
||||
} else {
|
||||
append("webtoon/homepage/latest/home?page=")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
|
||||
webClient.httpGet(url).parseJson().getJSONArray("data")
|
||||
}
|
||||
|
||||
null -> {
|
||||
val url = "https://api.$domain/api/v1/webtoon/homepage/latest/home?page=$page"
|
||||
webClient.httpGet(url).parseJson().getJSONArray("data")
|
||||
}
|
||||
val tagQuery = tags.joinToString(separator = ",") { it.key }
|
||||
val url = "https://api.$domain/api/v1/search/advance?=&genres=$tagQuery&serie_type=webtoon"
|
||||
webClient.httpGet(url).parseJson().getJSONArray("data")
|
||||
} else {
|
||||
val url = "https://api.$domain/api/v1/webtoon/homepage/latest/home?page=$page"
|
||||
webClient.httpGet(url).parseJson().getJSONArray("data")
|
||||
}
|
||||
return json.mapJSON { j ->
|
||||
val href = "https://$domain/series/${j.getString("prefix")}-${j.getString("id")}-${j.getString("slug")}"
|
||||
@@ -62,6 +96,8 @@ internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context
|
||||
state = when (j.getString("status")) {
|
||||
"ongoing" -> MangaState.ONGOING
|
||||
"completed" -> MangaState.FINISHED
|
||||
"onhold" -> MangaState.PAUSED
|
||||
"droped" -> MangaState.ABANDONED
|
||||
else -> null
|
||||
},
|
||||
author = null,
|
||||
@@ -77,9 +113,9 @@ internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context
|
||||
return tagsList.mapNotNullToSet { idTag ->
|
||||
val id = idTag.toInt()
|
||||
val idKey = json.getJSONObject(id).getInt("id")
|
||||
val key = json.get(idKey).toString()
|
||||
val key = json.getInt(idKey).toString()
|
||||
val idName = json.getJSONObject(id).getInt("name")
|
||||
val name = json.get(idName).toString()
|
||||
val name = json.getString(idName)
|
||||
MangaTag(
|
||||
key = key,
|
||||
title = name,
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.util.*
|
||||
@MangaSourceParser("MANGASTORM", "MangaStorm", "ar")
|
||||
internal class MangaStorm(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.MANGASTORM, 30) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.POPULARITY)
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED)
|
||||
override val configKeyDomain = ConfigKey.Domain("mangastorm.org")
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
@@ -21,35 +21,44 @@ internal class MangaStorm(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
.add("User-Agent", UserAgents.CHROME_DESKTOP)
|
||||
.build()
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
val url =
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/categories/")
|
||||
append(tag?.key.orEmpty())
|
||||
append("?page=")
|
||||
append(page)
|
||||
}
|
||||
} else {
|
||||
buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/mangas?page=")
|
||||
append(page)
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append("&query=")
|
||||
append(query.urlEncoded())
|
||||
append("&query=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
append("/categories/")
|
||||
append(tag?.key.orEmpty())
|
||||
append("?page=")
|
||||
append(page)
|
||||
} else {
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
append("/mangas?page=")
|
||||
append(page)
|
||||
} else {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/mangas?page=")
|
||||
append(page)
|
||||
}
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
return doc.select("div.row div.col").map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
@@ -74,9 +83,7 @@ internal class MangaStorm(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
|
||||
val root = doc.selectFirstOrThrow(".card-body .col-lg-9")
|
||||
|
||||
return manga.copy(
|
||||
altTitle = null,
|
||||
state = null,
|
||||
|
||||
@@ -17,46 +17,71 @@ import java.util.*
|
||||
internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.TEAMXNOVEL, 10) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
|
||||
override val availableStates: Set<MangaState> =
|
||||
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("team11x11.com")
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
append("/series?genre=")
|
||||
append(tag?.key.orEmpty())
|
||||
if (page > 1) {
|
||||
append("&page=")
|
||||
append(page)
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
append("/series?search=")
|
||||
append(filter.query.urlEncoded())
|
||||
if (page > 1) {
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
} else if (!query.isNullOrEmpty()) {
|
||||
append("/series?search=")
|
||||
append(query.urlEncoded())
|
||||
if (page > 1) {
|
||||
append("&page=")
|
||||
append(page)
|
||||
}
|
||||
} else {
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("/series")
|
||||
SortOrder.UPDATED -> append("/")
|
||||
else -> append("/")
|
||||
}
|
||||
if (page > 1) {
|
||||
append("?page=")
|
||||
append(page)
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
append("/series?genre=")
|
||||
append(tag?.key.orEmpty())
|
||||
if (page > 1) {
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
append("&")
|
||||
} else {
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("/series")
|
||||
SortOrder.UPDATED -> append("/")
|
||||
else -> append("/")
|
||||
}
|
||||
if (page > 1) {
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
append("&")
|
||||
} else {
|
||||
append("?")
|
||||
}
|
||||
}
|
||||
|
||||
if (filter.sortOrder == SortOrder.POPULARITY || filter.tags.isNotEmpty()) {
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("status=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "مستمرة"
|
||||
MangaState.FINISHED -> "مكتمل"
|
||||
MangaState.ABANDONED -> "متوقف"
|
||||
else -> "مستمرة"
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
null -> append("/?page=$page")
|
||||
}
|
||||
}
|
||||
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
return doc.select("div.listupd .bs .bsx").ifEmpty {
|
||||
doc.select("div.post-body .box")
|
||||
@@ -74,7 +99,8 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
tags = emptySet(),
|
||||
state = when (div.selectFirst(".status")?.text()) {
|
||||
"مستمرة" -> MangaState.ONGOING
|
||||
"متوقف", "مكتمل" -> MangaState.FINISHED
|
||||
"مكتمل" -> MangaState.FINISHED
|
||||
"متوقف" -> MangaState.ABANDONED
|
||||
else -> null
|
||||
},
|
||||
author = null,
|
||||
@@ -111,7 +137,8 @@ internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
altTitle = null,
|
||||
state = when (doc.selectFirstOrThrow(".full-list-info:contains(الحالة:) a").text()) {
|
||||
"مستمرة" -> MangaState.ONGOING
|
||||
"متوقف", "مكتمل" -> MangaState.FINISHED
|
||||
"مكتمل" -> MangaState.FINISHED
|
||||
"متوقف" -> MangaState.ABANDONED
|
||||
else -> null
|
||||
},
|
||||
tags = doc.select(".review-author-info a").mapNotNullToSet { a ->
|
||||
|
||||
@@ -19,43 +19,41 @@ internal class BeeToon(context: MangaLoaderContext) :
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
append("/?s=")
|
||||
append(query.urlEncoded())
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/genre/")
|
||||
append(tag?.key.orEmpty())
|
||||
append("/page-")
|
||||
append(page)
|
||||
append("/")
|
||||
}
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
else -> {
|
||||
when (sortOrder) {
|
||||
SortOrder.UPDATED -> append("/latest-update/")
|
||||
SortOrder.POPULARITY -> append("/popular-manga/")
|
||||
else -> append("/latest-update/")
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
append("/genre/")
|
||||
append(tag?.key.orEmpty())
|
||||
append("/page-")
|
||||
append(page)
|
||||
append("/")
|
||||
} else {
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.UPDATED -> append("/latest-update/")
|
||||
SortOrder.POPULARITY -> append("/popular-manga/")
|
||||
else -> append("/latest-update/")
|
||||
}
|
||||
append("page-")
|
||||
append(page)
|
||||
append("/")
|
||||
}
|
||||
append("page-")
|
||||
append(page)
|
||||
append("/")
|
||||
}
|
||||
|
||||
null -> append("/latest-update/page-$page/")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
@@ -19,11 +19,25 @@ internal class CloneMangaParser(context: MangaLoaderContext) : MangaParser(conte
|
||||
override val configKeyDomain = ConfigKey.Domain("manga.clone-army.org")
|
||||
|
||||
@InternalParsersApi
|
||||
override suspend fun getList(offset: Int, query: String?, tags: Set<MangaTag>?, sortOrder: SortOrder): List<Manga> {
|
||||
if (query != null || offset > 0) {
|
||||
return emptyList()
|
||||
override suspend fun getList(offset: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
val link = when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
if (offset > 0) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
"https://$domain/viewer_landing.php"
|
||||
}
|
||||
|
||||
null -> "https://$domain/viewer_landing.php"
|
||||
|
||||
}
|
||||
val link = "https://$domain/viewer_landing.php"
|
||||
|
||||
val doc = webClient.httpGet(link).parseHtml()
|
||||
val mangas = doc.getElementsByClass("comicPreviewContainer")
|
||||
return mangas.mapNotNull { item ->
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.network.UserAgents
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@@ -17,6 +18,8 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
override val availableSortOrders: Set<SortOrder> =
|
||||
EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.NEWEST)
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("comicextra.me")
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
@@ -25,38 +28,62 @@ internal class ComicExtra(context: MangaLoaderContext) : PagedMangaParser(contex
|
||||
.add("User-Agent", UserAgents.CHROME_DESKTOP)
|
||||
.build()
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://$domain/")
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
append(tag?.key.orEmpty())
|
||||
if (page > 1) {
|
||||
append("/")
|
||||
append(page)
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/")
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("comic-search?key=")
|
||||
append(filter.query.urlEncoded())
|
||||
if (page > 1) {
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
} else if (!query.isNullOrEmpty()) {
|
||||
append("comic-search?key=")
|
||||
append(query.urlEncoded())
|
||||
if (page > 1) {
|
||||
append("&page=")
|
||||
append(page)
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
if (filter.tags.isNotEmpty() && filter.states.isEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append(it.key)
|
||||
}
|
||||
} else if (filter.tags.isEmpty() && filter.states.isNotEmpty()) {
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "/ongoing-comic"
|
||||
MangaState.FINISHED -> "/completed-comic"
|
||||
else -> "/ongoing-comic"
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
} else if (filter.tags.isNotEmpty() && filter.states.isNotEmpty()) {
|
||||
throw IllegalArgumentException("Source does not support tag + states filters")
|
||||
} else {
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("popular-comic")
|
||||
SortOrder.UPDATED -> append("new-comic")
|
||||
SortOrder.NEWEST -> append("recent-comic")
|
||||
else -> append("new-comic")
|
||||
}
|
||||
}
|
||||
|
||||
if (page > 1) {
|
||||
append("/")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("popular-comic/")
|
||||
SortOrder.UPDATED -> append("new-comic/")
|
||||
SortOrder.NEWEST -> append("recent-comic/")
|
||||
else -> append("new-comic/")
|
||||
}
|
||||
if (page > 1) {
|
||||
append(page)
|
||||
|
||||
null -> {
|
||||
append("popular-comic")
|
||||
if (page > 1) {
|
||||
append("/")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
package org.koitharu.kotatsu.parsers.site.en
|
||||
|
||||
import androidx.collection.ArraySet
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import okhttp3.Headers
|
||||
import org.json.JSONArray
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
||||
@@ -22,85 +27,128 @@ internal class DynastyScans(context: MangaLoaderContext) : PagedMangaParser(cont
|
||||
.add("User-Agent", UserAgents.CHROME_DESKTOP)
|
||||
.build()
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append("/search?q=")
|
||||
append(query.urlEncoded())
|
||||
append("&")
|
||||
append("classes[]".urlEncoded())
|
||||
append("=Serie&page=")
|
||||
append(page.toString())
|
||||
} else if (!tags.isNullOrEmpty()) {
|
||||
append("/tags/")
|
||||
for (tag in tags) {
|
||||
append(tag.key)
|
||||
}
|
||||
append("?view=groupings&page=")
|
||||
append(page.toString())
|
||||
} else {
|
||||
append("/series?view=cover&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
// There are no images on the search page
|
||||
if (!query.isNullOrEmpty()) {
|
||||
return doc.select("dl.chapter-list dd")
|
||||
.map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
title = div.selectFirstOrThrow("a").text(),
|
||||
altTitle = null,
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(domain),
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = false,
|
||||
coverUrl = "",
|
||||
tags = div.select("span.tags a").mapNotNullToSet { a ->
|
||||
MangaTag(
|
||||
key = a.attr("href").removeSuffix('/').substringAfterLast('/'),
|
||||
title = a.text(),
|
||||
source = source,
|
||||
)
|
||||
},
|
||||
state = null,
|
||||
author = null,
|
||||
source = source,
|
||||
)
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/search?q=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&")
|
||||
append("classes[]".urlEncoded())
|
||||
append("=Serie&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
} else {
|
||||
return doc.select("li.span2")
|
||||
.map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
title = div.selectFirstOrThrow("div.caption").text(),
|
||||
altTitle = null,
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(domain),
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = false,
|
||||
coverUrl = div.selectFirstOrThrow("img").attrAsAbsoluteUrl("src"),
|
||||
tags = setOf(),
|
||||
state = null,
|
||||
author = null,
|
||||
source = source,
|
||||
)
|
||||
return parseMangaListQuery(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("/tags/")
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append(it.key)
|
||||
}
|
||||
append("?view=groupings")
|
||||
} else {
|
||||
append("/series?view=cover")
|
||||
|
||||
}
|
||||
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
|
||||
null -> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/series?view=cover&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> = emptySet()
|
||||
|
||||
private fun parseMangaList(doc: Document): List<Manga> {
|
||||
return doc.select("li.span2")
|
||||
.map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
title = div.selectFirstOrThrow("div.caption").text(),
|
||||
altTitle = null,
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(domain),
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = false,
|
||||
coverUrl = div.selectFirstOrThrow("img").attrAsAbsoluteUrl("src"),
|
||||
tags = setOf(),
|
||||
state = null,
|
||||
author = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseMangaListQuery(doc: Document): List<Manga> {
|
||||
return doc.select("dl.chapter-list dd")
|
||||
.map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
title = div.selectFirstOrThrow("a").text(),
|
||||
altTitle = null,
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(domain),
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = false,
|
||||
coverUrl = "",
|
||||
tags = div.select("span.tags a").mapNotNullToSet { a ->
|
||||
MangaTag(
|
||||
key = a.attr("href").removeSuffix('/').substringAfterLast('/'),
|
||||
title = a.text(),
|
||||
source = source,
|
||||
)
|
||||
},
|
||||
state = null,
|
||||
author = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
return coroutineScope {
|
||||
(1..3).map { page ->
|
||||
async { getTags(page) }
|
||||
}
|
||||
}.awaitAll().flattenTo(ArraySet(360))
|
||||
}
|
||||
|
||||
private suspend fun getTags(page: Int): Set<MangaTag> {
|
||||
val url = "https://$domain/tags?page=$page"
|
||||
val root = webClient.httpGet(url).parseHtml()
|
||||
return root.selectFirstOrThrow(".tag-list ").parseTags()
|
||||
}
|
||||
|
||||
private fun Element.parseTags() = select("a").mapToSet {
|
||||
MangaTag(
|
||||
key = it.attr("href").removeSuffix('/').substringAfterLast('/'),
|
||||
title = it.text(),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
@@ -110,19 +158,14 @@ internal class DynastyScans(context: MangaLoaderContext) : PagedMangaParser(cont
|
||||
altTitle = null,
|
||||
state = when (root.select("h2.tag-title small").last()?.text()) {
|
||||
"— Ongoing" -> MangaState.ONGOING
|
||||
"— Completed" -> MangaState.FINISHED
|
||||
"— Completed", "— Completed and Licensed" -> MangaState.FINISHED
|
||||
"— Dropped", "— Licensed and Removed", "— Abandoned" -> MangaState.ABANDONED
|
||||
"— On Hiatus" -> MangaState.PAUSED
|
||||
else -> null
|
||||
},
|
||||
coverUrl = root.selectFirst("img.thumbnail")?.src()
|
||||
.orEmpty(), // It is needed if the manga was found via the search.
|
||||
tags = root.select("div.tag-tags a").mapNotNullToSet { a ->
|
||||
val href = a.attr("href").removeSuffix('/').substringAfterLast('/')
|
||||
MangaTag(
|
||||
key = href,
|
||||
title = a.text(),
|
||||
source = source,
|
||||
)
|
||||
},
|
||||
tags = root.selectFirstOrThrow("div.tag-tags").parseTags(),
|
||||
author = null,
|
||||
description = null,
|
||||
chapters = chapters,
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
package org.koitharu.kotatsu.parsers.site.en
|
||||
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
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.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import org.koitharu.kotatsu.parsers.util.json.mapJSONToSet
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("FAKKU", "Fakku", "en", ContentType.HENTAI)
|
||||
internal class Fakku(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaSource.FAKKU, pageSize = 25) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> =
|
||||
EnumSet.of(SortOrder.ALPHABETICAL, SortOrder.NEWEST, SortOrder.UPDATED)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("fakku.cc")
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
append("/search?q=")
|
||||
append(query.urlEncoded())
|
||||
append("&")
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/tags/")
|
||||
append(tag?.key.orEmpty())
|
||||
append("?")
|
||||
}
|
||||
|
||||
else -> {
|
||||
append("?")
|
||||
}
|
||||
}
|
||||
append("page=")
|
||||
append(page)
|
||||
append("&sort=")
|
||||
when (sortOrder) {
|
||||
SortOrder.ALPHABETICAL -> append("title")
|
||||
SortOrder.NEWEST -> append("created_at")
|
||||
SortOrder.UPDATED -> append("published_at")
|
||||
else -> append("published_at")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
return doc.select("div.entries .entry a").map { a ->
|
||||
val href = a.attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(domain),
|
||||
coverUrl = a.selectFirst("img")?.src().orEmpty(),
|
||||
title = a.selectFirst(".title")?.text().orEmpty(),
|
||||
altTitle = null,
|
||||
rating = RATING_UNKNOWN,
|
||||
tags = emptySet(),
|
||||
author = null,
|
||||
state = null,
|
||||
source = source,
|
||||
isNsfw = isNsfwSource,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
val root = webClient.httpGet("https://$domain/tags").parseHtml()
|
||||
return root.select("div.entries .entry a").mapToSet {
|
||||
MangaTag(
|
||||
key = it.attr("href").substringAfterLast("/"),
|
||||
title = it.selectFirstOrThrow(".name").text(),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
|
||||
val fullUrl = manga.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||
val genreDeferred = async {
|
||||
webClient.httpGet(manga.url.toAbsoluteUrl(domain) + ".json").parseJson()
|
||||
}
|
||||
val genre = genreDeferred.await()
|
||||
manga.copy(
|
||||
author = doc.selectFirst("tr.artists a")?.text(),
|
||||
tags = if (genre.toString().contains("tags")) {
|
||||
genre.getJSONArray("tags").mapJSONToSet {
|
||||
MangaTag(
|
||||
key = it.getString("slug"),
|
||||
title = it.getString("name"),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
emptySet()
|
||||
},
|
||||
chapters = listOf(
|
||||
MangaChapter(
|
||||
id = manga.id,
|
||||
name = manga.title,
|
||||
number = 1,
|
||||
url = manga.url + "/1",
|
||||
scanlator = null,
|
||||
uploadDate = 0,
|
||||
branch = null,
|
||||
source = source,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val doc = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
val totalPages = doc.selectFirstOrThrow(".total").text().toInt()
|
||||
val rawUrl = chapter.url.substringBeforeLast("/")
|
||||
return (1..totalPages).map {
|
||||
val url = "$rawUrl/$it"
|
||||
MangaPage(
|
||||
id = generateUid(url),
|
||||
url = url,
|
||||
preview = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPageUrl(page: MangaPage): String {
|
||||
val doc = webClient.httpGet(page.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
val root = doc.body()
|
||||
return root.selectFirstOrThrow(".page img").attrAsAbsoluteUrl("src")
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
package org.koitharu.kotatsu.parsers.site.en
|
||||
|
||||
import androidx.collection.ArraySet
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import org.json.JSONArray
|
||||
import org.jsoup.nodes.Element
|
||||
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.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import org.koitharu.kotatsu.parsers.util.json.mapJSON
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("KSKMOE", "Ksk.moe", "en", ContentType.HENTAI)
|
||||
internal class KskMoe(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.KSKMOE, 35) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> =
|
||||
EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST, SortOrder.ALPHABETICAL)
|
||||
override val configKeyDomain = ConfigKey.Domain("ksk.moe")
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
append("/tags/")
|
||||
append(tag?.key.orEmpty())
|
||||
} else {
|
||||
append("/browse")
|
||||
}
|
||||
|
||||
if (page > 1) {
|
||||
append("/page/")
|
||||
append(page)
|
||||
}
|
||||
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("?sort=32")
|
||||
SortOrder.UPDATED -> append("")
|
||||
SortOrder.NEWEST -> append("?sort=16")
|
||||
SortOrder.ALPHABETICAL -> append("?sort=1")
|
||||
else -> append("")
|
||||
}
|
||||
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append("?s=")
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
if (!doc.html().contains("pagination") && page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
return doc.requireElementById("galleries").select("article").map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
title = div.selectLastOrThrow("h3 span").text(),
|
||||
altTitle = null,
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(domain),
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = true,
|
||||
coverUrl = div.selectFirstOrThrow("img").src()?.toAbsoluteUrl(domain).orEmpty(),
|
||||
tags = div.select("footer span").mapNotNullToSet { span ->
|
||||
MangaTag(
|
||||
key = span.text().urlEncoded(),
|
||||
title = span.text(),
|
||||
source = source,
|
||||
)
|
||||
},
|
||||
state = null,
|
||||
author = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
return coroutineScope {
|
||||
(1..2).map { page ->
|
||||
async { getTags(page) }
|
||||
}
|
||||
}.awaitAll().flattenTo(ArraySet(360))
|
||||
}
|
||||
|
||||
private suspend fun getTags(page: Int): Set<MangaTag> {
|
||||
val url = if (page == 1) {
|
||||
"https://$domain/tags"
|
||||
} else {
|
||||
"https://$domain/tags/page/$page"
|
||||
}
|
||||
val root = webClient.httpGet(url).parseHtml().body().getElementById("tags")
|
||||
return root?.parseTags().orEmpty()
|
||||
}
|
||||
|
||||
private fun Element.parseTags() = select("section.tags div a").mapToSet { a ->
|
||||
MangaTag(
|
||||
key = a.attr("href").substringAfterLast("/tags/"),
|
||||
title = a.selectFirstOrThrow("span").text(),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
|
||||
private val date = SimpleDateFormat("dd.MM.yyyy hh:mm 'UTC'", Locale.US)
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
|
||||
return manga.copy(
|
||||
tags = doc.requireElementById("metadata").select("main div:contains(Tag) a").mapNotNullToSet { a ->
|
||||
MangaTag(
|
||||
key = a.attr("href").substringAfterLast("/tags/"),
|
||||
title = a.selectFirstOrThrow("span").text(),
|
||||
source = source,
|
||||
)
|
||||
},
|
||||
author = doc.requireElementById("metadata").selectFirstOrThrow("main div:contains(Artist) a span").text(),
|
||||
chapters = listOf(
|
||||
MangaChapter(
|
||||
id = generateUid(manga.id),
|
||||
name = manga.title,
|
||||
number = 1,
|
||||
url = manga.url,
|
||||
scanlator = null,
|
||||
uploadDate = date.tryParse(doc.selectFirstOrThrow("time.updated").text()),
|
||||
branch = null,
|
||||
source = source,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val fullUrl = chapter.url
|
||||
.replace("/view/", "/read/")
|
||||
.let { "$it/1" }
|
||||
.toAbsoluteUrl(domain)
|
||||
val document = webClient.httpGet(fullUrl).parseHtml()
|
||||
|
||||
val id = fullUrl
|
||||
.substringAfter("/read/")
|
||||
.substringBeforeLast("/")
|
||||
|
||||
val cdnUrl = document.selectFirst("meta[itemprop=image]")
|
||||
?.attr("content")
|
||||
?.toHttpUrlOrNull()
|
||||
?.host
|
||||
.let { "https://" + (it ?: domain) }
|
||||
|
||||
val script = document.select("script:containsData(window.metadata)").html()
|
||||
|
||||
val rawJson = script
|
||||
.substringAfter("original:")
|
||||
.substringBefore("resampled:")
|
||||
.substringBeforeLast(",")
|
||||
|
||||
return JSONArray(rawJson).mapJSON {
|
||||
val fileName = it.getString("n")
|
||||
|
||||
val url = "$cdnUrl/original/$id/$fileName"
|
||||
val preview = "$cdnUrl/t/$id/320/$fileName"
|
||||
|
||||
MangaPage(
|
||||
id = generateUid(url),
|
||||
url = url,
|
||||
preview = preview,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,42 +25,49 @@ internal class MangaGeko(context: MangaLoaderContext) : PagedMangaParser(context
|
||||
.add("User-Agent", UserAgents.CHROME_DESKTOP)
|
||||
.build()
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
val url = if (!query.isNullOrEmpty()) {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
buildString {
|
||||
append("https://$domain/search/?search=")
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
} else {
|
||||
buildString {
|
||||
append("https://$domain/browse-comics/?results=")
|
||||
append(page)
|
||||
append("&filter=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("Updated")
|
||||
SortOrder.NEWEST -> append("New")
|
||||
else -> append("Updated")
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
append("/search/?search=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
append("&genre=")
|
||||
append(tag?.key.orEmpty())
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
append("/browse-comics/?results=")
|
||||
append(page)
|
||||
|
||||
append("&filter=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("Updated")
|
||||
SortOrder.NEWEST -> append("New")
|
||||
else -> append("Updated")
|
||||
}
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("&genre=")
|
||||
append(it.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/browse-comics/?results=")
|
||||
append(page)
|
||||
append("&filter=Updated")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
return doc.select("li.novel-item").map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
|
||||
@@ -22,35 +22,69 @@ internal class MangaTownParser(context: MangaLoaderContext) : MangaParser(contex
|
||||
SortOrder.UPDATED,
|
||||
)
|
||||
|
||||
private val regexTag = Regex("[^\\-]+-[^\\-]+-[^\\-]+-[^\\-]+-[^\\-]+-[^\\-]+")
|
||||
override val availableStates: Set<MangaState> = EnumSet.of(
|
||||
MangaState.ONGOING,
|
||||
MangaState.FINISHED,
|
||||
)
|
||||
|
||||
override suspend fun getList(
|
||||
offset: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val sortKey = when (sortOrder) {
|
||||
SortOrder.ALPHABETICAL -> "?name.az"
|
||||
SortOrder.RATING -> "?rating.za"
|
||||
SortOrder.UPDATED -> "?last_chapter_time.za"
|
||||
else -> ""
|
||||
}
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getList(offset: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val page = (offset / 30) + 1
|
||||
val url = when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
if (offset != 0) {
|
||||
return emptyList()
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/search?name=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
"/search?name=${query.urlEncoded()}".toAbsoluteUrl(domain)
|
||||
}
|
||||
|
||||
tags.isNullOrEmpty() -> "/directory/$page.htm$sortKey".toAbsoluteUrl(domain)
|
||||
tags.size == 1 -> "/directory/${tags.first().key}/$page.htm$sortKey".toAbsoluteUrl(domain)
|
||||
else -> tags.joinToString(
|
||||
prefix = "/search?page=$page".toAbsoluteUrl(domain),
|
||||
) { tag ->
|
||||
"&genres[${tag.key}]=1"
|
||||
is MangaListFilter.Advanced -> {
|
||||
append("/directory/")
|
||||
append("0-")
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append(it.key)
|
||||
}
|
||||
} else {
|
||||
append("0")
|
||||
}
|
||||
append("-0-")
|
||||
|
||||
if (filter.states.isNotEmpty()) {
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "ongoing"
|
||||
MangaState.FINISHED -> "completed"
|
||||
else -> "0"
|
||||
},
|
||||
)
|
||||
}
|
||||
} else {
|
||||
append("0")
|
||||
}
|
||||
|
||||
append("-0-0/")
|
||||
append(page.toString())
|
||||
append(".htm")
|
||||
|
||||
append(
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> ""
|
||||
SortOrder.UPDATED -> "?last_chapter_time.za"
|
||||
SortOrder.ALPHABETICAL -> "?name.az"
|
||||
SortOrder.RATING -> "?rating.za"
|
||||
else -> "?last_chapter_time.za"
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
null -> append("/directory/$page.htm?last_chapter_time.za")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
@@ -81,7 +115,7 @@ internal class MangaTownParser(context: MangaLoaderContext) : MangaParser(contex
|
||||
tags = li.selectFirst("p.keyWord")?.select("a")?.mapNotNullToSet tags@{ x ->
|
||||
MangaTag(
|
||||
title = x.attr("title").toTitleCase(),
|
||||
key = x.attr("href").parseTagKey() ?: return@tags null,
|
||||
key = x.attr("href").substringAfter("/directory/0-").substringBefore("-0-"),
|
||||
source = MangaSource.MANGATOWN,
|
||||
)
|
||||
}.orEmpty(),
|
||||
@@ -106,7 +140,7 @@ internal class MangaTownParser(context: MangaLoaderContext) : MangaParser(contex
|
||||
}?.select("a")?.mapNotNull { a ->
|
||||
MangaTag(
|
||||
title = a.attr("title").toTitleCase(),
|
||||
key = a.attr("href").parseTagKey() ?: return@mapNotNull null,
|
||||
key = a.attr("href").substringAfter("/directory/0-").substringBefore("-0-"),
|
||||
source = MangaSource.MANGATOWN,
|
||||
)
|
||||
}.orEmpty(),
|
||||
@@ -165,10 +199,7 @@ internal class MangaTownParser(context: MangaLoaderContext) : MangaParser(contex
|
||||
?.nextElementSibling() ?: doc.parseFailed("Root not found")
|
||||
return root.select("li").mapNotNullToSet { li ->
|
||||
val a = li.selectFirst("a") ?: return@mapNotNullToSet null
|
||||
val key = a.attr("href").parseTagKey()
|
||||
if (key.isNullOrEmpty()) {
|
||||
return@mapNotNullToSet null
|
||||
}
|
||||
val key = a.attr("href").substringAfter("/directory/0-").substringBefore("-0-")
|
||||
MangaTag(
|
||||
source = MangaSource.MANGATOWN,
|
||||
key = key,
|
||||
@@ -211,6 +242,4 @@ internal class MangaTownParser(context: MangaLoaderContext) : MangaParser(contex
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.parseTagKey() = split('/').findLast { regexTag matches it }
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ internal class Mangaowl(context: MangaLoaderContext) :
|
||||
SortOrder.UPDATED,
|
||||
SortOrder.RATING,
|
||||
)
|
||||
override val availableStates: Set<MangaState> = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("mangaowl.to")
|
||||
|
||||
@@ -31,46 +32,56 @@ internal class Mangaowl(context: MangaLoaderContext) :
|
||||
.add("User-Agent", UserAgents.CHROME_DESKTOP)
|
||||
.build()
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val sort = when (sortOrder) {
|
||||
SortOrder.POPULARITY -> "view_count"
|
||||
SortOrder.UPDATED -> "-modified_at"
|
||||
SortOrder.NEWEST -> "created_at"
|
||||
SortOrder.RATING -> "rating"
|
||||
else -> "modified_at"
|
||||
}
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
append("/8-search")
|
||||
append("?q=")
|
||||
append(query.urlEncoded())
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/10-search?q=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/8-genres/")
|
||||
for (tag in tags) {
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
append("/10-comics")
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
|
||||
filter.tags.forEach { tag ->
|
||||
append("&genres=")
|
||||
append(tag.key)
|
||||
}
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&status=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "ongoing"
|
||||
MangaState.FINISHED -> "completed"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
append("&ordering=")
|
||||
append(
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> "view_count"
|
||||
SortOrder.UPDATED -> "-modified_at"
|
||||
SortOrder.NEWEST -> "created_at"
|
||||
SortOrder.RATING -> "rating"
|
||||
else -> "modified_at"
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
append("/8-comics")
|
||||
append("?page=")
|
||||
null -> {
|
||||
append("/10-comics?ordering=-modified_at&page=")
|
||||
append(page.toString())
|
||||
append("&ordering=")
|
||||
append(sort)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,9 +106,9 @@ internal class Mangaowl(context: MangaLoaderContext) :
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/8-genres").parseHtml()
|
||||
val doc = webClient.httpGet("https://$domain/10-genres").parseHtml()
|
||||
return doc.select("div.genres-container span.genre-item a").mapNotNullToSet { a ->
|
||||
val key = a.attr("href").substringAfterLast("/")
|
||||
val key = a.attr("href").removeSuffix('/').substringAfterLast('/').substringBefore("-")
|
||||
MangaTag(
|
||||
key = key,
|
||||
title = a.text(),
|
||||
@@ -112,7 +123,7 @@ internal class Mangaowl(context: MangaLoaderContext) :
|
||||
manga.copy(
|
||||
tags = doc.body().select("div.comic-attrs div.column.my-2:contains(Genres) a").mapNotNullToSet { a ->
|
||||
MangaTag(
|
||||
key = a.attr("href").removeSuffix("/").substringAfterLast('/'),
|
||||
key = a.attr("href").removeSuffix("/").substringAfterLast('/').substringBefore("-"),
|
||||
title = a.text().toTitleCase().replace(",", ""),
|
||||
source = source,
|
||||
)
|
||||
|
||||
@@ -9,14 +9,26 @@ import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("MANHWA18", "Manhwa18", "en", type = ContentType.HENTAI)
|
||||
@MangaSourceParser("MANHWA18", "Manhwa18.net", "en", type = ContentType.HENTAI)
|
||||
class Manhwa18Parser(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaSource.MANHWA18, pageSize = 18, searchPageSize = 18) {
|
||||
|
||||
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("manhwa18.net")
|
||||
|
||||
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.NEWEST,
|
||||
SortOrder.RATING,
|
||||
)
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.of(
|
||||
MangaState.ONGOING,
|
||||
MangaState.FINISHED,
|
||||
MangaState.PAUSED,
|
||||
)
|
||||
|
||||
private val tagsMap = SuspendLazy(::parseTags)
|
||||
|
||||
@@ -29,6 +41,82 @@ class Manhwa18Parser(context: MangaLoaderContext) :
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/tim-kiem?page=")
|
||||
append(page.toString())
|
||||
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("&q=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
append("&accept_genres=")
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append(
|
||||
filter.tags.joinToString(",") { it.key },
|
||||
)
|
||||
}
|
||||
|
||||
append("&sort=")
|
||||
append(
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.ALPHABETICAL -> "az"
|
||||
SortOrder.POPULARITY -> "top"
|
||||
SortOrder.UPDATED -> "update"
|
||||
SortOrder.NEWEST -> "new"
|
||||
SortOrder.RATING -> "like"
|
||||
},
|
||||
)
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&status=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "1"
|
||||
MangaState.FINISHED -> "3"
|
||||
MangaState.PAUSED -> "2"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
null -> append("&sort=update")
|
||||
}
|
||||
}
|
||||
|
||||
val docs = webClient.httpGet(url).parseHtml()
|
||||
|
||||
return docs.select(".card-body .thumb-item-flow")
|
||||
.map {
|
||||
val titleElement = it.selectFirstOrThrow(".thumb_attr.series-title > a")
|
||||
val absUrl = titleElement.attrAsAbsoluteUrl("href")
|
||||
Manga(
|
||||
id = generateUid(absUrl.toRelativeUrl(domain)),
|
||||
title = titleElement.text(),
|
||||
altTitle = null,
|
||||
url = absUrl.toRelativeUrl(domain),
|
||||
publicUrl = absUrl,
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = true,
|
||||
coverUrl = it.selectFirst("div.img-in-ratio")?.attrAsAbsoluteUrl("data-bg").orEmpty(),
|
||||
tags = emptySet(),
|
||||
state = null,
|
||||
author = null,
|
||||
largeCoverUrl = null,
|
||||
description = null,
|
||||
source = MangaSource.MANHWA18,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
val docs = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
val cardInfoElement = docs.selectFirst("div.series-information")
|
||||
@@ -45,6 +133,7 @@ class Manhwa18Parser(context: MangaLoaderContext) :
|
||||
when (it.text().lowercase()) {
|
||||
"on going" -> MangaState.ONGOING
|
||||
"completed" -> MangaState.FINISHED
|
||||
"on hold" -> MangaState.PAUSED
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@@ -99,60 +188,6 @@ class Manhwa18Parser(context: MangaLoaderContext) :
|
||||
return cal.time.time
|
||||
}
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val sortQuery = when (sortOrder) {
|
||||
SortOrder.ALPHABETICAL -> "az"
|
||||
SortOrder.POPULARITY -> "top"
|
||||
SortOrder.UPDATED -> "update"
|
||||
SortOrder.NEWEST -> "new"
|
||||
else -> ""
|
||||
}
|
||||
|
||||
val tagQuery = tags?.joinToString(",") { it.key }.orEmpty()
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/tim-kiem?page=")
|
||||
append(page)
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append("&q=")
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
append("&accept_genres=$tagQuery")
|
||||
append("&sort=")
|
||||
append(sortQuery)
|
||||
}
|
||||
|
||||
val docs = webClient.httpGet(url).parseHtml()
|
||||
|
||||
return docs.select(".card-body .thumb-item-flow")
|
||||
.map {
|
||||
val titleElement = it.selectFirstOrThrow(".thumb_attr.series-title > a")
|
||||
val absUrl = titleElement.attrAsAbsoluteUrl("href")
|
||||
Manga(
|
||||
id = generateUid(absUrl.toRelativeUrl(domain)),
|
||||
title = titleElement.text(),
|
||||
altTitle = null,
|
||||
url = absUrl.toRelativeUrl(domain),
|
||||
publicUrl = absUrl,
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = true,
|
||||
coverUrl = it.selectFirst("div.img-in-ratio")?.attrAsAbsoluteUrl("data-bg").orEmpty(),
|
||||
tags = emptySet(),
|
||||
state = null,
|
||||
author = null,
|
||||
largeCoverUrl = null,
|
||||
description = null,
|
||||
source = MangaSource.MANHWA18,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val chapterUrl = chapter.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(chapterUrl).parseHtml()
|
||||
|
||||
@@ -21,29 +21,42 @@ class ManhwasMen(context: MangaLoaderContext) :
|
||||
override val availableSortOrders: Set<SortOrder>
|
||||
get() = EnumSet.of(SortOrder.POPULARITY)
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override val availableStates: Set<MangaState> = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/manga-list")
|
||||
append("?page=")
|
||||
append(page)
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
append(page.toString())
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("&search=")
|
||||
append(query.urlEncoded())
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("&genero=")
|
||||
append(tag?.key.orEmpty())
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("&genero=")
|
||||
append(it.key)
|
||||
}
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&estado=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "ongoing"
|
||||
MangaState.FINISHED -> "complete"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
null -> {}
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
@@ -89,9 +102,9 @@ class ManhwasMen(context: MangaLoaderContext) :
|
||||
)
|
||||
},
|
||||
description = doc.select(".sinopsis").html(),
|
||||
state = when (doc.selectLast(".anime-type-peli")?.text()?.lowercase()) {
|
||||
state = when (doc.selectLast("span.anime-type-peli")?.text()?.lowercase()) {
|
||||
"ongoing" -> MangaState.ONGOING
|
||||
"completed" -> MangaState.FINISHED
|
||||
"complete" -> MangaState.FINISHED
|
||||
else -> null
|
||||
},
|
||||
chapters = doc.select(".episodes-list li").mapChapters(reversed = true) { i, li ->
|
||||
|
||||
@@ -15,20 +15,28 @@ internal class Po2Scans(context: MangaLoaderContext) : MangaParser(context, Mang
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL)
|
||||
override val configKeyDomain = ConfigKey.Domain("po2scans.com")
|
||||
|
||||
override suspend fun getList(offset: Int, query: String?, tags: Set<MangaTag>?, sortOrder: SortOrder): List<Manga> {
|
||||
override suspend fun getList(offset: Int, filter: MangaListFilter?): List<Manga> {
|
||||
if (offset > 0) {
|
||||
return emptyList()
|
||||
}
|
||||
val url = buildString {
|
||||
append("https://$domain/series")
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append("?search=")
|
||||
append(query.urlEncoded())
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/series")
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("?search=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {}
|
||||
|
||||
null -> {}
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
return doc.select(".series-list").map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
val href = "/" + div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
title = div.selectFirstOrThrow("h2").text(),
|
||||
|
||||
@@ -24,37 +24,43 @@ internal class Pururin(context: MangaLoaderContext) :
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append("/search?q=")
|
||||
append(query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page)
|
||||
} else {
|
||||
append("/browse")
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
append("/tags/content/")
|
||||
append(tag?.key.orEmpty())
|
||||
append("/")
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/search?q=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
append("?page=")
|
||||
append(page)
|
||||
append("&sort=")
|
||||
when (sortOrder) {
|
||||
SortOrder.UPDATED -> append("")
|
||||
SortOrder.POPULARITY -> append("most-viewed")
|
||||
SortOrder.RATING -> append("highest-rated")
|
||||
SortOrder.ALPHABETICAL -> append("title")
|
||||
else -> append("")
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
append("/browse")
|
||||
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("/tags/content/")
|
||||
append(it.key)
|
||||
append("/")
|
||||
}
|
||||
|
||||
append("?page=")
|
||||
append(page)
|
||||
|
||||
append("&sort=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.UPDATED -> append("")
|
||||
SortOrder.POPULARITY -> append("most-viewed")
|
||||
SortOrder.RATING -> append("highest-rated")
|
||||
SortOrder.ALPHABETICAL -> append("title")
|
||||
else -> append("")
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/browse?page=")
|
||||
append(page)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.network.UserAgents
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("TEMPLESCANESP", "TempleScanEsp", "es", ContentType.HENTAI)
|
||||
@@ -24,22 +25,29 @@ internal class TempleScanEsp(context: MangaLoaderContext) :
|
||||
.add("User-Agent", UserAgents.CHROME_DESKTOP)
|
||||
.build()
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
if (sortOrder == SortOrder.NEWEST) {
|
||||
append("/comics")
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
} else {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
throw IllegalArgumentException("Search is not supported by this source")
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
if (filter.sortOrder == SortOrder.NEWEST) {
|
||||
append("/comics?page=")
|
||||
append(page.toString())
|
||||
} else {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/comics?page=")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,41 +32,47 @@ class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser(
|
||||
SortOrder.RATING,
|
||||
)
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
|
||||
val order =
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> "likes_count"
|
||||
SortOrder.UPDATED -> "release_date"
|
||||
SortOrder.NEWEST -> "creation"
|
||||
SortOrder.ALPHABETICAL -> "alphabetically"
|
||||
SortOrder.RATING -> "score"
|
||||
|
||||
}
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/library")
|
||||
if (query.isNullOrEmpty()) {
|
||||
append("?order_item=")
|
||||
append(order)
|
||||
append("&order_dir=desc")
|
||||
append("&filter_by=title")
|
||||
if (tags != null) {
|
||||
for (tag in tags) {
|
||||
append("&genders[]=${tag.key}")
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
append("?title=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
append("?order_item=")
|
||||
append(
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> "likes_count"
|
||||
SortOrder.UPDATED -> "release_date"
|
||||
SortOrder.NEWEST -> "creation"
|
||||
SortOrder.ALPHABETICAL -> "alphabetically"
|
||||
SortOrder.RATING -> "score"
|
||||
|
||||
},
|
||||
)
|
||||
append("&order_dir=desc")
|
||||
append("&filter_by=title")
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
for (tag in filter.tags) {
|
||||
append("&genders[]=")
|
||||
append(tag.key)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
append("?title=$query")
|
||||
}
|
||||
append("&_pg=1")
|
||||
append("&page=$page")
|
||||
}.toAbsoluteUrl(domain)
|
||||
|
||||
null -> {
|
||||
append("?order_item=release_date&order_dir=desc&filter_by=title")
|
||||
}
|
||||
}
|
||||
append("&_pg=1&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
val doc = webClient.httpGet(url, headers).parseHtml()
|
||||
val items = doc.body().select("div.element")
|
||||
return items.mapNotNull { item ->
|
||||
@@ -95,6 +101,13 @@ class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser(
|
||||
val contents = doc.body().selectFirstOrThrow("section.element-header-content")
|
||||
return manga.copy(
|
||||
description = contents.selectFirst("p.element-description")?.html(),
|
||||
tags = contents.select("h6 a").mapNotNullToSet { a ->
|
||||
MangaTag(
|
||||
key = a.attr("href").substringBefore("&").substringAfterLast("="),
|
||||
title = a.text(),
|
||||
source = source,
|
||||
)
|
||||
},
|
||||
largeCoverUrl = contents.selectFirst(".book-thumbnail")?.attrAsAbsoluteUrlOrNull("src"),
|
||||
state = parseStatus(contents.select("span.book-status").text().orEmpty()),
|
||||
author = contents.selectFirst("h5.card-title")?.attr("title")?.substringAfter(", "),
|
||||
@@ -105,9 +118,9 @@ class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser(
|
||||
} else {
|
||||
val chapters = ChaptersListBuilder(10)
|
||||
doc.select(regularChapterListSelector).reversed().forEachIndexed { i, item ->
|
||||
val chaptername = item.select("div.col-10.text-truncate").text().replace(" ", " ").trim()
|
||||
val scanelement = item.select("ul.chapter-list > li")
|
||||
scanelement.forEach { chapters.add(regularChapterFromElement(it, chaptername, i)) }
|
||||
val chapterName = item.select("div.col-10.text-truncate").text().replace(" ", " ").trim()
|
||||
val scanElement = item.select("ul.chapter-list > li")
|
||||
scanElement.forEach { chapters.add(regularChapterFromElement(it, chapterName, i)) }
|
||||
}
|
||||
chapters.toList()
|
||||
},
|
||||
|
||||
@@ -27,7 +27,11 @@ internal abstract class FmreaderParser(
|
||||
SortOrder.ALPHABETICAL,
|
||||
)
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
override val availableStates: Set<MangaState> = EnumSet.of(
|
||||
MangaState.ONGOING,
|
||||
MangaState.FINISHED,
|
||||
MangaState.ABANDONED,
|
||||
)
|
||||
|
||||
protected open val listUrl = "/manga-list.html"
|
||||
protected open val datePattern = "MMMM d, yyyy"
|
||||
@@ -40,58 +44,73 @@ internal abstract class FmreaderParser(
|
||||
|
||||
@JvmField
|
||||
protected val ongoing: Set<String> = setOf(
|
||||
"On going",
|
||||
"Incomplete",
|
||||
"En curso",
|
||||
"on going",
|
||||
"incomplete",
|
||||
"en curso",
|
||||
)
|
||||
|
||||
@JvmField
|
||||
protected val finished: Set<String> = setOf(
|
||||
"Completed",
|
||||
"Completado",
|
||||
"completed",
|
||||
"completado",
|
||||
)
|
||||
|
||||
@JvmField
|
||||
protected val abandoned: Set<String> = hashSetOf(
|
||||
"Canceled",
|
||||
"Cancelled",
|
||||
"Drop",
|
||||
"canceled",
|
||||
"cancelled",
|
||||
"drop",
|
||||
)
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append(listUrl)
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("&name=")
|
||||
append(query.urlEncoded())
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
append("&genre=")
|
||||
append(tag?.key.orEmpty())
|
||||
append(filter.tags.joinToString(",") { it.key })
|
||||
|
||||
|
||||
append("&sort=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("last_update")
|
||||
SortOrder.ALPHABETICAL -> append("name")
|
||||
else -> append("last_update")
|
||||
}
|
||||
|
||||
append("&m_status=")
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "2"
|
||||
MangaState.FINISHED -> "1"
|
||||
MangaState.ABANDONED -> "3"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
append("&sort=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("last_update")
|
||||
SortOrder.ALPHABETICAL -> append("name")
|
||||
else -> append("last_update")
|
||||
|
||||
null -> append("&sort=last_update")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
|
||||
}
|
||||
|
||||
protected open fun parseMangaList(doc: Document): List<Manga> {
|
||||
return doc.select("div.thumb-item-flow").map { div ->
|
||||
val href = div.selectFirstOrThrow("div.series-title a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
@@ -99,8 +118,8 @@ internal abstract class FmreaderParser(
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(div.host ?: domain),
|
||||
coverUrl = div.selectFirstOrThrow("div.img-in-ratio").attr("data-bg")
|
||||
?: div.selectFirstOrThrow("div.img-in-ratio").attr("style").substringAfter("('")
|
||||
.substringBeforeLast("')"),
|
||||
?: div.selectFirstOrThrow("div.img-in-ratio").attr("style").substringAfter("(")
|
||||
.substringBefore(")"),
|
||||
title = div.selectFirstOrThrow("div.series-title").text().orEmpty(),
|
||||
altTitle = null,
|
||||
rating = RATING_UNKNOWN,
|
||||
@@ -140,7 +159,7 @@ internal abstract class FmreaderParser(
|
||||
val desc = doc.selectFirst(selectDesc)?.html()
|
||||
val stateDiv = doc.selectFirst(selectState)
|
||||
val state = stateDiv?.let {
|
||||
when (it.text()) {
|
||||
when (it.text().lowercase()) {
|
||||
in ongoing -> MangaState.ONGOING
|
||||
in finished -> MangaState.FINISHED
|
||||
in abandoned -> MangaState.ABANDONED
|
||||
|
||||
@@ -20,79 +20,67 @@ internal class Manhwa18Com(context: MangaLoaderContext) :
|
||||
override val selectTag = "div.info-item:contains(Genre) span.info-value a"
|
||||
override val datePattern = "dd/MM/yyyy"
|
||||
override val selectPage = "div#chapter-content img"
|
||||
override val selectBodyTag = "div.genres-menu a"
|
||||
override val selectBodyTag = "div.advanced-wrapper .genre_label"
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
append("/genre/")
|
||||
append(tag?.key.orEmpty())
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
append("&sort=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("last_update")
|
||||
SortOrder.ALPHABETICAL -> append("name")
|
||||
else -> append("last_update")
|
||||
}
|
||||
} else {
|
||||
append(listUrl)
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
append("&q=")
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
}
|
||||
append("&sort=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("last_update")
|
||||
SortOrder.ALPHABETICAL -> append("name")
|
||||
else -> append("last_update")
|
||||
append("/tim-kiem?page=")
|
||||
append(page.toString())
|
||||
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("&q=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
append("&accept_genres=")
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append(
|
||||
filter.tags.joinToString(",") { it.key },
|
||||
)
|
||||
}
|
||||
|
||||
append("&sort=")
|
||||
append(
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.ALPHABETICAL -> "az"
|
||||
SortOrder.POPULARITY -> "top"
|
||||
SortOrder.UPDATED -> "update"
|
||||
SortOrder.NEWEST -> "new"
|
||||
SortOrder.RATING -> "like"
|
||||
},
|
||||
)
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&status=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "1"
|
||||
MangaState.FINISHED -> "3"
|
||||
MangaState.PAUSED -> "2"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
null -> append("&sort=update")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
return doc.select("div.thumb-item-flow").map { div ->
|
||||
val href = div.selectFirstOrThrow("div.series-title a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(div.host ?: domain),
|
||||
coverUrl = div.selectFirstOrThrow("div.img-in-ratio").attr("data-bg")
|
||||
?: div.selectFirstOrThrow("div.img-in-ratio").attr("style").substringAfter("('")
|
||||
.substringBeforeLast("')"),
|
||||
title = div.selectFirstOrThrow("div.series-title").text().orEmpty(),
|
||||
altTitle = null,
|
||||
rating = RATING_UNKNOWN,
|
||||
tags = emptySet(),
|
||||
author = null,
|
||||
state = null,
|
||||
source = source,
|
||||
isNsfw = isNsfwSource,
|
||||
)
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml()
|
||||
return doc.select(selectBodyTag).mapNotNullToSet { a ->
|
||||
val href = a.attr("href").substringAfterLast("/")
|
||||
return doc.select(selectBodyTag).mapNotNullToSet { label ->
|
||||
val key = label.attr("data-genre-id")
|
||||
MangaTag(
|
||||
key = href,
|
||||
title = a.text(),
|
||||
key = key,
|
||||
title = label.selectFirstOrThrow(".gerne-name").text(),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
@@ -105,7 +93,7 @@ internal class Manhwa18Com(context: MangaLoaderContext) :
|
||||
val desc = doc.selectFirstOrThrow(selectDesc).html()
|
||||
val stateDiv = doc.selectFirst(selectState)
|
||||
val state = stateDiv?.let {
|
||||
when (it.text()) {
|
||||
when (it.text().lowercase()) {
|
||||
in ongoing -> MangaState.ONGOING
|
||||
in finished -> MangaState.FINISHED
|
||||
else -> null
|
||||
|
||||
@@ -14,37 +14,60 @@ internal class OlimpoScans(context: MangaLoaderContext) :
|
||||
override val selectAlt = "ul.manga-info li:contains(Otros nombres)"
|
||||
override val selectTag = "ul.manga-info li:contains(Género) a"
|
||||
override val tagPrefix = "lista-de-comics-genero-"
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append(listUrl)
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append(listUrl)
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
append("&name=")
|
||||
append(query.urlEncoded())
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("&genre=")
|
||||
append(tag?.key.orEmpty())
|
||||
is MangaListFilter.Advanced -> {
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("/lista-de-comics-genero-")
|
||||
append(it.key)
|
||||
append(".html")
|
||||
}
|
||||
} else {
|
||||
append(listUrl)
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
append("&sort=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("last_update")
|
||||
SortOrder.ALPHABETICAL -> append("name")
|
||||
else -> append("last_update")
|
||||
}
|
||||
}
|
||||
|
||||
append("&m_status=")
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "2"
|
||||
MangaState.FINISHED -> "1"
|
||||
MangaState.ABANDONED -> "3"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append(listUrl)
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
append("&sort=last_update")
|
||||
}
|
||||
}
|
||||
append("&sort=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("last_update")
|
||||
SortOrder.ALPHABETICAL -> append("name")
|
||||
else -> append("last_update")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
@@ -3,13 +3,7 @@ package org.koitharu.kotatsu.parsers.site.fmreader.ja
|
||||
import org.jsoup.nodes.Document
|
||||
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.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.model.*
|
||||
import org.koitharu.kotatsu.parsers.site.fmreader.FmreaderParser
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.text.SimpleDateFormat
|
||||
@@ -27,40 +21,7 @@ internal class Klz9(context: MangaLoaderContext) :
|
||||
override val selectPage = "img"
|
||||
override val selectBodyTag = "div.panel-body a"
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/$listUrl")
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
|
||||
append("&name=")
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("&genre=")
|
||||
append(tag?.key.orEmpty())
|
||||
}
|
||||
}
|
||||
append("&sort=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("last_update")
|
||||
SortOrder.ALPHABETICAL -> append("name")
|
||||
else -> append("last_update")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
override fun parseMangaList(doc: Document): List<Manga> {
|
||||
return doc.select("div.thumb-item-flow").map { div ->
|
||||
val href = "/" + div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
@@ -112,7 +73,6 @@ internal class Klz9(context: MangaLoaderContext) :
|
||||
val docLoad = webClient.httpGet("https://$domain/app/manga/controllers/cont.listImg.php?cid=$cid").parseHtml()
|
||||
return docLoad.select(selectPage).map { img ->
|
||||
val url = img.src()?.toRelativeUrl(domain) ?: img.parseFailed("Image src not found")
|
||||
|
||||
MangaPage(
|
||||
id = generateUid(url),
|
||||
url = url,
|
||||
|
||||
@@ -32,35 +32,63 @@ internal abstract class FoolSlideParser(
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val doc = if (!query.isNullOrEmpty()) {
|
||||
val url = buildString {
|
||||
append("https://$domain/$searchUrl")
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
val q = query.urlEncoded()
|
||||
webClient.httpPost(url, "search=$q").parseHtml()
|
||||
} else {
|
||||
val url = buildString {
|
||||
append("https://$domain/$listUrl")
|
||||
// For some sites that don't have enough manga and page 2 links to page 1
|
||||
if (!pagination) {
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val doc =
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
} else {
|
||||
append(page.toString())
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/")
|
||||
append(searchUrl)
|
||||
}
|
||||
|
||||
webClient.httpPost(url, "search=${filter.query.urlEncoded()}").parseHtml()
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/")
|
||||
append(listUrl)
|
||||
// For some sites that don't have enough manga and page 2 links to page 1
|
||||
if (!pagination) {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
} else {
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
webClient.httpGet(url).parseHtml()
|
||||
|
||||
}
|
||||
|
||||
null -> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/")
|
||||
append(listUrl)
|
||||
if (!pagination) {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
} else {
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
webClient.httpGet(url).parseHtml()
|
||||
|
||||
}
|
||||
}
|
||||
webClient.httpGet(url).parseHtml()
|
||||
}
|
||||
|
||||
return doc.select("div.list div.group").map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
|
||||
@@ -16,40 +16,30 @@ internal class AssortedScans(context: MangaLoaderContext) :
|
||||
override val pagination = false
|
||||
override val selectInfo = "div.#series-info"
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
|
||||
val doc = if (!query.isNullOrEmpty()) {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append('/')
|
||||
append(searchUrl)
|
||||
append("?q=")
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
webClient.httpGet(url).parseHtml()
|
||||
} else {
|
||||
val url = buildString {
|
||||
append("https://$domain/$listUrl")
|
||||
// For some sites that don't have enough manga and page 2 links to page 1
|
||||
if (!pagination) {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
} else {
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
webClient.httpGet(url).parseHtml()
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append('/')
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append(searchUrl)
|
||||
append("?q=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
append(listUrl)
|
||||
}
|
||||
|
||||
null -> append(listUrl)
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
return doc.select("section.series, tr.result").map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
|
||||
@@ -5,7 +5,7 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.site.foolslide.FoolSlideParser
|
||||
|
||||
@MangaSourceParser("HNISCANTRAD", "Hni Scantrad", "fr")
|
||||
@MangaSourceParser("HNISCANTRAD", "HniScantrad", "fr")
|
||||
internal class HniScantrad(context: MangaLoaderContext) :
|
||||
FoolSlideParser(context, MangaSource.HNISCANTRAD, "hni-scantrad.com") {
|
||||
override val pagination = false
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package org.koitharu.kotatsu.parsers.site.foolslide.it
|
||||
|
||||
|
||||
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.foolslide.FoolSlideParser
|
||||
|
||||
|
||||
@MangaSourceParser("POWERMANGA", "PowerManga", "it")
|
||||
internal class PowerManga(context: MangaLoaderContext) :
|
||||
FoolSlideParser(context, MangaSource.POWERMANGA, "reader.powermanga.org") {
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package org.koitharu.kotatsu.parsers.site.foolslide.it
|
||||
|
||||
|
||||
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.foolslide.FoolSlideParser
|
||||
|
||||
|
||||
@MangaSourceParser("RAMAREADER", "RamaReader", "it")
|
||||
internal class Ramareader(context: MangaLoaderContext) :
|
||||
FoolSlideParser(context, MangaSource.RAMAREADER, "www.ramareader.it") {
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
package org.koitharu.kotatsu.parsers.site.foolslide.it
|
||||
|
||||
|
||||
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.foolslide.FoolSlideParser
|
||||
|
||||
|
||||
@MangaSourceParser("READNIFTEAM", "ReadNifTeam", "it")
|
||||
internal class ReadNifteam(context: MangaLoaderContext) :
|
||||
FoolSlideParser(context, MangaSource.READNIFTEAM, "read-nifteam.info") {
|
||||
|
||||
@@ -28,35 +28,55 @@ internal class BentomangaParser(context: MangaLoaderContext) : PagedMangaParser(
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("bentomanga.com", "www.bentomanga.com")
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
|
||||
|
||||
init {
|
||||
paginator.firstPage = 0
|
||||
searchPaginator.firstPage = 0
|
||||
}
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = urlBuilder()
|
||||
.host(domain)
|
||||
.addPathSegment("manga_list")
|
||||
.addQueryParameter("limit", page.toString())
|
||||
.addQueryParameter(
|
||||
"order_by",
|
||||
when (sortOrder) {
|
||||
SortOrder.UPDATED -> "update"
|
||||
SortOrder.POPULARITY -> "views"
|
||||
SortOrder.RATING -> "top"
|
||||
SortOrder.NEWEST -> "create"
|
||||
SortOrder.ALPHABETICAL -> "name"
|
||||
},
|
||||
)
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
url.addQueryParameter("withCategories", tags.joinToString(",") { it.key })
|
||||
}
|
||||
if (!query.isNullOrEmpty()) {
|
||||
url.addQueryParameter("search", query)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
url.addQueryParameter("search", filter.query)
|
||||
}
|
||||
|
||||
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"
|
||||
},
|
||||
)
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
url.addQueryParameter("withCategories", filter.tags.joinToString(",") { it.key })
|
||||
}
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
url.addQueryParameter(
|
||||
"state",
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "1"
|
||||
MangaState.FINISHED -> "2"
|
||||
MangaState.PAUSED -> "3"
|
||||
MangaState.ABANDONED -> "5"
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
null -> url.addQueryParameter("order_by", "update")
|
||||
}
|
||||
val root = webClient.httpGet(url.build()).parseHtml().requireElementById("mangas_content")
|
||||
return root.select(".manga[data-manga]").map { div ->
|
||||
@@ -109,7 +129,7 @@ internal class BentomangaParser(context: MangaLoaderContext) : PagedMangaParser(
|
||||
"En pause" -> MangaState.PAUSED
|
||||
else -> null
|
||||
},
|
||||
author = root.selectFirst(".datas_more-authors-people")?.textOrNull().assertNotNull("author"),
|
||||
author = root.selectFirst(".datas_more-authors-people")?.textOrNull(),
|
||||
chapters = run {
|
||||
val input = root.selectFirst("input[name=\"limit\"]") ?: return@run parseChapters(root)
|
||||
val max = input.attr("max").toInt()
|
||||
|
||||
@@ -18,51 +18,83 @@ internal class FmTeam(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaSource.FMTEAM, 0) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL)
|
||||
override val availableStates: Set<MangaState> = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED)
|
||||
override val configKeyDomain = ConfigKey.Domain("fmteam.fr")
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
val jsonManga = if (!query.isNullOrEmpty()) {
|
||||
//3 letters minimum
|
||||
webClient.httpGet("https://$domain/api/search/${query.urlEncoded()}").parseJson().getJSONArray("comics")
|
||||
} else {
|
||||
webClient.httpGet("https://$domain/api/comics").parseJson().getJSONArray("comics")
|
||||
}
|
||||
var foundTag = true
|
||||
var foundState = true
|
||||
|
||||
val manga = ArrayList<Manga>(jsonManga.length())
|
||||
for (i in 0 until jsonManga.length()) {
|
||||
val j = jsonManga.getJSONObject(i)
|
||||
val href = "/api" + j.getString("url")
|
||||
when {
|
||||
!tags.isNullOrEmpty() -> {
|
||||
val a = j.getJSONArray("genres").toString()
|
||||
var found = true
|
||||
tags.forEach {
|
||||
if (!a.contains(it.key, ignoreCase = true)) {
|
||||
found = false
|
||||
val manga = ArrayList<Manga>()
|
||||
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
val jsonManga = webClient.httpGet("https://$domain/api/search/${filter.query.urlEncoded()}").parseJson()
|
||||
.getJSONArray("comics")
|
||||
for (i in 0 until jsonManga.length()) {
|
||||
val j = jsonManga.getJSONObject(i)
|
||||
val href = "/api" + j.getString("url")
|
||||
manga.add(addManga(href, j))
|
||||
}
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
val jsonManga = webClient.httpGet("https://$domain/api/comics").parseJson().getJSONArray("comics")
|
||||
for (i in 0 until jsonManga.length()) {
|
||||
|
||||
val j = jsonManga.getJSONObject(i)
|
||||
val href = "/api" + j.getString("url")
|
||||
|
||||
if (filter.tags.isNotEmpty() && filter.states.isEmpty()) {
|
||||
val a = j.getJSONArray("genres").toString()
|
||||
foundTag = false
|
||||
filter.tags.forEach {
|
||||
if (a.contains(it.key, ignoreCase = true)) {
|
||||
foundTag = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
manga.add(
|
||||
addManga(href, j),
|
||||
)
|
||||
|
||||
if (filter.states.isNotEmpty()) {
|
||||
val a = j.getString("status")
|
||||
foundState = false
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
if (a.contains(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "En cours"
|
||||
MangaState.FINISHED -> "Terminé"
|
||||
else -> ""
|
||||
},
|
||||
ignoreCase = true,
|
||||
)
|
||||
) {
|
||||
foundState = true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (foundState && foundTag) {
|
||||
manga.add(addManga(href, j))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
null -> {
|
||||
val jsonManga = webClient.httpGet("https://$domain/api/comics").parseJson().getJSONArray("comics")
|
||||
for (i in 0 until jsonManga.length()) {
|
||||
val j = jsonManga.getJSONObject(i)
|
||||
val href = "/api" + j.getString("url")
|
||||
manga.add(
|
||||
addManga(href, j),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return manga
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.network.UserAgents
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
@@ -35,21 +36,27 @@ internal class FuryoSociety(context: MangaLoaderContext) :
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
if (page == 1) {
|
||||
if (sortOrder == SortOrder.ALPHABETICAL) {
|
||||
append("/mangas")
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
throw IllegalArgumentException("Search is not supported by this source")
|
||||
}
|
||||
} else {
|
||||
return emptyList()
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
if (filter.sortOrder == SortOrder.ALPHABETICAL) {
|
||||
append("/mangas")
|
||||
}
|
||||
}
|
||||
|
||||
null -> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.koitharu.kotatsu.parsers.site.fr
|
||||
|
||||
import okhttp3.Headers
|
||||
import org.json.JSONObject
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
||||
@@ -16,9 +17,7 @@ import java.util.*
|
||||
internal class LegacyScansParser(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaSource.LEGACY_SCANS, 18) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.POPULARITY,
|
||||
)
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.POPULARITY)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("legacy-scans.com")
|
||||
|
||||
@@ -26,86 +25,96 @@ internal class LegacyScansParser(context: MangaLoaderContext) :
|
||||
.add("User-Agent", UserAgents.CHROME_MOBILE)
|
||||
.build()
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val end = page * pageSize
|
||||
val start = end - (pageSize - 1)
|
||||
val url = if (!query.isNullOrEmpty()) {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
buildString {
|
||||
append("https://api.$domain/misc/home/search?title=")
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
} else {
|
||||
buildString {
|
||||
append("https://api.$domain/misc/comic/search/query?status=&order=&genreNames=")
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
for (tag in tags) {
|
||||
append(tag.key)
|
||||
append(',')
|
||||
}
|
||||
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
append("&type=&start=")
|
||||
append(start)
|
||||
append("&end=")
|
||||
append(end)
|
||||
val url = buildString {
|
||||
append("https://api.$domain/misc/home/search?title=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
return parseMangaListQuery(webClient.httpGet(url).parseJson())
|
||||
}
|
||||
|
||||
}
|
||||
val json = webClient.httpGet(url).parseJson()
|
||||
return if (!query.isNullOrEmpty()) {
|
||||
json.getJSONArray("results").mapJSON { j ->
|
||||
val slug = j.getString("slug")
|
||||
val urlManga = "https://$domain/comics/$slug"
|
||||
Manga(
|
||||
id = generateUid(urlManga),
|
||||
title = j.getString("title"),
|
||||
altTitle = null,
|
||||
url = urlManga,
|
||||
publicUrl = urlManga,
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = false,
|
||||
coverUrl = "",
|
||||
tags = setOf(),
|
||||
state = null,
|
||||
author = null,
|
||||
source = source,
|
||||
)
|
||||
is MangaListFilter.Advanced -> {
|
||||
val url = buildString {
|
||||
append("https://api.")
|
||||
append(domain)
|
||||
append("/misc/comic/search/query?status=&order=&genreNames=")
|
||||
append(filter.tags.joinToString(",") { it.key })
|
||||
append("&type=&start=")
|
||||
append(start)
|
||||
append("&end=")
|
||||
append(end)
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseJson())
|
||||
}
|
||||
} else {
|
||||
json.getJSONArray("comics").mapJSON { j ->
|
||||
val slug = j.getString("slug")
|
||||
val urlManga = "https://$domain/comics/$slug"
|
||||
Manga(
|
||||
id = generateUid(urlManga),
|
||||
title = j.getString("title"),
|
||||
altTitle = null,
|
||||
url = urlManga,
|
||||
publicUrl = urlManga,
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = false,
|
||||
coverUrl = "https://api.$domain/" + j.getString("cover"),
|
||||
tags = setOf(),
|
||||
state = null,
|
||||
author = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
val url = buildString {
|
||||
append("https://api.")
|
||||
append(domain)
|
||||
append("/misc/comic/search/query?status=&order=&genreNames=&type=&start=")
|
||||
append(start)
|
||||
append("&end=")
|
||||
append(end)
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseJson())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun parseMangaList(json: JSONObject): List<Manga> {
|
||||
return json.getJSONArray("comics").mapJSON { j ->
|
||||
val slug = j.getString("slug")
|
||||
val urlManga = "https://$domain/comics/$slug"
|
||||
Manga(
|
||||
id = generateUid(urlManga),
|
||||
title = j.getString("title"),
|
||||
altTitle = null,
|
||||
url = urlManga,
|
||||
publicUrl = urlManga,
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = false,
|
||||
coverUrl = "https://api.$domain/" + j.getString("cover"),
|
||||
tags = setOf(),
|
||||
state = null,
|
||||
author = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseMangaListQuery(json: JSONObject): List<Manga> {
|
||||
return json.getJSONArray("results").mapJSON { j ->
|
||||
val slug = j.getString("slug")
|
||||
val urlManga = "https://$domain/comics/$slug"
|
||||
Manga(
|
||||
id = generateUid(urlManga),
|
||||
title = j.getString("title"),
|
||||
altTitle = null,
|
||||
url = urlManga,
|
||||
publicUrl = urlManga,
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = false,
|
||||
coverUrl = "",
|
||||
tags = setOf(),
|
||||
state = null,
|
||||
author = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
val root = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.FRENCH)
|
||||
|
||||
return manga.copy(
|
||||
altTitle = null,
|
||||
tags = root.select("div.serieGenre span").mapNotNullToSet { span ->
|
||||
@@ -156,7 +165,6 @@ internal class LegacyScansParser(context: MangaLoaderContext) :
|
||||
val script = doc.requireElementById("__NUXT_DATA__").data()
|
||||
.substringAfterLast("\"genres\"").substringBeforeLast("\"comics\"")
|
||||
.split("\",\"").drop(1)
|
||||
|
||||
return script.mapNotNullToSet { tag ->
|
||||
MangaTag(
|
||||
key = tag.substringBeforeLast("\",{"),
|
||||
|
||||
@@ -24,36 +24,50 @@ internal class LireScan(context: MangaLoaderContext) : PagedMangaParser(context,
|
||||
.add("User-Agent", UserAgents.CHROME_MOBILE)
|
||||
.build()
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
val doc =
|
||||
if (!query.isNullOrEmpty()) { // search only works with 4 or more letters
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
val q = query.urlEncoded().replace("%20", "+")
|
||||
val post = "do=search&subaction=search&search_start=0&full_search=0&result_from=1&story=$q"
|
||||
webClient.httpPost("https://$domain/index.php?do=search", post).parseHtml()
|
||||
} else {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
append("/manga/")
|
||||
append(tag?.key.orEmpty())
|
||||
}
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
if (page > 1) {
|
||||
append("/page/")
|
||||
append(page)
|
||||
append('/')
|
||||
return emptyList()
|
||||
}
|
||||
val q = filter.query.urlEncoded().replace("%20", "+")
|
||||
val post = "do=search&subaction=search&search_start=0&full_search=0&result_from=1&story=$q"
|
||||
webClient.httpPost("https://$domain/index.php?do=search", post).parseHtml()
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("/manga/")
|
||||
append(it.key)
|
||||
}
|
||||
|
||||
if (page > 1) {
|
||||
append("/page/")
|
||||
append(page)
|
||||
append('/')
|
||||
}
|
||||
}
|
||||
webClient.httpGet(url).parseHtml()
|
||||
}
|
||||
|
||||
null -> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
if (page > 1) {
|
||||
append("/page/")
|
||||
append(page)
|
||||
append('/')
|
||||
}
|
||||
}
|
||||
webClient.httpGet(url).parseHtml()
|
||||
}
|
||||
webClient.httpGet(url).parseHtml()
|
||||
}
|
||||
|
||||
return doc.select("div.sect__content.grid-items div.item-poster").map { div ->
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.koitharu.kotatsu.parsers.site.fr
|
||||
|
||||
import okhttp3.Headers
|
||||
import org.json.JSONArray
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
||||
@@ -21,6 +22,8 @@ internal class LugnicaScans(context: MangaLoaderContext) : PagedMangaParser(cont
|
||||
SortOrder.UPDATED,
|
||||
)
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("lugnica-scans.com")
|
||||
|
||||
override val headers: Headers = Headers.Builder()
|
||||
@@ -43,77 +46,102 @@ internal class LugnicaScans(context: MangaLoaderContext) : PagedMangaParser(cont
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
if (!query.isNullOrEmpty()) {
|
||||
throw IllegalArgumentException("Search is not supported by this source")
|
||||
}
|
||||
if (sortOrder == SortOrder.ALPHABETICAL) {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/api/get/catalog?page=0&filter=all")
|
||||
}
|
||||
val json = webClient.httpGet(url).parseJsonArray()
|
||||
return json.mapJSON { j ->
|
||||
val urlManga = "https://$domain/api/get/card/${j.getString("slug")}"
|
||||
val img = "https://$domain/upload/min_cover/${j.getString("image")}"
|
||||
Manga(
|
||||
id = generateUid(urlManga),
|
||||
title = j.getString("title"),
|
||||
altTitle = null,
|
||||
url = urlManga,
|
||||
publicUrl = urlManga.toAbsoluteUrl(domain),
|
||||
rating = j.getString("rate").toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN,
|
||||
isNsfw = false,
|
||||
coverUrl = img,
|
||||
tags = setOf(),
|
||||
state = when (j.getString("status")) {
|
||||
"0" -> MangaState.ONGOING
|
||||
"1" -> MangaState.FINISHED
|
||||
"3" -> MangaState.ABANDONED
|
||||
else -> null
|
||||
},
|
||||
author = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/api/get/homegrid/")
|
||||
append(page)
|
||||
}
|
||||
val json = webClient.httpGet(url).parseJsonArray()
|
||||
return json.mapJSON { j ->
|
||||
val urlManga = "https://$domain/api/get/card/${j.getString("manga_slug")}"
|
||||
val img = "https://$domain/upload/min_cover/${j.getString("manga_image")}"
|
||||
Manga(
|
||||
id = generateUid(urlManga),
|
||||
title = j.getString("manga_title"),
|
||||
altTitle = null,
|
||||
url = urlManga,
|
||||
publicUrl = urlManga.toAbsoluteUrl(domain),
|
||||
rating = j.getString("manga_rate").toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN,
|
||||
isNsfw = false,
|
||||
coverUrl = img,
|
||||
tags = setOf(),
|
||||
state = null,
|
||||
author = null,
|
||||
source = source,
|
||||
)
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
throw IllegalArgumentException("Search is not supported by this source")
|
||||
}
|
||||
|
||||
}
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
if (filter.sortOrder == SortOrder.ALPHABETICAL) {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/api/get/catalog?page=0&filter=")
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
when (it) {
|
||||
MangaState.ONGOING -> append("0")
|
||||
MangaState.FINISHED -> append("1")
|
||||
MangaState.PAUSED -> append("4")
|
||||
MangaState.ABANDONED -> append("3")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
return parseMangaListAlpha(webClient.httpGet(url).parseJsonArray())
|
||||
} else {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/api/get/homegrid/")
|
||||
append(page)
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseJsonArray())
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/api/get/homegrid/")
|
||||
append(page)
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseJsonArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseMangaList(json: JSONArray): List<Manga> {
|
||||
return json.mapJSON { j ->
|
||||
val urlManga = "https://$domain/api/get/card/${j.getString("manga_slug")}"
|
||||
val img = "https://$domain/upload/min_cover/${j.getString("manga_image")}"
|
||||
Manga(
|
||||
id = generateUid(urlManga),
|
||||
title = j.getString("manga_title"),
|
||||
altTitle = null,
|
||||
url = urlManga,
|
||||
publicUrl = urlManga.toAbsoluteUrl(domain),
|
||||
rating = j.getString("manga_rate").toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN,
|
||||
isNsfw = false,
|
||||
coverUrl = img,
|
||||
tags = setOf(),
|
||||
state = null,
|
||||
author = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseMangaListAlpha(json: JSONArray): List<Manga> {
|
||||
return json.mapJSON { j ->
|
||||
val urlManga = "https://$domain/api/get/card/${j.getString("slug")}"
|
||||
val img = "https://$domain/upload/min_cover/${j.getString("image")}"
|
||||
Manga(
|
||||
id = generateUid(urlManga),
|
||||
title = j.getString("title"),
|
||||
altTitle = null,
|
||||
url = urlManga,
|
||||
publicUrl = urlManga.toAbsoluteUrl(domain),
|
||||
rating = j.getString("rate").toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN,
|
||||
isNsfw = false,
|
||||
coverUrl = img,
|
||||
tags = setOf(),
|
||||
state = when (j.getString("status")) {
|
||||
"0" -> MangaState.ONGOING
|
||||
"1" -> MangaState.FINISHED
|
||||
"3" -> MangaState.ABANDONED
|
||||
else -> null
|
||||
},
|
||||
author = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
|
||||
@@ -30,46 +30,45 @@ internal class ScansMangasMe(context: MangaLoaderContext) :
|
||||
.add("User-Agent", UserAgents.CHROME_DESKTOP)
|
||||
.build()
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
if (page == 1) {
|
||||
if (!query.isNullOrEmpty()) {
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/?s=")
|
||||
append(query.urlEncoded())
|
||||
append(filter.query.urlEncoded())
|
||||
append("&post_type=manga")
|
||||
}
|
||||
|
||||
} else if (!tags.isNullOrEmpty()) {
|
||||
append("/genres/")
|
||||
for (tag in tags) {
|
||||
append(tag.key)
|
||||
}
|
||||
} else {
|
||||
append("/tous-nos-mangas/")
|
||||
append("?order=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("popular")
|
||||
SortOrder.UPDATED -> append("update")
|
||||
SortOrder.ALPHABETICAL -> append("title")
|
||||
SortOrder.NEWEST -> append("create")
|
||||
else -> append("update")
|
||||
is MangaListFilter.Advanced -> {
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("/genres/")
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append(it.key)
|
||||
}
|
||||
} else {
|
||||
append("/tous-nos-mangas/?order=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("popular")
|
||||
SortOrder.UPDATED -> append("update")
|
||||
SortOrder.ALPHABETICAL -> append("title")
|
||||
SortOrder.NEWEST -> append("create")
|
||||
else -> append("update")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
null -> append("/tous-nos-mangas/?order=update")
|
||||
}
|
||||
}
|
||||
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
return doc.select("div.postbody .bs .bsx").map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
@@ -90,7 +89,6 @@ internal class ScansMangasMe(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/tous-nos-mangas/").parseHtml()
|
||||
return doc.select("ul.genre li").mapNotNullToSet { li ->
|
||||
@@ -104,25 +102,18 @@ internal class ScansMangasMe(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
|
||||
val fullUrl = manga.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||
|
||||
val chaptersDeferred = getChapters(doc)
|
||||
|
||||
val desc = doc.selectFirstOrThrow("div.desc").html()
|
||||
|
||||
val state = if (doc.select("div.spe span:contains(En cours)").isNullOrEmpty()) {
|
||||
MangaState.FINISHED
|
||||
} else {
|
||||
MangaState.ONGOING
|
||||
}
|
||||
|
||||
val alt = doc.body().select("div.infox span.alter").text()
|
||||
|
||||
val aut = doc.select("div.spe span")[2].text().replace("Auteur:", "")
|
||||
|
||||
manga.copy(
|
||||
tags = doc.select("div.spe span:contains(Genres) a").mapNotNullToSet { a ->
|
||||
MangaTag(
|
||||
@@ -140,7 +131,6 @@ internal class ScansMangasMe(context: MangaLoaderContext) :
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private fun getChapters(doc: Document): List<MangaChapter> {
|
||||
return doc.body().requireElementById("chapter_list").select("li").mapChapters(reversed = true) { i, li ->
|
||||
val a = li.selectFirstOrThrow("a")
|
||||
@@ -161,10 +151,8 @@ internal class ScansMangasMe(context: MangaLoaderContext) :
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val fullUrl = chapter.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||
|
||||
val script = doc.selectFirstOrThrow("script:containsData(page_image)")
|
||||
val images = JSONArray(script.data().substringAfterLast("var pages = ").substringBefore(';'))
|
||||
|
||||
val pages = ArrayList<MangaPage>(images.length())
|
||||
for (i in 0 until images.length()) {
|
||||
val pageTake = images.getJSONObject(i)
|
||||
@@ -177,7 +165,6 @@ internal class ScansMangasMe(context: MangaLoaderContext) :
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return pages
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,49 +20,54 @@ internal class ScantradUnion(context: MangaLoaderContext) : PagedMangaParser(con
|
||||
SortOrder.UPDATED,
|
||||
)
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("scantrad-union.com")
|
||||
|
||||
override val headers: Headers = Headers.Builder()
|
||||
.add("User-Agent", UserAgents.CHROME_DESKTOP)
|
||||
.build()
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/page/")
|
||||
append(page.toString())
|
||||
append("/?s=")
|
||||
append(query.urlEncoded())
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/tag/")
|
||||
for (tag in tags) {
|
||||
append(tag.key)
|
||||
append(',')
|
||||
is MangaListFilter.Advanced -> {
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("/tag/")
|
||||
append(it.key)
|
||||
append("/page/")
|
||||
append(page.toString())
|
||||
append("/")
|
||||
}
|
||||
} else {
|
||||
if (filter.sortOrder == SortOrder.ALPHABETICAL) {
|
||||
append("/manga/page/")
|
||||
append(page.toString())
|
||||
append("/")
|
||||
}
|
||||
|
||||
if (filter.sortOrder == SortOrder.UPDATED && page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
}
|
||||
append("/page/")
|
||||
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/manga/page/")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
else -> {
|
||||
if (sortOrder == SortOrder.ALPHABETICAL) {
|
||||
append("/manga/")
|
||||
append("/page/")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
if (sortOrder == SortOrder.UPDATED) {
|
||||
append("")
|
||||
}
|
||||
append("/")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -180,9 +185,8 @@ internal class ScantradUnion(context: MangaLoaderContext) : PagedMangaParser(con
|
||||
val root = body.select(".asp_gochosen")[1]
|
||||
val list = root?.select("option").orEmpty()
|
||||
return list.mapToSet { li ->
|
||||
|
||||
MangaTag(
|
||||
key = li.text(),
|
||||
key = li.text().lowercase().replace(" ", "-"),
|
||||
title = li.text(),
|
||||
source = source,
|
||||
)
|
||||
|
||||
@@ -24,36 +24,42 @@ internal abstract class GalleryAdultsParser(
|
||||
override val configKeyDomain = ConfigKey.Domain(domain)
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
if (tag?.key == "languageKey") {
|
||||
append("/language")
|
||||
append(tag.title)
|
||||
append("/?")
|
||||
} else {
|
||||
append("/tag/")
|
||||
append(tag?.key.orEmpty())
|
||||
append("/?")
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/search/?q=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&")
|
||||
}
|
||||
} else if (!query.isNullOrEmpty()) {
|
||||
append("/search/?q=")
|
||||
append(query.urlEncoded())
|
||||
append("&")
|
||||
} else {
|
||||
append("/?")
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
if (it.key == "languageKey") {
|
||||
append("/language")
|
||||
append(it.title)
|
||||
append("/?")
|
||||
} else {
|
||||
append("/tag/")
|
||||
append(it.key)
|
||||
append("/?")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
append("/?")
|
||||
}
|
||||
}
|
||||
|
||||
null -> append("/?")
|
||||
}
|
||||
append("page=")
|
||||
append(page)
|
||||
}
|
||||
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
|
||||
|
||||
@@ -31,45 +31,58 @@ internal class Hentai3(context: MangaLoaderContext) :
|
||||
"/japanese",
|
||||
)
|
||||
|
||||
override val sortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
|
||||
override val isMultipleTagsSupported = true
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
if (query.isNullOrEmpty() && tags != null && tags.size > 1) {
|
||||
return getListPage(page, buildQuery(tags), emptySet(), sortOrder)
|
||||
}
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
val tag = tags.single()
|
||||
if (tag.key == "languageKey") {
|
||||
append("/language")
|
||||
append(tag.title)
|
||||
} else {
|
||||
append("/tags/")
|
||||
append(tag.key)
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
append("/search?q=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
append("/")
|
||||
append(page)
|
||||
if (sortOrder == SortOrder.POPULARITY) {
|
||||
append("?sort=popular")
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
if (filter.tags.isNotEmpty() && filter.tags.size > 1) {
|
||||
append("/search?q=")
|
||||
append(buildQuery(filter.tags))
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
append("&sort=popular")
|
||||
}
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
} else if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
if (it.key == "languageKey") {
|
||||
append("/language")
|
||||
append(it.title)
|
||||
} else {
|
||||
append("/tags/")
|
||||
append(it.key)
|
||||
}
|
||||
}
|
||||
append("/")
|
||||
append(page.toString())
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
append("?sort=popular")
|
||||
}
|
||||
} else {
|
||||
append("/")
|
||||
append(page)
|
||||
}
|
||||
}
|
||||
} else if (!query.isNullOrEmpty()) {
|
||||
append("/search?q=")
|
||||
append(query.urlEncoded())
|
||||
if (sortOrder == SortOrder.POPULARITY) {
|
||||
append("&sort=popular")
|
||||
|
||||
null -> {
|
||||
append("/")
|
||||
append(page)
|
||||
}
|
||||
append("&page=")
|
||||
append(page)
|
||||
} else {
|
||||
append("/")
|
||||
append(page)
|
||||
}
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
|
||||
@@ -32,40 +32,45 @@ internal class HentaiEnvy(context: MangaLoaderContext) :
|
||||
"/portuguese",
|
||||
)
|
||||
|
||||
override val sortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
if (tag?.key == "languageKey") {
|
||||
append("/language")
|
||||
append(tag.title)
|
||||
append("/?")
|
||||
} else {
|
||||
append("/tag/")
|
||||
append(tag?.key.orEmpty())
|
||||
if (sortOrder == SortOrder.POPULARITY) {
|
||||
append("/popular")
|
||||
}
|
||||
append("/?")
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/search/?s_key=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&")
|
||||
}
|
||||
} else if (!query.isNullOrEmpty()) {
|
||||
append("/search/?s_key=")
|
||||
append(query.urlEncoded())
|
||||
append("&")
|
||||
} else {
|
||||
append("/?")
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
if (it.key == "languageKey") {
|
||||
append("/language")
|
||||
append(it.title)
|
||||
append("/?")
|
||||
} else {
|
||||
append("/tag/")
|
||||
append(it.key)
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
append("/popular")
|
||||
}
|
||||
append("/?")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
append("/?")
|
||||
}
|
||||
}
|
||||
|
||||
null -> append("/?")
|
||||
}
|
||||
append("page=")
|
||||
append(page)
|
||||
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
|
||||
@@ -25,7 +25,9 @@ internal class HentaiEra(context: MangaLoaderContext) :
|
||||
"/russian",
|
||||
)
|
||||
|
||||
override val sortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
|
||||
|
||||
override val isMultipleTagsSupported = true
|
||||
|
||||
override fun Element.parseTags() = select("a.tag, .gallery_title a").mapToSet {
|
||||
val key = it.attr("href").removeSuffix('/').substringAfterLast('/')
|
||||
@@ -37,46 +39,54 @@ internal class HentaiEra(context: MangaLoaderContext) :
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
if (query.isNullOrEmpty() && tags != null && tags.size > 1) {
|
||||
return getListPage(page, buildQuery(tags), emptySet(), sortOrder)
|
||||
}
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
val tag = tags.single()
|
||||
if (tag.key == "languageKey") {
|
||||
append("/language")
|
||||
append(tag.title)
|
||||
} else {
|
||||
append("/tag/")
|
||||
append(tag.key)
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
append("/search/?key=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&")
|
||||
}
|
||||
append("/")
|
||||
if (sortOrder == SortOrder.POPULARITY) {
|
||||
append("popular/")
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
if (filter.tags.isNotEmpty() && filter.tags.size > 1) {
|
||||
append("/search/?key=")
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
append(buildQuery(filter.tags).replace("<=1&dl=0&pp=0&tr=0", "<=0&dl=0&pp=1&tr=0"))
|
||||
} else {
|
||||
append(buildQuery(filter.tags))
|
||||
}
|
||||
append("&")
|
||||
} else if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
if (it.key == "languageKey") {
|
||||
append("/language")
|
||||
append(it.title)
|
||||
} else {
|
||||
append("/tag/")
|
||||
append(it.key)
|
||||
}
|
||||
}
|
||||
append("/")
|
||||
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
append("popular/")
|
||||
}
|
||||
append("?")
|
||||
} else {
|
||||
append("/?")
|
||||
}
|
||||
}
|
||||
append("?")
|
||||
} else if (!query.isNullOrEmpty()) {
|
||||
append("/search/?key=")
|
||||
if (sortOrder == SortOrder.POPULARITY) {
|
||||
append(query.replace("<=1&dl=0&pp=0&tr=0", "<=0&dl=0&pp=1&tr=0"))
|
||||
} else {
|
||||
append(query)
|
||||
}
|
||||
append("&")
|
||||
} else {
|
||||
append("/?")
|
||||
|
||||
null -> append("/?")
|
||||
}
|
||||
append("page=")
|
||||
append(page)
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
|
||||
|
||||
@@ -35,50 +35,60 @@ internal class HentaiForce(context: MangaLoaderContext) :
|
||||
"/vietnamese",
|
||||
)
|
||||
|
||||
override val sortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
|
||||
override val isMultipleTagsSupported = true
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
|
||||
|
||||
override suspend fun getPageUrl(page: MangaPage): String {
|
||||
val doc = webClient.httpGet(page.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
return doc.selectFirstOrThrow(idImg).src() ?: doc.parseFailed("Image src not found")
|
||||
}
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
if (query.isNullOrEmpty() && tags != null && tags.size > 1) {
|
||||
return getListPage(page, buildQuery(tags), emptySet(), sortOrder)
|
||||
}
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
val tag = tags.single()
|
||||
if (tag.key == "languageKey") {
|
||||
append("/language")
|
||||
append(tag.title)
|
||||
} else {
|
||||
append("/tag/")
|
||||
append(tag.key)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/search?q=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
}
|
||||
if (sortOrder == SortOrder.POPULARITY) {
|
||||
append("/popular")
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
if (filter.tags.isNotEmpty() && filter.tags.size > 1) {
|
||||
append("/search?q=")
|
||||
append(buildQuery(filter.tags))
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
append("&sort=popular")
|
||||
}
|
||||
append("&page=")
|
||||
} else if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
if (it.key == "languageKey") {
|
||||
append("/language")
|
||||
append(it.title)
|
||||
} else {
|
||||
append("/tag/")
|
||||
append(it.key)
|
||||
}
|
||||
}
|
||||
append("/")
|
||||
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
append("popular/")
|
||||
}
|
||||
append("?")
|
||||
} else {
|
||||
append("/page/")
|
||||
}
|
||||
}
|
||||
append("/")
|
||||
} else if (!query.isNullOrEmpty()) {
|
||||
append("/search?q=")
|
||||
append(query.urlEncoded())
|
||||
if (sortOrder == SortOrder.POPULARITY) {
|
||||
append("&sort=popular")
|
||||
}
|
||||
append("&page=")
|
||||
} else {
|
||||
append("/page/")
|
||||
|
||||
null -> append("/page/")
|
||||
}
|
||||
append(page)
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
|
||||
|
||||
@@ -32,59 +32,83 @@ internal class HentaiFox(context: MangaLoaderContext) :
|
||||
"/vietnamese",
|
||||
)
|
||||
|
||||
override val sortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
|
||||
override val isMultipleTagsSupported = true
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
if (query.isNullOrEmpty() && tags != null && tags.size > 1) {
|
||||
return getListPage(page, buildQuery(tags), emptySet(), sortOrder)
|
||||
}
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
val tag = tags.single()
|
||||
if (tag.key == "languageKey") {
|
||||
append("/language")
|
||||
append(tag.title)
|
||||
} else {
|
||||
append("/tag/")
|
||||
append(tag.key)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/search/?q=")
|
||||
append(filter.query.urlEncoded())
|
||||
if (page > 1) {
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
if (sortOrder == SortOrder.POPULARITY) {
|
||||
append("/popular")
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
if (filter.tags.isNotEmpty() && filter.tags.size > 1) {
|
||||
append("/search/?q=")
|
||||
append(buildQuery(filter.tags))
|
||||
if (page > 1) {
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
append("&sort=popular")
|
||||
}
|
||||
} else if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
if (it.key == "languageKey") {
|
||||
append("/language")
|
||||
append(it.title)
|
||||
} else {
|
||||
append("/tag/")
|
||||
append(it.key)
|
||||
}
|
||||
}
|
||||
append("/")
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
append("popular/")
|
||||
}
|
||||
|
||||
if (page > 1) {
|
||||
append("/pag/")
|
||||
append(page.toString())
|
||||
append("/")
|
||||
}
|
||||
} else {
|
||||
if (page > 2) {
|
||||
append("/pag/")
|
||||
append(page.toString())
|
||||
append("/")
|
||||
} else if (page > 1) {
|
||||
append("/page/")
|
||||
append(page.toString())
|
||||
append("/")
|
||||
}
|
||||
}
|
||||
}
|
||||
if (page > 1) {
|
||||
append("/pag/")
|
||||
append(page)
|
||||
append("/")
|
||||
}
|
||||
} else if (!query.isNullOrEmpty()) {
|
||||
append("/search/?q=")
|
||||
append(query.urlEncoded())
|
||||
if (sortOrder == SortOrder.POPULARITY) {
|
||||
append("&sort=popular")
|
||||
}
|
||||
if (page > 1) {
|
||||
append("&page=")
|
||||
append(page)
|
||||
}
|
||||
} else {
|
||||
if (page > 2) {
|
||||
append("/pag/")
|
||||
append(page)
|
||||
append("/")
|
||||
} else if (page > 1) {
|
||||
append("/page/")
|
||||
append(page)
|
||||
append("/")
|
||||
|
||||
null -> {
|
||||
if (page > 2) {
|
||||
append("/pag/")
|
||||
append(page.toString())
|
||||
append("/")
|
||||
} else if (page > 1) {
|
||||
append("/page/")
|
||||
append(page.toString())
|
||||
append("/")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
|
||||
|
||||
@@ -27,50 +27,60 @@ internal class NHentaiParser(context: MangaLoaderContext) :
|
||||
"/chinese",
|
||||
)
|
||||
|
||||
override val sortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY)
|
||||
|
||||
override val isMultipleTagsSupported = true
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
if (query.isNullOrEmpty() && tags != null && tags.size > 1) {
|
||||
return getListPage(page, buildQuery(tags), emptySet(), sortOrder)
|
||||
}
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
val tag = tags.single()
|
||||
if (tag.key == "languageKey") {
|
||||
append("/language")
|
||||
append(tag.title)
|
||||
} else {
|
||||
append("/tag/")
|
||||
append(tag.key)
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
append("/search/?q=pages:>0 ")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&")
|
||||
}
|
||||
append("/")
|
||||
if (sortOrder == SortOrder.POPULARITY) {
|
||||
append("popular")
|
||||
}
|
||||
append("?")
|
||||
} else if (!query.isNullOrEmpty()) {
|
||||
append("/search/?q=")
|
||||
append(query.urlEncoded())
|
||||
if (sortOrder == SortOrder.POPULARITY) {
|
||||
append("&sort=popular")
|
||||
}
|
||||
append("&")
|
||||
} else {
|
||||
if (sortOrder == SortOrder.POPULARITY) {
|
||||
append("/?sort=popular&")
|
||||
} else {
|
||||
append("/?")
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
if (filter.tags.isNotEmpty() && filter.tags.size > 1) {
|
||||
append("/search/?q=")
|
||||
append(buildQuery(filter.tags))
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
append("&sort=popular")
|
||||
}
|
||||
append("&")
|
||||
} else if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
if (it.key == "languageKey") {
|
||||
append("/language")
|
||||
append(it.title)
|
||||
} else {
|
||||
append("/tag/")
|
||||
append(it.key)
|
||||
}
|
||||
}
|
||||
append("/")
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
append("popular/")
|
||||
}
|
||||
|
||||
append("?")
|
||||
} else {
|
||||
if (filter.sortOrder == SortOrder.POPULARITY) {
|
||||
append("/?sort=popular&")
|
||||
} else {
|
||||
append("/?")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
null -> append("/?")
|
||||
}
|
||||
append("page=")
|
||||
append(page)
|
||||
append(page.toString())
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
|
||||
@@ -33,33 +33,39 @@ internal class NHentaiUk(context: MangaLoaderContext) :
|
||||
"/turkish",
|
||||
)
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
if (tag?.key == "languageKey") {
|
||||
append("/language")
|
||||
append(tag.title)
|
||||
append("/?p=")
|
||||
} else {
|
||||
append("/tag/")
|
||||
append(tag?.key)
|
||||
append("/?p=")
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
throw IllegalArgumentException("Search is not supported by this source")
|
||||
}
|
||||
} else if (!query.isNullOrEmpty()) {
|
||||
throw IllegalArgumentException("Search is not supported by this source")
|
||||
} else {
|
||||
append("/home?p=")
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
if (it.key == "languageKey") {
|
||||
append("/language")
|
||||
append(it.title)
|
||||
} else {
|
||||
append("/tag/")
|
||||
append(it.key)
|
||||
}
|
||||
}
|
||||
append("/?p=")
|
||||
} else {
|
||||
append("/home?p=")
|
||||
}
|
||||
}
|
||||
|
||||
null -> append("/?")
|
||||
}
|
||||
append(page)
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@ internal abstract class HeanCms(
|
||||
SortOrder.POPULARITY,
|
||||
)
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
|
||||
|
||||
override val headers: Headers = Headers.Builder()
|
||||
.add("User-Agent", UserAgents.CHROME_DESKTOP)
|
||||
.build()
|
||||
@@ -34,50 +36,54 @@ internal abstract class HeanCms(
|
||||
protected open val pathManga = "series"
|
||||
|
||||
//For some sources, you need to send a json. For the moment, this part only works in Get. ( ex source need json gloriousscan.com , omegascans.org )
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
|
||||
var firstTag = false
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://api.")
|
||||
append(domain)
|
||||
append("/query?query_string=")
|
||||
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
|
||||
append("&series_status=All&order=desc&orderBy=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("total_views")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("created_at")
|
||||
SortOrder.ALPHABETICAL -> append("title")
|
||||
else -> append("latest")
|
||||
}
|
||||
|
||||
append("&series_type=Comic&page=")
|
||||
append(page)
|
||||
append("&perPage=12&tags_ids=")
|
||||
append("[".urlEncoded())
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
for (tag in tags) {
|
||||
// Just to make it fit [1,2,44] ect
|
||||
if (!firstTag) {
|
||||
firstTag = true
|
||||
} else {
|
||||
append(",")
|
||||
}
|
||||
append(tag.key)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&series_status=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "Ongoing"
|
||||
MangaState.FINISHED -> "Completed"
|
||||
MangaState.ABANDONED -> "Dropped"
|
||||
MangaState.PAUSED -> "Hiatus"
|
||||
},
|
||||
)
|
||||
|
||||
}
|
||||
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")
|
||||
}
|
||||
append("&series_type=Comic&perPage=12")
|
||||
append("&tags_ids=")
|
||||
append("[".urlEncoded())
|
||||
append(filter.tags.joinToString(",") { it.key })
|
||||
append("]".urlEncoded())
|
||||
|
||||
}
|
||||
|
||||
null -> {}
|
||||
}
|
||||
append("]".urlEncoded())
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
val json = webClient.httpGet(url).parseJson()
|
||||
|
||||
return json.getJSONArray("data").mapJSON { j ->
|
||||
val slug = j.getString("series_slug")
|
||||
val urlManga = "https://$domain/$pathManga/$slug"
|
||||
@@ -107,8 +113,10 @@ internal abstract class HeanCms(
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected open val datePattern = "yyyy-MM-dd"
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
val root = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
|
||||
@@ -2,19 +2,10 @@ package org.koitharu.kotatsu.parsers.site.heancms.es
|
||||
|
||||
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.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaState
|
||||
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.model.*
|
||||
import org.koitharu.kotatsu.parsers.site.heancms.HeanCms
|
||||
import org.koitharu.kotatsu.parsers.util.domain
|
||||
import org.koitharu.kotatsu.parsers.util.generateUid
|
||||
import org.koitharu.kotatsu.parsers.util.json.mapJSON
|
||||
import org.koitharu.kotatsu.parsers.util.parseJson
|
||||
import org.koitharu.kotatsu.parsers.util.urlEncoded
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
|
||||
@MangaSourceParser("YUGEN_MANGAS_ES", "YugenMangas.lat", "es", ContentType.HENTAI)
|
||||
internal class YugenMangasEs(context: MangaLoaderContext) :
|
||||
@@ -22,46 +13,55 @@ internal class YugenMangasEs(context: MangaLoaderContext) :
|
||||
|
||||
private val domainAlt = "yugenmangas.net"
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
var firstTag = false
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://api.")
|
||||
append(domainAlt)
|
||||
append("/query?query_string=")
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
append("&series_status=All&order=desc&orderBy=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("total_views")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("created_at")
|
||||
SortOrder.ALPHABETICAL -> append("title")
|
||||
else -> append("latest")
|
||||
}
|
||||
append("&series_type=Comic&page=")
|
||||
append(page)
|
||||
append("&perPage=12&tags_ids=")
|
||||
append("[".urlEncoded())
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
for (tag in tags) {
|
||||
// Just to make it fit [1,2,44] ect
|
||||
if (!firstTag) {
|
||||
firstTag = true
|
||||
} else {
|
||||
append(",")
|
||||
}
|
||||
append(tag.key)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&series_status=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "Ongoing"
|
||||
MangaState.FINISHED -> "Completed"
|
||||
MangaState.ABANDONED -> "Dropped"
|
||||
MangaState.PAUSED -> "Hiatus"
|
||||
},
|
||||
)
|
||||
|
||||
}
|
||||
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")
|
||||
}
|
||||
append("&series_type=Comic&perPage=12")
|
||||
append("&tags_ids=")
|
||||
append("[".urlEncoded())
|
||||
append(filter.tags.joinToString(",") { it.key })
|
||||
append("]".urlEncoded())
|
||||
}
|
||||
|
||||
null -> {}
|
||||
}
|
||||
append("]".urlEncoded())
|
||||
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
val json = webClient.httpGet(url).parseJson()
|
||||
|
||||
return json.getJSONArray("data").mapJSON { j ->
|
||||
val slug = j.getString("series_slug")
|
||||
val urlManga = "https://$domain/series/$slug"
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.koitharu.kotatsu.parsers.PagedMangaParser
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
@@ -33,23 +34,24 @@ internal abstract class HeanCmsAlt(
|
||||
protected open val selectManga = "div.grid.grid-cols-2 div:not([class]):contains(M)"
|
||||
protected open val selectMangaTitle = "h5"
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
// No search or tag
|
||||
if (!query.isNullOrEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append(listUrl)
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
throw IllegalArgumentException("Search is not supported by this source")
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
}
|
||||
|
||||
null -> {}
|
||||
}
|
||||
if (page > 1) {
|
||||
append("?page=")
|
||||
append(page)
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
@@ -18,6 +18,78 @@ class DoujinDesuParser(context: MangaLoaderContext) : PagedMangaParser(context,
|
||||
override val availableSortOrders: Set<SortOrder>
|
||||
get() = EnumSet.of(SortOrder.UPDATED, SortOrder.NEWEST, SortOrder.ALPHABETICAL, SortOrder.POPULARITY)
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED)
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = urlBuilder().apply {
|
||||
addPathSegment("manga")
|
||||
addPathSegment("page")
|
||||
addPathSegment("$page/")
|
||||
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
addQueryParameter("title", filter.query)
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
addQueryParameter("title", "")
|
||||
addQueryParameter(
|
||||
"order",
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.UPDATED -> "update"
|
||||
SortOrder.POPULARITY -> "popular"
|
||||
SortOrder.ALPHABETICAL -> "title"
|
||||
SortOrder.NEWEST -> "latest"
|
||||
else -> "latest"
|
||||
},
|
||||
)
|
||||
|
||||
filter.tags.forEach {
|
||||
addEncodedQueryParameter("genre[]".urlEncoded(), it.key.urlEncoded())
|
||||
}
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
addEncodedQueryParameter(
|
||||
"statusx",
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "Publishing"
|
||||
MangaState.FINISHED -> "Finished"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
null -> addQueryParameter("order", "update")
|
||||
}
|
||||
}.build()
|
||||
|
||||
return webClient.httpGet(url).parseHtml()
|
||||
.requireElementById("archives")
|
||||
.selectFirstOrThrow("div.entries")
|
||||
.select(".entry")
|
||||
.map {
|
||||
val titleTag = it.selectFirstOrThrow(".metadata > a")
|
||||
val relativeUrl = titleTag.attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
id = generateUid(relativeUrl),
|
||||
title = titleTag.attr("title"),
|
||||
altTitle = null,
|
||||
url = relativeUrl,
|
||||
publicUrl = relativeUrl.toAbsoluteUrl(domain),
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = true,
|
||||
coverUrl = it.selectFirst(".thumbnail > img")?.src().orEmpty(),
|
||||
tags = emptySet(),
|
||||
state = null,
|
||||
author = null,
|
||||
largeCoverUrl = null,
|
||||
description = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
val docs = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml().selectFirstOrThrow("#archive")
|
||||
val chapterDateFormat = SimpleDateFormat("EEEE, dd MMMM yyyy", sourceLocale)
|
||||
@@ -58,56 +130,6 @@ class DoujinDesuParser(context: MangaLoaderContext) : PagedMangaParser(context,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val url = urlBuilder().apply {
|
||||
addPathSegment("manga")
|
||||
addPathSegment("page")
|
||||
addPathSegment("$page/")
|
||||
val order = when (sortOrder) {
|
||||
SortOrder.UPDATED -> "update"
|
||||
SortOrder.POPULARITY -> "popular"
|
||||
SortOrder.ALPHABETICAL -> "title"
|
||||
SortOrder.NEWEST -> "latest"
|
||||
else -> throw IllegalArgumentException("Sort order not supported")
|
||||
}
|
||||
addQueryParameter("order", order)
|
||||
addQueryParameter("title", query.orEmpty())
|
||||
tags?.forEach {
|
||||
addEncodedQueryParameter("genre[]".urlEncoded(), it.key.urlEncoded())
|
||||
}
|
||||
}.build()
|
||||
|
||||
return webClient.httpGet(url).parseHtml()
|
||||
.requireElementById("archives")
|
||||
.selectFirstOrThrow("div.entries")
|
||||
.select(".entry")
|
||||
.map {
|
||||
val titleTag = it.selectFirstOrThrow(".metadata > a")
|
||||
val relativeUrl = titleTag.attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
id = generateUid(relativeUrl),
|
||||
title = titleTag.attr("title"),
|
||||
altTitle = null,
|
||||
url = relativeUrl,
|
||||
publicUrl = relativeUrl.toAbsoluteUrl(domain),
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = true,
|
||||
coverUrl = it.selectFirst(".thumbnail > img")?.src().orEmpty(),
|
||||
tags = emptySet(),
|
||||
state = null,
|
||||
author = null,
|
||||
largeCoverUrl = null,
|
||||
description = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val id = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
.requireElementById("reader")
|
||||
|
||||
@@ -39,26 +39,37 @@ class NicovideoSeigaParser(context: MangaLoaderContext) :
|
||||
SortOrder.POPULARITY,
|
||||
)
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("nicovideo.jp")
|
||||
|
||||
@InternalParsersApi
|
||||
override suspend fun getList(
|
||||
offset: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
override suspend fun getList(offset: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val page = (offset / 20f).toIntUp().inc()
|
||||
val domain = getDomain("seiga")
|
||||
val url = when {
|
||||
!query.isNullOrEmpty() -> return if (offset == 0) getSearchList(query, page) else emptyList()
|
||||
tags.isNullOrEmpty() -> "https://$domain/manga/list?page=$page&sort=${getSortKey(sortOrder)}"
|
||||
tags.size == 1 -> "https://$domain/manga/list?category=${tags.first().key}&page=$page" +
|
||||
"&sort=${getSortKey(sortOrder)}"
|
||||
val url =
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
return if (offset == 0) getSearchList(filter.query, page) else emptyList()
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany().let {
|
||||
"https://$domain/manga/list?category=${it?.key}&page=$page&sort=${getSortKey(filter.sortOrder)}"
|
||||
}
|
||||
|
||||
} else {
|
||||
"https://$domain/manga/list?page=$page&sort=${getSortKey(filter.sortOrder)}"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
null -> "https://$domain/manga/list?page=$page"
|
||||
}
|
||||
|
||||
tags.size > 1 -> throw IllegalArgumentException("This source supports only 1 category")
|
||||
else -> "https://$domain/manga/list?page=$page&sort=${getSortKey(sortOrder)}"
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
val comicList = doc.body().select("#comic_list > ul > li") ?: doc.parseFailed("Container not found")
|
||||
val items = comicList.select("div > .description > div > div")
|
||||
@@ -145,12 +156,12 @@ class NicovideoSeigaParser(context: MangaLoaderContext) :
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://${getDomain("seiga")}/manga/list").parseHtml()
|
||||
val root = doc.body().selectOrThrow("#mg_category_list > ul > li")
|
||||
val root = doc.body().selectOrThrow("#mg_category_list > ul > li").drop(1)
|
||||
return root.mapToSet { li ->
|
||||
val a = li.selectFirstOrThrow("a")
|
||||
MangaTag(
|
||||
title = a.text(),
|
||||
key = a.attrAsRelativeUrlOrNull("href").orEmpty(),
|
||||
key = a.attrAsRelativeUrl("href").substringAfter("category=").substringBefore("&"),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -23,44 +23,75 @@ internal abstract class LikeMangaParser(
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> =
|
||||
EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST)
|
||||
|
||||
override val availableStates: Set<MangaState> =
|
||||
EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain(domain)
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/?act=search&f")
|
||||
append("[sortby]".urlEncoded())
|
||||
append("=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("hot")
|
||||
SortOrder.UPDATED -> append("lastest-chap")
|
||||
SortOrder.NEWEST -> append("lastest-manga")
|
||||
else -> append("lastest-chap")
|
||||
append("/?act=search")
|
||||
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("&f")
|
||||
append("[keyword]".urlEncoded())
|
||||
append("=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
append("&f")
|
||||
append("[sortby]".urlEncoded())
|
||||
append("=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("hot")
|
||||
SortOrder.UPDATED -> append("lastest-chap")
|
||||
SortOrder.NEWEST -> append("lastest-manga")
|
||||
else -> append("lastest-chap")
|
||||
}
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("&f")
|
||||
append("[genres]".urlEncoded())
|
||||
append("=")
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append(it.key)
|
||||
}
|
||||
}
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&f")
|
||||
append("[status]".urlEncoded())
|
||||
append("=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "in-process"
|
||||
MangaState.FINISHED -> "complete"
|
||||
MangaState.PAUSED -> "pause"
|
||||
else -> "all"
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("&f")
|
||||
append("[sortby]".urlEncoded())
|
||||
append("=lastest-chap")
|
||||
}
|
||||
}
|
||||
|
||||
if (page > 1) {
|
||||
append("&pageNum=")
|
||||
append(page)
|
||||
}
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
append("&f")
|
||||
append("[genres]".urlEncoded())
|
||||
append("=")
|
||||
append(tag?.key.orEmpty())
|
||||
}
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append("&f")
|
||||
append("[keyword]".urlEncoded())
|
||||
append("=")
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
return doc.select("div.card-body div.video").map { div ->
|
||||
|
||||
@@ -33,6 +33,8 @@ internal abstract class MadaraParser(
|
||||
SortOrder.RATING,
|
||||
)
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.allOf(MangaState::class.java)
|
||||
|
||||
protected open val tagPrefix = "manga-genre/"
|
||||
protected open val datePattern = "MMMM d, yyyy"
|
||||
protected open val stylePage = "?style=list"
|
||||
@@ -155,67 +157,123 @@ internal abstract class MadaraParser(
|
||||
// can be changed to retrieve tags see getTags
|
||||
protected open val listUrl = "manga/"
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
val doc = if (withoutAjax) {
|
||||
val pages = page + 1
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
val pages = page + 1
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
append("/$tagPrefix")
|
||||
append(tag?.key.orEmpty())
|
||||
if (pages > 1) {
|
||||
append("/page/")
|
||||
append(pages.toString())
|
||||
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
if (pages > 1) {
|
||||
append("/page/")
|
||||
append(pages.toString())
|
||||
}
|
||||
append("/?s=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&post_type=wp-manga")
|
||||
}
|
||||
append("?")
|
||||
} else {
|
||||
append("/page/")
|
||||
append(pages)
|
||||
append("/?s=")
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append(query.urlEncoded())
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("/$tagPrefix")
|
||||
append(it.key)
|
||||
if (pages > 1) {
|
||||
append("/page/")
|
||||
append(pages.toString())
|
||||
}
|
||||
append("/?")
|
||||
}
|
||||
} else {
|
||||
|
||||
if (pages > 1) {
|
||||
append("/page/")
|
||||
append(pages.toString())
|
||||
}
|
||||
append("/?s=&post_type=wp-manga")
|
||||
filter.states.forEach {
|
||||
append("&status[]=")
|
||||
when (it) {
|
||||
MangaState.ONGOING -> append("on-going")
|
||||
MangaState.FINISHED -> append("end")
|
||||
MangaState.ABANDONED -> append("canceled")
|
||||
MangaState.PAUSED -> append("on-hold")
|
||||
}
|
||||
}
|
||||
append("&")
|
||||
}
|
||||
|
||||
append("m_orderby=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("new-manga")
|
||||
SortOrder.ALPHABETICAL -> append("alphabet")
|
||||
SortOrder.RATING -> append("rating")
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("?s&post_type=wp-manga&m_orderby=latest")
|
||||
}
|
||||
append("&post_type=wp-manga&")
|
||||
/// &status[]= ( on-going - end - canceled - on-hold - upcoming )
|
||||
}
|
||||
append("m_orderby=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("new-manga")
|
||||
SortOrder.ALPHABETICAL -> append("alphabet")
|
||||
SortOrder.RATING -> append("rating")
|
||||
}
|
||||
}
|
||||
webClient.httpGet(url).parseHtml()
|
||||
} else {
|
||||
val payload = if (sortOrder == SortOrder.RATING) {
|
||||
val payload = if (filter?.sortOrder == SortOrder.RATING) {
|
||||
createRequestTemplate(ratingRequest)
|
||||
} else {
|
||||
createRequestTemplate(defaultRequest)
|
||||
}
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> payload["vars[meta_key]"] = "_wp_manga_views"
|
||||
SortOrder.UPDATED -> payload["vars[meta_key]"] = "_latest_update"
|
||||
SortOrder.NEWEST -> payload["vars[meta_key]"] = ""
|
||||
SortOrder.ALPHABETICAL -> {
|
||||
payload["vars[orderby]"] = "post_title"
|
||||
payload["vars[order]"] = "ASC"
|
||||
|
||||
payload["page"] = page.toString()
|
||||
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
payload["vars[s]"] = filter.query.urlEncoded()
|
||||
}
|
||||
|
||||
SortOrder.RATING -> {}
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
payload["vars[wp-manga-genre]"] = it.key
|
||||
}
|
||||
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> payload["vars[meta_key]"] = "_wp_manga_views"
|
||||
SortOrder.UPDATED -> payload["vars[meta_key]"] = "_latest_update"
|
||||
SortOrder.NEWEST -> payload["vars[meta_key]"] = ""
|
||||
SortOrder.ALPHABETICAL -> {
|
||||
payload["vars[orderby]"] = "post_title"
|
||||
payload["vars[order]"] = "ASC"
|
||||
}
|
||||
|
||||
SortOrder.RATING -> {}
|
||||
}
|
||||
|
||||
filter.states.forEach {
|
||||
payload["vars[meta_query][0][0][value][]"] =
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "on-going"
|
||||
MangaState.FINISHED -> "end"
|
||||
MangaState.ABANDONED -> "canceled"
|
||||
MangaState.PAUSED -> "on-hold"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
payload["vars[meta_key]"] = "_latest_update"
|
||||
}
|
||||
}
|
||||
payload["page"] = page.toString()
|
||||
payload["vars[wp-manga-genre]"] = tag?.key.orEmpty()
|
||||
payload["vars[s]"] = query?.urlEncoded().orEmpty()
|
||||
/// payload["vars[meta_query][0][0][value][]"] = ( on-going - end - canceled - on-hold - upcoming )
|
||||
|
||||
webClient.httpPost(
|
||||
"https://$domain/wp-admin/admin-ajax.php",
|
||||
payload,
|
||||
|
||||
@@ -19,39 +19,49 @@ internal class Manga18Fx(context: MangaLoaderContext) :
|
||||
override val selectDate = "span.chapter-time"
|
||||
override val selectChapter = "li.a-h"
|
||||
override val selectBodyPage = "div.read-content"
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
|
||||
init {
|
||||
paginator.firstPage = 1
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override val availableStates: Set<MangaState> get() = emptySet()
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
val pages = page + 1
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/search?q=")
|
||||
append(query.urlEncoded())
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(pages)
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/$tagPrefix")
|
||||
append(tag?.key.orEmpty())
|
||||
if (pages > 1) {
|
||||
append("/")
|
||||
append(pages)
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("/$tagPrefix")
|
||||
append(tag?.key.orEmpty())
|
||||
if (page > 1) {
|
||||
append("/")
|
||||
append(page.toString())
|
||||
}
|
||||
} else {
|
||||
if (page > 1) {
|
||||
append("/page/")
|
||||
append(page)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
if (pages > 1) {
|
||||
null -> {
|
||||
if (page > 1) {
|
||||
append("/page/")
|
||||
append(pages)
|
||||
append(page)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,49 +26,49 @@ internal class Manhwa18Cc(context: MangaLoaderContext) :
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override val availableStates: Set<MangaState> get() = emptySet()
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
append("/search?q=")
|
||||
append(query.urlEncoded())
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/$tagPrefix")
|
||||
append(tag?.key.orEmpty())
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("/$tagPrefix")
|
||||
append(tag?.key.orEmpty())
|
||||
} else {
|
||||
append("/$listUrl")
|
||||
}
|
||||
|
||||
if (page > 1) {
|
||||
append(page.toString())
|
||||
}
|
||||
append("?")
|
||||
|
||||
append("?orderby=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("trending")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.ALPHABETICAL -> append("alphabet")
|
||||
SortOrder.RATING -> append("rating")
|
||||
else -> append("latest")
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
append("/$listUrl")
|
||||
if (page > 1) {
|
||||
append(page)
|
||||
}
|
||||
append("?")
|
||||
null -> {
|
||||
append("?s&post_type=wp-manga&m_orderby=latest")
|
||||
}
|
||||
}
|
||||
append("m_orderby=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("trending")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.ALPHABETICAL -> append("alphabet")
|
||||
SortOrder.RATING -> append("rating")
|
||||
else -> append("latest")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
return doc.select("div.manga-lists div.manga-item").map { div ->
|
||||
|
||||
@@ -5,6 +5,7 @@ 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.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaListFilter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaState
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
@@ -20,48 +21,48 @@ internal class Hentai4Free(context: MangaLoaderContext) :
|
||||
override val listUrl = ""
|
||||
override val withoutAjax = true
|
||||
override val datePattern = "MMMM dd, yyyy"
|
||||
override val selectGenre = "div.tags-content a"
|
||||
|
||||
override val availableStates: Set<MangaState> get() = emptySet()
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
init {
|
||||
paginator.firstPage = 1
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
val pages = page + 1
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/page/")
|
||||
append(pages.toString())
|
||||
append(page.toString())
|
||||
append("/?s=")
|
||||
append(query.urlEncoded())
|
||||
append("&post_type=wp-manga&")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&post_type=wp-manga")
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/$tagPrefix")
|
||||
append(tag?.key.orEmpty())
|
||||
append("/")
|
||||
if (pages > 1) {
|
||||
append("page/")
|
||||
append(pages.toString())
|
||||
}
|
||||
}
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
else -> {
|
||||
|
||||
if (pages > 1) {
|
||||
append("/page/")
|
||||
append(pages.toString())
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("/$tagPrefix")
|
||||
append(tag?.key.orEmpty())
|
||||
append("/")
|
||||
if (page > 1) {
|
||||
append("page/")
|
||||
append(page.toString())
|
||||
}
|
||||
} else {
|
||||
if (page > 1) {
|
||||
append("/page/")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
|
||||
append("/?m_orderby=")
|
||||
when (sortOrder) {
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("new-manga")
|
||||
@@ -69,9 +70,17 @@ internal class Hentai4Free(context: MangaLoaderContext) :
|
||||
SortOrder.RATING -> append("rating")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
if (page > 1) {
|
||||
append("/page/")
|
||||
append(page.toString())
|
||||
}
|
||||
append("/?m_orderby=latest")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
return doc.select("div.row.c-tabs-item__content").ifEmpty {
|
||||
|
||||
@@ -23,46 +23,48 @@ internal class InstaManhwa(context: MangaLoaderContext) :
|
||||
SortOrder.NEWEST,
|
||||
)
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override val availableStates: Set<MangaState> get() = emptySet()
|
||||
|
||||
init {
|
||||
paginator.firstPage = 1
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
val pages = page + 1
|
||||
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
|
||||
append("/?search=")
|
||||
append(query.urlEncoded())
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/search?q=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(pages.toString())
|
||||
append("&post_type=wp-manga&post_type=wp-manga")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/genre/")
|
||||
append(tag?.key.orEmpty())
|
||||
append("?page=")
|
||||
append(pages.toString())
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
||||
when (sortOrder) {
|
||||
SortOrder.UPDATED -> append("/latest")
|
||||
SortOrder.NEWEST -> append("/new")
|
||||
SortOrder.ALPHABETICAL -> append("/alphabet")
|
||||
else -> append("/latest")
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("/genre/")
|
||||
append(tag?.key.orEmpty())
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
} else {
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.UPDATED -> append("/latest")
|
||||
SortOrder.NEWEST -> append("/new")
|
||||
SortOrder.ALPHABETICAL -> append("/alphabet")
|
||||
else -> append("/latest")
|
||||
}
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
}
|
||||
append("?page=")
|
||||
append(pages.toString())
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("/latest?page=")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,50 +22,58 @@ internal class IsekaiScan(context: MangaLoaderContext) :
|
||||
SortOrder.UPDATED,
|
||||
)
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override val availableStates: Set<MangaState> get() = emptySet()
|
||||
|
||||
init {
|
||||
paginator.firstPage = 1
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
val pages = page + 1
|
||||
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/?search=")
|
||||
append(query.urlEncoded())
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(pages.toString())
|
||||
append(page.toString())
|
||||
append("&post_type=wp-manga")
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/mangas/")
|
||||
append(tag?.key.orEmpty())
|
||||
append("?orderby=2&page=")
|
||||
append(pages.toString())
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("/$tagPrefix")
|
||||
append(tag?.key.orEmpty())
|
||||
append("?orderby=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("2")
|
||||
SortOrder.UPDATED -> append("3")
|
||||
else -> append("3")
|
||||
}
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
} else {
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("/popular-manga")
|
||||
SortOrder.UPDATED -> append("/latest-manga")
|
||||
else -> append("/latest-manga")
|
||||
}
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
||||
if (sortOrder == SortOrder.POPULARITY) {
|
||||
append("/popular-manga")
|
||||
}
|
||||
if (sortOrder == SortOrder.UPDATED) {
|
||||
append("/latest-manga")
|
||||
}
|
||||
append("?page=")
|
||||
append(pages.toString())
|
||||
null -> {
|
||||
append("/latest-manga?page=")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
return doc.select("div.row.c-tabs-item__content").ifEmpty {
|
||||
@@ -91,8 +99,8 @@ internal class IsekaiScan(context: MangaLoaderContext) :
|
||||
author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText(),
|
||||
state = when (summary?.selectFirst(".mg_status")?.selectFirst(".summary-content")?.ownText()?.trim()
|
||||
?.lowercase()) {
|
||||
"Ongoing" -> MangaState.ONGOING
|
||||
"Completed " -> MangaState.FINISHED
|
||||
"ongoing" -> MangaState.ONGOING
|
||||
"completed " -> MangaState.FINISHED
|
||||
else -> null
|
||||
},
|
||||
source = source,
|
||||
@@ -133,8 +141,8 @@ internal class IsekaiScan(context: MangaLoaderContext) :
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val fullUrl = chapter.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||
val urlarray = doc.select("p#arraydata").text().split(",").toTypedArray()
|
||||
return urlarray.map { url ->
|
||||
val urlArray = doc.select("p#arraydata").text().split(",").toTypedArray()
|
||||
return urlArray.map { url ->
|
||||
MangaPage(
|
||||
id = generateUid(url),
|
||||
url = url,
|
||||
|
||||
@@ -14,51 +14,72 @@ internal class IsekaiScanEuParser(context: MangaLoaderContext) :
|
||||
override val withoutAjax = true
|
||||
override val listUrl = "mangax/"
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
init {
|
||||
paginator.firstPage = 1
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
val pages = page + 1
|
||||
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
append("/page/")
|
||||
append(pages.toString())
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
if (page > 1) {
|
||||
append("/page/")
|
||||
append(page.toString())
|
||||
}
|
||||
append("/?s=")
|
||||
append(query.urlEncoded())
|
||||
append("&post_type=wp-manga&")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&post_type=wp-manga")
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/$tagPrefix")
|
||||
append(tag?.key.orEmpty())
|
||||
append("/page/")
|
||||
append(pages.toString())
|
||||
append("?")
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("/$tagPrefix")
|
||||
append(tag?.key.orEmpty())
|
||||
if (page > 1) {
|
||||
append("/page/")
|
||||
append(page.toString())
|
||||
}
|
||||
append("/?")
|
||||
} else {
|
||||
if (page > 1) {
|
||||
append("/page/")
|
||||
append(page.toString())
|
||||
}
|
||||
append("/?s=&post_type=wp-manga")
|
||||
filter.states.forEach {
|
||||
append("&status[]=")
|
||||
when (it) {
|
||||
MangaState.ONGOING -> append("on-going")
|
||||
MangaState.FINISHED -> append("end")
|
||||
MangaState.ABANDONED -> append("canceled")
|
||||
MangaState.PAUSED -> append("on-hold")
|
||||
}
|
||||
}
|
||||
append("&")
|
||||
}
|
||||
|
||||
append("m_orderby=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("new-manga")
|
||||
SortOrder.ALPHABETICAL -> append("alphabet")
|
||||
SortOrder.RATING -> append("rating")
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
||||
append("/$listUrl")
|
||||
append("/page/")
|
||||
append(pages.toString())
|
||||
append("?")
|
||||
null -> {
|
||||
append("/?s&post_type=wp-manga&m_orderby=latest")
|
||||
}
|
||||
}
|
||||
append("m_orderby=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("new-manga")
|
||||
SortOrder.ALPHABETICAL -> append("alphabet")
|
||||
SortOrder.RATING -> append("rating")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
|
||||
@@ -19,55 +19,61 @@ internal class MangaDass(context: MangaLoaderContext) :
|
||||
override val selectChapter = "li.a-h"
|
||||
override val selectDesc = "div.ss-manga"
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override val availableStates: Set<MangaState> get() = emptySet()
|
||||
|
||||
init {
|
||||
paginator.firstPage = 1
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
val pages = page + 1
|
||||
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
|
||||
append("/?s=")
|
||||
append(query.urlEncoded())
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/search?q=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(pages.toString())
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/$tagPrefix")
|
||||
append(tag?.key.orEmpty())
|
||||
append("/")
|
||||
append(pages.toString())
|
||||
append("?")
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("/$tagPrefix")
|
||||
append(tag?.key.orEmpty())
|
||||
append("/")
|
||||
append(page.toString())
|
||||
append("?")
|
||||
} else {
|
||||
append("/$listUrl")
|
||||
append("/")
|
||||
append(page.toString())
|
||||
append("?")
|
||||
}
|
||||
|
||||
append("orderby=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("new-manga")
|
||||
SortOrder.ALPHABETICAL -> append("alphabet")
|
||||
SortOrder.RATING -> append("rating")
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
||||
null -> {
|
||||
append("/$listUrl")
|
||||
append("/")
|
||||
append(pages.toString())
|
||||
append("?")
|
||||
append(page.toString())
|
||||
append("?orderby=latest")
|
||||
}
|
||||
}
|
||||
append("orderby=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("new-manga")
|
||||
SortOrder.ALPHABETICAL -> append("alphabet")
|
||||
SortOrder.RATING -> append("rating")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
|
||||
return doc.select("div.row.c-tabs-item__content").ifEmpty {
|
||||
doc.select("div.page-item-detail")
|
||||
}.map { div ->
|
||||
|
||||
@@ -18,53 +18,53 @@ internal class MangaDna(context: MangaLoaderContext) :
|
||||
override val withoutAjax = true
|
||||
override val selectDesc = "div.dsct"
|
||||
override val selectChapter = "li.a-h"
|
||||
override val availableStates: Set<MangaState> get() = emptySet()
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
val pages = page + 1
|
||||
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
append("/page/")
|
||||
append(pages.toString())
|
||||
append("/?s=")
|
||||
append(query.urlEncoded())
|
||||
append("&post_type=wp-manga&")
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/search?q=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/$tagPrefix")
|
||||
append(tag?.key.orEmpty())
|
||||
append("/page/")
|
||||
append(pages.toString())
|
||||
append("?")
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("/$tagPrefix")
|
||||
append(tag?.key.orEmpty())
|
||||
append("/")
|
||||
append(page.toString())
|
||||
} else {
|
||||
append("/$listUrl")
|
||||
append("/page/")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
append("?orderby=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("trending")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.ALPHABETICAL -> append("alphabet")
|
||||
SortOrder.RATING -> append("rating")
|
||||
else -> append("latest")
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
||||
null -> {
|
||||
append("/$listUrl")
|
||||
append("/page/")
|
||||
append(pages.toString())
|
||||
append("?")
|
||||
append(page.toString())
|
||||
append("?orderby=latest")
|
||||
}
|
||||
}
|
||||
append("m_orderby=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("new-manga")
|
||||
SortOrder.ALPHABETICAL -> append("alphabet")
|
||||
SortOrder.RATING -> append("rating")
|
||||
}
|
||||
}
|
||||
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
|
||||
|
||||
@@ -21,46 +21,54 @@ internal class MangaPure(context: MangaLoaderContext) :
|
||||
SortOrder.UPDATED,
|
||||
)
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override val availableStates: Set<MangaState> get() = emptySet()
|
||||
|
||||
init {
|
||||
paginator.firstPage = 1
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
val pages = page + 1
|
||||
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
|
||||
append("/?search=")
|
||||
append(query.urlEncoded())
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/search?s=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(pages.toString())
|
||||
append(page.toString())
|
||||
append("&post_type=wp-manga")
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/mangas/")
|
||||
append(tag?.key.orEmpty())
|
||||
append("?orderby=2&page=")
|
||||
append(pages.toString())
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("/$tagPrefix")
|
||||
append(tag?.key.orEmpty())
|
||||
append("?orderby=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("2")
|
||||
SortOrder.UPDATED -> append("3")
|
||||
else -> append("3")
|
||||
}
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
} else {
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("/popular-manga")
|
||||
SortOrder.UPDATED -> append("/latest-manga")
|
||||
else -> append("/latest-manga")
|
||||
}
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
||||
if (sortOrder == SortOrder.POPULARITY) {
|
||||
append("/popular-manga")
|
||||
}
|
||||
if (sortOrder == SortOrder.UPDATED) {
|
||||
append("/latest-manga")
|
||||
}
|
||||
append("?page=")
|
||||
append(pages.toString())
|
||||
null -> {
|
||||
append("/latest-manga?page=")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.util.EnumSet
|
||||
|
||||
@MangaSourceParser("MANHWAZ", "ManhwaZ", "en")
|
||||
internal class Manhwaz(context: MangaLoaderContext) :
|
||||
@@ -15,51 +16,65 @@ internal class Manhwaz(context: MangaLoaderContext) :
|
||||
override val withoutAjax = true
|
||||
override val selectTestAsync = "div.list-chapter"
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override val availableStates: Set<MangaState> get() = emptySet()
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.UPDATED,
|
||||
SortOrder.POPULARITY,
|
||||
SortOrder.NEWEST,
|
||||
SortOrder.RATING,
|
||||
)
|
||||
|
||||
init {
|
||||
paginator.firstPage = 1
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
val pages = page + 1
|
||||
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/search?s=")
|
||||
append(query.urlEncoded())
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(pages.toString())
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/$tagPrefix")
|
||||
append(tag?.key.orEmpty())
|
||||
append("?page=")
|
||||
append(pages.toString())
|
||||
append("&")
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("/$tagPrefix")
|
||||
append(tag?.key.orEmpty())
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
append("&")
|
||||
} else {
|
||||
append("/$listUrl")
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
append("&")
|
||||
}
|
||||
|
||||
append("m_orderby=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("new")
|
||||
SortOrder.RATING -> append("rating")
|
||||
else -> append("latest")
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
||||
null -> {
|
||||
append("/$listUrl")
|
||||
append("?page=")
|
||||
append(pages.toString())
|
||||
append("&")
|
||||
append(page.toString())
|
||||
append("&m_orderby=latest")
|
||||
}
|
||||
}
|
||||
append("m_orderby=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("new-manga")
|
||||
SortOrder.ALPHABETICAL -> append("alphabet")
|
||||
SortOrder.RATING -> append("rating")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
|
||||
@@ -3,8 +3,9 @@ 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.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaListFilter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.model.MangaState
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
@@ -13,48 +14,46 @@ import java.util.*
|
||||
@MangaSourceParser("DRAGONTRANSLATION", "Dragon Translation", "es")
|
||||
internal class DragonTranslationParser(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaSource.DRAGONTRANSLATION, "dragontranslation.net", 30) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
|
||||
|
||||
override val selectPage = "div#chapter_imgs img"
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
|
||||
override val availableStates: Set<MangaState> get() = emptySet()
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
init {
|
||||
paginator.firstPage = 1
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
val pages = page + 1
|
||||
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/mangas?buscar=")
|
||||
append(query.urlEncoded())
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(pages.toString())
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/mangas?tag=")
|
||||
append(tag?.key.orEmpty())
|
||||
append("&page=")
|
||||
append(pages.toString())
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
append("/mangas?page=")
|
||||
append(page.toString())
|
||||
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("&tag=")
|
||||
append(tag?.key.orEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
||||
append("/mangas")
|
||||
append("?page=")
|
||||
append(pages.toString())
|
||||
null -> {
|
||||
append("/mangas?page=")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
return doc.select("div.video-bg div.col-6 ").map { div ->
|
||||
|
||||
@@ -4,93 +4,9 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
|
||||
@MangaSourceParser("MONARCAMANGA", "MonarcaManga", "es")
|
||||
internal class MonarcaManga(context: MangaLoaderContext) :
|
||||
MadaraParser(context, MangaSource.MONARCAMANGA, "monarcamanga.com") {
|
||||
|
||||
override val tagPrefix = "manga-generos/"
|
||||
override val withoutAjax = true
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
val pages = page + 1
|
||||
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
append("/page/")
|
||||
append(pages.toString())
|
||||
append("/?s=")
|
||||
append(query.urlEncoded())
|
||||
append("&post_type=wp-manga&")
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/$tagPrefix")
|
||||
append(tag?.key.orEmpty())
|
||||
append("/page/")
|
||||
append(pages.toString())
|
||||
append("?")
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
||||
append("/$listUrl")
|
||||
append("/page/")
|
||||
append(pages.toString())
|
||||
append("?")
|
||||
}
|
||||
}
|
||||
append("m_orderby=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("new-manga")
|
||||
SortOrder.ALPHABETICAL -> append("alphabet")
|
||||
SortOrder.RATING -> append("rating")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
return doc.select("div.row.c-tabs-item__content").ifEmpty {
|
||||
doc.select("div.page-item-detail")
|
||||
}.map { div ->
|
||||
val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href") ?: div.parseFailed("Link not found")
|
||||
val summary = div.selectFirst(".tab-summary") ?: div.selectFirst(".item-summary")
|
||||
Manga(
|
||||
id = generateUid(href),
|
||||
url = href,
|
||||
publicUrl = href.toAbsoluteUrl(div.host ?: domain),
|
||||
coverUrl = div.selectFirst("img")?.src().orEmpty(),
|
||||
title = (div?.selectFirst("h3") ?: div?.selectFirst("h4"))?.text().orEmpty(),
|
||||
altTitle = null,
|
||||
rating = div.selectFirst("span.total_votes")?.ownText()?.toFloatOrNull()?.div(5f) ?: -1f,
|
||||
tags = summary?.selectFirst(".mg_genres")?.select("a")?.mapNotNullToSet { a ->
|
||||
MangaTag(
|
||||
key = a.attr("href").removeSuffix('/').substringAfterLast('/'),
|
||||
title = a.text().ifEmpty { return@mapNotNullToSet null }.toTitleCase(),
|
||||
source = source,
|
||||
)
|
||||
}.orEmpty(),
|
||||
author = summary?.selectFirst(".mg_author")?.selectFirst("a")?.ownText(),
|
||||
state = when (summary?.selectFirst(".mg_status")?.selectFirst(".summary-content")?.ownText()
|
||||
?.lowercase()) {
|
||||
in ongoing -> MangaState.ONGOING
|
||||
in finished -> MangaState.FINISHED
|
||||
else -> null
|
||||
},
|
||||
source = source,
|
||||
isNsfw = isNsfwSource,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,44 +16,49 @@ internal class TmoManga(context: MangaLoaderContext) :
|
||||
override val listUrl = "biblioteca/"
|
||||
override val selectGenre = "div.summary-content a.tags_manga"
|
||||
override val withoutAjax = true
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.POPULARITY)
|
||||
override val availableStates: Set<MangaState> get() = emptySet()
|
||||
|
||||
init {
|
||||
paginator.firstPage = 1
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.POPULARITY)
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/$listUrl")
|
||||
append("?search=")
|
||||
append(query.urlEncoded())
|
||||
append(filter.query.urlEncoded())
|
||||
if (page > 1) {
|
||||
append("&page=")
|
||||
append(page)
|
||||
}
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/$tagPrefix")
|
||||
append(tag?.key.orEmpty())
|
||||
if (page > 1) {
|
||||
append("?page=")
|
||||
append(page)
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("/$tagPrefix")
|
||||
append(tag?.key.orEmpty())
|
||||
if (page > 1) {
|
||||
append("?page=")
|
||||
append(page)
|
||||
}
|
||||
} else {
|
||||
append("/$listUrl")
|
||||
if (page > 1) {
|
||||
append("?page=")
|
||||
append(page)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
null -> {
|
||||
append("/$listUrl")
|
||||
if (page > 1) {
|
||||
append("?page=")
|
||||
@@ -62,6 +67,7 @@ internal class TmoManga(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
return doc.select("div.page-item-detail").map { div ->
|
||||
|
||||
@@ -17,56 +17,47 @@ internal class ManhwaHub(context: MangaLoaderContext) :
|
||||
override val withoutAjax = true
|
||||
override val listUrl = "genre/manhwa"
|
||||
override val selectTestAsync = "ul.box-list-chapter"
|
||||
override val availableStates: Set<MangaState> get() = emptySet()
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
|
||||
|
||||
init {
|
||||
paginator.firstPage = 1
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/search?s=")
|
||||
append(query.urlEncoded())
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page)
|
||||
append("&")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/$tagPrefix")
|
||||
append(tag?.key.orEmpty())
|
||||
append("?page=")
|
||||
append(page)
|
||||
append("&")
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("/$tagPrefix")
|
||||
append(tag?.key.orEmpty())
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
append("&m_orderby=latest")
|
||||
} else {
|
||||
append("/?page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
else -> {
|
||||
|
||||
append("/$listUrl")
|
||||
append("?page=")
|
||||
append(page)
|
||||
append("&")
|
||||
null -> {
|
||||
append("/?page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
append("m_orderby=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("new-manga")
|
||||
SortOrder.ALPHABETICAL -> append("alphabet")
|
||||
SortOrder.RATING -> append("rating")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.util.EnumSet
|
||||
|
||||
@MangaSourceParser("SAYTRUYENHAY", "Saytruyenhay", "vi")
|
||||
internal class Saytruyenhay(context: MangaLoaderContext) :
|
||||
@@ -15,50 +16,57 @@ internal class Saytruyenhay(context: MangaLoaderContext) :
|
||||
override val tagPrefix = "genre/"
|
||||
override val withoutAjax = true
|
||||
override val listUrl = "public/genre/manga/"
|
||||
override val availableStates: Set<MangaState> get() = emptySet()
|
||||
override val availableSortOrders: Set<SortOrder> =
|
||||
EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.RATING, SortOrder.NEWEST)
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
init {
|
||||
paginator.firstPage = 1
|
||||
searchPaginator.firstPage = 1
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
val pages = page + 1
|
||||
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
|
||||
append("/public/search?s=")
|
||||
append(query.urlEncoded())
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("/search?s=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(pages.toString())
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/$tagPrefix")
|
||||
append(tag?.key.orEmpty())
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
val tag = filter.tags.oneOrThrowIfMany()
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("/$tagPrefix")
|
||||
append(tag?.key.orEmpty())
|
||||
} else {
|
||||
append("/$listUrl")
|
||||
|
||||
}
|
||||
append("?page=")
|
||||
append(pages.toString())
|
||||
append(page.toString())
|
||||
append("&m_orderby=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.RATING -> append("rating")
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.NEWEST -> append("new")
|
||||
else -> append("latest")
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
null -> {
|
||||
append("/$listUrl")
|
||||
append("?page=")
|
||||
append(pages.toString())
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
append("&m_orderby=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("new-manga")
|
||||
SortOrder.ALPHABETICAL -> append("alphabet")
|
||||
SortOrder.RATING -> append("rating")
|
||||
}
|
||||
}
|
||||
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ internal abstract class MadthemeParser(
|
||||
SortOrder.RATING,
|
||||
)
|
||||
|
||||
override val availableStates: Set<MangaState> = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED)
|
||||
|
||||
protected open val listUrl = "search/"
|
||||
protected open val datePattern = "MMM dd, yyyy"
|
||||
|
||||
@@ -52,35 +54,52 @@ internal abstract class MadthemeParser(
|
||||
"COMPLETED",
|
||||
)
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/$listUrl?sort=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("updated_at")
|
||||
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")
|
||||
}
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append("&q=")
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
append('/')
|
||||
append(listUrl)
|
||||
when (filter) {
|
||||
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
for (tag in tags) {
|
||||
append("&")
|
||||
append("genre[]".urlEncoded())
|
||||
append("=")
|
||||
append(tag.key)
|
||||
is MangaListFilter.Search -> {
|
||||
append("?sort=updated_at&q=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
append("?sort=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("updated_at")
|
||||
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")
|
||||
}
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.forEach {
|
||||
append("&")
|
||||
append("genre[]".urlEncoded())
|
||||
append("=")
|
||||
append(it.key)
|
||||
}
|
||||
}
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&status=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "ongoing"
|
||||
MangaState.FINISHED -> "completed"
|
||||
else -> "all"
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
null -> append("?sort=updated_at")
|
||||
}
|
||||
|
||||
append("&page=")
|
||||
@@ -117,7 +136,7 @@ internal abstract class MadthemeParser(
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml()
|
||||
return doc.select("div.genres label.checkbox").mapNotNullToSet { checkbox ->
|
||||
return doc.select("div.genres .checkbox").mapNotNullToSet { checkbox ->
|
||||
val key = checkbox.selectFirstOrThrow("input").attr("value") ?: return@mapNotNullToSet null
|
||||
val name = checkbox.selectFirstOrThrow("span.radio__label").text()
|
||||
MangaTag(
|
||||
|
||||
@@ -7,44 +7,58 @@ import org.koitharu.kotatsu.parsers.site.madtheme.MadthemeParser
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.util.Locale
|
||||
|
||||
@MangaSourceParser("MANHUASCAN", "ManhuaScan", "")
|
||||
@MangaSourceParser("MANHUASCAN", "ManhuaScan.io", "")
|
||||
internal class ManhuaScan(context: MangaLoaderContext) :
|
||||
MadthemeParser(context, MangaSource.MANHUASCAN, "manhuascan.io") {
|
||||
override val sourceLocale: Locale = Locale.ENGLISH
|
||||
override val listUrl = "search"
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append('/')
|
||||
append(listUrl)
|
||||
append("?sort=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("updated_at")
|
||||
SortOrder.ALPHABETICAL -> append("name")
|
||||
SortOrder.NEWEST -> append("created_at")
|
||||
SortOrder.RATING -> append("rating")
|
||||
}
|
||||
when (filter) {
|
||||
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append("&q=")
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
for (tag in tags) {
|
||||
append("&")
|
||||
append("include[]".urlEncoded())
|
||||
append("=")
|
||||
append(tag.key)
|
||||
is MangaListFilter.Search -> {
|
||||
append("?sort=updated_at&q=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
append("?sort=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("updated_at")
|
||||
SortOrder.ALPHABETICAL -> append("name")
|
||||
SortOrder.NEWEST -> append("created_at")
|
||||
SortOrder.RATING -> append("rating")
|
||||
}
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.forEach {
|
||||
append("&")
|
||||
append("include[]".urlEncoded())
|
||||
append("=")
|
||||
append(it.key)
|
||||
}
|
||||
}
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&status=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "ongoing"
|
||||
MangaState.FINISHED -> "completed"
|
||||
else -> "all"
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
null -> append("?sort=updated_at")
|
||||
}
|
||||
|
||||
append("&page=")
|
||||
|
||||
@@ -34,6 +34,5 @@ internal class MangaBuddy(context: MangaLoaderContext) :
|
||||
)
|
||||
}
|
||||
return pages
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,5 @@ import org.koitharu.kotatsu.parsers.site.madtheme.MadthemeParser
|
||||
@MangaSourceParser("TOONITUBE", "TooniTube", "en", ContentType.HENTAI)
|
||||
internal class TooniTube(context: MangaLoaderContext) :
|
||||
MadthemeParser(context, MangaSource.TOONITUBE, "toonitube.com") {
|
||||
|
||||
override val selectDesc = "div.summary div.section-body p.content"
|
||||
}
|
||||
|
||||
@@ -9,6 +9,5 @@ import org.koitharu.kotatsu.parsers.site.madtheme.MadthemeParser
|
||||
@MangaSourceParser("TOONILY_ME", "Toonily.Me", "en", ContentType.HENTAI)
|
||||
internal class ToonilyMe(context: MangaLoaderContext) :
|
||||
MadthemeParser(context, MangaSource.TOONILY_ME, "toonily.me") {
|
||||
|
||||
override val selectDesc = "div.summary div.section-body p.content"
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ internal abstract class Manga18Parser(
|
||||
SortOrder.ALPHABETICAL,
|
||||
)
|
||||
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
protected open val listUrl = "list-manga/"
|
||||
protected open val tagUrl = "manga-list/"
|
||||
protected open val datePattern = "dd-MM-yyyy"
|
||||
@@ -47,49 +49,53 @@ internal abstract class Manga18Parser(
|
||||
"Completed",
|
||||
)
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
append("/$listUrl")
|
||||
append('/')
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
append(listUrl)
|
||||
append(page.toString())
|
||||
append("?search=")
|
||||
append(query.urlEncoded())
|
||||
append("&")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&order_by=latest")
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/$tagUrl")
|
||||
append(tag?.key.orEmpty())
|
||||
append("/")
|
||||
is MangaListFilter.Advanced -> {
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append(tagUrl)
|
||||
append(it.key)
|
||||
append("/")
|
||||
}
|
||||
} else {
|
||||
append(listUrl)
|
||||
}
|
||||
|
||||
append(page.toString())
|
||||
append("?")
|
||||
append("?order_by=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("lastest")
|
||||
SortOrder.ALPHABETICAL -> append("name")
|
||||
else -> append("latest")
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
append("/$listUrl")
|
||||
null -> {
|
||||
append(listUrl)
|
||||
append(page.toString())
|
||||
append("?")
|
||||
append("?order_by=latest")
|
||||
}
|
||||
}
|
||||
append("order_by=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("lastest")
|
||||
SortOrder.ALPHABETICAL -> append("name")
|
||||
else -> append("latest")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
|
||||
protected open fun parseMangaList(doc: Document): List<Manga> {
|
||||
return doc.select("div.story_item").map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.koitharu.kotatsu.parsers.site.manga18.en
|
||||
|
||||
import org.jsoup.nodes.Document
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
@@ -10,51 +11,7 @@ import org.koitharu.kotatsu.parsers.util.*
|
||||
internal class Hentai3zCc(context: MangaLoaderContext) :
|
||||
Manga18Parser(context, MangaSource.HENTAI3ZCC, "hentai3z.cc") {
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
val pages = page + 1
|
||||
when {
|
||||
!query.isNullOrEmpty() -> {
|
||||
append("/$listUrl")
|
||||
append(pages.toString())
|
||||
append("?search=")
|
||||
append(query.urlEncoded())
|
||||
append("&")
|
||||
}
|
||||
|
||||
!tags.isNullOrEmpty() -> {
|
||||
append("/$tagUrl")
|
||||
append(tag?.key.orEmpty())
|
||||
append("/")
|
||||
append(pages.toString())
|
||||
append("?")
|
||||
}
|
||||
|
||||
else -> {
|
||||
append("/$listUrl")
|
||||
append(pages.toString())
|
||||
append("?")
|
||||
}
|
||||
}
|
||||
append("order_by=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("views")
|
||||
SortOrder.UPDATED -> append("lastest")
|
||||
SortOrder.ALPHABETICAL -> append("name")
|
||||
else -> append("latest")
|
||||
}
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
|
||||
override fun parseMangaList(doc: Document): List<Manga> {
|
||||
return doc.select("div.story_item").map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
|
||||
@@ -9,7 +9,6 @@ import org.koitharu.kotatsu.parsers.site.manga18.Manga18Parser
|
||||
@MangaSourceParser("TUMANHWAS", "Tumanhwas", "es", ContentType.HENTAI)
|
||||
internal class Tumanhwas(context: MangaLoaderContext) :
|
||||
Manga18Parser(context, MangaSource.TUMANHWAS, "tumanhwas.club") {
|
||||
|
||||
override val selectTag = "div.item:contains(Géneros) div.info_value a"
|
||||
override val selectAlt = "div.item:contains(Títulos alternativos) div.info_value"
|
||||
}
|
||||
|
||||
@@ -21,9 +21,12 @@ internal abstract class MangaboxParser(
|
||||
SortOrder.UPDATED,
|
||||
SortOrder.POPULARITY,
|
||||
SortOrder.NEWEST,
|
||||
SortOrder.ALPHABETICAL,
|
||||
)
|
||||
|
||||
protected open val listUrl = "/genre-all"
|
||||
override val availableStates: Set<MangaState> = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED)
|
||||
|
||||
protected open val listUrl = "/advanced_search"
|
||||
protected open val searchUrl = "/search/story/"
|
||||
protected open val datePattern = "MMM dd,yy"
|
||||
|
||||
@@ -36,50 +39,64 @@ internal abstract class MangaboxParser(
|
||||
|
||||
@JvmField
|
||||
protected val ongoing: Set<String> = setOf(
|
||||
"Ongoing",
|
||||
"ongoing",
|
||||
)
|
||||
|
||||
@JvmField
|
||||
protected val finished: Set<String> = setOf(
|
||||
"Completed",
|
||||
"completed",
|
||||
)
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append(listUrl)
|
||||
append("/?s=all")
|
||||
when (filter) {
|
||||
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append(searchUrl)
|
||||
append(query.urlEncoded())
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
is MangaListFilter.Search -> {
|
||||
append("&keyw=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
} else if (!tags.isNullOrEmpty()) {
|
||||
append("/")
|
||||
append(tag?.key.orEmpty())
|
||||
append("/")
|
||||
append(page.toString())
|
||||
} else {
|
||||
append("$listUrl/")
|
||||
if (page > 1) {
|
||||
append(page.toString())
|
||||
}
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("?type=topview")
|
||||
SortOrder.UPDATED -> append("")
|
||||
SortOrder.NEWEST -> append("?type=newest")
|
||||
else -> append("")
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("&g_i=")
|
||||
filter.tags.forEach {
|
||||
append("_")
|
||||
append(it.key)
|
||||
append("_")
|
||||
}
|
||||
}
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&sts=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "ongoing"
|
||||
MangaState.FINISHED -> "completed"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
append("&orby=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("topview")
|
||||
SortOrder.UPDATED -> append("")
|
||||
SortOrder.NEWEST -> append("newest")
|
||||
SortOrder.ALPHABETICAL -> append("az")
|
||||
else -> append("")
|
||||
}
|
||||
}
|
||||
|
||||
null -> {}
|
||||
}
|
||||
|
||||
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
@@ -109,7 +126,8 @@ internal abstract class MangaboxParser(
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml()
|
||||
return doc.select(selectTagMap).mapNotNullToSet { a ->
|
||||
val tags = doc.select(selectTagMap).drop(1) // remove all tags
|
||||
return tags.mapNotNullToSet { a ->
|
||||
val key = a.attr("href").removeSuffix('/').substringAfterLast('/')
|
||||
val name = a.attr("title").replace(" Manga", "")
|
||||
MangaTag(
|
||||
@@ -129,25 +147,18 @@ internal abstract class MangaboxParser(
|
||||
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
|
||||
val fullUrl = manga.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||
|
||||
val chaptersDeferred = async { getChapters(doc) }
|
||||
|
||||
val desc = doc.selectFirstOrThrow(selectDesc).html()
|
||||
|
||||
val stateDiv = doc.select(selectState).text()
|
||||
|
||||
val state = stateDiv.let {
|
||||
when (it) {
|
||||
when (it.lowercase()) {
|
||||
in ongoing -> MangaState.ONGOING
|
||||
in finished -> MangaState.FINISHED
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
val alt = doc.body().select(selectAlt).text().replace("Alternative : ", "")
|
||||
|
||||
val aut = doc.body().select(selectAut).eachText().joinToString()
|
||||
|
||||
manga.copy(
|
||||
tags = doc.body().select(selectTag).mapNotNullToSet { a ->
|
||||
MangaTag(
|
||||
|
||||
@@ -9,13 +9,7 @@ import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser
|
||||
@MangaSourceParser("HMANGABAT", "MangaBat", "en")
|
||||
internal class Mangabat(context: MangaLoaderContext) :
|
||||
MangaboxParser(context, MangaSource.HMANGABAT) {
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("h.mangabat.com", "readmangabat.com")
|
||||
|
||||
override val otherDomain = "readmangabat.com"
|
||||
|
||||
override val searchUrl = "/search/manga/"
|
||||
|
||||
override val listUrl = "/manga-list-all"
|
||||
override val selectTagMap = "div.panel-category p.pn-category-row:not(.pn-category-row-border) a"
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ 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.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaListFilter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaState
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
@@ -13,73 +14,87 @@ import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.util.EnumSet
|
||||
|
||||
@MangaSourceParser("MANGAIRO", "MangaIro", "en")
|
||||
internal class Mangairo(context: MangaLoaderContext) :
|
||||
MangaboxParser(context, MangaSource.MANGAIRO) {
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("w.mangairo.com", "chap.mangairo.com")
|
||||
|
||||
override val otherDomain = "chap.mangairo.com"
|
||||
|
||||
override val datePattern = "MMM-dd-yy"
|
||||
override val listUrl = "/manga-list"
|
||||
override val searchUrl = "/list/search/"
|
||||
|
||||
override val selectDesc = "div#story_discription p"
|
||||
override val selectState = "ul.story_info_right li:contains(Status) a"
|
||||
override val selectAlt = "ul.story_info_right li:contains(Alter) h2"
|
||||
override val selectAut = "ul.story_info_right li:contains(Author) a"
|
||||
override val selectTag = "ul.story_info_right li:contains(Genres) a"
|
||||
|
||||
override val selectChapter = "div.chapter_list li"
|
||||
override val selectDate = "p"
|
||||
|
||||
override val selectPage = "div.panel-read-story img"
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.UPDATED,
|
||||
SortOrder.POPULARITY,
|
||||
SortOrder.NEWEST,
|
||||
)
|
||||
override val isMultipleTagsSupported = false
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append(searchUrl)
|
||||
append(query.urlEncoded())
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
append("$listUrl/")
|
||||
|
||||
append("/type-")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("topview")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("newest")
|
||||
else -> append("latest")
|
||||
is MangaListFilter.Search -> {
|
||||
append(searchUrl)
|
||||
append(filter.query.urlEncoded())
|
||||
append("?page=")
|
||||
}
|
||||
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
is MangaListFilter.Advanced -> {
|
||||
append(listUrl)
|
||||
append("/type-")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("topview")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("newest")
|
||||
else -> append("latest")
|
||||
}
|
||||
|
||||
append("/ctg-")
|
||||
append(tag?.key.orEmpty())
|
||||
} else {
|
||||
append("/ctg-all")
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append(it.key)
|
||||
}
|
||||
} else {
|
||||
append("all")
|
||||
}
|
||||
|
||||
append("/state-")
|
||||
if (filter.states.isNotEmpty()) {
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "ongoing"
|
||||
MangaState.FINISHED -> "completed"
|
||||
else -> "all"
|
||||
},
|
||||
)
|
||||
}
|
||||
} else {
|
||||
append("all")
|
||||
}
|
||||
|
||||
append("/page-")
|
||||
}
|
||||
|
||||
null -> {
|
||||
append(listUrl)
|
||||
append("/type-latest/ctg-all/state-all/page-")
|
||||
}
|
||||
append("/state-all/page-")
|
||||
append(page.toString())
|
||||
}
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
|
||||
return doc.select("div.story-item").map { div ->
|
||||
val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href")
|
||||
Manga(
|
||||
@@ -115,13 +130,9 @@ internal class Mangairo(context: MangaLoaderContext) :
|
||||
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
|
||||
val fullUrl = manga.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||
|
||||
val chaptersDeferred = async { getChapters(doc) }
|
||||
|
||||
val desc = doc.selectFirstOrThrow(selectDesc).html()
|
||||
|
||||
val stateDiv = doc.select(selectState).text()
|
||||
|
||||
val state = stateDiv.let {
|
||||
when (it) {
|
||||
in ongoing -> MangaState.ONGOING
|
||||
@@ -131,9 +142,7 @@ internal class Mangairo(context: MangaLoaderContext) :
|
||||
}
|
||||
|
||||
val alt = doc.body().select(selectAlt).text().replace("Alternative : ", "")
|
||||
|
||||
val aut = doc.body().select(selectAut).eachText().joinToString()
|
||||
|
||||
manga.copy(
|
||||
tags = doc.body().select(selectTag).mapNotNullToSet { a ->
|
||||
MangaTag(
|
||||
@@ -151,6 +160,4 @@ internal class Mangairo(context: MangaLoaderContext) :
|
||||
isNsfw = manga.isNsfw,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -8,54 +8,69 @@ import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.EnumSet
|
||||
|
||||
@MangaSourceParser("MANGAKAKALOT", "Mangakakalot", "en")
|
||||
@MangaSourceParser("MANGAKAKALOT", "Mangakakalot.com", "en")
|
||||
internal class Mangakakalot(context: MangaLoaderContext) :
|
||||
MangaboxParser(context, MangaSource.MANGAKAKALOT) {
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("mangakakalot.com", "chapmanganato.com")
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.UPDATED,
|
||||
SortOrder.POPULARITY,
|
||||
SortOrder.NEWEST,
|
||||
)
|
||||
override val isMultipleTagsSupported = false
|
||||
override val otherDomain = "chapmanganato.com"
|
||||
|
||||
override val listUrl = "/manga_list"
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append(searchUrl)
|
||||
append(query.replace(" ", "_").urlEncoded())
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
|
||||
} else {
|
||||
append("$listUrl/")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("?type=topview")
|
||||
SortOrder.UPDATED -> append("?type=latest")
|
||||
SortOrder.NEWEST -> append("?type=newest")
|
||||
else -> append("?type=latest")
|
||||
is MangaListFilter.Search -> {
|
||||
append(searchUrl)
|
||||
append(filter.query.urlEncoded())
|
||||
append("?page=")
|
||||
}
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
append("&category=")
|
||||
append(tag?.key.orEmpty())
|
||||
} else {
|
||||
append("&category=all")
|
||||
}
|
||||
append("&state=all&page=")
|
||||
append(page)
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
append(listUrl)
|
||||
append("?type=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("topview")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("newest")
|
||||
else -> append("latest")
|
||||
}
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("&category=")
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append(it.key)
|
||||
}
|
||||
}
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&state=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "ongoing"
|
||||
MangaState.FINISHED -> "completed"
|
||||
else -> "all"
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
append("&page=")
|
||||
}
|
||||
|
||||
null -> {
|
||||
append(listUrl)
|
||||
append("?type=latest&page=")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
@@ -81,8 +96,21 @@ internal class Mangakakalot(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getChapters(doc: Document): List<MangaChapter> {
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml()
|
||||
val tags = doc.select("ul.tag li a").drop(1)
|
||||
return tags.mapNotNullToSet { a ->
|
||||
val key = a.attr("href").substringAfterLast("category=").substringBefore("&")
|
||||
val name = a.attr("title").replace(" Manga", "")
|
||||
MangaTag(
|
||||
key = key,
|
||||
title = name,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getChapters(doc: Document): List<MangaChapter> {
|
||||
return doc.body().select(selectChapter).mapChapters(reversed = true) { i, li ->
|
||||
val a = li.selectFirstOrThrow("a")
|
||||
val href = a.attrAsRelativeUrl("href")
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package org.koitharu.kotatsu.parsers.site.mangabox.en
|
||||
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
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.*
|
||||
import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import java.util.EnumSet
|
||||
|
||||
@MangaSourceParser("MANGAKAKALOTTV", "Mangakakalot.tv", "en")
|
||||
internal class MangakakalotTv(context: MangaLoaderContext) :
|
||||
@@ -14,40 +17,61 @@ internal class MangakakalotTv(context: MangaLoaderContext) :
|
||||
override val configKeyDomain = ConfigKey.Domain("ww6.mangakakalot.tv")
|
||||
override val searchUrl = "/search/"
|
||||
override val listUrl = "/manga_list"
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.UPDATED,
|
||||
SortOrder.POPULARITY,
|
||||
SortOrder.NEWEST,
|
||||
)
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append(searchUrl)
|
||||
append(query.urlEncoded())
|
||||
append("?page=")
|
||||
append(page.toString())
|
||||
} else {
|
||||
append("$listUrl/")
|
||||
append("?type=")
|
||||
when (sortOrder) {
|
||||
SortOrder.POPULARITY -> append("topview")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("newest")
|
||||
else -> append("latest")
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
append(searchUrl)
|
||||
append(filter.query.urlEncoded())
|
||||
append("?page=")
|
||||
}
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
append("&category=")
|
||||
append(tag?.key.orEmpty())
|
||||
}
|
||||
if (page > 1) {
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
append(listUrl)
|
||||
append("?type=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("topview")
|
||||
SortOrder.UPDATED -> append("latest")
|
||||
SortOrder.NEWEST -> append("newest")
|
||||
else -> append("latest")
|
||||
}
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("&category=")
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append(it.key)
|
||||
}
|
||||
}
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&state=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "Ongoing"
|
||||
MangaState.FINISHED -> "Completed"
|
||||
else -> "all"
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
null -> {
|
||||
append(listUrl)
|
||||
append("?type=latest&page=")
|
||||
}
|
||||
}
|
||||
append(page.toString())
|
||||
}
|
||||
val doc = webClient.httpGet(url).parseHtml()
|
||||
return doc.select("div.list-truyen-item-wrap").ifEmpty {
|
||||
@@ -71,6 +95,38 @@ internal class MangakakalotTv(context: MangaLoaderContext) :
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga = coroutineScope {
|
||||
val fullUrl = manga.url.toAbsoluteUrl(domain)
|
||||
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||
val chaptersDeferred = async { getChapters(doc) }
|
||||
val desc = doc.selectFirstOrThrow(selectDesc).html()
|
||||
val stateDiv = doc.select(selectState).text().replace("Status : ", "")
|
||||
val state = stateDiv.let {
|
||||
when (it.lowercase()) {
|
||||
in ongoing -> MangaState.ONGOING
|
||||
in finished -> MangaState.FINISHED
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
val alt = doc.body().select(selectAlt).text().replace("Alternative : ", "")
|
||||
val aut = doc.body().select(selectAut).eachText().joinToString()
|
||||
manga.copy(
|
||||
tags = doc.body().select(selectTag).mapNotNullToSet { a ->
|
||||
MangaTag(
|
||||
key = a.attr("href").substringAfterLast("category=").substringBefore("&"),
|
||||
title = a.text().toTitleCase(),
|
||||
source = source,
|
||||
)
|
||||
},
|
||||
description = desc,
|
||||
altTitle = alt,
|
||||
author = aut,
|
||||
state = state,
|
||||
chapters = chaptersDeferred.await(),
|
||||
isNsfw = manga.isNsfw,
|
||||
)
|
||||
}
|
||||
|
||||
override val selectTagMap = "ul.tag li a"
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
|
||||
@@ -9,9 +9,6 @@ import org.koitharu.kotatsu.parsers.site.mangabox.MangaboxParser
|
||||
@MangaSourceParser("MANGANATO", "Manganato", "en")
|
||||
internal class Manganato(context: MangaLoaderContext) :
|
||||
MangaboxParser(context, MangaSource.MANGANATO) {
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("chapmanganato.com", "manganato.com")
|
||||
|
||||
override val otherDomain = "chapmanganato.com"
|
||||
|
||||
}
|
||||
|
||||
@@ -32,60 +32,72 @@ internal abstract class MangaReaderParser(
|
||||
override val availableSortOrders: Set<SortOrder>
|
||||
get() = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.ALPHABETICAL, SortOrder.NEWEST)
|
||||
|
||||
override val availableStates: Set<MangaState>
|
||||
get() = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED)
|
||||
|
||||
protected open val listUrl = "/manga"
|
||||
protected open val datePattern = "MMMM d, yyyy"
|
||||
protected open val isNetShieldProtected = false
|
||||
|
||||
private var tagCache: ArrayMap<String, MangaTag>? = null
|
||||
private val mutex = Mutex()
|
||||
protected open var lastSearchPage = 1
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
if (!query.isNullOrEmpty()) {
|
||||
if (page > lastSearchPage) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/page/")
|
||||
append(page)
|
||||
append("/?s=")
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
|
||||
val docs = webClient.httpGet(url).parseHtml()
|
||||
lastSearchPage = docs.selectFirst(".pagination .next")
|
||||
?.previousElementSibling()
|
||||
?.text()?.toIntOrNull() ?: 1
|
||||
return parseMangaList(docs)
|
||||
}
|
||||
|
||||
val sortQuery = when (sortOrder) {
|
||||
SortOrder.ALPHABETICAL -> "title"
|
||||
SortOrder.NEWEST -> "latest"
|
||||
SortOrder.POPULARITY -> "popular"
|
||||
SortOrder.UPDATED -> "update"
|
||||
else -> ""
|
||||
}
|
||||
val tagKey = "genre[]".urlEncoded()
|
||||
val tagQuery =
|
||||
if (tags.isNullOrEmpty()) "" else tags.joinToString(separator = "&", prefix = "&") { "$tagKey=${it.key}" }
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append(listUrl)
|
||||
append("/?order=")
|
||||
append(sortQuery)
|
||||
append(tagQuery)
|
||||
append("&page=")
|
||||
append(page)
|
||||
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
append("/page/")
|
||||
append(page.toString())
|
||||
append("/?s=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
append(listUrl)
|
||||
|
||||
append("/?order=")
|
||||
append(
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.ALPHABETICAL -> "title"
|
||||
SortOrder.NEWEST -> "latest"
|
||||
SortOrder.POPULARITY -> "popular"
|
||||
SortOrder.UPDATED -> "update"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
|
||||
val tagKey = "genre[]".urlEncoded()
|
||||
val tagQuery =
|
||||
if (filter.tags.isEmpty()) ""
|
||||
else filter.tags.joinToString(separator = "&", prefix = "&") { "$tagKey=${it.key}" }
|
||||
append(tagQuery)
|
||||
|
||||
if (filter.states.isNotEmpty()) {
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&status=")
|
||||
when (it) {
|
||||
MangaState.ONGOING -> append("ongoing")
|
||||
MangaState.FINISHED -> append("completed")
|
||||
MangaState.PAUSED -> append("hiatus")
|
||||
else -> append("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
null -> {
|
||||
append(listUrl)
|
||||
append("/?order=update&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
|
||||
@@ -17,52 +17,58 @@ internal class SwaTeam(context: MangaLoaderContext) :
|
||||
override val selectMangaListImg = "img"
|
||||
override val isNetShieldProtected = true
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
if (!query.isNullOrEmpty()) {
|
||||
if (page > lastSearchPage) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/?s=")
|
||||
append(query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page)
|
||||
}
|
||||
|
||||
val docs = webClient.httpGet(url).parseHtml()
|
||||
lastSearchPage = docs.selectFirst(".pagination .next")
|
||||
?.previousElementSibling()
|
||||
?.text()?.toIntOrNull() ?: 1
|
||||
return parseMangaList(docs)
|
||||
}
|
||||
|
||||
val sortQuery = when (sortOrder) {
|
||||
SortOrder.ALPHABETICAL -> "a-z"
|
||||
SortOrder.NEWEST -> "added"
|
||||
SortOrder.POPULARITY -> "popular"
|
||||
SortOrder.UPDATED -> "update"
|
||||
else -> ""
|
||||
}
|
||||
val tagKey = "genre[]".urlEncoded()
|
||||
val tagQuery =
|
||||
if (tags.isNullOrEmpty()) "" else tags.joinToString(separator = "&", prefix = "&") { "$tagKey=${it.key}" }
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append(listUrl)
|
||||
append("/?order=")
|
||||
append(sortQuery)
|
||||
append(tagQuery)
|
||||
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
append("/?s=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
append(listUrl)
|
||||
|
||||
append("/?order=")
|
||||
append(
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.ALPHABETICAL -> "a-z"
|
||||
SortOrder.NEWEST -> "added"
|
||||
SortOrder.POPULARITY -> "popular"
|
||||
SortOrder.UPDATED -> "update"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
|
||||
val tagKey = "genre[]".urlEncoded()
|
||||
val tagQuery =
|
||||
if (filter.tags.isEmpty()) ""
|
||||
else filter.tags.joinToString(separator = "&", prefix = "&") { "$tagKey=${it.key}" }
|
||||
append(tagQuery)
|
||||
|
||||
if (filter.states.isNotEmpty()) {
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&status=")
|
||||
when (it) {
|
||||
MangaState.ONGOING -> append("ongoing")
|
||||
MangaState.FINISHED -> append("completed")
|
||||
MangaState.PAUSED -> append("hiatus")
|
||||
else -> append("")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append(listUrl)
|
||||
append("/?order=update")
|
||||
}
|
||||
}
|
||||
append("&page=")
|
||||
append(page)
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
|
||||
@@ -17,60 +17,52 @@ internal class ManhwaFreak(context: MangaLoaderContext) :
|
||||
|
||||
override val selectMangaList = ".listupd .lastest-serie"
|
||||
override val selectMangaListImg = "img"
|
||||
override val availableStates: Set<MangaState> = emptySet()
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
if (!query.isNullOrEmpty()) {
|
||||
if (page > lastSearchPage) {
|
||||
return emptyList()
|
||||
}
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/page/")
|
||||
append(page)
|
||||
append("/?s=")
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
val docs = webClient.httpGet(url).parseHtml()
|
||||
lastSearchPage = docs.selectFirst(".pagination .next")
|
||||
?.previousElementSibling()
|
||||
?.text()?.toIntOrNull() ?: 1
|
||||
return parseMangaList(docs)
|
||||
}
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/genres/?genre=")
|
||||
append(tag?.key.orEmpty())
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
val sortQuery = when (sortOrder) {
|
||||
SortOrder.ALPHABETICAL -> "az"
|
||||
SortOrder.NEWEST -> "new"
|
||||
SortOrder.POPULARITY -> "views"
|
||||
SortOrder.UPDATED -> ""
|
||||
else -> ""
|
||||
}
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append(listUrl)
|
||||
append("/?order=")
|
||||
append(sortQuery)
|
||||
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
append("/page/")
|
||||
append(page.toString())
|
||||
append("/?s=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("/genres/?genre=")
|
||||
append(it.key)
|
||||
}
|
||||
} else {
|
||||
append(listUrl)
|
||||
append("/?order=")
|
||||
append(
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.ALPHABETICAL -> "az"
|
||||
SortOrder.NEWEST -> "new"
|
||||
SortOrder.POPULARITY -> "views"
|
||||
SortOrder.UPDATED -> ""
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append(listUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@ 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.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaListFilter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.model.MangaState
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser
|
||||
import org.koitharu.kotatsu.parsers.util.domain
|
||||
@@ -19,31 +20,40 @@ internal class RizzComic(context: MangaLoaderContext) :
|
||||
override val datePattern = "dd MMM yyyy"
|
||||
override val listUrl = "/series"
|
||||
|
||||
override val availableSortOrders: Set<SortOrder>
|
||||
get() = EnumSet.of(SortOrder.ALPHABETICAL)
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL)
|
||||
override val availableStates: Set<MangaState> = emptySet()
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
// TODO Query created in json
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
if (!query.isNullOrEmpty()) {
|
||||
throw IllegalArgumentException("Search is not supported by this source")
|
||||
}
|
||||
val url = if (!tags.isNullOrEmpty()) {
|
||||
buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/genre/")
|
||||
append(tag?.key.orEmpty())
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
throw IllegalArgumentException("Search is not supported by this source")
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("/genre/")
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append(it.key)
|
||||
}
|
||||
} else {
|
||||
append(listUrl)
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append(listUrl)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
"https://$domain$listUrl"
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ 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.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaListFilter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.model.MangaState
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser
|
||||
import org.koitharu.kotatsu.parsers.util.domain
|
||||
import org.koitharu.kotatsu.parsers.util.oneOrThrowIfMany
|
||||
import org.koitharu.kotatsu.parsers.util.parseHtml
|
||||
import org.koitharu.kotatsu.parsers.util.urlEncoded
|
||||
import java.util.*
|
||||
@@ -19,53 +21,34 @@ internal class Zahard(context: MangaLoaderContext) :
|
||||
override val listUrl = "/library"
|
||||
override val selectChapter = "#chapterlist > ul > a"
|
||||
override val selectPage = "div#chapter_imgs img"
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.NEWEST)
|
||||
override val availableStates: Set<MangaState> = emptySet()
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
|
||||
override val availableSortOrders: Set<SortOrder>
|
||||
get() = EnumSet.of(SortOrder.NEWEST)
|
||||
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
if (!query.isNullOrEmpty()) {
|
||||
if (page > lastSearchPage) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append(listUrl)
|
||||
append("?search=")
|
||||
append(query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page)
|
||||
}
|
||||
|
||||
val docs = webClient.httpGet(url).parseHtml()
|
||||
lastSearchPage = docs.selectFirst("a[rel=next]")
|
||||
?.previousElementSibling()
|
||||
?.text()?.toIntOrNull() ?: 1
|
||||
return parseMangaList(docs)
|
||||
}
|
||||
|
||||
val tagKey = "tag".urlEncoded()
|
||||
val tagQuery =
|
||||
if (tags.isNullOrEmpty()) "" else tags.joinToString(separator = "&", prefix = "&") { "$tagKey=${it.key}" }
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append(listUrl)
|
||||
append("?page=")
|
||||
append(page)
|
||||
append(tagQuery)
|
||||
append(page.toString())
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
append("&search=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("tag=")
|
||||
append(it.key)
|
||||
}
|
||||
}
|
||||
|
||||
null -> {}
|
||||
}
|
||||
}
|
||||
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,41 +12,41 @@ import java.util.ArrayList
|
||||
import java.util.Calendar
|
||||
import java.util.EnumSet
|
||||
|
||||
@MangaSourceParser("TU_MANHWAS", "TuManhwas", "es")
|
||||
@MangaSourceParser("TU_MANHWAS", "TuManhwas.com", "es")
|
||||
internal class TuManhwas(context: MangaLoaderContext) :
|
||||
MangaReaderParser(context, MangaSource.TU_MANHWAS, "tumanhwas.com", 20, 20) {
|
||||
override val listUrl = "/biblioteca"
|
||||
override val selectPage = "div#readerarea img"
|
||||
|
||||
|
||||
override val availableSortOrders: Set<SortOrder>
|
||||
get() = EnumSet.of(SortOrder.NEWEST)
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.NEWEST)
|
||||
override val availableStates: Set<MangaState> = emptySet()
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> = emptySet()
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append(listUrl)
|
||||
append("?page=")
|
||||
append(page)
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
append("&genero=")
|
||||
append(tag?.key.orEmpty())
|
||||
}
|
||||
if (!query.isNullOrEmpty()) {
|
||||
append("&search=")
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
append(page.toString())
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
append("&search=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("&genero=")
|
||||
append(it.key)
|
||||
}
|
||||
}
|
||||
|
||||
null -> {}
|
||||
}
|
||||
}
|
||||
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
|
||||
|
||||
@@ -18,61 +18,52 @@ internal class ManhwaFreakFr(context: MangaLoaderContext) :
|
||||
override val selectMangaList = ".listupd .lastest-serie"
|
||||
override val selectMangaListImg = "img"
|
||||
override val sourceLocale: Locale = Locale.ENGLISH
|
||||
override val availableStates: Set<MangaState> = emptySet()
|
||||
override val isMultipleTagsSupported = false
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
if (!query.isNullOrEmpty()) {
|
||||
if (page > lastSearchPage) {
|
||||
return emptyList()
|
||||
}
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/page/")
|
||||
append(page)
|
||||
append("/?s=")
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
val docs = webClient.httpGet(url).parseHtml()
|
||||
lastSearchPage = docs.selectFirst(".pagination .next")
|
||||
?.previousElementSibling()
|
||||
?.text()?.toIntOrNull() ?: 1
|
||||
return parseMangaList(docs)
|
||||
}
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
val tag = tags.oneOrThrowIfMany()
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/genres/?genre=")
|
||||
append(tag?.key.orEmpty())
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
val sortQuery = when (sortOrder) {
|
||||
SortOrder.ALPHABETICAL -> "az"
|
||||
SortOrder.NEWEST -> "new"
|
||||
SortOrder.POPULARITY -> "views"
|
||||
SortOrder.UPDATED -> ""
|
||||
else -> ""
|
||||
}
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append(listUrl)
|
||||
append("/?order=")
|
||||
append(sortQuery)
|
||||
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
append("/page/")
|
||||
append(page.toString())
|
||||
append("/?s=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("/genres/?genre=")
|
||||
append(it.key)
|
||||
}
|
||||
} else {
|
||||
append(listUrl)
|
||||
append("/?order=")
|
||||
append(
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.ALPHABETICAL -> "az"
|
||||
SortOrder.NEWEST -> "new"
|
||||
SortOrder.POPULARITY -> "views"
|
||||
SortOrder.UPDATED -> ""
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append(listUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
|
||||
@@ -3,13 +3,16 @@ package org.koitharu.kotatsu.parsers.site.mangareader.id
|
||||
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.MangaListFilter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.model.MangaState
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser
|
||||
import org.koitharu.kotatsu.parsers.util.domain
|
||||
import org.koitharu.kotatsu.parsers.util.oneOrThrowIfMany
|
||||
import org.koitharu.kotatsu.parsers.util.parseHtml
|
||||
import org.koitharu.kotatsu.parsers.util.urlEncoded
|
||||
import java.util.EnumSet
|
||||
import java.util.Locale
|
||||
|
||||
@MangaSourceParser("COSMIC_SCANS", "CosmicScans.id", "id")
|
||||
@@ -17,54 +20,50 @@ internal class CosmicScans(context: MangaLoaderContext) :
|
||||
MangaReaderParser(context, MangaSource.COSMIC_SCANS, "cosmicscans.id", pageSize = 30, searchPageSize = 30) {
|
||||
|
||||
override val sourceLocale: Locale = Locale.ENGLISH
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(SortOrder.UPDATED)
|
||||
override val availableStates: Set<MangaState> = emptySet()
|
||||
override val isMultipleTagsSupported = false
|
||||
override val listUrl = "/semua-komik"
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
if (!query.isNullOrEmpty()) {
|
||||
if (page > lastSearchPage) {
|
||||
return emptyList()
|
||||
}
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/page/")
|
||||
append(page)
|
||||
append("/?s=")
|
||||
append(query.urlEncoded())
|
||||
}
|
||||
val docs = webClient.httpGet(url).parseHtml()
|
||||
lastSearchPage = docs.selectFirst(".pagination .next")
|
||||
?.previousElementSibling()
|
||||
?.text()?.toIntOrNull() ?: 1
|
||||
return parseMangaList(docs)
|
||||
}
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
val sortQuery = when (sortOrder) {
|
||||
SortOrder.ALPHABETICAL -> "title"
|
||||
SortOrder.NEWEST -> "latest"
|
||||
SortOrder.POPULARITY -> "popular"
|
||||
SortOrder.UPDATED -> "update"
|
||||
else -> ""
|
||||
}
|
||||
val tagKey = "genre[]".urlEncoded()
|
||||
val tagQuery =
|
||||
if (tags.isNullOrEmpty()) "" else tags.joinToString(separator = "&", prefix = "&") { "$tagKey=${it.key}" }
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append(listUrl)
|
||||
append("/?order=")
|
||||
append(sortQuery)
|
||||
append(tagQuery)
|
||||
append("&page=")
|
||||
append(page)
|
||||
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
append("/page/")
|
||||
append(page.toString())
|
||||
append("/?s=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
filter.tags.oneOrThrowIfMany()?.let {
|
||||
append("/genres/")
|
||||
append(it.key)
|
||||
append("/page/")
|
||||
append(page.toString())
|
||||
append('/')
|
||||
}
|
||||
} else {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
append(listUrl)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
null -> {
|
||||
if (page > 1) {
|
||||
return emptyList()
|
||||
}
|
||||
append(listUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
|
||||
@@ -3,11 +3,13 @@ package org.koitharu.kotatsu.parsers.site.mangareader.id
|
||||
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.MangaListFilter
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.model.MangaState
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.parsers.site.mangareader.MangaReaderParser
|
||||
import org.koitharu.kotatsu.parsers.util.domain
|
||||
import org.koitharu.kotatsu.parsers.util.oneOrThrowIfMany
|
||||
import org.koitharu.kotatsu.parsers.util.parseHtml
|
||||
import org.koitharu.kotatsu.parsers.util.urlEncoded
|
||||
|
||||
@@ -19,50 +21,61 @@ internal class KomikSan(context: MangaLoaderContext) :
|
||||
override val listUrl = "/list"
|
||||
override val datePattern = "MMM d, yyyy"
|
||||
|
||||
override suspend fun getListPage(
|
||||
page: Int,
|
||||
query: String?,
|
||||
tags: Set<MangaTag>?,
|
||||
sortOrder: SortOrder,
|
||||
): List<Manga> {
|
||||
if (!query.isNullOrEmpty()) {
|
||||
if (page > lastSearchPage) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append("/search?search=")
|
||||
append(query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page)
|
||||
}
|
||||
val docs = webClient.httpGet(url).parseHtml()
|
||||
lastSearchPage = docs.selectFirst(".pagination .next")
|
||||
?.previousElementSibling()
|
||||
?.text()?.toIntOrNull() ?: 1
|
||||
return parseMangaList(docs)
|
||||
}
|
||||
val sortQuery = when (sortOrder) {
|
||||
SortOrder.ALPHABETICAL -> "title"
|
||||
SortOrder.NEWEST -> "latest"
|
||||
SortOrder.POPULARITY -> "popular"
|
||||
SortOrder.UPDATED -> "update"
|
||||
else -> ""
|
||||
}
|
||||
val tagKey = "genre[]".urlEncoded()
|
||||
val tagQuery =
|
||||
if (tags.isNullOrEmpty()) "" else tags.joinToString(separator = "&", prefix = "&") { "$tagKey=${it.key}" }
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append(domain)
|
||||
append(listUrl)
|
||||
append("/?order=")
|
||||
append(sortQuery)
|
||||
append(tagQuery)
|
||||
append("&page=")
|
||||
append(page)
|
||||
when (filter) {
|
||||
|
||||
is MangaListFilter.Search -> {
|
||||
append("/search?search=")
|
||||
append(filter.query.urlEncoded())
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
append(listUrl)
|
||||
|
||||
append("/?order=")
|
||||
append(
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.ALPHABETICAL -> "title"
|
||||
SortOrder.NEWEST -> "latest"
|
||||
SortOrder.POPULARITY -> "popular"
|
||||
SortOrder.UPDATED -> "update"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
|
||||
val tagKey = "genre[]".urlEncoded()
|
||||
val tagQuery =
|
||||
if (filter.tags.isEmpty()) ""
|
||||
else filter.tags.joinToString(separator = "&", prefix = "&") { "$tagKey=${it.key}" }
|
||||
append(tagQuery)
|
||||
|
||||
if (filter.states.isNotEmpty()) {
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&status=")
|
||||
when (it) {
|
||||
MangaState.ONGOING -> append("ongoing")
|
||||
MangaState.FINISHED -> append("completed")
|
||||
MangaState.PAUSED -> append("hiatus")
|
||||
else -> append("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
null -> {
|
||||
append(listUrl)
|
||||
append("/?order=update&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseHtml())
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user