Reapply "Store pToken in GalleryPreview and refine parser" (#2390)

This reverts commit 5b8d3a2824.

Reason for revert: Reland with fix

Also clear invalid pToken
This commit is contained in:
FooIbar committed 2025-06-05 22:36:57 +08:00
1 parent 32e2e91013
commit f809d724a0
7 files changed
+48 -66

No files matched your search

@@ -15,26 +15,31 @@
*/
package com.hippo.ehviewer.client.data
import com.hippo.ehviewer.client.keyToUrl
import com.hippo.ehviewer.client.getThumbKey
import com.hippo.ehviewer.client.getV2PreviewKey
sealed interface GalleryPreview {
val url: String
val imageKey: String
val position: Int
val pToken: String
}
data class V1GalleryPreview(
override val imageKey: String,
override val url: String,
override val position: Int,
override val pToken: String,
) : GalleryPreview {
override val url get() = keyToUrl(imageKey)
override val imageKey get() = getThumbKey(url)
}
data class V2GalleryPreview(
override val url: String,
override val imageKey: String,
override val position: Int,
override val pToken: String,
val offsetX: Int,
val clipWidth: Int,
val clipHeight: Int,
) : GalleryPreview
) : GalleryPreview {
override val imageKey get() = getV2PreviewKey(url)
}
@@ -17,7 +17,6 @@ package com.hippo.ehviewer.client.parser
import arrow.core.Either
import arrow.core.getOrElse
import arrow.core.unzip
import com.hippo.ehviewer.EhDB
import com.hippo.ehviewer.Settings
import com.hippo.ehviewer.client.EhFilter
@@ -40,7 +39,6 @@ import com.hippo.ehviewer.client.exception.OffensiveException
import com.hippo.ehviewer.client.exception.ParseException
import com.hippo.ehviewer.client.exception.PiningException
import com.hippo.ehviewer.client.getThumbKey
import com.hippo.ehviewer.client.getV2PreviewKey
import com.hippo.ehviewer.util.toEpochMillis
import com.hippo.ehviewer.util.toFloatOrDefault
import com.hippo.ehviewer.util.toIntOrDefault
@@ -57,25 +55,21 @@ import org.jsoup.select.NodeTraversor
object GalleryDetailParser {
private val PATTERN_ERROR = Regex("<div class=\"d\">\n<p>([^<]+)</p>")
private val PATTERN_DETAIL = Regex(
"var gid = (\\d+);.+?var token = \"([a-f0-9]+)\";.+?var apiuid = ([\\-\\d]+);.+?var apikey = \"([a-f0-9]+)\";",
RegexOption.DOT_MATCHES_ALL,
)
private val PATTERN_DETAIL =
Regex("var gid = (\\d+);\\s+var token = \"([a-f0-9]+)\";\\s+var apiuid = ([\\-\\d]+);\\s+var apikey = \"([a-f0-9]+)\";")
private val PATTERN_TORRENT =
Regex("<a[^<>]*onclick=\"return popUp\\('([^']+)'[^)]+\\)\">Torrent Download \\((\\d+)\\)</a>")
private val PATTERN_ARCHIVE =
Regex("<a[^<>]*onclick=\"return popUp\\('([^']+)'[^)]+\\)\">Archive Download</a>")
private val PATTERN_COVER = Regex("width:(\\d+)px; height:(\\d+)px.+?url\\((.+?)\\)")
private val PATTERN_COVER = Regex("(\\d+)\\D+(\\d+)[^(]+\\(([^)]+)")
private val PATTERN_PAGES =
Regex("<tr><td[^<>]*>Length:</td><td[^<>]*>([\\d,]+) pages</td></tr>")
private val PATTERN_PREVIEW_PAGES =
Regex("<td[^>]+><a[^>]+>([\\d,]+)</a></td><td[^>]+>(?:<a[^>]+>)?&gt;(?:</a>)?</td>")
private val PATTERN_NEWER_DATE = Regex(", added (.+?)<br />")
private val PATTERN_NEWER_DATE = Regex(", added ([^<]+)<br />")
private val PATTERN_FAVORITE_SLOT =
Regex("/fav.png\\); background-position:0px -(\\d+)px")
Regex("/fav\\.png\\); background-position:0px -(\\d+)px")
private val EMPTY_GALLERY_COMMENT_LIST = GalleryCommentList(emptyList(), false)
private val PreviewRegex =
Regex("<a href=\"([^\"]+)\">(?:<div>)?<div title=\"Page (\\d+)(?:[^\"]+\"){2}\\D+(\\d+)\\D+(\\d+)[^(]+\\(([^)]+)\\)(?: -(\\d+))?")
Regex("<a href=\"https[^s#\"]+(?:s/([0-9a-f]{10})/\\d+-|#page)(\\d+)\">(?:<div>)?<div title=\"(?:[^\"]+\"){2}\\D+(\\d+)\\D+(\\d+)[^(]+\\(([^)]+)\\)(?: -(\\d+))?")
// dd MMMM yyyy, HH:mm
private val WEB_COMMENT_DATE_FORMAT = LocalDateTime.Format {
@@ -107,7 +101,7 @@ object GalleryDetailParser {
val galleryDetail = GalleryDetail(
tagGroups = parseTagGroups(document),
comments = parseComments(document),
previewList = parsePreviewList(body).first,
previewList = parsePreviewList(body),
)
parseDetail(galleryDetail, document, body)
@@ -415,35 +409,25 @@ object GalleryDetailParser {
EMPTY_GALLERY_COMMENT_LIST
}
/**
* Parse preview pages with regular expressions
*/
fun parsePreviewPages(body: String): Int = PATTERN_PREVIEW_PAGES.find(body)?.groupValues?.get(1)?.toIntOrNull()
?: throw ParseException("Parse preview page count error")
/**
* Parse pages with regular expressions
*/
fun parsePages(body: String): Int = PATTERN_PAGES.find(body)?.groupValues?.get(1)?.toIntOrNull()
?: throw ParseException("Parse pages error")
fun parsePreviewList(body: String) = PreviewRegex.findAll(body).unzip {
val pageUrl = it.groupValues[1]
fun parsePreviewList(body: String) = PreviewRegex.findAll(body).map {
val pToken = it.groupValues[1]
val position = it.groupValues[2].toInt() - 1
val url = it.groupValues[5]
val offset = it.groupValues[6]
if (offset.isEmpty()) {
val imageKey = getThumbKey(url)
V1GalleryPreview(imageKey, position)
V1GalleryPreview(url, position, pToken)
} else {
val imageKey = getV2PreviewKey(url)
val width = it.groupValues[3].toInt()
val height = it.groupValues[4].toInt()
V2GalleryPreview(url, imageKey, position, offset.toInt(), width, height)
} to pageUrl
}.run {
first.toList().apply {
if (isEmpty()) throw ParseException("Parse preview list error")
} to second.toList()
V2GalleryPreview(url, position, pToken, offset.toInt(), width, height)
}
}.toList().apply {
if (isEmpty()) throw ParseException("Parse preview list error")
}
}
@@ -30,10 +30,11 @@ fun ImageRequest.Builder.ehUrl(info: GalleryInfo) = apply {
// Load in original size so the memory cache can be reused for preload requests
fun ImageRequest.Builder.ehPreview(preview: GalleryPreview) = apply {
with(preview) {
val key = imageKey
data(url)
size(SizeResolver.ORIGINAL)
memoryCacheKey(imageKey)
diskCacheKey(imageKey)
memoryCacheKey(key)
diskCacheKey(key)
}
}
@@ -10,15 +10,15 @@ fun Path.readLegacySpiderInfo() = read {
fun readInt() = read().toInt()
fun readLong() = read().toLong()
repeat(2) { read() } // We assert that only info v2
read() // Skip version, we assert it's v2
read() // Skip startPage
val gid = readLong()
val token = read()
read()
val previewPages = readInt()
read() // Skip mode
read() // Skip previewPages
val previewPerPage = readInt()
val pages = read().toInt()
val pTokenMap = hashMapOf<Int, String>()
SpiderInfo(gid, token, pages, pTokenMap, previewPages, previewPerPage).apply {
val pages = readInt()
SpiderInfo(gid, token, pages, previewPerPage = previewPerPage).apply {
runCatching {
while (true) {
val line = read()
@@ -27,8 +27,6 @@ class SpiderInfo(
val pTokenMap: MutableMap<Int, String> = hashMapOf(),
var previewPages: Int = -1,
var previewPerPage: Int = -1,
)
@@ -34,9 +34,7 @@ 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.GalleryDetailParser.parsePreviewPages
import com.hippo.ehviewer.client.parser.GalleryMultiPageViewerPTokenParser
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser
import com.hippo.ehviewer.util.displayString
import com.hippo.files.find
import eu.kanade.tachiyomi.util.system.logcat
@@ -321,19 +319,15 @@ 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) {
spiderInfo.previewPages = parsePreviewPages(body)
val (previewList, pageUrlList) = parsePreviewList(body)
if (previewList.isNotEmpty()) {
if (index == 0) {
spiderInfo.previewPerPage = previewList.size
} else {
spiderInfo.previewPerPage = previewList[0].position / index
}
val previewList = parsePreviewList(body)
if (index == 0) {
spiderInfo.previewPerPage = previewList.size
} else {
spiderInfo.previewPerPage = previewList[0].position / index
}
pageUrlList.forEach {
val result = GalleryPageUrlParser.parse(it)
if (result != null) {
spiderInfo.pTokenMap[result.page] = result.pToken
previewList.forEach {
if (it.pToken.isNotEmpty()) {
spiderInfo.pTokenMap[it.position] = it.pToken
}
}
}
@@ -370,16 +364,11 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS
}
suspend fun getPTokenFromInternet(index: Int): String? {
// Check previewIndex
var previewIndex: Int
previewIndex = if (spiderInfo.previewPerPage >= 0) {
val previewIndex = if (spiderInfo.previewPerPage > 0) {
index / spiderInfo.previewPerPage
} else {
0
}
if (spiderInfo.previewPages > 0) {
previewIndex = previewIndex.coerceAtMost(spiderInfo.previewPages - 1)
}
val url = getGalleryDetailUrl(
galleryInfo.gid,
galleryInfo.token,
@@ -670,6 +659,11 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS
// TODO: Check IP ban
}
error = it.displayString()
if (error == "Invalid page.") {
pTokenLock.withLock {
spiderInfo.pTokenMap.remove(index)
}
}
}
updatePageState(index, STATE_FAILED, error)
}
@@ -805,7 +805,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).first
EhEngine.getPreviewList(url)
}.flattenForEach {
previewPagesMap[it.position] = it
if (Settings.preloadThumbAggressively) {