From 4c359e50f8ad03d2d04efaa144bb58b23faf4dce Mon Sep 17 00:00:00 2001 From: FooIbar <118464521+FooIbar@users.noreply.github.com> Date: Fri, 13 Jun 2025 21:17:51 +0800 Subject: [PATCH] Migrate `GalleryDetailParser` to Rust Part 3/N --- .../com/hippo/ehviewer/client/EhEngine.kt | 6 +- .../ehviewer/client/data/GalleryPreview.kt | 8 + .../client/parser/GalleryDetailParser.kt | 18 ++- .../ehviewer/client/parser/VoteTagParser.kt | 17 ++- .../com/hippo/ehviewer/spider/SpiderQueen.kt | 26 ++-- .../ui/screen/GalleryDetailContent.kt | 2 +- app/src/main/rust/Cargo.lock | 1 + app/src/main/rust/Cargo.toml | 1 + app/src/main/rust/src/ffi/jvm.rs | 41 ++++- app/src/main/rust/src/parser.rs | 1 + app/src/main/rust/src/parser/api.rs | 18 +++ app/src/main/rust/src/parser/detail.rs | 144 +++++++++--------- app/src/main/rust/src/parser/test_parser.rs | 8 + app/src/main/rust/test_data/vote_tag.json | 1 + 14 files changed, 183 insertions(+), 109 deletions(-) create mode 100644 app/src/main/rust/src/parser/api.rs create mode 100644 app/src/main/rust/test_data/vote_tag.json 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 bfaa7cbd9..5921af45e 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/EhEngine.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/EhEngine.kt @@ -245,9 +245,7 @@ object EhEngine { } } - suspend fun getPreviewList(url: String) = ehRequest(url, EhUrl.referer).fetchUsingAsText { - GalleryDetailParser.parsePreviewList(this) - } + suspend fun getPreviewList(url: String) = ehRequest(url, EhUrl.referer).fetchUsingAsByteBuffer(GalleryDetailParser::parsePreviewsRust) suspend fun getFavorites(url: String) = ehRequest(url, EhUrl.referer).fetchUsingAsByteBuffer(FavoritesParser::parse) .apply { galleryInfoList.fillInfo(url) } @@ -426,7 +424,7 @@ object EhEngine { put("tags", tags) put("vote", vote) } - }.fetchUsingAsText(VoteTagParser::parse) + }.fetchUsingAsByteBuffer(VoteTagParser::parse) suspend fun getGalleryToken(gid: Long, gtoken: String, page: Int) = ehRequest(EhUrl.apiUrl, EhUrl.referer, EhUrl.origin) { jsonBody { diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryPreview.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryPreview.kt index 52459798f..df79126c1 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryPreview.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryPreview.kt @@ -19,6 +19,7 @@ import com.hippo.ehviewer.client.getThumbKey import com.hippo.ehviewer.client.getV2PreviewKey import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.cbor.CborArray @Serializable sealed interface GalleryPreview { @@ -50,3 +51,10 @@ data class V2GalleryPreview( ) : GalleryPreview { override val imageKey get() = getV2PreviewKey(url) } + +@Serializable +@CborArray +class GalleryPreviewList( + val previews: List, + val total: Int, +) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/GalleryDetailParser.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/GalleryDetailParser.kt index e2b8e79ca..1808b5ad0 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/GalleryDetailParser.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/GalleryDetailParser.kt @@ -22,6 +22,7 @@ import com.hippo.ehviewer.client.data.BaseGalleryInfo import com.hippo.ehviewer.client.data.GalleryComment import com.hippo.ehviewer.client.data.GalleryCommentList import com.hippo.ehviewer.client.data.GalleryDetail +import com.hippo.ehviewer.client.data.GalleryPreviewList import com.hippo.ehviewer.client.data.GalleryTag import com.hippo.ehviewer.client.data.GalleryTagGroup import com.hippo.ehviewer.client.data.PowerStatus @@ -404,21 +405,28 @@ object GalleryDetailParser { } fun parseRust(body: ByteBuffer) = Either.catch { - unmarshalParsingAs(body, ::parseGalleryDetail) + unmarshalParsingAs(body, ::nativeParse) }.getOrElse { throw ParseException("Failed to parse gallery detail", it) } fun parseCommentsRust(body: ByteBuffer) = Either.catch { - unmarshalParsingAs(body, ::parseGalleryComments) + unmarshalParsingAs(body, ::nativeParseComments) }.getOrElse { - throw ParseException("Failed to parse gallery comments", it) + throw ParseException("Failed to parse comments", it) + } + + fun parsePreviewsRust(body: ByteBuffer) = Either.catch { + unmarshalParsingAs(body, ::nativeParsePreviews) + }.getOrElse { + throw ParseException("Failed to parse previews", it) } @Serializable @CborArray class Result(val detail: GalleryDetail, val event: String?) - private external fun parseGalleryDetail(body: ByteBuffer, size: Int = body.limit()): Int - private external fun parseGalleryComments(body: ByteBuffer, size: Int = body.limit()): Int + private external fun nativeParse(body: ByteBuffer, size: Int = body.limit()): Int + private external fun nativeParseComments(body: ByteBuffer, size: Int = body.limit()): Int + private external fun nativeParsePreviews(body: ByteBuffer, size: Int = body.limit()): Int } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/VoteTagParser.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/VoteTagParser.kt index 23a8ae30e..75e52bf76 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/VoteTagParser.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/VoteTagParser.kt @@ -17,15 +17,18 @@ */ package com.hippo.ehviewer.client.parser +import arrow.core.Either.Companion.catch +import arrow.core.getOrElse import com.hippo.ehviewer.client.data.GalleryTagGroup -import com.hippo.ehviewer.client.parseAs -import org.jsoup.Jsoup +import com.hippo.ehviewer.client.exception.ParseException +import java.nio.ByteBuffer object VoteTagParser { - // {"error":"The tag \"neko\" is not allowed. Use character:neko or artist:neko"} - fun parse(body: String): List = body.parseAs().let { (error, tagpane) -> - error?.let { error(error) } - checkNotNull(tagpane) - GalleryDetailParser.parseTagGroups(Jsoup.parse("
$tagpane
")) + fun parse(body: ByteBuffer) = catch { + unmarshalParsingAs>(body, ::nativeParse) + }.getOrElse { + throw ParseException("Failed to parse vote tag result", it) } + + private external fun nativeParse(body: ByteBuffer, size: Int = body.limit()): Int } 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 101b59fdb..b863adcd6 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderQueen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderQueen.kt @@ -29,11 +29,10 @@ 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.GalleryDetailParser.parsePages -import com.hippo.ehviewer.client.parser.GalleryDetailParser.parsePreviewList import com.hippo.ehviewer.client.parser.GalleryMultiPageViewerPTokenParser import com.hippo.ehviewer.util.displayString import com.hippo.files.find @@ -318,27 +317,24 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS } ?: readFromCache(galleryInfo.gid)?.takeIf { it.gid == galleryInfo.gid && it.token == galleryInfo.token } - private fun readPreviews(body: String, index: Int, spiderInfo: SpiderInfo) { - val previewList = parsePreviewList(body) + private fun readPreviews(previews: List, index: Int, spiderInfo: SpiderInfo) { if (index == 0) { - spiderInfo.previewPerPage = previewList.size + spiderInfo.previewPerPage = previews.size } else { - spiderInfo.previewPerPage = previewList[0].position / index + spiderInfo.previewPerPage = previews[0].position / index } - previewList.forEach { + previews.forEach { if (it.pToken.isNotEmpty()) { spiderInfo.pTokenMap[it.position] = it.pToken } } } - private suspend fun readSpiderInfoFromInternet() = ehRequest( + private suspend fun readSpiderInfoFromInternet() = EhEngine.getPreviewList( getGalleryDetailUrl(galleryInfo.gid, galleryInfo.token, 0, false), - referer, - ).fetchUsingAsText { - val pages = parsePages(this) - val spiderInfo = SpiderInfo(galleryInfo.gid, galleryInfo.token, pages) - readPreviews(this, 0, spiderInfo) + ).run { + val spiderInfo = SpiderInfo(galleryInfo.gid, galleryInfo.token, total) + readPreviews(previews, 0, spiderInfo) spiderInfo } @@ -376,8 +372,8 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS false, ) return runSuspendCatching { - ehRequest(url, referer).fetchUsingAsText { - readPreviews(this, previewIndex, spiderInfo) + EhEngine.getPreviewList(url).run { + readPreviews(previews, previewIndex, spiderInfo) spiderInfo.pTokenMap[index] } }.getOrElse { diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailContent.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailContent.kt index c4cace82e..6cb71dab2 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailContent.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailContent.kt @@ -804,7 +804,7 @@ private fun GalleryDetail.collectPreviewItems() = rememberInVM(previewList) { (up..end).filterNot { it in previewPagesMap }.map { it / pageSize }.toSet() .parMap(concurrency = Settings.multiThreadDownload) { page -> val url = EhUrl.getGalleryDetailUrl(gid, token, page, false) - EhEngine.getPreviewList(url) + EhEngine.getPreviewList(url).previews }.flattenForEach { previewPagesMap[it.position] = it if (Settings.preloadThumbAggressively) { diff --git a/app/src/main/rust/Cargo.lock b/app/src/main/rust/Cargo.lock index 402864387..4779e5069 100644 --- a/app/src/main/rust/Cargo.lock +++ b/app/src/main/rust/Cargo.lock @@ -281,6 +281,7 @@ dependencies = [ "rxing", "serde", "serde_cbor", + "serde_json", "tl", "tokio", ] diff --git a/app/src/main/rust/Cargo.toml b/app/src/main/rust/Cargo.toml index 9e01f3815..5bd7ddd3a 100644 --- a/app/src/main/rust/Cargo.toml +++ b/app/src/main/rust/Cargo.toml @@ -21,6 +21,7 @@ tl = { git = "https://github.com/y21/tl.git", rev = "13ed128e35ce04c4f16c41cb1e3 log = "0.4" android_logger = { default-features = false, version = "0.15" } serde_cbor = "0.11" +serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } anyhow = "1.0" image = { default-features = false, version = "0.25" } diff --git a/app/src/main/rust/src/ffi/jvm.rs b/app/src/main/rust/src/ffi/jvm.rs index 9e80cfac5..88ffa5981 100644 --- a/app/src/main/rust/src/ffi/jvm.rs +++ b/app/src/main/rust/src/ffi/jvm.rs @@ -1,9 +1,11 @@ #![cfg(feature = "jvm")] use crate::EhError; +use crate::parser::api::parse_vote_tag; use crate::parser::archive::{parse_archive_url, parse_archives, parse_archives_with_funds}; use crate::parser::config::parse_fav_cat; use crate::parser::detail::{parse_comments, parse_event_pane, parse_gallery_detail}; +use crate::parser::detail::{parse_pages, parse_preview_list}; use crate::parser::fav::parse_fav; use crate::parser::home::parse_limit; use crate::parser::list::parse_info_list; @@ -45,7 +47,7 @@ pub fn parseGalleryInfoList(mut env: JNIEnv, _: JClass, buffer: JByteBuffer, lim } #[jni_fn("com.hippo.ehviewer.client.parser.GalleryDetailParser")] -pub fn parseGalleryDetail(mut env: JNIEnv, _: JClass, buffer: JByteBuffer, limit: jint) -> jint { +pub fn nativeParse(mut env: JNIEnv, _: JClass, buffer: JByteBuffer, limit: jint) -> jint { let options = ParserOptions::default().track_ids(); parse_marshal_inplace_with_options(&mut env, buffer, limit, options, |dom, html| { parse_gallery_detail(dom, html).map(|detail| (detail, parse_event_pane(dom, dom.parser()))) @@ -53,13 +55,29 @@ pub fn parseGalleryDetail(mut env: JNIEnv, _: JClass, buffer: JByteBuffer, limit } #[jni_fn("com.hippo.ehviewer.client.parser.GalleryDetailParser")] -pub fn parseGalleryComments(mut env: JNIEnv, _: JClass, buffer: JByteBuffer, limit: jint) -> jint { +pub fn nativeParseComments(mut env: JNIEnv, _: JClass, buffer: JByteBuffer, limit: jint) -> jint { let options = ParserOptions::default().track_ids(); parse_marshal_inplace_with_options(&mut env, buffer, limit, options, |dom, _| { parse_comments(dom) }) } +#[jni_fn("com.hippo.ehviewer.client.parser.GalleryDetailParser")] +pub fn nativeParsePreviews(mut env: JNIEnv, _: JClass, buffer: JByteBuffer, limit: jint) -> jint { + let options = ParserOptions::default().track_ids(); + parse_marshal_inplace_with_options(&mut env, buffer, limit, options, |dom, _| { + Ok(( + parse_preview_list(dom, dom.parser())?, + parse_pages(dom, dom.parser())?, + )) + }) +} + +#[jni_fn("com.hippo.ehviewer.client.parser.VoteTagParser")] +pub fn nativeParse(mut env: JNIEnv, _: JClass, buffer: JByteBuffer, limit: jint) -> jint { + parse_raw_marshal_inplace(&mut env, buffer, limit, parse_vote_tag) +} + #[jni_fn("com.hippo.ehviewer.client.parser.EventPaneParser")] pub fn parseEventPane(mut env: JNIEnv, _class: JClass, input: JByteBuffer, limit: jint) -> jint { parse_marshal_inplace(&mut env, input, limit, |dom, _| { @@ -190,16 +208,25 @@ pub fn parse_marshal_inplace_with_options( where F: Fn(&mut VDom, &str) -> Result, R: Serialize, +{ + parse_raw_marshal_inplace(env, str, limit, |html| { + let mut dom = tl::parse(html, options)?; + ensure!(dom.version().is_some(), EhError::Error(html.to_string())); + f(&mut dom, html) + }) +} + +pub fn parse_raw_marshal_inplace(env: &mut JNIEnv, str: JByteBuffer, limit: jint, f: F) -> i32 +where + F: Fn(&str) -> Result, + R: Serialize, { jni_throwing(env, |env| { let buffer = deref_mut_direct_bytebuffer(env, str)?; let value = { // SAFETY: ktor client ensure html content is valid utf-8. - let html = unsafe { from_utf8_unchecked(&buffer[..limit as usize]) }; - - let mut dom = tl::parse(html, options)?; - ensure!(dom.version().is_some(), EhError::Error(html.to_string())); - f(&mut dom, html)? + let body = unsafe { from_utf8_unchecked(&buffer[..limit as usize]) }; + f(body)? }; let mut cursor = Cursor::new(buffer); serde_cbor::to_writer(&mut cursor, &value)?; diff --git a/app/src/main/rust/src/parser.rs b/app/src/main/rust/src/parser.rs index 59662692f..a4330ad48 100644 --- a/app/src/main/rust/src/parser.rs +++ b/app/src/main/rust/src/parser.rs @@ -1,3 +1,4 @@ +pub mod api; pub mod archive; pub mod config; pub mod detail; diff --git a/app/src/main/rust/src/parser/api.rs b/app/src/main/rust/src/parser/api.rs new file mode 100644 index 000000000..3baa48e6c --- /dev/null +++ b/app/src/main/rust/src/parser/api.rs @@ -0,0 +1,18 @@ +use crate::EhError; +use crate::parser::detail::{GalleryTagGroup, parse_tag_groups}; +use anyhow::{Context, Result, bail}; +use serde_json::Value; +use tl::ParserOptions; + +pub fn parse_vote_tag(str: &str) -> Result> { + let result = serde_json::from_str::(str)?; + if let Some(error) = result.get("error").and_then(Value::as_str) { + bail!(EhError::Error(error.to_string())) + }; + let tagpane = result + .get("tagpane") + .and_then(Value::as_str) + .context("Failed to get tagpane")?; + let dom = tl::parse(tagpane, ParserOptions::default())?; + parse_tag_groups(&dom, dom.parser(), true) +} diff --git a/app/src/main/rust/src/parser/detail.rs b/app/src/main/rust/src/parser/detail.rs index 05145ddaf..953288259 100644 --- a/app/src/main/rust/src/parser/detail.rs +++ b/app/src/main/rust/src/parser/detail.rs @@ -55,7 +55,7 @@ pub struct GalleryTagGroup { pub tags: Vec, } -#[derive(Serialize, Debug)] +#[derive(Serialize, Debug, Default)] #[allow(non_snake_case)] pub struct GalleryComment { pub id: i64, @@ -80,7 +80,7 @@ pub struct GalleryCommentList { pub hasMore: bool, } -#[derive(Serialize, Debug)] +#[derive(Serialize, Debug, Default)] #[allow(non_snake_case)] pub struct PreviewData { pub url: String, @@ -248,9 +248,9 @@ pub fn parse_gallery_detail(dom: &mut VDom, body: &str) -> Result } let mut gallery_detail = GalleryDetail { - tagGroups: parse_tag_groups(dom, parser).context("Failed to parse tag groups")?, - previewList: parse_preview_list(dom, parser).context("Failed to parse preview list")?, - comments: parse_comments(dom).context("Failed to parse comments")?, + tagGroups: parse_tag_groups(dom, parser, false)?, + previewList: parse_preview_list(dom, parser)?, + comments: parse_comments(dom)?, ..Default::default() }; @@ -266,11 +266,23 @@ pub fn parse_event_pane(dom: &VDom, parser: &Parser) -> Option { .map(|n| n.inner_html(parser).to_string()) } -fn parse_tag_groups(dom: &VDom, parser: &Parser) -> Option> { - dom.get_element_by_id("taglist")? - .get(parser)? - .as_tag()? - .query_selector(parser, "tr")? +pub fn parse_tag_groups( + dom: &VDom, + parser: &Parser, + is_root: bool, +) -> Result> { + let nodes: Vec<_> = if is_root { + dom.query_selector("tr").unwrap().collect() + } else { + dom.get_element_by_id("taglist") + .and_then(|n| n.get(parser)?.as_tag()) + .context("Failed to find taglist")? + .query_selector(parser, "tr") + .unwrap() + .collect() + }; + nodes + .iter() .filter_map(|n| n.get(parser)?.children()) .map(|children| { let mut iter = children @@ -317,6 +329,7 @@ fn parse_tag_groups(dom: &VDom, parser: &Parser) -> Option> Some(GalleryTagGroup { namespace, tags }) }) .collect::>>() + .context("Failed to parse tag groups") } fn parse_comment_time(str: &str, prefix: &str) -> Option { @@ -363,22 +376,18 @@ pub fn parse_comments(dom: &mut VDom) -> Result { .map(|node_handle| { let parser = dom.parser_mut(); let node = node_handle.get(parser).unwrap(); + let mut c = GalleryComment::default(); let c3 = get_first_element_by_class_name(node, parser, "c3") .and_then(Node::as_tag) .context("Failed to find c3")?; let posted_node = c3.children().top()[0].get(parser).unwrap(); - let posted = parse_comment_time(&posted_node.inner_text(parser), "Posted on ") + c.time = parse_comment_time(&posted_node.inner_text(parser), "Posted on ") .context("Failed to parse posted date")?; - let user = get_first_child(c3, parser) + c.user = get_first_child(c3, parser) .map(|n| unescape(&n.inner_text(parser)).map(|s| s.to_string())) .transpose()?; - let mut editable = false; - let mut vote_up_able = false; - let mut vote_up_ed = false; - let mut vote_down_able = false; - let mut vote_down_ed = false; if let Some(c4) = get_first_element_by_class_name(node, parser, "c4").and_then(Node::as_tag) { @@ -388,19 +397,19 @@ pub fn parse_comments(dom: &mut VDom) -> Result { .filter_map(|n| n.get(parser)?.as_tag()) .for_each(|tag| match tag.inner_text(parser).as_ref() { "Vote+" => { - vote_up_able = true; - vote_up_ed = tag.attributes().contains("style"); + c.voteUpAble = true; + c.voteUpEd = tag.attributes().contains("style"); } "Vote-" => { - vote_down_able = true; - vote_down_ed = tag.attributes().contains("style"); + c.voteDownAble = true; + c.voteDownEd = tag.attributes().contains("style"); } - "Edit" => editable = true, + "Edit" => c.editable = true, _ => {} }) }; - let score = get_first_element_by_class_name(node, parser, "c5") + c.score = get_first_element_by_class_name(node, parser, "c5") .and_then(|c5| { get_first_child(c5.as_tag()?, parser)? .inner_text(parser) @@ -409,11 +418,11 @@ pub fn parse_comments(dom: &mut VDom) -> Result { }) .unwrap_or(0); - let vote_state = get_first_element_by_class_name(node, parser, "c7") + c.voteState = get_first_element_by_class_name(node, parser, "c7") .map(|c7| unescape(&c7.inner_text(parser)).map(|s| s.to_string())) .transpose()?; - let last_edited = get_first_element_by_class_name(node, parser, "c8") + c.lastEdited = get_first_element_by_class_name(node, parser, "c8") .and_then(|c8| parse_comment_time(&c8.inner_text(parser), "Last edited on ")) .unwrap_or(0); @@ -434,28 +443,15 @@ pub fn parse_comments(dom: &mut VDom) -> Result { tag.attributes_mut().remove("style"); } let c6 = c6_handle.get(parser).and_then(Node::as_tag).unwrap(); - let id = c6 + c.id = c6 .attributes() .id() .and_then(|id| id.try_as_utf8_str()?.strip_prefix("comment_")?.parse().ok()) .context("Failed to parse comment id")?; - let comment = c6.inner_html(parser).to_string(); + c.uploader = c.id == 0; + c.comment = c6.inner_html(parser).to_string(); - Ok(GalleryComment { - id, - score, - editable, - voteUpAble: vote_up_able, - voteUpEd: vote_up_ed, - voteDownAble: vote_down_able, - voteDownEd: vote_down_ed, - uploader: id == 0, - voteState: vote_state, - time: posted, - user, - comment, - lastEdited: last_edited, - }) + Ok(c) }) .collect::>>()?; @@ -465,10 +461,28 @@ pub fn parse_comments(dom: &mut VDom) -> Result { }) } -fn parse_preview_list(dom: &VDom, parser: &Parser) -> Option> { - dom.get_element_by_id("gdt")? - .get(parser)? - .children()? +pub fn parse_pages(dom: &VDom, parser: &Parser) -> Result { + dom.get_element_by_id("gdd") + .and_then(|gdd| { + gdd.get(parser)? + .as_tag()? + .query_selector(parser, "td.gdt2")? + .find_map(|n| { + n.get(parser)?.children()?.top()[0] + .get(parser)? + .inner_text(parser) + .strip_suffix(" pages")? + .parse() + .ok() + }) + }) + .context("Failed to parse pages") +} + +pub fn parse_preview_list(dom: &VDom, parser: &Parser) -> Result> { + dom.get_element_by_id("gdt") + .and_then(|n| n.get(parser)?.children()) + .context("Failed to find gdt")? .top() .iter() .filter_map(|n| n.get(parser)?.as_tag()) @@ -482,33 +496,23 @@ fn parse_preview_list(dom: &VDom, parser: &Parser) -> Option let style_regex = regex!(r#"\D+(\d+)\D+(\d+)[^(]+\(([^)]+)\)(?: -(\d+))?"#); let caps = style_regex.captures(get_node_handle_attr(&div, parser, "style")?)?; let url = caps[3].to_string(); - let preview = if let Some(offset) = caps.get(4) { - GalleryPreview( - "V2".to_string(), - PreviewData { - url, - position, - pToken: p_token, - offsetX: offset.as_str().parse().ok(), - clipWidth: caps[1].parse().ok(), - clipHeight: caps[2].parse().ok(), - }, - ) + let mut data = PreviewData { + url, + position, + pToken: p_token, + ..Default::default() + }; + let ty = if let Some(offset) = caps.get(4) { + data.offsetX = offset.as_str().parse().ok(); + data.clipWidth = caps[1].parse().ok(); + data.clipHeight = caps[2].parse().ok(); + "V2" } else { - GalleryPreview( - "V1".to_string(), - PreviewData { - url, - position, - pToken: p_token, - offsetX: None, - clipWidth: None, - clipHeight: None, - }, - ) + "V1" }; - Some(preview) + Some(GalleryPreview(ty.to_string(), data)) }) .collect::>>() + .context("Failed to parse preview list") } diff --git a/app/src/main/rust/src/parser/test_parser.rs b/app/src/main/rust/src/parser/test_parser.rs index 8d19eef08..6b9503bf0 100644 --- a/app/src/main/rust/src/parser/test_parser.rs +++ b/app/src/main/rust/src/parser/test_parser.rs @@ -1,5 +1,6 @@ #[cfg(test)] mod tests { + use crate::parser::api::parse_vote_tag; use crate::parser::archive::{parse_archive_url, parse_archives_with_funds}; use crate::parser::detail::parse_gallery_detail; use crate::parser::list::parse_info_list; @@ -65,4 +66,11 @@ mod tests { let result = parse_gallery_detail(&mut dom, &body).expect("Failed to parse gallery detail"); dbg!(result); } + + #[test] + fn test_parse_tag_gallery() { + let json = fs::read_to_string("test_data/vote_tag.json").expect("Failed to read json file"); + let result = parse_vote_tag(&json).expect_err("Should fail to parse"); + dbg!(result); + } } diff --git a/app/src/main/rust/test_data/vote_tag.json b/app/src/main/rust/test_data/vote_tag.json new file mode 100644 index 000000000..de9f07e60 --- /dev/null +++ b/app/src/main/rust/test_data/vote_tag.json @@ -0,0 +1 @@ +{"error":"The tag \"neko\" has been blocked for use. Did you mean \"character:neko\" or \"artist:neko\" ?","tagpane":"No tags have been added for this gallery yet."} \ No newline at end of file