diff --git a/app/build.gradle b/app/build.gradle
index a55fc455c..a3002fb65 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -219,6 +219,9 @@ dependencies {
//转换繁体
implementation('com.github.liuyueyi.quick-chinese-transfer:quick-transfer-core:0.2.3')
+ //加解密类库
+ implementation('cn.hutool:hutool-crypto:5.7.19')
+
//代码编辑com.github.AmrDeveloper:CodeView已集成到应用内
//epubLib集成到应用内
diff --git a/app/src/main/java/io/legado/app/data/entities/BookChapter.kt b/app/src/main/java/io/legado/app/data/entities/BookChapter.kt
index cf5b2b2b7..5c80fc034 100644
--- a/app/src/main/java/io/legado/app/data/entities/BookChapter.kt
+++ b/app/src/main/java/io/legado/app/data/entities/BookChapter.kt
@@ -109,6 +109,8 @@ data class BookChapter(
}
fun getAbsoluteURL(): String {
+ //二级目录解析的卷链接为空 返回目录页的链接
+ if (url.startsWith(title) && isVolume) return baseUrl
val urlMatcher = AnalyzeUrl.paramPattern.matcher(url)
val urlBefore = if (urlMatcher.find()) url.substring(0, urlMatcher.start()) else url
val urlAbsoluteBefore = NetworkUtils.getAbsoluteURL(baseUrl, urlBefore)
diff --git a/app/src/main/java/io/legado/app/help/JsExtensions.kt b/app/src/main/java/io/legado/app/help/JsExtensions.kt
index 9b4eecb63..bd515d46d 100644
--- a/app/src/main/java/io/legado/app/help/JsExtensions.kt
+++ b/app/src/main/java/io/legado/app/help/JsExtensions.kt
@@ -3,6 +3,9 @@ package io.legado.app.help
import android.net.Uri
import android.util.Base64
import androidx.annotation.Keep
+import cn.hutool.crypto.digest.DigestUtil
+import cn.hutool.crypto.symmetric.AES
+import cn.hutool.crypto.symmetric.DESede
import io.legado.app.BuildConfig
import io.legado.app.constant.AppConst
import io.legado.app.constant.AppConst.dateFormat
@@ -132,23 +135,23 @@ interface JsExtensions {
return cacheFile(urlStr, 0)
}
- /**
+ /**
* 缓存以文本方式保存的文件 如.js .txt等
- * @param urlStr 网络文件的链接
- * @param saveTime 缓存时间,单位:秒
- * @return 返回缓存后的文件内容
+ * @param urlStr 网络文件的链接
+ * @param saveTime 缓存时间,单位:秒
+ * @return 返回缓存后的文件内容
*/
fun cacheFile(urlStr: String, saveTime: Int = 0): String? {
- val key = md5Encode16(urlStr)
- val cache = CacheManager.getFile(key)
- if (cache.isNullOrBlank()) {
- log("首次下载 $urlStr")
- val value = ajax(urlStr) ?: return null
- CacheManager.putFile(key, value, saveTime)
- return value
- }
- return cache
- }
+ val key = md5Encode16(urlStr)
+ val cache = CacheManager.getFile(key)
+ if (cache.isNullOrBlank()) {
+ log("首次下载 $urlStr")
+ val value = ajax(urlStr) ?: return null
+ CacheManager.putFile(key, value, saveTime)
+ return value
+ }
+ return cache
+ }
/**
*js实现读取cookie
@@ -688,4 +691,172 @@ interface JsExtensions {
return AppConst.androidId
}
+ /**
+ * AES解密,算法参数经过Base64加密
+ *
+ * @param data 加密的字符串
+ * @param key Base64后的密钥
+ * @param mode 模式
+ * @param padding 补码方式
+ * @param iv Base64后的加盐
+ * @return 解密后的字符串
+ */
+ fun aesDecodeArgsBase64Str(
+ data: String,
+ key: String,
+ mode: String,
+ padding: String,
+ iv: String
+ ): String? {
+ return AES(
+ mode,
+ padding,
+ Base64.decode(key, Base64.NO_WRAP),
+ Base64.decode(iv, Base64.NO_WRAP)
+ ).decryptStr(data)
+ }
+
+ /**
+ * 3DES解密
+ *
+ * @param data 加密的字符串
+ * @param key 密钥
+ * @param mode 模式
+ * @param padding 补码方式
+ * @param iv 加盐
+ * @return 解密后的字符串
+ */
+ fun tripleDESDecodeStr(
+ data: String,
+ key: String,
+ mode: String,
+ padding: String,
+ iv: String
+ ): String? {
+ return DESede(mode, padding, key.toByteArray(), iv.toByteArray()).decryptStr(data)
+ }
+
+ /**
+ * 3DES解密,算法参数经过Base64加密
+ *
+ * @param data 加密的字符串
+ * @param key Base64后的密钥
+ * @param mode 模式
+ * @param padding 补码方式
+ * @param iv Base64后的加盐
+ * @return 解密后的字符串
+ */
+ fun tripleDESDecodeArgsBase64Str(
+ data: String,
+ key: String,
+ mode: String,
+ padding: String,
+ iv: String
+ ): String? {
+ return DESede(
+ mode,
+ padding,
+ Base64.decode(key, Base64.NO_WRAP),
+ Base64.decode(iv, Base64.NO_WRAP)
+ ).decryptStr(data)
+ }
+
+ /**
+ * AES加密并转为Base64,算法参数经过Base64加密
+ *
+ * @param data 被加密的字符串
+ * @param key Base64后的密钥
+ * @param mode 模式
+ * @param padding 补码方式
+ * @param iv Base64后的加盐
+ * @return 加密后的Base64
+ */
+ fun aesEncodeArgsBase64Str(
+ data: String,
+ key: String,
+ mode: String,
+ padding: String,
+ iv: String
+ ): String? {
+ return AES(
+ mode,
+ padding,
+ Base64.decode(key, Base64.NO_WRAP),
+ Base64.decode(iv, Base64.NO_WRAP)
+ ).encryptBase64(data)
+ }
+
+ /**
+ * 3DES加密并转为Base64
+ *
+ * @param data 被加密的字符串
+ * @param key 密钥
+ * @param mode 模式
+ * @param padding 补码方式
+ * @param iv 加盐
+ * @return 加密后的Base64
+ */
+ fun tripleDESEncodeBase64Str(
+ data: String,
+ key: String,
+ mode: String,
+ padding: String,
+ iv: String
+ ): String? {
+ return DESede(mode, padding, key.toByteArray(), iv.toByteArray()).encryptBase64(data)
+ }
+
+ /**
+ * 3DES加密并转为Base64,算法参数经过Base64加密
+ *
+ * @param data 被加密的字符串
+ * @param key Base64后的密钥
+ * @param mode 模式
+ * @param padding 补码方式
+ * @param iv Base64后的加盐
+ * @return 加密后的Base64
+ */
+ fun tripleDESEncodeArgsBase64Str(
+ data: String,
+ key: String,
+ mode: String,
+ padding: String,
+ iv: String
+ ): String? {
+ return DESede(
+ mode,
+ padding,
+ Base64.decode(key, Base64.NO_WRAP),
+ Base64.decode(iv, Base64.NO_WRAP)
+ ).encryptBase64(data)
+ }
+
+ /**
+ * 生成摘要,并转为16进制字符串
+ *
+ * @param data 被摘要数据
+ * @param algorithm 签名算法
+ * @return 16进制字符串
+ */
+ fun digestHex(
+ data: String,
+ algorithm: String,
+ ): String? {
+ return DigestUtil.digester(algorithm).digestHex(data)
+ }
+
+ /**
+ * 生成摘要,并转为Base64字符串
+ *
+ * @param data 被摘要数据
+ * @param algorithm 签名算法
+ * @return Base64字符串
+ */
+ fun digestBase64Str(
+ data: String,
+ algorithm: String,
+ ): String? {
+ return Base64.encodeToString(DigestUtil.digester(algorithm).digest(data), Base64.NO_WRAP)
+ }
+
}
diff --git a/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt b/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt
index 72d13880d..faf5f1fa4 100644
--- a/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt
+++ b/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt
@@ -193,8 +193,8 @@ object BookChapterList {
}
if (bookChapter.url.isEmpty()) {
if (bookChapter.isVolume) {
- bookChapter.url = bookChapter.title
- Debug.log(bookSource.bookSourceUrl, "一级目录${index}未获取到url,使用章节标题替代")
+ bookChapter.url = bookChapter.title + index
+ Debug.log(bookSource.bookSourceUrl, "一级目录${index}未获取到url,使用${bookChapter.title}${index}替代")
} else {
bookChapter.url = baseUrl
Debug.log(bookSource.bookSourceUrl, "目录${index}未获取到url,使用baseUrl替代")
diff --git a/app/src/main/java/io/legado/app/model/webBook/WebBook.kt b/app/src/main/java/io/legado/app/model/webBook/WebBook.kt
index 8cb3d151a..9cda244cf 100644
--- a/app/src/main/java/io/legado/app/model/webBook/WebBook.kt
+++ b/app/src/main/java/io/legado/app/model/webBook/WebBook.kt
@@ -261,10 +261,9 @@ object WebBook {
Debug.log(bookSource.bookSourceUrl, "⇒正文规则为空,使用章节链接:${bookChapter.url}")
return bookChapter.url
}
- if(bookChapter.isVolume && bookChapter.url == bookChapter.title) {
- //不返回空值,是为了过书源检测
- Debug.log(bookSource.bookSourceUrl, "⇒一级目录正文,使用章节标题:${bookChapter.title}")
- return bookChapter.title
+ if(bookChapter.isVolume && bookChapter.url.startsWith(bookChapter.title)) {
+ Debug.log(bookSource.bookSourceUrl, "⇒一级目录正文不解析规则")
+ return bookChapter.tag ?: ""
}
return if (bookChapter.url == book.bookUrl && !book.tocHtml.isNullOrEmpty()) {
BookContent.analyzeContent(
diff --git a/app/src/main/java/io/legado/app/service/CheckSourceService.kt b/app/src/main/java/io/legado/app/service/CheckSourceService.kt
index 6887de070..f4f592ed7 100644
--- a/app/src/main/java/io/legado/app/service/CheckSourceService.kt
+++ b/app/src/main/java/io/legado/app/service/CheckSourceService.kt
@@ -144,7 +144,7 @@ class CheckSourceService : BaseService() {
nextChapterUrl = nextChapterUrl,
needSave = false
)
- if (content.isBlank()) {
+ if ( !toc.first().isVolume && content.isBlank()) {
throw NoStackTraceException("正文内容为空")
}
}.timeout(180000L)
diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt
index 9f7e3f3a8..ebfbcb0f8 100644
--- a/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt
+++ b/app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt
@@ -129,7 +129,7 @@ object ChapterProvider {
matcher.appendTail(sb)
text = sb.toString()
val isTitle = index == 0
- val isVolumeTitle = bookChapter.isVolume && isTitle && bookChapter.url == bookChapter.title
+ val isVolumeTitle = bookChapter.isVolume && isTitle && contents.size == 1
val textPaint = if (isTitle) titlePaint else contentPaint
if (!(isTitle && ReadBookConfig.titleMode == 2)) {
setTypeText(
@@ -147,7 +147,7 @@ object ChapterProvider {
val text = content.substring(start, matcher.start())
if (text.isNotBlank()) {
val isTitle = index == 0
- val isVolumeTitle = bookChapter.isVolume && isTitle && bookChapter.url == bookChapter.title
+ val isVolumeTitle = bookChapter.isVolume && isTitle && contents.size == 1
val textPaint = if (isTitle) titlePaint else contentPaint
if (!(isTitle && ReadBookConfig.titleMode == 2)) {
setTypeText(
@@ -169,7 +169,7 @@ object ChapterProvider {
val text = content.substring(start, content.length)
if (text.isNotBlank()) {
val isTitle = index == 0
- val isVolumeTitle = bookChapter.isVolume && isTitle && bookChapter.url == bookChapter.title
+ val isVolumeTitle = bookChapter.isVolume && isTitle && contents.size == 1
val textPaint = if (isTitle) titlePaint else contentPaint
if (!(isTitle && ReadBookConfig.titleMode == 2)) {
setTypeText(
@@ -332,7 +332,7 @@ object ChapterProvider {
}
lineIndex == layout.lineCount - 1 -> {
//最后一行
- textLine.text = if(isVolumeTitle) "" else "$words\n"
+ textLine.text = "$words\n"
isLastLine = true
//标题居中
val startX = if (isTitle && ReadBookConfig.titleMode == 1 || isVolumeTitle)
@@ -349,7 +349,7 @@ object ChapterProvider {
}
else -> {
//中间行
- textLine.text = if(isVolumeTitle) "" else words
+ textLine.text = words
addCharsToLineMiddle(
absStartX,
textLine,
@@ -579,7 +579,7 @@ object ChapterProvider {
tPaint.typeface = titleFont
tPaint.textSize = with(ReadBookConfig) { textSize + titleSize }.sp.toFloat()
tPaint.isAntiAlias = true
- //to do:卷名的标题排版
+ //正文
val cPaint = TextPaint()
cPaint.color = ReadBookConfig.textColor
cPaint.letterSpacing = ReadBookConfig.letterSpacing
diff --git a/app/src/main/res/values-zh-rTW/arrays.xml b/app/src/main/res/values-zh-rTW/arrays.xml
index d88800ca3..d39084156 100644
--- a/app/src/main/res/values-zh-rTW/arrays.xml
+++ b/app/src/main/res/values-zh-rTW/arrays.xml
@@ -7,7 +7,7 @@
- 標籤
- - 文件夾
+ - 資料夾
@@ -87,8 +87,8 @@
- 書源
- - 訂閲源
- - 替換規則
+ - 訂閱源
+ - 取代規則
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 9195535bf..aac171aab 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -231,7 +231,7 @@
簡介: 暫無簡介
打開外部書籍
來源: %s
- 匯入替換規則
+ 匯入取代規則
匯入線上規則
檢查更新間隔
按閱讀時間
@@ -634,8 +634,8 @@
正在啟動服務\n具體訊息查看通知欄
預設路徑
系統資料夾選擇器
- 自帶資料夾選擇器
- 自帶資料夾選擇器
+ 內建資料夾選擇器
+ 內建資料夾選擇器
Android10以上因權限限制可能無法讀寫文件
長按文字在操作選單中顯示閱讀·搜尋
文字操作顯示搜尋
@@ -678,7 +678,7 @@
預設
主選單
點擊授予權限
- 閱讀需要存取記憶卡權限,請點擊下方的"授予權限"按鈕,或前往“設定”—“應用權限”—打開所需權限。如果授予權限後仍然不正常,請點擊右上角的“選擇資料夾”,使用系統資料夾選擇器。
+ 閱讀需要存取記憶卡權限,請點擊下方的"授予權限"按鈕,或前往「設定」—「應用權限」—打開所需權限。如果授予權限後仍然不正常,請點擊右上角的「選擇資料夾」,使用系統資料夾選擇器。
全文朗讀中不能朗讀選中文字
擴展到瀏海
更新目錄中
@@ -817,7 +817,7 @@
背景圖片
背景圖片虛化
虛化半徑
- 0為停用,啓用範圍1~25\n半徑數值越大,虛化效果越高
+ 0為停用,啟用範圍1~25\n半徑數值越大,虛化效果越高
匯出資料夾
匯出編碼
TXT不匯出章節名
@@ -837,7 +837,7 @@
列表原始碼
此url已訂閱
高刷
- 使用螢幕最高刷新率
+ 使用螢幕最高更新率
匯出所有
完成
顯示未讀標誌
@@ -879,8 +879,8 @@
設定源變數
設定書籍變數
注釋
- 封面設置
- 設置默認封面樣式
+ 封面設定
+ 設定預設封面樣式
顯示書名
封面上顯示書名
顯示作者
@@ -902,17 +902,17 @@
是否使用外部瀏覽器打開?
查看
打開
- 刪除登錄頭
- 查看登錄頭
- 登錄頭
+ 刪除登入頭
+ 查看登入頭
+ 登入頭
字體大小
- 當前字體大小:%.1f
- 語速减
+ 目前字體大小:%.1f
+ 語速減
語速加
- 打开系统文件夹选择器出错,自动打开应用文件夹选择器
- 展开文本选择菜单
- 搜索結果
- 书籍保存位置
- 从其它应用打开的书籍保存位置
+ 打開系統資料夾選擇器出錯,自動打開應用程式資料夾選擇器
+ 展開文字選擇選單
+ 搜尋結果
+ 書籍儲存位置
+ 從其它應用程式打開的書籍儲存位置