[优化] 搜索优化

This commit is contained in:
HapeLee committed 2026-04-30 12:42:35 +08:00
1 parent f6b4ecbe88
commit 12443cf222
1 file changed
+31 -3
@@ -1,6 +1,7 @@
package io.legado.app.domain.usecase package io.legado.app.domain.usecase
import io.legado.app.constant.AppLog import io.legado.app.constant.AppLog
import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.BookSourcePart import io.legado.app.data.entities.BookSourcePart
import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.SearchBook
import io.legado.app.domain.gateway.BookSearchGateway import io.legado.app.domain.gateway.BookSearchGateway
@@ -100,7 +101,7 @@ class SearchBooksUseCase(
when (result) { when (result) {
is SourceSearchResult.Found -> { is SourceSearchResult.Found -> {
result.books.forEach { it.releaseHtmlData() } result.books.forEach { it.releaseHtmlData() }
hasMore = hasMore || result.books.isNotEmpty() hasMore = hasMore || (result.supportsNextPage && result.books.isNotEmpty())
if (result.books.isNotEmpty()) { if (result.books.isNotEmpty()) {
gateway.saveSearchBooks(result.books) gateway.saveSearchBooks(result.books)
} }
@@ -148,6 +149,10 @@ class SearchBooksUseCase(
return try { return try {
val source = gateway.getBookSource(sourcePart.bookSourceUrl) val source = gateway.getBookSource(sourcePart.bookSourceUrl)
?: return SourceSearchResult.Found(emptyList()) ?: return SourceSearchResult.Found(emptyList())
val supportsSearchPage = source.supportsSearchPage()
if (page > 1 && !supportsSearchPage) {
return SourceSearchResult.Found(emptyList())
}
val books = withTimeout(30000L) { val books = withTimeout(30000L) {
WebBook.searchBookAwait( WebBook.searchBookAwait(
source, source,
@@ -158,7 +163,7 @@ class SearchBooksUseCase(
} }
) )
} }
SourceSearchResult.Found(books) SourceSearchResult.Found(books, supportsSearchPage)
} catch (exception: Throwable) { } catch (exception: Throwable) {
coroutineContext.ensureActive() coroutineContext.ensureActive()
if (exception is CancellationException) throw exception if (exception is CancellationException) throw exception
@@ -167,7 +172,11 @@ class SearchBooksUseCase(
} }
private sealed interface SourceSearchResult { private sealed interface SourceSearchResult {
data class Found(val books: List<SearchBook>) : SourceSearchResult data class Found(
val books: List<SearchBook>,
val supportsNextPage: Boolean = false,
) : SourceSearchResult
data class Failed(val throwable: Throwable) : SourceSearchResult data class Failed(val throwable: Throwable) : SourceSearchResult
} }
@@ -251,3 +260,22 @@ class SearchBooksUseCase(
val removedBookUrls: List<String> = emptyList(), val removedBookUrls: List<String> = emptyList(),
) )
} }
internal fun BookSource.supportsSearchPage(): Boolean {
val url = searchUrl.orEmpty()
if (url.isBlank()) return false
if (searchPageScriptPattern.findAll(url).any { searchPageTokenPattern.containsMatchIn(it.value) }) {
return true
}
val staticUrl = searchPageScriptPattern.replace(url, "")
return searchPageGroupPattern.containsMatchIn(staticUrl)
}
private val searchPageScriptPattern = Regex(
pattern = """\{\{[\s\S]*?}}|<js>[\s\S]*?</js>|@js:[\s\S]*""",
option = RegexOption.IGNORE_CASE,
)
private val searchPageTokenPattern = Regex("""(?<![A-Za-z0-9_])page(?![A-Za-z0-9_])""")
private val searchPageGroupPattern = Regex("""<[^<>]+>""")