diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt index 8bbca385..d1af2aa2 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/MangaParser.kt @@ -3,6 +3,7 @@ package org.koitharu.kotatsu.parsers import androidx.annotation.CallSuper import androidx.annotation.VisibleForTesting import okhttp3.Headers +import org.jsoup.nodes.Element import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.exception.ParseException import org.koitharu.kotatsu.parsers.model.* @@ -177,8 +178,8 @@ abstract class MangaParser @InternalParsersApi constructor(val source: MangaSour } @InternalParsersApi - protected fun parseFailed(message: String? = null): Nothing { - throw ParseException(message, null) + protected fun Element.parseFailed(message: String? = null): Nothing { + throw ParseException(message, ownerDocument()?.location() ?: baseUri(), null) } @InternalParsersApi diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/exception/ContentUnavailableException.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/exception/ContentUnavailableException.kt new file mode 100644 index 00000000..fba33577 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/exception/ContentUnavailableException.kt @@ -0,0 +1,3 @@ +package org.koitharu.kotatsu.parsers.exception + +class ContentUnavailableException(message: String) : RuntimeException(message) \ No newline at end of file diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/exception/ParseException.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/exception/ParseException.kt index c0415ac4..b2ad8a43 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/exception/ParseException.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/exception/ParseException.kt @@ -3,6 +3,7 @@ package org.koitharu.kotatsu.parsers.exception import org.koitharu.kotatsu.parsers.InternalParsersApi class ParseException @InternalParsersApi @JvmOverloads constructor( - message: String?, + val shortMessage: String?, + val url: String, cause: Throwable? = null, -) : RuntimeException(message, cause) \ No newline at end of file +) : RuntimeException("$shortMessage at $url", cause) \ No newline at end of file diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/BatoToParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/BatoToParser.kt index 058eb1c7..a366c1d1 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/BatoToParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/BatoToParser.kt @@ -8,6 +8,7 @@ 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.ParseException import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.util.* import java.nio.charset.StandardCharsets @@ -70,8 +71,8 @@ internal class BatoToParser(override val context: MangaLoaderContext) : PagedMan override suspend fun getDetails(manga: Manga): Manga { val root = context.httpGet(manga.url.toAbsoluteUrl(getDomain())).parseHtml() - .getElementById("mainer") ?: parseFailed("Cannot find root") - val details = root.selectFirst(".detail-set") ?: parseFailed("Cannot find detail-set") + .requireElementById("mainer") + val details = root.selectFirstOrThrow(".detail-set") val attrs = details.selectFirst(".attr-main")?.select(".attr-item")?.associate { it.child(0).text().trim() to it.child(1) }.orEmpty() @@ -113,11 +114,11 @@ internal class BatoToParser(override val context: MangaLoaderContext) : PagedMan } val images = JSONArray(scriptSrc.substring(start, end)) val batoPass = scriptSrc.substringBetweenFirst("batoPass =", ";")?.trim(' ', '"', '\n') - ?: parseFailed("Cannot find batoPass") + ?: script.parseFailed("Cannot find batoPass") val batoWord = scriptSrc.substringBetweenFirst("batoWord =", ";")?.trim(' ', '"', '\n') - ?: parseFailed("Cannot find batoWord") + ?: script.parseFailed("Cannot find batoWord") val password = context.evaluateJs(batoPass)?.removeSurrounding('"') - ?: parseFailed("Cannot evaluate batoPass") + ?: script.parseFailed("Cannot evaluate batoPass") val args = JSONArray(decryptAES(batoWord, password)) val result = ArrayList(images.length()) repeat(images.length()) { i -> @@ -132,13 +133,13 @@ internal class BatoToParser(override val context: MangaLoaderContext) : PagedMan } return result } - parseFailed("Cannot find images list") + throw ParseException("Cannot find images list", fullUrl) } override suspend fun getTags(): Set { val scripts = context.httpGet( "https://${getDomain()}/browse", - ).parseHtml().select("script") + ).parseHtml().selectOrThrow("script") for (script in scripts) { val genres = script.html().substringBetweenFirst("const _genres =", ";") ?: continue val jo = JSONObject(genres) @@ -153,7 +154,7 @@ internal class BatoToParser(override val context: MangaLoaderContext) : PagedMan } return result } - parseFailed("Cannot find gernes list") + throw ParseException("Cannot find gernes list", scripts[0].baseUri()) } override fun getFaviconUrl(): String = "https://styles.amarkcdn.com/img/batoto/favicon.ico?v0" @@ -173,7 +174,7 @@ internal class BatoToParser(override val context: MangaLoaderContext) : PagedMan private fun getActivePage(body: Element): Int = body.select("nav ul.pagination > li.page-item.active") .lastOrNull() ?.text() - ?.toIntOrNull() ?: parseFailed("Cannot determine current page") + ?.toIntOrNull() ?: body.parseFailed("Cannot determine current page") private suspend fun parseList(url: String, page: Int): List { val body = context.httpGet(url).parseHtml().body() @@ -184,11 +185,11 @@ internal class BatoToParser(override val context: MangaLoaderContext) : PagedMan if (activePage != page) { return emptyList() } - val root = body.getElementById("series-list") ?: parseFailed("Cannot find root") + val root = body.requireElementById("series-list") return root.children().map { div -> - val a = div.selectFirst("a") ?: parseFailed() + val a = div.selectFirstOrThrow("a") val href = a.attrAsRelativeUrl("href") - val title = div.selectFirst(".item-title")?.text() ?: parseFailed("Title not found") + val title = div.selectFirstOrThrow(".item-title").text() Manga( id = generateUid(href), title = title, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/DesuMeParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/DesuMeParser.kt index 76ff88f5..3efe8716 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/DesuMeParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/DesuMeParser.kt @@ -50,7 +50,8 @@ internal class DesuMeParser(override val context: MangaLoaderContext) : PagedMan append(query) } } - val json = context.httpGet(url).parseJson().getJSONArray("response") ?: parseFailed("Invalid response") + val json = context.httpGet(url).parseJson().getJSONArray("response") + ?: throw ParseException("Invalid response", url) val total = json.length() val list = ArrayList(total) for (i in 0 until total) { @@ -83,7 +84,7 @@ internal class DesuMeParser(override val context: MangaLoaderContext) : PagedMan override suspend fun getDetails(manga: Manga): Manga { val url = manga.url.toAbsoluteUrl(getDomain()) val json = context.httpGet(url).parseJson().getJSONObject("response") - ?: throw ParseException("Invalid response") + ?: throw ParseException("Invalid response", url) val baseChapterUrl = manga.url + "/chapter/" val chaptersList = json.getJSONObject("chapters").getJSONArray("list") val totalChapters = chaptersList.length() @@ -119,7 +120,7 @@ internal class DesuMeParser(override val context: MangaLoaderContext) : PagedMan val fullUrl = chapter.url.toAbsoluteUrl(getDomain()) val json = context.httpGet(fullUrl) .parseJson() - .getJSONObject("response") ?: throw ParseException("Invalid response") + .getJSONObject("response") ?: throw ParseException("Invalid response", fullUrl) return json.getJSONObject("pages").getJSONArray("list").mapJSON { jo -> MangaPage( id = generateUid(jo.getLong("id")), @@ -133,17 +134,17 @@ internal class DesuMeParser(override val context: MangaLoaderContext) : PagedMan override suspend fun getTags(): Set { val doc = context.httpGet("https://${getDomain()}/manga/").parseHtml() - val root = doc.body().getElementById("animeFilter") - ?.selectFirst(".catalog-genres") ?: throw ParseException("Root not found") + val root = doc.body().requireElementById("animeFilter") + .selectFirstOrThrow(".catalog-genres") return root.select("li").mapToSet { - val input = it.selectFirst("input") ?: parseFailed() + val input = it.selectFirstOrThrow("input") MangaTag( source = source, key = input.attr("data-genre-slug").ifEmpty { - parseFailed("data-genre-slug is empty") + it.parseFailed("data-genre-slug is empty") }, title = input.attr("data-genre-name").toTitleCase().ifEmpty { - parseFailed("data-genre-name is empty") + it.parseFailed("data-genre-name is empty") }, ) } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ExHentaiParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ExHentaiParser.kt index f7628cc9..f53a20f7 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ExHentaiParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/ExHentaiParser.kt @@ -7,7 +7,6 @@ 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.AuthRequiredException -import org.koitharu.kotatsu.parsers.exception.ParseException import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.util.* import java.util.* @@ -94,7 +93,7 @@ internal class ExHentaiParser( val root = body.selectFirst("table.itg") ?.selectFirst("tbody") ?: if (updateDm) { - parseFailed("Cannot find root") + body.parseFailed("Cannot find root") } else { updateDm = true return getListPage(page, query, tags, sortOrder) @@ -103,10 +102,10 @@ internal class ExHentaiParser( return root.children().mapNotNull { tr -> if (tr.childrenSize() != 2) return@mapNotNull null val (td1, td2) = tr.children() - val glink = td2.selectFirst("div.glink") ?: parseFailed("glink not found") - val a = glink.parents().select("a").first() ?: parseFailed("link not found") + val glink = td2.selectFirstOrThrow("div.glink") + val a = glink.parents().select("a").first() ?: glink.parseFailed("link not found") val href = a.attrAsRelativeUrl("href") - val tagsDiv = glink.nextElementSibling() ?: parseFailed("tags div not found") + val tagsDiv = glink.nextElementSibling() ?: glink.parseFailed("tags div not found") val mainTag = td2.selectFirst("div.cn")?.let { div -> MangaTag( title = div.text().toTitleCase(), @@ -134,7 +133,7 @@ internal class ExHentaiParser( override suspend fun getDetails(manga: Manga): Manga { val doc = context.httpGet(manga.url.toAbsoluteUrl(getDomain())).parseHtml() - val root = doc.body().selectFirst("div.gm") ?: parseFailed("Cannot find root") + val root = doc.body().selectFirstOrThrow("div.gm") val cover = root.getElementById("gd1")?.children()?.first() val title = root.getElementById("gd2") val taglist = root.getElementById("taglist") @@ -178,7 +177,7 @@ internal class ExHentaiParser( override suspend fun getPages(chapter: MangaChapter): List { val doc = context.httpGet(chapter.url.toAbsoluteUrl(getDomain())).parseHtml() - val root = doc.body().getElementById("gdt") ?: parseFailed("Root not found") + val root = doc.body().requireElementById("gdt") return root.select("a").map { a -> val url = a.attrAsRelativeUrl("href") MangaPage( @@ -193,14 +192,12 @@ internal class ExHentaiParser( override suspend fun getPageUrl(page: MangaPage): String { val doc = context.httpGet(page.url.toAbsoluteUrl(getDomain())).parseHtml() - return doc.body().getElementById("img")?.absUrl("src") - ?: parseFailed("Image not found") + return doc.body().requireElementById("img").attrAsAbsoluteUrl("src") } override suspend fun getTags(): Set { val doc = context.httpGet("https://${getDomain()}").parseHtml() - val root = doc.body().getElementById("searchbox")?.selectFirst("table") - ?: parseFailed("Root not found") + val root = doc.body().requireElementById("searchbox").selectFirstOrThrow("table") return root.select("div.cs").mapNotNullToSet { div -> val id = div.id().substringAfterLast('_').toIntOrNull() ?: return@mapNotNullToSet null @@ -221,7 +218,7 @@ internal class ExHentaiParser( ?: if (doc.getElementById("userlinksguest") != null) { throw AuthRequiredException(source) } else { - throw ParseException(null) + doc.parseFailed() } return username } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/MangaInUaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/MangaInUaParser.kt index 51308e56..7c70430c 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/MangaInUaParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/MangaInUaParser.kt @@ -45,7 +45,7 @@ class MangaInUaParser(override val context: MangaLoaderContext) : PagedMangaPars else -> "/mangas/page/$page".toAbsoluteUrl(getDomain()) } val doc = context.httpGet(url).parseHtml() - val container = doc.body().getElementById("dle-content") ?: parseFailed("Container not found") + val container = doc.body().requireElementById("dle-content") val items = container.select("div.col-6") return items.mapNotNull { item -> val href = item.selectFirst("a")?.attrAsRelativeUrl("href") ?: return@mapNotNull null @@ -81,8 +81,7 @@ class MangaInUaParser(override val context: MangaLoaderContext) : PagedMangaPars override suspend fun getDetails(manga: Manga): Manga { val doc = context.httpGet(manga.url.toAbsoluteUrl(getDomain())).parseHtml() - val root = - doc.body().getElementById("dle-content") ?: parseFailed("Cannot find root") + val root = doc.body().requireElementById("dle-content") val dateFormat = SimpleDateFormat("dd.MM.yyyy", Locale.US) val chapterNodes = root.selectFirstOrThrow(".linkstocomics") .select(".ltcitems") @@ -125,10 +124,9 @@ class MangaInUaParser(override val context: MangaLoaderContext) : PagedMangaPars override suspend fun getPages(chapter: MangaChapter): List { val fullUrl = chapter.url.toAbsoluteUrl(getDomain()) val doc = context.httpGet(fullUrl).parseHtml() - val root = - doc.body().getElementById("comics")?.selectFirst("ul.xfieldimagegallery") ?: parseFailed("Root not found") + val root = doc.body().requireElementById("comics").selectFirstOrThrow("ul.xfieldimagegallery") return root.select("li").map { ul -> - val img = ul.selectFirst("img") ?: parseFailed("Page image not found") + val img = ul.selectFirstOrThrow("img") val url = img.attrAsAbsoluteUrl("data-src") MangaPage( id = generateUid(url), @@ -143,8 +141,7 @@ class MangaInUaParser(override val context: MangaLoaderContext) : PagedMangaPars override suspend fun getTags(): Set { val domain = getDomain() val doc = context.httpGet("https://$domain/mangas").parseHtml() - val root = - doc.body().getElementById("menu_1")?.selectFirst("div.menu__wrapper") ?: parseFailed("Cannot find root") + val root = doc.body().requireElementById("menu_1").selectFirstOrThrow("div.menu__wrapper") return root.select("li").mapNotNullToSet { li -> val a = li.selectFirst("a") ?: return@mapNotNullToSet null MangaTag( diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/MangaOwlParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/MangaOwlParser.kt index 098f81ba..0f36feef 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/MangaOwlParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/MangaOwlParser.kt @@ -4,7 +4,6 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaParser import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.config.ConfigKey -import org.koitharu.kotatsu.parsers.exception.ParseException import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.util.* import java.text.SimpleDateFormat @@ -52,7 +51,7 @@ internal class MangaOwlParser(override val context: MangaLoaderContext) : MangaP } } val doc = context.httpGet(link).parseHtml() - val slides = doc.body().select("ul.slides") ?: parseFailed("An error occurred while parsing") + val slides = doc.body().selectOrThrow("ul.slides") val items = slides.select("div.col-md-2") return items.mapNotNull { item -> val href = item.selectFirst("h6 a")?.attrAsRelativeUrlOrNull("href") ?: return@mapNotNull null @@ -80,12 +79,12 @@ internal class MangaOwlParser(override val context: MangaLoaderContext) : MangaP override suspend fun getDetails(manga: Manga): Manga { val doc = context.httpGet(manga.publicUrl).parseHtml() - val info = doc.body().selectFirst("div.single_detail") ?: parseFailed("An error occurred while parsing") - val table = doc.body().selectFirst("div.single-grid-right") ?: parseFailed("An error occurred while parsing") + val info = doc.body().selectFirstOrThrow("div.single_detail") + val table = doc.body().selectFirstOrThrow("div.single-grid-right") val dateFormat = SimpleDateFormat("MM/dd/yyyy", Locale.US) val trRegex = "window\\['tr'] = '([^']*)';".toRegex(RegexOption.IGNORE_CASE) - val trElement = - doc.getElementsByTag("script").find { trRegex.find(it.data()) != null } ?: parseFailed("Oops, tr not found") + val trElement = doc.getElementsByTag("script").find { trRegex.find(it.data()) != null } + ?: doc.parseFailed("Oops, tr not found") val tr = trRegex.find(trElement.data())!!.groups[1]!!.value val s = context.encodeBase64(getDomain().toByteArray()) var isNsfw = manga.isNsfw @@ -115,7 +114,7 @@ internal class MangaOwlParser(override val context: MangaLoaderContext) : MangaP .asReversed().mapChapters { i, li -> val a = li.select("a") val href = a.attr("data-href").ifEmpty { - parseFailed("Link is missing") + li.parseFailed("Link is missing") } MangaChapter( id = generateUid(href), @@ -134,9 +133,9 @@ internal class MangaOwlParser(override val context: MangaLoaderContext) : MangaP override suspend fun getPages(chapter: MangaChapter): List { val fullUrl = chapter.url.toAbsoluteUrl(getDomain()) val doc = context.httpGet(fullUrl).parseHtml() - val root = doc.body().select("div.item img.owl-lazy") ?: throw ParseException("Root not found") + val root = doc.body().selectOrThrow("div.item img.owl-lazy") return root.map { div -> - val url = div?.attrAsRelativeUrlOrNull("data-src") ?: parseFailed("Page image not found") + val url = div?.attrAsRelativeUrlOrNull("data-src") ?: doc.parseFailed("Page image not found") MangaPage( id = generateUid(url), url = url, @@ -158,7 +157,7 @@ internal class MangaOwlParser(override val context: MangaLoaderContext) : MangaP val doc = context.httpGet("https://${getDomain()}/").parseHtml() val root = doc.body().select("ul.dropdown-menu.multi-column.columns-3").select("li") return root.mapToSet { p -> - val a = p.selectFirst("a") ?: parseFailed("a is null") + val a = p.selectFirstOrThrow("a") MangaTag( title = a.text().toTitleCase(), key = a.attr("href"), diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/MangaTownParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/MangaTownParser.kt index 30dd65b7..95623121 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/MangaTownParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/MangaTownParser.kt @@ -4,7 +4,6 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaParser import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.config.ConfigKey -import org.koitharu.kotatsu.parsers.exception.ParseException import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.util.* import java.text.DateFormat @@ -55,8 +54,7 @@ internal class MangaTownParser(override val context: MangaLoaderContext) : Manga } } val doc = context.httpGet(url).parseHtml() - val root = doc.body().selectFirst("ul.manga_pic_list") - ?: throw ParseException("Root not found") + val root = doc.body().selectFirstOrThrow("ul.manga_pic_list") return root.select("li").mapNotNull { li -> val a = li.selectFirst("a.manga_cover") val href = a?.attrAsRelativeUrlOrNull("href") @@ -96,8 +94,8 @@ internal class MangaTownParser(override val context: MangaLoaderContext) : Manga override suspend fun getDetails(manga: Manga): Manga { val doc = context.httpGet(manga.url.toAbsoluteUrl(getDomain())).parseHtml() - val root = doc.body().selectFirst("section.main") - ?.selectFirst("div.article_content") ?: throw ParseException("Cannot find root") + val root = doc.body().selectFirstOrThrow("section.main") + .selectFirstOrThrow("div.article_content") val info = root.selectFirst("div.detail_info")?.selectFirst("ul") val chaptersList = root.selectFirst("div.chapter_content") ?.selectFirst("ul.chapter_list")?.select("li")?.asReversed() @@ -139,9 +137,8 @@ internal class MangaTownParser(override val context: MangaLoaderContext) : Manga override suspend fun getPages(chapter: MangaChapter): List { val fullUrl = chapter.url.toAbsoluteUrl(getDomain()) val doc = context.httpGet(fullUrl).parseHtml() - val root = doc.body().selectFirst("div.page_select") - ?: throw ParseException("Cannot find root") - return root.selectFirst("select")?.select("option")?.mapNotNull { + val root = doc.body().selectFirstOrThrow("div.page_select") + return root.selectFirstOrThrow("select").selectOrThrow("option").mapNotNull { val href = it.attrAsRelativeUrlOrNull("value") if (href == null || href.endsWith("featured.html")) { return@mapNotNull null @@ -153,12 +150,12 @@ internal class MangaTownParser(override val context: MangaLoaderContext) : Manga referer = fullUrl, source = MangaSource.MANGATOWN, ) - } ?: parseFailed("Pages list not found") + } } override suspend fun getPageUrl(page: MangaPage): String { val doc = context.httpGet(page.url.toAbsoluteUrl(getDomain())).parseHtml() - return doc.getElementById("image")?.absUrl("src") ?: parseFailed("Image not found") + return doc.requireElementById("image").attrAsAbsoluteUrl("src") } override suspend fun getTags(): Set { @@ -166,7 +163,7 @@ internal class MangaTownParser(override val context: MangaLoaderContext) : Manga val root = doc.body().selectFirst("aside.right") ?.getElementsContainingOwnText("Genres") ?.first() - ?.nextElementSibling() ?: parseFailed("Root not found") + ?.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() diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/NHentaiParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/NHentaiParser.kt index f1d12996..42170f49 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/NHentaiParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/NHentaiParser.kt @@ -66,8 +66,8 @@ class NHentaiParser(override val context: MangaLoaderContext) : PagedMangaParser } } } - val root = context.httpGet(url).parseHtml().body().getElementById("content") - ?.selectLast("div.index-container") ?: parseFailed("Root not found") + val root = context.httpGet(url).parseHtml().body().requireElementById("content") + .selectLastOrThrow("div.index-container") val regexBrackets = Regex("\\[[^]]+]|\\([^)]+\\)") val regexSpaces = Regex("\\s+") return root.select(".gallery").map { div -> diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/NicovideoSeigaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/NicovideoSeigaParser.kt index dc9e18fe..de8953e4 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/NicovideoSeigaParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/NicovideoSeigaParser.kt @@ -54,7 +54,7 @@ class NicovideoSeigaParser(override val context: MangaLoaderContext) : else -> "https://$domain/manga/list?page=$page&sort=${getSortKey(sortOrder)}" } val doc = context.httpGet(url).parseHtml() - val comicList = doc.body().select("#comic_list > ul > li") ?: parseFailed("Container not found") + val comicList = doc.body().select("#comic_list > ul > li") ?: doc.parseFailed("Container not found") val items = comicList.select("div > .description > div > div") return items.mapNotNull { item -> val href = @@ -89,7 +89,7 @@ class NicovideoSeigaParser(override val context: MangaLoaderContext) : override suspend fun getDetails(manga: Manga): Manga { val doc = context.httpGet(manga.url.toAbsoluteUrl(getDomain("seiga"))).parseHtml() - val contents = doc.body().selectFirst("#contents") ?: parseFailed("Cannot find root") + val contents = doc.body().selectFirstOrThrow("#contents") val statusText = contents .select("div.mg_work_detail > div > div:nth-child(2) > div.tip.content_status.status_series > span") .text() @@ -105,7 +105,7 @@ class NicovideoSeigaParser(override val context: MangaLoaderContext) : isNsfw = contents.select(".icon_adult").isNotEmpty(), chapters = contents.select("#episode_list > ul > li").mapChapters { i, li -> val href = li.selectFirst("div > div.description > div.title > a") - ?.attrAsRelativeUrl("href") ?: parseFailed() + ?.attrAsRelativeUrl("href") ?: li.parseFailed() MangaChapter( id = generateUid(href), name = li.select("div > div.description > div.title > a").text(), @@ -140,9 +140,9 @@ class NicovideoSeigaParser(override val context: MangaLoaderContext) : override suspend fun getTags(): Set { val doc = context.httpGet("https://${getDomain("seiga")}/manga/list").parseHtml() - val root = doc.body().select("#mg_category_list > ul > li") ?: parseFailed("Cannot find tags") + val root = doc.body().selectOrThrow("#mg_category_list > ul > li") return root.mapToSet { li -> - val a = li.selectFirst("a") ?: parseFailed("a is null") + val a = li.selectFirstOrThrow("a") MangaTag( title = a.text(), key = a.attrAsRelativeUrlOrNull("href").orEmpty(), @@ -157,7 +157,7 @@ class NicovideoSeigaParser(override val context: MangaLoaderContext) : val root = doc.body().select(".search_result__item") return root.mapNotNull { item -> val href = item.selectFirst(".search_result__item__thumbnail > a") - ?.attrAsRelativeUrl("href") ?: parseFailed() + ?.attrAsRelativeUrl("href") ?: doc.parseFailed() Manga( id = generateUid(href), url = href, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/NineMangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/NineMangaParser.kt index 5cab866b..0918ac86 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/NineMangaParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/NineMangaParser.kt @@ -6,7 +6,6 @@ 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.ParseException import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.util.* import java.text.SimpleDateFormat @@ -66,11 +65,11 @@ internal abstract class NineMangaParser( } val doc = context.httpGet(url, headers).parseHtml() val root = doc.body().selectFirst("ul.direlist") - ?: throw ParseException("Cannot find root") + ?: doc.parseFailed("Cannot find root") val baseHost = root.baseUri().toHttpUrl().host return root.select("li").map { node -> val href = node.selectFirst("a")?.absUrl("href") - ?: parseFailed("Link not found") + ?: node.parseFailed("Link not found") val relUrl = href.toRelativeUrl(baseHost) val dd = node.selectFirst("dd") Manga( @@ -96,10 +95,8 @@ internal abstract class NineMangaParser( manga.url.toAbsoluteUrl(getDomain()) + "?waring=1", headers, ).parseHtml() - val root = doc.body().selectFirst("div.manga") - ?: throw ParseException("Cannot find root") - val infoRoot = root.selectFirst("div.bookintro") - ?: throw ParseException("Cannot find info") + val root = doc.body().selectFirstOrThrow("div.manga") + val infoRoot = root.selectFirstOrThrow("div.bookintro") return manga.copy( tags = infoRoot.getElementsByAttributeValue("itemprop", "genre").first() ?.select("a")?.mapToSet { a -> @@ -117,7 +114,7 @@ internal abstract class NineMangaParser( ?.asReversed()?.mapChapters { i, li -> val a = li.selectFirst("a.chapter_list_a") val href = a?.attrAsRelativeUrlOrNull("href") - ?.replace("%20", " ") ?: parseFailed("Link not found") + ?.replace("%20", " ") ?: li.parseFailed("Link not found") MangaChapter( id = generateUid(href), name = a.text(), @@ -143,14 +140,14 @@ internal abstract class NineMangaParser( preview = null, source = source, ) - } ?: throw ParseException("Pages list not found at ${chapter.url}") + } ?: doc.parseFailed("Pages list not found") } override suspend fun getPageUrl(page: MangaPage): String { val doc = context.httpGet(page.url.toAbsoluteUrl(getDomain()), headers).parseHtml() val root = doc.body() return root.selectFirst("a.pic_download")?.absUrl("href") - ?: throw ParseException("Page image not found") + ?: doc.parseFailed("Page image not found") } override suspend fun getTags(): Set { @@ -165,7 +162,7 @@ internal abstract class NineMangaParser( key = cateId, source = source, ) - } ?: parseFailed("Root not found") + } ?: doc.parseFailed("Root not found") } private fun parseStatus(status: String) = when { diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/NudeMoonParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/NudeMoonParser.kt index b2acbcdd..67520445 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/NudeMoonParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/NudeMoonParser.kt @@ -6,7 +6,6 @@ import org.koitharu.kotatsu.parsers.MangaParserAuthProvider import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.exception.AuthRequiredException -import org.koitharu.kotatsu.parsers.exception.ParseException import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.util.* import java.text.SimpleDateFormat @@ -59,12 +58,13 @@ internal class NudeMoonParser( postfix = "&rowstart=$offset", transform = { it.key.urlEncoded() }, ) + else -> "https://$domain/all_manga?${getSortKey(sortOrder)}&rowstart=$offset" } val doc = context.httpGet(url).parseHtml() val root = doc.body().run { selectFirst("td.main-bg") ?: selectFirst("td.main-body") - } ?: parseFailed("Cannot find root") + } ?: doc.parseFailed("Cannot find root") return root.select("table.news_pic2").mapNotNull { row -> val a = row.selectFirst("td.bg_style1")?.selectFirst("a") ?: return@mapNotNull null @@ -102,7 +102,7 @@ internal class NudeMoonParser( override suspend fun getDetails(manga: Manga): Manga { val body = context.httpGet(manga.url.toAbsoluteUrl(getDomain())).parseHtml().body() val root = body.selectFirst("table.shoutbox") - ?: parseFailed("Cannot find root") + ?: body.parseFailed("Cannot find root") val info = root.select("div.tbl2") val lastInfo = info.last() return manga.copy( @@ -143,7 +143,7 @@ internal class NudeMoonParser( val script = doc.select("script").firstNotNullOfOrNull { it.html().takeIf { x -> x.contains(" images = new ") } } ?: if (isAuthorized) { - parseFailed("Cannot find pages list") + doc.parseFailed("Cannot find pages list") } else { throw AuthRequiredException(source) } @@ -174,7 +174,7 @@ internal class NudeMoonParser( val root = doc.body().getElementsContainingOwnText("Поиск манги по тегам") .firstOrNull()?.parents()?.find { it.tag().normalName() == "tbody" } ?.selectFirst("td.textbox")?.selectFirst("td.small") - ?: parseFailed("Tags root not found") + ?: doc.parseFailed("Tags root not found") return root.select("a").mapToSet { MangaTag( title = it.text().toTitleCase(), @@ -197,7 +197,7 @@ internal class NudeMoonParser( throw if (body.selectFirst("form[name=\"loginform\"]") != null) { AuthRequiredException(source) } else { - ParseException("Cannot find username") + body.parseFailed("Cannot find username") } } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/RemangaParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/RemangaParser.kt index 4c585c11..34c751b1 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/RemangaParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/RemangaParser.kt @@ -11,6 +11,7 @@ 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.AuthRequiredException +import org.koitharu.kotatsu.parsers.exception.ContentUnavailableException import org.koitharu.kotatsu.parsers.exception.ParseException import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.util.* @@ -113,7 +114,7 @@ internal class RemangaParser( copyCookies() val domain = getDomain() val slug = manga.url.find(regexLastUrlPath) - ?: throw ParseException("Cannot obtain slug from ${manga.url}") + ?: throw ParseException("Cannot obtain slug from ${manga.url}", manga.publicUrl) val data = context.httpGet( url = "https://api.$domain/api/titles/$slug/", headers = getApiHeaders(), @@ -121,10 +122,10 @@ internal class RemangaParser( val content = try { data.getJSONObject("content") } catch (e: JSONException) { - throw ParseException(data.optString("msg"), e) + throw ParseException(data.optString("msg"), manga.publicUrl, e) } val branchId = content.getJSONArray("branches").optJSONObject(0) - ?.getLong("id") ?: throw ParseException("No branches found") + ?.getLong("id") ?: throw ParseException("No branches found", manga.publicUrl) val chapters = grabChapters(domain, branchId) val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US) return manga.copy( @@ -182,9 +183,9 @@ internal class RemangaParser( } if (pubDate != null && pubDate > System.currentTimeMillis()) { val at = SimpleDateFormat.getDateInstance(DateFormat.LONG).format(Date(pubDate)) - parseFailed("Глава станет доступной $at") + throw ContentUnavailableException("Глава станет доступной $at") } else { - parseFailed("Глава недоступна") + throw ContentUnavailableException("Глава недоступна") } } val result = ArrayList(pages.length()) @@ -192,7 +193,7 @@ internal class RemangaParser( when (val item = pages.get(i)) { is JSONObject -> result += parsePage(item, referer) is JSONArray -> item.mapJSONTo(result) { parsePage(it, referer) } - else -> throw ParseException("Unknown json item $item") + else -> throw ParseException("Unknown json item $item", chapter.url) } } return result diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/grouple/GroupleParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/grouple/GroupleParser.kt index f7425c1d..67ff07af 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/grouple/GroupleParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/grouple/GroupleParser.kt @@ -7,6 +7,7 @@ import org.json.JSONArray import org.koitharu.kotatsu.parsers.MangaParser import org.koitharu.kotatsu.parsers.MangaParserAuthProvider import org.koitharu.kotatsu.parsers.exception.AuthRequiredException +import org.koitharu.kotatsu.parsers.exception.ParseException import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.json.mapJSON @@ -79,13 +80,13 @@ internal abstract class GroupleParser( else -> advancedSearch(domain, tags) }.parseHtml().body() val root = (doc.getElementById("mangaBox") ?: doc.getElementById("mangaResults")) - ?: parseFailed("Cannot find root") + ?: doc.parseFailed("Cannot find root") val tiles = root.selectFirst("div.tiles.row") ?: if ( root.select(".alert").any { it.ownText() == NOTHING_FOUND } ) { return emptyList() } else { - parseFailed("No tiles found") + doc.parseFailed("No tiles found") } val baseHost = root.baseUri().toHttpUrl().host return tiles.select("div.tile").mapNotNull { node -> @@ -142,7 +143,7 @@ internal abstract class GroupleParser( override suspend fun getDetails(manga: Manga): Manga { val doc = context.httpGet(manga.url.toAbsoluteUrl(getDomain()), headers).parseHtml() val root = doc.body().getElementById("mangaBox")?.selectFirst("div.leftContent") - ?: parseFailed("Cannot find root") + ?: doc.parseFailed("Cannot find root") val dateFormat = SimpleDateFormat("dd.MM.yy", Locale.US) val coverImg = root.selectFirst("div.subject-cover")?.selectFirst("img") return manga.copy( @@ -218,7 +219,7 @@ internal abstract class GroupleParser( ) } } - parseFailed("Pages list not found at ${chapter.url}") + doc.parseFailed("Pages list not found at ${chapter.url}") } override suspend fun getPageUrl(page: MangaPage): String { @@ -232,14 +233,14 @@ internal abstract class GroupleParser( return url } } - val fallbackServer = servers.firstOrNull() ?: parseFailed("Cannot find any page url") + val fallbackServer = servers.firstOrNull() ?: throw ParseException("Cannot find any page url", page.url) return fallbackServer + path } override suspend fun getTags(): Set { val doc = context.httpGet("https://${getDomain()}/list/genres/sort_name", headers).parseHtml() val root = doc.body().getElementById("mangaBox")?.selectFirst("div.leftContent") - ?.selectFirst("table.table") ?: parseFailed("Cannot find root") + ?.selectFirst("table.table") ?: doc.parseFailed("Cannot find root") return root.select("a.element-link").mapToSet { a -> MangaTag( title = a.text().toTitleCase(), @@ -254,7 +255,7 @@ internal abstract class GroupleParser( val element = root.selectFirst("img.user-avatar") ?: throw AuthRequiredException(source) val res = element.parent()?.text() return if (res.isNullOrEmpty()) { - parseFailed("Cannot find username") + root.parseFailed("Cannot find username") } else res } @@ -273,21 +274,21 @@ internal abstract class GroupleParser( val tagsIndex = context.httpGet(url, headers).parseHtml() .body().selectFirst("form.search-form") ?.select("div.form-group") - ?.get(1) ?: parseFailed("Genres filter element not found") + ?.get(1) ?: throw ParseException("Genres filter element not found", url) val tagNames = tags.map { it.title.lowercase() } val payload = HashMap() var foundGenres = 0 tagsIndex.select("li.property").forEach { li -> val name = li.text().trim().lowercase() val id = li.selectFirst("input")?.id() - ?: parseFailed("Id for tag $name not found") + ?: li.parseFailed("Id for tag $name not found") payload[id] = if (name in tagNames) { foundGenres++ "in" } else "" } if (foundGenres != tags.size) { - parseFailed("Some genres are not found") + tagsIndex.parseFailed("Some genres are not found") } // Step 2: advanced search payload["q"] = "" diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/Madara5Parser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/Madara5Parser.kt index 2d641f94..741c3fb0 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/Madara5Parser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/madara/Madara5Parser.kt @@ -89,7 +89,7 @@ abstract class Madara5Parser @InternalParsersApi constructor( ?.getElementsByAttributeValueContaining("href", tagPrefix) ?.mapToSet { a -> a.asMangaTag() } ?: manga.tags val mangaId = root.getElementById("manga-chapters-holder")?.attr("data-id")?.toLongOrNull() - ?: parseFailed("Cannot find mangaId") + ?: root.parseFailed("Cannot find mangaId") return manga.copy( description = (root.selectFirst(".detail-content") ?: root.selectFirstOrThrow(".description-summary")).html(), @@ -106,7 +106,7 @@ abstract class Madara5Parser @InternalParsersApi constructor( override suspend fun getPages(chapter: MangaChapter): List { val fullUrl = chapter.url.toAbsoluteUrl(getDomain()) val doc = context.httpGet(fullUrl).parseHtml() - val arrayData = doc.getElementById("arraydata") ?: parseFailed("#arraydata not found") + val arrayData = doc.getElementById("arraydata") ?: doc.parseFailed("#arraydata not found") return arrayData.html().split(',').map { url -> MangaPage( id = generateUid(url), 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 b407b59a..4c8529b8 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 @@ -55,7 +55,7 @@ internal abstract class MadaraParser( ).parseHtml() return doc.select("div.row.c-tabs-item__content").map { div -> val href = div.selectFirst("a")?.attrAsRelativeUrlOrNull("href") - ?: parseFailed("Link not found") + ?: div.parseFailed("Link not found") val summary = div.selectFirst(".tab-summary") Manga( id = generateUid(href), @@ -94,7 +94,7 @@ internal abstract class MadaraParser( val root1 = body.selectFirst("header")?.selectFirst("ul.second-menu") val root2 = body.selectFirst("div.genres_wrap")?.selectFirst("ul.list-unstyled") if (root1 == null && root2 == null) { - parseFailed("Root not found") + doc.parseFailed("Root not found") } val list = root1?.select("li").orEmpty() + root2?.select("li").orEmpty() val keySet = HashSet(list.size) @@ -121,10 +121,10 @@ internal abstract class MadaraParser( val root = doc.body().selectFirst("div.profile-manga") ?.selectFirst("div.summary_content") ?.selectFirst("div.post-content") - ?: throw ParseException("Root not found") + ?: throw ParseException("Root not found", fullUrl) val root2 = doc.body().selectFirst("div.content-area") ?.selectFirst("div.c-page") - ?: throw ParseException("Root2 not found") + ?: throw ParseException("Root2 not found", fullUrl) val dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.US) return manga.copy( tags = root.selectFirst("div.genres-content")?.select("a") @@ -142,7 +142,7 @@ internal abstract class MadaraParser( ?.joinToString { it.html() }, chapters = root2.select("li").asReversed().mapChapters { i, li -> val a = li.selectFirst("a") - val href = a?.attrAsRelativeUrlOrNull("href") ?: parseFailed("Link is missing") + val href = a?.attrAsRelativeUrlOrNull("href") ?: li.parseFailed("Link is missing") MangaChapter( id = generateUid(href), name = a.ownText(), @@ -165,10 +165,10 @@ internal abstract class MadaraParser( val doc = context.httpGet(fullUrl).parseHtml() val root = doc.body().selectFirst("div.main-col-inner") ?.selectFirst("div.reading-content") - ?: throw ParseException("Root not found") + ?: throw ParseException("Root not found", fullUrl) return root.select("div.page-break").map { div -> - val img = div.selectFirst("img") ?: parseFailed("Page image not found") - val url = img.src()?.toRelativeUrl(getDomain()) ?: parseFailed("Image src not found") + val img = div.selectFirst("img") ?: div.parseFailed("Page image not found") + val url = img.src()?.toRelativeUrl(getDomain()) ?: div.parseFailed("Image src not found") MangaPage( id = generateUid(url), url = url, diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/multichan/ChanParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/multichan/ChanParser.kt index 8e567829..5792505d 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/multichan/ChanParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/multichan/ChanParser.kt @@ -3,7 +3,6 @@ package org.koitharu.kotatsu.parsers.site.multichan import org.koitharu.kotatsu.parsers.MangaParser import org.koitharu.kotatsu.parsers.MangaParserAuthProvider import org.koitharu.kotatsu.parsers.exception.AuthRequiredException -import org.koitharu.kotatsu.parsers.exception.ParseException import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.util.* import java.text.SimpleDateFormat @@ -48,7 +47,7 @@ internal abstract class ChanParser(source: MangaSource) : MangaParser(source), M } val doc = context.httpGet(url).parseHtml() val root = doc.body().selectFirst("div.main_fon")?.getElementById("content") - ?: parseFailed("Cannot find root") + ?: doc.parseFailed("Cannot find root") return root.select("div.content_row").mapNotNull { row -> val a = row.selectFirst("div.manga_row1")?.selectFirst("h2")?.selectFirst("a") ?: return@mapNotNull null @@ -84,8 +83,7 @@ internal abstract class ChanParser(source: MangaSource) : MangaParser(source), M override suspend fun getDetails(manga: Manga): Manga { val doc = context.httpGet(manga.url.toAbsoluteUrl(getDomain())).parseHtml() - val root = - doc.body().getElementById("dle-content") ?: parseFailed("Cannot find root") + val root = doc.body().getElementById("dle-content") ?: doc.parseFailed("Cannot find root") val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.US) return manga.copy( description = root.getElementById("description")?.html()?.substringBeforeLast(" { val domain = getDomain() val doc = context.httpGet("https://$domain/mostfavorites&sort=manga").parseHtml() val root = doc.body().selectFirst("div.main_fon")?.getElementById("side") - ?.select("ul")?.last() ?: throw ParseException("Cannot find root") + ?.select("ul")?.last() ?: doc.parseFailed("Cannot find root") return root.select("li.sidetag").mapToSet { li -> - val a = li.children().last() ?: throw ParseException("a is null") + val a = li.children().lastOrNull() ?: li.parseFailed("a is null") MangaTag( title = a.text().toTagName(), key = a.attr("href").substringAfterLast('/'), diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/multichan/HenChanParser.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/multichan/HenChanParser.kt index 7fd68684..97a5f032 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/multichan/HenChanParser.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/multichan/HenChanParser.kt @@ -3,12 +3,8 @@ package org.koitharu.kotatsu.parsers.site.multichan import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.config.ConfigKey -import org.koitharu.kotatsu.parsers.exception.ParseException import org.koitharu.kotatsu.parsers.model.* -import org.koitharu.kotatsu.parsers.util.mapToSet -import org.koitharu.kotatsu.parsers.util.parseHtml -import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl -import org.koitharu.kotatsu.parsers.util.toTitleCase +import org.koitharu.kotatsu.parsers.util.* @MangaSourceParser("HENCHAN", "Хентай-тян", "ru") internal class HenChanParser(override val context: MangaLoaderContext) : ChanParser(MangaSource.HENCHAN) { @@ -34,13 +30,13 @@ internal class HenChanParser(override val context: MangaLoaderContext) : ChanPar override suspend fun getDetails(manga: Manga): Manga { val doc = context.httpGet(manga.url.toAbsoluteUrl(getDomain())).parseHtml() - val root = doc.body().getElementById("dle-content") ?: throw ParseException("Cannot find root") + val root = doc.body().requireElementById("dle-content") val readLink = manga.url.replace("manga", "online") return manga.copy( description = root.getElementById("description")?.html()?.substringBeforeLast(" @@ -85,7 +85,7 @@ internal open class MangaLibParser( override suspend fun getDetails(manga: Manga): Manga { val fullUrl = manga.url.toAbsoluteUrl(getDomain()) val doc = context.httpGet("$fullUrl?section=info").parseHtml() - val root = doc.body().getElementById("main-page") ?: throw ParseException("Root not found") + val root = doc.body().getElementById("main-page") ?: throw ParseException("Root not found", fullUrl) val title = root.selectFirst("div.media-header__wrap")?.children() val info = root.selectFirst("div.media-content") val chaptersDoc = context.httpGet("$fullUrl?section=chapters").parseHtml() @@ -169,7 +169,7 @@ internal open class MangaLibParser( throw AuthRequiredException(source) } val scripts = doc.head().select("script") - val pg = (doc.body().getElementById("pg")?.html() ?: parseFailed("Element #pg not found")) + val pg = (doc.body().getElementById("pg")?.html() ?: doc.parseFailed("Element #pg not found")) .substringAfter('=') .substringBeforeLast(';') val pages = JSONArray(pg) @@ -199,7 +199,7 @@ internal open class MangaLibParser( } } } - throw ParseException("Script with info not found") + throw ParseException("Script with info not found", fullUrl) } override suspend fun getTags(): Set { @@ -222,7 +222,7 @@ internal open class MangaLibParser( return result } } - throw ParseException("Script with genres not found") + throw ParseException("Script with genres not found", url) } override val isAuthorized: Boolean @@ -237,13 +237,13 @@ internal open class MangaLibParser( if (body.baseUri().endsWith("/login")) { throw AuthRequiredException(source) } - return body.selectFirst(".profile-user__username")?.text() ?: parseFailed("Cannot find username") + return body.selectFirst(".profile-user__username")?.text() ?: body.parseFailed("Cannot find username") } protected open fun isNsfw(doc: Document): Boolean { val sidebar = doc.body().run { selectFirst(".media-sidebar") ?: selectFirst(".media-info") - } ?: parseFailed("Sidebar not found") + } ?: doc.parseFailed("Sidebar not found") return sidebar.getElementsContainingOwnText("18+").isNotEmpty() } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/Jsoup.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/Jsoup.kt index b4c0d1db..f76e079e 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/util/Jsoup.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/util/Jsoup.kt @@ -4,6 +4,7 @@ package org.koitharu.kotatsu.parsers.util import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import org.jsoup.nodes.Element +import org.jsoup.select.Elements import org.jsoup.select.Selector import org.koitharu.kotatsu.parsers.exception.ParseException @@ -90,13 +91,21 @@ fun Element.styleValueOrNull(property: String): String? { return css.substringAfter(':').removeSuffix(';').trim() } -@Deprecated("Now implemented in Jsoup", ReplaceWith("expectFirst(cssQuery)")) +/** + * Like a `expectFirst` but with detailed error message + */ fun Element.selectFirstOrThrow(cssQuery: String): Element { - return Selector.selectFirst(cssQuery, this) ?: throw ParseException("Cannot find \"$cssQuery\"") + return Selector.selectFirst(cssQuery, this) ?: throw ParseException("Cannot find \"$cssQuery\"", baseUri()) +} + +fun Element.selectOrThrow(cssQuery: String): Elements { + return Selector.select(cssQuery, this).ifEmpty { + throw ParseException("Empty result for \"$cssQuery\"", baseUri()) + } } fun Element.requireElementById(id: String): Element { - return getElementById(id) ?: throw ParseException("Cannot find \"#$id\"") + return getElementById(id) ?: throw ParseException("Cannot find \"#$id\"", baseUri()) } fun Element.selectLast(cssQuery: String): Element? { @@ -104,5 +113,5 @@ fun Element.selectLast(cssQuery: String): Element? { } fun Element.selectLastOrThrow(cssQuery: String): Element { - return selectLast(cssQuery) ?: throw ParseException("Cannot find \"$cssQuery\"") + return selectLast(cssQuery) ?: throw ParseException("Cannot find \"$cssQuery\"", baseUri()) } \ No newline at end of file