Merge branch 'master' into master

pull/1576/head
kunfei 3 years ago committed by GitHub
commit c24e26fccd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      app/src/main/assets/help/jsHelp.md
  2. 10
      app/src/main/assets/updateLog.md
  3. 12
      app/src/main/java/io/legado/app/help/JsExtensions.kt
  4. 38
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt
  5. 2
      app/src/main/java/io/legado/app/model/localBook/TextFile.kt
  6. 8
      app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt
  7. 10
      app/src/main/java/io/legado/app/model/webBook/BookContent.kt
  8. 8
      app/src/main/java/io/legado/app/model/webBook/BookInfo.kt
  9. 18
      app/src/main/java/io/legado/app/model/webBook/WebBook.kt
  10. 16
      app/src/main/java/io/legado/app/ui/association/FileAssociationActivity.kt
  11. 4
      app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt
  12. 8
      app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt
  13. 2
      app/src/main/java/io/legado/app/ui/widget/code/CodeView.kt
  14. 56
      app/src/main/java/io/legado/app/utils/ToastUtils.kt

@ -82,8 +82,16 @@ java.base64Encode(str: String, flags: Int)
* 文件 * 文件
> 所有对于文件的读写删操作都是相对路径,只能操作阅读缓存/android/data/{package}/cache/内的文件 > 所有对于文件的读写删操作都是相对路径,只能操作阅读缓存/android/data/{package}/cache/内的文件
``` ```
java.readTxtFile(path: String): String //文件下载,content为十六进制字符串,url用于生成文件名,返回文件路径
java.deleteFile(path: String) downloadFile(content: String, url: String): String
//文件解压,zipPath为压缩文件路径,返回解压路径
unzipFile(zipPath: String): String
//文件夹内所有文件读取
getTxtInFolder(unzipPath: String): String
//读取文本文件
readTxtFile(path: String): String
//删除文件
deleteFile(path: String)
``` ```
**** ****
> [常见加密解密算法介绍](https://www.yijiyong.com/algorithm/encryption/01-intro.html) > [常见加密解密算法介绍](https://www.yijiyong.com/algorithm/encryption/01-intro.html)

@ -11,6 +11,16 @@
* 正文出现缺字漏字、内容缺失、排版错乱等情况,有可能是净化规则或简繁转换出现问题。 * 正文出现缺字漏字、内容缺失、排版错乱等情况,有可能是净化规则或简繁转换出现问题。
* 漫画源看书显示乱码,**阅读与其他软件的源并不通用**,请导入阅读的支持的漫画源! * 漫画源看书显示乱码,**阅读与其他软件的源并不通用**,请导入阅读的支持的漫画源!
**2022/02/02**
* 字符串大于1024时禁用代码高亮
* 修复baseUrl丢失参数的bug
* 添加更新bookUrl和更新tocUrl函数
* 标题单独净化,防止出现正文标题没了的问题
* 修复一个null报错
* 按返回键关闭搜索界面 by Xwite
* 其它一些优化 by Xwite
**2022/01/28** **2022/01/28**
* 阅读背景添加透明度调节 * 阅读背景添加透明度调节

@ -9,6 +9,7 @@ import cn.hutool.crypto.symmetric.DESede
import io.legado.app.BuildConfig import io.legado.app.BuildConfig
import io.legado.app.constant.AppConst import io.legado.app.constant.AppConst
import io.legado.app.constant.AppConst.dateFormat import io.legado.app.constant.AppConst.dateFormat
import io.legado.app.constant.AppLog
import io.legado.app.data.entities.BaseSource import io.legado.app.data.entities.BaseSource
import io.legado.app.help.http.* import io.legado.app.help.http.*
import io.legado.app.model.Debug import io.legado.app.model.Debug
@ -513,13 +514,14 @@ interface JsExtensions {
/** /**
* 输出调试日志 * 输出调试日志
*/ */
fun log(msg: String): String { fun log(msg: Any?): Any? {
getSource()?.let { getSource()?.let {
Debug.log(it.getKey(), msg) Debug.log(it.getKey(), msg.toString())
} ?: Debug.log(msg) } ?: Debug.log(msg.toString())
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Timber.d(msg) Timber.d(msg.toString())
} }
AppLog.put(msg.toString())
return msg return msg
} }
@ -692,7 +694,7 @@ interface JsExtensions {
return aesEncodeToBase64ByteArray(data, key, transformation, iv)?.let { String(it) } return aesEncodeToBase64ByteArray(data, key, transformation, iv)?.let { String(it) }
} }
fun android(): String { fun androidId(): String {
return AppConst.androidId return AppConst.androidId
} }

