diff --git a/app/src/main/java/io/legado/app/data/entities/BookSource.kt b/app/src/main/java/io/legado/app/data/entities/BookSource.kt index 317579cee..a5f867d20 100644 --- a/app/src/main/java/io/legado/app/data/entities/BookSource.kt +++ b/app/src/main/java/io/legado/app/data/entities/BookSource.kt @@ -3,15 +3,19 @@ package io.legado.app.data.entities import android.os.Parcelable import android.text.TextUtils import androidx.room.* +import com.google.gson.JsonDeserializationContext +import com.google.gson.JsonDeserializer +import com.google.gson.JsonElement import io.legado.app.constant.AppPattern import io.legado.app.constant.BookSourceType import io.legado.app.data.entities.rule.* -import io.legado.app.help.source.SourceAnalyzer import io.legado.app.utils.GSON +import io.legado.app.utils.fromJsonArray import io.legado.app.utils.fromJsonObject import io.legado.app.utils.splitNotBlank import kotlinx.parcelize.Parcelize import java.io.InputStream +import java.lang.reflect.Type @Suppress("unused") @Parcelize @@ -222,17 +226,46 @@ data class BookSource( companion object { + private val gson by lazy { + GSON.newBuilder() + .registerTypeAdapter(String::class.java, StringJsonDeserializer()) + .registerTypeAdapter(ExploreRule::class.java, ExploreRule.jsonDeserializer) + .registerTypeAdapter(SearchRule::class.java, SearchRule.jsonDeserializer) + .registerTypeAdapter(BookInfoRule::class.java, BookInfoRule.jsonDeserializer) + .registerTypeAdapter(TocRule::class.java, TocRule.jsonDeserializer) + .registerTypeAdapter(ContentRule::class.java, ContentRule.jsonDeserializer) + .registerTypeAdapter(ReviewRule::class.java, ReviewRule.jsonDeserializer) + .create() + } + fun fromJson(json: String): Result { - return SourceAnalyzer.jsonToBookSource(json) + return gson.fromJsonObject(json) } - fun fromJsonArray(json: String): Result> { - return SourceAnalyzer.jsonToBookSources(json) + fun fromJsonArray(json: String): Result> { + return gson.fromJsonArray(json) } - fun fromJsonArray(inputStream: InputStream): Result> { - return SourceAnalyzer.jsonToBookSources(inputStream) + fun fromJsonArray(inputStream: InputStream): Result> { + return gson.fromJsonArray(inputStream) } + + class StringJsonDeserializer : JsonDeserializer { + + override fun deserialize( + json: JsonElement, + typeOfT: Type, + context: JsonDeserializationContext? + ): String? { + return when { + json.isJsonPrimitive -> json.asString + json.isJsonNull -> null + else -> json.toString() + } + } + + } + } class Converters { diff --git a/app/src/main/java/io/legado/app/data/entities/RssSource.kt b/app/src/main/java/io/legado/app/data/entities/RssSource.kt index 6ea441d1f..20a5c6647 100644 --- a/app/src/main/java/io/legado/app/data/entities/RssSource.kt +++ b/app/src/main/java/io/legado/app/data/entities/RssSource.kt @@ -176,7 +176,7 @@ data class RssSource( companion object { private val gson by lazy { GSON.newBuilder() - .registerTypeAdapter(String::class.java, RssJsonDeserializer()) + .registerTypeAdapter(String::class.java, StringJsonDeserializer()) .create() } @@ -187,20 +187,21 @@ data class RssSource( fun fromJsonArray(jsonArray: String): Result> { return gson.fromJsonArray(jsonArray) } - } - class RssJsonDeserializer : JsonDeserializer { + class StringJsonDeserializer : JsonDeserializer { - override fun deserialize( - json: JsonElement, - typeOfT: Type?, - context: JsonDeserializationContext? - ): String? { - return when { - json.isJsonPrimitive -> json.asString - json.isJsonNull -> null - else -> json.toString() + override fun deserialize( + json: JsonElement, + typeOfT: Type?, + context: JsonDeserializationContext? + ): String? { + return when { + json.isJsonPrimitive -> json.asString + json.isJsonNull -> null + else -> json.toString() + } } + } } diff --git a/app/src/main/java/io/legado/app/data/entities/rule/BookInfoRule.kt b/app/src/main/java/io/legado/app/data/entities/rule/BookInfoRule.kt index 06fc5a7f4..513696703 100644 --- a/app/src/main/java/io/legado/app/data/entities/rule/BookInfoRule.kt +++ b/app/src/main/java/io/legado/app/data/entities/rule/BookInfoRule.kt @@ -1,6 +1,8 @@ package io.legado.app.data.entities.rule import android.os.Parcelable +import com.google.gson.JsonDeserializer +import io.legado.app.utils.GSON import kotlinx.parcelize.Parcelize /** @@ -20,4 +22,18 @@ data class BookInfoRule( var wordCount: String? = null, var canReName: String? = null, var downloadUrls: String? = null -) : Parcelable \ No newline at end of file +) : Parcelable { + + companion object { + + val jsonDeserializer = JsonDeserializer { json, _, _ -> + when { + json.isJsonObject -> GSON.fromJson(json, BookInfoRule::class.java) + json.isJsonPrimitive -> GSON.fromJson(json.asString, BookInfoRule::class.java) + else -> null + } + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/rule/ContentRule.kt b/app/src/main/java/io/legado/app/data/entities/rule/ContentRule.kt index ab0ae21c0..ca8db8ece 100644 --- a/app/src/main/java/io/legado/app/data/entities/rule/ContentRule.kt +++ b/app/src/main/java/io/legado/app/data/entities/rule/ContentRule.kt @@ -1,6 +1,8 @@ package io.legado.app.data.entities.rule import android.os.Parcelable +import com.google.gson.JsonDeserializer +import io.legado.app.utils.GSON import kotlinx.parcelize.Parcelize /** @@ -16,4 +18,20 @@ data class ContentRule( var imageStyle: String? = null, //默认大小居中,FULL最大宽度 var imageDecode: String? = null, //图片bytes二次解密js, 返回解密后的bytes var payAction: String? = null, //购买操作,js或者包含{{js}}的url -) : Parcelable \ No newline at end of file +) : Parcelable { + + + companion object { + + val jsonDeserializer = JsonDeserializer { json, _, _ -> + when { + json.isJsonObject -> GSON.fromJson(json, ContentRule::class.java) + json.isJsonPrimitive -> GSON.fromJson(json.asString, ContentRule::class.java) + else -> null + } + } + + } + + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/rule/ExploreRule.kt b/app/src/main/java/io/legado/app/data/entities/rule/ExploreRule.kt index 1c492b5f9..6b6bfd584 100644 --- a/app/src/main/java/io/legado/app/data/entities/rule/ExploreRule.kt +++ b/app/src/main/java/io/legado/app/data/entities/rule/ExploreRule.kt @@ -1,6 +1,8 @@ package io.legado.app.data.entities.rule import android.os.Parcelable +import com.google.gson.JsonDeserializer +import io.legado.app.utils.GSON import kotlinx.parcelize.Parcelize /** @@ -18,4 +20,18 @@ data class ExploreRule( override var bookUrl: String? = null, override var coverUrl: String? = null, override var wordCount: String? = null -) : BookListRule, Parcelable \ No newline at end of file +) : BookListRule, Parcelable { + + companion object { + + val jsonDeserializer = JsonDeserializer { json, _, _ -> + when { + json.isJsonObject -> GSON.fromJson(json, ExploreRule::class.java) + json.isJsonPrimitive -> GSON.fromJson(json.asString, ExploreRule::class.java) + else -> null + } + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/rule/ReviewRule.kt b/app/src/main/java/io/legado/app/data/entities/rule/ReviewRule.kt index 07ff9fb32..c2a26203f 100644 --- a/app/src/main/java/io/legado/app/data/entities/rule/ReviewRule.kt +++ b/app/src/main/java/io/legado/app/data/entities/rule/ReviewRule.kt @@ -1,6 +1,8 @@ package io.legado.app.data.entities.rule import android.os.Parcelable +import com.google.gson.JsonDeserializer +import io.legado.app.utils.GSON import kotlinx.parcelize.Parcelize @Parcelize @@ -17,4 +19,18 @@ data class ReviewRule( var postReviewUrl: String? = null, // 发送回复URL var postQuoteUrl: String? = null, // 发送回复段评URL var deleteUrl: String? = null, // 删除段评URL -): Parcelable +) : Parcelable { + + companion object { + + val jsonDeserializer = JsonDeserializer { json, _, _ -> + when { + json.isJsonObject -> GSON.fromJson(json, ReviewRule::class.java) + json.isJsonPrimitive -> GSON.fromJson(json.asString, ReviewRule::class.java) + else -> null + } + } + + } + +} diff --git a/app/src/main/java/io/legado/app/data/entities/rule/SearchRule.kt b/app/src/main/java/io/legado/app/data/entities/rule/SearchRule.kt index 20758dee8..34b9c0cd4 100644 --- a/app/src/main/java/io/legado/app/data/entities/rule/SearchRule.kt +++ b/app/src/main/java/io/legado/app/data/entities/rule/SearchRule.kt @@ -1,6 +1,8 @@ package io.legado.app.data.entities.rule import android.os.Parcelable +import com.google.gson.JsonDeserializer +import io.legado.app.utils.GSON import kotlinx.parcelize.Parcelize /** @@ -20,4 +22,18 @@ data class SearchRule( override var bookUrl: String? = null, override var coverUrl: String? = null, override var wordCount: String? = null -) : BookListRule, Parcelable \ No newline at end of file +) : BookListRule, Parcelable { + + companion object { + + val jsonDeserializer = JsonDeserializer { json, _, _ -> + when { + json.isJsonObject -> GSON.fromJson(json, SearchRule::class.java) + json.isJsonPrimitive -> GSON.fromJson(json.asString, SearchRule::class.java) + else -> null + } + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/data/entities/rule/TocRule.kt b/app/src/main/java/io/legado/app/data/entities/rule/TocRule.kt index 1dfd97e83..a8061a72f 100644 --- a/app/src/main/java/io/legado/app/data/entities/rule/TocRule.kt +++ b/app/src/main/java/io/legado/app/data/entities/rule/TocRule.kt @@ -1,6 +1,8 @@ package io.legado.app.data.entities.rule import android.os.Parcelable +import com.google.gson.JsonDeserializer +import io.legado.app.utils.GSON import kotlinx.parcelize.Parcelize @Parcelize @@ -14,4 +16,18 @@ data class TocRule( var isPay: String? = null, var updateTime: String? = null, var nextTocUrl: String? = null -) : Parcelable \ No newline at end of file +) : Parcelable { + + companion object { + + val jsonDeserializer = JsonDeserializer { json, _, _ -> + when { + json.isJsonObject -> GSON.fromJson(json, TocRule::class.java) + json.isJsonPrimitive -> GSON.fromJson(json.asString, TocRule::class.java) + else -> null + } + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/source/SourceAnalyzer.kt b/app/src/main/java/io/legado/app/help/source/SourceAnalyzer.kt deleted file mode 100644 index 3f614acd0..000000000 --- a/app/src/main/java/io/legado/app/help/source/SourceAnalyzer.kt +++ /dev/null @@ -1,389 +0,0 @@ -package io.legado.app.help.source - -import androidx.annotation.Keep -import com.jayway.jsonpath.JsonPath -import io.legado.app.R -import io.legado.app.constant.AppConst -import io.legado.app.constant.AppLog -import io.legado.app.constant.BookSourceType -import io.legado.app.data.entities.BookSource -import io.legado.app.data.entities.rule.* -import io.legado.app.exception.NoStackTraceException -import io.legado.app.utils.* -import java.io.InputStream -import java.util.regex.Pattern -import splitties.init.appCtx - - -@Suppress("RegExpRedundantEscape") -object SourceAnalyzer { - private val headerPattern = Pattern.compile("@Header:\\{.+?\\}", Pattern.CASE_INSENSITIVE) - private val jsPattern = Pattern.compile("\\{\\{.+?\\}\\}", Pattern.CASE_INSENSITIVE) - - fun jsonToBookSources(json: String): Result> { - return kotlin.runCatching { - val bookSources = mutableListOf() - when { - json.isJsonArray() -> { - val items: List> = jsonPath.parse(json).read("$") - for (item in items) { - val jsonItem = jsonPath.parse(item) - jsonToBookSource(jsonItem.jsonString()).getOrThrow().let { - bookSources.add(it) - } - } - } - json.isJsonObject() -> { - jsonToBookSource(json).getOrThrow().let { - bookSources.add(it) - } - } - else -> { - throw NoStackTraceException(appCtx.getString(R.string.wrong_format)) - } - } - bookSources - } - } - - fun jsonToBookSources(inputStream: InputStream): Result> { - return kotlin.runCatching { - val bookSources = mutableListOf() - val documentContext = jsonPath.parse(inputStream) - try { - val items: List> = documentContext.read("$") - items.forEach { - val jsonItem = jsonPath.parse(it) - jsonToBookSource(jsonItem.jsonString()).getOrThrow().let { - bookSources.add(it) - } - } - } catch (_: Exception) { - val item: Map = documentContext.read("$") - val jsonItem = jsonPath.parse(item) - jsonToBookSource(jsonItem.jsonString()).getOrThrow().let { - bookSources.add(it) - } - } - bookSources - }.onFailure { - throw NoStackTraceException(appCtx.getString(R.string.wrong_format)) - } - } - - fun jsonToBookSource(json: String): Result { - val source = BookSource() - val sourceAny = GSON.fromJsonObject(json.trim()) - .onFailure { - AppLog.put(appCtx.getString(R.string.wrong_format), it) - }.getOrNull() - return kotlin.runCatching { - if (sourceAny?.ruleToc == null) { - source.apply { - val jsonItem = jsonPath.parse(json.trim()) - bookSourceUrl = jsonItem.readString("bookSourceUrl") - ?: throw NoStackTraceException(appCtx.getString(R.string.wrong_format)) - bookSourceName = jsonItem.readString("bookSourceName") ?: "" - bookSourceGroup = jsonItem.readString("bookSourceGroup") - loginUrl = jsonItem.readString("loginUrl") - loginUi = jsonItem.readString("loginUi") - loginCheckJs = jsonItem.readString("loginCheckJs") - coverDecodeJs = jsonItem.readString("coverDecodeJs") - bookSourceComment = jsonItem.readString("bookSourceComment") ?: "" - bookUrlPattern = jsonItem.readString("ruleBookUrlPattern") - customOrder = jsonItem.readInt("serialNumber") ?: 0 - header = uaToHeader(jsonItem.readString("httpUserAgent")) - searchUrl = toNewUrl(jsonItem.readString("ruleSearchUrl")) - exploreUrl = toNewUrls(jsonItem.readString("ruleFindUrl")) - bookSourceType = - if (jsonItem.readString("bookSourceType") == "AUDIO") BookSourceType.audio else BookSourceType.default - enabled = jsonItem.readBool("enable") ?: true - if (exploreUrl.isNullOrBlank()) { - enabledExplore = false - } - ruleSearch = SearchRule( - bookList = toNewRule(jsonItem.readString("ruleSearchList")), - name = toNewRule(jsonItem.readString("ruleSearchName")), - author = toNewRule(jsonItem.readString("ruleSearchAuthor")), - intro = toNewRule(jsonItem.readString("ruleSearchIntroduce")), - kind = toNewRule(jsonItem.readString("ruleSearchKind")), - bookUrl = toNewRule(jsonItem.readString("ruleSearchNoteUrl")), - coverUrl = toNewRule(jsonItem.readString("ruleSearchCoverUrl")), - lastChapter = toNewRule(jsonItem.readString("ruleSearchLastChapter")) - ) - ruleExplore = ExploreRule( - bookList = toNewRule(jsonItem.readString("ruleFindList")), - name = toNewRule(jsonItem.readString("ruleFindName")), - author = toNewRule(jsonItem.readString("ruleFindAuthor")), - intro = toNewRule(jsonItem.readString("ruleFindIntroduce")), - kind = toNewRule(jsonItem.readString("ruleFindKind")), - bookUrl = toNewRule(jsonItem.readString("ruleFindNoteUrl")), - coverUrl = toNewRule(jsonItem.readString("ruleFindCoverUrl")), - lastChapter = toNewRule(jsonItem.readString("ruleFindLastChapter")) - ) - ruleBookInfo = BookInfoRule( - init = toNewRule(jsonItem.readString("ruleBookInfoInit")), - name = toNewRule(jsonItem.readString("ruleBookName")), - author = toNewRule(jsonItem.readString("ruleBookAuthor")), - intro = toNewRule(jsonItem.readString("ruleIntroduce")), - kind = toNewRule(jsonItem.readString("ruleBookKind")), - coverUrl = toNewRule(jsonItem.readString("ruleCoverUrl")), - lastChapter = toNewRule(jsonItem.readString("ruleBookLastChapter")), - tocUrl = toNewRule(jsonItem.readString("ruleChapterUrl")) - ) - ruleToc = TocRule( - chapterList = toNewRule(jsonItem.readString("ruleChapterList")), - chapterName = toNewRule(jsonItem.readString("ruleChapterName")), - chapterUrl = toNewRule(jsonItem.readString("ruleContentUrl")), - nextTocUrl = toNewRule(jsonItem.readString("ruleChapterUrlNext")) - ) - var content = toNewRule(jsonItem.readString("ruleBookContent")) ?: "" - if (content.startsWith("$") && !content.startsWith("$.")) { - content = content.substring(1) - } - ruleContent = ContentRule( - content = content, - replaceRegex = toNewRule(jsonItem.readString("ruleBookContentReplace")), - nextContentUrl = toNewRule(jsonItem.readString("ruleContentUrlNext")) - ) - } - } else { - source.bookSourceUrl = sourceAny.bookSourceUrl - source.bookSourceName = sourceAny.bookSourceName - source.bookSourceGroup = sourceAny.bookSourceGroup - source.bookSourceType = sourceAny.bookSourceType - source.bookUrlPattern = sourceAny.bookUrlPattern - source.customOrder = sourceAny.customOrder - source.enabled = sourceAny.enabled - source.enabledExplore = sourceAny.enabledExplore - source.enabledCookieJar = sourceAny.enabledCookieJar - source.enabledReview = sourceAny.enabledReview - source.concurrentRate = sourceAny.concurrentRate - source.header = sourceAny.header - source.loginUrl = when (sourceAny.loginUrl) { - null -> null - is String -> sourceAny.loginUrl.toString() - else -> JsonPath.parse(sourceAny.loginUrl).readString("url") - } - source.loginUi = if (sourceAny.loginUi is List<*>) { - GSON.toJson(sourceAny.loginUi) - } else { - sourceAny.loginUi?.toString() - } - source.loginCheckJs = sourceAny.loginCheckJs - source.coverDecodeJs = sourceAny.coverDecodeJs - source.bookSourceComment = sourceAny.bookSourceComment - source.variableComment = sourceAny.variableComment - source.lastUpdateTime = sourceAny.lastUpdateTime - source.respondTime = sourceAny.respondTime - source.weight = sourceAny.weight - source.exploreUrl = sourceAny.exploreUrl - source.ruleExplore = if (sourceAny.ruleExplore is String) { - GSON.fromJsonObject(sourceAny.ruleExplore.toString()) - .getOrNull() - } else { - GSON.fromJsonObject(GSON.toJson(sourceAny.ruleExplore)) - .getOrNull() - } - source.searchUrl = sourceAny.searchUrl - source.ruleSearch = if (sourceAny.ruleSearch is String) { - GSON.fromJsonObject(sourceAny.ruleSearch.toString()) - .getOrNull() - } else { - GSON.fromJsonObject(GSON.toJson(sourceAny.ruleSearch)) - .getOrNull() - } - source.ruleBookInfo = if (sourceAny.ruleBookInfo is String) { - GSON.fromJsonObject(sourceAny.ruleBookInfo.toString()) - .getOrNull() - } else { - GSON.fromJsonObject(GSON.toJson(sourceAny.ruleBookInfo)) - .getOrNull() - } - source.ruleToc = if (sourceAny.ruleToc is String) { - GSON.fromJsonObject(sourceAny.ruleToc.toString()) - .getOrNull() - } else { - GSON.fromJsonObject(GSON.toJson(sourceAny.ruleToc)) - .getOrNull() - } - source.ruleContent = if (sourceAny.ruleContent is String) { - GSON.fromJsonObject(sourceAny.ruleContent.toString()) - .getOrNull() - } else { - GSON.fromJsonObject(GSON.toJson(sourceAny.ruleContent)) - .getOrNull() - } - source.ruleReview = if (sourceAny.ruleReview is String) { - GSON.fromJsonObject(sourceAny.ruleReview.toString()) - .getOrNull() - } else { - GSON.fromJsonObject(GSON.toJson(sourceAny.ruleReview)) - .getOrNull() - } - } - source - } - } - - @Keep - data class BookSourceAny( - var bookSourceName: String = "", // 名称 - var bookSourceGroup: String? = null, // 分组 - var bookSourceUrl: String = "", // 地址,包括 http/https - var bookSourceType: Int = BookSourceType.default, // 类型,0 文本,1 音频 - var bookUrlPattern: String? = null, // 详情页url正则 - var customOrder: Int = 0, // 手动排序编号 - var enabled: Boolean = true, // 是否启用 - var enabledExplore: Boolean = true, // 启用发现 - var enabledReview: Boolean = false, // 启用段评 - var enabledCookieJar: Boolean = false, // 启用CookieJar - var concurrentRate: String? = null, // 并发率 - var header: String? = null, // 请求头 - var loginUrl: Any? = null, // 登录规则 - var loginUi: Any? = null, // 登录UI - var loginCheckJs: String? = null, // 登录检测js - var coverDecodeJs: String? = null, // 封面解密js - var bookSourceComment: String? = "", // 书源注释 - var variableComment: String? = null, // 变量说明 - var lastUpdateTime: Long = 0, // 最后更新时间,用于排序 - var respondTime: Long = 180000L, // 响应时间,用于排序 - var weight: Int = 0, // 智能排序的权重 - var exploreUrl: String? = null, // 发现url - var ruleExplore: Any? = null, // 发现规则 - var searchUrl: String? = null, // 搜索url - var ruleSearch: Any? = null, // 搜索规则 - var ruleBookInfo: Any? = null, // 书籍信息页规则 - var ruleToc: Any? = null, // 目录页规则 - var ruleContent: Any? = null, // 正文页规则 - var ruleReview: Any? = null // 段评规则 - ) - - // default规则适配 - // #正则#替换内容 替换成 ##正则##替换内容 - // | 替换成 || - // & 替换成 && - private fun toNewRule(oldRule: String?): String? { - if (oldRule.isNullOrBlank()) return null - var newRule = oldRule - var reverse = false - var allinone = false - if (oldRule.startsWith("-")) { - reverse = true - newRule = oldRule.substring(1) - } - if (newRule.startsWith("+")) { - allinone = true - newRule = newRule.substring(1) - } - if (!newRule.startsWith("@CSS:", true) && - !newRule.startsWith("@XPath:", true) && - !newRule.startsWith("//") && - !newRule.startsWith("##") && - !newRule.startsWith(":") && - !newRule.contains("@js:", true) && - !newRule.contains("", true) - ) { - if (newRule.contains("#") && !newRule.contains("##")) { - newRule = oldRule.replace("#", "##") - } - if (newRule.contains("|") && !newRule.contains("||")) { - if (newRule.contains("##")) { - val list = newRule.split("##") - if (list[0].contains("|")) { - newRule = list[0].replace("|", "||") - for (i in 1 until list.size) { - newRule += "##" + list[i] - } - } - } else { - newRule = newRule.replace("|", "||") - } - } - if (newRule.contains("&") - && !newRule.contains("&&") - && !newRule.contains("http") - && !newRule.startsWith("/") - ) { - newRule = newRule.replace("&", "&&") - } - } - if (allinone) { - newRule = "+$newRule" - } - if (reverse) { - newRule = "-$newRule" - } - return newRule - } - - private fun toNewUrls(oldUrls: String?): String? { - if (oldUrls.isNullOrBlank()) return null - if (oldUrls.startsWith("@js:") || oldUrls.startsWith("")) { - return oldUrls - } - if (!oldUrls.contains("\n") && !oldUrls.contains("&&")) { - return toNewUrl(oldUrls) - } - val urls = oldUrls.split("(&&|\r?\n)+".toRegex()) - return urls.map { - toNewUrl(it)?.replace("\n\\s*".toRegex(), "") - }.joinToString("\n") - } - - private fun toNewUrl(oldUrl: String?): String? { - if (oldUrl.isNullOrBlank()) return null - var url: String = oldUrl - if (oldUrl.startsWith("", true)) { - url = url.replace("=searchKey", "={{key}}") - .replace("=searchPage", "={{page}}") - return url - } - val map = HashMap() - var mather = headerPattern.matcher(url) - if (mather.find()) { - val header = mather.group() - url = url.replace(header, "") - map["headers"] = header.substring(8) - } - var urlList = url.split("|") - url = urlList[0] - if (urlList.size > 1) { - map["charset"] = urlList[1].split("=")[1] - } - mather = jsPattern.matcher(url) - val jsList = arrayListOf() - while (mather.find()) { - jsList.add(mather.group()) - url = url.replace(jsList.last(), "$${jsList.size - 1}") - } - url = url.replace("{", "<").replace("}", ">") - url = url.replace("searchKey", "{{key}}") - url = url.replace("".toRegex(), "{{page$1}}") - .replace("searchPage([-+]1)".toRegex(), "{{page$1}}") - .replace("searchPage", "{{page}}") - for ((index, item) in jsList.withIndex()) { - url = url.replace( - "$$index", - item.replace("searchKey", "key").replace("searchPage", "page") - ) - } - urlList = url.split("@") - url = urlList[0] - if (urlList.size > 1) { - map["method"] = "POST" - map["body"] = urlList[1] - } - if (map.size > 0) { - url += "," + GSON.toJson(map) - } - return url - } - - private fun uaToHeader(ua: String?): String? { - if (ua.isNullOrEmpty()) return null - val map = mapOf(Pair(AppConst.UA_NAME, ua)) - return GSON.toJson(map) - } - -} diff --git a/app/src/main/java/io/legado/app/help/storage/ImportOldData.kt b/app/src/main/java/io/legado/app/help/storage/ImportOldData.kt index 015118ccb..27aac8fc6 100644 --- a/app/src/main/java/io/legado/app/help/storage/ImportOldData.kt +++ b/app/src/main/java/io/legado/app/help/storage/ImportOldData.kt @@ -3,16 +3,26 @@ package io.legado.app.help.storage import android.content.Context import android.net.Uri import androidx.documentfile.provider.DocumentFile +import io.legado.app.R +import io.legado.app.constant.AppConst +import io.legado.app.constant.BookSourceType import io.legado.app.constant.BookType import io.legado.app.data.appDb import io.legado.app.data.entities.Book import io.legado.app.data.entities.BookSource +import io.legado.app.data.entities.rule.* +import io.legado.app.exception.NoStackTraceException import io.legado.app.help.ReplaceAnalyzer import io.legado.app.utils.* +import splitties.init.appCtx import java.io.File +import java.util.regex.Pattern object ImportOldData { + private val headerPattern = Pattern.compile("@Header:\\{.+?\\}", Pattern.CASE_INSENSITIVE) + private val jsPattern = Pattern.compile("\\{\\{.+?\\}\\}", Pattern.CASE_INSENSITIVE) + fun importUri(context: Context, uri: Uri) { if (uri.isContentScheme()) { DocumentFile.fromTreeUri(context, uri)?.listFiles()?.forEach { doc -> @@ -92,10 +102,9 @@ object ImportOldData { } fun importOldSource(json: String): Int { - val count = BookSource.fromJsonArray(json).onSuccess { - appDb.bookSourceDao.insert(*it.toTypedArray()) - }.getOrNull()?.size - return count ?: 0 + val sources = fromOldBookSources(json) + appDb.bookSourceDao.insert(*sources.toTypedArray()) + return sources.size } private fun importOldReplaceRule(json: String): Int { @@ -147,4 +156,211 @@ object ImportOldData { } return books } + + private fun fromOldBookSources(json: String): MutableList { + val sources = mutableListOf() + val items: List> = jsonPath.parse(json).read("$") + for (item in items) { + val jsonItem = jsonPath.parse(item) + val source = BookSource() + source.apply { + bookSourceUrl = jsonItem.readString("bookSourceUrl") + ?: throw NoStackTraceException(appCtx.getString(R.string.wrong_format)) + bookSourceName = jsonItem.readString("bookSourceName") ?: "" + bookSourceGroup = jsonItem.readString("bookSourceGroup") + loginUrl = jsonItem.readString("loginUrl") + loginUi = jsonItem.readString("loginUi") + loginCheckJs = jsonItem.readString("loginCheckJs") + coverDecodeJs = jsonItem.readString("coverDecodeJs") + bookSourceComment = jsonItem.readString("bookSourceComment") ?: "" + bookUrlPattern = jsonItem.readString("ruleBookUrlPattern") + customOrder = jsonItem.readInt("serialNumber") ?: 0 + header = uaToHeader(jsonItem.readString("httpUserAgent")) + searchUrl = toNewUrl(jsonItem.readString("ruleSearchUrl")) + exploreUrl = toNewUrls(jsonItem.readString("ruleFindUrl")) + bookSourceType = + if (jsonItem.readString("bookSourceType") == "AUDIO") BookSourceType.audio else BookSourceType.default + enabled = jsonItem.readBool("enable") ?: true + if (exploreUrl.isNullOrBlank()) { + enabledExplore = false + } + ruleSearch = SearchRule( + bookList = toNewRule(jsonItem.readString("ruleSearchList")), + name = toNewRule(jsonItem.readString("ruleSearchName")), + author = toNewRule(jsonItem.readString("ruleSearchAuthor")), + intro = toNewRule(jsonItem.readString("ruleSearchIntroduce")), + kind = toNewRule(jsonItem.readString("ruleSearchKind")), + bookUrl = toNewRule(jsonItem.readString("ruleSearchNoteUrl")), + coverUrl = toNewRule(jsonItem.readString("ruleSearchCoverUrl")), + lastChapter = toNewRule(jsonItem.readString("ruleSearchLastChapter")) + ) + ruleExplore = ExploreRule( + bookList = toNewRule(jsonItem.readString("ruleFindList")), + name = toNewRule(jsonItem.readString("ruleFindName")), + author = toNewRule(jsonItem.readString("ruleFindAuthor")), + intro = toNewRule(jsonItem.readString("ruleFindIntroduce")), + kind = toNewRule(jsonItem.readString("ruleFindKind")), + bookUrl = toNewRule(jsonItem.readString("ruleFindNoteUrl")), + coverUrl = toNewRule(jsonItem.readString("ruleFindCoverUrl")), + lastChapter = toNewRule(jsonItem.readString("ruleFindLastChapter")) + ) + ruleBookInfo = BookInfoRule( + init = toNewRule(jsonItem.readString("ruleBookInfoInit")), + name = toNewRule(jsonItem.readString("ruleBookName")), + author = toNewRule(jsonItem.readString("ruleBookAuthor")), + intro = toNewRule(jsonItem.readString("ruleIntroduce")), + kind = toNewRule(jsonItem.readString("ruleBookKind")), + coverUrl = toNewRule(jsonItem.readString("ruleCoverUrl")), + lastChapter = toNewRule(jsonItem.readString("ruleBookLastChapter")), + tocUrl = toNewRule(jsonItem.readString("ruleChapterUrl")) + ) + ruleToc = TocRule( + chapterList = toNewRule(jsonItem.readString("ruleChapterList")), + chapterName = toNewRule(jsonItem.readString("ruleChapterName")), + chapterUrl = toNewRule(jsonItem.readString("ruleContentUrl")), + nextTocUrl = toNewRule(jsonItem.readString("ruleChapterUrlNext")) + ) + var content = toNewRule(jsonItem.readString("ruleBookContent")) ?: "" + if (content.startsWith("$") && !content.startsWith("$.")) { + content = content.substring(1) + } + ruleContent = ContentRule( + content = content, + replaceRegex = toNewRule(jsonItem.readString("ruleBookContentReplace")), + nextContentUrl = toNewRule(jsonItem.readString("ruleContentUrlNext")) + ) + } + sources.add(source) + } + return sources + } + + + // default规则适配 + // #正则#替换内容 替换成 ##正则##替换内容 + // | 替换成 || + // & 替换成 && + private fun toNewRule(oldRule: String?): String? { + if (oldRule.isNullOrBlank()) return null + var newRule = oldRule + var reverse = false + var allinone = false + if (oldRule.startsWith("-")) { + reverse = true + newRule = oldRule.substring(1) + } + if (newRule.startsWith("+")) { + allinone = true + newRule = newRule.substring(1) + } + if (!newRule.startsWith("@CSS:", true) && + !newRule.startsWith("@XPath:", true) && + !newRule.startsWith("//") && + !newRule.startsWith("##") && + !newRule.startsWith(":") && + !newRule.contains("@js:", true) && + !newRule.contains("", true) + ) { + if (newRule.contains("#") && !newRule.contains("##")) { + newRule = oldRule.replace("#", "##") + } + if (newRule.contains("|") && !newRule.contains("||")) { + if (newRule.contains("##")) { + val list = newRule.split("##") + if (list[0].contains("|")) { + newRule = list[0].replace("|", "||") + for (i in 1 until list.size) { + newRule += "##" + list[i] + } + } + } else { + newRule = newRule.replace("|", "||") + } + } + if (newRule.contains("&") + && !newRule.contains("&&") + && !newRule.contains("http") + && !newRule.startsWith("/") + ) { + newRule = newRule.replace("&", "&&") + } + } + if (allinone) { + newRule = "+$newRule" + } + if (reverse) { + newRule = "-$newRule" + } + return newRule + } + + private fun toNewUrls(oldUrls: String?): String? { + if (oldUrls.isNullOrBlank()) return null + if (oldUrls.startsWith("@js:") || oldUrls.startsWith("")) { + return oldUrls + } + if (!oldUrls.contains("\n") && !oldUrls.contains("&&")) { + return toNewUrl(oldUrls) + } + val urls = oldUrls.split("(&&|\r?\n)+".toRegex()) + return urls.map { + toNewUrl(it)?.replace("\n\\s*".toRegex(), "") + }.joinToString("\n") + } + + private fun toNewUrl(oldUrl: String?): String? { + if (oldUrl.isNullOrBlank()) return null + var url: String = oldUrl + if (oldUrl.startsWith("", true)) { + url = url.replace("=searchKey", "={{key}}") + .replace("=searchPage", "={{page}}") + return url + } + val map = HashMap() + var mather = headerPattern.matcher(url) + if (mather.find()) { + val header = mather.group() + url = url.replace(header, "") + map["headers"] = header.substring(8) + } + var urlList = url.split("|") + url = urlList[0] + if (urlList.size > 1) { + map["charset"] = urlList[1].split("=")[1] + } + mather = jsPattern.matcher(url) + val jsList = arrayListOf() + while (mather.find()) { + jsList.add(mather.group()) + url = url.replace(jsList.last(), "$${jsList.size - 1}") + } + url = url.replace("{", "<").replace("}", ">") + url = url.replace("searchKey", "{{key}}") + url = url.replace("".toRegex(), "{{page$1}}") + .replace("searchPage([-+]1)".toRegex(), "{{page$1}}") + .replace("searchPage", "{{page}}") + for ((index, item) in jsList.withIndex()) { + url = url.replace( + "$$index", + item.replace("searchKey", "key").replace("searchPage", "page") + ) + } + urlList = url.split("@") + url = urlList[0] + if (urlList.size > 1) { + map["method"] = "POST" + map["body"] = urlList[1] + } + if (map.size > 0) { + url += "," + GSON.toJson(map) + } + return url + } + + private fun uaToHeader(ua: String?): String? { + if (ua.isNullOrEmpty()) return null + val map = mapOf(Pair(AppConst.UA_NAME, ua)) + return GSON.toJson(map) + } + } \ No newline at end of file