Rename
This commit is contained in:
1 parent
b86d90021c
commit
6c0757c91b
972 files changed
+6885
-7321
No files matched your search
@@ -0,0 +1,21 @@
|
||||
package io.legato.kazusa.help.source
|
||||
|
||||
import io.legato.kazusa.constant.SourceType
|
||||
import io.legato.kazusa.data.entities.BaseSource
|
||||
import io.legato.kazusa.data.entities.BookSource
|
||||
import io.legato.kazusa.data.entities.RssSource
|
||||
import io.legato.kazusa.model.SharedJsScope
|
||||
import org.mozilla.javascript.Scriptable
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
fun BaseSource.getShareScope(coroutineContext: CoroutineContext? = null): Scriptable? {
|
||||
return SharedJsScope.getScope(jsLib, coroutineContext)
|
||||
}
|
||||
|
||||
fun BaseSource.getSourceType(): Int {
|
||||
return when (this) {
|
||||
is BookSource -> SourceType.book
|
||||
is RssSource -> SourceType.rss
|
||||
else -> error("unknown source type: ${this::class.simpleName}.")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package io.legato.kazusa.help.source
|
||||
|
||||
import com.script.rhino.runScriptWithContext
|
||||
import io.legato.kazusa.constant.BookSourceType
|
||||
import io.legato.kazusa.constant.BookType
|
||||
import io.legato.kazusa.data.entities.BookSource
|
||||
import io.legato.kazusa.data.entities.BookSourcePart
|
||||
import io.legato.kazusa.data.entities.rule.ExploreKind
|
||||
import io.legato.kazusa.utils.ACache
|
||||
import io.legato.kazusa.utils.GSON
|
||||
import io.legato.kazusa.utils.MD5Utils
|
||||
import io.legato.kazusa.utils.fromJsonArray
|
||||
import io.legato.kazusa.utils.isJsonArray
|
||||
import io.legato.kazusa.utils.printOnDebug
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
* 采用md5作为key可以在分类修改后自动重新计算,不需要手动刷新
|
||||
*/
|
||||
|
||||
private val mutexMap by lazy { hashMapOf<String, Mutex>() }
|
||||
private val exploreKindsMap by lazy { ConcurrentHashMap<String, List<ExploreKind>>() }
|
||||
private val aCache by lazy { ACache.get("explore") }
|
||||
|
||||
private fun BookSource.getExploreKindsKey(): String {
|
||||
return MD5Utils.md5Encode(bookSourceUrl + exploreUrl)
|
||||
}
|
||||
|
||||
private fun BookSourcePart.getExploreKindsKey(): String {
|
||||
return getBookSource()!!.getExploreKindsKey()
|
||||
}
|
||||
|
||||
suspend fun BookSourcePart.exploreKinds(): List<ExploreKind> {
|
||||
return getBookSource()!!.exploreKinds()
|
||||
}
|
||||
|
||||
suspend fun BookSource.exploreKinds(): List<ExploreKind> {
|
||||
val exploreKindsKey = getExploreKindsKey()
|
||||
exploreKindsMap[exploreKindsKey]?.let { return it }
|
||||
val exploreUrl = exploreUrl
|
||||
if (exploreUrl.isNullOrBlank()) {
|
||||
return emptyList()
|
||||
}
|
||||
val mutex = mutexMap[bookSourceUrl] ?: Mutex().apply { mutexMap[bookSourceUrl] = this }
|
||||
mutex.withLock {
|
||||
exploreKindsMap[exploreKindsKey]?.let { return it }
|
||||
val kinds = arrayListOf<ExploreKind>()
|
||||
withContext(Dispatchers.IO) {
|
||||
kotlin.runCatching {
|
||||
var ruleStr = exploreUrl
|
||||
if (exploreUrl.startsWith("<js>", true)
|
||||
|| exploreUrl.startsWith("@js:", true)
|
||||
) {
|
||||
ruleStr = aCache.getAsString(exploreKindsKey)
|
||||
if (ruleStr.isNullOrBlank()) {
|
||||
val jsStr = if (exploreUrl.startsWith("@")) {
|
||||
exploreUrl.substring(4)
|
||||
} else {
|
||||
exploreUrl.substring(4, exploreUrl.lastIndexOf("<"))
|
||||
}
|
||||
ruleStr = runScriptWithContext {
|
||||
evalJS(jsStr).toString().trim()
|
||||
}
|
||||
aCache.put(exploreKindsKey, ruleStr)
|
||||
}
|
||||
}
|
||||
if (ruleStr.isJsonArray()) {
|
||||
GSON.fromJsonArray<ExploreKind>(ruleStr).getOrThrow().let {
|
||||
kinds.addAll(it)
|
||||
}
|
||||
} else {
|
||||
ruleStr.split("(&&|\n)+".toRegex()).forEach { kindStr ->
|
||||
val kindCfg = kindStr.split("::")
|
||||
kinds.add(ExploreKind(kindCfg.first(), kindCfg.getOrNull(1)))
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
kinds.add(ExploreKind("ERROR:${it.localizedMessage}", it.stackTraceToString()))
|
||||
it.printOnDebug()
|
||||
}
|
||||
}
|
||||
exploreKindsMap[exploreKindsKey] = kinds
|
||||
return kinds
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun BookSourcePart.clearExploreKindsCache() {
|
||||
withContext(Dispatchers.IO) {
|
||||
val exploreKindsKey = getExploreKindsKey()
|
||||
aCache.remove(exploreKindsKey)
|
||||
exploreKindsMap.remove(exploreKindsKey)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun BookSource.clearExploreKindsCache() {
|
||||
withContext(Dispatchers.IO) {
|
||||
val exploreKindsKey = getExploreKindsKey()
|
||||
aCache.remove(exploreKindsKey)
|
||||
exploreKindsMap.remove(exploreKindsKey)
|
||||
}
|
||||
}
|
||||
|
||||
fun BookSource.exploreKindsJson(): String {
|
||||
val exploreKindsKey = getExploreKindsKey()
|
||||
return aCache.getAsString(exploreKindsKey)?.takeIf { it.isJsonArray() }
|
||||
?: exploreUrl.takeIf { it.isJsonArray() }
|
||||
?: ""
|
||||
}
|
||||
|
||||
fun BookSource.getBookType(): Int {
|
||||
return when (bookSourceType) {
|
||||
BookSourceType.file -> BookType.text or BookType.webFile
|
||||
BookSourceType.image -> BookType.image
|
||||
BookSourceType.audio -> BookType.audio
|
||||
else -> BookType.text
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package io.legato.kazusa.help.source
|
||||
|
||||
import io.legato.kazusa.data.entities.RssSource
|
||||
import io.legato.kazusa.utils.ACache
|
||||
import io.legato.kazusa.utils.MD5Utils
|
||||
import io.legato.kazusa.utils.NetworkUtils
|
||||
import com.script.rhino.runScriptWithContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
private val aCache by lazy { ACache.get("rssSortUrl") }
|
||||
|
||||
private fun RssSource.getSortUrlsKey(): String {
|
||||
return MD5Utils.md5Encode(sourceUrl + sortUrl)
|
||||
}
|
||||
|
||||
suspend fun RssSource.sortUrls(): List<Pair<String, String>> {
|
||||
return arrayListOf<Pair<String, String>>().apply {
|
||||
val sortUrlsKey = getSortUrlsKey()
|
||||
withContext(Dispatchers.IO) {
|
||||
kotlin.runCatching {
|
||||
var str = sortUrl
|
||||
if (sortUrl?.startsWith("<js>", false) == true
|
||||
|| sortUrl?.startsWith("@js:", false) == true
|
||||
) {
|
||||
str = aCache.getAsString(sortUrlsKey)
|
||||
if (str.isNullOrBlank()) {
|
||||
val jsStr = if (sortUrl!!.startsWith("@")) {
|
||||
sortUrl!!.substring(4)
|
||||
} else {
|
||||
sortUrl!!.substring(4, sortUrl!!.lastIndexOf("<"))
|
||||
}
|
||||
str = runScriptWithContext {
|
||||
evalJS(jsStr).toString()
|
||||
}
|
||||
aCache.put(sortUrlsKey, str)
|
||||
}
|
||||
}
|
||||
str?.split("(&&|\n)+".toRegex())?.forEach { sort ->
|
||||
val name = sort.substringBefore("::")
|
||||
val url = sort.substringAfter("::", "")
|
||||
if (url.isNotEmpty()) {
|
||||
if (url.startsWith("{{")) {
|
||||
add(Pair(name, url))
|
||||
} else {
|
||||
add(Pair(name, NetworkUtils.getAbsoluteURL(sourceUrl, url)))
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isEmpty()) {
|
||||
add(Pair("", sourceUrl))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun RssSource.removeSortCache() {
|
||||
withContext(Dispatchers.IO) {
|
||||
aCache.remove(getSortUrlsKey())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package io.legato.kazusa.help.source
|
||||
|
||||
import io.legato.kazusa.constant.SourceType
|
||||
import io.legato.kazusa.data.appDb
|
||||
import io.legato.kazusa.data.entities.BaseSource
|
||||
import io.legato.kazusa.data.entities.BookSource
|
||||
import io.legato.kazusa.data.entities.BookSourcePart
|
||||
import io.legato.kazusa.data.entities.RssSource
|
||||
import io.legato.kazusa.help.AppCacheManager
|
||||
import io.legato.kazusa.help.config.SourceConfig
|
||||
import io.legato.kazusa.help.coroutine.Coroutine
|
||||
import io.legato.kazusa.model.AudioPlay
|
||||
import io.legato.kazusa.model.ReadBook
|
||||
import io.legato.kazusa.model.ReadManga
|
||||
import io.legato.kazusa.utils.EncoderUtils
|
||||
import io.legato.kazusa.utils.NetworkUtils
|
||||
import io.legato.kazusa.utils.splitNotBlank
|
||||
import io.legato.kazusa.utils.toastOnUi
|
||||
import splitties.init.appCtx
|
||||
|
||||
object SourceHelp {
|
||||
|
||||
private val list18Plus by lazy {
|
||||
try {
|
||||
return@lazy String(appCtx.assets.open("18PlusList.txt").readBytes())
|
||||
.splitNotBlank("\n").map {
|
||||
EncoderUtils.base64Decode(it)
|
||||
}.toHashSet()
|
||||
} catch (_: Exception) {
|
||||
return@lazy emptySet()
|
||||
}
|
||||
}
|
||||
|
||||
fun getSource(key: String?): BaseSource? {
|
||||
key ?: return null
|
||||
if (ReadBook.bookSource?.bookSourceUrl == key) {
|
||||
return ReadBook.bookSource
|
||||
} else if (AudioPlay.bookSource?.bookSourceUrl == key) {
|
||||
return AudioPlay.bookSource
|
||||
} else if (ReadManga.bookSource?.bookSourceUrl == key) {
|
||||
return ReadManga.bookSource
|
||||
}
|
||||
return appDb.bookSourceDao.getBookSource(key)
|
||||
?: appDb.rssSourceDao.getByKey(key)
|
||||
}
|
||||
|
||||
fun getSource(key: String?, @SourceType.Type type: Int): BaseSource? {
|
||||
key ?: return null
|
||||
return when (type) {
|
||||
SourceType.book -> appDb.bookSourceDao.getBookSource(key)
|
||||
SourceType.rss -> appDb.rssSourceDao.getByKey(key)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteSource(key: String, @SourceType.Type type: Int) {
|
||||
when (type) {
|
||||
SourceType.book -> deleteBookSource(key)
|
||||
SourceType.rss -> deleteRssSource(key)
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteBookSourceParts(sources: List<BookSourcePart>) {
|
||||
appDb.runInTransaction {
|
||||
sources.forEach {
|
||||
deleteBookSourceInternal(it.bookSourceUrl)
|
||||
}
|
||||
}
|
||||
AppCacheManager.clearSourceVariables()
|
||||
}
|
||||
|
||||
fun deleteBookSources(sources: List<BookSource>) {
|
||||
appDb.runInTransaction {
|
||||
sources.forEach {
|
||||
deleteBookSourceInternal(it.bookSourceUrl)
|
||||
}
|
||||
}
|
||||
AppCacheManager.clearSourceVariables()
|
||||
}
|
||||
|
||||
private fun deleteBookSourceInternal(key: String) {
|
||||
appDb.bookSourceDao.delete(key)
|
||||
appDb.cacheDao.deleteSourceVariables(key)
|
||||
SourceConfig.removeSource(key)
|
||||
}
|
||||
|
||||
fun deleteBookSource(key: String) {
|
||||
deleteBookSourceInternal(key)
|
||||
AppCacheManager.clearSourceVariables()
|
||||
}
|
||||
|
||||
fun deleteRssSources(sources: List<RssSource>) {
|
||||
appDb.runInTransaction {
|
||||
sources.forEach {
|
||||
deleteRssSourceInternal(it.sourceUrl)
|
||||
}
|
||||
}
|
||||
AppCacheManager.clearSourceVariables()
|
||||
}
|
||||
|
||||
private fun deleteRssSourceInternal(key: String) {
|
||||
appDb.rssSourceDao.delete(key)
|
||||
appDb.rssArticleDao.delete(key)
|
||||
appDb.cacheDao.deleteSourceVariables(key)
|
||||
}
|
||||
|
||||
fun deleteRssSource(key: String) {
|
||||
deleteRssSourceInternal(key)
|
||||
AppCacheManager.clearSourceVariables()
|
||||
}
|
||||
|
||||
fun enableSource(key: String, @SourceType.Type type: Int, enable: Boolean) {
|
||||
when (type) {
|
||||
SourceType.book -> appDb.bookSourceDao.enable(key, enable)
|
||||
SourceType.rss -> appDb.rssSourceDao.enable(key, enable)
|
||||
}
|
||||
}
|
||||
|
||||
fun insertRssSource(vararg rssSources: RssSource) {
|
||||
val rssSourcesGroup = rssSources.groupBy {
|
||||
is18Plus(it.sourceUrl)
|
||||
}
|
||||
rssSourcesGroup[true]?.forEach {
|
||||
appCtx.toastOnUi("${it.sourceName}是18+网址,禁止导入.")
|
||||
}
|
||||
rssSourcesGroup[false]?.let {
|
||||
appDb.rssSourceDao.insert(*it.toTypedArray())
|
||||
}
|
||||
}
|
||||
|
||||
fun insertBookSource(vararg bookSources: BookSource) {
|
||||
val bookSourcesGroup = bookSources.groupBy {
|
||||
is18Plus(it.bookSourceUrl)
|
||||
}
|
||||
bookSourcesGroup[true]?.forEach {
|
||||
appCtx.toastOnUi("${it.bookSourceName}是18+网址,禁止导入.")
|
||||
}
|
||||
bookSourcesGroup[false]?.let {
|
||||
appDb.bookSourceDao.insert(*it.toTypedArray())
|
||||
}
|
||||
Coroutine.async {
|
||||
adjustSortNumber()
|
||||
}
|
||||
}
|
||||
|
||||
private fun is18Plus(url: String?): Boolean {
|
||||
if (list18Plus.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
url ?: return false
|
||||
val baseUrl = NetworkUtils.getBaseUrl(url) ?: return false
|
||||
kotlin.runCatching {
|
||||
val host = baseUrl.split("//", ".").let {
|
||||
if (it.size > 2) "${it[it.lastIndex - 1]}.${it.last()}" else return false
|
||||
}
|
||||
return list18Plus.contains(host)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 调整排序序号
|
||||
*/
|
||||
fun adjustSortNumber() {
|
||||
if (
|
||||
appDb.bookSourceDao.maxOrder > 99999
|
||||
|| appDb.bookSourceDao.minOrder < -99999
|
||||
|| appDb.bookSourceDao.hasDuplicateOrder
|
||||
) {
|
||||
val sources = appDb.bookSourceDao.allPart
|
||||
sources.forEachIndexed { index, bookSource ->
|
||||
bookSource.customOrder = index
|
||||
}
|
||||
appDb.bookSourceDao.upOrder(sources)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package io.legato.kazusa.help.source
|
||||
|
||||
import io.legato.kazusa.constant.AppLog
|
||||
import io.legato.kazusa.data.entities.BaseSource
|
||||
import io.legato.kazusa.exception.NoStackTraceException
|
||||
import io.legato.kazusa.help.CacheManager
|
||||
import io.legato.kazusa.help.IntentData
|
||||
import io.legato.kazusa.ui.association.VerificationCodeActivity
|
||||
import io.legato.kazusa.ui.browser.WebViewActivity
|
||||
import io.legato.kazusa.utils.isMainThread
|
||||
import io.legato.kazusa.utils.startActivity
|
||||
import splitties.init.appCtx
|
||||
import java.util.concurrent.locks.LockSupport
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
/**
|
||||
* 源验证
|
||||
*/
|
||||
object SourceVerificationHelp {
|
||||
|
||||
private val waitTime = 1.minutes.inWholeNanoseconds
|
||||
|
||||
private fun getVerificationResultKey(source: BaseSource) =
|
||||
getVerificationResultKey(source.getKey())
|
||||
|
||||
private fun getVerificationResultKey(sourceKey: String) = "${sourceKey}_verificationResult"
|
||||
|
||||
/**
|
||||
* 获取书源验证结果
|
||||
* 图片验证码 防爬 滑动验证码 点击字符 等等
|
||||
*/
|
||||
fun getVerificationResult(
|
||||
source: BaseSource?,
|
||||
url: String,
|
||||
title: String,
|
||||
useBrowser: Boolean,
|
||||
refetchAfterSuccess: Boolean = true
|
||||
): String {
|
||||
source
|
||||
?: throw NoStackTraceException("getVerificationResult parameter source cannot be null")
|
||||
if (isMainThread) {
|
||||
error("getVerificationResult must be called on a background thread")
|
||||
}
|
||||
|
||||
clearResult(source.getKey())
|
||||
|
||||
if (!useBrowser) {
|
||||
appCtx.startActivity<VerificationCodeActivity> {
|
||||
putExtra("imageUrl", url)
|
||||
putExtra("sourceOrigin", source.getKey())
|
||||
putExtra("sourceName", source.getTag())
|
||||
putExtra("sourceType", source.getSourceType())
|
||||
IntentData.put(getVerificationResultKey(source), Thread.currentThread())
|
||||
}
|
||||
} else {
|
||||
startBrowser(source, url, title, true, refetchAfterSuccess)
|
||||
}
|
||||
|
||||
var waitUserInput = false
|
||||
while (getResult(source.getKey()) == null) {
|
||||
if (!waitUserInput) {
|
||||
AppLog.putDebug("等待返回验证结果...")
|
||||
waitUserInput = true
|
||||
}
|
||||
LockSupport.parkNanos(this, waitTime)
|
||||
}
|
||||
|
||||
return getResult(source.getKey())!!.let {
|
||||
it.ifBlank {
|
||||
throw NoStackTraceException("验证结果为空")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动内置浏览器
|
||||
* @param saveResult 保存网页源代码到数据库
|
||||
*/
|
||||
fun startBrowser(
|
||||
source: BaseSource?,
|
||||
url: String,
|
||||
title: String,
|
||||
saveResult: Boolean? = false,
|
||||
refetchAfterSuccess: Boolean? = true
|
||||
) {
|
||||
source ?: throw NoStackTraceException("startBrowser parameter source cannot be null")
|
||||
appCtx.startActivity<WebViewActivity> {
|
||||
putExtra("title", title)
|
||||
putExtra("url", url)
|
||||
putExtra("sourceOrigin", source.getKey())
|
||||
putExtra("sourceName", source.getTag())
|
||||
putExtra("sourceType", source.getSourceType())
|
||||
putExtra("sourceVerificationEnable", saveResult)
|
||||
putExtra("refetchAfterSuccess", refetchAfterSuccess)
|
||||
IntentData.put(getVerificationResultKey(source), Thread.currentThread())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun checkResult(sourceKey: String) {
|
||||
getResult(sourceKey) ?: setResult(sourceKey, "")
|
||||
val thread = IntentData.get<Thread>(getVerificationResultKey(sourceKey))
|
||||
LockSupport.unpark(thread)
|
||||
}
|
||||
|
||||
fun setResult(sourceKey: String, result: String?) {
|
||||
CacheManager.putMemory(getVerificationResultKey(sourceKey), result ?: "")
|
||||
}
|
||||
|
||||
fun getResult(sourceKey: String): String? {
|
||||
return CacheManager.get(getVerificationResultKey(sourceKey))
|
||||
}
|
||||
|
||||
fun clearResult(sourceKey: String) {
|
||||
CacheManager.delete(getVerificationResultKey(sourceKey))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user