From 81caa59a362203ef4f4ca901209345c1f0aeba14 Mon Sep 17 00:00:00 2001 From: devi Date: Sat, 25 Nov 2023 17:06:21 +0100 Subject: [PATCH 01/18] Add status filter support Add oneOrThrowIfMany() on MangaState --- .../kotatsu/parsers/site/all/BatoToParser.kt | 91 +++++++++++++------ 1 file changed, 63 insertions(+), 28 deletions(-) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/BatoToParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/BatoToParser.kt index c9da08f5..a453063a 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/BatoToParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/BatoToParser.kt @@ -33,6 +33,8 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser( SortOrder.ALPHABETICAL, ) + override val availableStates: Set = EnumSet.allOf(MangaState::class.java) + override val configKeyDomain = ConfigKey.Domain( "bato.to", "batocomic.com", @@ -61,37 +63,68 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser( "zbato.org", ) - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - 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 { + when (filter) { - SortOrder.POPULARITY -> append("views_a.za") - SortOrder.NEWEST -> append("create.za") - SortOrder.ALPHABETICAL -> append("title.az") - SortOrder.RATING -> Unit + 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") + } + + if (filter.states.isNotEmpty()) { + val state = filter.states.oneOrThrowIfMany() + append("&release=") + append( + when (state) { + MangaState.ONGOING -> "ongoing" + MangaState.FINISHED -> "completed" + MangaState.ABANDONED -> "cancelled" + MangaState.PAUSED -> "hiatus" + else -> "" + }, + ) + + } + + // 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 +142,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, From 44d91e43adfbdefe8ff0f7dbd17e529fed0fa7ac Mon Sep 17 00:00:00 2001 From: devi Date: Sat, 25 Nov 2023 17:34:52 +0100 Subject: [PATCH 02/18] Add altName on ComicK Change oneOrThrowIfMany on Bato.To --- .../koitharu/kotatsu/parsers/site/all/BatoToParser.kt | 10 +++------- .../kotatsu/parsers/site/all/ComickFunParser.kt | 6 ++++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/BatoToParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/BatoToParser.kt index a453063a..39c95502 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/BatoToParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/BatoToParser.kt @@ -64,8 +64,8 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser( ) override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - when (filter) { + when (filter) { is MangaListFilter.Search -> { return search(page, filter.query) } @@ -85,21 +85,17 @@ internal class BatoToParser(context: MangaLoaderContext) : PagedMangaParser( else -> append("update.za") } - if (filter.states.isNotEmpty()) { - val state = filter.states.oneOrThrowIfMany() + filter.states.oneOrThrowIfMany()?.let { append("&release=") append( - when (state) { + when (it) { MangaState.ONGOING -> "ongoing" MangaState.FINISHED -> "completed" MangaState.ABANDONED -> "cancelled" MangaState.PAUSED -> "hiatus" - else -> "" }, ) - } - // langs= en ... if (filter.tags.isNotEmpty()) { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ComickFunParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ComickFunParser.kt index 9e579518..74524ec7 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ComickFunParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ComickFunParser.kt @@ -96,7 +96,7 @@ internal class ComickFunParser(context: MangaLoaderContext) : PagedMangaParser(c largeCoverUrl = null, description = jo.getStringOrNull("desc"), tags = jo.selectGenres(tagsMap), - state = when (jo.getIntOrDefault("status", 0)) { + state = when (jo.getInt("status")) { 1 -> MangaState.ONGOING 2 -> MangaState.FINISHED 3 -> MangaState.ABANDONED @@ -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 { From b8ca21d58832c02533d504d624e6c44217d9bac8 Mon Sep 17 00:00:00 2001 From: devi Date: Sat, 25 Nov 2023 17:37:04 +0100 Subject: [PATCH 03/18] unwanted change --- .../org/koitharu/kotatsu/parsers/site/all/ComickFunParser.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ComickFunParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ComickFunParser.kt index 74524ec7..30a4778d 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ComickFunParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ComickFunParser.kt @@ -96,7 +96,7 @@ internal class ComickFunParser(context: MangaLoaderContext) : PagedMangaParser(c largeCoverUrl = null, description = jo.getStringOrNull("desc"), tags = jo.selectGenres(tagsMap), - state = when (jo.getInt("status")) { + state = when (jo.getIntOrDefault("status", 0)) { 1 -> MangaState.ONGOING 2 -> MangaState.FINISHED 3 -> MangaState.ABANDONED From 7d67b718a5f77a83c07787a8810b516d0fc5d47a Mon Sep 17 00:00:00 2001 From: devi Date: Sat, 25 Nov 2023 17:45:31 +0100 Subject: [PATCH 04/18] Mangadex add filter.states and info states --- .../parsers/site/all/MangaDexParser.kt | 82 +++++++++++-------- 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt index 7edcff84..53e2b361 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/MangaDexParser.kt @@ -30,19 +30,12 @@ internal class MangaDexParser(context: MangaLoaderContext) : MangaParser(context override val configKeyDomain = ConfigKey.Domain("mangadex.org") - override val availableSortOrders: EnumSet = EnumSet.of( - SortOrder.UPDATED, - SortOrder.ALPHABETICAL, - SortOrder.NEWEST, - SortOrder.POPULARITY, - ) + override val availableSortOrders: EnumSet = EnumSet.allOf(SortOrder::class.java) - override suspend fun getList( - offset: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { + override val availableStates: Set = EnumSet.allOf(MangaState::class.java) + + + override suspend fun getList(offset: Int, filter: MangaListFilter?): List { 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"]) From 5fff2adbfb10d53ab30ded58d880d4bc19a2dd31 Mon Sep 17 00:00:00 2001 From: devi Date: Sat, 25 Nov 2023 17:57:38 +0100 Subject: [PATCH 05/18] MadaraParser add filter.states - Some sources madara, need to be changed too --- .../parsers/site/madara/MadaraParser.kt | 144 ++++++++++++------ 1 file changed, 100 insertions(+), 44 deletions(-) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt index 1efb0be5..1bf55d38 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt @@ -33,6 +33,8 @@ internal abstract class MadaraParser( SortOrder.RATING, ) + override val availableStates: Set = 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,121 @@ 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?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + 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 -> { + + val tag = filter.tags.oneOrThrowIfMany() + if (filter.tags.isNotEmpty()) { + append("/$tagPrefix") + append(tag?.key.orEmpty()) + 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() + } + + is MangaListFilter.Advanced -> { + + val tag = filter.tags.oneOrThrowIfMany() + payload["vars[wp-manga-genre]"] = tag?.key.orEmpty() + + 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" + } + } } - SortOrder.RATING -> {} + 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, From 1293233ad92fdda2d68db110c43d77b5b839b4d6 Mon Sep 17 00:00:00 2001 From: devi Date: Sat, 25 Nov 2023 20:04:47 +0100 Subject: [PATCH 06/18] fix getListPage Some sources madara --- .../parsers/site/madara/all/Manga18Fx.kt | 54 ++++++----- .../parsers/site/madara/all/Manhwa18Cc.kt | 56 ++++++------ .../parsers/site/madara/en/Hentai4Free.kt | 69 +++++++------- .../parsers/site/madara/en/InstaManhwa.kt | 64 ++++++------- .../parsers/site/madara/en/IsekaiScan.kt | 78 ++++++++-------- .../site/madara/en/IsekaiScanEuParser.kt | 89 ++++++++++++------- .../parsers/site/madara/en/MangaDass.kt | 76 ++++++++-------- .../parsers/site/madara/en/MangaDna.kt | 68 +++++++------- .../parsers/site/madara/en/MangaPure.kt | 70 ++++++++------- .../kotatsu/parsers/site/madara/en/Manhwaz.kt | 79 +++++++++------- .../site/madara/es/DragonTranslationParser.kt | 55 ++++++------ .../parsers/site/madara/es/MonarcaManga.kt | 84 ----------------- .../parsers/site/madara/es/TmoManga.kt | 42 +++++---- .../parsers/site/madara/id/ManhwaHub.kt | 57 +++++------- .../parsers/site/madara/vi/Saytruyenhay.kt | 66 ++++++++------ 15 files changed, 503 insertions(+), 504 deletions(-) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/Manga18Fx.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/Manga18Fx.kt index 5547d042..60451400 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/Manga18Fx.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/Manga18Fx.kt @@ -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?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + + init { + paginator.firstPage = 1 + searchPaginator.firstPage = 1 + } + + override val availableStates: Set get() = emptySet() + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + 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) } } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/Manhwa18Cc.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/Manhwa18Cc.kt index 83274803..8e5ca159 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/Manhwa18Cc.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/all/Manhwa18Cc.kt @@ -26,48 +26,48 @@ internal class Manhwa18Cc(context: MangaLoaderContext) : searchPaginator.firstPage = 1 } - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override val availableStates: Set get() = emptySet() + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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("?") - } - else -> { - append("/$listUrl") - if (page > 1) { - append(page) + append("?orderby=") + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("trending") + SortOrder.UPDATED -> append("latest") + SortOrder.ALPHABETICAL -> append("alphabet") + SortOrder.RATING -> append("rating") + else -> append("latest") } - append("?") } - } - append("m_orderby=") - when (sortOrder) { - SortOrder.POPULARITY -> append("trending") - SortOrder.UPDATED -> append("latest") - SortOrder.ALPHABETICAL -> append("alphabet") - SortOrder.RATING -> append("rating") - else -> append("latest") + + null -> { + append("?s&post_type=wp-manga&m_orderby=latest") + } } } val doc = webClient.httpGet(url).parseHtml() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Hentai4Free.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Hentai4Free.kt index 0a0e3334..fdca0963 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Hentai4Free.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Hentai4Free.kt @@ -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 get() = emptySet() - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - - val tag = tags.oneOrThrowIfMany() + init { + paginator.firstPage = 1 + searchPaginator.firstPage = 1 + } + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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 { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/InstaManhwa.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/InstaManhwa.kt index 1febb799..cde6e581 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/InstaManhwa.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/InstaManhwa.kt @@ -23,46 +23,48 @@ internal class InstaManhwa(context: MangaLoaderContext) : SortOrder.NEWEST, ) - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override val availableStates: Set get() = emptySet() + + init { + paginator.firstPage = 1 + searchPaginator.firstPage = 1 + } + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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 -> { + 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()) + } } - else -> { - - when (sortOrder) { - SortOrder.UPDATED -> append("/latest") - SortOrder.NEWEST -> append("/new") - SortOrder.ALPHABETICAL -> append("/alphabet") - else -> append("/latest") - } - append("?page=") - append(pages.toString()) + null -> { + append("/latest?page=") + append(page.toString()) } } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/IsekaiScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/IsekaiScan.kt index 6464a2de..c0a9278f 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/IsekaiScan.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/IsekaiScan.kt @@ -22,50 +22,58 @@ internal class IsekaiScan(context: MangaLoaderContext) : SortOrder.UPDATED, ) - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override val availableStates: Set get() = emptySet() + + init { + paginator.firstPage = 1 + searchPaginator.firstPage = 1 + } + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + 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 { 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, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/IsekaiScanEuParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/IsekaiScanEuParser.kt index 01657f53..c5b561e3 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/IsekaiScanEuParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/IsekaiScanEuParser.kt @@ -14,50 +14,71 @@ internal class IsekaiScanEuParser(context: MangaLoaderContext) : override val withoutAjax = true override val listUrl = "mangax/" - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + init { + paginator.firstPage = 1 + searchPaginator.firstPage = 1 + } + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + 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 -> { - else -> { + 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("/$listUrl") - append("/page/") - append(pages.toString()) - 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("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() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaDass.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaDass.kt index 3520f9ff..d17a4199 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaDass.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaDass.kt @@ -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?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override val availableStates: Set get() = emptySet() + + init { + paginator.firstPage = 1 + searchPaginator.firstPage = 1 + } + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + 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 -> diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaDna.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaDna.kt index 4e8d905c..e3416f46 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaDna.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaDna.kt @@ -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 get() = emptySet() - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaPure.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaPure.kt index 44e53ff6..6277b7ce 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaPure.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/MangaPure.kt @@ -21,46 +21,54 @@ internal class MangaPure(context: MangaLoaderContext) : SortOrder.UPDATED, ) - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override val availableStates: Set get() = emptySet() + + init { + paginator.firstPage = 1 + searchPaginator.firstPage = 1 + } + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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()) } } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manhwaz.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manhwaz.kt index b2ada718..4e8bc2fe 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manhwaz.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/en/Manhwaz.kt @@ -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?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override val availableStates: Set get() = emptySet() + + override val availableSortOrders: Set = 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 { 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 -> { - else -> { + 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") + } + } + + 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() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/DragonTranslationParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/DragonTranslationParser.kt index a4afe517..b86bcbb8 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/DragonTranslationParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/DragonTranslationParser.kt @@ -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 = EnumSet.of(SortOrder.UPDATED) - override val selectPage = "div#chapter_imgs img" + override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) + override val availableStates: Set get() = emptySet() - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - - val tag = tags.oneOrThrowIfMany() + init { + paginator.firstPage = 1 + searchPaginator.firstPage = 1 + } + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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 -> { - else -> { + append("/mangas?page=") + append(page.toString()) - append("/mangas") - append("?page=") - append(pages.toString()) + val tag = filter.tags.oneOrThrowIfMany() + if (filter.tags.isNotEmpty()) { + append("&tag=") + append(tag?.key.orEmpty()) + } + } + + null -> { + append("/mangas?page=") + append(page.toString()) } } } + val doc = webClient.httpGet(url).parseHtml() return doc.select("div.video-bg div.col-6 ").map { div -> diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/MonarcaManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/MonarcaManga.kt index 9f4d6186..48b58636 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/MonarcaManga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/MonarcaManga.kt @@ -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?, - sortOrder: SortOrder, - ): List { - 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, - ) - } - } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/TmoManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/TmoManga.kt index 641b79f7..1ec9a6df 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/TmoManga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/es/TmoManga.kt @@ -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 = EnumSet.of(SortOrder.POPULARITY) + override val availableStates: Set get() = emptySet() init { paginator.firstPage = 1 searchPaginator.firstPage = 1 } - override val availableSortOrders: Set = EnumSet.of(SortOrder.POPULARITY) - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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 -> diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/id/ManhwaHub.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/id/ManhwaHub.kt index 36f06b33..a4c1991f 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/id/ManhwaHub.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/id/ManhwaHub.kt @@ -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 get() = emptySet() + override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) init { paginator.firstPage = 1 searchPaginator.firstPage = 1 } - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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 -> { - else -> { + 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()) + } - append("/$listUrl") - append("?page=") - append(page) - 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") + null -> { + append("/?page=") + append(page.toString()) + } } } val doc = webClient.httpGet(url).parseHtml() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/vi/Saytruyenhay.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/vi/Saytruyenhay.kt index c69b31b1..6a67b7e2 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/vi/Saytruyenhay.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/vi/Saytruyenhay.kt @@ -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 get() = emptySet() + override val availableSortOrders: Set = + EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.RATING, SortOrder.NEWEST) - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + init { + paginator.firstPage = 1 + searchPaginator.firstPage = 1 + } + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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() From ab2aad6bd045e3a79dc7d898f4f6c160dba0c497 Mon Sep 17 00:00:00 2001 From: devi Date: Sat, 25 Nov 2023 20:52:02 +0100 Subject: [PATCH 07/18] add filter on bentomanga add filter on ninemanga --- .../parsers/site/all/NineMangaParser.kt | 64 +++++++++++++------ .../parsers/site/fr/BentomangaParser.kt | 64 ++++++++++++------- 2 files changed, 88 insertions(+), 40 deletions(-) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/NineMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/NineMangaParser.kt index 5d9ef843..4ef6b192 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/NineMangaParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/NineMangaParser.kt @@ -35,6 +35,11 @@ internal abstract class NineMangaParser( SortOrder.POPULARITY, ) + override val availableStates: Set = 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?, - sortOrder: SortOrder, - ): List { + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/BentomangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/BentomangaParser.kt index 7b029cd5..ae86a442 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/BentomangaParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/BentomangaParser.kt @@ -28,35 +28,55 @@ internal class BentomangaParser(context: MangaLoaderContext) : PagedMangaParser( override val configKeyDomain = ConfigKey.Domain("bentomanga.com", "www.bentomanga.com") + override val availableStates: Set = EnumSet.allOf(MangaState::class.java) + init { paginator.firstPage = 0 searchPaginator.firstPage = 0 } - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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() From f62ea43de83615f83b4cf4b0b7bf9336a37670f0 Mon Sep 17 00:00:00 2001 From: devi Date: Sun, 26 Nov 2023 18:44:34 +0100 Subject: [PATCH 08/18] fix getlist on AnimeBootstrapParser, ImHentai, beetoon, CloneManga, MangaGeko, Po2Scans, Pururin add filter FlixScans, TeamXNovel, ComicExtra, MangaOwl.to, MangaTown, Manhwa18.net, ManhwasMen fix getlist and add tag list on DynastyScans fix getlist and add SortOrder.UPDATED on MangaStorm --- .../kotatsu/parsers/site/all/ImHentai.kt | 67 +++--- .../animebootstrap/AnimeBootstrapParser.kt | 51 ++--- .../parsers/site/animebootstrap/fr/PapScan.kt | 57 ++--- .../site/animebootstrap/id/KomikzoId.kt | 2 +- .../site/animebootstrap/id/NeuManga.kt | 4 +- .../site/animebootstrap/id/SekteKomik.kt | 1 - .../kotatsu/parsers/site/ar/FlixScans.kt | 84 +++++-- .../kotatsu/parsers/site/ar/MangaStorm.kt | 59 ++--- .../kotatsu/parsers/site/ar/TeamXNovel.kt | 91 +++++--- .../kotatsu/parsers/site/en/BeeToon.kt | 48 ++-- .../parsers/site/en/CloneMangaParser.kt | 22 +- .../kotatsu/parsers/site/en/ComicExtra.kt | 83 ++++--- .../kotatsu/parsers/site/en/DynastyScans.kt | 205 +++++++++++------- .../koitharu/kotatsu/parsers/site/en/Fakku.kt | 148 ------------- .../kotatsu/parsers/site/en/KskMoe.kt | 187 ---------------- .../kotatsu/parsers/site/en/MangaGeko.kt | 69 +++--- .../parsers/site/en/MangaTownParser.kt | 97 ++++++--- .../kotatsu/parsers/site/en/Mangaowl.kt | 73 ++++--- .../kotatsu/parsers/site/en/Manhwa18Parser.kt | 147 ++++++++----- .../kotatsu/parsers/site/en/ManhwasMen.kt | 45 ++-- .../kotatsu/parsers/site/en/Po2Scans.kt | 20 +- .../kotatsu/parsers/site/en/Pururin.kt | 60 ++--- 22 files changed, 767 insertions(+), 853 deletions(-) delete mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Fakku.kt delete mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/KskMoe.kt diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ImHentai.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ImHentai.kt index 845259d3..3c9e0e39 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ImHentai.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/ImHentai.kt @@ -24,43 +24,50 @@ internal class ImHentai(context: MangaLoaderContext) : override val isMultipleTagsSupported = false - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { val url = buildString { append("https://") append(domain) + when (filter) { + is MangaListFilter.Search -> { + append("/search/?key=") + append(filter.query.urlEncoded()) + append("&page=") + append(page) + } + + is MangaListFilter.Advanced -> { - 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("") + 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") + } + } } - 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") + + 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 -> diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/AnimeBootstrapParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/AnimeBootstrapParser.kt index c5a89e1d..28a02ff6 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/AnimeBootstrapParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/AnimeBootstrapParser.kt @@ -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?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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 -> { + + 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") + } - append("&sort=") - when (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 { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/fr/PapScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/fr/PapScan.kt index 0833785d..667cc56a 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/fr/PapScan.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/fr/PapScan.kt @@ -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 = EnumSet.of( @@ -34,40 +27,39 @@ internal class PapScan(context: MangaLoaderContext) : SortOrder.ALPHABETICAL, ) - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - - val tag = tags.oneOrThrowIfMany() - + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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) : ) } } - } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/id/KomikzoId.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/id/KomikzoId.kt index cd7d64a1..6e2da189 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/id/KomikzoId.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/id/KomikzoId.kt @@ -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") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/id/NeuManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/id/NeuManga.kt index 625459f6..4912109e 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/id/NeuManga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/id/NeuManga.kt @@ -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") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/id/SekteKomik.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/id/SekteKomik.kt index 86f48685..574cba24 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/id/SekteKomik.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/animebootstrap/id/SekteKomik.kt @@ -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 diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/FlixScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/FlixScans.kt index 95a12125..54d4e545 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/FlixScans.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/FlixScans.kt @@ -19,32 +19,66 @@ import java.util.* internal class FlixScans(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.FLIXSCANS, 18) { override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) + override val availableStates: Set = EnumSet.allOf(MangaState::class.java) override val configKeyDomain = ConfigKey.Domain("flixscans.com") - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val json = if (!query.isNullOrEmpty()) { - if (page > 1) { - return emptyList() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + + 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, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/MangaStorm.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/MangaStorm.kt index 74aec220..4859e841 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/MangaStorm.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/MangaStorm.kt @@ -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 = EnumSet.of(SortOrder.POPULARITY) + override val availableSortOrders: Set = 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?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() - val url = - if (!tags.isNullOrEmpty()) { - buildString { - append("https://") - append(domain) - append("/categories/") - append(tag?.key.orEmpty()) - append("?page=") + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + val url = buildString { + append("https://") + append(domain) + when (filter) { + is MangaListFilter.Search -> { + append("/mangas?page=") append(page) + 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() + } + } + } } - } else { - buildString { - append("https://") - append(domain) + + null -> { append("/mangas?page=") append(page) - if (!query.isNullOrEmpty()) { - append("&query=") - append(query.urlEncoded()) - } } } + } 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, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/TeamXNovel.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/TeamXNovel.kt index a2805601..252c6fcb 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/TeamXNovel.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ar/TeamXNovel.kt @@ -17,46 +17,71 @@ import java.util.* internal class TeamXNovel(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.TEAMXNOVEL, 10) { override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) + override val availableStates: Set = + 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?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + val url = buildString { append("https://") append(domain) - if (!tags.isNullOrEmpty()) { - append("/series?genre=") - append(tag?.key.orEmpty()) - if (page > 1) { - append("&page=") - append(page) - } - } 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("/") + when (filter) { + + is MangaListFilter.Search -> { + append("/series?search=") + append(filter.query.urlEncoded()) + if (page > 1) { + append("&page=") + append(page.toString()) + } } - 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 -> diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/BeeToon.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/BeeToon.kt index cb073d18..c5b6591d 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/BeeToon.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/BeeToon.kt @@ -19,43 +19,41 @@ internal class BeeToon(context: MangaLoaderContext) : override val isMultipleTagsSupported = false - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/CloneMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/CloneMangaParser.kt index 7d36dab3..e01e0060 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/CloneMangaParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/CloneMangaParser.kt @@ -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?, sortOrder: SortOrder): List { - if (query != null || offset > 0) { - return emptyList() + override suspend fun getList(offset: Int, filter: MangaListFilter?): List { + + 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 -> diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt index 1b1b40e2..042beaef 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ComicExtra.kt @@ -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 = EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED, SortOrder.NEWEST) + override val availableStates: Set = 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?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { val url = buildString { - append("https://$domain/") - if (!tags.isNullOrEmpty()) { - append(tag?.key.orEmpty()) - if (page > 1) { - append("/") - append(page) - } - } else if (!query.isNullOrEmpty()) { - append("comic-search?key=") - append(query.urlEncoded()) - if (page > 1) { - append("&page=") - 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 { - when (sortOrder) { - SortOrder.POPULARITY -> append("popular-comic/") - SortOrder.UPDATED -> append("new-comic/") - SortOrder.NEWEST -> append("recent-comic/") - else -> append("new-comic/") + + 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()) + } } - if (page > 1) { - append(page) + + null -> { + append("popular-comic") + if (page > 1) { + append("/") + append(page.toString()) + } } + } } val doc = webClient.httpGet(url).parseHtml() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/DynastyScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/DynastyScans.kt index 634dc9ec..2200b663 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/DynastyScans.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/DynastyScans.kt @@ -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?, - sortOrder: SortOrder, - ): List { - 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) + override val isMultipleTagsSupported = false + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + 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()) } - append("?view=groupings&page=") - append(page.toString()) - } else { - append("/series?view=cover&page=") - append(page.toString()) + return parseMangaListQuery(webClient.httpGet(url).parseHtml()) } - } - val doc = webClient.httpGet(url).parseHtml() - - // 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, - ) + + 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()) } - } 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 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 = emptySet() + + private fun parseMangaList(doc: Document): List { + 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 { + 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 { + return coroutineScope { + (1..3).map { page -> + async { getTags(page) } + } + }.awaitAll().flattenTo(ArraySet(360)) + } + + private suspend fun getTags(page: Int): Set { + 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, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Fakku.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Fakku.kt deleted file mode 100644 index b231da15..00000000 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Fakku.kt +++ /dev/null @@ -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 = - 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?, - sortOrder: SortOrder, - ): List { - 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 { - 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 { - 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") - } -} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/KskMoe.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/KskMoe.kt deleted file mode 100644 index e9c22fe8..00000000 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/KskMoe.kt +++ /dev/null @@ -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 = - 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?, - sortOrder: SortOrder, - ): List { - 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 { - return coroutineScope { - (1..2).map { page -> - async { getTags(page) } - } - }.awaitAll().flattenTo(ArraySet(360)) - } - - private suspend fun getTags(page: Int): Set { - 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 { - 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, - ) - } - } -} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaGeko.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaGeko.kt index 81327a2d..c1cf6a1e 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaGeko.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaGeko.kt @@ -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?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() - - 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") + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + + 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( diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaTownParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaTownParser.kt index 4cb4ed8f..6aad3269 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaTownParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/MangaTownParser.kt @@ -22,35 +22,69 @@ internal class MangaTownParser(context: MangaLoaderContext) : MangaParser(contex SortOrder.UPDATED, ) - private val regexTag = Regex("[^\\-]+-[^\\-]+-[^\\-]+-[^\\-]+-[^\\-]+-[^\\-]+") - - override suspend fun getList( - offset: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val sortKey = when (sortOrder) { - SortOrder.ALPHABETICAL -> "?name.az" - SortOrder.RATING -> "?rating.za" - SortOrder.UPDATED -> "?last_chapter_time.za" - else -> "" - } + override val availableStates: Set = EnumSet.of( + MangaState.ONGOING, + MangaState.FINISHED, + ) + + override val isMultipleTagsSupported = false + + override suspend fun getList(offset: Int, filter: MangaListFilter?): List { 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 } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Mangaowl.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Mangaowl.kt index 71f1d23a..a5e6fe0e 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Mangaowl.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Mangaowl.kt @@ -24,6 +24,7 @@ internal class Mangaowl(context: MangaLoaderContext) : SortOrder.UPDATED, SortOrder.RATING, ) + override val availableStates: Set = 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?, - sortOrder: SortOrder, - ): List { - 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 { + 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) { - append(tag.key) - } + is MangaListFilter.Advanced -> { + + append("/10-comics") append("?page=") append(page.toString()) + + filter.tags.forEach { tag -> + append("&genres=") + append(tag.key) + } + + 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 { - 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, ) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Parser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Parser.kt index 76f77453..27b1d5e9 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Parser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Parser.kt @@ -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 - 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 = 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 { + + 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?, - sortOrder: SortOrder, - ): List { - 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 { val chapterUrl = chapter.url.toAbsoluteUrl(domain) val doc = webClient.httpGet(chapterUrl).parseHtml() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ManhwasMen.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ManhwasMen.kt index 8bb514ca..970bd688 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ManhwasMen.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ManhwasMen.kt @@ -21,29 +21,42 @@ class ManhwasMen(context: MangaLoaderContext) : override val availableSortOrders: Set get() = EnumSet.of(SortOrder.POPULARITY) - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + 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 -> diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Po2Scans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Po2Scans.kt index 1659a2c5..bbd415dd 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Po2Scans.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Po2Scans.kt @@ -15,20 +15,28 @@ internal class Po2Scans(context: MangaLoaderContext) : MangaParser(context, Mang override val availableSortOrders: Set = EnumSet.of(SortOrder.ALPHABETICAL) override val configKeyDomain = ConfigKey.Domain("po2scans.com") - override suspend fun getList(offset: Int, query: String?, tags: Set?, sortOrder: SortOrder): List { + override suspend fun getList(offset: Int, filter: MangaListFilter?): List { 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(), diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Pururin.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Pururin.kt index 1940df62..ce48030f 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Pururin.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Pururin.kt @@ -24,37 +24,43 @@ internal class Pururin(context: MangaLoaderContext) : override val isMultipleTagsSupported = false - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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) } } } From 14b2457627c3ba36ba1b2623aa165fc62eab02e4 Mon Sep 17 00:00:00 2001 From: devi Date: Mon, 27 Nov 2023 18:57:47 +0100 Subject: [PATCH 09/18] fix getlist TempleScanEsp fix getlist and add tag on Details mangas TuMangaOnline --- .../kotatsu/parsers/site/es/TempleScanEsp.kt | 34 +++++--- .../parsers/site/es/TuMangaOnlineParser.kt | 79 +++++++++++-------- 2 files changed, 67 insertions(+), 46 deletions(-) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TempleScanEsp.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TempleScanEsp.kt index 1413e7b1..ec17b58d 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TempleScanEsp.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TempleScanEsp.kt @@ -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?, - sortOrder: SortOrder, - ): List { + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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()) } } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TuMangaOnlineParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TuMangaOnlineParser.kt index 595831ea..02ba7388 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TuMangaOnlineParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/es/TuMangaOnlineParser.kt @@ -32,41 +32,47 @@ class TuMangaOnlineParser(context: MangaLoaderContext) : PagedMangaParser( SortOrder.RATING, ) - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - - 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 { 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() }, From db770351f36a465b7fc71af0a349297a5853861f Mon Sep 17 00:00:00 2001 From: devi Date: Tue, 28 Nov 2023 21:07:54 +0100 Subject: [PATCH 10/18] add filter on FmreaderParser, LugnicaScans Fix GetList on FoolSlideParser , FuryoSociety , LegacyScans, LireScan, ScansMangas.me, ScantradUnion --- .../parsers/site/fmreader/FmreaderParser.kt | 85 ++++++---- .../parsers/site/fmreader/en/Manhwa18Com.kt | 106 ++++++------ .../parsers/site/fmreader/es/OlimpoScans.kt | 69 +++++--- .../kotatsu/parsers/site/fmreader/ja/Klz9.kt | 44 +---- .../parsers/site/foolslide/FoolSlideParser.kt | 76 ++++++--- .../site/foolslide/en/AssortedScans.kt | 50 +++--- .../parsers/site/foolslide/fr/HniScantrad.kt | 2 +- .../parsers/site/foolslide/it/PowerManga.kt | 2 - .../parsers/site/foolslide/it/Ramareader.kt | 2 - .../parsers/site/foolslide/it/ReadNifteam.kt | 2 - .../kotatsu/parsers/site/fr/FuryoSociety.kt | 29 ++-- .../parsers/site/fr/LegacyScansParser.kt | 148 ++++++++-------- .../kotatsu/parsers/site/fr/LireScan.kt | 64 ++++--- .../kotatsu/parsers/site/fr/LugnicaScans.kt | 158 +++++++++++------- .../kotatsu/parsers/site/fr/ScansMangasMe.kt | 65 +++---- .../kotatsu/parsers/site/fr/ScantradUnion.kt | 60 +++---- 16 files changed, 506 insertions(+), 456 deletions(-) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/FmreaderParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/FmreaderParser.kt index 3dbceae2..7fa7a03d 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/FmreaderParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/FmreaderParser.kt @@ -27,7 +27,11 @@ internal abstract class FmreaderParser( SortOrder.ALPHABETICAL, ) - override val isMultipleTagsSupported = false + override val availableStates: Set = 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 = setOf( - "On going", - "Incomplete", - "En curso", + "on going", + "incomplete", + "en curso", ) @JvmField protected val finished: Set = setOf( - "Completed", - "Completado", + "completed", + "completado", ) @JvmField protected val abandoned: Set = hashSetOf( - "Canceled", - "Cancelled", - "Drop", + "canceled", + "cancelled", + "drop", ) - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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 { 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 diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/en/Manhwa18Com.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/en/Manhwa18Com.kt index e3a86ef5..676c5564 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/en/Manhwa18Com.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/en/Manhwa18Com.kt @@ -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?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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") + append("/tim-kiem?page=") + append(page.toString()) + + when (filter) { + is MangaListFilter.Search -> { + append("&q=") + append(filter.query.urlEncoded()) } - } else { - append(listUrl) - append("?page=") - append(page.toString()) - when { - !query.isNullOrEmpty() -> { - append("&q=") - append(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 -> "" + }, + ) } - } - append("&sort=") - when (sortOrder) { - SortOrder.POPULARITY -> append("views") - SortOrder.UPDATED -> append("last_update") - SortOrder.ALPHABETICAL -> append("name") - else -> append("last_update") } + 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 { 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 diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/es/OlimpoScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/es/OlimpoScans.kt index 0b603f13..2ed70663 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/es/OlimpoScans.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/es/OlimpoScans.kt @@ -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?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/ja/Klz9.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/ja/Klz9.kt index 9d363040..4ab0ee63 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/ja/Klz9.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/ja/Klz9.kt @@ -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?, - sortOrder: SortOrder, - ): List { - 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 { 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, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/FoolSlideParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/FoolSlideParser.kt index f0b5211c..70bf7e34 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/FoolSlideParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/FoolSlideParser.kt @@ -32,35 +32,63 @@ internal abstract class FoolSlideParser( searchPaginator.firstPage = 1 } - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - 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 { + 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( diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/en/AssortedScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/en/AssortedScans.kt index 957cab69..4dbf8916 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/en/AssortedScans.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/en/AssortedScans.kt @@ -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?, - sortOrder: SortOrder, - ): List { + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + if (page > 1) { + return emptyList() + } - 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()) + 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) } - webClient.httpGet(url).parseHtml() } + val doc = webClient.httpGet(url).parseHtml() return doc.select("section.series, tr.result").map { div -> val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") Manga( diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/fr/HniScantrad.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/fr/HniScantrad.kt index 0debd791..bd069d28 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/fr/HniScantrad.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/fr/HniScantrad.kt @@ -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 diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/it/PowerManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/it/PowerManga.kt index e400f8a8..39f6844d 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/it/PowerManga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/it/PowerManga.kt @@ -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") { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/it/Ramareader.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/it/Ramareader.kt index 8a704477..cc0a6906 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/it/Ramareader.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/it/Ramareader.kt @@ -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") { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/it/ReadNifteam.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/it/ReadNifteam.kt index 1f567e69..5aee24f1 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/it/ReadNifteam.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/it/ReadNifteam.kt @@ -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") { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FuryoSociety.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FuryoSociety.kt index 2363c994..047e2eaa 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FuryoSociety.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FuryoSociety.kt @@ -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?, - sortOrder: SortOrder, - ): List { + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + 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") + } + + is MangaListFilter.Advanced -> { + + if (filter.sortOrder == SortOrder.ALPHABETICAL) { + append("/mangas") + } } - } else { - return emptyList() + + null -> {} } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LegacyScansParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LegacyScansParser.kt index cdb87743..3817021d 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LegacyScansParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LegacyScansParser.kt @@ -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 = EnumSet.of( - SortOrder.POPULARITY, - ) + override val availableSortOrders: Set = 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?, - sortOrder: SortOrder, - ): List { - + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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 { + 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 { + 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("\",{"), diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LireScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LireScan.kt index 6b3ca531..593d4470 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LireScan.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LireScan.kt @@ -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?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + val doc = - if (!query.isNullOrEmpty()) { // search only works with 4 or more letters - if (page > 1) { - return emptyList() + when (filter) { + is MangaListFilter.Search -> { + if (page > 1) { + 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() } - 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()) + + 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('/') + } } - 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 -> diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LugnicaScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LugnicaScans.kt index 7ad9c279..3545a377 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LugnicaScans.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/LugnicaScans.kt @@ -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 = 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?, - sortOrder: SortOrder, - ): List { - 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, - ) + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + when (filter) { + is MangaListFilter.Search -> { + throw IllegalArgumentException("Search is not supported by this source") } - } else { - val url = buildString { - append("https://") - append(domain) - append("/api/get/homegrid/") - append(page) + + 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()) + } } - 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, - ) + + 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 { + 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 { + 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 { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScansMangasMe.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScansMangasMe.kt index 15c285c2..1e9e892e 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScansMangasMe.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScansMangasMe.kt @@ -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?, - sortOrder: SortOrder, - ): List { + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + 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 { 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 { 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 { 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(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 } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScantradUnion.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScantradUnion.kt index 07f8dcc2..b5c13aff 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScantradUnion.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/ScantradUnion.kt @@ -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?, - sortOrder: SortOrder, - ): List { + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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(',') - } - append("/page/") - append(page.toString()) - } + 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("/") + } - else -> { - if (sortOrder == SortOrder.ALPHABETICAL) { - append("/manga/") - append("/page/") - append(page.toString()) - } + if (filter.sortOrder == SortOrder.UPDATED && page > 1) { + return emptyList() + } - if (sortOrder == SortOrder.UPDATED) { - append("") } + + } + + null -> { + append("/manga/page/") + append(page.toString()) + 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, ) From 7c2ac033d7598a8dcacbe4151feaccb970745819 Mon Sep 17 00:00:00 2001 From: devi Date: Wed, 29 Nov 2023 18:31:26 +0100 Subject: [PATCH 11/18] add filter on HeanCms , DoujinDesu.tv Fix GetList GalleryAdultsParser --- .../site/galleryadults/GalleryAdultsParser.kt | 50 +++---- .../parsers/site/galleryadults/all/Hentai3.kt | 77 ++++++----- .../site/galleryadults/all/HentaiEnvy.kt | 55 ++++---- .../site/galleryadults/all/HentaiEra.kt | 76 ++++++----- .../site/galleryadults/all/HentaiForce.kt | 72 ++++++----- .../site/galleryadults/all/HentaiFox.kt | 112 +++++++++------- .../site/galleryadults/all/NHentaiParser.kt | 82 ++++++------ .../site/galleryadults/all/NHentaiUk.kt | 48 ++++--- .../kotatsu/parsers/site/heancms/HeanCms.kt | 72 ++++++----- .../parsers/site/heancms/es/YugenMangasEs.kt | 84 ++++++------ .../parsers/site/heancmsalt/HeanCmsAlt.kt | 24 ++-- .../parsers/site/id/DoujinDesuParser.kt | 122 +++++++++++------- 12 files changed, 495 insertions(+), 379 deletions(-) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/GalleryAdultsParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/GalleryAdultsParser.kt index 9def242d..5b69fd57 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/GalleryAdultsParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/GalleryAdultsParser.kt @@ -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?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + 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("&") + } + + 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("/?") + } } - } else if (!query.isNullOrEmpty()) { - append("/search/?q=") - append(query.urlEncoded()) - append("&") - } else { - append("/?") + + null -> append("/?") } append("page=") append(page) } + return parseMangaList(webClient.httpGet(url).parseHtml()) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/Hentai3.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/Hentai3.kt index 65b653b5..ed39facf 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/Hentai3.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/Hentai3.kt @@ -31,45 +31,58 @@ internal class Hentai3(context: MangaLoaderContext) : "/japanese", ) - override val sortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) + override val isMultipleTagsSupported = true - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - if (query.isNullOrEmpty() && tags != null && tags.size > 1) { - return getListPage(page, buildQuery(tags), emptySet(), sortOrder) - } + override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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()) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiEnvy.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiEnvy.kt index cb8a199a..95407f77 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiEnvy.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiEnvy.kt @@ -32,40 +32,45 @@ internal class HentaiEnvy(context: MangaLoaderContext) : "/portuguese", ) - override val sortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) + override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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") + when (filter) { + is MangaListFilter.Search -> { + append("/search/?s_key=") + append(filter.query.urlEncoded()) + 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("/?") } - append("/?") } - } else if (!query.isNullOrEmpty()) { - append("/search/?s_key=") - append(query.urlEncoded()) - append("&") - } else { - append("/?") + + null -> append("/?") } append("page=") append(page) + } return parseMangaList(webClient.httpGet(url).parseHtml()) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiEra.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiEra.kt index 8d4628eb..0b83be26 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiEra.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiEra.kt @@ -25,7 +25,9 @@ internal class HentaiEra(context: MangaLoaderContext) : "/russian", ) - override val sortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) + override val availableSortOrders: Set = 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?, - sortOrder: SortOrder, - ): List { - if (query.isNullOrEmpty() && tags != null && tags.size > 1) { - return getListPage(page, buildQuery(tags), emptySet(), sortOrder) - } + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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) - } - append("/") - if (sortOrder == SortOrder.POPULARITY) { - append("popular/") + when (filter) { + + is MangaListFilter.Search -> { + append("/search/?key=") + append(filter.query.urlEncoded()) + 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) + + 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 { - append("/?") + + null -> append("/?") } append("page=") - append(page) + append(page.toString()) } + return parseMangaList(webClient.httpGet(url).parseHtml()) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiForce.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiForce.kt index dc5a76c7..ed97854f 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiForce.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiForce.kt @@ -35,50 +35,60 @@ internal class HentaiForce(context: MangaLoaderContext) : "/vietnamese", ) - override val sortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) + override val isMultipleTagsSupported = true + + override val availableSortOrders: Set = 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?, - sortOrder: SortOrder, - ): List { - if (query.isNullOrEmpty() && tags != null && tags.size > 1) { - return getListPage(page, buildQuery(tags), emptySet(), sortOrder) - } + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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") - } - append("/") - } else if (!query.isNullOrEmpty()) { - append("/search?q=") - append(query.urlEncoded()) - 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=") + } 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("&page=") - } else { - append("/page/") + + null -> append("/page/") } - append(page) + append(page.toString()) } + return parseMangaList(webClient.httpGet(url).parseHtml()) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiFox.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiFox.kt index 8bcde87c..02c108c0 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiFox.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/HentaiFox.kt @@ -32,59 +32,83 @@ internal class HentaiFox(context: MangaLoaderContext) : "/vietnamese", ) - override val sortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) + override val isMultipleTagsSupported = true - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - if (query.isNullOrEmpty() && tags != null && tags.size > 1) { - return getListPage(page, buildQuery(tags), emptySet(), sortOrder) - } + override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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) - } - if (sortOrder == SortOrder.POPULARITY) { - append("/popular") + when (filter) { + is MangaListFilter.Search -> { + append("/search/?q=") + append(filter.query.urlEncoded()) + if (page > 1) { + append("&page=") + append(page.toString()) + } } - 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) + + 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("/") + } + } } - } 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()) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/NHentaiParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/NHentaiParser.kt index 078d5eaf..b6cac6b5 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/NHentaiParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/NHentaiParser.kt @@ -27,50 +27,60 @@ internal class NHentaiParser(context: MangaLoaderContext) : "/chinese", ) - override val sortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) + override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY) + + override val isMultipleTagsSupported = true + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - 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) - } - 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") + when (filter) { + + is MangaListFilter.Search -> { + append("/search/?q=") + append(filter.query.urlEncoded()) + append("&") } - 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()) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/NHentaiUk.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/NHentaiUk.kt index 0a8b8403..cc434d16 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/NHentaiUk.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/NHentaiUk.kt @@ -33,33 +33,39 @@ internal class NHentaiUk(context: MangaLoaderContext) : "/turkish", ) - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + 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") + } + + 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=") + } } - } else if (!query.isNullOrEmpty()) { - throw IllegalArgumentException("Search is not supported by this source") - } else { - append("/home?p=") + + null -> append("/?") } - append(page) + append(page.toString()) } + return parseMangaList(webClient.httpGet(url).parseHtml()) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/HeanCms.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/HeanCms.kt index e196f8d9..70bea32a 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/HeanCms.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/HeanCms.kt @@ -27,6 +27,8 @@ internal abstract class HeanCms( SortOrder.POPULARITY, ) + override val availableStates: Set = 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?, - sortOrder: SortOrder, - ): List { - - var firstTag = false + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { val url = buildString { append("https://api.") append(domain) append("/query?query_string=") + when (filter) { + is MangaListFilter.Search -> { + append(filter.query.urlEncoded()) + } - if (!query.isNullOrEmpty()) { - append(query.urlEncoded()) - } + is MangaListFilter.Advanced -> { - 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") - } + filter.states.oneOrThrowIfMany()?.let { + append("&series_status=") + append( + when (it) { + MangaState.ONGOING -> "Ongoing" + MangaState.FINISHED -> "Completed" + MangaState.ABANDONED -> "Dropped" + MangaState.PAUSED -> "Hiatus" + }, + ) - 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) + 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() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/es/YugenMangasEs.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/es/YugenMangasEs.kt index e1700bbb..64b7349a 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/es/YugenMangasEs.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/es/YugenMangasEs.kt @@ -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?, - sortOrder: SortOrder, - ): List { - var firstTag = false + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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(",") + 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(tag.key) + 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" diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancmsalt/HeanCmsAlt.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancmsalt/HeanCmsAlt.kt index a6768be4..588e23f3 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancmsalt/HeanCmsAlt.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancmsalt/HeanCmsAlt.kt @@ -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?, - sortOrder: SortOrder, - ): List { - // No search or tag - if (!query.isNullOrEmpty()) { - return emptyList() - } + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/DoujinDesuParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/DoujinDesuParser.kt index babd84fc..69ef6220 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/DoujinDesuParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/id/DoujinDesuParser.kt @@ -18,6 +18,78 @@ class DoujinDesuParser(context: MangaLoaderContext) : PagedMangaParser(context, override val availableSortOrders: Set get() = EnumSet.of(SortOrder.UPDATED, SortOrder.NEWEST, SortOrder.ALPHABETICAL, SortOrder.POPULARITY) + override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + 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?, - sortOrder: SortOrder, - ): List { - 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 { val id = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml() .requireElementById("reader") From 37e9b33c24a29c8d7a2e86b06e9d4d9da745bff8 Mon Sep 17 00:00:00 2001 From: devi Date: Thu, 30 Nov 2023 18:46:10 +0100 Subject: [PATCH 12/18] add filter : LikeMangaParser, FmTeam, MadthemeParser , MangaReaderParser Fix GetList : NicovideoSeigaParser, Manga18Parser add filter & multitags on MangaboxParser --- .../kotatsu/parsers/site/fr/FmTeam.kt | 88 +++++++++----- .../parsers/site/ja/NicovideoSeigaParser.kt | 45 +++++--- .../parsers/site/likemanga/LikeMangaParser.kt | 85 +++++++++----- .../parsers/site/madtheme/MadthemeParser.kt | 69 +++++++---- .../parsers/site/madtheme/all/ManhuaScan.kt | 64 +++++++---- .../parsers/site/madtheme/en/MangaBuddy.kt | 1 - .../parsers/site/madtheme/en/TooniTube.kt | 1 - .../parsers/site/madtheme/en/ToonilyMe.kt | 1 - .../parsers/site/manga18/Manga18Parser.kt | 62 +++++----- .../parsers/site/manga18/en/Hentai3zCc.kt | 47 +------- .../parsers/site/manga18/es/Tumanhwas.kt | 1 - .../parsers/site/mangabox/MangaboxParser.kt | 92 ++++++++------- .../parsers/site/mangabox/en/Mangabat.kt | 6 - .../parsers/site/mangabox/en/Mangairo.kt | 99 ++++++++-------- .../parsers/site/mangabox/en/Mangakakalot.kt | 96 ++++++++++------ .../site/mangabox/en/MangakakalotTv.kt | 108 +++++++++++++----- .../parsers/site/mangabox/en/Manganato.kt | 3 - .../site/mangareader/MangaReaderParser.kt | 99 +++++++++------- 18 files changed, 570 insertions(+), 397 deletions(-) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FmTeam.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FmTeam.kt index 73f68a4f..8b81136b 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FmTeam.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/FmTeam.kt @@ -18,51 +18,83 @@ internal class FmTeam(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.FMTEAM, 0) { override val availableSortOrders: Set = EnumSet.of(SortOrder.ALPHABETICAL) + override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) override val configKeyDomain = ConfigKey.Domain("fmteam.fr") - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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() + + 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") - val manga = ArrayList(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 + 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 } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ja/NicovideoSeigaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ja/NicovideoSeigaParser.kt index 17cff2ea..06eafe33 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ja/NicovideoSeigaParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ja/NicovideoSeigaParser.kt @@ -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?, - sortOrder: SortOrder, - ): List { + override suspend fun getList(offset: Int, filter: MangaListFilter?): List { 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)}" - - tags.size > 1 -> throw IllegalArgumentException("This source supports only 1 category") - else -> "https://$domain/manga/list?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" + } + 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 { 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, ) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/likemanga/LikeMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/likemanga/LikeMangaParser.kt index 10f3df59..8c359f1a 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/likemanga/LikeMangaParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/likemanga/LikeMangaParser.kt @@ -23,44 +23,75 @@ internal abstract class LikeMangaParser( override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST) + + override val availableStates: Set = + 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?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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 -> diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/MadthemeParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/MadthemeParser.kt index 2e795f13..0c7d3665 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/MadthemeParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/MadthemeParser.kt @@ -29,6 +29,8 @@ internal abstract class MadthemeParser( SortOrder.RATING, ) + override val availableStates: Set = 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?, - sortOrder: SortOrder, - ): List { + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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 { 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( diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/all/ManhuaScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/all/ManhuaScan.kt index 031861c5..341b086b 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/all/ManhuaScan.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/all/ManhuaScan.kt @@ -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?, - sortOrder: SortOrder, - ): List { + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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()) - } + 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" + }, + ) + } - if (!tags.isNullOrEmpty()) { - for (tag in tags) { - append("&") - append("include[]".urlEncoded()) - append("=") - append(tag.key) } + + null -> append("?sort=updated_at") } append("&page=") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/MangaBuddy.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/MangaBuddy.kt index e08c1bd8..23a8331b 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/MangaBuddy.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/MangaBuddy.kt @@ -34,6 +34,5 @@ internal class MangaBuddy(context: MangaLoaderContext) : ) } return pages - } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/TooniTube.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/TooniTube.kt index a00a728d..34aa29ea 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/TooniTube.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/TooniTube.kt @@ -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" } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/ToonilyMe.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/ToonilyMe.kt index 4dd02782..ef25c4b7 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/ToonilyMe.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madtheme/en/ToonilyMe.kt @@ -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" } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/Manga18Parser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/Manga18Parser.kt index 3ec81dc2..7703908d 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/Manga18Parser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/Manga18Parser.kt @@ -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?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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 { return doc.select("div.story_item").map { div -> val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") Manga( diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/en/Hentai3zCc.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/en/Hentai3zCc.kt index d7016dd5..71a72bc8 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/en/Hentai3zCc.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/en/Hentai3zCc.kt @@ -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?, - sortOrder: SortOrder, - ): List { - 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 { return doc.select("div.story_item").map { div -> val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") Manga( diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/es/Tumanhwas.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/es/Tumanhwas.kt index ee087215..8c8101dc 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/es/Tumanhwas.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/manga18/es/Tumanhwas.kt @@ -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" } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt index 0954d696..1c17d28d 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt @@ -5,6 +5,7 @@ import kotlinx.coroutines.coroutineScope import org.jsoup.nodes.Document import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.text.DateFormat @@ -21,9 +22,12 @@ internal abstract class MangaboxParser( SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST, + SortOrder.ALPHABETICAL, ) - protected open val listUrl = "/genre-all" + override val availableStates: Set = 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 +40,64 @@ internal abstract class MangaboxParser( @JvmField protected val ongoing: Set = setOf( - "Ongoing", + "ongoing", ) @JvmField protected val finished: Set = setOf( - "Completed", + "completed", ) - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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()) - - } else if (!tags.isNullOrEmpty()) { - append("/") - append(tag?.key.orEmpty()) - append("/") - append(page.toString()) - } else { - append("$listUrl/") - if (page > 1) { - append(page.toString()) + is MangaListFilter.Search -> { + append("&keyw=") + append(filter.query.urlEncoded()) } - 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 +127,8 @@ internal abstract class MangaboxParser( override suspend fun getAvailableTags(): Set { 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 +148,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( diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangabat.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangabat.kt index a4e6b1da..23efaaba 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangabat.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangabat.kt @@ -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" } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt index 51eb5e02..70241a79 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangairo.kt @@ -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?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override val availableSortOrders: Set = EnumSet.of( + SortOrder.UPDATED, + SortOrder.POPULARITY, + SortOrder.NEWEST, + ) + override val isMultipleTagsSupported = false + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { val url = buildString { append("https://") append(domain) + when (filter) { - if (!query.isNullOrEmpty()) { - append(searchUrl) - append(query.urlEncoded()) - append("?page=") - append(page.toString()) - - - } else { + is MangaListFilter.Search -> { + append(searchUrl) + append(filter.query.urlEncoded()) + append("?page=") + } - append("$listUrl/") + 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("/type-") - when (sortOrder) { - SortOrder.POPULARITY -> append("topview") - SortOrder.UPDATED -> append("latest") - SortOrder.NEWEST -> append("newest") - else -> append("latest") + append("/ctg-") + 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-") } - if (!tags.isNullOrEmpty()) { - append("/ctg-") - append(tag?.key.orEmpty()) - } else { - append("/ctg-all") + 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, ) } - - } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt index 99324654..ec3c171a 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Mangakakalot.kt @@ -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 = 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?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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") + + 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("&state=all&page=") - 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 { + override suspend fun getAvailableTags(): Set { + 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 { return doc.body().select(selectChapter).mapChapters(reversed = true) { i, li -> val a = li.selectFirstOrThrow("a") val href = a.attrAsRelativeUrl("href") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt index 0531c005..2c545e17 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/MangakakalotTv.kt @@ -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 = EnumSet.of( + SortOrder.UPDATED, + SortOrder.POPULARITY, + SortOrder.NEWEST, + ) + override val isMultipleTagsSupported = false - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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") - } - if (!tags.isNullOrEmpty()) { - append("&category=") - append(tag?.key.orEmpty()) + when (filter) { + + is MangaListFilter.Search -> { + append(searchUrl) + append(filter.query.urlEncoded()) + append("?page=") } - 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 { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Manganato.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Manganato.kt index c9f09346..3022f7d4 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Manganato.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/en/Manganato.kt @@ -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" - } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt index a28442d6..33d402d6 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt @@ -32,6 +32,9 @@ internal abstract class MangaReaderParser( override val availableSortOrders: Set get() = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.ALPHABETICAL, SortOrder.NEWEST) + override val availableStates: Set + 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 @@ -40,52 +43,62 @@ internal abstract class MangaReaderParser( private val mutex = Mutex() protected open var lastSearchPage = 1 - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - 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 { 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()) } From 743a92a3e82cd918adf73da757755b066297dbda Mon Sep 17 00:00:00 2001 From: devi Date: Thu, 30 Nov 2023 18:46:52 +0100 Subject: [PATCH 13/18] remove import --- .../org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt index 1c17d28d..f0f5383f 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangabox/MangaboxParser.kt @@ -5,7 +5,6 @@ import kotlinx.coroutines.coroutineScope import org.jsoup.nodes.Document import org.koitharu.kotatsu.parsers.MangaLoaderContext 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.text.DateFormat From 019b8cc0875a8fedca86945c72f1a3718a340837 Mon Sep 17 00:00:00 2001 From: devi Date: Fri, 1 Dec 2023 18:49:30 +0100 Subject: [PATCH 14/18] fix getlist some source mangaeader, MmrcmsParser add filter on NepnepParser --- .../parsers/site/mangareader/ar/SwaTeam.kt | 90 ++++----- .../site/mangareader/en/ManhwaFreak.kt | 94 +++++----- .../parsers/site/mangareader/en/RizzComic.kt | 52 +++--- .../parsers/site/mangareader/en/Zahard.kt | 65 +++---- .../parsers/site/mangareader/es/TuManhwas.kt | 44 ++--- .../site/mangareader/fr/ManhwaFreakFr.kt | 95 +++++----- .../site/mangareader/id/CosmicScans.kt | 87 +++++---- .../parsers/site/mangareader/id/KomikSan.kt | 97 +++++----- .../parsers/site/mangareader/id/Komikcast.kt | 95 ++++++---- .../parsers/site/mmrcms/MmrcmsParser.kt | 171 ++++++++++-------- .../kotatsu/parsers/site/mmrcms/ar/Onma.kt | 96 ++-------- .../kotatsu/parsers/site/mmrcms/id/Mangaid.kt | 4 - .../parsers/site/nepnep/NepnepParser.kt | 65 +++++-- 13 files changed, 529 insertions(+), 526 deletions(-) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/SwaTeam.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/SwaTeam.kt index eb82d9f9..f672d1c5 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/SwaTeam.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/ar/SwaTeam.kt @@ -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?, - sortOrder: SortOrder, - ): List { - 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 { 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()) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/ManhwaFreak.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/ManhwaFreak.kt index ae686339..6ffc0851 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/ManhwaFreak.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/ManhwaFreak.kt @@ -17,60 +17,54 @@ internal class ManhwaFreak(context: MangaLoaderContext) : override val selectMangaList = ".listupd .lastest-serie" override val selectMangaListImg = "img" + override val availableStates: Set = emptySet() + override val isMultipleTagsSupported = false - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - 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 { 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()) { + append("/genres/?genre=") + append( + filter.tags.oneOrThrowIfMany()?.let { + 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()) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/RizzComic.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/RizzComic.kt index c1eb518d..48ffe06c 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/RizzComic.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/RizzComic.kt @@ -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 - get() = EnumSet.of(SortOrder.ALPHABETICAL) + override val availableSortOrders: Set = EnumSet.of(SortOrder.ALPHABETICAL) + override val availableStates: Set = emptySet() + override val isMultipleTagsSupported = false - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + // TODO Query created in json + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { 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()) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/Zahard.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/Zahard.kt index ef1064ce..7d57f9a9 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/Zahard.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/Zahard.kt @@ -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 = EnumSet.of(SortOrder.NEWEST) + override val availableStates: Set = emptySet() + override val isMultipleTagsSupported = false - - override val availableSortOrders: Set - get() = EnumSet.of(SortOrder.NEWEST) - - - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - 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 { 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()) } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/TuManhwas.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/TuManhwas.kt index 06afc82b..d8e2af53 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/TuManhwas.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/es/TuManhwas.kt @@ -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 - get() = EnumSet.of(SortOrder.NEWEST) + override val availableSortOrders: Set = EnumSet.of(SortOrder.NEWEST) + override val availableStates: Set = emptySet() + override val isMultipleTagsSupported = false override suspend fun getAvailableTags(): Set = emptySet() - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { val url = buildString { append("https://") append(domain) append(listUrl) append("?page=") - append(page) - if (!tags.isNullOrEmpty()) { - append("&genero=") - append(tag?.key.orEmpty()) + 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 -> {} } - if (!query.isNullOrEmpty()) { - append("&search=") - append(query.urlEncoded()) - } - } - return parseMangaList(webClient.httpGet(url).parseHtml()) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/ManhwaFreakFr.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/ManhwaFreakFr.kt index ac5a6929..26991985 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/ManhwaFreakFr.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/ManhwaFreakFr.kt @@ -18,61 +18,54 @@ 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 = emptySet() + override val isMultipleTagsSupported = false - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - 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 { 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()) { + append("/genres/?genre=") + append( + filter.tags.oneOrThrowIfMany()?.let { + 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()) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/CosmicScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/CosmicScans.kt index 52526dc1..f979bece 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/CosmicScans.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/CosmicScans.kt @@ -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 = EnumSet.of(SortOrder.UPDATED) + override val availableStates: Set = emptySet() + override val isMultipleTagsSupported = false override val listUrl = "/semua-komik" - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - 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 { 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()) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/KomikSan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/KomikSan.kt index 10efd3fa..9074f62c 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/KomikSan.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/KomikSan.kt @@ -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?, - sortOrder: SortOrder, - ): List { - 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 { 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()) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/Komikcast.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/Komikcast.kt index 63fd834f..989284df 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/Komikcast.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/id/Komikcast.kt @@ -13,55 +13,70 @@ import java.util.* @MangaSourceParser("KOMIKCAST", "KomikCast", "id") internal class Komikcast(context: MangaLoaderContext) : - MangaReaderParser(context, MangaSource.KOMIKCAST, "komikcast.ch", pageSize = 60, searchPageSize = 28) { + MangaReaderParser(context, MangaSource.KOMIKCAST, "komikcast.lol", pageSize = 60, searchPageSize = 28) { override val listUrl = "/daftar-komik" override val datePattern = "MMM d, yyyy" override val sourceLocale: Locale = Locale.ENGLISH - override val availableSortOrders: Set - get() = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.ALPHABETICAL) - - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - 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 tagKey = "genre[]".urlEncoded() - val tagQuery = - if (tags.isNullOrEmpty()) "" else tags.joinToString(separator = "&", prefix = "&") { "$tagKey=${it.key}" } + override val availableSortOrders: Set = + EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.ALPHABETICAL) + override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { val url = buildString { append("https://") append(domain) - append(listUrl) - append("/page/") - append(page) - when (sortOrder) { - SortOrder.ALPHABETICAL -> append("/?orderby=titleasc") - SortOrder.POPULARITY -> append("/?orderby=popular") - SortOrder.UPDATED -> append("/?sortby=update") - else -> append("/?sortby=update") + + when (filter) { + + is MangaListFilter.Search -> { + append("/page/") + append(page.toString()) + append("/?s=") + append(filter.query.urlEncoded()) + } + + is MangaListFilter.Advanced -> { + append(listUrl) + append("/page/") + append(page.toString()) + + append("/?orderby=") + append( + when (filter.sortOrder) { + SortOrder.ALPHABETICAL -> append("titleasc") + SortOrder.POPULARITY -> append("popular") + SortOrder.UPDATED -> append("update") + else -> append("update") + }, + ) + + 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") + else -> append("") + } + } + } + } + + null -> { + append(listUrl) + append("/?orderby=update&page=") + append(page.toString()) + } } - append(tagQuery) } + return parseMangaList(webClient.httpGet(url).parseHtml()) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt index 86385d29..9d025b62 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/MmrcmsParser.kt @@ -55,87 +55,114 @@ internal abstract class MmrcmsParser( "مكتملة", ) + override val isMultipleTagsSupported = false + protected open val imgUpdated = "/cover/cover_250x350.jpg" - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() - val url = if (!query.isNullOrEmpty() || !tags.isNullOrEmpty() || sortOrder != SortOrder.UPDATED) { - buildString { - append("https://") - append(domain) - append("/$listUrl/") - append("?page=") - append(page.toString()) - append("&asc=true&author=&tag=") - append("&alpha=") - if (!query.isNullOrEmpty()) { - append(query.urlEncoded()) - } - append("&cat=") - if (!tags.isNullOrEmpty()) { - append(tag?.key.orEmpty()) + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + + when (filter) { + + is MangaListFilter.Search -> { + val url = buildString { + append("https://") + append(domain) + append('/') + append(listUrl) + append("/?page=") + append(page.toString()) + append("&asc=true&author=&tag=&alpha=") + append(filter.query.urlEncoded()) + append("&cat=&sortBy=views") } - append("&sortBy=") - when (sortOrder) { - SortOrder.POPULARITY -> append("views") - SortOrder.ALPHABETICAL -> append("name") - else -> append("name") + return parseMangaList(webClient.httpGet(url).parseHtml()) + } + + is MangaListFilter.Advanced -> { + + if (filter.sortOrder == SortOrder.UPDATED) { + val url = buildString { + append("https://") + append(domain) + append("/latest-release?page=") + append(page.toString()) + } + return parseMangaListUpdated(webClient.httpGet(url).parseHtml()) + + } else { + val url = buildString { + append("https://") + append(domain) + append('/') + append(listUrl) + append("/?page=") + append(page.toString()) + append("&asc=true&author=&tag=&alpha=&cat=") + filter.tags.oneOrThrowIfMany()?.let { + append(it.key) + } + append("&sortBy=") + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("views") + SortOrder.ALPHABETICAL -> append("name") + else -> append("name") + } + } + return parseMangaList(webClient.httpGet(url).parseHtml()) } } - } else { - buildString { - append("https://") - append(domain) - append("/latest-release") - append("?page=") - append(page.toString()) + + null -> { + val url = buildString { + append("https://") + append(domain) + append("/latest-release?page=") + append(page.toString()) + } + return parseMangaList(webClient.httpGet(url).parseHtml()) } } - val doc = webClient.httpGet(url).parseHtml() - if (!query.isNullOrEmpty() || !tags.isNullOrEmpty() || sortOrder != SortOrder.UPDATED) { - return doc.select("div.media").map { div -> - val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") - Manga( - id = generateUid(href), - url = href, - publicUrl = href.toAbsoluteUrl(div.host ?: domain), - coverUrl = div.selectFirst("img")?.src().orEmpty(), - title = div.selectFirstOrThrow("div.media-body h5").text().orEmpty(), - altTitle = null, - rating = div.selectFirstOrThrow("span").ownText().toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN, - tags = emptySet(), - author = null, - state = null, - source = source, - isNsfw = isNsfwSource, - ) - } - } else { - return doc.select("div.manga-item").map { div -> - val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") - val deeplink = href.substringAfterLast("/") - Manga( - id = generateUid(href), - url = href, - publicUrl = href.toAbsoluteUrl(div.host ?: domain), - coverUrl = "https://$domain/uploads/manga/$deeplink$imgUpdated", - title = div.selectFirstOrThrow("h3 a").text().orEmpty(), - altTitle = null, - rating = RATING_UNKNOWN, - tags = emptySet(), - author = null, - state = null, - source = source, - isNsfw = isNsfwSource, - ) - } + } + + protected open fun parseMangaList(doc: Document): List { + return doc.select("div.media").map { div -> + val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + url = href, + publicUrl = href.toAbsoluteUrl(div.host ?: domain), + coverUrl = div.selectFirst("img")?.src().orEmpty(), + title = div.selectFirstOrThrow("div.media-body h5").text().orEmpty(), + altTitle = null, + rating = div.selectFirstOrThrow("span").ownText().toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN, + tags = emptySet(), + author = null, + state = null, + source = source, + isNsfw = isNsfwSource, + ) } + } + protected open fun parseMangaListUpdated(doc: Document): List { + return doc.select("div.manga-item").map { div -> + val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") + val deeplink = href.substringAfterLast("/") + Manga( + id = generateUid(href), + url = href, + publicUrl = href.toAbsoluteUrl(div.host ?: domain), + coverUrl = "https://$domain/uploads/manga/$deeplink$imgUpdated", + title = div.selectFirstOrThrow("h3 a").text().orEmpty(), + altTitle = null, + rating = RATING_UNKNOWN, + tags = emptySet(), + author = null, + state = null, + source = source, + isNsfw = isNsfwSource, + ) + } } override suspend fun getAvailableTags(): Set { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/ar/Onma.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/ar/Onma.kt index 5101e05a..787f2bcb 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/ar/Onma.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/ar/Onma.kt @@ -2,6 +2,7 @@ package org.koitharu.kotatsu.parsers.site.mmrcms.ar import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope +import org.jsoup.nodes.Document import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.model.* @@ -19,83 +20,24 @@ internal class Onma(context: MangaLoaderContext) : override val selectAut = "h3:contains(المؤلف) .text" override val selectTag = "h3:contains(التصنيفات) .text" - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() - val url = if (sortOrder == SortOrder.UPDATED) { - buildString { - append("https://") - append(domain) - append("/latest-release") - append("?page=") - append(page.toString()) - } - } else { - buildString { - append("https://") - append(domain) - append("/$listUrl/") - append("?page=") - append(page.toString()) - append("&asc=true&author=&tag=") - append("&alpha=") - if (!query.isNullOrEmpty()) { - append(query.urlEncoded()) - } - append("&cat=") - if (!tags.isNullOrEmpty()) { - append(tag?.key.orEmpty()) - } - append("&sortBy=") - when (sortOrder) { - SortOrder.POPULARITY -> append("views") - SortOrder.ALPHABETICAL -> append("name") - else -> append("views") - } - } - } - val doc = webClient.httpGet(url).parseHtml() - if (sortOrder == SortOrder.UPDATED) { - return doc.select("div.manga-item").map { div -> - val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") - val deeplink = href.substringAfterLast('/') - Manga( - id = generateUid(href), - url = href, - publicUrl = href.toAbsoluteUrl(div.host ?: domain), - coverUrl = "https://$domain/uploads/manga/$deeplink$imgUpdated", - title = div.selectFirstOrThrow("div.content-left a").text().orEmpty(), - altTitle = null, - rating = RATING_UNKNOWN, - tags = emptySet(), - author = null, - state = null, - source = source, - isNsfw = isNsfwSource, - ) - } - } else { - return doc.select("div.chapter-container").map { div -> - val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") - Manga( - id = generateUid(href), - url = href, - publicUrl = href.toAbsoluteUrl(div.host ?: domain), - coverUrl = div.selectFirst("img")?.src().orEmpty(), - title = div.selectFirstOrThrow("h5.media-heading").text().orEmpty(), - altTitle = null, - rating = div.selectFirstOrThrow("span").ownText().toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN, - tags = emptySet(), - author = null, - state = null, - source = source, - isNsfw = isNsfwSource, - ) - } + + override fun parseMangaList(doc: Document): List { + return doc.select("div.chapter-container").map { div -> + val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + url = href, + publicUrl = href.toAbsoluteUrl(div.host ?: domain), + coverUrl = div.selectFirst("img")?.src().orEmpty(), + title = div.selectFirstOrThrow("h5.media-heading").text().orEmpty(), + altTitle = null, + rating = div.selectFirstOrThrow("span").ownText().toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN, + tags = emptySet(), + author = null, + state = null, + source = source, + isNsfw = isNsfwSource, + ) } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/id/Mangaid.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/id/Mangaid.kt index dff1955a..f0a0bfa7 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/id/Mangaid.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mmrcms/id/Mangaid.kt @@ -1,18 +1,14 @@ package org.koitharu.kotatsu.parsers.site.mmrcms.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.mmrcms.MmrcmsParser import java.util.Locale - @MangaSourceParser("MANGAID", "MangaId", "id") internal class Mangaid(context: MangaLoaderContext) : MmrcmsParser(context, MangaSource.MANGAID, "mangaid.click") { - - override val selectState = "dt:contains(Status)" override val selectAlt = "dt:contains(Other names)" override val selectAut = "dt:contains(Author(s))" diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/nepnep/NepnepParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/nepnep/NepnepParser.kt index 2845daec..f175cbe1 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/nepnep/NepnepParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/nepnep/NepnepParser.kt @@ -24,15 +24,20 @@ internal abstract class NepnepParser( override val configKeyDomain = ConfigKey.Domain(domain) override val availableSortOrders: Set = EnumSet.of(SortOrder.ALPHABETICAL) + override val availableStates: Set = EnumSet.allOf(MangaState::class.java) override val headers: Headers = Headers.Builder() .add("User-Agent", UserAgents.CHROME_DESKTOP) .build() - override suspend fun getList(offset: Int, query: String?, tags: Set?, sortOrder: SortOrder): List { + override suspend fun getList(offset: Int, filter: MangaListFilter?): List { if (offset > 0) { return emptyList() } + + var foundTag = true + var foundState = true + val doc = webClient.httpGet("https://$domain/search/").parseHtml() val json = JSONArray( doc.selectFirstOrThrow("script:containsData(MainFunction)").data() @@ -41,18 +46,18 @@ internal abstract class NepnepParser( .trim() .replace(';', ' '), ) - - val manga = ArrayList(json.length()) + for (i in 0 until json.length()) { val m = json.getJSONObject(i) val href = "/manga/" + m.getString("i") val imgUrl = "https://temp.compsci88.com/cover/" + m.getString("i") + ".jpg" - when { - !query.isNullOrEmpty() -> { - if (m.getString("s").contains(query, ignoreCase = true) || m.getString("al") - .contains(query, ignoreCase = true) + when (filter) { + + is MangaListFilter.Search -> { + if (m.getString("s").contains(filter.query, ignoreCase = true) || m.getString("al") + .contains(filter.query, ignoreCase = true) ) { manga.add( addManga(href, imgUrl, m), @@ -60,29 +65,49 @@ internal abstract class NepnepParser( } } - !tags.isNullOrEmpty() -> { - val a = m.getJSONArray("g").toString() - var found = true - tags.forEach { - if (!a.contains(it.key, ignoreCase = true)) { - found = false + is MangaListFilter.Advanced -> { + + if (filter.tags.isNotEmpty()) { + val tagsJon = m.getJSONArray("g").toString() + filter.tags.forEach { + foundTag = false + if (tagsJon.contains(it.key, ignoreCase = true)) { + foundTag = true + } } } - if (found) { - manga.add( - addManga(href, imgUrl, m), - ) + + if (filter.states.isNotEmpty()) { + val stateJson = m.getString("ps") + filter.states.oneOrThrowIfMany().let { + foundState = false + if (stateJson.contains( + when (it) { + MangaState.ONGOING -> "Ongoing" + MangaState.FINISHED -> "Complete" + MangaState.ABANDONED -> "Cancelled" + MangaState.PAUSED -> "Hiatus" + else -> "" + }, + ignoreCase = true, + ) + ) { + foundState = true + } + } + } + + if (foundTag && foundState) { + manga.add(addManga(href, imgUrl, m)) } } - else -> { + null -> { manga.add( addManga(href, imgUrl, m), ) } } - - } return manga } From 26b758ae5036b2bafa7d9d991083f2bfffea031b Mon Sep 17 00:00:00 2001 From: devi Date: Fri, 1 Dec 2023 18:50:34 +0100 Subject: [PATCH 15/18] fix getlist some source mangaeader, MmrcmsParser add filter on NepnepParser --- .../kotatsu/parsers/site/mangareader/en/ManhwaFreak.kt | 10 ++++------ .../parsers/site/mangareader/fr/ManhwaFreakFr.kt | 10 ++++------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/ManhwaFreak.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/ManhwaFreak.kt index 6ffc0851..1c9ba41c 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/ManhwaFreak.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/en/ManhwaFreak.kt @@ -40,12 +40,10 @@ internal class ManhwaFreak(context: MangaLoaderContext) : } if (filter.tags.isNotEmpty()) { - append("/genres/?genre=") - append( - filter.tags.oneOrThrowIfMany()?.let { - it.key - }, - ) + filter.tags.oneOrThrowIfMany()?.let { + append("/genres/?genre=") + append(it.key) + } } else { append(listUrl) append("/?order=") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/ManhwaFreakFr.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/ManhwaFreakFr.kt index 26991985..ff49525b 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/ManhwaFreakFr.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/fr/ManhwaFreakFr.kt @@ -41,12 +41,10 @@ internal class ManhwaFreakFr(context: MangaLoaderContext) : } if (filter.tags.isNotEmpty()) { - append("/genres/?genre=") - append( - filter.tags.oneOrThrowIfMany()?.let { - it.key - }, - ) + filter.tags.oneOrThrowIfMany()?.let { + append("/genres/?genre=") + append(it.key) + } } else { append(listUrl) append("/?order=") From 6ad78f1cbae2ab3fe91140bacfd2e04632a9450b Mon Sep 17 00:00:00 2001 From: devi Date: Sat, 2 Dec 2023 16:41:37 +0100 Subject: [PATCH 16/18] fix getlist OtakuSanctuaryParser, bakai , brmangas, LerMangaOnline, MangaOnline, YugenMangas, MangaAy, SadScans, YaoiFlix add filter on SinmhParser, TrWebtoon --- .../site/mangareader/MangaReaderParser.kt | 1 - .../otakusanctuary/OtakuSanctuaryParser.kt | 109 +++++----- .../koitharu/kotatsu/parsers/site/pt/Bakai.kt | 152 +++++++------ .../kotatsu/parsers/site/pt/BrMangas.kt | 89 +++++--- .../kotatsu/parsers/site/pt/LerMangaOnline.kt | 57 +++-- .../kotatsu/parsers/site/pt/MangaOnline.kt | 43 ++-- .../kotatsu/parsers/site/pt/YugenMangas.kt | 80 ++++--- .../kotatsu/parsers/site/sinmh/SinmhParser.kt | 63 ++++-- .../kotatsu/parsers/site/tr/MangaAy.kt | 163 ++++++++------ .../kotatsu/parsers/site/tr/SadScans.kt | 23 +- .../kotatsu/parsers/site/tr/TrWebtoon.kt | 204 ++++++++++-------- .../kotatsu/parsers/site/tr/YaoiFlix.kt | 51 +++-- 12 files changed, 625 insertions(+), 410 deletions(-) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt index 33d402d6..861de4eb 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/mangareader/MangaReaderParser.kt @@ -41,7 +41,6 @@ internal abstract class MangaReaderParser( private var tagCache: ArrayMap? = null private val mutex = Mutex() - protected open var lastSearchPage = 1 override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { val url = buildString { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/OtakuSanctuaryParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/OtakuSanctuaryParser.kt index b4d6918c..8d454c84 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/OtakuSanctuaryParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/otakusanctuary/OtakuSanctuaryParser.kt @@ -25,7 +25,7 @@ internal abstract class OtakuSanctuaryParser( SortOrder.NEWEST, ) - protected open val listeurl = "Manga/Newest" + protected open val listUrl = "Manga/Newest" protected open val datePattern = "dd/MM/yyyy" protected open val lang = "" @@ -40,60 +40,71 @@ internal abstract class OtakuSanctuaryParser( "Done", ) - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() - - val doc = if (!tags.isNullOrEmpty()) { - val url = buildString { - - append("https://$domain/Genre/MangaGenrePartial?id=") - append(tag?.key.orEmpty()) - append("&lang=$lang") - append("&offset=") - append(page * pageSize) - append("&pagesize=$pageSize") - } + override val isMultipleTagsSupported = false + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + val doc = + when (filter) { + + is MangaListFilter.Search -> { + if (page > 1) { + return emptyList() + } + val url = buildString { + append("https://") + append(domain) + append("/Home/Search?search=") + append(filter.query.urlEncoded()) + } + webClient.httpGet(url).parseHtml().requireElementById("collection-manga") + } - webClient.httpGet(url).parseHtml() - } else if (!query.isNullOrEmpty()) { - if (page > 1) { - return emptyList() - } + is MangaListFilter.Advanced -> { + + if (filter.tags.isNotEmpty()) { + val url = buildString { + append("https://") + append(domain) + append("/Genre/MangaGenrePartial?id=") + filter.tags.oneOrThrowIfMany()?.let { + append(it.key) + } + append("&lang=") + append(lang) + append("&offset=") + append(page * pageSize) + append("&pagesize=") + append(pageSize) + } + webClient.httpGet(url).parseHtml() + + } else { + val payload = HashMap() + payload["Lang"] = lang + payload["Page"] = page.toString() + payload["Type"] = "Include" + when (filter.sortOrder) { + SortOrder.NEWEST -> payload["Dir"] = "CreatedDate" + SortOrder.UPDATED -> payload["Dir"] = "NewPostedDate" + else -> payload["Dir"] = "NewPostedDate" + } + webClient.httpPost("https://$domain/$listUrl", payload).parseHtml() + } - val url = buildString { - append("https://$domain/Home/Search?search=") - append(query.urlEncoded()) - } + } - webClient.httpGet(url).parseHtml() - } else { - val url = "https://$domain/$listeurl" - val payload = HashMap() - payload["Lang"] = lang - payload["Page"] = page.toString() - payload["Type"] = "Include" - when (sortOrder) { - SortOrder.NEWEST -> payload["Dir"] = "CreatedDate" - SortOrder.UPDATED -> payload["Dir"] = "NewPostedDate" - else -> payload["Dir"] = "NewPostedDate" + null -> { + val payload = HashMap() + payload["Lang"] = lang + payload["Page"] = page.toString() + payload["Type"] = "Include" + payload["Dir"] = "NewPostedDate" + webClient.httpPost("https://$domain/$listUrl", payload).parseHtml() + } } - webClient.httpPost(url, payload).parseHtml() - } - - - val mangas = if (!query.isNullOrEmpty()) { - doc.requireElementById("collection-manga").select("div.picture-card") - } else { - doc.select("div.picture-card") - } - return mangas.map { div -> + return doc.select("div.picture-card").map { div -> val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") Manga( id = generateUid(href), diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/Bakai.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/Bakai.kt index aee33c96..3ecd17ad 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/Bakai.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/Bakai.kt @@ -1,6 +1,7 @@ package org.koitharu.kotatsu.parsers.site.pt import okhttp3.Headers +import org.jsoup.nodes.Document import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.PagedMangaParser @@ -16,79 +17,102 @@ internal class Bakai(context: MangaLoaderContext) : PagedMangaParser(context, Ma override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) override val configKeyDomain = ConfigKey.Domain("bakai.org") override val headers: Headers = Headers.Builder() - .add("User-Agent", UserAgents.CHROME_MOBILE) + .add("User-Agent", UserAgents.CHROME_DESKTOP) .build() - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val url = buildString { - append("https://") - append(domain) - if (!query.isNullOrEmpty()) { - append("/search/?q=") - append(query.urlEncoded()) - append("&quick=1&type=cms_records1&page=") - append(page.toString()) - } else if (!tags.isNullOrEmpty()) { - append("/search/?tags=") - for (tag in tags) { - append(tag.key) - append(",") + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + + when (filter) { + + is MangaListFilter.Search -> { + val url = buildString { + append("https://") + append(domain) + append("/search1/?q=") + append(filter.query.urlEncoded()) + append("&quick=1&type=cms_records1&updated_after=any&sortby=newest&page=") + append(page.toString()) } - append("&quick=1&type=cms_records1&page=") - append(page.toString()) - } else { - append("/hentai/") - append("page/") - append(page.toString()) + return parseMangaListQueryOrTags(webClient.httpGet(url).parseHtml()) } - } - val doc = webClient.httpGet(url).parseHtml() - if (!tags.isNullOrEmpty() or !query.isNullOrEmpty()) { - return doc.select("ol.ipsStream li.ipsStreamItem") - .map { div -> - val href = div.selectFirstOrThrow("div.ipsStreamItem_snippet a").attrAsRelativeUrl("href") - Manga( - id = generateUid(href), - title = div.selectFirstOrThrow("h2.ipsStreamItem_title").text(), - altTitle = null, - url = href, - publicUrl = href.toAbsoluteUrl(domain), - rating = RATING_UNKNOWN, - isNsfw = true, - coverUrl = div.selectFirstOrThrow("span.ipsThumb img").attrAsAbsoluteUrl("src"), - tags = setOf(), - state = null, - author = null, - source = source, - ) + + is MangaListFilter.Advanced -> { + if (filter.tags.isNotEmpty()) { + val url = buildString { + append("https://") + append(domain) + append("/search1/?tags=") + append(filter.tags.joinToString(separator = ",") { it.key }) + append("&updated_after=any&sortby=newest&search_and_or=and&page=") + append(page.toString()) + } + return parseMangaListQueryOrTags(webClient.httpGet(url).parseHtml()) + } else { + val url = buildString { + append("https://") + append(domain) + append("/hentai/page/") + append(page.toString()) + } + return parseMangaList(webClient.httpGet(url).parseHtml()) } - } else { - return doc.select("section.ipsType_normal li.ipsGrid_span4") - .map { div -> - val href = div.selectFirstOrThrow("h2.ipsType_pageTitle a").attrAsRelativeUrl("href") - Manga( - id = generateUid(href), - title = div.selectFirstOrThrow("h2.ipsType_pageTitle").text(), - altTitle = null, - url = href, - publicUrl = href.toAbsoluteUrl(domain), - rating = RATING_UNKNOWN, - isNsfw = true, - coverUrl = div.selectFirstOrThrow("img").attrAsAbsoluteUrl("src"), - tags = setOf(), - state = null, - author = null, - source = source, - ) + } + + null -> { + val url = buildString { + append("https://") + append(domain) + append("/hentai/page/") + append(page.toString()) } + return parseMangaList(webClient.httpGet(url).parseHtml()) + } } } + private fun parseMangaList(doc: Document): List { + return doc.select("section.ipsType_normal li.ipsGrid_span4") + .map { div -> + val href = div.selectFirstOrThrow("h2.ipsType_pageTitle a").attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + title = div.selectFirstOrThrow("h2.ipsType_pageTitle").text(), + altTitle = null, + url = href, + publicUrl = href.toAbsoluteUrl(domain), + rating = RATING_UNKNOWN, + isNsfw = true, + coverUrl = div.selectFirst("img")?.src().orEmpty(), + tags = setOf(), + state = null, + author = null, + source = source, + ) + } + } + + private fun parseMangaListQueryOrTags(doc: Document): List { + return doc.select("ol.ipsStream li.ipsStreamItem") + .mapNotNull { div -> + val href = + div.selectFirst(".ipsStreamItem_snippet a")?.attrAsRelativeUrl("href") ?: return@mapNotNull null + Manga( + id = generateUid(href), + title = div.selectFirstOrThrow("h2.ipsStreamItem_title").text(), + altTitle = null, + url = href, + publicUrl = href.toAbsoluteUrl(domain), + rating = RATING_UNKNOWN, + isNsfw = true, + coverUrl = div.selectFirst(".ipsStreamItem_snippet img")?.src().orEmpty(), + tags = setOf(), + state = null, + author = null, + source = source, + ) + } + } + override suspend fun getAvailableTags(): Set { val doc = webClient.httpGet("https://$domain").parseHtml() return doc.requireElementById("elNavigation_17_menu").select("li.ipsMenu_item a").mapNotNullToSet { a -> diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt index 688b63bb..f51e1d62 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/BrMangas.kt @@ -17,51 +17,78 @@ internal class BrMangas(context: MangaLoaderContext) : PagedMangaParser(context, override val configKeyDomain = ConfigKey.Domain("www.brmangas.net") + override val isMultipleTagsSupported = false + override val headers: Headers = Headers.Builder() .add("User-Agent", UserAgents.CHROME_DESKTOP) .build() - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { val url = buildString { - append("https://$domain/") - if (!tags.isNullOrEmpty()) { - append("category/") - append(tag?.key.orEmpty()) - if (page > 1) { - append("/page/$page/") - } - } else if (!query.isNullOrEmpty()) { - if (page > 1) { - append("/page/$page/") + append("https://") + append(domain) + append('/') + when (filter) { + is MangaListFilter.Search -> { + if (page > 1) { + append("/page/$page/") + } + append("/?s=") + append(filter.query.urlEncoded()) } - append("/?s=") - append(query.urlEncoded()) - } else { - when (sortOrder) { - SortOrder.POPULARITY -> append("/") - SortOrder.UPDATED -> append("manga/") - else -> append("manga/") + + is MangaListFilter.Advanced -> { + if (filter.tags.isNotEmpty()) { + filter.tags.oneOrThrowIfMany()?.let { + append("category/") + append(it.key) + if (page > 1) { + append("/page/$page/") + } + } + } else { + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("/") + SortOrder.UPDATED -> append("manga/") + else -> append("manga/") + } + if (page > 1) { + append("page/$page/") + } + } + } - if (page > 1) { - append("page/$page/") + + null -> { + append("manga/") + if (page > 1) { + append("page/$page/") + } } } + } val doc = webClient.httpGet(url).parseHtml() - val item = if (sortOrder == SortOrder.POPULARITY) { - doc.select("div.listagem")[1].select("div.item") // To remove the 6 mangas updated on the home page - } else { - doc.select("div.listagem div.item") - } + val item = + when (filter) { + is MangaListFilter.Search -> { + doc.select("div.listagem div.item") + } + + is MangaListFilter.Advanced -> { + if (filter.sortOrder == SortOrder.POPULARITY && filter.tags.isEmpty()) { + doc.select("div.listagem")[1].select("div.item") // To remove the 6 mangas updated on the home page + } else { + doc.select("div.listagem div.item") + } + } + + null -> doc.select("div.listagem div.item") + + } return item.map { div -> val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") Manga( diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerMangaOnline.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerMangaOnline.kt index 2f548ea3..9a8e824f 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerMangaOnline.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/LerMangaOnline.kt @@ -7,7 +7,6 @@ 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.SimpleDateFormat import java.util.* @@ -18,30 +17,48 @@ class LerMangaOnline(context: MangaLoaderContext) : PagedMangaParser(context, Ma override val configKeyDomain = ConfigKey.Domain("lermangaonline.com.br") - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - if (!query.isNullOrEmpty()) { - throw IllegalArgumentException("Search is not supported by this source") - } - val tag = tags.oneOrThrowIfMany() + override val isMultipleTagsSupported = false + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + val url = buildString { append("https://") append(domain) - append("/") - if (!tags.isNullOrEmpty()) { - append(tag?.key.orEmpty()) - append("/") - } - if (page > 1) { - append("page/") - append(page) - append("/") + append('/') + when (filter) { + is MangaListFilter.Search -> { + if (page > 1) { + append("page/") + append(page.toString()) + append("/") + } + append("?s=") + append(filter.query.urlEncoded()) + } + + is MangaListFilter.Advanced -> { + filter.tags.oneOrThrowIfMany()?.let { + append(it.key) + append('/') + } + + if (page > 1) { + append("page/") + append(page.toString()) + append('/') + } + } + + null -> { + if (page > 1) { + append("page/") + append(page.toString()) + append('/') + } + } } } + return parseManga(webClient.httpGet(url).parseHtml()) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/MangaOnline.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/MangaOnline.kt index dd41b478..c7f4a735 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/MangaOnline.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/MangaOnline.kt @@ -16,37 +16,40 @@ class MangaOnline(context: MangaLoaderContext) : PagedMangaParser(context, Manga override val configKeyDomain = ConfigKey.Domain("mangaonline.biz") - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override val isMultipleTagsSupported = false + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + val url = buildString { append("https://") append(domain) - when { - !tags.isNullOrEmpty() -> { - append("/genero/") - append(tag?.key.orEmpty()) - append("/") - } + when (filter) { - !query.isNullOrEmpty() -> { + is MangaListFilter.Search -> { append("/search/") - append(query.urlEncoded()) - append("/") + append(filter.query.urlEncoded()) + append('/') } - else -> { - append("/manga/") + is MangaListFilter.Advanced -> { + if (filter.tags.isNotEmpty()) { + filter.tags.oneOrThrowIfMany()?.let { + append("/genero/") + append(it.key) + append('/') + } + } else { + append("/manga/") + } } + + null -> append("/manga/") + } if (page > 1) { append("page/") - append(page) - append("/") + append(page.toString()) + append('/') } } val doc = webClient.httpGet(url).parseHtml() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/YugenMangas.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/YugenMangas.kt index 55564083..3dc110c3 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/YugenMangas.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/pt/YugenMangas.kt @@ -8,7 +8,6 @@ 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 org.koitharu.kotatsu.parsers.util.json.mapJSONIndexed import java.text.DateFormat import java.text.SimpleDateFormat import java.util.* @@ -19,44 +18,71 @@ class YugenMangas(context: MangaLoaderContext) : PagedMangaParser(context, Manga override val availableSortOrders: Set = EnumSet.of(SortOrder.ALPHABETICAL, SortOrder.UPDATED) override val configKeyDomain = ConfigKey.Domain("yugenmangas.net.br") - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + + if (page > 1) { + return emptyList() + } + val json = - if (!query.isNullOrEmpty()) { - if (page > 1) { - return emptyList() - } - val url = buildString { - append("https://api.") - append(domain) - append("/api/series/list/?query=") - append(query.urlEncoded()) + when (filter) { + + is MangaListFilter.Search -> { + + val url = buildString { + append("https://api.") + append(domain) + append("/api/series/list/?query=") + append(filter.query.urlEncoded()) + } + webClient.httpGet(url).parseJsonArray() } - webClient.httpGet(url).parseJsonArray() - } else { - if (page > 1) { - return emptyList() + + is MangaListFilter.Advanced -> { + + + if (filter.sortOrder == SortOrder.UPDATED) { + val url = buildString { + append("https://api.") + append(domain) + append("/api/latest_updates/") + } + webClient.httpGet(url).parseJsonArray() + } else { + val url = buildString { + append("https://api.") + append(domain) + append("/api/all_series/") + } + webClient.httpGet(url).parseJson().getJSONArray("series") + } + } - val url = buildString { - append("https://api.") - append(domain) - append("/api/all_series/?page=1") + + null -> { + val url = buildString { + append("https://api.") + append(domain) + append("/api/latest_updates/") + } + webClient.httpGet(url).parseJsonArray() } - webClient.httpGet(url).parseJson().getJSONArray("series") } return json.mapJSON { j -> val slug = j.getString("slug") + val cover = if (!j.getString("cover").startsWith("https://")) { + // Some covers don't have the "/" so we ensure that the URL will be spelled correctly. + "https://$domain/media/" + j.getString("cover").removePrefix("/") + } else { + j.getString("cover") + } Manga( id = generateUid(slug), url = slug, publicUrl = slug, title = j.getString("name"), - coverUrl = j.getString("cover"), + coverUrl = cover, altTitle = null, rating = RATING_UNKNOWN, tags = emptySet(), @@ -90,7 +116,7 @@ class YugenMangas(context: MangaLoaderContext) : PagedMangaParser(context, Manga else -> null } }, - chapters = chapterManga.mapJSONIndexed { i, j -> + chapters = chapterManga.mapJSON { j -> val url = "https://api.$domain/api/serie/${manga.url}/chapter/${j.getString("slug")}/images/imgs/" MangaChapter( id = generateUid(url), diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/sinmh/SinmhParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/sinmh/SinmhParser.kt index 3af8d4ac..ebbf48ed 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/sinmh/SinmhParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/sinmh/SinmhParser.kt @@ -23,6 +23,10 @@ internal abstract class SinmhParser( SortOrder.POPULARITY, ) + override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) + + override val isMultipleTagsSupported = false + protected open val searchUrl = "search/" protected open val listUrl = "list/" @@ -41,40 +45,55 @@ internal abstract class SinmhParser( "已完结", ) - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { val url = buildString { append("https://") append(domain) - when { - !query.isNullOrEmpty() -> { - append("/$searchUrl?keywords=") - append(query.urlEncoded()) + append('/') + when (filter) { + + is MangaListFilter.Search -> { + append(searchUrl) + append("?keywords=") + append(filter.query.urlEncoded()) append("&page=") append(page) } - !tags.isNullOrEmpty() -> { - append("/$listUrl") - append(tag?.key.orEmpty()) - append("/$page/") - } + is MangaListFilter.Advanced -> { + append(listUrl) + filter.tags.oneOrThrowIfMany()?.let { + append(it.key) + } - else -> { + filter.states.oneOrThrowIfMany()?.let { + append( + when (it) { + MangaState.ONGOING -> "-lianzai" + MangaState.FINISHED -> "-wanjie" + else -> "" + }, + ) + } - append("/$listUrl") - when (sortOrder) { + if (filter.tags.isNotEmpty() && filter.states.isNotEmpty()) { + append('/') + } + + when (filter.sortOrder) { SortOrder.POPULARITY -> append("click/") SortOrder.UPDATED -> append("update/") - else -> append("") + else -> append("/") } - append("?page=") - append(page) + append(page.toString()) + append('/') + } + + null -> { + append(listUrl) + append("update/") + append(page.toString()) + append('/') } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/MangaAy.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/MangaAy.kt index d55cb8a3..8e3f0a6d 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/MangaAy.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/MangaAy.kt @@ -3,6 +3,7 @@ package org.koitharu.kotatsu.parsers.site.tr import androidx.collection.ArrayMap import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import org.jsoup.nodes.Document import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.PagedMangaParser @@ -19,75 +20,111 @@ class MangaAy(context: MangaLoaderContext) : PagedMangaParser(context, MangaSour override val configKeyDomain = ConfigKey.Domain("manga-ay.com") - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() - if (!query.isNullOrEmpty() || !tags.isNullOrEmpty()) { - if (page > 1) { - return emptyList() - } - val url = "https://$domain/arama" - val doc = webClient.httpPost( - url, - mapOf( - "title" to query?.urlEncoded().orEmpty(), - "genres" to tag?.key.orEmpty(), - ), - ).parseHtml() - return doc.select(".table tr").map { tr -> - val a = tr.selectFirstOrThrow("a") - val href = a.attrAsRelativeUrl("href") - Manga( - id = generateUid(href), - url = href, - publicUrl = a.attrAsAbsoluteUrl("href"), - title = a.text(), - coverUrl = "", - altTitle = null, - rating = RATING_UNKNOWN, - tags = emptySet(), - description = null, - state = null, - author = null, - isNsfw = isNsfwSource, - source = source, + override val isMultipleTagsSupported = false + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + + when (filter) { + is MangaListFilter.Search -> { + if (page > 1) { + return emptyList() + } + return parseMangaListQueryOrTags( + webClient.httpPost( + "https://$domain/arama", + mapOf("title" to filter.query.urlEncoded(), "genres" to ""), + ).parseHtml(), ) } - } else { - val url = buildString { - append("https://") - append(domain) - append("/seriler") - if (page > 1) { - append("/") - append(page) + + is MangaListFilter.Advanced -> { + + if (filter.tags.isNotEmpty()) { + filter.tags.oneOrThrowIfMany()?.let { + if (page > 1) { + return emptyList() + } + return parseMangaListQueryOrTags( + webClient.httpPost( + "https://$domain/arama", + mapOf("title" to "", "genres" to it.key), + ).parseHtml(), + ) + } + } else { + val url = buildString { + append("https://") + append(domain) + append("/seriler") + if (page > 1) { + append("/") + append(page) + } + } + return parseMangaList(webClient.httpGet(url).parseHtml()) } + } - val doc = webClient.httpGet(url).parseHtml().requireElementById("ecommerce-products") - return doc.select(".card").map { div -> - val a = div.selectFirstOrThrow("a") - val href = a.attrAsRelativeUrl("href") - Manga( - id = generateUid(href), - url = href, - publicUrl = a.attrAsAbsoluteUrl("href"), - title = div.selectLastOrThrow(".item-name").text(), - coverUrl = div.selectFirst("img")?.src().orEmpty(), - altTitle = null, - rating = RATING_UNKNOWN, - tags = emptySet(), - description = null, - state = null, - author = null, - isNsfw = isNsfwSource, - source = source, - ) + + null -> { + val url = buildString { + append("https://") + append(domain) + append("/seriler") + if (page > 1) { + append("/") + append(page) + } + } + return parseMangaList(webClient.httpGet(url).parseHtml()) } } + + return emptyList() + } + + private fun parseMangaList(doc: Document): List { + return doc.requireElementById("ecommerce-products").select(".card").map { div -> + val a = div.selectFirstOrThrow("a") + val href = a.attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + url = href, + publicUrl = a.attrAsAbsoluteUrl("href"), + title = div.selectLastOrThrow(".item-name").text(), + coverUrl = div.selectFirst("img")?.src().orEmpty(), + altTitle = null, + rating = RATING_UNKNOWN, + tags = emptySet(), + description = null, + state = null, + author = null, + isNsfw = isNsfwSource, + source = source, + ) + } + } + + private fun parseMangaListQueryOrTags(doc: Document): List { + return doc.select(".table tr").map { tr -> + val a = tr.selectFirstOrThrow("a") + val href = a.attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + url = href, + publicUrl = a.attrAsAbsoluteUrl("href"), + title = a.text(), + coverUrl = "", + altTitle = null, + rating = RATING_UNKNOWN, + tags = emptySet(), + description = null, + state = null, + author = null, + isNsfw = isNsfwSource, + source = source, + ) + } } private var tagCache: ArrayMap? = null diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/SadScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/SadScans.kt index 381331bf..5c686374 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/SadScans.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/SadScans.kt @@ -15,20 +15,29 @@ internal class SadScans(context: MangaLoaderContext) : MangaParser(context, Mang override val availableSortOrders: Set = EnumSet.of(SortOrder.ALPHABETICAL) override val configKeyDomain = ConfigKey.Domain("sadscans.com") - override suspend fun getList(offset: Int, query: String?, tags: Set?, sortOrder: SortOrder): List { + override suspend fun getList(offset: Int, filter: MangaListFilter?): List { 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(), @@ -64,7 +73,7 @@ internal class SadScans(context: MangaLoaderContext) : MangaParser(context, Mang chapters = doc.select(".chap-section .chap") .mapChapters(reversed = true) { i, div -> val a = div.selectFirstOrThrow("a") - val url = a.attrAsRelativeUrl("href").toAbsoluteUrl(domain) + val url = "/" + a.attrAsRelativeUrl("href").toAbsoluteUrl(domain) MangaChapter( id = generateUid(url), name = a.text(), diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/TrWebtoon.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/TrWebtoon.kt index 1d16a585..0d732396 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/TrWebtoon.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/TrWebtoon.kt @@ -1,11 +1,13 @@ package org.koitharu.kotatsu.parsers.site.tr +import org.jsoup.nodes.Document 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 java.lang.IllegalArgumentException import java.text.DateFormat import java.text.SimpleDateFormat import java.util.* @@ -16,100 +18,134 @@ class TrWebtoon(context: MangaLoaderContext) : override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("trwebtoon.com") - override val availableSortOrders: Set - get() = EnumSet.of(SortOrder.POPULARITY, SortOrder.ALPHABETICAL, SortOrder.UPDATED) - - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() - val url = if (sortOrder == SortOrder.UPDATED && query.isNullOrEmpty() && tags.isNullOrEmpty()) { - buildString { - append("https://") - append(domain) - append("/son-eklenenler") - append("?page=") - append(page) + override val availableSortOrders: Set = + EnumSet.of(SortOrder.POPULARITY, SortOrder.ALPHABETICAL, SortOrder.UPDATED) + + override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) + + override val isMultipleTagsSupported = false + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + when (filter) { + is MangaListFilter.Search -> { + val url = buildString { + append("https://") + append(domain) + append("/webtoon-listesi?page=") + append(page.toString()) + append("&q=") + append(filter.query.urlEncoded()) + append("&sort=views&short_type=DESC") + } + return parseMangaList(webClient.httpGet(url).parseHtml()) } - } else { - buildString { - append("https://") - append(domain) - append("/webtoon-listesi") - append("?page=") - append(page) - when { - !query.isNullOrEmpty() -> { - append("&q=") - append(query.urlEncoded()) - } - !tags.isNullOrEmpty() -> { - append("&genre=") - append(tag?.key.orEmpty()) + is MangaListFilter.Advanced -> { + + if (filter.sortOrder == SortOrder.UPDATED) { + if (filter.tags.isNotEmpty()) { + throw IllegalArgumentException("Sort order updated + Tags or States is not supported by this source") + } + val url = buildString { + append("https://") + append(domain) + append("/son-eklenenler?page=") + append(page.toString()) } + return parseMangaListUpdated(webClient.httpGet(url).parseHtml()) + } else { + val url = buildString { + append("https://") + append(domain) + append("/webtoon-listesi?page=") + append(page.toString()) + filter.tags.oneOrThrowIfMany()?.let { + append("&genre=") + append(it.key) + } + filter.states.oneOrThrowIfMany()?.let { + append("&status=") + append( + when (it) { + MangaState.ONGOING -> "continues" + MangaState.FINISHED -> "complated" + else -> "" + }, + ) + } + append("&sort=") + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("views&short_type=DESC") + SortOrder.ALPHABETICAL -> append("name&short_type=ASC") + else -> append("views&short_type=DESC") + } + } + + return parseMangaList(webClient.httpGet(url).parseHtml()) } - append("&sort=") - when (sortOrder) { - SortOrder.POPULARITY -> append("views&short_type=DESC") - SortOrder.ALPHABETICAL -> append("name&short_type=ASC") - else -> append("views&short_type=DESC") + + } + + null -> { + val url = buildString { + append("https://") + append(domain) + append("/son-eklenenler?page=") + append(page.toString()) } + return parseMangaListUpdated(webClient.httpGet(url).parseHtml()) } } + } - val doc = webClient.httpGet(url).parseHtml() - val mangas = if (sortOrder == SortOrder.UPDATED && query.isNullOrEmpty() && tags.isNullOrEmpty()) { - doc.select(".page-content div.bslist_item").map { li -> - val href = li.selectFirstOrThrow("a").attrAsRelativeUrl("href") - Manga( - id = generateUid(href), - url = href, - publicUrl = href.toAbsoluteUrl(domain), - coverUrl = li.selectFirst(".figure img")?.src().orEmpty(), - title = li.selectFirst(".title")?.text().orEmpty(), - altTitle = null, - rating = RATING_UNKNOWN, - tags = emptySet(), - author = null, - state = when (doc.selectFirst("d-inline .badge")?.text()) { - "Devam Ediyor", "Güncel" -> MangaState.ONGOING - "Tamamlandı" -> MangaState.FINISHED - else -> null - }, - source = source, - isNsfw = isNsfwSource, - ) - } - } else { - doc.select(".row .col-xl-4 .card-body").map { li -> - val href = li.selectFirstOrThrow("a").attrAsRelativeUrl("href") - Manga( - id = generateUid(href), - url = href, - publicUrl = href.toAbsoluteUrl(domain), - coverUrl = li.selectFirst("img")?.src().orEmpty(), - title = li.selectFirst(".table-responsive a")?.text().orEmpty(), - altTitle = null, - rating = li.selectFirst(".row .col-xl-4 .mt-2 .my-1 .text-muted")?.text()?.substringBefore("/") - ?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN, - tags = emptySet(), - author = null, - state = when (doc.selectLast(".row .col-xl-4 .mt-2 .rounded-pill")?.text()) { - "Devam Ediyor", "Güncel" -> MangaState.ONGOING - "Tamamlandı" -> MangaState.FINISHED - else -> null - }, - source = source, - isNsfw = isNsfwSource, - ) - } + + private fun parseMangaList(doc: Document): List { + return doc.select(".row .col-xl-4 .card-body").map { li -> + val href = li.selectFirstOrThrow("a").attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + url = href, + publicUrl = href.toAbsoluteUrl(domain), + coverUrl = li.selectFirst("img")?.src().orEmpty(), + title = li.selectFirst(".table-responsive a")?.text().orEmpty(), + altTitle = null, + rating = li.selectFirst(".row .col-xl-4 .mt-2 .my-1 .text-muted")?.text()?.substringBefore("/") + ?.toFloatOrNull()?.div(5f) ?: RATING_UNKNOWN, + tags = emptySet(), + author = null, + state = when (doc.selectLast(".row .col-xl-4 .mt-2 .rounded-pill")?.text()) { + "Devam Ediyor", "Güncel" -> MangaState.ONGOING + "Tamamlandı" -> MangaState.FINISHED + else -> null + }, + source = source, + isNsfw = isNsfwSource, + ) } + } - return mangas + private fun parseMangaListUpdated(doc: Document): List { + return doc.select(".page-content div.bslist_item").map { li -> + val href = li.selectFirstOrThrow("a").attrAsRelativeUrl("href") + Manga( + id = generateUid(href), + url = href, + publicUrl = href.toAbsoluteUrl(domain), + coverUrl = li.selectFirst(".figure img")?.src().orEmpty(), + title = li.selectFirst(".title")?.text().orEmpty(), + altTitle = null, + rating = RATING_UNKNOWN, + tags = emptySet(), + author = null, + state = when (doc.selectFirst("d-inline .badge")?.text()) { + "Devam Ediyor", "Güncel" -> MangaState.ONGOING + "Tamamlandı" -> MangaState.FINISHED + else -> null + }, + source = source, + isNsfw = isNsfwSource, + ) + } } override suspend fun getAvailableTags(): Set { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/YaoiFlix.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/YaoiFlix.kt index ef915b6f..cb41dc3a 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/YaoiFlix.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/tr/YaoiFlix.kt @@ -16,43 +16,50 @@ class YaoiFlix(context: MangaLoaderContext) : PagedMangaParser(context, MangaSou override val configKeyDomain = ConfigKey.Domain("www.yaoiflix.pro") - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override val isMultipleTagsSupported = false + + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { val url = buildString { append("https://") append(domain) - when { - !query.isNullOrEmpty() -> { + when (filter) { + is MangaListFilter.Search -> { if (page > 1) { append("/page/") - append(page) + append(page.toString()) } append("/?s=") - append(query.urlEncoded()) + append(filter.query.urlEncoded()) } - !tags.isNullOrEmpty() -> { - append("/dizi-kategori/") - append(tag?.key.orEmpty()) - append("/") - if (page > 1) { - append("page/") - append(page) - append("/") + is MangaListFilter.Advanced -> { + if (filter.tags.isNotEmpty()) { + filter.tags.oneOrThrowIfMany()?.let { + append("/dizi-kategori/") + append(it.key) + append("/") + if (page > 1) { + append("page/") + append(page.toString()) + append('/') + } + } + } else { + append("/tum-seriler/") + if (page > 1) { + append("page/") + append(page.toString()) + append('/') + } } } - else -> { + null -> { append("/tum-seriler/") if (page > 1) { append("page/") - append(page) - append("/") + append(page.toString()) + append('/') } } } From 56eb059bcd493c9c5a8bc1eed9e9d1dd9639c3bd Mon Sep 17 00:00:00 2001 From: devi Date: Sun, 3 Dec 2023 12:56:52 +0100 Subject: [PATCH 17/18] fix getlist VmpParser, LineWebtoonsParser, BlogTruyenParser, HentaiVNParser, YurinekoParser add filter on WpComicsParser, ZMangaParser, LxManga, Truyenqq, TruyentranhLHParser Move NetTruyen to template WpComicsParser and add multitags on WpComicsParser --- .../parsers/site/all/LineWebtoonsParser.kt | 188 +++++++++------ .../parsers/site/madara/MadaraParser.kt | 20 +- .../parsers/site/vi/BlogTruyenParser.kt | 215 ++++++++--------- .../kotatsu/parsers/site/vi/HentaiVNParser.kt | 93 +++---- .../kotatsu/parsers/site/vi/LxManga.kt | 78 +++--- .../parsers/site/vi/NetTruyenParser.kt | 228 ------------------ .../kotatsu/parsers/site/vi/Truyenqq.kt | 90 ++++--- .../parsers/site/vi/TruyentranhLHParser.kt | 122 ++++++---- .../kotatsu/parsers/site/vi/YurinekoParser.kt | 101 ++++---- .../kotatsu/parsers/site/vmp/VmpParser.kt | 56 +++-- .../parsers/site/wpcomics/WpComicsParser.kt | 192 +++++++++------ .../parsers/site/wpcomics/en/XoxoComics.kt | 129 +++++++--- .../parsers/site/wpcomics/vi/NetTruyen.kt | 19 ++ .../parsers/site/wpcomics/vi/Nettruyenmax.kt | 4 +- .../parsers/site/zmanga/ZMangaParser.kt | 76 +++--- 15 files changed, 823 insertions(+), 788 deletions(-) delete mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/NetTruyenParser.kt create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyen.kt diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/LineWebtoonsParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/LineWebtoonsParser.kt index 33cbe5af..80120a6d 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/LineWebtoonsParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/all/LineWebtoonsParser.kt @@ -132,83 +132,117 @@ internal abstract class LineWebtoonsParser( } } - override suspend fun getList( - offset: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val genre = tags.oneOrThrowIfMany()?.key ?: "ALL" - - val sortOrderStr = when (sortOrder) { - SortOrder.UPDATED -> "UPDATE" - SortOrder.POPULARITY -> "READ_COUNT" - SortOrder.RATING -> "LIKEIT" - else -> throw IllegalArgumentException("Unsupported sort order: $sortOrder") - } - - val manga = if (query != null) { - if (!tags.isNullOrEmpty()) { - throw IllegalArgumentException("This source does not support search with tags") - } + override suspend fun getList(offset: Int, filter: MangaListFilter?): List { + 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") + + 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, + ) + } + } - 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, - ) + 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, + ) + } } - } 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, - ) + + 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, + ) + } } - } + } + 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()) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt index 1bf55d38..405afa56 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/MadaraParser.kt @@ -180,15 +180,16 @@ internal abstract class MadaraParser( is MangaListFilter.Advanced -> { - val tag = filter.tags.oneOrThrowIfMany() if (filter.tags.isNotEmpty()) { - append("/$tagPrefix") - append(tag?.key.orEmpty()) - if (pages > 1) { - append("/page/") - append(pages.toString()) + filter.tags.oneOrThrowIfMany()?.let { + append("/$tagPrefix") + append(it.key) + if (pages > 1) { + append("/page/") + append(pages.toString()) + } + append("/?") } - append("/?") } else { if (pages > 1) { @@ -241,8 +242,9 @@ internal abstract class MadaraParser( is MangaListFilter.Advanced -> { - val tag = filter.tags.oneOrThrowIfMany() - payload["vars[wp-manga-genre]"] = tag?.key.orEmpty() + filter.tags.oneOrThrowIfMany()?.let { + payload["vars[wp-manga-genre]"] = it.key + } when (filter.sortOrder) { SortOrder.POPULARITY -> payload["vars[meta_key]"] = "_wp_manga_views" diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenParser.kt index 3ec7263b..f5bee237 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/BlogTruyenParser.kt @@ -29,98 +29,38 @@ class BlogTruyenParser(context: MangaLoaderContext) : .add("User-Agent", UserAgents.CHROME_DESKTOP) .build() + override val isMultipleTagsSupported = false + private val dateFormat = SimpleDateFormat("dd/MM/yyyy HH:mm", Locale.US) private var cacheTags = SuspendLazy(::fetchTags) - override suspend fun getDetails(manga: Manga): Manga { - val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() - val descriptionElement = doc.selectFirstOrThrow("div.description") - val statusText = descriptionElement - .selectFirst("p:contains(Trạng thái) > span.color-red") - ?.text() - val state = when (statusText) { - "Đang tiến hành" -> MangaState.ONGOING - "Đã hoàn thành" -> MangaState.FINISHED - else -> null - } + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { - val rating = doc.selectFirst("span.total-vote")?.attr("ng-init")?.let { text -> - val like = text.substringAfter("TotalLike=") - .substringBefore(';') - .toIntOrNull() ?: return@let RATING_UNKNOWN - val dislike = text.substringAfter("TotalDisLike=") - .toIntOrNull() ?: return@let RATING_UNKNOWN + return when (filter) { - when { - like == 0 && dislike == 0 -> RATING_UNKNOWN - else -> like.toFloat() / (like + dislike) - } - } - - val tags = cacheTags.tryGet().getOrNull()?.let { tagMap -> - descriptionElement.select("p > span.category").mapNotNullToSet { - val tagName = it.selectFirst("a")?.text()?.trim() ?: return@mapNotNullToSet null - tagMap[tagName] - } - } - - return manga.copy( - tags = tags ?: emptySet(), - author = descriptionElement.selectFirst("p:contains(Tác giả) > a")?.text(), - description = doc.selectFirst(".detail .content")?.html(), - chapters = parseChapterList(doc), - largeCoverUrl = doc.selectLast("div.thumbnail > img")?.imageUrl(), - state = state, - rating = rating ?: RATING_UNKNOWN, - isNsfw = doc.getElementById("warningCategory") != null, - ) - } - - private fun parseChapterList(doc: Document): List { - val chapterList = doc.select("#list-chapters > p") - return chapterList.mapChapters(reversed = true) { index, element -> - val titleElement = element.selectFirst("span.title > a") ?: return@mapChapters null - val name = titleElement.text() - val relativeUrl = titleElement.attrAsRelativeUrl("href") - val id = relativeUrl.substringAfter('/').substringBefore('/') - val uploadDate = dateFormat.tryParse(element.select("span.publishedDate").text()) - MangaChapter( - id = generateUid(id), - name = name, - number = index + 1, - url = relativeUrl, - scanlator = null, - uploadDate = uploadDate, - branch = null, - source = source, - ) - } - } - - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - return when { - !query.isNullOrEmpty() -> { - val searchUrl = "https://${domain}/timkiem/nangcao/1/0/-1/-1?txt=${query.urlEncoded()}&p=$page" + is MangaListFilter.Search -> { + val searchUrl = "https://${domain}/timkiem/nangcao/1/0/-1/-1?txt=${filter.query.urlEncoded()}&p=$page" val searchContent = webClient.httpGet(searchUrl).parseHtml() .selectFirst("section.list-manga-bycate > div.list") parseMangaList(searchContent) } - !tags.isNullOrEmpty() -> { - val tag = tags.oneOrThrowIfMany()!! - val categoryAjax = - "https://${domain}/ajax/Category/AjaxLoadMangaByCategory?id=${tag.key}&orderBy=5&p=$page" - val listContent = webClient.httpGet(categoryAjax).parseHtml().selectFirst("div.list") - parseMangaList(listContent) + is MangaListFilter.Advanced -> { + + if (filter.tags.isNotEmpty()) { + filter.tags.oneOrThrowIfMany().let { + val categoryAjax = + "https://${domain}/ajax/Category/AjaxLoadMangaByCategory?id=${it?.key}&orderBy=5&p=$page" + val listContent = webClient.httpGet(categoryAjax).parseHtml().selectFirst("div.list") + parseMangaList(listContent) + } + } else { + getNormalList(page) + } } - else -> getNormalList(page) + null -> getNormalList(page) } } @@ -146,7 +86,7 @@ class BlogTruyenParser(context: MangaLoaderContext) : description = el.selectFirst("p.al-j.break.line-height-15")?.text(), url = relativeUrl, publicUrl = relativeUrl.toAbsoluteUrl(domain), - coverUrl = linkTag.selectLast("img")?.imageUrl().orEmpty(), + coverUrl = linkTag.selectLast("img")?.src().orEmpty(), source = source, tags = tags ?: emptySet(), isNsfw = false, @@ -171,7 +111,7 @@ class BlogTruyenParser(context: MangaLoaderContext) : description = mangaInfo.select("div.al-j.fs-12").text(), url = relativeUrl, publicUrl = relativeUrl.toAbsoluteUrl(domain), - coverUrl = mangaInfo.selectFirst("div > img.img")?.imageUrl().orEmpty(), + coverUrl = mangaInfo.selectFirst("div > img.img")?.src().orEmpty(), isNsfw = false, rating = RATING_UNKNOWN, tags = emptySet(), @@ -182,6 +122,91 @@ class BlogTruyenParser(context: MangaLoaderContext) : } } + override suspend fun getAvailableTags(): Set { + return cacheTags.get().values.toSet() + } + + private suspend fun fetchTags(): Map { + val doc = webClient.httpGet("/timkiem/nangcao".toAbsoluteUrl(domain)).parseHtml() + val tagItems = doc.select("li[data-id]") + val tagMap = ArrayMap(tagItems.size) + for (tag in tagItems) { + val title = tag.text().trim() + tagMap[tag.text().trim()] = MangaTag( + title = title, + key = tag.attr("data-id"), + source = source, + ) + } + return tagMap + } + + override suspend fun getDetails(manga: Manga): Manga { + val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() + val descriptionElement = doc.selectFirstOrThrow("div.description") + val statusText = descriptionElement + .selectFirst("p:contains(Trạng thái) > span.color-red") + ?.text() + + val state = when (statusText) { + "Đang tiến hành" -> MangaState.ONGOING + "Đã hoàn thành" -> MangaState.FINISHED + else -> null + } + + val rating = doc.selectFirst("span.total-vote")?.attr("ng-init")?.let { text -> + val like = text.substringAfter("TotalLike=") + .substringBefore(';') + .toIntOrNull() ?: return@let RATING_UNKNOWN + val dislike = text.substringAfter("TotalDisLike=") + .toIntOrNull() ?: return@let RATING_UNKNOWN + + when { + like == 0 && dislike == 0 -> RATING_UNKNOWN + else -> like.toFloat() / (like + dislike) + } + } + + val tags = cacheTags.tryGet().getOrNull()?.let { tagMap -> + descriptionElement.select("p > span.category").mapNotNullToSet { + val tagName = it.selectFirst("a")?.text()?.trim() ?: return@mapNotNullToSet null + tagMap[tagName] + } + } + + return manga.copy( + tags = tags ?: emptySet(), + author = descriptionElement.selectFirst("p:contains(Tác giả) > a")?.text(), + description = doc.selectFirst(".detail .content")?.html(), + chapters = parseChapterList(doc), + largeCoverUrl = doc.selectLast("div.thumbnail > img")?.src().orEmpty(), + state = state, + rating = rating ?: RATING_UNKNOWN, + isNsfw = doc.getElementById("warningCategory") != null, + ) + } + + private fun parseChapterList(doc: Document): List { + val chapterList = doc.select("#list-chapters > p") + return chapterList.mapChapters(reversed = true) { index, element -> + val titleElement = element.selectFirst("span.title > a") ?: return@mapChapters null + val name = titleElement.text() + val relativeUrl = titleElement.attrAsRelativeUrl("href") + val id = relativeUrl.substringAfter('/').substringBefore('/') + val uploadDate = dateFormat.tryParse(element.select("span.publishedDate").text()) + MangaChapter( + id = generateUid(id), + name = name, + number = index + 1, + url = relativeUrl, + scanlator = null, + uploadDate = uploadDate, + branch = null, + source = source, + ) + } + } + override suspend fun getPages(chapter: MangaChapter): List { fun generateImageId(index: Int) = generateUid("${chapter.url}/$index") @@ -191,7 +216,7 @@ class BlogTruyenParser(context: MangaLoaderContext) : pages.add( MangaPage( id = generateImageId(pages.size), - url = img.imageUrl(), + url = img.src().orEmpty(), preview = null, source = source, ), @@ -218,30 +243,4 @@ class BlogTruyenParser(context: MangaLoaderContext) : return pages } - - override suspend fun getAvailableTags(): Set { - return cacheTags.get().values.toSet() - } - - - private suspend fun fetchTags(): Map { - val doc = webClient.httpGet("/timkiem/nangcao".toAbsoluteUrl(domain)).parseHtml() - val tagItems = doc.select("li[data-id]") - val tagMap = ArrayMap(tagItems.size) - for (tag in tagItems) { - val title = tag.text().trim() - tagMap[tag.text().trim()] = MangaTag( - title = title, - key = tag.attr("data-id"), - source = source, - ) - } - return tagMap - } - - private fun Element.imageUrl(): String { - return attrAsAbsoluteUrlOrNull("src") - ?: attrAsAbsoluteUrlOrNull("data-cfsrc") - ?: "" - } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt index 54edb7a6..5b398558 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/HentaiVNParser.kt @@ -35,6 +35,57 @@ class HentaiVNParser(context: MangaLoaderContext) : MangaParser(context, MangaSo SortOrder.NEWEST, ) + override suspend fun getList(offset: Int, filter: MangaListFilter?): List { + return when (filter) { + + is MangaListFilter.Search -> { + val page = (offset / PAGE_SIZE.toFloat()).toIntUp() + 1 + urlBuilder() + val searchUrl = + "/tim-kiem-truyen.html?key=${filter.query.urlEncoded()}&page=$page".toAbsoluteUrl(domain) + val docs = webClient.httpGet(searchUrl).parseHtml() + parseMainList(docs, page) + } + + is MangaListFilter.Advanced -> { + val pageSize = if (filter.tags.isEmpty()) PAGE_SIZE else SEARCH_PAGE_SIZE + val page = (offset / pageSize.toFloat()).toIntUp() + 1 + + if (filter.tags.isNotEmpty()) { + val url = buildString { + val tagKey = "tag[]".urlEncoded() + append("/forum/search-plus.php?name=") + append("&dou=&char=") + filter.tags.forEach { tag -> + append("&") + append(tagKey) + append("=") + append(tag.key) + } + append("&search=") + append("&page=") + append(page) + }.toAbsoluteUrl(domain) + + val docs = webClient.httpGet(url).parseHtml() + return parseAdvanceSearch(docs, page) + } else { + val site = if (filter.sortOrder == SortOrder.UPDATED) "/chap-moi" else "/danh-sach" + val url = "$site.html?page=$page".toAbsoluteUrl(domain) + context.cookieJar.insertCookies(domain, *getSortCookies(filter.sortOrder)) + val docs = webClient.httpGet(url).parseHtml() + parseMainList(docs, page) + } + } + + null -> { + val page = (offset / PAGE_SIZE.toFloat()).toIntUp() + 1 + val url = "/chap-moi.html?page=$page".toAbsoluteUrl(domain) + parseMainList(webClient.httpGet(url).parseHtml(), page) + } + } + } + override suspend fun getDetails(manga: Manga): Manga = coroutineScope { val chapterDeferred = async { fetchChapters(manga.url) } val docs = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() @@ -79,48 +130,6 @@ class HentaiVNParser(context: MangaLoaderContext) : MangaParser(context, MangaSo ) } - override suspend fun getList(offset: Int, query: String?, tags: Set?, sortOrder: SortOrder): List { - val pageSize = if (tags.isNullOrEmpty()) PAGE_SIZE else SEARCH_PAGE_SIZE - val page = (offset / pageSize.toFloat()).toIntUp() + 1 - return when { - !tags.isNullOrEmpty() -> { - val url = buildString { - val tagKey = "tag[]".urlEncoded() - append("/forum/search-plus.php?name=") - append(query?.urlEncoded().orEmpty()) - append("&dou=&char=") - tags.forEach { tag -> - append("&") - append(tagKey) - append("=") - append(tag.key) - } - append("&search=") - append("&page=") - append(page) - }.toAbsoluteUrl(domain) - - val docs = webClient.httpGet(url).parseHtml() - return parseAdvanceSearch(docs, page) - } - - !query.isNullOrEmpty() -> { - urlBuilder() - val searchUrl = "/tim-kiem-truyen.html?key=${query.urlEncoded()}&page=$page".toAbsoluteUrl(domain) - val docs = webClient.httpGet(searchUrl).parseHtml() - parseMainList(docs, page) - } - - else -> { - val site = if (sortOrder == SortOrder.UPDATED) "/chap-moi" else "/danh-sach" - val url = "$site.html?page=$page".toAbsoluteUrl(domain) - context.cookieJar.insertCookies(domain, *getSortCookies(sortOrder)) - val docs = webClient.httpGet(url).parseHtml() - parseMainList(docs, page) - } - } - } - override suspend fun getPages(chapter: MangaChapter): List { val docs = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml() return docs.select("#image > img").map { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt index 6fdd9b0a..bd61e0aa 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/LxManga.kt @@ -20,57 +20,76 @@ internal class LxManga(context: MangaLoaderContext) : PagedMangaParser(context, SortOrder.NEWEST, SortOrder.POPULARITY, ) + override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) override val configKeyDomain = ConfigKey.Domain("lxmanga.net") + override val isMultipleTagsSupported = false + override val headers: Headers = Headers.Builder() .add("User-Agent", UserAgents.CHROME_DESKTOP) .build() - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { val url = buildString { append("https://") append(domain) - when { - !query.isNullOrEmpty() -> { + + when (filter) { + + is MangaListFilter.Search -> { val skey = "filter[name]=".urlEncoded() append("/tim-kiem?$skey") - append(query.urlEncoded()) + append(filter.query.urlEncoded()) + append("&page=") + append(page.toString()) } - !tags.isNullOrEmpty() -> { - append("/the-loai/") - for (tag in tags) { - append(tag.key) + is MangaListFilter.Advanced -> { + if (filter.tags.isNotEmpty()) { + filter.tags.oneOrThrowIfMany()?.let { + append("/the-loai/") + append(it.key) + } + } else { + append("/danh-sach") + } + append("?page=") + append(page.toString()) + + if (filter.states.isNotEmpty()) { + append("&filter[status]=") + filter.states.forEach { + append( + when (it) { + MangaState.ONGOING -> "2," + MangaState.FINISHED -> "1," + else -> "1,2" + }, + ) + } + } + + append("&sort=") + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("-views") + SortOrder.UPDATED -> append("-updated_at") + SortOrder.NEWEST -> append("-created_at") + SortOrder.ALPHABETICAL -> append("name") + else -> append("-updated_at") } } - else -> { - append("/danh-sach") + null -> { + append("/danh-sach?sort=-updated_at&page=") + append(page.toString()) } } - append("?page=") - append(page.toString()) - append("&sort=") - when (sortOrder) { - SortOrder.POPULARITY -> append("-views") - SortOrder.UPDATED -> append("-updated_at") - SortOrder.NEWEST -> append("-created_at") - SortOrder.ALPHABETICAL -> append("name") - - else -> append("-updated_at") - } - } + } val doc = webClient.httpGet(url).parseHtml() - return doc.select("div.grid div.manga-vertical") .map { div -> val href = div.selectFirstOrThrow("a").attr("href") @@ -95,7 +114,6 @@ internal class LxManga(context: MangaLoaderContext) : PagedMangaParser(context, override suspend fun getDetails(manga: Manga): Manga { val root = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US) - return manga.copy( altTitle = root.select(".divider2:contains(Noms associés :)").firstOrNull()?.text(), state = when (root.select("div.grow div.mt-2:contains(Tình trạng) a").first()!!.text()) { @@ -135,9 +153,7 @@ internal class LxManga(context: MangaLoaderContext) : PagedMangaParser(context, override suspend fun getPages(chapter: MangaChapter): List { val fullUrl = chapter.url.toAbsoluteUrl(domain) - val doc = webClient.httpGet(fullUrl).parseHtml() - return doc.select("div.text-center img.lazy").map { img -> val url = img.attrAsRelativeUrlOrNull("data-src") ?: img.attrAsRelativeUrlOrNull("src") ?: img.parseFailed("Image src not found") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/NetTruyenParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/NetTruyenParser.kt deleted file mode 100644 index db799ac0..00000000 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/NetTruyenParser.kt +++ /dev/null @@ -1,228 +0,0 @@ -package org.koitharu.kotatsu.parsers.site.vi - -import androidx.collection.ArrayMap -import androidx.collection.ArraySet -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -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.exception.NotFoundException -import org.koitharu.kotatsu.parsers.model.* -import org.koitharu.kotatsu.parsers.util.* -import java.text.SimpleDateFormat -import java.util.* - -@MangaSourceParser("NETTRUYEN", "NetTruyen", "vi") -class NetTruyenParser(context: MangaLoaderContext) : - PagedMangaParser(context, MangaSource.NETTRUYEN, pageSize = 36) { - - override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain( - "www.nettruyenlive.com", - "www.nettruyenio.com", - "www.nettruyento.com", - "nettruyento.com", - "nettruyenin.com", - ) - - override val availableSortOrders: Set - get() = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST, SortOrder.RATING) - - private val mutex = Mutex() - private val dateFormat = SimpleDateFormat("dd/MM/yy", Locale.US) - private var tagCache: ArrayMap? = null - - override suspend fun getDetails(manga: Manga): Manga { - val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() - val rating = doc.selectFirst("span[itemprop=ratingValue]") - ?.ownText() - ?.toFloatOrNull() ?: 0f - - val chapterElements = doc.getElementById("nt_listchapter")?.select("ul > li") ?: doc.parseFailed() - val chapters = chapterElements.mapChapters(reversed = true) { index, element -> - val a = element.selectFirst("div.chapter > a") ?: return@mapChapters null - val relativeUrl = a.attrAsRelativeUrlOrNull("href") ?: return@mapChapters null - val timeText = element.selectFirst("div.col-xs-4.text-center.no-wrap.small")?.text() - - MangaChapter( - id = generateUid(relativeUrl), - name = a.text(), - number = index + 1, - url = relativeUrl, - scanlator = null, - uploadDate = parseChapterTime(timeText), - branch = null, - source = source, - ) - } - - return manga.copy( - rating = rating / 5, - chapters = chapters, - description = doc.selectFirst("div.detail-content > p")?.html(), - isNsfw = doc.selectFirst("div.alert.alert-danger > strong:contains(Cảnh báo độ tuổi)") != null, - ) - } - - // 20 giây trước - // 52 phút trước - // 6 giờ trước - // 2 ngày trước - // 19:09 30/07 - // 23/12/21 - private fun parseChapterTime(timeText: String?): Long { - if (timeText.isNullOrEmpty()) { - return 0L - } - - val timeWords = arrayOf("giây", "phút", "giờ", "ngày") - val calendar = Calendar.getInstance() - val timeArr = timeText.split(' ') - if (WordSet(*timeWords).anyWordIn(timeText)) { - val timeSuffix = timeArr.getOrNull(1) - val timeDiff = timeArr.getOrNull(0)?.toIntOrNull() ?: return 0L - when (timeSuffix) { - timeWords[0] -> calendar.add(Calendar.SECOND, -timeDiff) - timeWords[1] -> calendar.add(Calendar.MINUTE, -timeDiff) - timeWords[2] -> calendar.add(Calendar.HOUR, -timeDiff) - timeWords[3] -> calendar.add(Calendar.DATE, -timeDiff) - else -> return 0L - } - } else { - val relativeDate = timeArr.lastOrNull() ?: return 0L - val dateString = when (relativeDate.split('/').size) { - 2 -> { - val currentYear = calendar.get(Calendar.YEAR).toString().takeLast(2) - "$relativeDate/$currentYear" - } - - 3 -> relativeDate - else -> return 0L - } - - calendar.timeInMillis = dateFormat.tryParse(dateString) - } - - - return calendar.time.time - } - - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val isSearching = !query.isNullOrEmpty() - val url = buildString { - append("https://") - append(domain) - if (isSearching) { - append("/tim-truyen?keyword=") - append(query!!.urlEncoded()) - append("&page=") - append(page) - } else { - val tagQuery = tags.orEmpty().joinToString(",") { it.key } - append("/tim-truyen-nang-cao?genres=$tagQuery") - append("¬genres=&gender=-1&status=-1&minchapter=1&sort=${getSortOrderKey(sortOrder)}") - append("&page=$page") - } - } - - val response = if (isSearching) { - val result = runCatchingCancellable { webClient.httpGet(url) } - val exception = result.exceptionOrNull() - if (exception is NotFoundException) { - return emptyList() - } - - result.getOrThrow() - } else { - webClient.httpGet(url) - } - - val itemsElements = response.parseHtml() - .select("div.ModuleContent > div.items") - .select("div.item") - return itemsElements.mapNotNull { item -> - val tooltipElement = item.selectFirst("div.box_tootip") ?: return@mapNotNull null - val absUrl = item.selectFirst("div.image > a")?.attrAsAbsoluteUrlOrNull("href") ?: return@mapNotNull null - val slug = absUrl.substringAfterLast('/') - val mangaState = when (tooltipElement.selectFirst("div.message_main > p:contains(Tình trạng)")?.ownText()) { - "Đang tiến hành" -> MangaState.ONGOING - "Hoàn thành" -> MangaState.FINISHED - else -> null - } - - val tagMap = getOrCreateTagMap() - val tagsElement = tooltipElement.selectFirst("div.message_main > p:contains(Thể loại)")?.ownText().orEmpty() - val mangaTags = tagsElement.split(',').mapNotNullToSet { tagMap[it.trim()] } - Manga( - id = generateUid(slug), - title = tooltipElement.selectFirst("div.title")?.text().orEmpty(), - altTitle = null, - url = absUrl.toRelativeUrl(domain), - publicUrl = absUrl, - rating = RATING_UNKNOWN, - isNsfw = false, - coverUrl = item.selectFirst("div.image a img")?.absUrl("data-original").orEmpty(), - largeCoverUrl = null, - tags = mangaTags, - state = mangaState, - author = tooltipElement.selectFirst("div.message_main > p:contains(Tác giả)")?.ownText(), - description = tooltipElement.selectFirst("div.box_text")?.text(), - chapters = null, - source = source, - ) - } - } - - - override suspend fun getPages(chapter: MangaChapter): List { - val pageElements = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml() - .select("div.reading-detail.box_doc > div img") - return pageElements.map { element -> - val url = element.attrAsAbsoluteUrl("data-original") - MangaPage( - id = generateUid(url), - url = url, - preview = null, - source = source, - ) - } - } - - override suspend fun getAvailableTags(): Set { - val map = getOrCreateTagMap() - val tagSet = ArraySet(map.size) - for (entry in map) { - tagSet.add(entry.value) - } - - return tagSet - } - - private suspend fun getOrCreateTagMap(): ArrayMap = mutex.withLock { - tagCache?.let { return@withLock it } - val doc = webClient.httpGet("/tim-truyen-nang-cao".toAbsoluteUrl(domain)).parseHtml() - val tagItems = doc.select("div.genre-item") - val result = ArrayMap(tagItems.size) - for (item in tagItems) { - val title = item.text().trim() - val key = item.select("span[data-id]").attr("data-id") - result[title] = MangaTag(title = title, key = key, source = source) - } - tagCache = result - result - } - - private fun getSortOrderKey(sortOrder: SortOrder) = when (sortOrder) { - SortOrder.UPDATED -> 0 - SortOrder.POPULARITY -> 10 - SortOrder.NEWEST -> 15 - SortOrder.RATING -> 20 - else -> throw IllegalArgumentException("Sort order ${sortOrder.name} not supported") - } -} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Truyenqq.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Truyenqq.kt index 6341e2be..256892d7 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Truyenqq.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/Truyenqq.kt @@ -14,40 +14,70 @@ internal class Truyenqq(context: MangaLoaderContext) : PagedMangaParser(context, override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED, SortOrder.POPULARITY, SortOrder.NEWEST) + + override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) + override val configKeyDomain = ConfigKey.Domain("truyenqqvn.com") - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tagQuery = if (tags.isNullOrEmpty()) "" else tags.joinToString(separator = ",") { it.key } - val url = if (!query.isNullOrEmpty()) { - buildString { - append("https://") - append(domain) - append("/tim-kiem/trang-$page.html") - append("?q=") - append(query.urlEncoded()) - } - } else { - buildString { - append("https://") - append(domain) - append("/tim-kiem-nang-cao/trang-$page.html") - append("?status=-1&country=0&sort=") - when (sortOrder) { - SortOrder.POPULARITY -> append("4") - SortOrder.UPDATED -> append("2") - SortOrder.NEWEST -> append("0") - else -> append("2") + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + + val url = + when (filter) { + is MangaListFilter.Search -> { + buildString { + append("https://") + append(domain) + append("/tim-kiem/trang-$page.html") + append("?q=") + append(filter.query.urlEncoded()) + } + } + + is MangaListFilter.Advanced -> { + buildString { + append("https://") + append(domain) + append("/tim-kiem-nang-cao/trang-") + append(page.toString()) + append(".html?country=0&sort=") + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("4") + SortOrder.UPDATED -> append("2") + SortOrder.NEWEST -> append("0") + else -> append("2") + } + if (filter.states.isNotEmpty()) { + filter.states.oneOrThrowIfMany()?.let { + append("&status=") + append( + when (it) { + MangaState.ONGOING -> "0" + MangaState.FINISHED -> "1" + else -> "-1" + }, + ) + } + } else { + append("&status=-1") + } + + append("&category=") + append(filter.tags.joinToString(separator = ",") { it.key }) + append("¬category=&minchapter=0") + } + } + + null -> { + buildString { + append("https://") + append(domain) + append("/tim-kiem-nang-cao/trang-") + append(page.toString()) + append(".html?status=-1&country=0&sort=2&category=¬category=&minchapter=0") + } + } - append("&category=") - append(tagQuery) - append("¬category=&minchapter=0") } - } val doc = webClient.httpGet(url).parseHtml() return doc.requireElementById("main_homepage").select("li").map { li -> val href = li.selectFirstOrThrow("a").attrAsRelativeUrl("href") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyentranhLHParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyentranhLHParser.kt index 640a089b..02fa5fc0 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyentranhLHParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/TruyentranhLHParser.kt @@ -19,10 +19,84 @@ class TruyentranhLHParser(context: MangaLoaderContext) : override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("truyentranhlh.net") override val availableSortOrders: Set = EnumSet.allOf(SortOrder::class.java) + override val availableStates: Set = + EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED) private val mutex = Mutex() private var tagCache: Map? = null + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + + val url = urlBuilder().apply { + addPathSegment("tim-kiem") + addQueryParameter("page", page.toString()) + when (filter) { + + is MangaListFilter.Search -> { + addQueryParameter("q", filter.query) + } + + is MangaListFilter.Advanced -> { + + addQueryParameter( + "sort", + when (filter.sortOrder) { + SortOrder.UPDATED -> "update" + SortOrder.NEWEST -> "new" + SortOrder.RATING -> "like" + SortOrder.POPULARITY -> "top" + SortOrder.ALPHABETICAL -> "az" + }, + ) + + if (filter.states.isNotEmpty()) { + filter.states.oneOrThrowIfMany()?.let { + addQueryParameter( + "status", + when (it) { + MangaState.ONGOING -> "1" + MangaState.FINISHED -> "3" + MangaState.PAUSED -> "2" + else -> "0" + }, + ) + } + } + + if (filter.tags.isNotEmpty()) { + val tagsQuery = filter.tags.joinToString(separator = ",") { it.key } + addEncodedQueryParameter("accept_genres", tagsQuery) + } + } + + null -> { + addQueryParameter("sort", "update") + } + } + + }.build() + + return webClient.httpGet(url).parseHtml() + .select(".container .card.card-dark .row > .thumb-item-flow") + .mapNotNull { + val a = it.selectFirstOrThrow(".thumb-wrapper a") + Manga( + id = generateUid(a.attrAsRelativeUrl("href")), + url = a.attrAsRelativeUrl("href"), + publicUrl = a.attrAsAbsoluteUrl("href"), + title = it.select(".thumb_attr.series-title").text(), + altTitle = null, + rating = RATING_UNKNOWN, + isNsfw = false, + coverUrl = a.selectFirst("div[data-bg]")?.attrAsAbsoluteUrl("data-bg").orEmpty(), + tags = emptySet(), + state = null, + author = null, + source = source, + ) + } + } + override suspend fun getDetails(manga: Manga): Manga { val docs = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() val infoHeaderEl = docs.selectFirst("main.section-body") @@ -33,6 +107,7 @@ class TruyentranhLHParser(context: MangaLoaderContext) : val state = when (infoEl?.selectFirst(".info-item:contains(Tình trạng) > .info-value")?.text()) { "Đang tiến hành" -> MangaState.ONGOING "Đã hoàn thành" -> MangaState.FINISHED + "Tạm ngưng" -> MangaState.PAUSED else -> null } val rating = infoHeaderEl?.let { @@ -68,53 +143,6 @@ class TruyentranhLHParser(context: MangaLoaderContext) : ) } - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val sortQuery = when (sortOrder) { - SortOrder.UPDATED -> "update" - SortOrder.NEWEST -> "new" - SortOrder.RATING -> "like" - SortOrder.POPULARITY -> "top" - SortOrder.ALPHABETICAL -> "az" - } - val url = urlBuilder().apply { - addPathSegment("tim-kiem") - addQueryParameter("sort", sortQuery) - addQueryParameter("page", page.toString()) - if (!query.isNullOrEmpty()) { - addQueryParameter("q", query) - } - if (!tags.isNullOrEmpty()) { - val tagsQuery = tags.joinToString(separator = ",") { it.key } - addEncodedQueryParameter("accept_genres", tagsQuery) - } - }.build() - - return webClient.httpGet(url).parseHtml() - .select(".container .card.card-dark .row > .thumb-item-flow") - .mapNotNull { - val a = it.selectFirstOrThrow(".thumb-wrapper a") - Manga( - id = generateUid(a.attrAsRelativeUrl("href")), - url = a.attrAsRelativeUrl("href"), - publicUrl = a.attrAsAbsoluteUrl("href"), - title = it.select(".thumb_attr.series-title").text(), - altTitle = null, - rating = RATING_UNKNOWN, - isNsfw = false, - coverUrl = a.selectFirst("div[data-bg]")?.attrAsAbsoluteUrl("data-bg").orEmpty(), - tags = emptySet(), - state = null, - author = null, - source = source, - ) - } - } - override suspend fun getPages(chapter: MangaChapter): List { val url = chapter.url.toAbsoluteUrl(domain) return webClient.httpGet(url).parseHtml().select("#chapter-content > img").mapNotNull { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/YurinekoParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/YurinekoParser.kt index 1cdd0b6e..336e7e88 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/YurinekoParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vi/YurinekoParser.kt @@ -5,30 +5,12 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.config.ConfigKey -import org.koitharu.kotatsu.parsers.model.ContentType -import org.koitharu.kotatsu.parsers.model.Manga -import org.koitharu.kotatsu.parsers.model.MangaChapter -import org.koitharu.kotatsu.parsers.model.MangaPage -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.util.domain import org.koitharu.kotatsu.parsers.util.generateUid -import org.koitharu.kotatsu.parsers.util.json.asIterable -import org.koitharu.kotatsu.parsers.util.json.getStringOrNull -import org.koitharu.kotatsu.parsers.util.json.mapJSON -import org.koitharu.kotatsu.parsers.util.json.mapJSONToSet -import org.koitharu.kotatsu.parsers.util.json.toJSONList +import org.koitharu.kotatsu.parsers.util.json.* import org.koitharu.kotatsu.parsers.util.mapChapters -import org.koitharu.kotatsu.parsers.util.parseHtml -import org.koitharu.kotatsu.parsers.util.parseJson -import org.koitharu.kotatsu.parsers.util.parseJsonArray -import org.koitharu.kotatsu.parsers.util.requireElementById -import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl -import org.koitharu.kotatsu.parsers.util.tryParse -import org.koitharu.kotatsu.parsers.util.urlEncoded +import org.koitharu.kotatsu.parsers.util.* import java.text.SimpleDateFormat import java.util.EnumSet import java.util.Locale @@ -44,45 +26,25 @@ class YurinekoParser(context: MangaLoaderContext) : PagedMangaParser(context, Ma private val apiDomain get() = "api.$domain" - override suspend fun getDetails(manga: Manga): Manga { - val response = webClient.httpGet(manga.url.toAbsoluteUrl(apiDomain)).parseJson() - val df = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US) - return manga.copy( - chapters = response.getJSONArray("chapters") - .toJSONList() - .mapChapters(true) { i, jo -> - val mangaId = jo.getInt("mangaID") - val chapterId = jo.getInt("id") - MangaChapter( - id = generateUid(chapterId.toLong()), - name = jo.getString("name"), - number = i + 1, - scanlator = null, - url = "/read/$mangaId/$chapterId", - uploadDate = df.tryParse(jo.getString("date")), - branch = null, - source = source, - ) - }, - ) - } + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + val listUrl = + when (filter) { + + is MangaListFilter.Search -> { + "/search?query=${filter.query.urlEncoded()}&page=$page" + } + + is MangaListFilter.Advanced -> { + if (filter.tags.isNotEmpty()) { + val tagKeys = filter.tags.joinToString(separator = ",") { it.key } + "/advancedSearch?genre=$tagKeys¬Genre=&sort=7&minChapter=1&status=0&page=$page" + } else { + "/lastest2?page=$page" + } + } - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val listUrl = when { - !query.isNullOrEmpty() -> "/search?query=${query.urlEncoded()}&page=$page" - tags.isNullOrEmpty() -> "/lastest2?page=$page" - tags.size == 1 -> "/searchType?type=tag&id=${tags.first().key}&page=$page" - else -> { - // Sort order is different when filter with multiple tags - val tagKeys = tags.joinToString(separator = ",") { it.key } - "/advancedSearch?genre=$tagKeys¬Genre=&sort=7&minChapter=1&status=0&page=$page" + null -> "/lastest2?page=$page" } - } val jsonResponse = webClient.httpGet(listUrl.toAbsoluteUrl(apiDomain)).parseJson() return jsonResponse.getJSONArray("result") .mapJSON { jo -> @@ -119,6 +81,29 @@ class YurinekoParser(context: MangaLoaderContext) : PagedMangaParser(context, Ma } } + override suspend fun getDetails(manga: Manga): Manga { + val response = webClient.httpGet(manga.url.toAbsoluteUrl(apiDomain)).parseJson() + val df = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US) + return manga.copy( + chapters = response.getJSONArray("chapters") + .toJSONList() + .mapChapters(true) { i, jo -> + val mangaId = jo.getInt("mangaID") + val chapterId = jo.getInt("id") + MangaChapter( + id = generateUid(chapterId.toLong()), + name = jo.getString("name"), + number = i + 1, + scanlator = null, + url = "/read/$mangaId/$chapterId", + uploadDate = df.tryParse(jo.getString("date")), + branch = null, + source = source, + ) + }, + ) + } + override suspend fun getPages(chapter: MangaChapter): List { val jsonData = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml() .requireElementById("__NEXT_DATA__") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vmp/VmpParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vmp/VmpParser.kt index 5cbd5844..69b9c256 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vmp/VmpParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/vmp/VmpParser.kt @@ -16,8 +16,11 @@ internal abstract class VmpParser( ) : PagedMangaParser(context, source, pageSize) { override val configKeyDomain = ConfigKey.Domain(domain) + override val availableSortOrders: Set = EnumSet.of(SortOrder.UPDATED) + override val isMultipleTagsSupported = false + protected open val listUrl = "xxx/" protected open val geneUrl = "genero/" @@ -26,27 +29,42 @@ internal abstract class VmpParser( searchPaginator.firstPage = 1 } - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + val url = buildString { - append("https://$domain/") - if (!tags.isNullOrEmpty()) { - append(geneUrl) - append(tag?.key.orEmpty()) - append("/page/") - append(page.toString()) - } else { - append(listUrl) - append("/page/") - append(page.toString()) - if (!query.isNullOrEmpty()) { + append("https://") + append(domain) + append('/') + when (filter) { + + is MangaListFilter.Search -> { + append(listUrl) + append("/page/") + append(page.toString()) append("?s=") - append(query.urlEncoded()) + append(filter.query.urlEncoded()) + } + + is MangaListFilter.Advanced -> { + + if (filter.tags.isNotEmpty()) { + filter.tags.oneOrThrowIfMany()?.let { + append(geneUrl) + append(it.key) + append("/page/") + append(page.toString()) + } + } else { + append(listUrl) + append("/page/") + append(page.toString()) + } + } + + null -> { + append(listUrl) + append("/page/") + append(page.toString()) } } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/WpComicsParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/WpComicsParser.kt index 952f365f..b6c3c5b4 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/WpComicsParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/WpComicsParser.kt @@ -1,11 +1,16 @@ package org.koitharu.kotatsu.parsers.site.wpcomics +import androidx.collection.ArrayMap +import androidx.collection.ArraySet import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import org.jsoup.nodes.Document import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.PagedMangaParser import org.koitharu.kotatsu.parsers.config.ConfigKey +import org.koitharu.kotatsu.parsers.exception.NotFoundException import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.util.* import java.text.DateFormat @@ -21,15 +26,16 @@ internal abstract class WpComicsParser( override val configKeyDomain = ConfigKey.Domain(domain) - override val isMultipleTagsSupported = false - override val availableSortOrders: Set = EnumSet.of( SortOrder.UPDATED, SortOrder.NEWEST, SortOrder.POPULARITY, + SortOrder.RATING, ) - protected open val listUrl = "/the-loai" + override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) + + protected open val listUrl = "/tim-truyen-nang-cao" protected open val datePattern = "dd/MM/yy" @@ -48,75 +54,136 @@ internal abstract class WpComicsParser( @JvmField protected val finished: Set = setOf( "Hoàn thành", - "Completed ", + "Completed", ) - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() - val url = buildString { - append("https://") - append(domain) - append(listUrl) - - if (!tags.isNullOrEmpty()) { - append("/") - append(tag?.key.orEmpty()) - } + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { + val response = + when (filter) { + is MangaListFilter.Search -> { + val url = buildString { + append("https://") + append(domain) + append("/tim-truyen?keyword=") + append(filter.query.urlEncoded()) + append("&page=") + append(page.toString()) + } + + val result = runCatchingCancellable { webClient.httpGet(url) } + val exception = result.exceptionOrNull() + if (exception is NotFoundException) { + return emptyList() + } + result.getOrThrow() + } - append("?page=") - append(page.toString()) + is MangaListFilter.Advanced -> { + val url = buildString { + append("https://") + append(domain) + val tagQuery = filter.tags.joinToString(",") { it.key } + append("/tim-truyen-nang-cao?genres=") + append(tagQuery) + append("¬genres=&gender=-1&minchapter=1&sort=") + append( + when (filter.sortOrder) { + SortOrder.UPDATED -> 0 + SortOrder.POPULARITY -> 10 + SortOrder.NEWEST -> 15 + SortOrder.RATING -> 20 + else -> throw IllegalArgumentException("Sort order ${filter.sortOrder.name} not supported") + }, + ) + filter.states.oneOrThrowIfMany()?.let { + append("&status=") + append( + when (it) { + MangaState.ONGOING -> "1" + MangaState.FINISHED -> "2" + else -> "-1" + }, + ) + } + append("&page=") + append(page.toString()) + } + + webClient.httpGet(url) + } - if (!query.isNullOrEmpty()) { - append("&keyword=") - append(query.urlEncoded()) + null -> { + val url = buildString { + append("https://") + append(domain) + append("/tim-truyen-nang-cao?genres=¬genres=&gender=-1&status=-1&minchapter=1&sort=0&page=") + append(page.toString()) + } + webClient.httpGet(url) + } } - - append("&sort=") - when (sortOrder) { - SortOrder.POPULARITY -> append("10") - SortOrder.UPDATED -> append("") - SortOrder.NEWEST -> append("15") - else -> append("") + val itemsElements = response.parseHtml() + .select("div.ModuleContent > div.items") + .select("div.item") + return itemsElements.mapNotNull { item -> + val tooltipElement = item.selectFirst("div.box_tootip") ?: return@mapNotNull null + val absUrl = item.selectFirst("div.image > a")?.attrAsAbsoluteUrlOrNull("href") ?: return@mapNotNull null + val slug = absUrl.substringAfterLast('/') + val mangaState = when (tooltipElement.selectFirst("div.message_main > p:contains(Tình trạng)")?.ownText()) { + "Đang tiến hành" -> MangaState.ONGOING + "Hoàn thành" -> MangaState.FINISHED + else -> null } - } - val doc = webClient.httpGet(url).parseHtml() - return doc.select("div.item").map { div -> - val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") + val tagMap = getOrCreateTagMap() + val tagsElement = tooltipElement.selectFirst("div.message_main > p:contains(Thể loại)")?.ownText().orEmpty() + val mangaTags = tagsElement.split(',').mapNotNullToSet { tagMap[it.trim()] } Manga( - id = generateUid(href), - url = href, - publicUrl = href.toAbsoluteUrl(div.host ?: domain), - coverUrl = div.selectFirst("img")?.src().orEmpty(), - title = div.selectFirstOrThrow("h3").text().orEmpty(), + id = generateUid(slug), + title = tooltipElement.selectFirst("div.title")?.text().orEmpty(), altTitle = null, + url = absUrl.toRelativeUrl(domain), + publicUrl = absUrl, rating = RATING_UNKNOWN, - tags = emptySet(), - author = null, - state = null, + isNsfw = false, + coverUrl = item.selectFirst("div.image a img")?.absUrl("data-original").orEmpty(), + largeCoverUrl = null, + tags = mangaTags, + state = mangaState, + author = tooltipElement.selectFirst("div.message_main > p:contains(Tác giả)")?.ownText(), + description = tooltipElement.selectFirst("div.box_text")?.text(), + chapters = null, source = source, - isNsfw = isNsfwSource, ) } } override suspend fun getAvailableTags(): Set { - val doc = webClient.httpGet("https://$domain$listUrl").parseHtml() - return doc.select("div.genres ul li:not(.active)").mapNotNullToSet { li -> - val a = li.selectFirst("a") ?: return@mapNotNullToSet null - val href = a.attr("href").removeSuffix('/').substringAfterLast('/') - MangaTag( - key = href, - title = a.text(), - source = source, - ) + val map = getOrCreateTagMap() + val tagSet = ArraySet(map.size) + for (entry in map) { + tagSet.add(entry.value) } + return tagSet + } + + + private val mutex = Mutex() + private var tagCache: ArrayMap? = null + + private suspend fun getOrCreateTagMap(): ArrayMap = mutex.withLock { + tagCache?.let { return@withLock it } + val doc = webClient.httpGet("/tim-truyen-nang-cao".toAbsoluteUrl(domain)).parseHtml() + val tagItems = doc.select("div.genre-item") + val result = ArrayMap(tagItems.size) + for (item in tagItems) { + val title = item.text().trim() + val key = item.select("span[data-id]").attr("data-id") + result[title] = MangaTag(title = title, key = key, source = source) + } + tagCache = result + result } protected open val selectDesc = "div.detail-content p" @@ -127,13 +194,9 @@ internal abstract class WpComicsParser( 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.selectFirst(selectState) - val state = stateDiv?.let { when (it.text()) { in ongoing -> MangaState.ONGOING @@ -141,17 +204,8 @@ internal abstract class WpComicsParser( else -> null } } - val aut = doc.body().select(selectAut).text() - manga.copy( - tags = doc.body().select(selectTag).mapNotNullToSet { a -> - MangaTag( - key = a.attr("href").removeSuffix('/').substringAfterLast('/'), - title = a.text().toTitleCase(), - source = source, - ) - }, description = desc, altTitle = null, author = aut, @@ -165,19 +219,16 @@ internal abstract class WpComicsParser( protected open val selectChapter = "div#nt_listchapter li:not(.heading)" protected open suspend fun getChapters(doc: Document): List { - return doc.body().select(selectChapter).mapChapters(reversed = true) { i, li -> val a = li.selectFirstOrThrow("a") val href = a.attrAsRelativeUrl("href") val dateText = li.selectFirst(selectDate)?.text() - val findHours = dateText?.contains(":") val dateFormat = if (findHours == true) { SimpleDateFormat("HH:mm dd/MM", sourceLocale) } else { SimpleDateFormat(datePattern, sourceLocale) } - MangaChapter( id = generateUid(href), name = a.text(), @@ -295,5 +346,4 @@ internal abstract class WpComicsParser( else -> 0 } } - } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/en/XoxoComics.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/en/XoxoComics.kt index 581cacfa..047b3cbf 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/en/XoxoComics.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/en/XoxoComics.kt @@ -1,5 +1,7 @@ package org.koitharu.kotatsu.parsers.site.wpcomics.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.model.* @@ -9,11 +11,13 @@ import java.util.EnumSet @MangaSourceParser("XOXOCOMICS", "XoxoComics", "en", ContentType.COMICS) internal class XoxoComics(context: MangaLoaderContext) : - WpComicsParser(context, MangaSource.XOXOCOMICS, "xoxocomics.net", 50) { + WpComicsParser(context, MangaSource.XOXOCOMICS, "xoxocomic.com", 50) { - override val listUrl = "/genre" + override val listUrl = "/comic-list" override val datePattern = "MM/dd/yyyy" + override val isMultipleTagsSupported = false + override val availableSortOrders: Set = EnumSet.of( SortOrder.UPDATED, SortOrder.NEWEST, @@ -21,46 +25,66 @@ internal class XoxoComics(context: MangaLoaderContext) : SortOrder.ALPHABETICAL, ) - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { - val tag = tags.oneOrThrowIfMany() + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { val url = buildString { append("https://") append(domain) + when (filter) { - if (!query.isNullOrEmpty()) { - append("/search?keyword=") - append(query.urlEncoded()) - append("&page=") - append(page.toString()) - } else { - append(listUrl) - if (!tags.isNullOrEmpty()) { - append("/") - append(tag?.key.orEmpty()) + is MangaListFilter.Search -> { + append("/search-comic?keyword=") + append(filter.query.urlEncoded()) + append("&page=") + append(page.toString()) } - append("/") - when (sortOrder) { - SortOrder.POPULARITY -> append("popular") - SortOrder.UPDATED -> append("") - SortOrder.NEWEST -> append("newest") - SortOrder.ALPHABETICAL -> append("alphabet") - else -> append("") - } + is MangaListFilter.Advanced -> { + + if (filter.tags.isNotEmpty()) { + filter.tags.oneOrThrowIfMany()?.let { + append("/") + append(it.key) + } + } + + filter.states.oneOrThrowIfMany()?.let { + append( + when (it) { + MangaState.ONGOING -> "/ongoing" + MangaState.FINISHED -> "/completed" + else -> "" + }, + ) + if (filter.tags.isEmpty()) { + append("-comic") + } + } + + if (filter.states.isEmpty() && filter.tags.isEmpty()) { + append(listUrl) + } - append("?page=") - append(page.toString()) + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("/popular") + SortOrder.UPDATED -> append("/latest") + SortOrder.NEWEST -> append("/newest") + SortOrder.ALPHABETICAL -> append("") + else -> append("/latest") + } + append("?page=") + append(page.toString()) + } + null -> { + append(listUrl) + append("/?page=") + append(page.toString()) + } } } val doc = webClient.httpGet(url).parseHtml() - return doc.select("div.item").map { div -> + return doc.select("div.item, #nt_listchapter nav ul li").map { div -> val href = div.selectFirstOrThrow("a").attrAsRelativeUrl("href") Manga( id = generateUid(href), @@ -79,12 +103,53 @@ internal class XoxoComics(context: MangaLoaderContext) : } } + override suspend fun getAvailableTags(): Set { + val doc = webClient.httpGet("https://$domain$listUrl").parseHtml() + return doc.select("div.genres ul li:not(.active)").mapNotNullToSet { li -> + val a = li.selectFirst("a") ?: return@mapNotNullToSet null + val href = a.attr("href").removeSuffix('/').substringAfterLast('/') + MangaTag( + key = href, + title = a.text(), + source = source, + ) + } + } - override suspend fun getPages(chapter: MangaChapter): List { - val fullUrl = chapter.url.toAbsoluteUrl(domain) + "/all" + 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.selectFirst(selectState) + val state = stateDiv?.let { + when (it.text()) { + in ongoing -> MangaState.ONGOING + in finished -> MangaState.FINISHED + else -> null + } + } + val aut = doc.body().select(selectAut).text() + manga.copy( + tags = doc.body().select(selectTag).mapNotNullToSet { a -> + MangaTag( + key = a.attr("href").removeSuffix('/').substringAfterLast('/'), + title = a.text().toTitleCase(), + source = source, + ) + }, + description = desc, + altTitle = null, + author = aut, + state = state, + chapters = chaptersDeferred.await(), + ) + } + override suspend fun getPages(chapter: MangaChapter): List { + val fullUrl = chapter.url.toAbsoluteUrl(domain) + "/all" + val doc = webClient.httpGet(fullUrl).parseHtml() return doc.select(selectPage).map { url -> val img = url.src()?.toRelativeUrl(domain) ?: url.parseFailed("Image src not found") MangaPage( diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyen.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyen.kt new file mode 100644 index 00000000..81691cf7 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/NetTruyen.kt @@ -0,0 +1,19 @@ +package org.koitharu.kotatsu.parsers.site.wpcomics.vi + +import org.koitharu.kotatsu.parsers.MangaLoaderContext +import org.koitharu.kotatsu.parsers.MangaSourceParser +import org.koitharu.kotatsu.parsers.config.ConfigKey +import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser + +@MangaSourceParser("NETTRUYEN", "NetTruyen", "vi") +internal class NetTruyen(context: MangaLoaderContext) : + WpComicsParser(context, MangaSource.NETTRUYEN, "www.nettruyenlive.com", 36) { + override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain( + "www.nettruyenlive.com", + "www.nettruyenio.com", + "www.nettruyento.com", + "nettruyento.com", + "nettruyenin.com", + ) +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/Nettruyenmax.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/Nettruyenmax.kt index 37868bde..070093fb 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/Nettruyenmax.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/wpcomics/vi/Nettruyenmax.kt @@ -7,6 +7,4 @@ import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser @MangaSourceParser("NETTRUYENMAX", "NettruyenMax", "vi") internal class Nettruyenmax(context: MangaLoaderContext) : - WpComicsParser(context, MangaSource.NETTRUYENMAX, "www.nettruyenus.com", 36) { - override val listUrl = "/tim-truyen" -} + WpComicsParser(context, MangaSource.NETTRUYENMAX, "www.nettruyenus.com", 36) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/ZMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/ZMangaParser.kt index 486f0b25..6c928a53 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/ZMangaParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/zmanga/ZMangaParser.kt @@ -21,13 +21,9 @@ internal abstract class ZMangaParser( override val configKeyDomain = ConfigKey.Domain(domain) - override val availableSortOrders: Set = EnumSet.of( - SortOrder.UPDATED, - SortOrder.POPULARITY, - SortOrder.ALPHABETICAL, - SortOrder.NEWEST, - SortOrder.RATING, - ) + override val availableSortOrders: Set = EnumSet.allOf(SortOrder::class.java) + + override val availableStates: Set = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED) protected open val listUrl = "advanced-search/" protected open val datePattern = "MMMM d, yyyy" @@ -50,42 +46,56 @@ internal abstract class ZMangaParser( "Completed", ) - override suspend fun getListPage( - page: Int, - query: String?, - tags: Set?, - sortOrder: SortOrder, - ): List { + override suspend fun getListPage(page: Int, filter: MangaListFilter?): List { val url = buildString { append("https://") append(domain) - append("/$listUrl") + append('/') + append(listUrl) if (page > 1) { append("page/") append(page.toString()) - append("/") + append('/') } - append("?order=") - when (sortOrder) { - SortOrder.POPULARITY -> append("popular") - SortOrder.UPDATED -> append("update") - SortOrder.ALPHABETICAL -> append("title") - SortOrder.NEWEST -> append("latest") - SortOrder.RATING -> append("rating") - } - if (!query.isNullOrEmpty()) { - append("&title=") - append(query.urlEncoded()) - } + when (filter) { - if (!tags.isNullOrEmpty()) { - for (tag in tags) { - append("&") - append("genre[]".urlEncoded()) - append("=") - append(tag.key) + is MangaListFilter.Search -> { + append("&title=") + append(filter.query.urlEncoded()) } + + is MangaListFilter.Advanced -> { + + append("?order=") + when (filter.sortOrder) { + SortOrder.POPULARITY -> append("popular") + SortOrder.UPDATED -> append("update") + SortOrder.ALPHABETICAL -> append("title") + SortOrder.NEWEST -> append("latest") + SortOrder.RATING -> append("rating") + } + + 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 -> "" + }, + ) + } + } + + null -> append("?order=update") } } From 65c567dbcb328470f586596357298607e23064b6 Mon Sep 17 00:00:00 2001 From: devi Date: Mon, 4 Dec 2023 18:16:56 +0100 Subject: [PATCH 18/18] Fix NHentaiParser --- .../kotatsu/parsers/site/galleryadults/all/NHentaiParser.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/NHentaiParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/NHentaiParser.kt index b6cac6b5..c7ecb9ab 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/NHentaiParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/galleryadults/all/NHentaiParser.kt @@ -39,7 +39,7 @@ internal class NHentaiParser(context: MangaLoaderContext) : when (filter) { is MangaListFilter.Search -> { - append("/search/?q=") + append("/search/?q=pages:>0 ") append(filter.query.urlEncoded()) append("&") }