diff --git a/app/src/main/java/io/legado/app/constant/AppConst.kt b/app/src/main/java/io/legado/app/constant/AppConst.kt
index 863f9e27a..0d98ed75e 100644
--- a/app/src/main/java/io/legado/app/constant/AppConst.kt
+++ b/app/src/main/java/io/legado/app/constant/AppConst.kt
@@ -2,6 +2,7 @@ package io.legado.app.constant
import io.legado.app.App
import io.legado.app.R
+import javax.script.ScriptEngineManager
object AppConst {
const val channelIdDownload = "channel_download"
@@ -11,6 +12,8 @@ object AppConst {
const val APP_TAG = "Legado"
const val RC_IMPORT_YUEDU_DATA = 100
+ val SCRIPT_ENGINE = ScriptEngineManager().getEngineByName("rhino")
+
val NOT_AVAILABLE = App.INSTANCE.getString(R.string.not_available)
}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/constant/Pattern.kt b/app/src/main/java/io/legado/app/constant/Pattern.kt
new file mode 100644
index 000000000..917f4d52e
--- /dev/null
+++ b/app/src/main/java/io/legado/app/constant/Pattern.kt
@@ -0,0 +1,8 @@
+package io.legado.app.constant
+
+import java.util.regex.Pattern
+
+object Pattern {
+ val JS_PATTERN = Pattern.compile("([\\w\\W]*?|@js:[\\w\\W]*$)", Pattern.CASE_INSENSITIVE)
+ val EXP_PATTERN = Pattern.compile("\\{\\{([\\w\\W]*?)\\}\\}")
+}
\ No newline at end of file
diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt
new file mode 100644
index 000000000..dffcd7aee
--- /dev/null
+++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt
@@ -0,0 +1,290 @@
+package io.legado.app.model.analyzeRule
+
+import android.annotation.SuppressLint
+import android.text.TextUtils
+import androidx.annotation.Keep
+import io.legado.app.constant.AppConst.SCRIPT_ENGINE
+import io.legado.app.constant.Pattern.EXP_PATTERN
+import io.legado.app.constant.Pattern.JS_PATTERN
+import io.legado.app.utils.NetworkUtils
+import java.net.URLEncoder
+import java.util.*
+import java.util.regex.Pattern
+import javax.script.SimpleBindings
+
+
+/**
+ * Created by GKF on 2018/1/24.
+ * 搜索URL规则解析
+ */
+@Keep
+class AnalyzeUrl @SuppressLint("DefaultLocale")
+@Throws(Exception::class)
+constructor(ruleUrl: String, key: String?, page: Int?, headerMapF: Map?, baseUrl: String?) {
+ private var baseUrl: String? = null
+ var url: String? = null
+ private set
+ var host: String? = null
+ private set
+ var path: String? = null
+ private set
+ var queryStr: String? = null
+ private set
+ private val queryMap = LinkedHashMap()
+ private val headerMap = HashMap()
+ private var charCode: String? = null
+ var urlMode = UrlMode.DEFAULT
+ private set
+
+ val postData: ByteArray
+ get() {
+ val builder = StringBuilder()
+ val keys = queryMap.keys
+ for (key in keys) {
+ builder.append(String.format("%s=%s&", key, queryMap[key]))
+ }
+ builder.deleteCharAt(builder.lastIndexOf("&"))
+ return builder.toString().toByteArray()
+ }
+
+ @Throws(Exception::class)
+ constructor(urlRule: String) : this(urlRule, null, null, null, null)
+
+ @Throws(Exception::class)
+ constructor(urlRule: String, headerMapF: Map, baseUrl: String) : this(
+ urlRule,
+ null,
+ null,
+ headerMapF,
+ baseUrl
+ )
+
+ init {
+ var ruleUrl = ruleUrl
+ if (!TextUtils.isEmpty(baseUrl)) {
+// this.baseUrl = headerPattern.matcher(baseUrl).replaceAll("")
+ }
+ //解析Header
+ ruleUrl = analyzeHeader(ruleUrl, headerMapF)
+ //替换关键字
+ key?.let {
+ if (it.isNotBlank()) {
+ ruleUrl = ruleUrl.replace("searchKey", it)
+ }
+ }
+ //分离编码规则
+ ruleUrl = splitCharCode(ruleUrl)
+ //判断是否有下一页
+ if (page != null && page > 1 && !ruleUrl.contains("searchPage"))
+ throw Exception("没有下一页")
+ //替换js
+ ruleUrl = replaceJs(ruleUrl, baseUrl, page, key)
+ //设置页数
+ ruleUrl = analyzePage(ruleUrl, page)
+ //执行规则列表
+ val ruleList = splitRule(ruleUrl)
+ for (rule in ruleList) {
+ ruleUrl = if (rule.startsWith("")) {
+ evalJS(rule.substring(4, rule.lastIndexOf("<")), ruleUrl) as String
+ } else {
+ rule.replace("@result", ruleUrl)
+ }
+ }
+ //分离post参数
+ var ruleUrlS = ruleUrl.split("@".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
+ if (ruleUrlS.size > 1) {
+ urlMode = UrlMode.POST
+ } else {
+ //分离get参数
+ ruleUrlS = ruleUrlS[0].split("\\?".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
+ if (ruleUrlS.size > 1) {
+ urlMode = UrlMode.GET
+ }
+ }
+ generateUrlPath(ruleUrlS[0])
+ if (urlMode != UrlMode.DEFAULT) {
+ analyzeQuery(ruleUrlS[1])
+ }
+ }
+
+ /**
+ * 解析Header
+ */
+ private fun analyzeHeader(ruleUrl: String, headerMapF: Map?): String {
+// var ruleUrl = ruleUrl
+// if (headerMapF != null) {
+// headerMap.putAll(headerMapF)
+// }
+// val matcher = headerPattern.matcher(ruleUrl)
+// if (matcher.find()) {
+// var find = matcher.group(0)
+// ruleUrl = ruleUrl.replace(find, "")
+// find = find.substring(8)
+// try {
+// val map = Gson().fromJson