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 c9da08f54..a453063a6 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 a453063a6..39c955026 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 9e5795188..74524ec73 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 74524ec73..30a4778df 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 7edcff84a..53e2b3619 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 1efb0be59..1bf55d38d 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 5547d042d..604514001 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 832748032..8e5ca1598 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 0a0e33349..fdca09636 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 1febb7994..cde6e581b 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 6464a2de5..c0a9278f2 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 01657f536..c5b561e3b 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 3520f9ff2..d17a41990 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 4e8d905c9..e3416f46b 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 44e53ff66..6277b7ced 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 b2ada7188..4e8bc2fe5 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 a4afe5176..b86bcbb8b 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 9f4d6186d..48b58636a 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 641b79f77..1ec9a6df3 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 36f06b33a..a4c1991f8 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 c69b31b16..6a67b7e24 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 5d9ef8438..4ef6b192a 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 7b029cd59..ae86a442e 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 845259d32..3c9e0e397 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 c5a89e1d4..28a02ff6f 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 0833785dd..667cc56af 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 cd7d64a1e..6e2da1898 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 625459f66..4912109e1 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 86f48685e..574cba241 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 95a121253..54d4e5452 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 74aec2203..4859e8414 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 a28056010..252c6fcba 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 cb073d18b..c5b6591d4 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 7d36dab39..e01e0060e 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 1b1b40e26..042beaef4 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 634dc9ecc..2200b6631 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 b231da154..000000000 --- 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 e9c22fe84..000000000 --- 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 81327a2d3..c1cf6a1e6 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 4cb4ed8fa..6aad32692 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 71f1d23ae..a5e6fe0ee 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 76f774530..27b1d5e92 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 8bb514caa..970bd688e 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 1659a2c58..bbd415ddf 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 1940df62c..ce48030f4 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 1413e7b10..ec17b58d5 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 595831eaa..02ba73882 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 3dbceae2b..7fa7a03d2 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 e3a86ef5d..676c55642 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 0b603f13e..2ed70663b 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 9d363040f..4ab0ee63a 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 f0b5211c7..70bf7e34e 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 957cab69e..4dbf8916a 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 0debd791c..bd069d28d 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 e400f8a86..39f6844d9 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 8a7044777..cc0a6906a 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 1f567e69a..5aee24f1c 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 2363c9949..047e2eaa3 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 cdb877439..3817021d8 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 6b3ca5314..593d4470f 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 7ad9c2791..3545a3773 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 15c285c2d..1e9e892ea 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 07f8dcc2f..b5c13aff1 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 9def242d8..5b69fd57f 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 65b653b59..ed39facfa 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 cb8a199ad..95407f771 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 8d4628eb1..0b83be262 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 dc5a76c7a..ed97854fe 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 8bcde87cd..02c108c07 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 078d5eaff..b6cac6b58 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 0a8b8403f..cc434d169 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 e196f8d9d..70bea32a3 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 e1700bbb0..64b7349ae 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 a6768be41..588e23f34 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 babd84fc4..69ef62209 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 73f68a4f0..8b81136be 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 17cff2ea0..06eafe332 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 10f3df590..8c359f1aa 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 2e795f130..0c7d3665f 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 031861c57..341b086bc 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 e08c1bd82..23a8331bd 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 a00a728d2..34aa29ea7 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 4dd027822..ef25c4b70 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 3ec81dc27..7703908d1 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 d7016dd53..71a72bc86 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 ee0872157..8c8101dc5 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 0954d6962..1c17d28d3 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 a4e6b1da7..23efaaba8 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 51eb5e02f..70241a79b 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 993246547..ec3c171ae 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 0531c0051..2c545e17f 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 c9f093460..3022f7d40 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 a28442d6c..33d402d66 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 1c17d28d3..f0f5383f0 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 eb82d9f98..f672d1c51 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 ae6863399..6ffc08513 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 c1eb518d7..48ffe06cf 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 ef1064cec..7d57f9a9b 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 06afc82ba..d8e2af53b 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 ac5a6929d..269919857 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 52526dc1b..f979bece2 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 10efd3fa7..9074f62c2 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 63fd834ff..989284dff 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 86385d294..9d025b624 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 5101e05a5..787f2bcb2 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 dff1955a1..f0a0bfa76 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 2845daec8..f175cbe1d 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 6ffc08513..1c9ba41c2 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 269919857..ff49525b5 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 33d402d66..861de4eb5 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 b4d6918c4..8d454c844 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 aee33c960..3ecd17adb 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 688b63bb2..f51e1d627 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 2f548ea3f..9a8e824f4 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 dd41b478e..c7f4a7351 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 55564083b..3dc110c3b 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 3af8d4acd..ebbf48ed3 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 d55cb8a30..8e3f0a6dd 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 381331bfd..5c686374f 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 1d16a585e..0d7323960 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 ef915b6f1..cb41dc3a5 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 33cbe5afb..80120a6d5 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 1bf55d38d..405afa56c 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 3ec7263ba..f5bee237b 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 54edb7a6e..5b3985588 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 6fdd9b0af..bd61e0aaf 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 db799ac04..000000000 --- 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 6341e2be6..256892d79 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 640a089b7..02fa5fc0a 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 1cdd0b6e2..336e7e887 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 5cbd58441..69b9c256e 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 952f365f7..b6c3c5b4b 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 581cacfaa..047b3cbf0 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 000000000..81691cf70 --- /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 37868bde4..070093fbc 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 486f0b25a..6c928a53f 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 b6cac6b58..c7ecb9ab6 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("&") }