From 1077a30363184fcb541e37505113ae6bc6e8ebaf Mon Sep 17 00:00:00 2001 From: Xwite <1797350009@qq.com> Date: Tue, 20 Sep 2022 23:09:26 +0800 Subject: [PATCH 1/2] feat: decode decrypted cover --- app/src/main/assets/help/ruleHelp.md | 5 +- .../java/io/legado/app/data/AppDatabase.kt | 5 +- .../io/legado/app/data/entities/BookSource.kt | 3 + .../io/legado/app/data/entities/RssSource.kt | 10 ++- .../main/java/io/legado/app/help/BookHelp.kt | 26 ++----- .../app/help/glide/OkHttpStreamFetcher.kt | 20 ++++-- .../legado/app/help/source/SourceAnalyzer.kt | 3 + .../source/edit/BookSourceEditActivity.kt | 2 + .../rss/source/edit/RssSourceEditActivity.kt | 2 + .../java/io/legado/app/utils/ImageUtils.kt | 69 +++++++++++++++++++ app/src/main/res/values-es-rES/strings.xml | 3 +- app/src/main/res/values-ja-rJP/strings.xml | 3 +- app/src/main/res/values-pt-rBR/strings.xml | 3 +- app/src/main/res/values-zh-rHK/strings.xml | 3 +- app/src/main/res/values-zh-rTW/strings.xml | 3 +- app/src/main/res/values-zh/strings.xml | 3 +- app/src/main/res/values/strings.xml | 3 +- 17 files changed, 131 insertions(+), 35 deletions(-) create mode 100644 app/src/main/java/io/legado/app/utils/ImageUtils.kt diff --git a/app/src/main/assets/help/ruleHelp.md b/app/src/main/assets/help/ruleHelp.md index 75b1849b1..15d48357a 100644 --- a/app/src/main/assets/help/ruleHelp.md +++ b/app/src/main/assets/help/ruleHelp.md @@ -155,5 +155,6 @@ let options = { > 可直接填写链接或者JavaScript,如果执行结果是字符串链接将会自动打开浏览器 * 图片解密 -> 适用于图片需要二次解密的情况,直接填写JavaScript,返回解密后的bytes -> 部分变量说明:java(仅支持[js扩展类](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/help/JsExtensions.kt)),result为待解密图片的bytes,src为图片链接 +> 适用于图片需要二次解密的情况,直接填写JavaScript,返回解密后的`ByteArray` +> 部分变量说明:java(仅支持[js扩展类](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/help/JsExtensions.kt)),result为待解密图片的`inputStream`,src为图片链接 + diff --git a/app/src/main/java/io/legado/app/data/AppDatabase.kt b/app/src/main/java/io/legado/app/data/AppDatabase.kt index 270614a1e..82631303c 100644 --- a/app/src/main/java/io/legado/app/data/AppDatabase.kt +++ b/app/src/main/java/io/legado/app/data/AppDatabase.kt @@ -20,7 +20,7 @@ val appDb by lazy { } @Database( - version = 53, + version = 54, exportSchema = true, entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class, ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class, @@ -37,7 +37,8 @@ val appDb by lazy { AutoMigration(from = 49, to = 50), AutoMigration(from = 50, to = 51), AutoMigration(from = 51, to = 52), - AutoMigration(from = 52, to = 53) + AutoMigration(from = 52, to = 53), + AutoMigration(from = 53, to = 54), ] ) abstract class AppDatabase : RoomDatabase() { diff --git a/app/src/main/java/io/legado/app/data/entities/BookSource.kt b/app/src/main/java/io/legado/app/data/entities/BookSource.kt index 7f68e9fa8..bf7b4aa6d 100644 --- a/app/src/main/java/io/legado/app/data/entities/BookSource.kt +++ b/app/src/main/java/io/legado/app/data/entities/BookSource.kt @@ -54,6 +54,8 @@ data class BookSource( override var loginUi: String? = null, // 登录检测js var loginCheckJs: String? = null, + // 封面解密js + var coverDecodeJs: String? = null, // 注释 var bookSourceComment: String? = null, // 自定义变量说明 @@ -206,6 +208,7 @@ data class BookSource( && equal(loginUrl, source.loginUrl) && equal(loginUi, source.loginUi) && equal(loginCheckJs, source.loginCheckJs) + && equal(coverDecodeJs, source.coverDecodeJs) && equal(exploreUrl, source.exploreUrl) && equal(searchUrl, source.searchUrl) && getSearchRule() == source.getSearchRule() diff --git a/app/src/main/java/io/legado/app/data/entities/RssSource.kt b/app/src/main/java/io/legado/app/data/entities/RssSource.kt index 9982e4196..657c77473 100644 --- a/app/src/main/java/io/legado/app/data/entities/RssSource.kt +++ b/app/src/main/java/io/legado/app/data/entities/RssSource.kt @@ -40,6 +40,8 @@ data class RssSource( override var loginUi: String? = null, //登录检测js var loginCheckJs: String? = null, + //封面解密js + var coverDecodeJs: String? = null, var sortUrl: String? = null, var singleUrl: Boolean = false, /*列表规则*/ @@ -58,6 +60,9 @@ data class RssSource( var enableJs: Boolean = true, var loadWithBaseUrl: Boolean = true, /*其它规则*/ + // 最后更新时间,用于排序 + @ColumnInfo(defaultValue = "0") + var lastUpdateTime: Long = 0, var customOrder: Int = 0 ) : Parcelable, BaseSource { @@ -90,6 +95,7 @@ data class RssSource( && equal(loginUrl, source.loginUrl) && equal(loginUi, source.loginUi) && equal(loginCheckJs, source.loginCheckJs) + && equal(coverDecodeJs, source.coverDecodeJs) && equal(sortUrl, source.sortUrl) && singleUrl == source.singleUrl && articleStyle == source.articleStyle @@ -174,7 +180,9 @@ data class RssSource( enableJs = doc.readBool("$.enableJs") ?: true, loadWithBaseUrl = doc.readBool("$.loadWithBaseUrl") ?: true, enabledCookieJar = doc.readBool("$.enabledCookieJar") ?: false, - customOrder = doc.readInt("$.customOrder") ?: 0 + customOrder = doc.readInt("$.customOrder") ?: 0, + lastUpdateTime = doc.readLong("$.lastUpdateTime") ?: 0L, + coverDecodeJs = doc.readString("$.coverDecodeJs") ) } } diff --git a/app/src/main/java/io/legado/app/help/BookHelp.kt b/app/src/main/java/io/legado/app/help/BookHelp.kt index aa31d30c7..65250e595 100644 --- a/app/src/main/java/io/legado/app/help/BookHelp.kt +++ b/app/src/main/java/io/legado/app/help/BookHelp.kt @@ -136,31 +136,19 @@ object BookHelp { downloadImages.add(src) val analyzeUrl = AnalyzeUrl(src, source = bookSource) try { - var bytes = analyzeUrl.getByteArrayAwait() + val bytes = analyzeUrl.getByteArrayAwait() //某些图片被加密,需要进一步解密 - bookSource?.getContentRule()?.imageDecode?.let { - if (it.isBlank()) { - return@let - } - kotlin.runCatching { - bookSource.evalJS(it) { - put("book", book) - put("result", bytes) - put("src", src) - } as ByteArray - }.onSuccess { - bytes = it - }.onFailure { - AppLog.putDebug("${src}解密bytes错误", it) - } - } - FileUtils.createFileIfNotExist( + ImageUtils.decode( + src, bytes, isCover = false, bookSource, book + )?.let { + FileUtils.createFileIfNotExist( downloadDir, cacheFolderName, book.getFolderName(), cacheImageFolderName, "${MD5Utils.md5Encode16(src)}.${getImageSuffix(src)}" - ).writeBytes(bytes) + ).writeBytes(it) + } } catch (e: Exception) { AppLog.putDebug("${src}下载错误", e) } finally { diff --git a/app/src/main/java/io/legado/app/help/glide/OkHttpStreamFetcher.kt b/app/src/main/java/io/legado/app/help/glide/OkHttpStreamFetcher.kt index def845e17..1bc86e656 100644 --- a/app/src/main/java/io/legado/app/help/glide/OkHttpStreamFetcher.kt +++ b/app/src/main/java/io/legado/app/help/glide/OkHttpStreamFetcher.kt @@ -9,10 +9,12 @@ import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.util.ContentLengthInputStream import com.bumptech.glide.util.Preconditions import io.legado.app.data.appDb +import io.legado.app.data.entities.BaseSource import io.legado.app.exception.NoStackTraceException import io.legado.app.help.http.addHeaders import io.legado.app.help.http.okHttpClient import io.legado.app.utils.isWifiConnect +import io.legado.app.utils.ImageUtils import okhttp3.Call import okhttp3.Request import okhttp3.Response @@ -20,6 +22,7 @@ import okhttp3.ResponseBody import splitties.init.appCtx import java.io.IOException import java.io.InputStream +import java.io.ByteArrayInputStream class OkHttpStreamFetcher(private val url: GlideUrl, private val options: Options) : @@ -27,6 +30,7 @@ class OkHttpStreamFetcher(private val url: GlideUrl, private val options: Option private var stream: InputStream? = null private var responseBody: ResponseBody? = null private var callback: DataFetcher.DataCallback? = null + private var source: BaseSource? = null @Volatile private var call: Call? = null @@ -40,7 +44,7 @@ class OkHttpStreamFetcher(private val url: GlideUrl, private val options: Option val requestBuilder: Request.Builder = Request.Builder().url(url.toStringUrl()) val headerMap = HashMap() options.get(OkHttpModelLoader.sourceOriginOption)?.let { sourceUrl -> - val source = appDb.bookSourceDao.getBookSource(sourceUrl) + source = appDb.bookSourceDao.getBookSource(sourceUrl) ?: appDb.rssSourceDao.getByKey(sourceUrl) source?.getHeaderMap(true)?.let { headerMap.putAll(it) @@ -81,9 +85,17 @@ class OkHttpStreamFetcher(private val url: GlideUrl, private val options: Option override fun onResponse(call: Call, response: Response) { responseBody = response.body if (response.isSuccessful) { - val contentLength: Long = Preconditions.checkNotNull(responseBody).contentLength() - stream = ContentLengthInputStream.obtain(responseBody!!.byteStream(), contentLength) - callback?.onDataReady(stream) + val decodeResult = ImageUtils.decode( + url.toStringUrl(), responseBody!!.byteStream(), + isCover = true, source + ) + if (decodeResult == null) { + callback?.onLoadFailed(NoStackTraceException("封面二次解密失败")) + } else { + val contentLength: Long = if (decodeResult is ByteArrayInputStream) decodeResult.available().toLong() else Preconditions.checkNotNull(responseBody).contentLength() + stream = ContentLengthInputStream.obtain(decodeResult, contentLength) + callback?.onDataReady(stream) + } } else { callback?.onLoadFailed(HttpException(response.message, response.code)) } diff --git a/app/src/main/java/io/legado/app/help/source/SourceAnalyzer.kt b/app/src/main/java/io/legado/app/help/source/SourceAnalyzer.kt index f958c09b3..517bba282 100644 --- a/app/src/main/java/io/legado/app/help/source/SourceAnalyzer.kt +++ b/app/src/main/java/io/legado/app/help/source/SourceAnalyzer.kt @@ -83,6 +83,7 @@ object SourceAnalyzer { loginUrl = jsonItem.readString("loginUrl") loginUi = jsonItem.readString("loginUi") loginCheckJs = jsonItem.readString("loginCheckJs") + coverDecodeJs = jsonItem.readString("coverDecodeJs") bookSourceComment = jsonItem.readString("bookSourceComment") ?: "" bookUrlPattern = jsonItem.readString("ruleBookUrlPattern") customOrder = jsonItem.readInt("serialNumber") ?: 0 @@ -165,6 +166,7 @@ object SourceAnalyzer { sourceAny.loginUi?.toString() } source.loginCheckJs = sourceAny.loginCheckJs + source.coverDecodeJs = sourceAny.coverDecodeJs source.bookSourceComment = sourceAny.bookSourceComment source.variableComment = sourceAny.variableComment source.lastUpdateTime = sourceAny.lastUpdateTime @@ -236,6 +238,7 @@ object SourceAnalyzer { var loginUrl: Any? = null, // 登录规则 var loginUi: Any? = null, // 登录UI var loginCheckJs: String? = null, // 登录检测js + var coverDecodeJs: String? = null, // 封面解密js var bookSourceComment: String? = "", // 书源注释 var variableComment: String? = null, // 变量说明 var lastUpdateTime: Long = 0, // 最后更新时间,用于排序 diff --git a/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt b/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt index 603a244ae..78d66005a 100644 --- a/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt @@ -221,6 +221,7 @@ class BookSourceEditActivity : add(EditEntity("loginUrl", source?.loginUrl, R.string.login_url)) add(EditEntity("loginUi", source?.loginUi, R.string.login_ui)) add(EditEntity("loginCheckJs", source?.loginCheckJs, R.string.login_check_js)) + add(EditEntity("coverDecodeJs", source?.coverDecodeJs, R.string.cover_decode_js)) add(EditEntity("bookUrlPattern", source?.bookUrlPattern, R.string.book_url_pattern)) add(EditEntity("header", source?.header, R.string.source_http_header)) add(EditEntity("variableComment", source?.variableComment, R.string.variable_comment)) @@ -345,6 +346,7 @@ class BookSourceEditActivity : "loginUrl" -> source.loginUrl = it.value "loginUi" -> source.loginUi = it.value "loginCheckJs" -> source.loginCheckJs = it.value + "coverDecodeJs" -> source.coverDecodeJs = it.value "bookUrlPattern" -> source.bookUrlPattern = it.value "header" -> source.header = it.value "bookSourceComment" -> source.bookSourceComment = it.value diff --git a/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt b/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt index 45cea1484..5303cdfd9 100644 --- a/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt +++ b/app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt @@ -169,6 +169,7 @@ class RssSourceEditActivity : add(EditEntity("loginUrl", source?.loginUrl, R.string.login_url)) add(EditEntity("loginUi", source?.loginUi, R.string.login_ui)) add(EditEntity("loginCheckJs", source?.loginCheckJs, R.string.login_check_js)) + add(EditEntity("coverDecodeJs", source?.coverDecodeJs, R.string.cover_decode_js)) add(EditEntity("header", source?.header, R.string.source_http_header)) add(EditEntity("variableComment", source?.variableComment, R.string.variable_comment)) add(EditEntity("concurrentRate", source?.concurrentRate, R.string.concurrent_rate)) @@ -203,6 +204,7 @@ class RssSourceEditActivity : "loginUrl" -> source.loginUrl = it.value "loginUi" -> source.loginUi = it.value "loginCheckJs" -> source.loginCheckJs = it.value + "coverDecodeJs" -> source.coverDecodeJs = it.value "header" -> source.header = it.value "variableComment" -> source.variableComment = it.value "concurrentRate" -> source.concurrentRate = it.value diff --git a/app/src/main/java/io/legado/app/utils/ImageUtils.kt b/app/src/main/java/io/legado/app/utils/ImageUtils.kt new file mode 100644 index 000000000..368f16e14 --- /dev/null +++ b/app/src/main/java/io/legado/app/utils/ImageUtils.kt @@ -0,0 +1,69 @@ +package io.legado.app.utils + +import io.legado.app.constant.AppLog +import io.legado.app.data.entities.Book +import io.legado.app.data.entities.BaseSource +import io.legado.app.data.entities.BookSource +import io.legado.app.data.entities.RssSource +import java.io.InputStream +import java.io.ByteArrayInputStream + +/** + * 加密图片解密工具 + */ +object ImageUtils { + + /** + * @param isCover 根据这个执行书源中不同的解密规则 + * @return 解密失败返回Null 解密规则为空不处理 + */ + fun decode( + src: String, bytes: ByteArray, isCover: Boolean, + source: BaseSource?, book: Book? = null + ): ByteArray? { + val ruleJs = getRuleJs(source, isCover) + if (ruleJs.isNullOrBlank()) return bytes + //解密库hutool.crypto ByteArray|InputStream -> ByteArray + return kotlin.runCatching { + source?.evalJS(ruleJs) { + put("book", book) + put("result", bytes) + put("src", src) + } as ByteArray + }.onFailure { + AppLog.putDebug("${src}解密错误", it) + }.getOrNull() + } + + fun decode( + src: String, inputStream: InputStream, isCover: Boolean, + source: BaseSource?, book: Book? = null + ): InputStream? { + val ruleJs = getRuleJs(source, isCover) + if (ruleJs.isNullOrBlank()) return inputStream + //解密库hutool.crypto ByteArray|InputStream -> ByteArray + return kotlin.runCatching { + val bytes = source?.evalJS(ruleJs) { + put("book", book) + put("result", inputStream) + put("src", src) + } as ByteArray + ByteArrayInputStream(bytes) + }.onFailure { + AppLog.putDebug("${src}解密错误", it) + }.getOrNull() + } + + + private fun getRuleJs( + source: BaseSource?, isCover: Boolean + ): String? { + return when (source) { + is BookSource -> + if (isCover) source?.coverDecodeJs else source?.getContentRule()?.imageDecode + is RssSource -> source?.coverDecodeJs + else -> null + } + } + +} \ No newline at end of file diff --git a/app/src/main/res/values-es-rES/strings.xml b/app/src/main/res/values-es-rES/strings.xml index 31e6e88c1..1fb6af765 100644 --- a/app/src/main/res/values-es-rES/strings.xml +++ b/app/src/main/res/values-es-rES/strings.xml @@ -872,7 +872,7 @@ Anti-Aliasing Anti-Aliasing when draw picture 上传URL - 下载URL规则 + downloadUrlRule(downloadUrls) Ordenar por tiempo de respuesta 导出成功 路径 @@ -1032,4 +1032,5 @@ 忽略音频焦点 允许与其他应用同时播放音频 刷新分类 + Decode Cover Js(coverDecodeJs) diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml index 8d0e7eaa0..e0e27038f 100644 --- a/app/src/main/res/values-ja-rJP/strings.xml +++ b/app/src/main/res/values-ja-rJP/strings.xml @@ -875,7 +875,7 @@ Anti-Aliasing Anti-Aliasing when draw picture 上传URL - 下载URL规则 + downloadUrlRule(downloadUrls) Sort by respond time 导出成功 路径 @@ -1035,4 +1035,5 @@ 忽略音频焦点 允许与其他应用同时播放音频 刷新分类 + Decode Cover Js(coverDecodeJs) diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 4dbc4974c..afaca31d4 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -873,7 +873,7 @@ Anti-Aliasing Anti-Aliasing when draw picture 上传URL - 下载URL规则 + downloadUrlRule(downloadUrls) Classificar por tempo de resposta 导出成功 路径 @@ -1035,4 +1035,5 @@ 忽略音频焦点 允许与其他应用同时播放音频 刷新分类 + Decode Cover Js(coverDecodeJs) diff --git a/app/src/main/res/values-zh-rHK/strings.xml b/app/src/main/res/values-zh-rHK/strings.xml index 400033552..af865ca72 100644 --- a/app/src/main/res/values-zh-rHK/strings.xml +++ b/app/src/main/res/values-zh-rHK/strings.xml @@ -872,7 +872,7 @@ 抗鋸齒 繪製圖片時抗鋸齒 上傳URL - 下載URL規則 + 下载URL规则(downloadUrls) 響應時間排序 導出成功 路徑 @@ -1032,4 +1032,5 @@ 忽略音频焦点 允许与其他应用同时播放音频 刷新分类 + 封面解密(coverDecodeJs) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 8c8f6e7a7..17bbd385e 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -874,7 +874,7 @@ 抗鋸齒 繪製圖片時抗鋸齒 上傳URL - 下載URL規則 + 下载URL规则(downloadUrls) 反應時間排序 匯出成功 路徑 @@ -1034,4 +1034,5 @@ 忽略音频焦点 允许与其他应用同时播放音频 刷新分类 + 封面解密(coverDecodeJs) diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index ae9d90f4b..be99bd644 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -876,7 +876,7 @@ 抗锯齿 绘制图片时抗锯齿 上传 URL - 下载 URL 规则 + 下载URL规则(downloadUrls) 响应时间排序 导出成功 路径 @@ -1034,4 +1034,5 @@ 忽略音频焦点 允许与其他应用同时播放音频 刷新分类 + 封面解密(coverDecodeJs) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5b4c2b95a..cbce0bd2d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -877,7 +877,7 @@ Anti-Aliasing Anti-Aliasing when draw picture upload url - 下载URL规则 + downloadUrlRule(downloadUrls) export success path 直链上传规则 @@ -1035,4 +1035,5 @@ 忽略音频焦点 允许与其他应用同时播放音频 刷新分类 + Decode Cover Js(coverDecodeJs) From 2b8342b6cfd1dc3fc698113e1022a4e6d55671c6 Mon Sep 17 00:00:00 2001 From: Xwite <82232510+Xwite@users.noreply.github.com> Date: Wed, 21 Sep 2022 09:48:46 +0800 Subject: [PATCH 2/2] docs: update RuleHelp.md --- app/src/main/assets/help/ruleHelp.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/assets/help/ruleHelp.md b/app/src/main/assets/help/ruleHelp.md index 15d48357a..190cda96e 100644 --- a/app/src/main/assets/help/ruleHelp.md +++ b/app/src/main/assets/help/ruleHelp.md @@ -156,5 +156,7 @@ let options = { * 图片解密 > 适用于图片需要二次解密的情况,直接填写JavaScript,返回解密后的`ByteArray` -> 部分变量说明:java(仅支持[js扩展类](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/help/JsExtensions.kt)),result为待解密图片的`inputStream`,src为图片链接 +> 部分变量说明:java(仅支持[js扩展类](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/help/JsExtensions.kt)),result为待解密图片的`ByteArray`,src为图片链接 +* 封面解密 +> 同图片解密 其中result为待解密封面的`inputStream`