pull/737/head
Robot 4 years ago
commit f4751e6ba9
  1. 6
      app/build.gradle
  2. 4
      app/src/main/assets/updateLog.md
  3. 2
      app/src/main/java/io/legado/app/data/entities/BookChapter.kt
  4. 12
      app/src/main/java/io/legado/app/help/JsExtensions.kt
  5. 12
      app/src/main/java/io/legado/app/help/http/AjaxWebView.kt
  6. 20
      app/src/main/java/io/legado/app/help/http/ByteConverter.kt
  7. 2
      app/src/main/java/io/legado/app/help/http/CookieStore.kt
  8. 40
      app/src/main/java/io/legado/app/help/http/EncodeConverter.kt
  9. 31
      app/src/main/java/io/legado/app/help/http/HttpHelper.kt
  10. 6
      app/src/main/java/io/legado/app/help/http/Res.kt
  11. 124
      app/src/main/java/io/legado/app/help/http/StrResponse.kt
  12. 67
      app/src/main/java/io/legado/app/help/http/api/HttpGetApi.kt
  13. 60
      app/src/main/java/io/legado/app/help/http/api/HttpPostApi.kt
  14. 2
      app/src/main/java/io/legado/app/help/http/parser/ByteParser.kt
  15. 2
      app/src/main/java/io/legado/app/help/http/parser/InputStreamParser.kt
  16. 39
      app/src/main/java/io/legado/app/help/http/parser/StrResponseParser.kt
  17. 2
      app/src/main/java/io/legado/app/help/http/parser/TextParser.kt
  18. 15
      app/src/main/java/io/legado/app/lib/webdav/WebDav.kt
  19. 2
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt
  20. 137
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt
  21. 4
      app/src/main/java/io/legado/app/model/rss/Rss.kt
  22. 2
      app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt
  23. 4
      app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt
  24. 4
      app/src/main/java/io/legado/app/model/webBook/BookContent.kt
  25. 10
      app/src/main/java/io/legado/app/model/webBook/WebBook.kt
  26. 5
      app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt
  27. 2
      app/src/main/java/io/legado/app/ui/book/explore/ExploreShowActivity.kt
  28. 5
      app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt
  29. 3
      app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
  30. 7
      app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt
  31. 7
      app/src/main/java/io/legado/app/ui/widget/recycler/LoadMoreView.kt
  32. 13
      app/src/main/java/io/legado/app/utils/NetworkUtils.kt
  33. 5
      app/src/main/res/layout/activity_explore_show.xml
  34. 1
      app/src/main/res/layout/view_load_more.xml

