From 000a932ff1e00e9f1a6b2ffacd481fd55149ccac Mon Sep 17 00:00:00 2001 From: FooIbar <118464521+FooIbar@users.noreply.github.com> Date: Tue, 3 Jun 2025 23:46:55 +0800 Subject: [PATCH] Migrate profile parser to rust --- .../com/hippo/ehviewer/client/EhEngine.kt | 5 +- .../ehviewer/client/parser/ForumsParser.kt | 33 ------------- .../ehviewer/client/parser/ProfileParser.kt | 42 +++++++---------- app/src/main/rust/src/ffi/jvm.rs | 19 ++++++++ app/src/main/rust/src/parser.rs | 1 + app/src/main/rust/src/parser/profile.rs | 46 +++++++++++++++++++ app/src/main/rust/src/parser/test_parser.rs | 18 ++++++++ app/src/main/rust/test_data/profile_url.html | 3 ++ 8 files changed, 106 insertions(+), 61 deletions(-) delete mode 100644 app/src/main/kotlin/com/hippo/ehviewer/client/parser/ForumsParser.kt create mode 100644 app/src/main/rust/src/parser/profile.rs create mode 100644 app/src/main/rust/test_data/profile_url.html 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 958f29c30..8bca1ecdd 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/EhEngine.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/EhEngine.kt @@ -38,7 +38,6 @@ import com.hippo.ehviewer.client.parser.ArchiveParser import com.hippo.ehviewer.client.parser.EventPaneParser import com.hippo.ehviewer.client.parser.FavParserResult import com.hippo.ehviewer.client.parser.FavoritesParser -import com.hippo.ehviewer.client.parser.ForumsParser import com.hippo.ehviewer.client.parser.GalleryApiParser import com.hippo.ehviewer.client.parser.GalleryDetailParser import com.hippo.ehviewer.client.parser.GalleryListParser @@ -209,8 +208,8 @@ object EhEngine { .fetchUsingAsText { if (parse) EventPaneParser.parse(this) else null } suspend fun getProfile(): ProfileParser.Result { - val url = ehRequest(EhUrl.URL_FORUMS).fetchUsingAsText(ForumsParser::parse) - return ehRequest(url, EhUrl.URL_FORUMS).fetchUsingAsText(ProfileParser::parse) + val url = ehRequest(EhUrl.URL_FORUMS).fetchUsingAsByteBuffer(ProfileParser::parseProfileUrl) + return ehRequest(url, EhUrl.URL_FORUMS).fetchUsingAsByteBuffer(ProfileParser::parse) } suspend fun getUConfig(url: String = EhUrl.uConfigUrl) { diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/ForumsParser.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/ForumsParser.kt deleted file mode 100644 index d6a572b88..000000000 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/ForumsParser.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2016 Hippo Seven - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.hippo.ehviewer.client.parser - -import arrow.core.Either -import arrow.core.getOrElse -import com.hippo.ehviewer.client.EhUrl -import com.hippo.ehviewer.client.exception.NotLoggedInException -import org.jsoup.Jsoup - -object ForumsParser { - fun parse(body: String): String = Either.catch { - val d = Jsoup.parse(body, EhUrl.URL_FORUMS) - val userlinks = d.getElementById("userlinks") - val child = userlinks!!.child(0).child(0).child(0) - child.attr("href") - }.getOrElse { - throw NotLoggedInException() - } -} diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/ProfileParser.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/ProfileParser.kt index f76c4b406..bed69553f 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/ProfileParser.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/ProfileParser.kt @@ -15,36 +15,28 @@ */ package com.hippo.ehviewer.client.parser -import arrow.core.Either +import arrow.core.Either.Companion.catch import arrow.core.getOrElse -import com.hippo.ehviewer.client.EhUrl import com.hippo.ehviewer.client.exception.ParseException -import eu.kanade.tachiyomi.util.system.logcat -import org.jsoup.Jsoup +import java.nio.ByteBuffer +import kotlinx.serialization.Serializable object ProfileParser { - fun parse(body: String): Result = Either.catch { - val d = Jsoup.parse(body) - val profilename = d.getElementById("profilename") - val displayName = profilename!!.child(0).text() - val avatar = Either.catch { - val avatar = - profilename.nextElementSibling()!!.nextElementSibling()!!.child(0).attr("src") - if (avatar.isEmpty()) { - null - } else if (!avatar.startsWith("http")) { - EhUrl.URL_FORUMS + avatar - } else { - avatar - } - }.getOrElse { - logcat { "No avatar" } - null - } - Result(displayName, avatar) + fun parse(body: ByteBuffer) = catch { + unmarshalParsingAs(body, ::parseProfile) }.getOrElse { - throw ParseException("Parse forums error") + throw ParseException("Failed to parse profile", it) } - class Result(val displayName: String?, val avatar: String?) + fun parseProfileUrl(body: ByteBuffer) = catch { + unmarshalParsingAs(body, ::parseProfileUrl) + }.getOrElse { + throw ParseException("Failed to parse profile url", it) + } + + @Serializable + data class Result(val displayName: String, val avatar: String?) + + private external fun parseProfile(body: ByteBuffer, size: Int = body.limit()): Int + private external fun parseProfileUrl(body: ByteBuffer, size: Int = body.limit()): Int } diff --git a/app/src/main/rust/src/ffi/jvm.rs b/app/src/main/rust/src/ffi/jvm.rs index 05f2ed476..41b2cd601 100644 --- a/app/src/main/rust/src/ffi/jvm.rs +++ b/app/src/main/rust/src/ffi/jvm.rs @@ -5,6 +5,7 @@ use crate::parser::config::parse_fav_cat; use crate::parser::fav::parse_fav; use crate::parser::home::parse_limit; use crate::parser::list::parse_info_list; +use crate::parser::profile::{parse_profile, parse_profile_url}; use crate::parser::torrent::parse_torrent_list; use crate::EhError; use android_logger::Config; @@ -94,6 +95,24 @@ pub fn parseArchiveUrl(mut env: JNIEnv, _class: JClass, buffer: JByteBuffer, lim }) } +#[no_mangle] +#[allow(non_snake_case)] +#[jni_fn("com.hippo.ehviewer.client.parser.ProfileParser")] +pub fn parseProfileUrl(mut env: JNIEnv, _class: JClass, buffer: JByteBuffer, limit: jint) -> jint { + parse_marshal_inplace(&mut env, buffer, limit, |dom, _| { + parse_profile_url(dom, dom.parser()) + }) +} + +#[no_mangle] +#[allow(non_snake_case)] +#[jni_fn("com.hippo.ehviewer.client.parser.ProfileParser")] +pub fn parseProfile(mut env: JNIEnv, _class: JClass, buffer: JByteBuffer, limit: jint) -> jint { + parse_marshal_inplace(&mut env, buffer, limit, |dom, _| { + parse_profile(dom, dom.parser()) + }) +} + fn deref_mut_direct_bytebuffer<'local>( env: &JNIEnv, buffer: JByteBuffer<'local>, diff --git a/app/src/main/rust/src/parser.rs b/app/src/main/rust/src/parser.rs index fe38bda12..8a0a2753b 100644 --- a/app/src/main/rust/src/parser.rs +++ b/app/src/main/rust/src/parser.rs @@ -3,6 +3,7 @@ pub mod config; pub mod fav; pub mod home; pub mod list; +pub mod profile; mod test_parser; pub mod torrent; mod utils; diff --git a/app/src/main/rust/src/parser/profile.rs b/app/src/main/rust/src/parser/profile.rs new file mode 100644 index 000000000..d85419da3 --- /dev/null +++ b/app/src/main/rust/src/parser/profile.rs @@ -0,0 +1,46 @@ +use crate::{get_node_handle_attr, get_tag_attr, select_first, EhError}; +use anyhow::{Context, Result}; +use serde::Serialize; +use tl::{Parser, VDom}; + +const HOST_FORUMS: &str = "https://forums.e-hentai.org"; + +#[derive(Serialize, Debug)] +#[allow(non_snake_case)] +pub struct Profile { + pub displayName: String, + pub avatar: Option, +} + +pub fn parse_profile_url(dom: &VDom, parser: &Parser) -> Result { + dom.get_element_by_id("userlinks") + .and_then(|n| { + let mut iter = n.get(parser)?.as_tag()?.query_selector(parser, "a")?; + get_node_handle_attr(&iter.next()?, parser, "href").map(str::to_string) + }) + .context(EhError::NeedLogin) +} + +pub fn parse_profile(dom: &VDom, parser: &Parser) -> Result { + dom.get_elements_by_class_name("row1") + .next() + .and_then(|n| { + let mut iter = n.get(parser)?.as_tag()?.query_selector(parser, "div")?; + let display_name = iter.next()?.get(parser)?.inner_text(parser).to_string(); + let avatar = iter.next().and_then(|n| { + let img = select_first(n.get(parser)?.as_tag()?, parser, "img")?; + get_tag_attr(img, "src").map(|src| { + if src.starts_with("http") { + src.to_string() + } else { + format!("{}{}", HOST_FORUMS, src) + } + }) + }); + Some(Profile { + displayName: display_name, + avatar, + }) + }) + .context("Failed to parse profile") +} diff --git a/app/src/main/rust/src/parser/test_parser.rs b/app/src/main/rust/src/parser/test_parser.rs index a5cae45f2..fb59da048 100644 --- a/app/src/main/rust/src/parser/test_parser.rs +++ b/app/src/main/rust/src/parser/test_parser.rs @@ -2,6 +2,7 @@ mod tests { use crate::parser::archive::{parse_archive_url, parse_archives_with_funds}; use crate::parser::list::parse_info_list; + use crate::parser::profile::{parse_profile, parse_profile_url}; use reqwest::get; use std::fs; use tl::ParserOptions; @@ -34,4 +35,21 @@ mod tests { parse_archive_url(&dom, dom.parser(), &body).expect("Failed to parse archives"); assert_eq!(result, Some("https://0?start=1".to_string())); } + + #[tokio::test] + async fn test_parse_profile() { + let body = + fs::read_to_string("test_data/profile_url.html").expect("Failed to read html file"); + let dom = tl::parse(&body, ParserOptions::default()).expect("Failed to parse html"); + let result = parse_profile_url(&dom, dom.parser()).expect("Failed to parse profile url"); + let resp = get(result).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_profile(&dom, dom.parser()).expect("Failed to parse profile"); + assert_eq!(result.displayName, "Tenboro".to_string()); + assert_eq!( + result.avatar, + Some("https://forums.e-hentai.org/ehgt/jdk_180.png".to_string()) + ); + } } diff --git a/app/src/main/rust/test_data/profile_url.html b/app/src/main/rust/test_data/profile_url.html new file mode 100644 index 000000000..399ffb7a0 --- /dev/null +++ b/app/src/main/rust/test_data/profile_url.html @@ -0,0 +1,3 @@ +