Merge pull request #2314 from Xwite/master

feat: decode decrypted cover
pull/2319/head
kunfei 3 years ago committed by GitHub
commit 2edeb25731
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      app/src/main/assets/help/ruleHelp.md
  2. 5
      app/src/main/java/io/legado/app/data/AppDatabase.kt
  3. 3
      app/src/main/java/io/legado/app/data/entities/BookSource.kt
  4. 10
      app/src/main/java/io/legado/app/data/entities/RssSource.kt
  5. 34
      app/src/main/java/io/legado/app/help/BookHelp.kt
  6. 20
      app/src/main/java/io/legado/app/help/glide/OkHttpStreamFetcher.kt
  7. 3
      app/src/main/java/io/legado/app/help/source/SourceAnalyzer.kt
  8. 2
      app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt
  9. 2
      app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt
  10. 69
      app/src/main/java/io/legado/app/utils/ImageUtils.kt
  11. 3
      app/src/main/res/values-es-rES/strings.xml
  12. 3
      app/src/main/res/values-ja-rJP/strings.xml
  13. 3
      app/src/main/res/values-pt-rBR/strings.xml
  14. 3
      app/src/main/res/values-zh-rHK/strings.xml
  15. 3
      app/src/main/res/values-zh-rTW/strings.xml
  16. 3
      app/src/main/res/values-zh/strings.xml
  17. 3
      app/src/main/res/values/strings.xml

