From 95631ef468f319d5f033893ae2fa932a961aa0fc Mon Sep 17 00:00:00 2001 From: FooIbar <118464521+FooIbar@users.noreply.github.com> Date: Sat, 14 Jun 2025 18:33:46 +0800 Subject: [PATCH] Remove jsoup usage and clean up --- README.md | 1 - app/build.gradle.kts | 1 - .../com/hippo/ehviewer/client/EhEngine.kt | 8 ++++--- .../kotlin/com/hippo/ehviewer/client/EhUrl.kt | 7 +++++- .../ehviewer/client/data/GalleryDetail.kt | 2 -- .../ehviewer/client/parser/HomeParser.kt | 6 ++--- .../ehviewer/client/parser/ParserUtils.kt | 8 +------ .../ehviewer/ui/GalleryInfoBottomSheet.kt | 2 +- .../ui/screen/GalleryDetailContent.kt | 4 ++-- .../com/hippo/ehviewer/util/StringUtils.kt | 17 +++++++------- app/src/main/rust/src/parser/detail.rs | 22 ++++++------------- app/src/main/rust/src/parser/list.rs | 17 +++++++------- app/src/main/rust/src/parser/profile.rs | 4 +++- docs/README/ja.md | 1 - docs/README/zh-cn.md | 1 - docs/README/zh-tw.md | 1 - gradle/libs.versions.toml | 1 - 17 files changed, 46 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 3528645cc..585bc34f8 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,6 @@ Here is the libraries - [Coil](https://coil-kt.github.io/coil/) - [Compose Destinations](https://composedestinations.rafaelcosta.xyz/) - [libarchive](https://www.libarchive.org/) -- [jsoup](https://jsoup.org/) # License diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b789687bb..1c48b0c10 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -240,7 +240,6 @@ dependencies { implementation(libs.kotlinx.coroutines.android) implementation(libs.kotlinx.datetime) - implementation(libs.jsoup) coreLibraryDesugaring(libs.desugar) 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 8099f7370..e871cf58d 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/EhEngine.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/EhEngine.kt @@ -178,7 +178,8 @@ object EhEngine { location.takeIf { "bounce_login" !in it } ?: throw NotLoggedInException() } - suspend fun getTorrentList(url: String, gid: Long, token: String): TorrentResult { + suspend fun getTorrentList(gid: Long, token: String): TorrentResult { + val url = EhUrl.getTorrentUrl(gid, token) val referer = EhUrl.getGalleryDetailUrl(gid, token) return ehRequest(url, referer).fetchUsingAsByteBuffer(TorrentParser::parse) } @@ -188,7 +189,8 @@ object EhEngine { "${id}x$key" } - suspend fun getArchiveList(url: String, gid: Long, token: String): ArchiveParser.Result { + suspend fun getArchiveList(gid: Long, token: String): ArchiveParser.Result { + val url = EhUrl.getArchiveUrl(gid, token) val funds = if (EhUtils.isExHentai) getFunds() else null return ehRequest(url, EhUrl.getGalleryDetailUrl(gid, token)) .fetchUsingAsByteBuffer(ArchiveParser::parse.partially2(funds)) @@ -304,7 +306,7 @@ object EhEngine { .fetchUsingAsText(FavoritesParser::parseNote) suspend fun downloadArchive(gid: Long, token: String, res: String, isHath: Boolean): String? { - val url = EhUrl.getDownloadArchive(gid, token) + val url = EhUrl.getArchiveUrl(gid, token) val referer = EhUrl.getGalleryDetailUrl(gid, token) val request = ehRequest(url, referer, EhUrl.origin) { formBody { diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/EhUrl.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/EhUrl.kt index a67c46167..58d50b3f7 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/EhUrl.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/EhUrl.kt @@ -125,7 +125,12 @@ object EhUrl { addQueryParameter("act", "addfav") }.buildString() - fun getDownloadArchive(gid: Long, token: String) = ehUrl("archiver.php") { + fun getArchiveUrl(gid: Long, token: String) = ehUrl("archiver.php") { + addQueryParameter("gid", "$gid") + addQueryParameter("token", token) + }.buildString() + + fun getTorrentUrl(gid: Long, token: String) = ehUrl("gallerytorrents.php") { addQueryParameter("gid", "$gid") addQueryParameter("token", token) }.buildString() diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryDetail.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryDetail.kt index afef0b39f..092e2c862 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryDetail.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryDetail.kt @@ -44,8 +44,6 @@ data class GalleryDetail( var apiUid: Long = -1L, var apiKey: String? = null, var torrentCount: Int = 0, - var torrentUrl: String? = null, - var archiveUrl: String? = null, var parent: String? = null, var newerVersions: List = emptyList(), var visible: String? = null, diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/HomeParser.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/HomeParser.kt index 1b965b808..cb71d906e 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/HomeParser.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/HomeParser.kt @@ -25,9 +25,9 @@ object HomeParser { ?: throw ParseException("Parse torrent key error") fun parseFunds(body: String): Funds { - FundsRegex.find(body)?.groupValues?.run { - val fundsC = ParserUtils.parseInt(get(1), 0) - val fundsGP = ParserUtils.parseInt(get(2), 0) + FundsRegex.find(body)?.run { + val fundsC = ParserUtils.parseInt(groupValues[1], 0) + val fundsGP = ParserUtils.parseInt(groupValues[2], 0) return Funds(fundsGP, fundsC) } throw ParseException("Parse funds error") diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/ParserUtils.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/ParserUtils.kt index cf80e0415..b184ee773 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/ParserUtils.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/ParserUtils.kt @@ -18,8 +18,6 @@ package com.hippo.ehviewer.client.parser import com.hippo.ehviewer.util.toEpochMillis import com.hippo.ehviewer.util.toIntOrDefault import com.hippo.ehviewer.util.toLocalDateTime -import com.hippo.ehviewer.util.toLongOrDefault -import com.hippo.ehviewer.util.unescapeXml import kotlinx.datetime.LocalDateTime import kotlinx.datetime.format.char @@ -41,9 +39,5 @@ object ParserUtils { fun parseDate(str: String): Long = formatter.parse(str).toEpochMillis() - fun trim(str: String?): String = str?.unescapeXml()?.trim().orEmpty() - - fun parseInt(str: String?, defValue: Int): Int = trim(str).replace(",", "").toIntOrDefault(defValue) - - fun parseLong(str: String?, defValue: Long): Long = trim(str).replace(",", "").toLongOrDefault(defValue) + fun parseInt(str: String, defValue: Int): Int = str.replace(",", "").toIntOrDefault(defValue) } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/GalleryInfoBottomSheet.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/GalleryInfoBottomSheet.kt index dc9835d9c..d1937a35d 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/GalleryInfoBottomSheet.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/GalleryInfoBottomSheet.kt @@ -58,7 +58,7 @@ private fun GalleryDetail.content() = arrayOf( R.string.key_rating_count to ratingCount.toString(), R.string.key_rating to rating.toString(), R.string.key_torrents to torrentCount.toString(), - R.string.key_torrent_url to torrentUrl, + R.string.key_torrent_url to EhUrl.getTorrentUrl(gid, token), R.string.favorite_name to favoriteName, ) 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 6cb71dab2..d7b7a95ec 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 @@ -551,7 +551,7 @@ fun BelowHeader(galleryDetail: GalleryDetail, voteTag: VoteTag) { val archiveResult = remember(galleryDetail) { async(Dispatchers.IO + Job(), CoroutineStart.LAZY) { with(galleryDetail) { - EhEngine.getArchiveList(archiveUrl!!, gid, token) + EhEngine.getArchiveList(gid, token) } } } @@ -598,7 +598,7 @@ fun BelowHeader(galleryDetail: GalleryDetail, voteTag: VoteTag) { val torrentResult = remember(galleryDetail) { async(Dispatchers.IO + Job(), CoroutineStart.LAZY) { parZip( - { EhEngine.getTorrentList(galleryDetail.torrentUrl!!, galleryDetail.gid, galleryDetail.token) }, + { EhEngine.getTorrentList(galleryDetail.gid, galleryDetail.token) }, { EhEngine.getTorrentKey() }, { list, key -> list to key }, ) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/util/StringUtils.kt b/app/src/main/kotlin/com/hippo/ehviewer/util/StringUtils.kt index 7722fb2b6..33c22a1b9 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/util/StringUtils.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/util/StringUtils.kt @@ -1,15 +1,16 @@ package com.hippo.ehviewer.util -import org.jsoup.parser.Parser +private val EntityRegex = "&(?:amp|lt|gt|quot|#039);".toRegex() +private val EntityMap = mapOf( + "&" to "&", + "<" to "<", + ">" to ">", + """ to "\"", + "'" to "'", // HTML uses ' instead of ' +) -fun String.unescapeXml(): String = Parser.unescapeEntities(this, true) - -inline infix fun CharSequence.trimAnd(block: CharSequence.() -> T): T = block(trim()) +fun String.unescapeXml() = replace(EntityRegex) { EntityMap[it.value]!! } fun String.toIntOrDefault(defaultValue: Int): Int = toIntOrNull() ?: defaultValue -fun String.toLongOrDefault(defaultValue: Long): Long = toLongOrNull() ?: defaultValue - -fun String.toFloatOrDefault(defaultValue: Float): Float = toFloatOrNull() ?: defaultValue - fun String?.containsIgnoreCase(other: String) = this?.contains(other, true) == true diff --git a/app/src/main/rust/src/parser/detail.rs b/app/src/main/rust/src/parser/detail.rs index 953288259..1c143bf42 100644 --- a/app/src/main/rust/src/parser/detail.rs +++ b/app/src/main/rust/src/parser/detail.rs @@ -14,8 +14,6 @@ pub struct GalleryDetail { pub apiUid: i64, pub apiKey: Option, pub torrentCount: i32, - pub torrentUrl: Option, - pub archiveUrl: Option, pub parent: Option, pub newerVersions: Vec, pub visible: Option, @@ -148,22 +146,16 @@ fn parse_detail(gd: &mut GalleryDetail, dom: &VDom, parser: &Parser, body: &str) gd.apiKey = Some(caps[4].to_string()); gd.galleryInfo.rating = caps[5].parse().ok()?; - let torrent_regex = regex!( - r#"]*onclick="return popUp\('([^']+)'[^)]+\)">Torrent Download \((\d+)\)"# - ); - let caps = torrent_regex.captures(body)?; - gd.torrentUrl = unescape(&caps[1]).ok().map(|s| s.to_string()); - gd.torrentCount = caps[2].parse().ok()?; - - let archive_regex = - regex!(r#"]*onclick="return popUp\('([^']+)'[^)]+\)">Archive Download"#); - let caps = archive_regex.captures(body)?; - gd.archiveUrl = unescape(&caps[1]).ok().map(|s| s.to_string()); + let regex = regex!(r#"[^(]+\(([^)]+)"#); + let gd5 = dom.get_element_by_id("gd5")?.get(parser)?.as_tag()?; + let node = gd5.query_selector(parser, "a[onclick]")?.nth(1)?; + let text = node.get(parser)?.inner_text(parser); + let caps = regex.captures(&text)?; + gd.torrentCount = caps[1].parse().ok()?; let gd1 = dom.get_element_by_id("gd1")?.get(parser)?.as_tag()?; let style = get_tag_attr(get_first_child(gd1, parser)?, "style")?; - let cover_regex = regex!(r#"[^(]+\(([^)]+)"#); - let caps = cover_regex.captures(style)?; + let caps = regex.captures(style)?; gd.galleryInfo.thumbKey = get_thumb_key(&caps[1]); let title = dom.get_element_by_id("gn")?.get(parser)?.inner_text(parser); diff --git a/app/src/main/rust/src/parser/list.rs b/app/src/main/rust/src/parser/list.rs index c4f453e17..7d5030a8a 100644 --- a/app/src/main/rust/src/parser/list.rs +++ b/app/src/main/rust/src/parser/list.rs @@ -96,11 +96,8 @@ fn parse_uploader_and_pages(str: &str) -> (Option, bool, i32) { regex!(r#"(.*?)|(\(Disowned\))"#) .captures(str) .map(|grp| { - grp.get(1) - .or_else(|| grp.get(2)) - .unwrap() - .as_str() - .to_string() + let str = grp.get(1).or_else(|| grp.get(2)).unwrap().as_str(); + unescape(str).as_deref().unwrap_or(str).to_string() }); let pages = match regex!(r"
(\d+) pages
").captures(str) { None => 0, @@ -155,7 +152,8 @@ fn parse_gallery_info(node: &Node, parser: &Parser) -> Option { None => ("".to_string(), None), Some(node) => ( node.inner_text(parser).trim().to_string(), - get_node_attr(node, "title").map(str::to_string), + get_node_attr(node, "title") + .map(|s| unescape(s).as_deref().unwrap_or(s).to_string()), ), }; let ir = get_first_element_by_class_name(node, parser, "ir")? @@ -168,8 +166,11 @@ fn parse_gallery_info(node: &Node, parser: &Parser) -> Option { None => (None, false, 0), Some(node) => parse_uploader_and_pages(&node.get(parser)?.inner_html(parser)), }; - let favorite_note = get_element_by_id(node, parser, format!("favnote_{gid}").as_str()) - .map(|e| e.inner_text(parser).to_string()); + let favorite_note = + get_element_by_id(node, parser, format!("favnote_{gid}").as_str()).map(|e| { + let str = e.inner_text(parser); + unescape(&str).as_deref().unwrap_or(&str).to_string() + }); Some(BaseGalleryInfo { gid, token, diff --git a/app/src/main/rust/src/parser/profile.rs b/app/src/main/rust/src/parser/profile.rs index 6410332f7..21d0416a4 100644 --- a/app/src/main/rust/src/parser/profile.rs +++ b/app/src/main/rust/src/parser/profile.rs @@ -1,5 +1,6 @@ use crate::{EhError, get_node_handle_attr, get_tag_attr, select_first}; use anyhow::{Context, Result}; +use quick_xml::escape::unescape; use serde::Serialize; use tl::{Parser, VDom}; @@ -26,7 +27,8 @@ pub fn parse_profile(dom: &VDom, parser: &Parser) -> Result { .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 str = iter.next()?.get(parser)?.inner_text(parser); + let display_name = unescape(&str).as_deref().unwrap_or(&str).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| { diff --git a/docs/README/ja.md b/docs/README/ja.md index 8acaef731..475f8f16e 100644 --- a/docs/README/ja.md +++ b/docs/README/ja.md @@ -98,7 +98,6 @@ - [Coil](https://coil-kt.github.io/coil/) - [Compose Destinations](https://composedestinations.rafaelcosta.xyz/) - [libarchive](https://www.libarchive.org/) -- [jsoup](https://jsoup.org/) # ライセンス diff --git a/docs/README/zh-cn.md b/docs/README/zh-cn.md index 7d6a5a630..b310b6811 100644 --- a/docs/README/zh-cn.md +++ b/docs/README/zh-cn.md @@ -97,7 +97,6 @@ - [Coil](https://coil-kt.github.io/coil/) - [Compose Destinations](https://composedestinations.rafaelcosta.xyz/) - [libarchive](https://www.libarchive.org/) -- [jsoup](https://jsoup.org/) # 许可证 diff --git a/docs/README/zh-tw.md b/docs/README/zh-tw.md index e17a11410..2aa542998 100644 --- a/docs/README/zh-tw.md +++ b/docs/README/zh-tw.md @@ -95,7 +95,6 @@ - [Coil](https://coil-kt.github.io/coil/) - [Compose Destinations](https://composedestinations.rafaelcosta.xyz/) - [libarchive](https://www.libarchive.org/) -- [jsoup](https://jsoup.org/) # 許可證書 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e58125121..a8d02b68e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -85,7 +85,6 @@ material-kolor = "com.materialkolor:material-kolor:2.1.1" desugar = "com.android.tools:desugar_jdk_libs:2.1.5" diff = "io.github.petertrr:kotlin-multiplatform-diff:1.1.0" -jsoup = "org.jsoup:jsoup:1.20.1" kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }