mirror of
https://github.com/gedoor/legado.git
synced 2025-08-10 00:52:30 +00:00
Merge branch 'master' into master
This commit is contained in:
@@ -82,8 +82,16 @@ java.base64Encode(str: String, flags: Int)
|
||||
* 文件
|
||||
> 所有对于文件的读写删操作都是相对路径,只能操作阅读缓存/android/data/{package}/cache/内的文件
|
||||
```
|
||||
java.readTxtFile(path: String): String
|
||||
java.deleteFile(path: String)
|
||||
//文件下载,content为十六进制字符串,url用于生成文件名,返回文件路径
|
||||
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)
|
||||
|
||||
@@ -11,6 +11,16 @@
|
||||
* 正文出现缺字漏字、内容缺失、排版错乱等情况,有可能是净化规则或简繁转换出现问题。
|
||||
* 漫画源看书显示乱码,**阅读与其他软件的源并不通用**,请导入阅读的支持的漫画源!
|
||||
|
||||
**2022/02/02**
|
||||
|
||||
* 字符串大于1024时禁用代码高亮
|
||||
* 修复baseUrl丢失参数的bug
|
||||
* 添加更新bookUrl和更新tocUrl函数
|
||||
* 标题单独净化,防止出现正文标题没了的问题
|
||||
* 修复一个null报错
|
||||
* 按返回键关闭搜索界面 by Xwite
|
||||
* 其它一些优化 by Xwite
|
||||
|
||||
**2022/01/28**
|
||||
|
||||
* 阅读背景添加透明度调节
|
||||
|
||||
@@ -9,6 +9,7 @@ import cn.hutool.crypto.symmetric.DESede
|
||||
import io.legado.app.BuildConfig
|
||||
import io.legado.app.constant.AppConst
|
||||
import io.legado.app.constant.AppConst.dateFormat
|
||||
import io.legado.app.constant.AppLog
|
||||
import io.legado.app.data.entities.BaseSource
|
||||
import io.legado.app.help.http.*
|
||||
import io.legado.app.model.Debug
|
||||
@@ -513,13 +514,14 @@ interface JsExtensions {
|
||||
/**
|
||||
* 输出调试日志
|
||||
*/
|
||||
fun log(msg: String): String {
|
||||
fun log(msg: Any?): Any? {
|
||||
getSource()?.let {
|
||||
Debug.log(it.getKey(), msg)
|
||||
} ?: Debug.log(msg)
|
||||
Debug.log(it.getKey(), msg.toString())
|
||||
} ?: Debug.log(msg.toString())
|
||||
if (BuildConfig.DEBUG) {
|
||||
Timber.d(msg)
|
||||
Timber.d(msg.toString())
|
||||
}
|
||||
AppLog.put(msg.toString())
|
||||
return msg
|
||||
}
|
||||
|
||||
@@ -692,7 +694,7 @@ interface JsExtensions {
|
||||
return aesEncodeToBase64ByteArray(data, key, transformation, iv)?.let { String(it) }
|
||||
}
|
||||
|
||||
fun android(): String {
|
||||
fun androidId(): String {
|
||||
return AppConst.androidId
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,11 @@ import android.text.TextUtils
|
||||
import androidx.annotation.Keep
|
||||
import io.legado.app.constant.AppConst.SCRIPT_ENGINE
|
||||
import io.legado.app.constant.AppPattern.JS_PATTERN
|
||||
import io.legado.app.data.entities.BaseBook
|
||||
import io.legado.app.data.entities.BaseSource
|
||||
import io.legado.app.data.entities.BookChapter
|
||||
import io.legado.app.data.entities.*
|
||||
import io.legado.app.help.CacheManager
|
||||
import io.legado.app.help.JsExtensions
|
||||
import io.legado.app.help.http.CookieStore
|
||||
import io.legado.app.model.webBook.WebBook
|
||||
import io.legado.app.utils.*
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jsoup.nodes.Entities
|
||||
@@ -684,6 +683,39 @@ class AnalyzeRule(
|
||||
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 {
|
||||
private val putPattern = Pattern.compile("@put:(\\{[^}]+?\\})", Pattern.CASE_INSENSITIVE)
|
||||
private val evalPattern =
|
||||
|
||||
@@ -126,7 +126,7 @@ class TextFile(private val book: Book) {
|
||||
val chapterContent = blockContent.substring(seekPos, chapterStart)
|
||||
val chapterLength = chapterContent.toByteArray(charset).size
|
||||
val lastStart = toc.lastOrNull()?.start ?: curOffset
|
||||
if (curOffset + chapterLength - lastStart > 50000) {
|
||||
if (curOffset + chapterLength - lastStart > 102400) {
|
||||
bis.close()
|
||||
return analyze()
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ object BookChapterList {
|
||||
scope: CoroutineScope,
|
||||
bookSource: BookSource,
|
||||
book: Book,
|
||||
redirectUrl: String,
|
||||
baseUrl: String,
|
||||
redirectUrl: String,
|
||||
body: String?
|
||||
): List<BookChapter> {
|
||||
body ?: throw NoStackTraceException(
|
||||
@@ -39,7 +39,7 @@ object BookChapterList {
|
||||
Debug.log(bookSource.bookSourceUrl, "≡获取成功:${baseUrl}")
|
||||
Debug.log(bookSource.bookSourceUrl, body, state = 30)
|
||||
val tocRule = bookSource.getTocRule()
|
||||
val nextUrlList = arrayListOf(baseUrl)
|
||||
val nextUrlList = arrayListOf(redirectUrl)
|
||||
var reverse = false
|
||||
var listRule = tocRule.chapterList ?: ""
|
||||
if (listRule.startsWith("-")) {
|
||||
@@ -157,7 +157,7 @@ object BookChapterList {
|
||||
Debug.log(bookSource.bookSourceUrl, "┌获取目录下一页列表", log)
|
||||
analyzeRule.getStringList(nextTocRule, isUrl = true)?.let {
|
||||
for (item in it) {
|
||||
if (item != baseUrl) {
|
||||
if (item != redirectUrl) {
|
||||
nextUrlList.add(item)
|
||||
}
|
||||
}
|
||||
@@ -180,7 +180,7 @@ object BookChapterList {
|
||||
elements.forEachIndexed { index, item ->
|
||||
scope.ensureActive()
|
||||
analyzeRule.setContent(item)
|
||||
val bookChapter = BookChapter(bookUrl = book.bookUrl, baseUrl = baseUrl)
|
||||
val bookChapter = BookChapter(bookUrl = book.bookUrl, baseUrl = redirectUrl)
|
||||
analyzeRule.chapter = bookChapter
|
||||
bookChapter.title = analyzeRule.getString(nameRule)
|
||||
bookChapter.url = analyzeRule.getString(urlRule)
|
||||
|
||||
@@ -32,8 +32,8 @@ object BookContent {
|
||||
bookSource: BookSource,
|
||||
book: Book,
|
||||
bookChapter: BookChapter,
|
||||
redirectUrl: String,
|
||||
baseUrl: String,
|
||||
redirectUrl: String,
|
||||
body: String?,
|
||||
nextChapterUrl: String?,
|
||||
needSave: Boolean = true
|
||||
@@ -50,10 +50,10 @@ object BookContent {
|
||||
nextChapterUrl
|
||||
}
|
||||
val content = StringBuilder()
|
||||
val nextUrlList = arrayListOf(baseUrl)
|
||||
val nextUrlList = arrayListOf(redirectUrl)
|
||||
val contentRule = bookSource.getContentRule()
|
||||
val analyzeRule = AnalyzeRule(book, bookSource).setContent(body, baseUrl)
|
||||
analyzeRule.setRedirectUrl(baseUrl)
|
||||
analyzeRule.setRedirectUrl(redirectUrl)
|
||||
analyzeRule.nextChapterUrl = mNextChapterUrl
|
||||
scope.ensureActive()
|
||||
var contentData = analyzeContent(
|
||||
@@ -64,8 +64,8 @@ object BookContent {
|
||||
var nextUrl = contentData.second[0]
|
||||
while (nextUrl.isNotEmpty() && !nextUrlList.contains(nextUrl)) {
|
||||
if (!mNextChapterUrl.isNullOrEmpty()
|
||||
&& NetworkUtils.getAbsoluteURL(baseUrl, nextUrl)
|
||||
== NetworkUtils.getAbsoluteURL(baseUrl, mNextChapterUrl)
|
||||
&& NetworkUtils.getAbsoluteURL(redirectUrl, nextUrl)
|
||||
== NetworkUtils.getAbsoluteURL(redirectUrl, mNextChapterUrl)
|
||||
) break
|
||||
nextUrlList.add(nextUrl)
|
||||
scope.ensureActive()
|
||||
|
||||
@@ -25,8 +25,8 @@ object BookInfo {
|
||||
scope: CoroutineScope,
|
||||
bookSource: BookSource,
|
||||
book: Book,
|
||||
redirectUrl: String,
|
||||
baseUrl: String,
|
||||
redirectUrl: String,
|
||||
body: String?,
|
||||
canReName: Boolean,
|
||||
) {
|
||||
@@ -126,7 +126,7 @@ object BookInfo {
|
||||
Debug.log(bookSource.bookSourceUrl, "┌获取封面链接")
|
||||
try {
|
||||
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}")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
@@ -136,8 +136,8 @@ object BookInfo {
|
||||
scope.ensureActive()
|
||||
Debug.log(bookSource.bookSourceUrl, "┌获取目录链接")
|
||||
book.tocUrl = analyzeRule.getString(infoRule.tocUrl, isUrl = true)
|
||||
if (book.tocUrl.isEmpty()) book.tocUrl = redirectUrl
|
||||
if (book.tocUrl == redirectUrl) {
|
||||
if (book.tocUrl.isEmpty()) book.tocUrl = baseUrl
|
||||
if (book.tocUrl == baseUrl) {
|
||||
book.tocHtml = body
|
||||
}
|
||||
Debug.log(bookSource.bookSourceUrl, "└${book.tocUrl}")
|
||||
|
||||
@@ -144,8 +144,8 @@ object WebBook {
|
||||
scope = scope,
|
||||
bookSource = bookSource,
|
||||
book = book,
|
||||
redirectUrl = book.bookUrl,
|
||||
baseUrl = book.bookUrl,
|
||||
redirectUrl = book.bookUrl,
|
||||
body = book.infoHtml,
|
||||
canReName = canReName
|
||||
)
|
||||
@@ -168,8 +168,8 @@ object WebBook {
|
||||
scope = scope,
|
||||
bookSource = bookSource,
|
||||
book = book,
|
||||
redirectUrl = book.bookUrl,
|
||||
baseUrl = res.url,
|
||||
baseUrl = book.bookUrl,
|
||||
redirectUrl = res.url,
|
||||
body = res.body,
|
||||
canReName = canReName
|
||||
)
|
||||
@@ -202,8 +202,8 @@ object WebBook {
|
||||
scope = scope,
|
||||
bookSource = bookSource,
|
||||
book = book,
|
||||
redirectUrl = book.tocUrl,
|
||||
baseUrl = book.tocUrl,
|
||||
redirectUrl = book.tocUrl,
|
||||
body = book.tocHtml
|
||||
)
|
||||
} else {
|
||||
@@ -225,8 +225,8 @@ object WebBook {
|
||||
scope = scope,
|
||||
bookSource = bookSource,
|
||||
book = book,
|
||||
redirectUrl = book.tocUrl,
|
||||
baseUrl = res.url,
|
||||
baseUrl = book.tocUrl,
|
||||
redirectUrl = res.url,
|
||||
body = res.body
|
||||
)
|
||||
}
|
||||
@@ -271,8 +271,8 @@ object WebBook {
|
||||
bookSource = bookSource,
|
||||
book = book,
|
||||
bookChapter = bookChapter,
|
||||
redirectUrl = bookChapter.getAbsoluteURL(),
|
||||
baseUrl = bookChapter.getAbsoluteURL(),
|
||||
redirectUrl = bookChapter.getAbsoluteURL(),
|
||||
body = book.tocHtml,
|
||||
nextChapterUrl = nextChapterUrl,
|
||||
needSave = needSave
|
||||
@@ -301,8 +301,8 @@ object WebBook {
|
||||
bookSource = bookSource,
|
||||
book = book,
|
||||
bookChapter = bookChapter,
|
||||
redirectUrl = bookChapter.getAbsoluteURL(),
|
||||
baseUrl = res.url,
|
||||
baseUrl = bookChapter.getAbsoluteURL(),
|
||||
redirectUrl = res.url,
|
||||
body = res.body,
|
||||
nextChapterUrl = nextChapterUrl,
|
||||
needSave = needSave
|
||||
|
||||
@@ -18,6 +18,7 @@ import io.legado.app.utils.viewbindingdelegate.viewBinding
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
|
||||
@@ -108,7 +109,8 @@ class FileAssociationActivity :
|
||||
var doc = treeDoc!!.findFile(name)
|
||||
if (doc == null || bookDoc.lastModified() > doc.lastModified()) {
|
||||
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.openInputStream(bookDoc.uri)!!.use { iStream ->
|
||||
@@ -137,7 +139,17 @@ class FileAssociationActivity :
|
||||
}
|
||||
}
|
||||
}.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 {
|
||||
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_share_it -> viewModel.rssArticle?.let {
|
||||
share(it.link)
|
||||
|
||||
@@ -104,17 +104,17 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application),
|
||||
}
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
fun refresh(finish: () -> Unit) {
|
||||
rssArticle?.let { rssArticle ->
|
||||
rssSource?.let {
|
||||
val ruleContent = it.ruleContent
|
||||
if (!ruleContent.isNullOrBlank()) {
|
||||
loadContent(rssArticle, ruleContent)
|
||||
} else {
|
||||
loadUrl(rssArticle.link, rssArticle.origin)
|
||||
finish.invoke()
|
||||
}
|
||||
} ?: loadUrl(rssArticle.link, rssArticle.origin)
|
||||
}
|
||||
} ?: finish.invoke()
|
||||
} ?: finish.invoke()
|
||||
}
|
||||
|
||||
fun favorite() {
|
||||
|
||||
@@ -169,7 +169,7 @@ class CodeView : AppCompatMultiAutoCompleteTextView {
|
||||
}
|
||||
|
||||
private fun highlight(editable: Editable): Editable {
|
||||
if (editable.isEmpty()) return editable
|
||||
if (editable.isEmpty() || editable.length > 1024) return editable
|
||||
try {
|
||||
clearSpans(editable)
|
||||
highlightErrorLines(editable)
|
||||
|
||||
@@ -10,49 +10,57 @@ private var toast: Toast? = null
|
||||
|
||||
fun Context.toastOnUi(message: Int) {
|
||||
runOnUI {
|
||||
if (toast == null) {
|
||||
toast = Toast.makeText(this, message, Toast.LENGTH_SHORT)
|
||||
} else {
|
||||
toast?.setText(message)
|
||||
toast?.duration = Toast.LENGTH_SHORT
|
||||
kotlin.runCatching {
|
||||
if (toast == null) {
|
||||
toast = Toast.makeText(this, message, Toast.LENGTH_SHORT)
|
||||
} else {
|
||||
toast?.setText(message)
|
||||
toast?.duration = Toast.LENGTH_SHORT
|
||||
}
|
||||
toast?.show()
|
||||
}
|
||||
toast?.show()
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.toastOnUi(message: CharSequence?) {
|
||||
runOnUI {
|
||||
if (toast == null) {
|
||||
toast = Toast.makeText(this, message, Toast.LENGTH_SHORT)
|
||||
} else {
|
||||
toast?.setText(message)
|
||||
toast?.duration = Toast.LENGTH_SHORT
|
||||
kotlin.runCatching {
|
||||
if (toast == null) {
|
||||
toast = Toast.makeText(this, message, Toast.LENGTH_SHORT)
|
||||
} else {
|
||||
toast?.setText(message)
|
||||
toast?.duration = Toast.LENGTH_SHORT
|
||||
}
|
||||
toast?.show()
|
||||
}
|
||||
toast?.show()
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.longToastOnUi(message: Int) {
|
||||
runOnUI {
|
||||
if (toast == null) {
|
||||
toast = Toast.makeText(this, message, Toast.LENGTH_LONG)
|
||||
} else {
|
||||
toast?.setText(message)
|
||||
toast?.duration = Toast.LENGTH_LONG
|
||||
kotlin.runCatching {
|
||||
if (toast == null) {
|
||||
toast = Toast.makeText(this, message, Toast.LENGTH_LONG)
|
||||
} else {
|
||||
toast?.setText(message)
|
||||
toast?.duration = Toast.LENGTH_LONG
|
||||
}
|
||||
toast?.show()
|
||||
}
|
||||
toast?.show()
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.longToastOnUi(message: CharSequence?) {
|
||||
runOnUI {
|
||||
if (toast == null) {
|
||||
toast = Toast.makeText(this, message, Toast.LENGTH_LONG)
|
||||
} else {
|
||||
toast?.setText(message)
|
||||
toast?.duration = Toast.LENGTH_LONG
|
||||
kotlin.runCatching {
|
||||
if (toast == null) {
|
||||
toast = Toast.makeText(this, message, Toast.LENGTH_LONG)
|
||||
} else {
|
||||
toast?.setText(message)
|
||||
toast?.duration = Toast.LENGTH_LONG
|
||||
}
|
||||
toast?.show()
|
||||
}
|
||||
toast?.show()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user