@ -155,5 +155,8 @@ let options = {
> 可直接填写链接或者JavaScript,如果执行结果是字符串链接将会自动打开浏览器 > 可直接填写链接或者JavaScript,如果执行结果是字符串链接将会自动打开浏览器
* 图片解密 * 图片解密
> 适用于图片需要二次解密的情况,直接填写JavaScript,返回解密后的bytes > 适用于图片需要二次解密的情况,直接填写JavaScript,返回解密后的`ByteArray`
> 部分变量说明:java(仅支持[js扩展类](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/help/JsExtensions.kt)),result为待解密图片的bytes,src为图片链接 > 部分变量说明:java(仅支持[js扩展类](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/help/JsExtensions.kt)),result为待解密图片的`ByteArray`,src为图片链接
* 封面解密
> 同图片解密 其中result为待解密封面的`inputStream`

@ -20,7 +20,7 @@ val appDb by lazy {
} }
@Database( @Database(
version = 53, version = 54,
exportSchema = true, exportSchema = true,
entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class, entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class,
ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class, ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class,
@ -37,7 +37,8 @@ val appDb by lazy {
AutoMigration(from = 49, to = 50), AutoMigration(from = 49, to = 50),
AutoMigration(from = 50, to = 51), AutoMigration(from = 50, to = 51),
AutoMigration(from = 51, to = 52), AutoMigration(from = 51, to = 52),
AutoMigration(from = 52, to = 53) AutoMigration(from = 52, to = 53),
AutoMigration(from = 53, to = 54),
] ]
) )
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {

@ -54,6 +54,8 @@ data class BookSource(
override var loginUi: String? = null, override var loginUi: String? = null,
// 登录检测js // 登录检测js
var loginCheckJs: String? = null, var loginCheckJs: String? = null,
// 封面解密js
var coverDecodeJs: String? = null,
// 注释 // 注释
var bookSourceComment: String? = null, var bookSourceComment: String? = null,
// 自定义变量说明 // 自定义变量说明
@ -206,6 +208,7 @@ data class BookSource(
&& equal(loginUrl, source.loginUrl) && equal(loginUrl, source.loginUrl)
&& equal(loginUi, source.loginUi) && equal(loginUi, source.loginUi)
&& equal(loginCheckJs, source.loginCheckJs) && equal(loginCheckJs, source.loginCheckJs)
&& equal(coverDecodeJs, source.coverDecodeJs)
&& equal(exploreUrl, source.exploreUrl) && equal(exploreUrl, source.exploreUrl)
&& equal(searchUrl, source.searchUrl) && equal(searchUrl, source.searchUrl)
&& getSearchRule() == source.getSearchRule() && getSearchRule() == source.getSearchRule()

@ -40,6 +40,8 @@ data class RssSource(
override var loginUi: String? = null, override var loginUi: String? = null,
//登录检测js //登录检测js
var loginCheckJs: String? = null, var loginCheckJs: String? = null,
//封面解密js
var coverDecodeJs: String? = null,
var sortUrl: String? = null, var sortUrl: String? = null,
var singleUrl: Boolean = false, var singleUrl: Boolean = false,
/*列表规则*/ /*列表规则*/
@ -58,6 +60,9 @@ data class RssSource(
var enableJs: Boolean = true, var enableJs: Boolean = true,
var loadWithBaseUrl: Boolean = true, var loadWithBaseUrl: Boolean = true,
/*其它规则*/ /*其它规则*/
// 最后更新时间,用于排序
@ColumnInfo(defaultValue = "0")
var lastUpdateTime: Long = 0,
var customOrder: Int = 0 var customOrder: Int = 0
) : Parcelable, BaseSource { ) : Parcelable, BaseSource {
@ -90,6 +95,7 @@ data class RssSource(
&& equal(loginUrl, source.loginUrl) && equal(loginUrl, source.loginUrl)
&& equal(loginUi, source.loginUi) && equal(loginUi, source.loginUi)
&& equal(loginCheckJs, source.loginCheckJs) && equal(loginCheckJs, source.loginCheckJs)
&& equal(coverDecodeJs, source.coverDecodeJs)
&& equal(sortUrl, source.sortUrl) && equal(sortUrl, source.sortUrl)
&& singleUrl == source.singleUrl && singleUrl == source.singleUrl
&& articleStyle == source.articleStyle && articleStyle == source.articleStyle
@ -174,7 +180,9 @@ data class RssSource(
enableJs = doc.readBool("$.enableJs") ?: true, enableJs = doc.readBool("$.enableJs") ?: true,
loadWithBaseUrl = doc.readBool("$.loadWithBaseUrl") ?: true, loadWithBaseUrl = doc.readBool("$.loadWithBaseUrl") ?: true,
enabledCookieJar = doc.readBool("$.enabledCookieJar") ?: false, enabledCookieJar = doc.readBool("$.enabledCookieJar") ?: false,
customOrder = doc.readInt("$.customOrder") ?: 0 customOrder = doc.readInt("$.customOrder") ?: 0,
lastUpdateTime = doc.readLong("$.lastUpdateTime") ?: 0L,
coverDecodeJs = doc.readString("$.coverDecodeJs")
) )
} }
} }

@ -136,31 +136,19 @@ object BookHelp {
downloadImages.add(src) downloadImages.add(src)
val analyzeUrl = AnalyzeUrl(src, source = bookSource) val analyzeUrl = AnalyzeUrl(src, source = bookSource)
try { try {
var bytes = analyzeUrl.getByteArrayAwait() val bytes = analyzeUrl.getByteArrayAwait()
//某些图片被加密,需要进一步解密 //某些图片被加密,需要进一步解密
bookSource?.getContentRule()?.imageDecode?.let { imageDecode -> ImageUtils.decode(
if (imageDecode.isBlank()) { src, bytes, isCover = false, bookSource, book
return@let )?.let {
} FileUtils.createFileIfNotExist(
kotlin.runCatching { downloadDir,
bookSource.evalJS(imageDecode) { cacheFolderName,
put("book", book) book.getFolderName(),
put("result", bytes) cacheImageFolderName,
put("src", src) "${MD5Utils.md5Encode16(src)}.${getImageSuffix(src)}"
} as ByteArray ).writeBytes(it)
}.onSuccess {
bytes = it
}.onFailure {
AppLog.putDebug("${src}解密bytes错误", it)
}
} }
FileUtils.createFileIfNotExist(
downloadDir,
cacheFolderName,
book.getFolderName(),
cacheImageFolderName,
"${MD5Utils.md5Encode16(src)}.${getImageSuffix(src)}"
).writeBytes(bytes)
} catch (e: Exception) { } catch (e: Exception) {
AppLog.putDebug("${src}下载错误", e) AppLog.putDebug("${src}下载错误", e)
} finally { } finally {

@ -9,10 +9,12 @@ import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.util.ContentLengthInputStream import com.bumptech.glide.util.ContentLengthInputStream
import com.bumptech.glide.util.Preconditions import com.bumptech.glide.util.Preconditions
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.BaseSource
import io.legado.app.exception.NoStackTraceException import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.http.addHeaders import io.legado.app.help.http.addHeaders
import io.legado.app.help.http.okHttpClient import io.legado.app.help.http.okHttpClient
import io.legado.app.utils.isWifiConnect import io.legado.app.utils.isWifiConnect
import io.legado.app.utils.ImageUtils
import okhttp3.Call import okhttp3.Call
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
@ -20,6 +22,7 @@ import okhttp3.ResponseBody
import splitties.init.appCtx import splitties.init.appCtx
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.ByteArrayInputStream
class OkHttpStreamFetcher(private val url: GlideUrl, private val options: Options) : class OkHttpStreamFetcher(private val url: GlideUrl, private val options: Options) :
@ -27,6 +30,7 @@ class OkHttpStreamFetcher(private val url: GlideUrl, private val options: Option
private var stream: InputStream? = null private var stream: InputStream? = null
private var responseBody: ResponseBody? = null private var responseBody: ResponseBody? = null
private var callback: DataFetcher.DataCallback<in InputStream>? = null private var callback: DataFetcher.DataCallback<in InputStream>? = null
private var source: BaseSource? = null
@Volatile @Volatile
private var call: Call? = null private var call: Call? = null
@ -40,7 +44,7 @@ class OkHttpStreamFetcher(private val url: GlideUrl, private val options: Option
val requestBuilder: Request.Builder = Request.Builder().url(url.toStringUrl()) val requestBuilder: Request.Builder = Request.Builder().url(url.toStringUrl())
val headerMap = HashMap<String, String>() val headerMap = HashMap<String, String>()
options.get(OkHttpModelLoader.sourceOriginOption)?.let { sourceUrl -> options.get(OkHttpModelLoader.sourceOriginOption)?.let { sourceUrl ->
val source = appDb.bookSourceDao.getBookSource(sourceUrl) source = appDb.bookSourceDao.getBookSource(sourceUrl)
?: appDb.rssSourceDao.getByKey(sourceUrl) ?: appDb.rssSourceDao.getByKey(sourceUrl)
source?.getHeaderMap(true)?.let { source?.getHeaderMap(true)?.let {
headerMap.putAll(it) headerMap.putAll(it)
@ -81,9 +85,17 @@ class OkHttpStreamFetcher(private val url: GlideUrl, private val options: Option
override fun onResponse(call: Call, response: Response) { override fun onResponse(call: Call, response: Response) {
responseBody = response.body responseBody = response.body
if (response.isSuccessful) { if (response.isSuccessful) {
val contentLength: Long = Preconditions.checkNotNull(responseBody).contentLength() val decodeResult = ImageUtils.decode(
stream = ContentLengthInputStream.obtain(responseBody!!.byteStream(), contentLength) url.toStringUrl(), responseBody!!.byteStream(),
callback?.onDataReady(stream) isCover = true, source
)
if (decodeResult == null) {
callback?.onLoadFailed(NoStackTraceException("封面二次解密失败"))
} else {
val contentLength: Long = if (decodeResult is ByteArrayInputStream) decodeResult.available().toLong() else Preconditions.checkNotNull(responseBody).contentLength()
stream = ContentLengthInputStream.obtain(decodeResult, contentLength)
callback?.onDataReady(stream)
}
} else { } else {
callback?.onLoadFailed(HttpException(response.message, response.code)) callback?.onLoadFailed(HttpException(response.message, response.code))
} }

@ -83,6 +83,7 @@ object SourceAnalyzer {
loginUrl = jsonItem.readString("loginUrl") loginUrl = jsonItem.readString("loginUrl")
loginUi = jsonItem.readString("loginUi") loginUi = jsonItem.readString("loginUi")
loginCheckJs = jsonItem.readString("loginCheckJs") loginCheckJs = jsonItem.readString("loginCheckJs")
coverDecodeJs = jsonItem.readString("coverDecodeJs")
bookSourceComment = jsonItem.readString("bookSourceComment") ?: "" bookSourceComment = jsonItem.readString("bookSourceComment") ?: ""
bookUrlPattern = jsonItem.readString("ruleBookUrlPattern") bookUrlPattern = jsonItem.readString("ruleBookUrlPattern")
customOrder = jsonItem.readInt("serialNumber") ?: 0 customOrder = jsonItem.readInt("serialNumber") ?: 0
@ -165,6 +166,7 @@ object SourceAnalyzer {
sourceAny.loginUi?.toString() sourceAny.loginUi?.toString()
} }
source.loginCheckJs = sourceAny.loginCheckJs source.loginCheckJs = sourceAny.loginCheckJs
source.coverDecodeJs = sourceAny.coverDecodeJs
source.bookSourceComment = sourceAny.bookSourceComment source.bookSourceComment = sourceAny.bookSourceComment
source.variableComment = sourceAny.variableComment source.variableComment = sourceAny.variableComment
source.lastUpdateTime = sourceAny.lastUpdateTime source.lastUpdateTime = sourceAny.lastUpdateTime
@ -236,6 +238,7 @@ object SourceAnalyzer {
var loginUrl: Any? = null, // 登录规则 var loginUrl: Any? = null, // 登录规则
var loginUi: Any? = null, // 登录UI var loginUi: Any? = null, // 登录UI
var loginCheckJs: String? = null, // 登录检测js var loginCheckJs: String? = null, // 登录检测js
var coverDecodeJs: String? = null, // 封面解密js
var bookSourceComment: String? = "", // 书源注释 var bookSourceComment: String? = "", // 书源注释
var variableComment: String? = null, // 变量说明 var variableComment: String? = null, // 变量说明
var lastUpdateTime: Long = 0, // 最后更新时间,用于排序 var lastUpdateTime: Long = 0, // 最后更新时间,用于排序

@ -221,6 +221,7 @@ class BookSourceEditActivity :
add(EditEntity("loginUrl", source?.loginUrl, R.string.login_url)) add(EditEntity("loginUrl", source?.loginUrl, R.string.login_url))
add(EditEntity("loginUi", source?.loginUi, R.string.login_ui)) add(EditEntity("loginUi", source?.loginUi, R.string.login_ui))
add(EditEntity("loginCheckJs", source?.loginCheckJs, R.string.login_check_js)) add(EditEntity("loginCheckJs", source?.loginCheckJs, R.string.login_check_js))
add(EditEntity("coverDecodeJs", source?.coverDecodeJs, R.string.cover_decode_js))
add(EditEntity("bookUrlPattern", source?.bookUrlPattern, R.string.book_url_pattern)) add(EditEntity("bookUrlPattern", source?.bookUrlPattern, R.string.book_url_pattern))
add(EditEntity("header", source?.header, R.string.source_http_header)) add(EditEntity("header", source?.header, R.string.source_http_header))
add(EditEntity("variableComment", source?.variableComment, R.string.variable_comment)) add(EditEntity("variableComment", source?.variableComment, R.string.variable_comment))
@ -345,6 +346,7 @@ class BookSourceEditActivity :
"loginUrl" -> source.loginUrl = it.value "loginUrl" -> source.loginUrl = it.value
"loginUi" -> source.loginUi = it.value "loginUi" -> source.loginUi = it.value
"loginCheckJs" -> source.loginCheckJs = it.value "loginCheckJs" -> source.loginCheckJs = it.value
"coverDecodeJs" -> source.coverDecodeJs = it.value
"bookUrlPattern" -> source.bookUrlPattern = it.value "bookUrlPattern" -> source.bookUrlPattern = it.value
"header" -> source.header = it.value "header" -> source.header = it.value
"bookSourceComment" -> source.bookSourceComment = it.value "bookSourceComment" -> source.bookSourceComment = it.value

@ -169,6 +169,7 @@ class RssSourceEditActivity :
add(EditEntity("loginUrl", source?.loginUrl, R.string.login_url)) add(EditEntity("loginUrl", source?.loginUrl, R.string.login_url))
add(EditEntity("loginUi", source?.loginUi, R.string.login_ui)) add(EditEntity("loginUi", source?.loginUi, R.string.login_ui))
add(EditEntity("loginCheckJs", source?.loginCheckJs, R.string.login_check_js)) add(EditEntity("loginCheckJs", source?.loginCheckJs, R.string.login_check_js))
add(EditEntity("coverDecodeJs", source?.coverDecodeJs, R.string.cover_decode_js))
add(EditEntity("header", source?.header, R.string.source_http_header)) add(EditEntity("header", source?.header, R.string.source_http_header))
add(EditEntity("variableComment", source?.variableComment, R.string.variable_comment)) add(EditEntity("variableComment", source?.variableComment, R.string.variable_comment))
add(EditEntity("concurrentRate", source?.concurrentRate, R.string.concurrent_rate)) add(EditEntity("concurrentRate", source?.concurrentRate, R.string.concurrent_rate))
@ -203,6 +204,7 @@ class RssSourceEditActivity :
"loginUrl" -> source.loginUrl = it.value "loginUrl" -> source.loginUrl = it.value
"loginUi" -> source.loginUi = it.value "loginUi" -> source.loginUi = it.value
"loginCheckJs" -> source.loginCheckJs = it.value "loginCheckJs" -> source.loginCheckJs = it.value
"coverDecodeJs" -> source.coverDecodeJs = it.value
"header" -> source.header = it.value "header" -> source.header = it.value
"variableComment" -> source.variableComment = it.value "variableComment" -> source.variableComment = it.value
"concurrentRate" -> source.concurrentRate = it.value "concurrentRate" -> source.concurrentRate = it.value

@ -0,0 +1,69 @@
package io.legado.app.utils
import io.legado.app.constant.AppLog
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BaseSource
import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.RssSource
import java.io.InputStream
import java.io.ByteArrayInputStream
/**
* 加密图片解密工具
*/
object ImageUtils {
/**
* @param isCover 根据这个执行书源中不同的解密规则
* @return 解密失败返回Null 解密规则为空不处理
*/
fun decode(
src: String, bytes: ByteArray, isCover: Boolean,
source: BaseSource?, book: Book? = null
): ByteArray? {
val ruleJs = getRuleJs(source, isCover)
if (ruleJs.isNullOrBlank()) return bytes
//解密库hutool.crypto ByteArray|InputStream -> ByteArray
return kotlin.runCatching {
source?.evalJS(ruleJs) {
put("book", book)
put("result", bytes)
put("src", src)
} as ByteArray
}.onFailure {
AppLog.putDebug("${src}解密错误", it)
}.getOrNull()
}
fun decode(
src: String, inputStream: InputStream, isCover: Boolean,
source: BaseSource?, book: Book? = null
): InputStream? {
val ruleJs = getRuleJs(source, isCover)
if (ruleJs.isNullOrBlank()) return inputStream
//解密库hutool.crypto ByteArray|InputStream -> ByteArray
return kotlin.runCatching {
val bytes = source?.evalJS(ruleJs) {
put("book", book)
put("result", inputStream)
put("src", src)
} as ByteArray
ByteArrayInputStream(bytes)
}.onFailure {
AppLog.putDebug("${src}解密错误", it)
}.getOrNull()
}
private fun getRuleJs(
source: BaseSource?, isCover: Boolean
): String? {
return when (source) {
is BookSource ->
if (isCover) source?.coverDecodeJs else source?.getContentRule()?.imageDecode
is RssSource -> source?.coverDecodeJs
else -> null
}
}
}

@ -872,7 +872,7 @@
<string name="anti_alias">Anti-Aliasing</string> <string name="anti_alias">Anti-Aliasing</string>
<string name="pref_anti_alias_summary">Anti-Aliasing when draw picture</string> <string name="pref_anti_alias_summary">Anti-Aliasing when draw picture</string>
<string name="upload_url">上传URL</string> <string name="upload_url">上传URL</string>
<string name="download_url_rule">下载URL规则</string> <string name="download_url_rule">downloadUrlRule(downloadUrls)</string>
<string name="sort_by_respondTime">Ordenar por tiempo de respuesta</string> <string name="sort_by_respondTime">Ordenar por tiempo de respuesta</string>
<string name="export_success">导出成功</string> <string name="export_success">导出成功</string>
<string name="path">路径</string> <string name="path">路径</string>
@ -1032,4 +1032,5 @@
<string name="ignore_audio_focus_title">忽略音频焦点</string> <string name="ignore_audio_focus_title">忽略音频焦点</string>
<string name="ignore_audio_focus_summary">允许与其他应用同时播放音频</string> <string name="ignore_audio_focus_summary">允许与其他应用同时播放音频</string>
<string name="refresh_sort">刷新分类</string> <string name="refresh_sort">刷新分类</string>
<string name="cover_decode_js">Decode Cover Js(coverDecodeJs)</string>
</resources> </resources>

@ -875,7 +875,7 @@
<string name="anti_alias">Anti-Aliasing</string> <string name="anti_alias">Anti-Aliasing</string>
<string name="pref_anti_alias_summary">Anti-Aliasing when draw picture</string> <string name="pref_anti_alias_summary">Anti-Aliasing when draw picture</string>
<string name="upload_url">上传URL</string> <string name="upload_url">上传URL</string>
<string name="download_url_rule">下载URL规则</string> <string name="download_url_rule">downloadUrlRule(downloadUrls)</string>
<string name="sort_by_respondTime">Sort by respond time</string> <string name="sort_by_respondTime">Sort by respond time</string>
<string name="export_success">导出成功</string> <string name="export_success">导出成功</string>
<string name="path">路径</string> <string name="path">路径</string>
@ -1035,4 +1035,5 @@
<string name="ignore_audio_focus_title">忽略音频焦点</string> <string name="ignore_audio_focus_title">忽略音频焦点</string>
<string name="ignore_audio_focus_summary">允许与其他应用同时播放音频</string> <string name="ignore_audio_focus_summary">允许与其他应用同时播放音频</string>
<string name="refresh_sort">刷新分类</string> <string name="refresh_sort">刷新分类</string>
<string name="cover_decode_js">Decode Cover Js(coverDecodeJs)</string>
</resources> </resources>

@ -873,7 +873,7 @@
<string name="anti_alias">Anti-Aliasing</string> <string name="anti_alias">Anti-Aliasing</string>
<string name="pref_anti_alias_summary">Anti-Aliasing when draw picture</string> <string name="pref_anti_alias_summary">Anti-Aliasing when draw picture</string>
<string name="upload_url">上传URL</string> <string name="upload_url">上传URL</string>
<string name="download_url_rule">下载URL规则</string> <string name="download_url_rule">downloadUrlRule(downloadUrls)</string>
<string name="sort_by_respondTime">Classificar por tempo de resposta</string> <string name="sort_by_respondTime">Classificar por tempo de resposta</string>
<string name="export_success">导出成功</string> <string name="export_success">导出成功</string>
<string name="path">路径</string> <string name="path">路径</string>
@ -1035,4 +1035,5 @@
<string name="ignore_audio_focus_title">忽略音频焦点</string> <string name="ignore_audio_focus_title">忽略音频焦点</string>
<string name="ignore_audio_focus_summary">允许与其他应用同时播放音频</string> <string name="ignore_audio_focus_summary">允许与其他应用同时播放音频</string>
<string name="refresh_sort">刷新分类</string> <string name="refresh_sort">刷新分类</string>
<string name="cover_decode_js">Decode Cover Js(coverDecodeJs)</string>
</resources> </resources>

@ -872,7 +872,7 @@
<string name="anti_alias">抗鋸齒</string> <string name="anti_alias">抗鋸齒</string>
<string name="pref_anti_alias_summary">繪製圖片時抗鋸齒</string> <string name="pref_anti_alias_summary">繪製圖片時抗鋸齒</string>
<string name="upload_url">上傳URL</string> <string name="upload_url">上傳URL</string>
<string name="download_url_rule">載URL規則</string> <string name="download_url_rule">载URL规则(downloadUrls)</string>
<string name="sort_by_respondTime">響應時間排序</string> <string name="sort_by_respondTime">響應時間排序</string>
<string name="export_success">導出成功</string> <string name="export_success">導出成功</string>
<string name="path">路徑</string> <string name="path">路徑</string>
@ -1032,4 +1032,5 @@
<string name="ignore_audio_focus_title">忽略音频焦点</string> <string name="ignore_audio_focus_title">忽略音频焦点</string>
<string name="ignore_audio_focus_summary">允许与其他应用同时播放音频</string> <string name="ignore_audio_focus_summary">允许与其他应用同时播放音频</string>
<string name="refresh_sort">刷新分类</string> <string name="refresh_sort">刷新分类</string>
<string name="cover_decode_js">封面解密(coverDecodeJs)</string>
</resources> </resources>

@ -874,7 +874,7 @@
<string name="anti_alias">抗鋸齒</string> <string name="anti_alias">抗鋸齒</string>
<string name="pref_anti_alias_summary">繪製圖片時抗鋸齒</string> <string name="pref_anti_alias_summary">繪製圖片時抗鋸齒</string>
<string name="upload_url">上傳URL</string> <string name="upload_url">上傳URL</string>
<string name="download_url_rule">載URL規則</string> <string name="download_url_rule">载URL规则(downloadUrls)</string>
<string name="sort_by_respondTime">反應時間排序</string> <string name="sort_by_respondTime">反應時間排序</string>
<string name="export_success">匯出成功</string> <string name="export_success">匯出成功</string>
<string name="path">路徑</string> <string name="path">路徑</string>
@ -1034,4 +1034,5 @@
<string name="ignore_audio_focus_title">忽略音频焦点</string> <string name="ignore_audio_focus_title">忽略音频焦点</string>
<string name="ignore_audio_focus_summary">允许与其他应用同时播放音频</string> <string name="ignore_audio_focus_summary">允许与其他应用同时播放音频</string>
<string name="refresh_sort">刷新分类</string> <string name="refresh_sort">刷新分类</string>
<string name="cover_decode_js">封面解密(coverDecodeJs)</string>
</resources> </resources>

@ -876,7 +876,7 @@
<string name="anti_alias">抗锯齿</string> <string name="anti_alias">抗锯齿</string>
<string name="pref_anti_alias_summary">绘制图片时抗锯齿</string> <string name="pref_anti_alias_summary">绘制图片时抗锯齿</string>
<string name="upload_url">上传 URL</string> <string name="upload_url">上传 URL</string>
<string name="download_url_rule">下载 URL 规则</string> <string name="download_url_rule">下载URL规则(downloadUrls)</string>
<string name="sort_by_respondTime">响应时间排序</string> <string name="sort_by_respondTime">响应时间排序</string>
<string name="export_success">导出成功</string> <string name="export_success">导出成功</string>
<string name="path">路径</string> <string name="path">路径</string>
@ -1034,4 +1034,5 @@
<string name="ignore_audio_focus_title">忽略音频焦点</string> <string name="ignore_audio_focus_title">忽略音频焦点</string>
<string name="ignore_audio_focus_summary">允许与其他应用同时播放音频</string> <string name="ignore_audio_focus_summary">允许与其他应用同时播放音频</string>
<string name="refresh_sort">刷新分类</string> <string name="refresh_sort">刷新分类</string>
<string name="cover_decode_js">封面解密(coverDecodeJs)</string>
</resources> </resources>

@ -877,7 +877,7 @@
<string name="anti_alias">Anti-Aliasing</string> <string name="anti_alias">Anti-Aliasing</string>
<string name="pref_anti_alias_summary">Anti-Aliasing when draw picture</string> <string name="pref_anti_alias_summary">Anti-Aliasing when draw picture</string>
<string name="upload_url">upload url</string> <string name="upload_url">upload url</string>
<string name="download_url_rule">下载URL规则</string> <string name="download_url_rule">downloadUrlRule(downloadUrls)</string>
<string name="export_success">export success</string> <string name="export_success">export success</string>
<string name="path">path</string> <string name="path">path</string>
<string name="direct_link_upload_rule">直链上传规则</string> <string name="direct_link_upload_rule">直链上传规则</string>
@ -1035,4 +1035,5 @@
<string name="ignore_audio_focus_title">忽略音频焦点</string> <string name="ignore_audio_focus_title">忽略音频焦点</string>
<string name="ignore_audio_focus_summary">允许与其他应用同时播放音频</string> <string name="ignore_audio_focus_summary">允许与其他应用同时播放音频</string>
<string name="refresh_sort">刷新分类</string> <string name="refresh_sort">刷新分类</string>
<string name="cover_decode_js">Decode Cover Js(coverDecodeJs)</string>
</resources> </resources>

Loading…
Cancel
Save