@ -4,12 +4,11 @@ import android.text.TextUtils
import androidx.annotation.Keep import androidx.annotation.Keep
import io.legado.app.constant.AppConst.SCRIPT_ENGINE import io.legado.app.constant.AppConst.SCRIPT_ENGINE
import io.legado.app.constant.AppPattern.JS_PATTERN import io.legado.app.constant.AppPattern.JS_PATTERN
import io.legado.app.data.entities.BaseBook import io.legado.app.data.entities.*
import io.legado.app.data.entities.BaseSource
import io.legado.app.data.entities.BookChapter
import io.legado.app.help.CacheManager import io.legado.app.help.CacheManager
import io.legado.app.help.JsExtensions import io.legado.app.help.JsExtensions
import io.legado.app.help.http.CookieStore import io.legado.app.help.http.CookieStore
import io.legado.app.model.webBook.WebBook
import io.legado.app.utils.* import io.legado.app.utils.*
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.jsoup.nodes.Entities import org.jsoup.nodes.Entities
@ -684,6 +683,39 @@ class AnalyzeRule(
return s return s
} }
/**
* 更新BookUrl,如果搜索结果有tocUrl也会更新,有些书源bookUrl定期更新,可以在js内调用更新
*/
fun refreshBookUrl() {
runBlocking {
val bookSource = source as? BookSource
val book = book as? Book
if (bookSource == null || book == null) return@runBlocking
val books = WebBook.searchBookAwait(this, bookSource, book.name)
books.forEach {
if (it.name == book.name && it.author == book.author) {
book.bookUrl = it.bookUrl
if (it.tocUrl.isNotBlank()) {
book.tocUrl = it.tocUrl
}
return@runBlocking
}
}
}
}
/**
* 更新tocUrl,有些书源目录url定期更新,可以在js调用更新
*/
fun refreshTocUrl() {
runBlocking {
val bookSource = source as? BookSource
val book = book as? Book
if (bookSource == null || book == null) return@runBlocking
WebBook.getBookInfoAwait(this, bookSource, book)
}
}
companion object { companion object {
private val putPattern = Pattern.compile("@put:(\\{[^}]+?\\})", Pattern.CASE_INSENSITIVE) private val putPattern = Pattern.compile("@put:(\\{[^}]+?\\})", Pattern.CASE_INSENSITIVE)
private val evalPattern = private val evalPattern =

@ -126,7 +126,7 @@ class TextFile(private val book: Book) {
val chapterContent = blockContent.substring(seekPos, chapterStart) val chapterContent = blockContent.substring(seekPos, chapterStart)
val chapterLength = chapterContent.toByteArray(charset).size val chapterLength = chapterContent.toByteArray(charset).size
val lastStart = toc.lastOrNull()?.start ?: curOffset val lastStart = toc.lastOrNull()?.start ?: curOffset
if (curOffset + chapterLength - lastStart > 50000) { if (curOffset + chapterLength - lastStart > 102400) {
bis.close() bis.close()
return analyze() return analyze()
} }

@ -28,8 +28,8 @@ object BookChapterList {
scope: CoroutineScope, scope: CoroutineScope,
bookSource: BookSource, bookSource: BookSource,
book: Book, book: Book,
redirectUrl: String,
baseUrl: String, baseUrl: String,
redirectUrl: String,
body: String? body: String?
): List<BookChapter> { ): List<BookChapter> {
body ?: throw NoStackTraceException( body ?: throw NoStackTraceException(
@ -39,7 +39,7 @@ object BookChapterList {
Debug.log(bookSource.bookSourceUrl, "≡获取成功:${baseUrl}") Debug.log(bookSource.bookSourceUrl, "≡获取成功:${baseUrl}")
Debug.log(bookSource.bookSourceUrl, body, state = 30) Debug.log(bookSource.bookSourceUrl, body, state = 30)
val tocRule = bookSource.getTocRule() val tocRule = bookSource.getTocRule()
val nextUrlList = arrayListOf(baseUrl) val nextUrlList = arrayListOf(redirectUrl)
var reverse = false var reverse = false
var listRule = tocRule.chapterList ?: "" var listRule = tocRule.chapterList ?: ""
if (listRule.startsWith("-")) { if (listRule.startsWith("-")) {
@ -157,7 +157,7 @@ object BookChapterList {
Debug.log(bookSource.bookSourceUrl, "┌获取目录下一页列表", log) Debug.log(bookSource.bookSourceUrl, "┌获取目录下一页列表", log)
analyzeRule.getStringList(nextTocRule, isUrl = true)?.let { analyzeRule.getStringList(nextTocRule, isUrl = true)?.let {
for (item in it) { for (item in it) {
if (item != baseUrl) { if (item != redirectUrl) {
nextUrlList.add(item) nextUrlList.add(item)
} }
} }
@ -180,7 +180,7 @@ object BookChapterList {
elements.forEachIndexed { index, item -> elements.forEachIndexed { index, item ->
scope.ensureActive() scope.ensureActive()
analyzeRule.setContent(item) analyzeRule.setContent(item)
val bookChapter = BookChapter(bookUrl = book.bookUrl, baseUrl = baseUrl) val bookChapter = BookChapter(bookUrl = book.bookUrl, baseUrl = redirectUrl)
analyzeRule.chapter = bookChapter analyzeRule.chapter = bookChapter
bookChapter.title = analyzeRule.getString(nameRule) bookChapter.title = analyzeRule.getString(nameRule)
bookChapter.url = analyzeRule.getString(urlRule) bookChapter.url = analyzeRule.getString(urlRule)

@ -32,8 +32,8 @@ object BookContent {
bookSource: BookSource, bookSource: BookSource,
book: Book, book: Book,
bookChapter: BookChapter, bookChapter: BookChapter,
redirectUrl: String,
baseUrl: String, baseUrl: String,
redirectUrl: String,
body: String?, body: String?,
nextChapterUrl: String?, nextChapterUrl: String?,
needSave: Boolean = true needSave: Boolean = true
@ -50,10 +50,10 @@ object BookContent {
nextChapterUrl nextChapterUrl
} }
val content = StringBuilder() val content = StringBuilder()
val nextUrlList = arrayListOf(baseUrl) val nextUrlList = arrayListOf(redirectUrl)
val contentRule = bookSource.getContentRule() val contentRule = bookSource.getContentRule()
val analyzeRule = AnalyzeRule(book, bookSource).setContent(body, baseUrl) val analyzeRule = AnalyzeRule(book, bookSource).setContent(body, baseUrl)
analyzeRule.setRedirectUrl(baseUrl) analyzeRule.setRedirectUrl(redirectUrl)
analyzeRule.nextChapterUrl = mNextChapterUrl analyzeRule.nextChapterUrl = mNextChapterUrl
scope.ensureActive() scope.ensureActive()
var contentData = analyzeContent( var contentData = analyzeContent(
@ -64,8 +64,8 @@ object BookContent {
var nextUrl = contentData.second[0] var nextUrl = contentData.second[0]
while (nextUrl.isNotEmpty() && !nextUrlList.contains(nextUrl)) { while (nextUrl.isNotEmpty() && !nextUrlList.contains(nextUrl)) {
if (!mNextChapterUrl.isNullOrEmpty() if (!mNextChapterUrl.isNullOrEmpty()
&& NetworkUtils.getAbsoluteURL(baseUrl, nextUrl) && NetworkUtils.getAbsoluteURL(redirectUrl, nextUrl)
== NetworkUtils.getAbsoluteURL(baseUrl, mNextChapterUrl) == NetworkUtils.getAbsoluteURL(redirectUrl, mNextChapterUrl)
) break ) break
nextUrlList.add(nextUrl) nextUrlList.add(nextUrl)
scope.ensureActive() scope.ensureActive()

@ -25,8 +25,8 @@ object BookInfo {
scope: CoroutineScope, scope: CoroutineScope,
bookSource: BookSource, bookSource: BookSource,
book: Book, book: Book,
redirectUrl: String,
baseUrl: String, baseUrl: String,
redirectUrl: String,
body: String?, body: String?,
canReName: Boolean, canReName: Boolean,
) { ) {
@ -126,7 +126,7 @@ object BookInfo {
Debug.log(bookSource.bookSourceUrl, "┌获取封面链接") Debug.log(bookSource.bookSourceUrl, "┌获取封面链接")
try { try {
analyzeRule.getString(infoRule.coverUrl).let { analyzeRule.getString(infoRule.coverUrl).let {
if (it.isNotEmpty()) book.coverUrl = NetworkUtils.getAbsoluteURL(baseUrl, it) if (it.isNotEmpty()) book.coverUrl = NetworkUtils.getAbsoluteURL(redirectUrl, it)
Debug.log(bookSource.bookSourceUrl, "${it}") Debug.log(bookSource.bookSourceUrl, "${it}")
} }
} catch (e: Exception) { } catch (e: Exception) {
@ -136,8 +136,8 @@ object BookInfo {
scope.ensureActive() scope.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取目录链接") Debug.log(bookSource.bookSourceUrl, "┌获取目录链接")
book.tocUrl = analyzeRule.getString(infoRule.tocUrl, isUrl = true) book.tocUrl = analyzeRule.getString(infoRule.tocUrl, isUrl = true)
if (book.tocUrl.isEmpty()) book.tocUrl = redirectUrl if (book.tocUrl.isEmpty()) book.tocUrl = baseUrl
if (book.tocUrl == redirectUrl) { if (book.tocUrl == baseUrl) {
book.tocHtml = body book.tocHtml = body
} }
Debug.log(bookSource.bookSourceUrl, "${book.tocUrl}") Debug.log(bookSource.bookSourceUrl, "${book.tocUrl}")

@ -144,8 +144,8 @@ object WebBook {
scope = scope, scope = scope,
bookSource = bookSource, bookSource = bookSource,
book = book, book = book,
redirectUrl = book.bookUrl,
baseUrl = book.bookUrl, baseUrl = book.bookUrl,
redirectUrl = book.bookUrl,
body = book.infoHtml, body = book.infoHtml,
canReName = canReName canReName = canReName
) )
@ -168,8 +168,8 @@ object WebBook {
scope = scope, scope = scope,
bookSource = bookSource, bookSource = bookSource,
book = book, book = book,
redirectUrl = book.bookUrl, baseUrl = book.bookUrl,
baseUrl = res.url, redirectUrl = res.url,
body = res.body, body = res.body,
canReName = canReName canReName = canReName
) )
@ -202,8 +202,8 @@ object WebBook {
scope = scope, scope = scope,
bookSource = bookSource, bookSource = bookSource,
book = book, book = book,
redirectUrl = book.tocUrl,
baseUrl = book.tocUrl, baseUrl = book.tocUrl,
redirectUrl = book.tocUrl,
body = book.tocHtml body = book.tocHtml
) )
} else { } else {
@ -225,8 +225,8 @@ object WebBook {
scope = scope, scope = scope,
bookSource = bookSource, bookSource = bookSource,
book = book, book = book,
redirectUrl = book.tocUrl, baseUrl = book.tocUrl,
baseUrl = res.url, redirectUrl = res.url,
body = res.body body = res.body
) )
} }
@ -271,8 +271,8 @@ object WebBook {
bookSource = bookSource, bookSource = bookSource,
book = book, book = book,
bookChapter = bookChapter, bookChapter = bookChapter,
redirectUrl = bookChapter.getAbsoluteURL(),
baseUrl = bookChapter.getAbsoluteURL(), baseUrl = bookChapter.getAbsoluteURL(),
redirectUrl = bookChapter.getAbsoluteURL(),
body = book.tocHtml, body = book.tocHtml,
nextChapterUrl = nextChapterUrl, nextChapterUrl = nextChapterUrl,
needSave = needSave needSave = needSave
@ -301,8 +301,8 @@ object WebBook {
bookSource = bookSource, bookSource = bookSource,
book = book, book = book,
bookChapter = bookChapter, bookChapter = bookChapter,
redirectUrl = bookChapter.getAbsoluteURL(), baseUrl = bookChapter.getAbsoluteURL(),
baseUrl = res.url, redirectUrl = res.url,
body = res.body, body = res.body,
nextChapterUrl = nextChapterUrl, nextChapterUrl = nextChapterUrl,
needSave = needSave needSave = needSave

@ -18,6 +18,7 @@ import io.legado.app.utils.viewbindingdelegate.viewBinding
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
@ -108,7 +109,8 @@ class FileAssociationActivity :
var doc = treeDoc!!.findFile(name) var doc = treeDoc!!.findFile(name)
if (doc == null || bookDoc.lastModified() > doc.lastModified()) { if (doc == null || bookDoc.lastModified() > doc.lastModified()) {
if (doc == null) { if (doc == null) {
doc = treeDoc.createFile(FileUtils.getMimeType(name), name)!! doc = treeDoc.createFile(FileUtils.getMimeType(name), name)
?: throw SecurityException("Permission Denial")
} }
contentResolver.openOutputStream(doc.uri)!!.use { oStream -> contentResolver.openOutputStream(doc.uri)!!.use { oStream ->
contentResolver.openInputStream(bookDoc.uri)!!.use { iStream -> contentResolver.openInputStream(bookDoc.uri)!!.use { iStream ->
@ -137,7 +139,17 @@ class FileAssociationActivity :
} }
} }
}.onFailure { }.onFailure {
toastOnUi(it.localizedMessage) when (it) {
is SecurityException -> localBookTreeSelect.launch {
title = "选择保存书籍的文件夹"
mode = HandleFileContract.DIR_SYS
}
else -> {
Timber.e(it, "导入书籍失败")
toastOnUi(it.localizedMessage)
finish()
}
}
} }
} }
} }

@ -91,7 +91,9 @@ class ReadRssActivity : VMBaseActivity<ActivityRssReadBinding, ReadRssViewModel>
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean { override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.menu_rss_refresh -> viewModel.refresh() R.id.menu_rss_refresh -> viewModel.refresh {
binding.webView.reload()
}
R.id.menu_rss_star -> viewModel.favorite() R.id.menu_rss_star -> viewModel.favorite()
R.id.menu_share_it -> viewModel.rssArticle?.let { R.id.menu_share_it -> viewModel.rssArticle?.let {
share(it.link) share(it.link)

@ -104,17 +104,17 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application),
} }
} }
fun refresh() { fun refresh(finish: () -> Unit) {
rssArticle?.let { rssArticle -> rssArticle?.let { rssArticle ->
rssSource?.let { rssSource?.let {
val ruleContent = it.ruleContent val ruleContent = it.ruleContent
if (!ruleContent.isNullOrBlank()) { if (!ruleContent.isNullOrBlank()) {
loadContent(rssArticle, ruleContent) loadContent(rssArticle, ruleContent)
} else { } else {
loadUrl(rssArticle.link, rssArticle.origin) finish.invoke()
} }
} ?: loadUrl(rssArticle.link, rssArticle.origin) } ?: finish.invoke()
} } ?: finish.invoke()
} }
fun favorite() { fun favorite() {

@ -169,7 +169,7 @@ class CodeView : AppCompatMultiAutoCompleteTextView {
} }
private fun highlight(editable: Editable): Editable { private fun highlight(editable: Editable): Editable {
if (editable.isEmpty()) return editable if (editable.isEmpty() || editable.length > 1024) return editable
try { try {
clearSpans(editable) clearSpans(editable)
highlightErrorLines(editable) highlightErrorLines(editable)

@ -10,49 +10,57 @@ private var toast: Toast? = null
fun Context.toastOnUi(message: Int) { fun Context.toastOnUi(message: Int) {
runOnUI { runOnUI {
if (toast == null) { kotlin.runCatching {
toast = Toast.makeText(this, message, Toast.LENGTH_SHORT) if (toast == null) {
} else { toast = Toast.makeText(this, message, Toast.LENGTH_SHORT)
toast?.setText(message) } else {
toast?.duration = Toast.LENGTH_SHORT toast?.setText(message)
toast?.duration = Toast.LENGTH_SHORT
}
toast?.show()
} }
toast?.show()
} }
} }
fun Context.toastOnUi(message: CharSequence?) { fun Context.toastOnUi(message: CharSequence?) {
runOnUI { runOnUI {
if (toast == null) { kotlin.runCatching {
toast = Toast.makeText(this, message, Toast.LENGTH_SHORT) if (toast == null) {
} else { toast = Toast.makeText(this, message, Toast.LENGTH_SHORT)
toast?.setText(message) } else {
toast?.duration = Toast.LENGTH_SHORT toast?.setText(message)
toast?.duration = Toast.LENGTH_SHORT
}
toast?.show()
} }
toast?.show()
} }
} }
fun Context.longToastOnUi(message: Int) { fun Context.longToastOnUi(message: Int) {
runOnUI { runOnUI {
if (toast == null) { kotlin.runCatching {
toast = Toast.makeText(this, message, Toast.LENGTH_LONG) if (toast == null) {
} else { toast = Toast.makeText(this, message, Toast.LENGTH_LONG)
toast?.setText(message) } else {
toast?.duration = Toast.LENGTH_LONG toast?.setText(message)
toast?.duration = Toast.LENGTH_LONG
}
toast?.show()
} }
toast?.show()
} }
} }
fun Context.longToastOnUi(message: CharSequence?) { fun Context.longToastOnUi(message: CharSequence?) {
runOnUI { runOnUI {
if (toast == null) { kotlin.runCatching {
toast = Toast.makeText(this, message, Toast.LENGTH_LONG) if (toast == null) {
} else { toast = Toast.makeText(this, message, Toast.LENGTH_LONG)
toast?.setText(message) } else {
toast?.duration = Toast.LENGTH_LONG toast?.setText(message)
toast?.duration = Toast.LENGTH_LONG
}
toast?.show()
} }
toast?.show()
} }
} }

Loading…
Cancel
Save