Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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替代")
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -144,7 +144,7 @@ class CheckSourceService : BaseService() {
|
||||
nextChapterUrl = nextChapterUrl,
|
||||
needSave = false
|
||||
)
|
||||
if (content.isBlank()) {
|
||||
if ( !toc.first().isVolume && content.isBlank()) {
|
||||
throw NoStackTraceException("正文内容为空")
|
||||
}
|
||||
}.timeout(180000L)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user