From 348d45f2f52a861357b551b79ab814df44b97187 Mon Sep 17 00:00:00 2001 From: FooIbar <118464521+FooIbar@users.noreply.github.com> Date: Fri, 12 Sep 2025 21:05:43 +0800 Subject: [PATCH] Treat search warnings as errors --- .../com/hippo/ehviewer/client/EhEngine.kt | 22 ++++-- .../com/hippo/ehviewer/spider/SpiderQueen.kt | 16 +--- app/src/main/rust/src/ffi/jvm.rs | 8 +- app/src/main/rust/src/parser/fav.rs | 8 +- app/src/main/rust/src/parser/list.rs | 79 +++++++++---------- app/src/main/rust/src/parser/test_parser.rs | 2 +- 6 files changed, 65 insertions(+), 70 deletions(-) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/EhEngine.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/EhEngine.kt index 0603fde00..6ff77b752 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/EhEngine.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/EhEngine.kt @@ -41,6 +41,7 @@ import com.hippo.ehviewer.client.parser.FavoritesParser import com.hippo.ehviewer.client.parser.GalleryApiParser import com.hippo.ehviewer.client.parser.GalleryDetailParser import com.hippo.ehviewer.client.parser.GalleryListParser +import com.hippo.ehviewer.client.parser.GalleryMultiPageViewerPTokenParser import com.hippo.ehviewer.client.parser.GalleryPageParser import com.hippo.ehviewer.client.parser.GalleryTokenApiParser import com.hippo.ehviewer.client.parser.HomeParser @@ -62,6 +63,8 @@ import eu.kanade.tachiyomi.util.system.logcat import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.HttpStatement import io.ktor.client.statement.bodyAsChannel +import io.ktor.client.statement.request +import io.ktor.http.Url import io.ktor.utils.io.pool.DirectByteBufferPool import io.ktor.utils.io.pool.useInstance import io.ktor.utils.io.readAvailable @@ -86,7 +89,9 @@ private const val MAX_REQUEST_SIZE = 25 private const val MAX_SEQUENTIAL_REQUESTS = 5 private const val REQUEST_INTERVAL = 5000L -fun Either.saveParseError(e: Throwable) { +private val Url.isLogin get() = segments.firstOrNull() == "bounce_login.php" + +private fun Either.saveParseError(e: Throwable) { val dir = AppConfig.externalParseErrorDir ?: return val file = File(dir, ReadableTime.getFilenamableTime() + ".txt") file.sink().buffer().use { sink -> @@ -98,7 +103,7 @@ fun Either.saveParseError(e: Throwable) { } } -fun rethrowExactly(response: HttpResponse, body: Either, e: Throwable): Nothing { +private fun rethrowExactly(response: HttpResponse, body: Either, e: Throwable): Nothing { // Don't translate coroutine cancellation if (e is CancellationException) throw e @@ -148,9 +153,10 @@ fun rethrowExactly(response: HttpResponse, body: Either, e: throw e } -val httpContentPool = DirectByteBufferPool(8, 0x80000) +private val httpContentPool = DirectByteBufferPool(8, 0x80000) -suspend inline fun HttpStatement.fetchUsingAsText(crossinline block: suspend String.() -> T) = executeSafely { response -> +private suspend inline fun HttpStatement.fetchUsingAsText(crossinline block: suspend String.() -> T) = executeSafely { response -> + if (response.request.url.isLogin) throw NotLoggedInException() val body = response.bodyAsUtf8Text() runSuspendCatching { block(body) @@ -159,7 +165,8 @@ suspend inline fun HttpStatement.fetchUsingAsText(crossinline block: suspend }.getOrThrow() } -suspend inline fun HttpStatement.fetchUsingAsByteBuffer(crossinline block: suspend ByteBuffer.() -> T) = executeSafely { response -> +private suspend inline fun HttpStatement.fetchUsingAsByteBuffer(crossinline block: suspend ByteBuffer.() -> T) = executeSafely { response -> + if (response.request.url.isLogin) throw NotLoggedInException() httpContentPool.useInstance { buffer -> with(response.bodyAsChannel()) { while (!isClosedForRead) readAvailable(buffer) } buffer.flip() @@ -175,9 +182,12 @@ suspend inline fun HttpStatement.fetchUsingAsByteBuffer(crossinline block: s object EhEngine { suspend fun getOriginalImageUrl(url: String, referer: String?) = noRedirectEhRequest(url, referer).executeSafely { response -> val location = response.headers["Location"] ?: throw InsufficientFundsException() - location.takeIf { "bounce_login" !in it } ?: throw NotLoggedInException() + location.takeUnless { Url(it).isLogin } ?: throw NotLoggedInException() } + suspend fun getPTokenListFromMpv(gid: Long, token: String) = ehRequest(EhUrl.getGalleryMultiPageViewerUrl(gid, token), EhUrl.referer) + .fetchUsingAsText(GalleryMultiPageViewerPTokenParser::parse) + suspend fun getTorrentList(gid: Long, token: String): TorrentResult { val url = EhUrl.getTorrentUrl(gid, token) val referer = EhUrl.getGalleryDetailUrl(gid, token) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderQueen.kt b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderQueen.kt index fd170f039..553601dd8 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderQueen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderQueen.kt @@ -25,15 +25,11 @@ import com.hippo.ehviewer.Settings import com.hippo.ehviewer.client.EhEngine import com.hippo.ehviewer.client.EhUrl import com.hippo.ehviewer.client.EhUrl.getGalleryDetailUrl -import com.hippo.ehviewer.client.EhUrl.getGalleryMultiPageViewerUrl import com.hippo.ehviewer.client.EhUrl.referer import com.hippo.ehviewer.client.EhUtils import com.hippo.ehviewer.client.data.GalleryInfo import com.hippo.ehviewer.client.data.GalleryPreview -import com.hippo.ehviewer.client.ehRequest import com.hippo.ehviewer.client.exception.QuotaExceededException -import com.hippo.ehviewer.client.fetchUsingAsText -import com.hippo.ehviewer.client.parser.GalleryMultiPageViewerPTokenParser import com.hippo.ehviewer.util.displayString import com.hippo.files.find import eu.kanade.tachiyomi.util.system.logcat @@ -342,17 +338,11 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS suspend fun getPTokenFromMultiPageViewer(index: Int): String? { if (!isMpvAvailable) return null - val url = getGalleryMultiPageViewerUrl( - galleryInfo.gid, - galleryInfo.token, - ) return runSuspendCatching { - ehRequest(url, referer).fetchUsingAsText { - GalleryMultiPageViewerPTokenParser.parse(this).forEachIndexed { index, s -> - spiderInfo.pTokenMap[index] = s - } - spiderInfo.pTokenMap[index] + EhEngine.getPTokenListFromMpv(galleryInfo.gid, galleryInfo.token).forEachIndexed { index, s -> + spiderInfo.pTokenMap[index] = s } + spiderInfo.pTokenMap[index] }.getOrElse { logcat(it) null diff --git a/app/src/main/rust/src/ffi/jvm.rs b/app/src/main/rust/src/ffi/jvm.rs index d30acc08b..96de356a3 100644 --- a/app/src/main/rust/src/ffi/jvm.rs +++ b/app/src/main/rust/src/ffi/jvm.rs @@ -29,8 +29,8 @@ use tl::{ParserOptions, VDom}; #[jni_fn("com.hippo.ehviewer.client.parser.FavoritesParserKt")] pub fn parseFav(mut env: JNIEnv, _class: JClass, input: JByteBuffer, limit: jint) -> jint { - parse_marshal_inplace(&mut env, input, limit, |dom, html| { - parse_fav(dom, dom.parser(), html) + parse_marshal_inplace(&mut env, input, limit, |dom, _| { + parse_fav(dom, dom.parser()) }) } @@ -43,8 +43,8 @@ pub fn parseLimit(mut env: JNIEnv, _class: JClass, input: JByteBuffer, limit: ji #[jni_fn("com.hippo.ehviewer.client.parser.GalleryListParserKt")] pub fn parseGalleryInfoList(mut env: JNIEnv, _: JClass, buffer: JByteBuffer, limit: jint) -> jint { - parse_marshal_inplace(&mut env, buffer, limit, |dom, str| { - parse_info_list(dom, dom.parser(), str) + parse_marshal_inplace(&mut env, buffer, limit, |dom, _| { + parse_info_list(dom, dom.parser()) }) } diff --git a/app/src/main/rust/src/parser/fav.rs b/app/src/main/rust/src/parser/fav.rs index 0c8824cc3..b7159ddbd 100644 --- a/app/src/main/rust/src/parser/fav.rs +++ b/app/src/main/rust/src/parser/fav.rs @@ -1,4 +1,3 @@ -use crate::EhError; use crate::parser::list::{GalleryListResult, parse_info_list}; use anyhow::{Result, bail}; use quick_xml::escape::unescape; @@ -14,10 +13,7 @@ pub struct FavResult { galleryListResult: GalleryListResult, } -pub fn parse_fav(dom: &VDom, parser: &Parser, html: &str) -> Result { - if html.contains("This page requires you to log on.

") { - bail!(EhError::NeedLogin) - } +pub fn parse_fav(dom: &VDom, parser: &Parser) -> Result { let vec: Vec<(String, i32)> = dom .get_elements_by_class_name("fp") .enumerate() @@ -38,7 +34,7 @@ pub fn parse_fav(dom: &VDom, parser: &Parser, html: &str) -> Result { }) .collect(); if vec.len() == 10 { - let list = parse_info_list(dom, parser, html)?; + let list = parse_info_list(dom, parser)?; let cat = vec.iter().cloned().unzip(); Ok(FavResult { catArray: cat.0, diff --git a/app/src/main/rust/src/parser/list.rs b/app/src/main/rust/src/parser/list.rs index 7d5030a8a..ae137c269 100644 --- a/app/src/main/rust/src/parser/list.rs +++ b/app/src/main/rust/src/parser/list.rs @@ -194,47 +194,46 @@ fn parse_gallery_info(node: &Node, parser: &Parser) -> Option { }) } -pub fn parse_info_list(dom: &VDom, parser: &Parser, str: &str) -> Result { - if str.contains("

You do not have any watched tags") { - bail!(EhError::NoWatched) - } - if str.contains("No hits found

") || str.contains("No unfiltered results") { - let e = get_vdom_first_element_by_class_name(dom, "searchwarn") - .map_or(EhError::NoHits, |n| { - EhError::Error(n.inner_text(parser).to_string()) - }); +pub fn parse_info_list(dom: &VDom, parser: &Parser) -> Result { + if let Some(n) = get_vdom_first_element_by_class_name(dom, "searchwarn") { + let text = n.inner_text(parser); + let e = if text.contains("You do not have any watched tags") { + EhError::NoWatched + } else { + EhError::Error(text.to_string()) + }; bail!(e) } - let f = || { - let itg = get_vdom_first_element_by_class_name(dom, "itg")?; - let children = itg.children()?; - let iter = children.top().iter(); - let info: Vec = iter - .filter_map(|x| parse_gallery_info(x.get(parser)?, parser)) - .collect(); - let prev = dom.get_element_by_id("uprev").and_then(|e| { - let str = get_node_handle_attr(&e, parser, "href")?; - Some( - regex!("prev=(\\d+(-\\d+)?)") - .captures(str)? - .index(1) - .to_string(), - ) - }); - let next = dom.get_element_by_id("unext").and_then(|e| { - let str = get_node_handle_attr(&e, parser, "href")?; - Some( - regex!("next=(\\d+(-\\d+)?)") - .captures(str)? - .index(1) - .to_string(), - ) - }); - (!info.is_empty()).then_some(GalleryListResult { - prev, - next, - galleryInfoList: info, + let info = get_vdom_first_element_by_class_name(dom, "itg") + .map(|itg| { + let children = itg.children().unwrap(); + let iter = children.top().iter(); + iter.filter_map(|x| parse_gallery_info(x.get(parser)?, parser)) + .collect::>() }) - }; - f().context("No content") + .filter(|v| !v.is_empty()) + .context(EhError::NoHits)?; + let prev = dom.get_element_by_id("uprev").and_then(|e| { + let str = get_node_handle_attr(&e, parser, "href")?; + Some( + regex!("prev=(\\d+(-\\d+)?)") + .captures(str)? + .index(1) + .to_string(), + ) + }); + let next = dom.get_element_by_id("unext").and_then(|e| { + let str = get_node_handle_attr(&e, parser, "href")?; + Some( + regex!("next=(\\d+(-\\d+)?)") + .captures(str)? + .index(1) + .to_string(), + ) + }); + Ok(GalleryListResult { + prev, + next, + galleryInfoList: info, + }) } diff --git a/app/src/main/rust/src/parser/test_parser.rs b/app/src/main/rust/src/parser/test_parser.rs index 6b9503bf0..e97ef432e 100644 --- a/app/src/main/rust/src/parser/test_parser.rs +++ b/app/src/main/rust/src/parser/test_parser.rs @@ -14,7 +14,7 @@ mod tests { let resp = get("https://e-hentai.org/").await.expect("Failed to get!"); let body = resp.text().await.expect("Failed to receive!"); let dom = tl::parse(&body, ParserOptions::default()).expect("Failed to parse html"); - let result = parse_info_list(&dom, dom.parser(), &body).expect("Failed to parse info list"); + let result = parse_info_list(&dom, dom.parser()).expect("Failed to parse info list"); dbg!(result); }