From 2061a971b85b8cdc7f165e06d3c5afff8346526b Mon Sep 17 00:00:00 2001 From: devi Date: Tue, 24 Sep 2024 19:35:22 +0200 Subject: [PATCH] [fmreader] add SearchWithFilters [LegacyScans] add ContentTypes, SortOrder.UPDATED [MangaMana] add SortOrder.RATING_ASC , SortOrder.NEWEST_ASC [Manhwa18.com] move to /en Add new option on .toAbsoluteUrl() --- .../kotatsu/parsers/site/en/Manhwa18Com.kt | 245 ++++++++++++++++++ .../parsers/site/fmreader/FmreaderParser.kt | 109 ++++---- .../parsers/site/fmreader/en/Manhwa18Com.kt | 140 ---------- .../parsers/site/fmreader/es/OlimpoScans.kt | 82 ------ .../kotatsu/parsers/site/fmreader/ja/Klz9.kt | 9 +- .../parsers/site/foolslide/FoolSlideParser.kt | 22 +- .../site/foolslide/es/Pzykosis666hFansub.kt | 14 +- .../site/foolslide/es/SeinagiAdulto.kt | 12 +- .../parsers/site/fr/BentomangaParser.kt | 81 +++--- .../kotatsu/parsers/site/fr/FuryoSociety.kt | 23 +- .../parsers/site/fr/LegacyScansParser.kt | 111 +++++--- .../kotatsu/parsers/site/fr/LugnicaScans.kt | 18 +- .../kotatsu/parsers/site/fr/MangaKawaii.kt | 20 +- .../kotatsu/parsers/site/fr/MangaMana.kt | 30 ++- .../kotatsu/parsers/site/fr/ScansMangasMe.kt | 2 + .../kotatsu/parsers/site/fr/ScantradUnion.kt | 24 +- .../parsers/site/ru/grouple/GroupleParser.kt | 2 +- .../koitharu/kotatsu/parsers/util/Parse.kt | 3 +- 18 files changed, 510 insertions(+), 437 deletions(-) create mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Com.kt delete mode 100644 src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/en/Manhwa18Com.kt diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Com.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Com.kt new file mode 100644 index 00000000..eb1bf4df --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/Manhwa18Com.kt @@ -0,0 +1,245 @@ +package org.koitharu.kotatsu.parsers.site.en + +import androidx.collection.ArrayMap +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.util.* + +@MangaSourceParser("MANHWA18COM", "Manhwa18.com", "en", type = ContentType.HENTAI) +internal class Manhwa18Com(context: MangaLoaderContext) : + PagedMangaParser(context, MangaParserSource.MANHWA18COM, pageSize = 18, searchPageSize = 18) { + + override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain("manhwa18.com") + + override fun onCreateConfig(keys: MutableCollection>) { + super.onCreateConfig(keys) + keys.add(userAgentKey) + } + + override val availableSortOrders: Set + get() = EnumSet.of( + SortOrder.UPDATED, + SortOrder.POPULARITY, + SortOrder.ALPHABETICAL, + SortOrder.NEWEST, + SortOrder.RATING, + ) + + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isMultipleTagsSupported = true, + isTagsExclusionSupported = true, + isSearchSupported = true, + isSearchWithFiltersSupported = true, + ) + + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = tagsMap.get().values.toSet(), + availableStates = EnumSet.of( + MangaState.ONGOING, + MangaState.FINISHED, + MangaState.PAUSED, + ), + ) + + override suspend fun getFavicons(): Favicons { + return Favicons( + listOf( + Favicon("https://$domain/uploads/logos/logo-mini.png", 92, null), + ), + domain, + ) + } + + override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { + val url = buildString { + append("https://") + append(domain) + append("/tim-kiem?page=") + append(page.toString()) + + filter.query?.let { + append("&q=") + append(filter.query.urlEncoded()) + } + + append("&accept_genres=") + if (filter.tags.isNotEmpty()) { + append( + filter.tags.joinToString(",") { it.key }, + ) + } + + append("&reject_genres=") + if (filter.tagsExclude.isNotEmpty()) { + append( + filter.tagsExclude.joinToString(",") { it.key }, + ) + } + + append("&sort=") + append( + when (order) { + SortOrder.ALPHABETICAL -> "az" + SortOrder.ALPHABETICAL_DESC -> "za" + SortOrder.POPULARITY -> "top" + SortOrder.UPDATED -> "update" + SortOrder.NEWEST -> "new" + SortOrder.RATING -> "like" + else -> "update" + }, + ) + + filter.states.oneOrThrowIfMany()?.let { + append("&status=") + append( + when (it) { + MangaState.ONGOING -> "1" + MangaState.FINISHED -> "3" + MangaState.PAUSED -> "2" + else -> "" + }, + ) + } + + // Support author + // filter.author.let{ + // the + // append("&artist=") + // append(filter.author) + // } + + } + + 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 = MangaParserSource.MANHWA18, + ) + } + } + + override suspend fun getDetails(manga: Manga): Manga { + val docs = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml() + val cardInfoElement = docs.selectFirst("div.series-information") + val author = cardInfoElement?.selectFirst(".info-name:contains(Author)")?.parent() + ?.select("a") + ?.joinToString(", ") { it.text() } + val availableTags = tagsMap.get() + val tags = cardInfoElement?.selectFirst(".info-name:contains(Genre)")?.parent() + ?.select("a") + ?.mapNotNullToSet { availableTags[it.text().lowercase(Locale.ENGLISH)] } + val state = cardInfoElement?.selectFirst(".info-name:contains(Status)")?.parent() + ?.selectFirst("a") + ?.let { + when (it.text().lowercase()) { + "on going" -> MangaState.ONGOING + "completed" -> MangaState.FINISHED + "on hold" -> MangaState.PAUSED + else -> null + } + } + + return manga.copy( + altTitle = cardInfoElement?.selectFirst("b:contains(Other names)")?.parent()?.ownText()?.removePrefix(": "), + author = author, + description = docs.selectFirst(".series-summary .summary-content")?.html(), + tags = tags.orEmpty(), + state = state, + chapters = docs.select(".card-body > .list-chapters > a").mapChapters(reversed = true) { index, element -> + val chapterUrl = element.attrAsAbsoluteUrlOrNull("href")?.toRelativeUrl(domain) + ?: return@mapChapters null + val uploadDate = parseUploadDate(element.selectFirst(".chapter-time")?.text()) + MangaChapter( + id = generateUid(chapterUrl), + name = element.selectFirst(".chapter-name")?.text().orEmpty(), + number = index + 1f, + volume = 0, + url = chapterUrl, + scanlator = null, + uploadDate = uploadDate, + branch = null, + source = MangaParserSource.MANHWA18, + ) + }, + ) + } + + private fun parseUploadDate(timeStr: String?): Long { + timeStr ?: return 0 + val timeWords = timeStr.split(' ') + if (timeWords.size != 3) return 0 + val timeWord = timeWords[1] + val timeAmount = timeWords[0].toIntOrNull() ?: return 0 + val timeUnit = when (timeWord) { + "minute", "minutes" -> Calendar.MINUTE + "hour", "hours" -> Calendar.HOUR + "day", "days" -> Calendar.DAY_OF_YEAR + "week", "weeks" -> Calendar.WEEK_OF_YEAR + "month", "months" -> Calendar.MONTH + "year", "years" -> Calendar.YEAR + else -> return 0 + } + val cal = Calendar.getInstance() + cal.add(timeUnit, -timeAmount) + return cal.time.time + } + + override suspend fun getPages(chapter: MangaChapter): List { + val chapterUrl = chapter.url.toAbsoluteUrl(domain) + val doc = webClient.httpGet(chapterUrl).parseHtml() + return doc.requireElementById("chapter-content").select("img").mapNotNull { + val url = it.attrAsRelativeUrlOrNull("data-src") + ?: it.attrAsRelativeUrlOrNull("src") + ?: return@mapNotNull null + MangaPage( + id = generateUid(url), + url = url, + preview = null, + source = MangaParserSource.MANHWA18, + ) + } + } + + private val tagsMap = SuspendLazy(::parseTags) + + private suspend fun parseTags(): Map { + val doc = webClient.httpGet("https://$domain/tim-kiem?q=").parseHtml() + val list = doc.getElementsByAttribute("data-genre-id") + if (list.isEmpty()) { + return emptyMap() + } + val result = ArrayMap(list.size) + for (item in list) { + val id = item.attr("data-genre-id") + val name = item.text() + result[name.lowercase(Locale.ENGLISH)] = MangaTag( + title = name.toTitleCase(Locale.ENGLISH), + key = id, + source = source, + ) + } + return result + } +} 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 834700e3..2f256e7d 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 @@ -35,17 +35,23 @@ internal abstract class FmreaderParser( SortOrder.ALPHABETICAL_DESC, ) - protected open val listUrl = "/manga-list.html" - protected open val datePattern = "MMMM d, yyyy" - protected open val tagPrefix = "manga-list-genre-" - override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( + isSearchSupported = true, + isSearchWithFiltersSupported = true, isMultipleTagsSupported = true, isTagsExclusionSupported = true, - isSearchSupported = true, ) + override suspend fun getFilterOptions() = MangaListFilterOptions( + availableTags = fetchAvailableTags(), + availableStates = EnumSet.of( + MangaState.ONGOING, + MangaState.FINISHED, + MangaState.ABANDONED, + ), + ) + init { paginator.firstPage = 1 searchPaginator.firstPage = 1 @@ -71,14 +77,9 @@ internal abstract class FmreaderParser( "drop", ) - override suspend fun getFilterOptions() = MangaListFilterOptions( - availableTags = fetchAvailableTags(), - availableStates = EnumSet.of( - MangaState.ONGOING, - MangaState.FINISHED, - MangaState.ABANDONED, - ), - ) + protected open val listUrl = "/manga-list.html" + protected open val datePattern = "MMMM d, yyyy" + protected open val tagPrefix = "manga-list-genre-" override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { val url = buildString { @@ -87,45 +88,45 @@ internal abstract class FmreaderParser( append(listUrl) append("?page=") append(page.toString()) - when { - !filter.query.isNullOrEmpty() -> { - append("&name=") - append(filter.query.urlEncoded()) - } - else -> { - - append("&genre=") - append(filter.tags.joinToString(",") { it.key }) - - append("&ungenre=") - append(filter.tagsExclude.joinToString(",") { it.key }) - - - append("&sort=") - when (order) { - SortOrder.POPULARITY -> append("views&sort_type=DESC") - SortOrder.POPULARITY_ASC -> append("views&sort_type=ASC") - SortOrder.UPDATED -> append("last_update&sort_type=DESC") - SortOrder.UPDATED_ASC -> append("last_update&sort_type=ASC") - SortOrder.ALPHABETICAL -> append("name&sort_type=ASC") - SortOrder.ALPHABETICAL_DESC -> append("name&sort_type=DESC") - else -> append("last_update&sort_type=DESC") - } - - append("&m_status=") - filter.states.oneOrThrowIfMany()?.let { - append( - when (it) { - MangaState.ONGOING -> "2" - MangaState.FINISHED -> "1" - MangaState.ABANDONED -> "3" - else -> "" - }, - ) - } + filter.query?.let { + append("&name=") + append(filter.query.urlEncoded()) + } - } + // filter.author?.let { + // append("&author=") + // append(filter.author.urlEncoded()) + // } + + append("&genre=") + append(filter.tags.joinToString(",") { it.key }) + + append("&ungenre=") + append(filter.tagsExclude.joinToString(",") { it.key }) + + + append("&sort=") + when (order) { + SortOrder.POPULARITY -> append("views&sort_type=DESC") + SortOrder.POPULARITY_ASC -> append("views&sort_type=ASC") + SortOrder.UPDATED -> append("last_update&sort_type=DESC") + SortOrder.UPDATED_ASC -> append("last_update&sort_type=ASC") + SortOrder.ALPHABETICAL -> append("name&sort_type=ASC") + SortOrder.ALPHABETICAL_DESC -> append("name&sort_type=DESC") + else -> append("last_update&sort_type=DESC") + } + + append("&m_status=") + filter.states.oneOrThrowIfMany()?.let { + append( + when (it) { + MangaState.ONGOING -> "2" + MangaState.FINISHED -> "1" + MangaState.ABANDONED -> "3" + else -> "" + }, + ) } } return parseMangaList(webClient.httpGet(url).parseHtml()) @@ -139,10 +140,10 @@ internal abstract class FmreaderParser( 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("(") - .substringBefore(")"), - title = div.selectFirstOrThrow("div.series-title").text().orEmpty(), + coverUrl = (div.selectFirst("div.img-in-ratio")?.attr("data-bg") + ?: div.selectFirst("div.img-in-ratio")?.attr("style")?.substringAfter("(") + ?.substringBefore(")"))?.toAbsoluteUrl(domain).orEmpty(), + title = div.selectFirst("div.series-title")?.text().orEmpty(), altTitle = null, rating = RATING_UNKNOWN, tags = emptySet(), 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 deleted file mode 100644 index c3134873..00000000 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fmreader/en/Manhwa18Com.kt +++ /dev/null @@ -1,140 +0,0 @@ -package org.koitharu.kotatsu.parsers.site.fmreader.en - -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.* -import org.koitharu.kotatsu.parsers.site.fmreader.FmreaderParser -import org.koitharu.kotatsu.parsers.util.* -import java.text.SimpleDateFormat - -@MangaSourceParser("MANHWA18COM", "Manhwa18.com", "en", ContentType.HENTAI) -internal class Manhwa18Com(context: MangaLoaderContext) : - FmreaderParser(context, MangaParserSource.MANHWA18COM, "manhwa18.com") { - - override val listUrl = "/tim-kiem" - override val selectState = "div.info-item:contains(Status) span.info-value " - override val selectAlt = "div.info-item:contains(Other name) span.info-value " - 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.advanced-wrapper .genre_label" - - override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { - val url = buildString { - append("https://") - append(domain) - append("/tim-kiem?page=") - append(page.toString()) - - when { - !filter.query.isNullOrEmpty() -> { - append("&q=") - append(filter.query.urlEncoded()) - } - - else -> { - - append("&accept_genres=") - append(filter.tags.joinToString(",") { it.key }) - - append("&reject_genres=") - append(filter.tagsExclude.joinToString(",") { it.key }) - - append("&sort=") - append( - when (order) { - SortOrder.ALPHABETICAL -> "az" - SortOrder.ALPHABETICAL_DESC -> "za" - SortOrder.POPULARITY -> "top" - SortOrder.UPDATED -> "update" - SortOrder.NEWEST -> "new" - SortOrder.RATING -> "like" - else -> null - }, - ) - - filter.states.oneOrThrowIfMany()?.let { - append("&status=") - append( - when (it) { - MangaState.ONGOING -> "1" - MangaState.FINISHED -> "3" - MangaState.PAUSED -> "2" - else -> "" - }, - ) - } - } - } - } - return parseMangaList(webClient.httpGet(url).parseHtml()) - } - - override suspend fun fetchAvailableTags(): Set { - val doc = webClient.httpGet("https://$domain/$listUrl").parseHtml() - return doc.select(selectBodyTag).mapNotNullToSet { label -> - val key = label.attr("data-genre-id") - MangaTag( - key = key, - title = label.selectFirstOrThrow(".gerne-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 chaptersDeferred = async { getChapters(doc) } - val desc = doc.selectFirstOrThrow(selectDesc).html() - val stateDiv = doc.selectFirst(selectState) - val state = stateDiv?.let { - when (it.text().lowercase()) { - in ongoing -> MangaState.ONGOING - in finished -> MangaState.FINISHED - else -> null - } - } - val alt = doc.body().selectFirst(selectAlt)?.text()?.replace("Other name", "") - val auth = doc.body().selectFirst(selectAut)?.text() - manga.copy( - tags = doc.body().select(selectTag).mapNotNullToSet { a -> - MangaTag( - key = a.attr("href").substringAfter("manga-list-genre-").substringBeforeLast(".html"), - title = a.text().toTitleCase(), - source = source, - ) - }, - description = desc, - altTitle = alt, - author = auth, - state = state, - chapters = chaptersDeferred.await(), - ) - } - - override suspend fun getChapters(doc: Document): List { - val dateFormat = SimpleDateFormat(datePattern, sourceLocale) - return doc.body().select(selectChapter).mapChapters(reversed = true) { i, a -> - val href = a.attrAsRelativeUrl("href") - val dateText = a.selectFirst(selectDate)?.text()?.substringAfter("- ") - MangaChapter( - id = generateUid(href), - name = a.selectFirstOrThrow("div.chapter-name").text(), - number = i + 1f, - volume = 0, - url = href, - uploadDate = parseChapterDate( - dateFormat, - dateText, - ), - source = source, - scanlator = null, - branch = 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 547aed23..89109a0a 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 @@ -15,88 +15,6 @@ internal class OlimpoScans(context: MangaLoaderContext) : override val selectTag = "ul.manga-info li:contains(Género) a" override val tagPrefix = "lista-de-comics-genero-" - override val filterCapabilities: MangaListFilterCapabilities - get() = super.filterCapabilities.copy( - isMultipleTagsSupported = false, - isTagsExclusionSupported = false, - ) - - override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { - val url = buildString { - append("https://") - append(domain) - when { - !filter.query.isNullOrEmpty() -> { - append(listUrl) - append("?page=") - append(page.toString()) - append("&name=") - append(filter.query.urlEncoded()) - } - - else -> { - 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 (order) { - SortOrder.POPULARITY -> append("views&sort_type=DESC") - SortOrder.POPULARITY_ASC -> append("views&sort_type=ASC") - SortOrder.UPDATED -> append("last_update&sort_type=DESC") - SortOrder.UPDATED_ASC -> append("last_update&sort_type=ASC") - SortOrder.ALPHABETICAL -> append("name&sort_type=ASC") - SortOrder.ALPHABETICAL_DESC -> append("name&sort_type=DESC") - else -> append("last_update&sort_type=DESC") - } - } - - append("&m_status=") - filter.states.oneOrThrowIfMany()?.let { - append( - when (it) { - MangaState.ONGOING -> "2" - MangaState.FINISHED -> "1" - MangaState.ABANDONED -> "3" - else -> "" - }, - ) - } - } - } - } - val doc = webClient.httpGet(url).parseHtml() - val lastPage = - doc.selectLast(".pagination a")?.attr("href")?.substringAfterLast("page=")?.substringBeforeLast("&artist") - ?.toInt() ?: 1 - if (lastPage < page) { - return emptyList() - } - 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").toAbsoluteUrl(domain), - title = div.selectFirstOrThrow("div.series-title").text().orEmpty(), - altTitle = null, - rating = RATING_UNKNOWN, - tags = emptySet(), - author = null, - state = null, - source = source, - isNsfw = isNsfwSource, - ) - } - } - override suspend fun getPages(chapter: MangaChapter): List { val fullUrl = ("/" + chapter.url).toAbsoluteUrl(domain) val doc = webClient.httpGet(fullUrl).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 39892edb..eb5d3497 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 @@ -45,19 +45,18 @@ internal class Klz9(context: MangaLoaderContext) : private val chapterListSelector = "div#list-chapters p, table.table tr, .list-chapters > a" - private fun generateRandomStr(length: Int): String { - return (1..length).map { toPathCharacters.random() }.joinToString("") + private fun generateRandomStr(): String { + return (1..25).map { toPathCharacters.random() }.joinToString("") } private val toPathCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" override suspend fun getChapters(doc: Document): List { val slug = doc.selectFirstOrThrow("div.h0rating").attr("slug") - val xhrUrl = "https://$domain/${generateRandomStr(25)}.lstc".toHttpUrl().newBuilder() + val xhrUrl = "https://$domain/${generateRandomStr()}.lstc".toHttpUrl().newBuilder() .addQueryParameter("slug", slug) .build() - val docLoad = - webClient.httpGet(xhrUrl).parseHtml() + val docLoad = webClient.httpGet(xhrUrl).parseHtml() val dateFormat = SimpleDateFormat(datePattern, sourceLocale) return docLoad.body().select(chapterListSelector).mapChapters(reversed = true) { i, a -> 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 131401be..583a9561 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 @@ -66,7 +66,7 @@ internal abstract class FoolSlideParser( val url = buildString { append("https://") append(domain) - append("/") + append('/') append(listUrl) // For some sites that don't have enough manga and page 2 links to page 1 if (!pagination) { @@ -89,7 +89,7 @@ internal abstract class FoolSlideParser( url = href, publicUrl = href.toAbsoluteUrl(div.host ?: domain), coverUrl = div.selectFirst("img")?.src().orEmpty(),// in search no img - title = div.selectFirstOrThrow(".title a").text().orEmpty(), + title = div.selectFirst(".title a")?.text().orEmpty(), altTitle = null, rating = RATING_UNKNOWN, tags = emptySet(), @@ -113,21 +113,21 @@ internal abstract class FoolSlideParser( testAdultPage } val chapters = getChapters(doc) - val desc = if (doc.selectFirstOrThrow(selectInfo).html().contains("")) { - doc.selectFirstOrThrow(selectInfo).text().substringAfterLast(": ") + val desc = if (doc.selectFirst(selectInfo)?.html()?.contains("") == true) { + doc.selectFirst(selectInfo)?.text()?.substringAfterLast(": ") } else { - doc.selectFirstOrThrow(selectInfo).text() + doc.selectFirst(selectInfo)?.text() } - val author = if (doc.selectFirstOrThrow(selectInfo).html().contains("")) { - doc.selectFirstOrThrow(selectInfo).text().substringAfter(": ").substringBefore("Art") + val author = if (doc.selectFirst(selectInfo)?.html()?.contains("") == true) { + doc.selectFirst(selectInfo)?.text()?.substringAfter(": ")?.substringBefore("Art") } else { null } manga.copy( coverUrl = doc.selectFirst(".thumbnail img")?.src() ?: manga.coverUrl, - description = desc, + description = desc.orEmpty(), altTitle = null, - author = author, + author = author.orEmpty(), state = null, chapters = chapters, ) @@ -142,14 +142,14 @@ internal abstract class FoolSlideParser( return doc.body().select(selectChapter).mapChapters(reversed = true) { i, div -> val a = div.selectFirstOrThrow(".title a") val href = a.attrAsRelativeUrl("href") - val dateText = div.selectFirstOrThrow(selectDate).text().substringAfter(", ") + val dateText = div.selectFirst(selectDate)?.text()?.substringAfter(", ") MangaChapter( id = generateUid(href), name = a.text(), number = i + 1f, volume = 0, url = href, - uploadDate = if (div.selectFirstOrThrow(selectDate).text().contains(", ")) { + uploadDate = if (div.selectFirst(selectDate)?.text()?.contains(", ") == true) { dateFormat.tryParse(dateText) } else { 0 diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/es/Pzykosis666hFansub.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/es/Pzykosis666hFansub.kt index da9bc20f..ab208a12 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/es/Pzykosis666hFansub.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/es/Pzykosis666hFansub.kt @@ -22,21 +22,21 @@ internal class Pzykosis666hFansub(context: MangaLoaderContext) : testAdultPage } val chapters = getChapters(doc) - val desc = if (doc.selectFirstOrThrow(selectInfo).html().contains("Descripción")) { - doc.selectFirstOrThrow(selectInfo).text().substringAfter("Descripción: ").substringBefore("Lecturas") + val desc = if (doc.selectFirst(selectInfo)?.html()?.contains("Descripción") == true) { + doc.selectFirst(selectInfo)?.text()?.substringAfter("Descripción: ")?.substringBefore("Lecturas") } else { - doc.selectFirstOrThrow(selectInfo).text() + doc.selectFirst(selectInfo)?.text() } - val author = if (doc.selectFirstOrThrow(selectInfo).html().contains("Author")) { - doc.selectFirstOrThrow(selectInfo).text().substringAfter("Author: ").substringBefore("Art") + val author = if (doc.selectFirst(selectInfo)?.html()?.contains("Author") == true) { + doc.selectFirst(selectInfo)?.text()?.substringAfter("Author: ")?.substringBefore("Art") } else { null } manga.copy( coverUrl = doc.selectFirst(".thumbnail img")?.src() ?: manga.coverUrl, - description = desc, + description = desc.orEmpty(), altTitle = null, - author = author, + author = author.orEmpty(), state = null, chapters = chapters, ) diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/es/SeinagiAdulto.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/es/SeinagiAdulto.kt index bfa7741c..e9047c11 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/es/SeinagiAdulto.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/foolslide/es/SeinagiAdulto.kt @@ -23,20 +23,20 @@ internal class SeinagiAdulto(context: MangaLoaderContext) : } val chapters = getChapters(doc) val desc = if (doc.selectFirstOrThrow(selectInfo).html().contains("Descripción")) { - doc.selectFirstOrThrow(selectInfo).text().substringAfter("Descripción: ").substringBefore("Lecturas") + doc.selectFirst(selectInfo)?.text()?.substringAfter("Descripción: ")?.substringBefore("Lecturas") } else { - doc.selectFirstOrThrow(selectInfo).text() + doc.selectFirst(selectInfo)?.text() } - val author = if (doc.selectFirstOrThrow(selectInfo).html().contains("Author")) { - doc.selectFirstOrThrow(selectInfo).text().substringAfter("Author: ").substringBefore("Art") + val author = if (doc.selectFirst(selectInfo)?.html()?.contains("Author") == true) { + doc.selectFirst(selectInfo)?.text()?.substringAfter("Author: ")?.substringBefore("Art") } else { null } manga.copy( coverUrl = doc.selectFirst(".thumbnail img")?.src().orEmpty(),// for manga result on search - description = desc, + description = desc.orEmpty(), altTitle = null, - author = author, + author = author.orEmpty(), state = null, chapters = chapters, ) 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 cc571c15..5a8260a9 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 @@ -59,58 +59,53 @@ internal class BentomangaParser(context: MangaLoaderContext) : .host(domain) .addPathSegment("manga_list") .addQueryParameter("limit", page.toString()) - when { - !filter.query.isNullOrEmpty() -> { - url.addQueryParameter("search", filter.query) - } - else -> { + filter.query?.let { + url.addQueryParameter("search", filter.query) + } - when (order) { - SortOrder.UPDATED -> url.addQueryParameter("order_by", "update") - .addQueryParameter("order", "desc") + when (order) { + SortOrder.UPDATED -> url.addQueryParameter("order_by", "update") + .addQueryParameter("order", "desc") - SortOrder.POPULARITY -> url.addQueryParameter("order_by", "views") - .addQueryParameter("order", "desc") + SortOrder.POPULARITY -> url.addQueryParameter("order_by", "views") + .addQueryParameter("order", "desc") - SortOrder.RATING -> url.addQueryParameter("order_by", "top") - .addQueryParameter("order", "desc") + SortOrder.RATING -> url.addQueryParameter("order_by", "top") + .addQueryParameter("order", "desc") - SortOrder.NEWEST -> url.addQueryParameter("order_by", "create") - .addQueryParameter("order", "desc") + SortOrder.NEWEST -> url.addQueryParameter("order_by", "create") + .addQueryParameter("order", "desc") - SortOrder.ALPHABETICAL -> url.addQueryParameter("order_by", "name") - .addQueryParameter("order", "asc") + SortOrder.ALPHABETICAL -> url.addQueryParameter("order_by", "name") + .addQueryParameter("order", "asc") - SortOrder.ALPHABETICAL_DESC -> url.addQueryParameter("order_by", "name") - .addQueryParameter("order", "desc") + SortOrder.ALPHABETICAL_DESC -> url.addQueryParameter("order_by", "name") + .addQueryParameter("order", "desc") - else -> url.addQueryParameter("order_by", "update") - .addQueryParameter("order", "desc") - } - - if (filter.tags.isNotEmpty()) { - url.addQueryParameter("withCategories", filter.tags.joinToString(",") { it.key }) - } + else -> url.addQueryParameter("order_by", "update") + .addQueryParameter("order", "desc") + } - if (filter.tagsExclude.isNotEmpty()) { - url.addQueryParameter("withoutCategories", filter.tagsExclude.joinToString(",") { it.key }) - } + 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" - else -> "1" - }, - ) - } + if (filter.tagsExclude.isNotEmpty()) { + url.addQueryParameter("withoutCategories", filter.tagsExclude.joinToString(",") { it.key }) + } - } + filter.states.oneOrThrowIfMany()?.let { + url.addQueryParameter( + "state", + when (it) { + MangaState.ONGOING -> "1" + MangaState.FINISHED -> "2" + MangaState.PAUSED -> "3" + MangaState.ABANDONED -> "5" + else -> "1" + }, + ) } val root = webClient.httpGet(url.build()).parseHtml().requireElementById("mangas_content") return root.select(".manga[data-manga]").map { div -> @@ -118,7 +113,7 @@ internal class BentomangaParser(context: MangaLoaderContext) : val href = header.selectFirstOrThrow("a").attrAsRelativeUrl("href") Manga( id = generateUid(href), - title = div.selectFirstOrThrow("h1").text(), + title = div.selectFirst("h1")?.text().orEmpty(), altTitle = null, url = href, publicUrl = href.toAbsoluteUrl(domain), @@ -129,7 +124,7 @@ internal class BentomangaParser(context: MangaLoaderContext) : ?.div(10f) ?: RATING_UNKNOWN, isNsfw = div.selectFirst(".badge-adult_content") != null, - coverUrl = div.selectFirstOrThrow("img").src().assertNotNull("src").orEmpty(), + coverUrl = div.selectFirst("img")?.src().assertNotNull("src").orEmpty(), tags = div.selectFirst(".component-manga-categories") .assertNotNull("tags") ?.select("a") 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 dc6170a8..18d160b6 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 @@ -87,7 +87,7 @@ internal class FuryoSociety(context: MangaLoaderContext) : val doc = webClient.httpGet(fullUrl).parseHtml() val chaptersDeferred = getChapters(doc) manga.copy( - description = doc.selectFirstOrThrow("div.fs-comic-description").html(), + description = doc.selectFirst("div.fs-comic-description")?.html().orEmpty(), chapters = chaptersDeferred, isNsfw = doc.selectFirst(".adult-text") != null, ) @@ -98,10 +98,10 @@ internal class FuryoSociety(context: MangaLoaderContext) : val a = div.selectFirstOrThrow("div.title a") val href = a.attrAsRelativeUrl("href") val dateFormat = SimpleDateFormat("dd/MM/yyyy", sourceLocale) - val dateText = div.selectFirstOrThrow("div.meta_r").text().replace("Hier", "1 jour") + val dateText = div.selectFirst("div.meta_r")?.text()?.replace("Hier", "1 jour") MangaChapter( id = generateUid(href), - name = div.selectFirstOrThrow("div.title").text() + " : " + div.selectFirstOrThrow("div.name").text(), + name = div.selectFirst("div.title")?.text() + " : " + div.selectFirst("div.name")?.text(), number = i + 1f, volume = 0, url = href, @@ -133,13 +133,16 @@ internal class FuryoSociety(context: MangaLoaderContext) : private fun parseChapterDate(dateFormat: DateFormat, date: String?): Long { val d = date?.lowercase() ?: return 0 return when { - d.startsWith("il y a") || // Handle translated 'ago' in French. - d.endsWith(" an") || d.endsWith(" ans") || - d.endsWith(" mois") || - d.endsWith(" jour") || d.endsWith(" jours") || - d.endsWith(" heure") || d.endsWith(" heures") || - d.endsWith(" seconde") || d.endsWith(" secondes") || - d.endsWith(" minute") || d.endsWith(" minutes") -> parseRelativeDate(date) + + WordSet("il y a").startsWith(d) -> { + parseRelativeDate(d) + } + + WordSet( + " an", " mois", " jour", " jours", " heure", " heures", " minute", " minutes", " seconde", " secondes", + ).endsWith(d) -> { + parseRelativeDate(d) + } else -> dateFormat.tryParse(date) } 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 64513f07..f0729279 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,5 +1,6 @@ package org.koitharu.kotatsu.parsers.site.fr +import org.json.JSONArray import org.json.JSONObject import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser @@ -15,10 +16,15 @@ import java.util.* internal class LegacyScansParser(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.LEGACY_SCANS, 18) { - override val availableSortOrders: Set = EnumSet.of(SortOrder.POPULARITY) - override val configKeyDomain = ConfigKey.Domain("legacy-scans.com") + override fun onCreateConfig(keys: MutableCollection>) { + super.onCreateConfig(keys) + keys.add(userAgentKey) + } + + override val availableSortOrders: Set = EnumSet.of(SortOrder.POPULARITY, SortOrder.UPDATED) + override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( isMultipleTagsSupported = true, @@ -28,13 +34,14 @@ internal class LegacyScansParser(context: MangaLoaderContext) : override suspend fun getFilterOptions() = MangaListFilterOptions( availableTags = fetchAvailableTags(), availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED, MangaState.PAUSED), + availableContentTypes = EnumSet.of( + ContentType.MANGA, + ContentType.MANHWA, + ContentType.MANHUA, + ContentType.ONE_SHOT, + ), ) - override fun onCreateConfig(keys: MutableCollection>) { - super.onCreateConfig(keys) - keys.add(userAgentKey) - } - override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { val end = page * pageSize val start = end - (pageSize - 1) @@ -52,36 +59,74 @@ internal class LegacyScansParser(context: MangaLoaderContext) : } else -> { - val url = buildString { - append("https://api.") - append(domain) - append("/misc/comic/search/query?status=") - filter.states.oneOrThrowIfMany()?.let { - append( - when (it) { - MangaState.ONGOING -> "En cours" - MangaState.FINISHED -> "Terminé" - MangaState.ABANDONED -> "Abandonné" - MangaState.PAUSED -> "En pause" - else -> "" - }, - ) + + if (order == SortOrder.UPDATED) { + + if (filter.states.isNotEmpty() || filter.tags.isNotEmpty() || filter.types.isNotEmpty()) { + throw IllegalArgumentException("La recherche part mis à jour + des filtres n'est pas supporté par cette source.") + } + + val url = buildString { + append("https://api.") + append(domain) + append("/misc/comic/home/updates?start=") + append(start.toString()) + append("&end=") + append(end.toString()) } - append("&order=&genreNames=") - append(filter.tags.joinToString(",") { it.key }) - append("&type=&start=") - append(start) - append("&end=") - append(end) + + return parseMangaList(webClient.httpGet(url).parseJson().getJSONArray("results")) + + } else { + val url = buildString { + append("https://api.") + append(domain) + append("/misc/comic/search/query?status=") + filter.states.oneOrThrowIfMany()?.let { + append( + when (it) { + MangaState.ONGOING -> "En cours" + MangaState.FINISHED -> "Terminé" + MangaState.ABANDONED -> "Abandonné" + MangaState.PAUSED -> "En pause" + else -> "" + }, + ) + } + + append("&order=&genreNames=") + append(filter.tags.joinToString(",") { it.key }) + + append("&type=") + filter.types.forEach { + append( + when (it) { + ContentType.MANGA -> "Manga" + ContentType.MANHWA -> "Manhwa" + ContentType.MANHUA -> "Manhua" + ContentType.ONE_SHOT -> "One shot" + else -> "" + }, + ) + } + + append("&start=") + append(start.toString()) + append("&end=") + append(end.toString()) + } + + return parseMangaList(webClient.httpGet(url).parseJson().getJSONArray("comics")) } - return parseMangaList(webClient.httpGet(url).parseJson()) + + } } } - private fun parseMangaList(json: JSONObject): List { - return json.getJSONArray("comics").mapJSON { j -> + private fun parseMangaList(json: JSONArray): List { + return json.mapJSON { j -> val slug = j.getString("slug") val urlManga = "https://$domain/comics/$slug" Manga( @@ -134,17 +179,17 @@ internal class LegacyScansParser(context: MangaLoaderContext) : source = source, ) }, - coverUrl = root.selectFirstOrThrow("div.serieImg img").attr("src"), + coverUrl = root.selectFirst("div.serieImg img")?.attr("src").orEmpty(), author = root.select("div.serieAdd p:contains(Auteur:) strong").text(), description = root.selectFirst("div.serieDescription div")?.html(), chapters = root.select("div.chapterList a") .mapChapters(reversed = true) { i, a -> val href = a.attrAsRelativeUrl("href") - val name = a.selectFirstOrThrow("span").text() + val name = a.selectFirst("span")?.text() val dateText = a.selectLast("span")?.text() ?: "0" MangaChapter( id = generateUid(href), - name = name, + name = name ?: "Chapitre : ${i + 1f}", number = i + 1f, volume = 0, url = href, 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 445d1388..ee3b0b35 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 @@ -18,15 +18,20 @@ import java.util.* internal class LugnicaScans(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.LUGNICASCANS, 10) { + override val configKeyDomain = ConfigKey.Domain("lugnica-scans.com") + + override fun onCreateConfig(keys: MutableCollection>) { + super.onCreateConfig(keys) + keys.add(userAgentKey) + } + + override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP) + override val availableSortOrders: Set = EnumSet.of( SortOrder.ALPHABETICAL, SortOrder.UPDATED, ) - override val configKeyDomain = ConfigKey.Domain("lugnica-scans.com") - - override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP) - override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( isMultipleTagsSupported = true, @@ -36,11 +41,6 @@ internal class LugnicaScans(context: MangaLoaderContext) : availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.PAUSED, MangaState.ABANDONED), ) - override fun onCreateConfig(keys: MutableCollection>) { - super.onCreateConfig(keys) - keys.add(userAgentKey) - } - init { context.cookieJar.insertCookies( domain, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaKawaii.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaKawaii.kt index 8dda82ed..e57f58f0 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaKawaii.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaKawaii.kt @@ -14,25 +14,25 @@ import java.util.* @MangaSourceParser("MANGAKAWAII", "MangaKawaii Fr", "fr") internal class MangaKawaii(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGAKAWAII, 50) { - override val availableSortOrders: Set = - EnumSet.of(SortOrder.UPDATED, SortOrder.ALPHABETICAL) - override val configKeyDomain = ConfigKey.Domain("www.mangakawaii.io") + override fun onCreateConfig(keys: MutableCollection>) { + super.onCreateConfig(keys) + keys.add(userAgentKey) + } + override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( isSearchSupported = true, ) + override val availableSortOrders: Set = + EnumSet.of(SortOrder.UPDATED, SortOrder.ALPHABETICAL) + override suspend fun getFilterOptions() = MangaListFilterOptions( availableTags = fetchAvailableTags(), ) - override fun onCreateConfig(keys: MutableCollection>) { - super.onCreateConfig(keys) - keys.add(userAgentKey) - } - override fun getRequestHeaders(): Headers = Headers.Builder() .add("Accept-Language", "fr") .build() @@ -52,7 +52,7 @@ internal class MangaKawaii(context: MangaLoaderContext) : PagedMangaParser(conte else -> { if (order == SortOrder.UPDATED && filter.tags.isNotEmpty()) { - throw IllegalArgumentException("Filtrer part tag n'est pas disponible avec le tri pas mis à jour") + throw IllegalArgumentException("Filtrer par tag n'est pas avec le tri pas mis à jour") } if (order == SortOrder.ALPHABETICAL) { @@ -81,7 +81,7 @@ internal class MangaKawaii(context: MangaLoaderContext) : PagedMangaParser(conte url = href, publicUrl = href.toAbsoluteUrl(div.host ?: domain), coverUrl = (div.selectFirst("img")?.src() ?: a.attr("data-bg")).orEmpty(), - title = div.selectFirstOrThrow("h4, .media-thumbnail__name").text().orEmpty(), + title = div.selectFirst("h4, .media-thumbnail__name")?.text().orEmpty(), altTitle = null, rating = RATING_UNKNOWN, tags = emptySet(), diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaMana.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaMana.kt index 0e6b78c6..aeba33a8 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaMana.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/fr/MangaMana.kt @@ -23,20 +23,27 @@ import java.util.* @MangaSourceParser("MANGAMANA", "MangaMana", "fr") internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.MANGAMANA, 25) { + override val configKeyDomain = ConfigKey.Domain("www.manga-mana.com") + + override fun onCreateConfig(keys: MutableCollection>) { + super.onCreateConfig(keys) + keys.add(userAgentKey) + } + + override val filterCapabilities: MangaListFilterCapabilities + get() = MangaListFilterCapabilities( + isSearchSupported = true, + ) + override val availableSortOrders: Set = EnumSet.of( SortOrder.UPDATED, SortOrder.RATING, + SortOrder.RATING_ASC, SortOrder.ALPHABETICAL, SortOrder.ALPHABETICAL_DESC, SortOrder.NEWEST, - ) - - override val configKeyDomain = ConfigKey.Domain("www.manga-mana.com") - - override val filterCapabilities: MangaListFilterCapabilities - get() = MangaListFilterCapabilities( - isSearchSupported = true, + SortOrder.NEWEST_ASC, ) override suspend fun getFilterOptions() = MangaListFilterOptions( @@ -44,11 +51,6 @@ internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context availableStates = EnumSet.of(MangaState.ONGOING, MangaState.FINISHED, MangaState.ABANDONED), ) - override fun onCreateConfig(keys: MutableCollection>) { - super.onCreateConfig(keys) - keys.add(userAgentKey) - } - override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { val postData = buildString { append("page=") @@ -97,7 +99,7 @@ internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context if (order == SortOrder.UPDATED) { if (filter.tags.isNotEmpty() or filter.states.isNotEmpty()) { - throw IllegalArgumentException("Le filtrage par « tri par : mis à jour » avec les genres ou les statuts n'est pas pris en charge par cette source.") + throw IllegalArgumentException("Le filtrage par « tri par : mis à jour » avec d'autres n'est pas pris en charge par cette source.") } val doc = webClient.httpGet("https://$domain/?page=$page").parseHtml() @@ -146,7 +148,9 @@ internal class MangaMana(context: MangaLoaderContext) : PagedMangaParser(context append("&sort_by=") when (order) { SortOrder.RATING -> append("score&sort_dir=desc") + SortOrder.RATING_ASC -> append("score&sort_dir=asc") SortOrder.NEWEST -> append("updated_at&sort_dir=desc") + SortOrder.NEWEST_ASC -> append("updated_at&sort_dir=asc") SortOrder.ALPHABETICAL -> append("name&sort_dir=asc") SortOrder.ALPHABETICAL_DESC -> append("name&sort_dir=desc") else -> append("updated_at&sort_dir=desc") 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 2014ba87..e6573d9b 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 @@ -3,6 +3,7 @@ package org.koitharu.kotatsu.parsers.site.fr import kotlinx.coroutines.coroutineScope import org.json.JSONArray import org.jsoup.nodes.Document +import org.koitharu.kotatsu.parsers.Broken import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.SinglePageMangaParser @@ -12,6 +13,7 @@ import org.koitharu.kotatsu.parsers.network.UserAgents import org.koitharu.kotatsu.parsers.util.* import java.util.* +@Broken @MangaSourceParser("SCANS_MANGAS_ME", "ScansMangas.me", "fr") internal class ScansMangasMe(context: MangaLoaderContext) : SinglePageMangaParser(context, MangaParserSource.SCANS_MANGAS_ME) { 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 f9740c21..afbb9185 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 @@ -15,15 +15,20 @@ import java.util.* internal class ScantradUnion(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.SCANTRADUNION, 10) { + override val configKeyDomain = ConfigKey.Domain("scantrad-union.com") + + override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP) + + override fun onCreateConfig(keys: MutableCollection>) { + super.onCreateConfig(keys) + keys.add(userAgentKey) + } + override val availableSortOrders: Set = EnumSet.of( SortOrder.ALPHABETICAL, SortOrder.UPDATED, ) - override val configKeyDomain = ConfigKey.Domain("scantrad-union.com") - - override val userAgentKey = ConfigKey.UserAgent(UserAgents.CHROME_DESKTOP) - override val filterCapabilities: MangaListFilterCapabilities get() = MangaListFilterCapabilities( isSearchSupported = true, @@ -33,11 +38,6 @@ internal class ScantradUnion(context: MangaLoaderContext) : availableTags = fetchAvailableTags(), ) - override fun onCreateConfig(keys: MutableCollection>) { - super.onCreateConfig(keys) - keys.add(userAgentKey) - } - override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List { val url = buildString { append("https://") @@ -89,7 +89,7 @@ internal class ScantradUnion(context: MangaLoaderContext) : publicUrl = href.toAbsoluteUrl(domain), rating = RATING_UNKNOWN, isNsfw = false, - coverUrl = article.selectFirstOrThrow("img.attachment-thumbnail").attrAsAbsoluteUrl("src"), + coverUrl = article.selectFirst("img.attachment-thumbnail")?.attrAsAbsoluteUrl("src").orEmpty(), tags = setOf(), state = null, author = null, @@ -109,7 +109,7 @@ internal class ScantradUnion(context: MangaLoaderContext) : publicUrl = href.toAbsoluteUrl(domain), rating = RATING_UNKNOWN, isNsfw = false, - coverUrl = article.selectFirstOrThrow("img").attrAsAbsoluteUrl("src"), + coverUrl = article.selectFirst("img")?.attrAsAbsoluteUrl("src").orEmpty(), tags = setOf(), state = null, author = null, @@ -151,7 +151,7 @@ internal class ScantradUnion(context: MangaLoaderContext) : } else { "Chapter $i" } - val date = li.select(".name-chapter").first()!!.children().elementAt(2).text() + val date = li.select(".name-chapter").first()?.children()?.elementAt(2)?.text() MangaChapter( id = generateUid(href), name = name, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/grouple/GroupleParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/grouple/GroupleParser.kt index 4a70f6f6..4120f0f9 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/grouple/GroupleParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ru/grouple/GroupleParser.kt @@ -317,7 +317,7 @@ internal abstract class GroupleParser( SortOrder.UPDATED -> "updated" SortOrder.ADDED, SortOrder.NEWEST, - -> "created" + -> "created" SortOrder.RATING -> "votes" else -> "rate" diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/Parse.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/Parse.kt index b38be2da..cd8728e8 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/Parse.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/Parse.kt @@ -79,7 +79,8 @@ public fun String.toRelativeUrl(domain: String): String { public fun String.toAbsoluteUrl(domain: String): String = when { this.startsWith("//") -> "https:$this" this.startsWith('/') -> "https://$domain$this" - else -> this + this.startsWith("https://") -> this + else -> "https://$domain/$this" } public fun concatUrl(host: String, path: String): String {