Migrate profile parser to rust

This commit is contained in:
FooIbar committed 2025-06-03 23:58:52 +08:00
1 parent 60cd7728e6
commit 000a932ff1
8 files changed
+106 -61

No files matched your search

@@ -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) {
@@ -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()
}
}
@@ -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<Result>(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<String>(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
}
+19
View File
@@ -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>,
+1
View File
@@ -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;
+46
View File
@@ -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<String>,
}
pub fn parse_profile_url(dom: &VDom, parser: &Parser) -> Result<String> {
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<Profile> {
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")
}
@@ -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())
);
}
}
@@ -0,0 +1,3 @@
<div id="userlinks">
<p class="home"><b>Logged in as: <a href="https://forums.e-hentai.org/index.php?showuser=6">Tenboro</a></b> ( <a href="https://forums.e-hentai.org/index.php?act=Login&amp;CODE=03">Log Out</a> )</p>
</div>