This commit is contained in:
Horis
2025-03-25 22:53:42 +08:00
parent 8c2aabaaac
commit 450065aec9
31 changed files with 178 additions and 78 deletions

View File

@@ -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()
//清除过期数据

View File

@@ -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)
}

View File

@@ -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}")

View File

@@ -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 {

View File

@@ -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> {

View File

@@ -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("封面二次解密失败"))

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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())
}
}

View File

@@ -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)

View File

@@ -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"]

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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() //控制并发访问

View File

@@ -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() //控制并发访问

View File

@@ -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(

View File

@@ -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

View File

@@ -328,7 +328,6 @@ class HttpReadAloudService : BaseReadAloudService(),
speakText = speakText,
speakSpeed = speechRate,
source = httpTts,
headerMapF = httpTts.getHeaderMap(true),
readTimeout = 300 * 1000L,
coroutineContext = coroutineContext
)

View File

@@ -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)

View File

@@ -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)
}
}
}

View File

@@ -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()) {
//购买成功后刷新目录

View File

@@ -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)
}
}
}

View File

@@ -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 {

View File

@@ -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()

View File

@@ -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) {

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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()
}
}

View File

@@ -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 {