mirror of
https://github.com/gedoor/legado.git
synced 2025-08-10 00:52:30 +00:00
优化
This commit is contained in:
@@ -12,6 +12,7 @@ import android.os.Build
|
||||
import com.github.liuyueyi.quick.transfer.constants.TransType
|
||||
import com.jeremyliao.liveeventbus.LiveEventBus
|
||||
import com.jeremyliao.liveeventbus.logger.DefaultLogger
|
||||
import com.script.rhino.RhinoScriptEngine
|
||||
import io.legado.app.base.AppContextWrapper
|
||||
import io.legado.app.constant.AppConst.channelIdDownload
|
||||
import io.legado.app.constant.AppConst.channelIdReadAloud
|
||||
@@ -77,6 +78,7 @@ class App : Application() {
|
||||
Coroutine.async {
|
||||
URL.setURLStreamHandlerFactory(ObsoleteUrlFactory(okHttpClient))
|
||||
launch { installGmsTlsProvider(appCtx) }
|
||||
RhinoScriptEngine
|
||||
//初始化封面
|
||||
BookCover.toString()
|
||||
//清除过期数据
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import io.legado.app.model.analyzeRule.AnalyzeRule
|
||||
import io.legado.app.model.analyzeRule.AnalyzeUrl
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
/**
|
||||
* 字典规则
|
||||
@@ -36,12 +37,12 @@ data class DictRule(
|
||||
* 搜索字典
|
||||
*/
|
||||
suspend fun search(word: String): String {
|
||||
val analyzeUrl = AnalyzeUrl(urlRule, key = word)
|
||||
val analyzeUrl = AnalyzeUrl(urlRule, key = word, coroutineContext = coroutineContext)
|
||||
val body = analyzeUrl.getStrResponseAwait().body
|
||||
if (showRule.isBlank()) {
|
||||
return body!!
|
||||
}
|
||||
val analyzeRule = AnalyzeRule()
|
||||
val analyzeRule = AnalyzeRule().setCoroutineContext(coroutineContext)
|
||||
return analyzeRule.getString(showRule, mContent = body)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import io.legado.app.utils.fromJsonArray
|
||||
import io.legado.app.utils.fromJsonObject
|
||||
import splitties.init.appCtx
|
||||
import java.io.File
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
object DirectLinkUpload {
|
||||
@@ -60,6 +61,7 @@ object DirectLinkUpload {
|
||||
mFile.delete()
|
||||
}
|
||||
val analyzeRule = AnalyzeRule().setContent(res.body, res.url)
|
||||
.setCoroutineContext(coroutineContext)
|
||||
val downloadUrl = analyzeRule.getString(downloadUrlRule)
|
||||
if (downloadUrl.isBlank()) {
|
||||
throw NoStackTraceException("上传失败,${res.body}")
|
||||
|
||||
@@ -28,6 +28,7 @@ import io.legado.app.utils.getFile
|
||||
import io.legado.app.utils.isContentScheme
|
||||
import io.legado.app.utils.onEachParallel
|
||||
import io.legado.app.utils.postEvent
|
||||
import io.legado.app.utils.runScriptWithContext
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.ensureActive
|
||||
@@ -232,12 +233,16 @@ object BookHelp {
|
||||
if (isImageExist(book, src)) {
|
||||
return
|
||||
}
|
||||
val analyzeUrl = AnalyzeUrl(src, source = bookSource)
|
||||
val analyzeUrl = AnalyzeUrl(
|
||||
src, source = bookSource, coroutineContext = coroutineContext
|
||||
)
|
||||
val bytes = analyzeUrl.getByteArrayAwait()
|
||||
//某些图片被加密,需要进一步解密
|
||||
ImageUtils.decode(
|
||||
src, bytes, isCover = false, bookSource, book
|
||||
)?.let {
|
||||
runScriptWithContext {
|
||||
ImageUtils.decode(
|
||||
src, bytes, isCover = false, bookSource, book
|
||||
)
|
||||
}?.let {
|
||||
if (!checkImage(it)) {
|
||||
// 如果部分图片失效,每次进入正文都会花很长时间再次获取图片数据
|
||||
// 所以无论如何都要将数据写入到文件里
|
||||
@@ -546,12 +551,16 @@ object BookHelp {
|
||||
}
|
||||
|
||||
private val chapterNamePattern1 by lazy {
|
||||
Pattern.compile(".*?第([\\d零〇一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+)[章节篇回集话]")
|
||||
Pattern.compile(
|
||||
".*?第([\\d零〇一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+)[章节篇回集话]"
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("RegExpSimplifiable")
|
||||
private val chapterNamePattern2 by lazy {
|
||||
Pattern.compile("^(?:[\\d零〇一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+[,:、])*([\\d零〇一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+)(?:[,:、]|\\.[^\\d])")
|
||||
Pattern.compile(
|
||||
"^(?:[\\d零〇一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+[,:、])*([\\d零〇一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+)(?:[,:、]|\\.[^\\d])"
|
||||
)
|
||||
}
|
||||
|
||||
private val regexA by lazy {
|
||||
|
||||
@@ -12,6 +12,8 @@ import io.legado.app.exception.NoStackTraceException
|
||||
import io.legado.app.model.ReadManga
|
||||
import io.legado.app.model.analyzeRule.AnalyzeUrl
|
||||
import io.legado.app.utils.ImageUtils
|
||||
import io.legado.app.utils.runScriptWithContext
|
||||
import kotlinx.coroutines.Job
|
||||
import java.io.InputStream
|
||||
|
||||
class LegadoDataUrlLoader : ModelLoader<String, InputStream> {
|
||||
@@ -33,15 +35,23 @@ class LegadoDataUrlLoader : ModelLoader<String, InputStream> {
|
||||
}
|
||||
|
||||
class LegadoDataUrlFetcher(private val model: String) : DataFetcher<InputStream> {
|
||||
|
||||
private val coroutineContext = Job()
|
||||
|
||||
override fun loadData(
|
||||
priority: Priority,
|
||||
callback: DataFetcher.DataCallback<in InputStream>
|
||||
) {
|
||||
try {
|
||||
val bytes = AnalyzeUrl(model, source = ReadManga.bookSource).getByteArray()
|
||||
val decoded = ImageUtils.decode(
|
||||
model, bytes, isCover = false, ReadManga.bookSource, ReadManga.book
|
||||
)?.inputStream()
|
||||
val bytes = AnalyzeUrl(
|
||||
model, source = ReadManga.bookSource,
|
||||
coroutineContext = coroutineContext
|
||||
).getByteArray()
|
||||
val decoded = runScriptWithContext(coroutineContext) {
|
||||
ImageUtils.decode(
|
||||
model, bytes, isCover = false, ReadManga.bookSource, ReadManga.book
|
||||
)?.inputStream()
|
||||
}
|
||||
if (decoded == null) {
|
||||
throw NoStackTraceException("漫画图片解密失败")
|
||||
}
|
||||
@@ -56,7 +66,7 @@ class LegadoDataUrlLoader : ModelLoader<String, InputStream> {
|
||||
}
|
||||
|
||||
override fun cancel() {
|
||||
// do nothing
|
||||
coroutineContext.cancel()
|
||||
}
|
||||
|
||||
override fun getDataClass(): Class<InputStream> {
|
||||
|
||||
@@ -17,6 +17,8 @@ import io.legado.app.help.source.SourceHelp
|
||||
import io.legado.app.model.ReadManga
|
||||
import io.legado.app.utils.ImageUtils
|
||||
import io.legado.app.utils.isWifiConnect
|
||||
import io.legado.app.utils.runScriptWithContext
|
||||
import kotlinx.coroutines.Job
|
||||
import okhttp3.Call
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
@@ -38,6 +40,7 @@ class OkHttpStreamFetcher(
|
||||
private var callback: DataFetcher.DataCallback<in InputStream>? = null
|
||||
private var source: BaseSource? = null
|
||||
private val manga = options.get(OkHttpModelLoader.mangaOption) == true
|
||||
private val coroutineContext = Job()
|
||||
|
||||
@Volatile
|
||||
private var call: Call? = null
|
||||
@@ -89,6 +92,7 @@ class OkHttpStreamFetcher(
|
||||
|
||||
override fun cancel() {
|
||||
call?.cancel()
|
||||
coroutineContext.cancel()
|
||||
}
|
||||
|
||||
override fun getDataClass(): Class<InputStream> {
|
||||
@@ -106,19 +110,21 @@ class OkHttpStreamFetcher(
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
responseBody = response.body
|
||||
if (response.isSuccessful) {
|
||||
val decodeResult = if (manga) {
|
||||
ImageUtils.decode(
|
||||
oldUrl.toString(),
|
||||
responseBody!!.bytes(),
|
||||
isCover = false,
|
||||
source,
|
||||
ReadManga.book
|
||||
)?.inputStream()
|
||||
} else {
|
||||
ImageUtils.decode(
|
||||
url.toStringUrl(), responseBody!!.byteStream(),
|
||||
isCover = true, source
|
||||
)
|
||||
val decodeResult = runScriptWithContext(coroutineContext) {
|
||||
if (manga) {
|
||||
ImageUtils.decode(
|
||||
oldUrl.toString(),
|
||||
responseBody!!.bytes(),
|
||||
isCover = false,
|
||||
source,
|
||||
ReadManga.book
|
||||
)?.inputStream()
|
||||
} else {
|
||||
ImageUtils.decode(
|
||||
url.toStringUrl(), responseBody!!.byteStream(),
|
||||
isCover = true, source
|
||||
)
|
||||
}
|
||||
}
|
||||
if (decodeResult == null) {
|
||||
callback?.onLoadFailed(NoStackTraceException("封面二次解密失败"))
|
||||
|
||||
@@ -11,6 +11,7 @@ import io.legado.app.utils.MD5Utils
|
||||
import io.legado.app.utils.fromJsonArray
|
||||
import io.legado.app.utils.isJsonArray
|
||||
import io.legado.app.utils.printOnDebug
|
||||
import io.legado.app.utils.runScriptWithContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
@@ -61,7 +62,9 @@ suspend fun BookSource.exploreKinds(): List<ExploreKind> {
|
||||
} else {
|
||||
exploreUrl.substring(4, exploreUrl.lastIndexOf("<"))
|
||||
}
|
||||
ruleStr = evalJS(jsStr).toString().trim()
|
||||
ruleStr = runScriptWithContext {
|
||||
evalJS(jsStr).toString().trim()
|
||||
}
|
||||
aCache.put(exploreKindsKey, ruleStr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import io.legado.app.data.entities.RssSource
|
||||
import io.legado.app.utils.ACache
|
||||
import io.legado.app.utils.MD5Utils
|
||||
import io.legado.app.utils.NetworkUtils
|
||||
import io.legado.app.utils.runScriptWithContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@@ -29,7 +30,9 @@ suspend fun RssSource.sortUrls(): List<Pair<String, String>> {
|
||||
} else {
|
||||
sortUrl!!.substring(4, sortUrl!!.lastIndexOf("<"))
|
||||
}
|
||||
str = evalJS(jsStr).toString()
|
||||
str = runScriptWithContext {
|
||||
evalJS(jsStr).toString()
|
||||
}
|
||||
aCache.put(sortUrlsKey, str)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,6 @@ object SourceVerificationHelp {
|
||||
putExtra("sourceName", source.getTag())
|
||||
putExtra("sourceVerificationEnable", saveResult)
|
||||
putExtra("refetchAfterSuccess", refetchAfterSuccess)
|
||||
IntentData.put(url, source.getHeaderMap(true))
|
||||
IntentData.put(getVerificationResultKey(source), Thread.currentThread())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import io.legado.app.utils.getPrefBoolean
|
||||
import io.legado.app.utils.getPrefString
|
||||
import splitties.init.appCtx
|
||||
import java.io.File
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
@Keep
|
||||
object BookCover {
|
||||
@@ -174,10 +175,12 @@ object BookCover {
|
||||
config.searchUrl,
|
||||
book.name,
|
||||
source = config,
|
||||
headerMapF = config.getHeaderMap()
|
||||
coroutineContext = coroutineContext,
|
||||
hasLoginHeader = false
|
||||
)
|
||||
val res = analyzeUrl.getStrResponseAwait()
|
||||
val analyzeRule = AnalyzeRule(book)
|
||||
analyzeRule.setCoroutineContext(coroutineContext)
|
||||
analyzeRule.setContent(res.body)
|
||||
analyzeRule.setRedirectUrl(res.url)
|
||||
return analyzeRule.getString(config.coverRule, isUrl = true)
|
||||
|
||||
@@ -45,6 +45,7 @@ import io.legado.app.utils.isJson
|
||||
import io.legado.app.utils.isJsonArray
|
||||
import io.legado.app.utils.isJsonObject
|
||||
import io.legado.app.utils.isXml
|
||||
import io.legado.app.utils.runScriptWithContext
|
||||
import io.legado.app.utils.splitNotBlank
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
@@ -81,6 +82,7 @@ class AnalyzeUrl(
|
||||
private val readTimeout: Long? = null,
|
||||
private var coroutineContext: CoroutineContext = EmptyCoroutineContext,
|
||||
headerMapF: Map<String, String>? = null,
|
||||
hasLoginHeader: Boolean = true
|
||||
) : JsExtensions {
|
||||
companion object {
|
||||
val paramPattern: Pattern = Pattern.compile("\\s*,\\s*(?=\\{)")
|
||||
@@ -118,7 +120,9 @@ class AnalyzeUrl(
|
||||
coroutineContext = coroutineContext.minusKey(ContinuationInterceptor)
|
||||
val urlMatcher = paramPattern.matcher(baseUrl)
|
||||
if (urlMatcher.find()) baseUrl = baseUrl.substring(0, urlMatcher.start())
|
||||
(headerMapF ?: source?.getHeaderMap(true))?.let {
|
||||
(headerMapF ?: runScriptWithContext(coroutineContext) {
|
||||
source?.getHeaderMap(hasLoginHeader)
|
||||
})?.let {
|
||||
headerMap.putAll(it)
|
||||
if (it.containsKey("proxy")) {
|
||||
proxy = it["proxy"]
|
||||
|
||||
@@ -12,6 +12,7 @@ import io.legado.app.utils.NetworkUtils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
object Rss {
|
||||
@@ -41,7 +42,8 @@ object Rss {
|
||||
page = page,
|
||||
source = rssSource,
|
||||
ruleData = ruleData,
|
||||
headerMapF = rssSource.getHeaderMap()
|
||||
coroutineContext = coroutineContext,
|
||||
hasLoginHeader = false
|
||||
)
|
||||
val res = analyzeUrl.getStrResponseAwait()
|
||||
checkRedirect(rssSource, res)
|
||||
@@ -70,7 +72,8 @@ object Rss {
|
||||
baseUrl = rssArticle.origin,
|
||||
source = rssSource,
|
||||
ruleData = rssArticle,
|
||||
headerMapF = rssSource.getHeaderMap()
|
||||
coroutineContext = coroutineContext,
|
||||
hasLoginHeader = false
|
||||
)
|
||||
val res = analyzeUrl.getStrResponseAwait()
|
||||
checkRedirect(rssSource, res)
|
||||
@@ -79,6 +82,7 @@ object Rss {
|
||||
val analyzeRule = AnalyzeRule(rssArticle, rssSource)
|
||||
analyzeRule.setContent(res.body)
|
||||
.setBaseUrl(NetworkUtils.getAbsoluteURL(rssArticle.origin, rssArticle.link))
|
||||
.setCoroutineContext(coroutineContext)
|
||||
.setRedirectUrl(res.url)
|
||||
return analyzeRule.getString(ruleContent)
|
||||
}
|
||||
|
||||
@@ -11,12 +11,13 @@ import io.legado.app.model.analyzeRule.RuleData
|
||||
import io.legado.app.utils.NetworkUtils
|
||||
import splitties.init.appCtx
|
||||
import java.util.Locale
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
@Keep
|
||||
object RssParserByRule {
|
||||
|
||||
@Throws(Exception::class)
|
||||
fun parseXML(
|
||||
suspend fun parseXML(
|
||||
sortName: String,
|
||||
sortUrl: String,
|
||||
redirectUrl: String,
|
||||
@@ -40,6 +41,7 @@ object RssParserByRule {
|
||||
} else {
|
||||
val articleList = mutableListOf<RssArticle>()
|
||||
val analyzeRule = AnalyzeRule(ruleData, rssSource)
|
||||
analyzeRule.setCoroutineContext(coroutineContext)
|
||||
analyzeRule.setContent(body).setBaseUrl(sortUrl)
|
||||
analyzeRule.setRedirectUrl(redirectUrl)
|
||||
var reverse = false
|
||||
|
||||
@@ -70,7 +70,6 @@ object BookChapterList {
|
||||
mUrl = nextUrl,
|
||||
source = bookSource,
|
||||
ruleData = book,
|
||||
headerMapF = bookSource.getHeaderMap(),
|
||||
coroutineContext = coroutineContext
|
||||
)
|
||||
val res = analyzeUrl.getStrResponseAwait() //控制并发访问
|
||||
@@ -100,7 +99,6 @@ object BookChapterList {
|
||||
mUrl = urlStr,
|
||||
source = bookSource,
|
||||
ruleData = book,
|
||||
headerMapF = bookSource.getHeaderMap(),
|
||||
coroutineContext = coroutineContext
|
||||
)
|
||||
val res = analyzeUrl.getStrResponseAwait() //控制并发访问
|
||||
|
||||
@@ -90,7 +90,6 @@ object BookContent {
|
||||
mUrl = nextUrl,
|
||||
source = bookSource,
|
||||
ruleData = book,
|
||||
headerMapF = bookSource.getHeaderMap(),
|
||||
coroutineContext = coroutineContext
|
||||
)
|
||||
val res = analyzeUrl.getStrResponseAwait() //控制并发访问
|
||||
@@ -118,7 +117,6 @@ object BookContent {
|
||||
mUrl = urlStr,
|
||||
source = bookSource,
|
||||
ruleData = book,
|
||||
headerMapF = bookSource.getHeaderMap(),
|
||||
coroutineContext = coroutineContext
|
||||
)
|
||||
val res = analyzeUrl.getStrResponseAwait() //控制并发访问
|
||||
|
||||
@@ -62,7 +62,6 @@ object WebBook {
|
||||
key = key,
|
||||
page = page,
|
||||
baseUrl = bookSource.bookSourceUrl,
|
||||
headerMapF = bookSource.getHeaderMap(true),
|
||||
source = bookSource,
|
||||
ruleData = ruleData,
|
||||
coroutineContext = coroutineContext
|
||||
@@ -115,7 +114,6 @@ object WebBook {
|
||||
baseUrl = bookSource.bookSourceUrl,
|
||||
source = bookSource,
|
||||
ruleData = ruleData,
|
||||
headerMapF = bookSource.getHeaderMap(true),
|
||||
coroutineContext = coroutineContext
|
||||
)
|
||||
var res = analyzeUrl.getStrResponseAwait()
|
||||
@@ -173,7 +171,6 @@ object WebBook {
|
||||
baseUrl = bookSource.bookSourceUrl,
|
||||
source = bookSource,
|
||||
ruleData = book,
|
||||
headerMapF = bookSource.getHeaderMap(true),
|
||||
coroutineContext = coroutineContext
|
||||
)
|
||||
var res = analyzeUrl.getStrResponseAwait()
|
||||
@@ -249,7 +246,6 @@ object WebBook {
|
||||
baseUrl = book.bookUrl,
|
||||
source = bookSource,
|
||||
ruleData = book,
|
||||
headerMapF = bookSource.getHeaderMap(true),
|
||||
coroutineContext = coroutineContext
|
||||
)
|
||||
var res = analyzeUrl.getStrResponseAwait()
|
||||
@@ -328,7 +324,6 @@ object WebBook {
|
||||
source = bookSource,
|
||||
ruleData = book,
|
||||
chapter = bookChapter,
|
||||
headerMapF = bookSource.getHeaderMap(true),
|
||||
coroutineContext = coroutineContext
|
||||
)
|
||||
var res = analyzeUrl.getStrResponseAwait(
|
||||
|
||||
@@ -225,6 +225,7 @@ class AudioPlayService : BaseService(),
|
||||
source = AudioPlay.bookSource,
|
||||
ruleData = AudioPlay.book,
|
||||
chapter = AudioPlay.durChapter,
|
||||
coroutineContext = coroutineContext
|
||||
)
|
||||
exoPlayer.setMediaItem(analyzeUrl.getMediaItem())
|
||||
exoPlayer.playWhenReady = true
|
||||
|
||||
@@ -328,7 +328,6 @@ class HttpReadAloudService : BaseReadAloudService(),
|
||||
speakText = speakText,
|
||||
speakSpeed = speechRate,
|
||||
source = httpTts,
|
||||
headerMapF = httpTts.getHeaderMap(true),
|
||||
readTimeout = 300 * 1000L,
|
||||
coroutineContext = coroutineContext
|
||||
)
|
||||
|
||||
@@ -274,7 +274,10 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
|
||||
val fileNameNoExtension = if (book.author.isBlank()) book.name
|
||||
else "${book.name} 作者:${book.author}"
|
||||
book.downloadUrls!!.map {
|
||||
val analyzeUrl = AnalyzeUrl(it, source = bookSource)
|
||||
val analyzeUrl = AnalyzeUrl(
|
||||
it, source = bookSource,
|
||||
coroutineContext = coroutineContext
|
||||
)
|
||||
val mFileName = UrlUtil.getFileName(analyzeUrl)
|
||||
?: "${fileNameNoExtension}.${analyzeUrl.type}"
|
||||
WebFile(it, mFileName)
|
||||
|
||||
@@ -12,10 +12,10 @@ import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import io.legado.app.R
|
||||
import io.legado.app.databinding.ViewMangaMenuBinding
|
||||
import io.legado.app.help.IntentData
|
||||
import io.legado.app.help.config.AppConfig
|
||||
import io.legado.app.lib.dialogs.alert
|
||||
import io.legado.app.lib.theme.bottomBackground
|
||||
import io.legado.app.model.ReadBook
|
||||
import io.legado.app.model.ReadManga
|
||||
import io.legado.app.ui.browser.WebViewActivity
|
||||
import io.legado.app.ui.widget.seekbar.SeekBarChangeListener
|
||||
@@ -176,9 +176,11 @@ class MangaMenu @JvmOverloads constructor(
|
||||
} else {
|
||||
context.startActivity<WebViewActivity> {
|
||||
val url = tvChapterUrl.text.toString()
|
||||
val bookSource = ReadBook.bookSource
|
||||
putExtra("title", tvChapterName.text)
|
||||
putExtra("url", url)
|
||||
IntentData.put(url, ReadManga.bookSource?.getHeaderMap(true))
|
||||
putExtra("sourceOrigin", bookSource?.bookSourceUrl)
|
||||
putExtra("sourceName", bookSource?.bookSourceName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1281,7 +1281,7 @@ class ReadBookActivity : BaseReadBookActivity(),
|
||||
alert(R.string.chapter_pay) {
|
||||
setMessage(chapter.title)
|
||||
yesButton {
|
||||
Coroutine.async {
|
||||
Coroutine.async(lifecycleScope) {
|
||||
val source =
|
||||
ReadBook.bookSource ?: throw NoStackTraceException("no book source")
|
||||
val payAction = source.getContentRule().payAction
|
||||
@@ -1289,6 +1289,7 @@ class ReadBookActivity : BaseReadBookActivity(),
|
||||
throw NoStackTraceException("no pay action")
|
||||
}
|
||||
val analyzeRule = AnalyzeRule(book, source)
|
||||
analyzeRule.setCoroutineContext(coroutineContext)
|
||||
analyzeRule.setBaseUrl(chapter.url)
|
||||
analyzeRule.chapter = chapter
|
||||
analyzeRule.evalJS(payAction).toString()
|
||||
@@ -1297,7 +1298,7 @@ class ReadBookActivity : BaseReadBookActivity(),
|
||||
startActivity<WebViewActivity> {
|
||||
putExtra("title", getString(R.string.chapter_pay))
|
||||
putExtra("url", it)
|
||||
IntentData.put(it, ReadBook.bookSource?.getHeaderMap(true))
|
||||
putExtra("sourceOrigin", ReadBook.bookSource?.bookSourceUrl)
|
||||
}
|
||||
} else if (it.isTrue()) {
|
||||
//购买成功后刷新目录
|
||||
|
||||
@@ -18,7 +18,6 @@ import androidx.core.view.isVisible
|
||||
import io.legado.app.R
|
||||
import io.legado.app.constant.PreferKey
|
||||
import io.legado.app.databinding.ViewReadMenuBinding
|
||||
import io.legado.app.help.IntentData
|
||||
import io.legado.app.help.config.AppConfig
|
||||
import io.legado.app.help.config.LocalConfig
|
||||
import io.legado.app.help.config.ReadBookConfig
|
||||
@@ -350,9 +349,11 @@ class ReadMenu @JvmOverloads constructor(
|
||||
Coroutine.async {
|
||||
context.startActivity<WebViewActivity> {
|
||||
val url = tvChapterUrl.text.toString()
|
||||
val bookSource = ReadBook.bookSource
|
||||
putExtra("title", tvChapterName.text)
|
||||
putExtra("url", url)
|
||||
IntentData.put(url, ReadBook.bookSource?.getHeaderMap(true))
|
||||
putExtra("sourceOrigin", bookSource?.bookSourceUrl)
|
||||
putExtra("sourceName", bookSource?.bookSourceName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import io.legado.app.base.BaseViewModel
|
||||
import io.legado.app.constant.AppConst
|
||||
import io.legado.app.data.appDb
|
||||
import io.legado.app.exception.NoStackTraceException
|
||||
import io.legado.app.help.IntentData
|
||||
import io.legado.app.help.http.newCallResponseBody
|
||||
import io.legado.app.help.http.okHttpClient
|
||||
import io.legado.app.help.source.SourceVerificationHelp
|
||||
@@ -48,8 +47,8 @@ class WebViewModel(application: Application) : BaseViewModel(application) {
|
||||
sourceOrigin = intent.getStringExtra("sourceOrigin") ?: ""
|
||||
sourceVerificationEnable = intent.getBooleanExtra("sourceVerificationEnable", false)
|
||||
refetchAfterSuccess = intent.getBooleanExtra("refetchAfterSuccess", true)
|
||||
val headerMapF = IntentData.get<Map<String, String>>(url)
|
||||
val analyzeUrl = AnalyzeUrl(url, headerMapF = headerMapF)
|
||||
val source = appDb.bookSourceDao.getBookSource(sourceOrigin)
|
||||
val analyzeUrl = AnalyzeUrl(url, source = source, coroutineContext = coroutineContext)
|
||||
baseUrl = analyzeUrl.url
|
||||
headerMap.putAll(analyzeUrl.headerMap)
|
||||
if (analyzeUrl.isPost()) {
|
||||
@@ -107,7 +106,8 @@ class WebViewModel(application: Application) : BaseViewModel(application) {
|
||||
html = AnalyzeUrl(
|
||||
url,
|
||||
headerMapF = headerMap,
|
||||
source = source
|
||||
source = source,
|
||||
coroutineContext = coroutineContext
|
||||
).getStrResponseAwait(useWebView = false).body
|
||||
SourceVerificationHelp.setResult(sourceOrigin, html ?: "")
|
||||
}.onSuccess {
|
||||
|
||||
@@ -16,7 +16,6 @@ import io.legado.app.data.entities.rule.RowUi
|
||||
import io.legado.app.databinding.DialogLoginBinding
|
||||
import io.legado.app.databinding.ItemFilletTextBinding
|
||||
import io.legado.app.databinding.ItemSourceEditBinding
|
||||
import io.legado.app.help.coroutine.Coroutine
|
||||
import io.legado.app.lib.dialogs.alert
|
||||
import io.legado.app.lib.theme.primaryColor
|
||||
import io.legado.app.ui.about.AppLogDialog
|
||||
@@ -26,6 +25,7 @@ import io.legado.app.utils.dpToPx
|
||||
import io.legado.app.utils.isAbsUrl
|
||||
import io.legado.app.utils.openUrl
|
||||
import io.legado.app.utils.printOnDebug
|
||||
import io.legado.app.utils.runScriptWithContext
|
||||
import io.legado.app.utils.setLayout
|
||||
import io.legado.app.utils.showDialogFragment
|
||||
import io.legado.app.utils.toastOnUi
|
||||
@@ -119,16 +119,18 @@ class SourceLoginDialog : BaseDialogFragment(R.layout.dialog_login, true) {
|
||||
}
|
||||
|
||||
private fun handleButtonClick(source: BaseSource, rowUi: RowUi, loginUi: List<RowUi>) {
|
||||
Coroutine.async {
|
||||
lifecycleScope.launch(IO) {
|
||||
if (rowUi.action.isAbsUrl()) {
|
||||
context?.openUrl(rowUi.action!!)
|
||||
} else if (rowUi.action != null) {
|
||||
// JavaScript
|
||||
val buttonFunctionJS = rowUi.action!!
|
||||
val loginJS = source.getLoginJs() ?: return@async
|
||||
val loginJS = source.getLoginJs() ?: return@launch
|
||||
kotlin.runCatching {
|
||||
source.evalJS("$loginJS\n$buttonFunctionJS") {
|
||||
put("result", getLoginData(loginUi))
|
||||
runScriptWithContext {
|
||||
source.evalJS("$loginJS\n$buttonFunctionJS") {
|
||||
put("result", getLoginData(loginUi))
|
||||
}
|
||||
}
|
||||
}.onFailure { e ->
|
||||
AppLog.put("LoginUI Button ${rowUi.name} JavaScript error", e)
|
||||
@@ -161,7 +163,9 @@ class SourceLoginDialog : BaseDialogFragment(R.layout.dialog_login, true) {
|
||||
}
|
||||
} else if (source.putLoginInfo(GSON.toJson(loginData))) {
|
||||
try {
|
||||
source.login()
|
||||
runScriptWithContext {
|
||||
source.login()
|
||||
}
|
||||
context?.toastOnUi(R.string.success)
|
||||
withContext(Main) {
|
||||
dismiss()
|
||||
|
||||
@@ -6,11 +6,13 @@ import io.legado.app.base.BaseViewModel
|
||||
import io.legado.app.data.appDb
|
||||
import io.legado.app.data.entities.BaseSource
|
||||
import io.legado.app.exception.NoStackTraceException
|
||||
import io.legado.app.utils.runScriptWithContext
|
||||
import io.legado.app.utils.toastOnUi
|
||||
|
||||
class SourceLoginViewModel(application: Application) : BaseViewModel(application) {
|
||||
|
||||
var source: BaseSource? = null
|
||||
var headerMap: Map<String, String> = emptyMap()
|
||||
|
||||
fun initData(intent: Intent, success: (bookSource: BaseSource) -> Unit) {
|
||||
execute {
|
||||
@@ -21,6 +23,9 @@ class SourceLoginViewModel(application: Application) : BaseViewModel(application
|
||||
"rssSource" -> source = appDb.rssSourceDao.getByKey(sourceKey)
|
||||
"httpTts" -> source = appDb.httpTTSDao.get(sourceKey.toLong())
|
||||
}
|
||||
headerMap = runScriptWithContext {
|
||||
source?.getHeaderMap(true) ?: emptyMap()
|
||||
}
|
||||
source
|
||||
}.onSuccess {
|
||||
if (it != null) {
|
||||
|
||||
@@ -16,6 +16,7 @@ import android.webkit.WebSettings
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import io.legado.app.R
|
||||
import io.legado.app.base.BaseFragment
|
||||
import io.legado.app.constant.AppConst
|
||||
@@ -29,6 +30,7 @@ import io.legado.app.utils.longSnackbar
|
||||
import io.legado.app.utils.openUrl
|
||||
import io.legado.app.utils.snackbar
|
||||
import io.legado.app.utils.viewbindingdelegate.viewBinding
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class WebViewLoginFragment : BaseFragment(R.layout.fragment_web_view_login) {
|
||||
|
||||
@@ -55,11 +57,8 @@ class WebViewLoginFragment : BaseFragment(R.layout.fragment_web_view_login) {
|
||||
if (!checking) {
|
||||
checking = true
|
||||
binding.titleBar.snackbar(R.string.check_host_cookie)
|
||||
viewModel.source?.let { source ->
|
||||
source.loginUrl?.let {
|
||||
val absoluteUrl = NetworkUtils.getAbsoluteURL(source.getKey(), it)
|
||||
binding.webView.loadUrl(absoluteUrl, source.getHeaderMap(true))
|
||||
}
|
||||
viewModel.source?.let {
|
||||
loadUrl(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,7 +76,7 @@ class WebViewLoginFragment : BaseFragment(R.layout.fragment_web_view_login) {
|
||||
builtInZoomControls = true
|
||||
javaScriptEnabled = true
|
||||
displayZoomControls = false
|
||||
source.getHeaderMap()[AppConst.UA_NAME]?.let {
|
||||
viewModel.headerMap[AppConst.UA_NAME]?.let {
|
||||
userAgentString = it
|
||||
}
|
||||
}
|
||||
@@ -143,9 +142,14 @@ class WebViewLoginFragment : BaseFragment(R.layout.fragment_web_view_login) {
|
||||
}
|
||||
|
||||
}
|
||||
source.loginUrl?.let {
|
||||
val absoluteUrl = NetworkUtils.getAbsoluteURL(source.getKey(), it)
|
||||
binding.webView.loadUrl(absoluteUrl, source.getHeaderMap(true))
|
||||
loadUrl(source)
|
||||
}
|
||||
|
||||
private fun loadUrl(source: BaseSource) {
|
||||
lifecycleScope.launch {
|
||||
val loginUrl = source.loginUrl ?: return@launch
|
||||
val absoluteUrl = NetworkUtils.getAbsoluteURL(source.getKey(), loginUrl)
|
||||
binding.webView.loadUrl(absoluteUrl, viewModel.headerMap)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.app.Application
|
||||
import io.legado.app.base.BaseViewModel
|
||||
import io.legado.app.data.appDb
|
||||
import io.legado.app.data.entities.RssSource
|
||||
import io.legado.app.utils.runScriptWithContext
|
||||
import io.legado.app.utils.toastOnUi
|
||||
|
||||
class RssViewModel(application: Application) : BaseViewModel(application) {
|
||||
@@ -58,7 +59,9 @@ class RssViewModel(application: Application) : BaseViewModel(application) {
|
||||
} else {
|
||||
sortUrl.substring(4, sortUrl.lastIndexOf("<"))
|
||||
}
|
||||
val result = rssSource.evalJS(jsStr)?.toString()
|
||||
val result = runScriptWithContext {
|
||||
rssSource.evalJS(jsStr)?.toString()
|
||||
}
|
||||
if (!result.isNullOrBlank()) {
|
||||
sortUrl = result
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ import io.legado.app.ui.login.SourceLoginActivity
|
||||
import io.legado.app.ui.rss.favorites.RssFavoritesDialog
|
||||
import io.legado.app.utils.ACache
|
||||
import io.legado.app.utils.NetworkUtils
|
||||
import io.legado.app.utils.get
|
||||
import io.legado.app.utils.gone
|
||||
import io.legado.app.utils.invisible
|
||||
import io.legado.app.utils.isTrue
|
||||
@@ -187,7 +186,7 @@ class ReadRssActivity : VMBaseActivity<ActivityRssReadBinding, ReadRssViewModel>
|
||||
}
|
||||
|
||||
override fun updateFavorite(title: String?, group: String?) {
|
||||
viewModel.rssArticle?.let{
|
||||
viewModel.rssArticle?.let {
|
||||
if (title != null) {
|
||||
it.title = title
|
||||
}
|
||||
@@ -294,8 +293,7 @@ class ReadRssActivity : VMBaseActivity<ActivityRssReadBinding, ReadRssViewModel>
|
||||
val url = NetworkUtils.getAbsoluteURL(it.origin, it.link)
|
||||
val html = viewModel.clHtml(content)
|
||||
binding.webView.settings.userAgentString =
|
||||
viewModel.rssSource?.getHeaderMap()?.get(AppConst.UA_NAME, true)
|
||||
?: AppConfig.userAgent
|
||||
viewModel.headerMap[AppConst.UA_NAME] ?: AppConfig.userAgent
|
||||
if (viewModel.rssSource?.loadWithBaseUrl == true) {
|
||||
binding.webView
|
||||
.loadDataWithBaseURL(url, html, "text/html", "utf-8", url)//不想用baseUrl进else
|
||||
|
||||
@@ -21,11 +21,13 @@ import io.legado.app.help.http.newCallResponseBody
|
||||
import io.legado.app.help.http.okHttpClient
|
||||
import io.legado.app.model.analyzeRule.AnalyzeUrl
|
||||
import io.legado.app.model.rss.Rss
|
||||
import io.legado.app.utils.runScriptWithContext
|
||||
import io.legado.app.utils.toastOnUi
|
||||
import io.legado.app.utils.writeBytes
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import splitties.init.appCtx
|
||||
import java.util.Date
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
|
||||
class ReadRssViewModel(application: Application) : BaseViewModel(application), JsExtensions {
|
||||
@@ -37,6 +39,7 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application), J
|
||||
var rssStar: RssStar? = null
|
||||
val upTtsMenuData = MutableLiveData<Boolean>()
|
||||
val upStarMenuData = MutableLiveData<Boolean>()
|
||||
var headerMap: Map<String, String> = emptyMap()
|
||||
|
||||
override fun getSource(): BaseSource? {
|
||||
return rssSource
|
||||
@@ -47,6 +50,9 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application), J
|
||||
val origin = intent.getStringExtra("origin") ?: return@execute
|
||||
val link = intent.getStringExtra("link")
|
||||
rssSource = appDb.rssSourceDao.getByKey(origin)
|
||||
headerMap = runScriptWithContext {
|
||||
rssSource?.getHeaderMap() ?: emptyMap()
|
||||
}
|
||||
if (link != null) {
|
||||
rssStar = appDb.rssStarDao.get(origin, link)
|
||||
rssArticle = rssStar?.toRssArticle() ?: appDb.rssArticleDao.get(origin, link)
|
||||
@@ -80,11 +86,13 @@ class ReadRssViewModel(application: Application) : BaseViewModel(application), J
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadUrl(url: String, baseUrl: String) {
|
||||
private suspend fun loadUrl(url: String, baseUrl: String) {
|
||||
val analyzeUrl = AnalyzeUrl(
|
||||
mUrl = url,
|
||||
baseUrl = baseUrl,
|
||||
headerMapF = rssSource?.getHeaderMap()
|
||||
source = rssSource,
|
||||
coroutineContext = coroutineContext,
|
||||
hasLoginHeader = false
|
||||
)
|
||||
urlLiveData.postValue(analyzeUrl)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package io.legado.app.utils
|
||||
|
||||
import com.script.rhino.RhinoContext
|
||||
import com.script.rhino.RhinoScriptEngine
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
@@ -7,6 +9,9 @@ import org.mozilla.javascript.Context
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.coroutines.ContinuationInterceptor
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
@Suppress("LEAKED_IN_PLACE_LAMBDA", "WRONG_INVOCATION_KIND")
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
@@ -29,3 +34,28 @@ inline fun <T> suspendContinuation(crossinline block: suspend CoroutineScope.()
|
||||
Context.exit()
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T> runScriptWithContext(context: CoroutineContext, block: () -> T): T {
|
||||
RhinoScriptEngine
|
||||
val rhinoContext = Context.enter() as RhinoContext
|
||||
val previousCoroutineContext = rhinoContext.coroutineContext
|
||||
rhinoContext.coroutineContext = context.minusKey(ContinuationInterceptor)
|
||||
try {
|
||||
return block()
|
||||
} finally {
|
||||
rhinoContext.coroutineContext = previousCoroutineContext
|
||||
Context.exit()
|
||||
}
|
||||
}
|
||||
|
||||
suspend inline fun <T> runScriptWithContext(block: () -> T): T {
|
||||
val rhinoContext = Context.enter() as RhinoContext
|
||||
val previousCoroutineContext = rhinoContext.coroutineContext
|
||||
rhinoContext.coroutineContext = coroutineContext.minusKey(ContinuationInterceptor)
|
||||
try {
|
||||
return block()
|
||||
} finally {
|
||||
rhinoContext.coroutineContext = previousCoroutineContext
|
||||
Context.exit()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,9 @@ object RhinoScriptEngine : AbstractScriptEngine(), Invocable, Compilable {
|
||||
): Any? {
|
||||
val cx = Context.enter() as RhinoContext
|
||||
val previousCoroutineContext = cx.coroutineContext
|
||||
cx.coroutineContext = coroutineContext
|
||||
if (coroutineContext != null) {
|
||||
cx.coroutineContext = coroutineContext
|
||||
}
|
||||
cx.allowScriptRun = true
|
||||
val ret: Any?
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user