@ -172,10 +172,10 @@ dependencies {
implementation 'com.github.gedoor:rhino-android:1.4'
//
implementation 'com.ljx.rxhttp:rxhttp:2.4.4'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.ljx.rxhttp:rxhttp:2.4.4-beta3'
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'com.github.franmontiel:PersistentCookieJar:v1.0.1'
kapt 'com.ljx.rxhttp:rxhttp-compiler:2.4.4'
kapt 'com.ljx.rxhttp:rxhttp-compiler:2.4.4-beta3'
//Glide
implementation 'com.github.bumptech.glide:glide:4.11.0'

@ -3,6 +3,10 @@
* 关注合作公众号 **[小说拾遗]** 获取好看的小说。
* 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。
**2020/12/11**
* 修复bug
* 网络访问框架修改为RxHttp, 有bug及时反馈
**2020/12/11**
* 修复因修改进度同步导致的bug

@ -63,7 +63,7 @@ data class BookChapter(
fun getAbsoluteURL(): String {
val urlArray = url.split(AnalyzeUrl.splitUrlRegex)
var absoluteUrl = NetworkUtils.getAbsoluteURL(baseUrl, urlArray[0])!!
var absoluteUrl = NetworkUtils.getAbsoluteURL(baseUrl, urlArray[0])
if (urlArray.size > 1) {
absoluteUrl = "$absoluteUrl,${urlArray[1]}"
}

@ -31,9 +31,9 @@ interface JsExtensions {
fun ajax(urlStr: String): String? {
return try {
val analyzeUrl = AnalyzeUrl(urlStr)
val call = analyzeUrl.getResponse(urlStr)
val response = call.execute()
response.body()
runBlocking {
analyzeUrl.getStrResponse(urlStr).body
}
} catch (e: Exception) {
e.msg
}
@ -45,9 +45,9 @@ interface JsExtensions {
fun connect(urlStr: String): Any {
return try {
val analyzeUrl = AnalyzeUrl(urlStr)
val call = analyzeUrl.getResponse(urlStr)
val response = call.execute()
response
runBlocking {
analyzeUrl.getStrResponse(urlStr)
}
} catch (e: Exception) {
e.msg
}

@ -39,7 +39,7 @@ class AjaxWebView {
mWebView = createAjaxWebView(params, this)
}
MSG_SUCCESS -> {
ajaxWebView.callback?.onResult(msg.obj as Res)
ajaxWebView.callback?.onResult(msg.obj as StrResponse)
destroyWebView()
}
MSG_ERROR -> {
@ -160,7 +160,7 @@ class AjaxWebView {
if (it.isNotEmpty() && it != "null") {
val content = StringEscapeUtils.unescapeJson(it)
.replace("^\"|\"$".toRegex(), "")
handler.obtainMessage(MSG_SUCCESS, Res(url, content))
handler.obtainMessage(MSG_SUCCESS, StrResponse.success(content, url))
.sendToTarget()
handler.removeCallbacks(this)
return@evaluateJavascript
@ -186,8 +186,10 @@ class AjaxWebView {
override fun onLoadResource(view: WebView, url: String) {
params.sourceRegex?.let {
if (url.matches(it.toRegex())) {
handler.obtainMessage(MSG_SUCCESS, Res(view.url ?: params.url, url))
.sendToTarget()
handler.obtainMessage(
MSG_SUCCESS,
StrResponse.success(url, view.url ?: params.url)
).sendToTarget()
}
}
}
@ -226,7 +228,7 @@ class AjaxWebView {
}
abstract class Callback {
abstract fun onResult(response: Res)
abstract fun onResult(response: StrResponse)
abstract fun onError(error: Throwable)
}
}

@ -1,20 +0,0 @@
package io.legado.app.help.http
import okhttp3.ResponseBody
import retrofit2.Converter
import retrofit2.Retrofit
import java.lang.reflect.Type
class ByteConverter : Converter.Factory() {
override fun responseBodyConverter(
type: Type?,
annotations: Array<Annotation>?,
retrofit: Retrofit?
): Converter<ResponseBody, ByteArray>? {
return Converter { value ->
value.bytes()
}
}
}

@ -108,6 +108,6 @@ object CookieStore : CookiePersistor, CookieManager {
}
private fun createCookieKey(cookie: okhttp3.Cookie): String {
return (if (cookie.secure()) "https" else "http") + "://" + cookie.domain() + cookie.path() + "|" + cookie.name()
return (if (cookie.secure) "https" else "http") + "://" + cookie.domain + cookie.path + "|" + cookie.name
}
}

@ -1,40 +0,0 @@
package io.legado.app.help.http
import io.legado.app.utils.EncodingDetect
import io.legado.app.utils.UTF8BOMFighter
import okhttp3.ResponseBody
import retrofit2.Converter
import retrofit2.Retrofit
import java.lang.reflect.Type
import java.nio.charset.Charset
class EncodeConverter(private val encode: String? = null) : Converter.Factory() {
override fun responseBodyConverter(
type: Type?,
annotations: Array<Annotation>?,
retrofit: Retrofit?
): Converter<ResponseBody, String>? {
return Converter { value ->
val responseBytes = UTF8BOMFighter.removeUTF8BOM(value.bytes())
var charsetName: String? = encode
charsetName?.let {
try {
return@Converter String(responseBytes, Charset.forName(charsetName))
} catch (e: Exception) {
}
}
//根据http头判断
value.contentType()?.charset()?.let {
return@Converter String(responseBytes, it)
}
//根据内容判断
charsetName = EncodingDetect.getHtmlEncode(responseBytes)
String(responseBytes, Charset.forName(charsetName))
}
}
}

@ -1,8 +1,8 @@
package io.legado.app.help.http
import io.legado.app.utils.msg
import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.*
import retrofit2.Retrofit
import java.io.IOException
import java.net.InetSocketAddress
import java.net.Proxy
@ -55,26 +55,6 @@ object HttpHelper {
})
}
inline fun <reified T> getApiService(
baseUrl: String,
encode: String? = null,
proxy: String? = null
): T {
return getRetrofit(baseUrl, encode, proxy).create(T::class.java)
}
fun getRetrofit(
baseUrl: String,
encode: String? = null,
proxy: String? = null
): Retrofit {
return Retrofit.Builder().baseUrl(baseUrl)
//增加返回值为字符串的支持(以实体类返回)
.addConverterFactory(EncodeConverter(encode))
.client(getProxyClient(proxy))
.build()
}
fun getProxyClient(proxy: String? = null): OkHttpClient {
if (proxy.isNullOrBlank()) {
return client
@ -108,7 +88,7 @@ object HttpHelper {
if (username != "" && password != "") {
builder.proxyAuthenticator { _, response -> //设置代理服务器账号密码
val credential: String = Credentials.basic(username, password)
response.request().newBuilder()
response.request.newBuilder()
.header("Proxy-Authorization", credential)
.build()
}
@ -130,21 +110,22 @@ object HttpHelper {
}
}
suspend fun ajax(params: AjaxWebView.AjaxParams): Res =
suspend fun ajax(params: AjaxWebView.AjaxParams): StrResponse =
suspendCancellableCoroutine { block ->
val webView = AjaxWebView()
block.invokeOnCancellation {
webView.destroyWebView()
}
webView.callback = object : AjaxWebView.Callback() {
override fun onResult(response: Res) {
override fun onResult(response: StrResponse) {
if (!block.isCompleted)
block.resume(response)
}
override fun onError(error: Throwable) {
if (!block.isCompleted)
block.resume(Res(params.url, error.localizedMessage))
block.resume(StrResponse.success(error.msg, params.url))
}
}
webView.load(params)

@ -1,6 +0,0 @@
package io.legado.app.help.http
data class Res(
val url: String,
val body: String?,
)

@ -0,0 +1,124 @@
package io.legado.app.help.http
import okhttp3.*
import okhttp3.Response.Builder
import java.util.*
/**
* An HTTP response.
*/
@Suppress("unused", "MemberVisibilityCanBePrivate")
class StrResponse private constructor(
val rawResponse: Response,
val body: String?,
val errorBody: ResponseBody?
) {
val raw get() = rawResponse
val url: String
get() {
raw.networkResponse?.let {
return it.request.url.toString()
}
return raw.request.url.toString()
}
fun code(): Int {
return rawResponse.code
}
fun message(): String {
return rawResponse.message
}
fun headers(): Headers {
return rawResponse.headers
}
val isSuccessful: Boolean
get() = rawResponse.isSuccessful
fun errorBody(): ResponseBody? {
return errorBody
}
override fun toString(): String {
return rawResponse.toString()
}
companion object {
fun success(code: Int, body: String): StrResponse {
require(!(code < 200 || code >= 300)) { "code < 200 or >= 300: $code" }
return success(
body,
Builder() //
.code(code)
.message("Response.success()")
.protocol(Protocol.HTTP_1_1)
.request(Request.Builder().url("http://localhost/").build())
.build()
)
}
fun success(body: String, headers: Headers): StrResponse {
return success(
body,
Builder() //
.code(200)
.message("OK")
.protocol(Protocol.HTTP_1_1)
.headers(headers)
.request(Request.Builder().url("http://localhost/").build())
.build()
)
}
fun success(body: String, url: String): StrResponse {
return success(
body,
Builder() //
.code(200)
.message("OK")
.protocol(Protocol.HTTP_1_1)
.request(Request.Builder().url(url).build())
.build()
)
}
@JvmOverloads
fun success(
body: String?, rawResponse: Response =
Builder() //
.code(200)
.message("OK")
.protocol(Protocol.HTTP_1_1)
.request(Request.Builder().url("http://localhost/").build())
.build()
): StrResponse {
Objects.requireNonNull(rawResponse, "rawResponse == null")
require(rawResponse.isSuccessful) { "rawResponse must be successful response" }
return StrResponse(rawResponse, body, null)
}
fun error(code: Int, body: ResponseBody?): StrResponse {
Objects.requireNonNull(body, "body == null")
require(code >= 400) { "code < 400: $code" }
return error(
body,
Builder() //
.code(code)
.message("Response.error()")
.protocol(Protocol.HTTP_1_1)
.request(Request.Builder().url("http://localhost/").build())
.build()
)
}
fun error(body: ResponseBody?, rawResponse: Response): StrResponse {
Objects.requireNonNull(body, "body == null")
Objects.requireNonNull(rawResponse, "rawResponse == null")
require(!rawResponse.isSuccessful) { "rawResponse should not be successful response" }
return StrResponse(rawResponse, null, body)
}
}
}

@ -1,67 +0,0 @@
package io.legado.app.help.http.api
import retrofit2.Call
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.HeaderMap
import retrofit2.http.QueryMap
import retrofit2.http.Url
/**
* Created by GKF on 2018/1/21.
* get web content
*/
@Suppress("unused")
interface HttpGetApi {
@GET
suspend fun getAsync(
@Url url: String,
@HeaderMap headers: Map<String, String>
): Response<String>
@GET
suspend fun getMapAsync(
@Url url: String,
@QueryMap(encoded = true) queryMap: Map<String, String>,
@HeaderMap headers: Map<String, String>
): Response<String>
@GET
fun get(
@Url url: String,
@HeaderMap headers: Map<String, String>
): Call<String>
@GET
fun getByte(
@Url url: String,
@HeaderMap headers: Map<String, String>
): Call<ByteArray>
@GET
fun getMap(
@Url url: String,
@QueryMap(encoded = true) queryMap: Map<String, String>,
@HeaderMap headers: Map<String, String>
): Call<String>
@GET
fun getMapByte(
@Url url: String,
@QueryMap(encoded = true) queryMap: Map<String, String>,
@HeaderMap headers: Map<String, String>
): Call<ByteArray>
@GET
suspend fun getByteAsync(
@Url url: String,
@HeaderMap headers: Map<String, String>
): Response<ByteArray>
@GET
suspend fun getMapByteAsync(
@Url url: String,
@QueryMap(encoded = true) queryMap: Map<String, String>,
@HeaderMap headers: Map<String, String>
): Response<ByteArray>
}

@ -1,60 +0,0 @@
package io.legado.app.help.http.api
import okhttp3.RequestBody
import retrofit2.Call
import retrofit2.Response
import retrofit2.http.*
/**
* Created by GKF on 2018/1/29.
* post
*/
@Suppress("unused")
interface HttpPostApi {
@FormUrlEncoded
@POST
suspend fun postMapAsync(
@Url url: String,
@FieldMap(encoded = true) fieldMap: Map<String, String>,
@HeaderMap headers: Map<String, String>
): Response<String>
@POST
suspend fun postBodyAsync(
@Url url: String,
@Body body: RequestBody,
@HeaderMap headers: Map<String, String>
): Response<String>
@FormUrlEncoded
@POST
fun postMap(
@Url url: String,
@FieldMap(encoded = true) fieldMap: Map<String, String>,
@HeaderMap headers: Map<String, String>
): Call<String>
@POST
fun postBody(
@Url url: String,
@Body body: RequestBody,
@HeaderMap headers: Map<String, String>
): Call<String>
@FormUrlEncoded
@POST
suspend fun postMapByteAsync(
@Url url: String,
@FieldMap(encoded = true) fieldMap: Map<String, String>,
@HeaderMap headers: Map<String, String>
): Response<ByteArray>
@POST
suspend fun postBodyByteAsync(
@Url url: String,
@Body body: RequestBody,
@HeaderMap headers: Map<String, String>
): Response<ByteArray>
}

@ -7,7 +7,7 @@ import rxhttp.wrapper.annotation.Parser
class ByteParser : rxhttp.wrapper.parse.Parser<ByteArray> {
override fun onParse(response: Response): ByteArray {
return response.body()!!.bytes()
return response.body!!.bytes()
}
}

@ -8,7 +8,7 @@ import java.io.InputStream
class InputStreamParser : rxhttp.wrapper.parse.Parser<InputStream> {
override fun onParse(response: Response): InputStream {
return response.body()!!.byteStream()
return response.body!!.byteStream()
}
}

@ -0,0 +1,39 @@
package io.legado.app.help.http.parser
import io.legado.app.help.http.StrResponse
import io.legado.app.utils.EncodingDetect
import io.legado.app.utils.UTF8BOMFighter
import okhttp3.Response
import rxhttp.wrapper.annotation.Parser
import rxhttp.wrapper.exception.HttpStatusCodeException
import java.nio.charset.Charset
@Parser(name = "StrResponse")
class StrResponseParser(val encode: String? = null) : rxhttp.wrapper.parse.Parser<StrResponse> {
override fun onParse(response: Response): StrResponse {
val body = getString(response)
return StrResponse.success(body, response)
}
private fun getString(response: Response): String {
val responseBody = response.body ?: throw HttpStatusCodeException(response, "内容为空")
val responseBytes = UTF8BOMFighter.removeUTF8BOM(responseBody.bytes())
var charsetName: String? = encode
charsetName?.let {
return String(responseBytes, Charset.forName(charsetName))
}
//根据http头判断
responseBody.contentType()?.charset()?.let {
return String(responseBytes, it)
}
//根据内容判断
charsetName = EncodingDetect.getHtmlEncode(responseBytes)
return String(responseBytes, Charset.forName(charsetName))
}
}

@ -12,7 +12,7 @@ class TextParser(val encode: String? = null) : rxhttp.wrapper.parse.Parser<Strin
override fun onParse(response: Response): String {
val responseBody = response.body() ?: throw HttpStatusCodeException(response, "内容为空")
val responseBody = response.body ?: throw HttpStatusCodeException(response, "内容为空")
val responseBytes = UTF8BOMFighter.removeUTF8BOM(responseBody.bytes())
var charsetName: String? = encode

@ -2,6 +2,9 @@ package io.legado.app.lib.webdav
import io.legado.app.help.http.HttpHelper
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.asRequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import org.jsoup.Jsoup
import rxhttp.wrapper.param.RxHttp
import rxhttp.wrapper.param.toInputStream
@ -64,7 +67,7 @@ class WebDav(urlStr: String) {
this.exists = false
return false
}
response.body()?.let {
response.body?.let {
@Suppress("BlockingMethodInNonBlockingContext")
if (it.string().isNotEmpty()) {
return true
@ -83,7 +86,7 @@ class WebDav(urlStr: String) {
suspend fun listFiles(propsList: ArrayList<String> = ArrayList()): List<WebDav> {
propFindResponse(propsList)?.let { response ->
if (response.isSuccessful) {
response.body()?.let { body ->
response.body?.let { body ->
@Suppress("BlockingMethodInNonBlockingContext")
return parseDir(body.string())
}
@ -107,7 +110,7 @@ class WebDav(urlStr: String) {
httpUrl?.let { url ->
// 添加RequestBody对象,可以只返回的属性。如果设为null,则会返回全部属性
// 注意:尽量手动指定需要返回的属性。若返回全部属性,可能后由于Prop.java里没有该属性名,而崩溃。
val requestBody = RequestBody.create(MediaType.parse("text/plain"), requestPropsStr)
val requestBody = requestPropsStr.toRequestBody("text/plain".toMediaType())
val request = Request.Builder()
.url(url)
.method("PROPFIND", requestBody)
@ -197,9 +200,8 @@ class WebDav(urlStr: String) {
suspend fun upload(localPath: String, contentType: String? = null): Boolean {
val file = File(localPath)
if (!file.exists()) return false
val mediaType = contentType?.let { MediaType.parse(it) }
// 务必注意RequestBody不要嵌套,不然上传时内容可能会被追加多余的文件信息
val fileBody = RequestBody.create(mediaType, file)
val fileBody = file.asRequestBody(contentType?.toMediaType())
httpUrl?.let {
val request = Request.Builder()
.url(it)
@ -210,9 +212,8 @@ class WebDav(urlStr: String) {
}
suspend fun upload(byteArray: ByteArray, contentType: String? = null): Boolean {
val mediaType = contentType?.let { MediaType.parse(it) }
// 务必注意RequestBody不要嵌套,不然上传时内容可能会被追加多余的文件信息
val fileBody = RequestBody.create(mediaType, byteArray)
val fileBody = byteArray.toRequestBody(contentType?.toMediaType())
httpUrl?.let {
val request = Request.Builder()
.url(it)

@ -647,7 +647,7 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions {
return try {
val analyzeUrl = AnalyzeUrl(urlStr, book = book)
runBlocking {
analyzeUrl.getRes(urlStr).body
analyzeUrl.getStrResponse(urlStr).body
}
} catch (e: Exception) {
e.localizedMessage

@ -15,15 +15,10 @@ import io.legado.app.help.AppConfig
import io.legado.app.help.CacheManager
import io.legado.app.help.JsExtensions
import io.legado.app.help.http.*
import io.legado.app.help.http.api.HttpGetApi
import io.legado.app.help.http.api.HttpPostApi
import io.legado.app.utils.*
import okhttp3.FormBody
import okhttp3.MediaType
import okhttp3.RequestBody
import retrofit2.Call
import rxhttp.wrapper.param.RxHttp
import rxhttp.wrapper.param.toByteArray
import rxhttp.wrapper.param.toStrResponse
import java.net.URLEncoder
import java.util.*
import java.util.regex.Pattern
@ -50,7 +45,6 @@ class AnalyzeUrl(
companion object {
val splitUrlRegex = Regex(",\\s*(?=\\{)")
private val pagePattern = Pattern.compile("<(.*?)>")
private val jsonType = MediaType.parse("application/json; charset=utf-8")
}
var url: String = ""
@ -61,7 +55,6 @@ class AnalyzeUrl(
private var queryStr: String? = null
private val fieldMap = LinkedHashMap<String, String>()
private var charset: String? = null
private var requestBody: RequestBody? = null
private var method = RequestMethod.GET
private var proxy: String? = null
@ -172,7 +165,7 @@ class AnalyzeUrl(
*/
private fun initUrl() {
var urlArray = ruleUrl.split(splitUrlRegex, 2)
url = urlArray[0]
url = NetworkUtils.getAbsoluteURL(baseUrl, urlArray[0])
urlHasQuery = urlArray[0]
NetworkUtils.getBaseUrl(url)?.let {
baseUrl = it
@ -223,13 +216,9 @@ class AnalyzeUrl(
}
RequestMethod.POST -> {
body?.let {
if (it.isJson()) {
requestBody = RequestBody.create(jsonType, it)
} else {
if (!it.isJson()) {
analyzeFields(it)
}
} ?: let {
requestBody = FormBody.Builder().build()
}
}
}
@ -285,56 +274,15 @@ class AnalyzeUrl(
return book?.variableMap?.get(key) ?: ""
}
fun getResponse(tag: String): Call<String> {
val cookie = CookieStore.getCookie(tag)
if (cookie.isNotEmpty()) {
val cookieMap = CookieStore.cookieToMap(cookie)
val customCookieMap = CookieStore.cookieToMap(headerMap["Cookie"] ?: "")
cookieMap.putAll(customCookieMap)
val newCookie = CookieStore.mapToCookie(cookieMap)
newCookie?.let {
headerMap.put("Cookie", it)
}
}
return when {
method == RequestMethod.POST -> {
if (fieldMap.isNotEmpty()) {
HttpHelper
.getApiService<HttpPostApi>(baseUrl, charset, proxy)
.postMap(url, fieldMap, headerMap)
} else {
HttpHelper
.getApiService<HttpPostApi>(baseUrl, charset, proxy)
.postBody(url, requestBody!!, headerMap)
}
}
fieldMap.isEmpty() -> HttpHelper
.getApiService<HttpGetApi>(baseUrl, charset, proxy)
.get(url, headerMap)
else -> HttpHelper
.getApiService<HttpGetApi>(baseUrl, charset, proxy)
.getMap(url, fieldMap, headerMap)
}
}
suspend fun getRes(
suspend fun getStrResponse(
tag: String,
jsStr: String? = null,
sourceRegex: String? = null,
): Res {
): StrResponse {
if (type != null) {
return Res(url, StringUtils.byteToHexString(getByteArray(tag)))
}
val cookie = CookieStore.getCookie(tag)
if (cookie.isNotEmpty()) {
val cookieMap = CookieStore.cookieToMap(cookie)
val customCookieMap = CookieStore.cookieToMap(headerMap["Cookie"] ?: "")
cookieMap.putAll(customCookieMap)
val newCookie = CookieStore.mapToCookie(cookieMap)
newCookie?.let {
headerMap.put("Cookie", it)
}
return StrResponse.success(StringUtils.byteToHexString(getByteArray(tag)), url)
}
setCookie(tag)
if (useWebView) {
val params = AjaxWebView.AjaxParams(url)
params.headerMap = headerMap
@ -345,65 +293,70 @@ class AnalyzeUrl(
params.tag = tag
return HttpHelper.ajax(params)
}
val res = when {
method == RequestMethod.POST -> {
return when (method) {
RequestMethod.POST -> {
if (fieldMap.isNotEmpty()) {
HttpHelper
.getApiService<HttpPostApi>(baseUrl, charset, proxy)
.postMapAsync(url, fieldMap, headerMap)
RxHttp.postForm(url)
.setOkClient(HttpHelper.getProxyClient(proxy))
.addAllEncoded(fieldMap)
.addAllHeader(headerMap)
.toStrResponse().await()
} else {
HttpHelper
.getApiService<HttpPostApi>(baseUrl, charset, proxy)
.postBodyAsync(url, requestBody!!, headerMap)
}
}
fieldMap.isEmpty() -> {
HttpHelper
.getApiService<HttpGetApi>(baseUrl, charset, proxy)
.getAsync(url, headerMap)
RxHttp.postJson(url)
.setOkClient(HttpHelper.getProxyClient(proxy))
.addAll(body)
.addAllHeader(headerMap)
.toStrResponse().await()
}
else -> {
HttpHelper
.getApiService<HttpGetApi>(baseUrl, charset, proxy)
.getMapAsync(url, fieldMap, headerMap)
}
else -> RxHttp.get(url)
.setOkClient(HttpHelper.getProxyClient(proxy))
.addAllEncoded(fieldMap)
.addAllHeader(headerMap)
.toStrResponse().await()
}
return Res(NetworkUtils.getUrl(res), res.body())
}
suspend fun getByteArray(tag: String? = null): ByteArray {
if (tag != null) {
val cookie = CookieStore.getCookie(tag)
if (cookie.isNotEmpty()) {
val cookieMap = CookieStore.cookieToMap(cookie)
val customCookieMap = CookieStore.cookieToMap(headerMap["Cookie"] ?: "")
cookieMap.putAll(customCookieMap)
val newCookie = CookieStore.mapToCookie(cookieMap)
newCookie?.let {
headerMap.put("Cookie", it)
}
}
}
setCookie(tag)
return when (method) {
RequestMethod.POST -> {
if (fieldMap.isNotEmpty()) {
RxHttp.postForm(url)
.addAll(fieldMap)
.setOkClient(HttpHelper.getProxyClient(proxy))
.addAllEncoded(fieldMap)
.addAllHeader(headerMap)
.toByteArray().await()
} else {
RxHttp.postJson(url)
.setOkClient(HttpHelper.getProxyClient(proxy))
.addAll(body)
.addAllHeader(headerMap)
.toByteArray().await()
}
}
else -> RxHttp.get(url)
.addAll(fieldMap)
.setOkClient(HttpHelper.getProxyClient(proxy))
.addAllEncoded(fieldMap)
.toByteArray().await()
}
}
private fun setCookie(tag: String?) {
if (tag != null) {
val cookie = CookieStore.getCookie(tag)
if (cookie.isNotEmpty()) {
val cookieMap = CookieStore.cookieToMap(cookie)
val customCookieMap = CookieStore.cookieToMap(headerMap["Cookie"] ?: "")
cookieMap.putAll(customCookieMap)
val newCookie = CookieStore.mapToCookie(cookieMap)
newCookie?.let {
headerMap.put("Cookie", it)
}
}
}
}
fun getGlideUrl(): GlideUrl {
val headers = LazyHeaders.Builder()
headerMap.forEach { (key, value) ->

@ -26,7 +26,7 @@ object Rss {
page = page,
headerMapF = rssSource.getHeaderMap()
)
val body = analyzeUrl.getRes(rssSource.sourceUrl).body
val body = analyzeUrl.getStrResponse(rssSource.sourceUrl).body
RssParserByRule.parseXML(sortName, sortUrl, body, rssSource)
}
}
@ -42,7 +42,7 @@ object Rss {
val body = AnalyzeUrl(
rssArticle.link, baseUrl = rssArticle.origin,
headerMapF = rssSource?.getHeaderMap()
).getRes(rssArticle.origin)
).getStrResponse(rssArticle.origin)
.body
val analyzeRule = AnalyzeRule()
analyzeRule.setContent(body)

@ -112,7 +112,7 @@ object RssParserByRule {
rssArticle.image = analyzeRule.getString(ruleImage, true)
Debug.log(sourceUrl, "${rssArticle.image}", log)
Debug.log(sourceUrl, "┌获取文章链接", log)
rssArticle.link = NetworkUtils.getAbsoluteURL(sourceUrl, analyzeRule.getString(ruleLink))!!
rssArticle.link = NetworkUtils.getAbsoluteURL(sourceUrl, analyzeRule.getString(ruleLink))
Debug.log(sourceUrl, "${rssArticle.link}", log)
if (rssArticle.title.isBlank()) {
return null

@ -63,7 +63,7 @@ object BookChapterList {
ruleUrl = nextUrl,
book = book,
headerMapF = bookSource.getHeaderMap()
).getRes(bookSource.bookSourceUrl)
).getStrResponse(bookSource.bookSourceUrl)
.body?.let { nextBody ->
chapterData = analyzeChapterList(
book, nextUrl, nextBody, tocRule, listRule, bookSource
@ -132,7 +132,7 @@ object BookChapterList {
ruleUrl = chapterData.nextUrl,
book = book,
headerMapF = bookSource.getHeaderMap()
).getRes(bookSource.bookSourceUrl).body
).getStrResponse(bookSource.bookSourceUrl).body
?: throw Exception("${chapterData.nextUrl}, 下载失败")
val nextChapterData = analyzeChapterList(
book, chapterData.nextUrl, nextBody, tocRule, listRule, bookSource,

@ -56,7 +56,7 @@ object BookContent {
ruleUrl = nextUrl,
book = book,
headerMapF = bookSource.getHeaderMap()
).getRes(bookSource.bookSourceUrl).body?.let { nextBody ->
).getStrResponse(bookSource.bookSourceUrl).body?.let { nextBody ->
contentData = analyzeContent(
book, nextUrl, nextBody, contentRule, bookChapter, bookSource, false
)
@ -78,7 +78,7 @@ object BookContent {
ruleUrl = item.nextUrl,
book = book,
headerMapF = bookSource.getHeaderMap()
).getRes(bookSource.bookSourceUrl).body?.let {
).getStrResponse(bookSource.bookSourceUrl).body?.let {
contentData = analyzeContent(
book, item.nextUrl, it, contentRule, bookChapter, bookSource, false
)

@ -46,7 +46,7 @@ class WebBook(val bookSource: BookSource) {
headerMapF = bookSource.getHeaderMap(),
book = variableBook
)
val res = analyzeUrl.getRes(bookSource.bookSourceUrl)
val res = analyzeUrl.getStrResponse(bookSource.bookSourceUrl)
return BookList.analyzeBookList(
scope,
res.body,
@ -77,7 +77,7 @@ class WebBook(val bookSource: BookSource) {
baseUrl = sourceUrl,
headerMapF = bookSource.getHeaderMap()
)
val res = analyzeUrl.getRes(bookSource.bookSourceUrl)
val res = analyzeUrl.getStrResponse(bookSource.bookSourceUrl)
BookList.analyzeBookList(
scope,
res.body,
@ -110,7 +110,7 @@ class WebBook(val bookSource: BookSource) {
baseUrl = sourceUrl,
headerMapF = bookSource.getHeaderMap(),
book = book
).getRes(bookSource.bookSourceUrl)
).getStrResponse(bookSource.bookSourceUrl)
BookInfo.analyzeBookInfo(book, res.body, bookSource, book.bookUrl, canReName)
}
book
@ -141,7 +141,7 @@ class WebBook(val bookSource: BookSource) {
ruleUrl = book.tocUrl,
baseUrl = book.bookUrl,
headerMapF = bookSource.getHeaderMap()
).getRes(bookSource.bookSourceUrl)
).getStrResponse(bookSource.bookSourceUrl)
BookChapterList.analyzeChapterList(
this,
book,
@ -201,7 +201,7 @@ class WebBook(val bookSource: BookSource) {
headerMapF = bookSource.getHeaderMap(),
book = book,
chapter = bookChapter
).getRes(
).getStrResponse(
bookSource.bookSourceUrl,
jsStr = bookSource.getContentRule().webJs,
sourceRegex = bookSource.getContentRule().sourceRegex

@ -109,9 +109,8 @@ class CacheViewModel(application: Application) : BaseViewModel(application) {
content.split("\n").forEachIndexed { index, text ->
val matcher = AppPattern.imgPattern.matcher(text)
if (matcher.find()) {
var src = matcher.group(1)
src = NetworkUtils.getAbsoluteURL(chapter.url, src)
src?.let {
matcher.group(1)?.let {
val src = NetworkUtils.getAbsoluteURL(chapter.url, it)
srcList.add(Triple(chapter.title, index, src))
}
}

@ -1,7 +1,6 @@
package io.legado.app.ui.book.explore
import android.os.Bundle
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R
import io.legado.app.base.VMBaseActivity
@ -37,7 +36,6 @@ class ExploreShowActivity : VMBaseActivity<ActivityExploreShowBinding, ExploreSh
private fun initRecyclerView() {
adapter = ExploreShowAdapter(this, this)
binding.recyclerView.layoutManager = LinearLayoutManager(this)
binding.recyclerView.addItemDecoration(VerticalDivider(this))
binding.recyclerView.adapter = adapter
loadMoreView = LoadMoreView(this)

@ -8,11 +8,13 @@ import io.legado.app.base.BaseViewModel
import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.SearchBook
import io.legado.app.model.webBook.WebBook
import io.legado.app.utils.msg
import kotlinx.coroutines.Dispatchers.IO
class ExploreShowViewModel(application: Application) : BaseViewModel(application) {
val booksData = MutableLiveData<List<SearchBook>>()
val errorLiveData = MutableLiveData<String>()
private var bookSource: BookSource? = null
private val variableBook = SearchBook()
private var exploreUrl: String? = null
@ -39,6 +41,9 @@ class ExploreShowViewModel(application: Application) : BaseViewModel(application
booksData.postValue(searchBooks)
App.db.searchBookDao.insert(*searchBooks.toTypedArray())
page++
}.onError {
it.printStackTrace()
errorLiveData.postValue(it.msg)
}
}
}

@ -97,6 +97,9 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
ReadBook.loadContent(resetPageOffset = true)
}
}
if (!BaseReadAloudService.isRun) {
syncBookProgress(book)
}
}
}

@ -95,15 +95,14 @@ object ChapterProvider {
contents.forEachIndexed { index, text ->
val matcher = AppPattern.imgPattern.matcher(text)
if (matcher.find()) {
var src = matcher.group(1)
matcher.group(1)?.let {
if (!book.isEpub()) {
src = NetworkUtils.getAbsoluteURL(bookChapter.url, src)
}
src?.let {
val src = NetworkUtils.getAbsoluteURL(bookChapter.url, it)
durY = setTypeImage(
book, bookChapter, src, durY, textPages, imageStyle
)
}
}
} else {
val isTitle = index == 0
val textPaint = if (isTitle) paint.first else paint.second

@ -47,4 +47,11 @@ class LoadMoreView(context: Context, attrs: AttributeSet? = null) : FrameLayout(
binding.tvText.visible()
}
fun error(msg: String) {
hasMore = false
binding.rotateLoading.hide()
binding.tvText.text = msg
binding.tvText.visible()
}
}

@ -1,6 +1,5 @@
package io.legado.app.utils
import retrofit2.Response
import java.net.InetAddress
import java.net.NetworkInterface
import java.net.SocketException
@ -11,13 +10,6 @@ import java.util.regex.Pattern
@Suppress("unused", "MemberVisibilityCanBePrivate")
object NetworkUtils {
fun getUrl(response: Response<*>): String {
response.raw().networkResponse()?.let {
return it.request().url().toString()
}
return response.raw().request().url().toString()
}
private val notNeedEncoding: BitSet by lazy {
val bitSet = BitSet(256)
for (i in 'a'.toInt()..'z'.toInt()) {
@ -77,9 +69,8 @@ object NetworkUtils {
/**
* 获取绝对地址
*/
fun getAbsoluteURL(baseURL: String?, relativePath: String?): String? {
fun getAbsoluteURL(baseURL: String?, relativePath: String): String {
if (baseURL.isNullOrEmpty()) return relativePath
if (relativePath.isNullOrEmpty()) return baseURL
if (relativePath.isAbsUrl()) return relativePath
var relativeUrl = relativePath
try {
@ -96,7 +87,7 @@ object NetworkUtils {
/**
* 获取绝对地址
*/
fun getAbsoluteURL(baseURL: URL?, relativePath: String): String? {
fun getAbsoluteURL(baseURL: URL?, relativePath: String): String {
if (baseURL == null) return relativePath
var relativeUrl = relativePath
try {

@ -25,8 +25,9 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_search" />
</io.legado.app.ui.widget.dynamiclayout.DynamicFrameLayout>

@ -25,6 +25,7 @@
android:textSize="14sp"
android:background="?attr/selectableItemBackground"
android:visibility="invisible"
android:singleLine="true"
tools:text="加载状态"
tools:visibility="visible" />

Loading…
Cancel
Save