| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002 |
- package io.legato.kazusa.help
- import android.webkit.WebSettings
- import androidx.annotation.Keep
- import cn.hutool.core.codec.Base64
- import cn.hutool.core.util.HexUtil
- import com.script.rhino.rhinoContext
- import com.script.rhino.rhinoContextOrNull
- import io.legato.kazusa.constant.AppConst
- import io.legato.kazusa.constant.AppConst.dateFormat
- import io.legato.kazusa.constant.AppLog
- import io.legato.kazusa.constant.AppPattern
- import io.legato.kazusa.data.entities.BaseSource
- import io.legato.kazusa.exception.NoStackTraceException
- import io.legato.kazusa.help.config.AppConfig
- import io.legato.kazusa.help.http.BackstageWebView
- import io.legato.kazusa.help.http.CookieManager.cookieJarHeader
- import io.legato.kazusa.help.http.CookieStore
- import io.legato.kazusa.help.http.SSLHelper
- import io.legato.kazusa.help.http.StrResponse
- import io.legato.kazusa.help.source.SourceVerificationHelp
- import io.legato.kazusa.help.source.getSourceType
- import io.legato.kazusa.model.Debug
- import io.legato.kazusa.model.analyzeRule.AnalyzeUrl
- import io.legato.kazusa.model.analyzeRule.QueryTTF
- import io.legato.kazusa.ui.association.OpenUrlConfirmActivity
- import io.legato.kazusa.utils.ArchiveUtils
- import io.legato.kazusa.utils.ChineseUtils
- import io.legato.kazusa.utils.EncoderUtils
- import io.legato.kazusa.utils.EncodingDetect
- import io.legato.kazusa.utils.FileUtils
- import io.legato.kazusa.utils.GSON
- import io.legato.kazusa.utils.HtmlFormatter
- import io.legato.kazusa.utils.JsURL
- import io.legato.kazusa.utils.MD5Utils
- import io.legato.kazusa.utils.StringUtils
- import io.legato.kazusa.utils.UrlUtil
- import io.legato.kazusa.utils.compress.LibArchiveUtils
- import io.legato.kazusa.utils.createFileReplace
- import io.legato.kazusa.utils.externalCache
- import io.legato.kazusa.utils.fromJsonObject
- import io.legato.kazusa.utils.isAbsUrl
- import io.legato.kazusa.utils.isMainThread
- import io.legato.kazusa.utils.longToastForJs
- import io.legato.kazusa.utils.mapAsync
- import io.legato.kazusa.utils.stackTraceStr
- import io.legato.kazusa.utils.startActivity
- import io.legato.kazusa.utils.toStringArray
- import io.legato.kazusa.utils.toastForJs
- import kotlinx.coroutines.Dispatchers.IO
- import kotlinx.coroutines.flow.asFlow
- import kotlinx.coroutines.flow.flowOn
- import kotlinx.coroutines.flow.toList
- import kotlinx.coroutines.runBlocking
- import okio.use
- import org.jsoup.Connection
- import org.jsoup.Jsoup
- import splitties.init.appCtx
- import java.io.ByteArrayInputStream
- import java.io.ByteArrayOutputStream
- import java.io.File
- import java.net.URLEncoder
- import java.nio.charset.Charset
- import java.security.MessageDigest
- import java.text.SimpleDateFormat
- import java.util.Date
- import java.util.Locale
- import java.util.SimpleTimeZone
- import java.util.UUID
- import java.util.zip.ZipEntry
- import java.util.zip.ZipInputStream
- import kotlin.coroutines.CoroutineContext
- import kotlin.coroutines.EmptyCoroutineContext
- /**
- * js扩展类, 在js中通过java变量调用
- * 添加方法,请更新文档/legado/app/src/main/assets/help/JsHelp.md
- * 所有对于文件的读写删操作都是相对路径,只能操作阅读缓存内的文件
- * /android/data/{package}/cache/...
- */
- @Keep
- @Suppress("unused")
- interface JsExtensions : JsEncodeUtils {
- fun getSource(): BaseSource?
- private val context: CoroutineContext
- get() = rhinoContext.coroutineContext ?: EmptyCoroutineContext
- /**
- * 访问网络,返回String
- */
- fun ajax(url: Any): String? {
- val urlStr = if (url is List<*>) {
- url.firstOrNull().toString()
- } else {
- url.toString()
- }
- val analyzeUrl = AnalyzeUrl(urlStr, source = getSource(), coroutineContext = context)
- return kotlin.runCatching {
- analyzeUrl.getStrResponse().body
- }.onFailure {
- rhinoContext.ensureActive()
- AppLog.put("ajax(${urlStr}) error\n${it.localizedMessage}", it)
- }.getOrElse {
- it.stackTraceStr
- }
- }
- /**
- * 并发访问网络
- */
- fun ajaxAll(urlList: Array<String>): Array<StrResponse> {
- return runBlocking(context) {
- urlList.asFlow().mapAsync(AppConfig.threadCount) { url ->
- val analyzeUrl = AnalyzeUrl(
- url,
- source = getSource(),
- coroutineContext = coroutineContext
- )
- analyzeUrl.getStrResponseAwait()
- }.flowOn(IO).toList().toTypedArray()
- }
- }
- /**
- * 访问网络,返回Response<String>
- */
- fun connect(urlStr: String): StrResponse {
- val analyzeUrl = AnalyzeUrl(
- urlStr,
- source = getSource(),
- coroutineContext = context
- )
- return kotlin.runCatching {
- analyzeUrl.getStrResponse()
- }.onFailure {
- rhinoContext.ensureActive()
- AppLog.put("connect(${urlStr}) error\n${it.localizedMessage}", it)
- }.getOrElse {
- StrResponse(analyzeUrl.url, it.stackTraceStr)
- }
- }
- fun connect(urlStr: String, header: String?): StrResponse {
- val headerMap = GSON.fromJsonObject<Map<String, String>>(header).getOrNull()
- val analyzeUrl = AnalyzeUrl(
- urlStr,
- headerMapF = headerMap,
- source = getSource(),
- coroutineContext = context
- )
- return kotlin.runCatching {
- analyzeUrl.getStrResponse()
- }.onFailure {
- rhinoContext.ensureActive()
- AppLog.put("ajax($urlStr,$header) error\n${it.localizedMessage}", it)
- }.getOrElse {
- StrResponse(analyzeUrl.url, it.stackTraceStr)
- }
- }
- /**
- * 使用webView访问网络
- * @param html 直接用webView载入的html, 如果html为空直接访问url
- * @param url html内如果有相对路径的资源不传入url访问不了
- * @param js 用来取返回值的js语句, 没有就返回整个源代码
- * @return 返回js获取的内容
- */
- fun webView(html: String?, url: String?, js: String?): String? {
- if (isMainThread) {
- error("webView must be called on a background thread")
- }
- return runBlocking(context) {
- BackstageWebView(
- url = url,
- html = html,
- javaScript = js,
- headerMap = getSource()?.getHeaderMap(true),
- tag = getSource()?.getKey()
- ).getStrResponse().body
- }
- }
- /**
- * 使用webView获取资源url
- */
- fun webViewGetSource(html: String?, url: String?, js: String?, sourceRegex: String): String? {
- if (isMainThread) {
- error("webViewGetSource must be called on a background thread")
- }
- return runBlocking(context) {
- BackstageWebView(
- url = url,
- html = html,
- javaScript = js,
- headerMap = getSource()?.getHeaderMap(true),
- tag = getSource()?.getKey(),
- sourceRegex = sourceRegex
- ).getStrResponse().body
- }
- }
- /**
- * 使用webView获取跳转url
- */
- fun webViewGetOverrideUrl(
- html: String?,
- url: String?,
- js: String?,
- overrideUrlRegex: String
- ): String? {
- if (isMainThread) {
- error("webViewGetOverrideUrl must be called on a background thread")
- }
- return runBlocking(context) {
- BackstageWebView(
- url = url,
- html = html,
- javaScript = js,
- headerMap = getSource()?.getHeaderMap(true),
- tag = getSource()?.getKey(),
- overrideUrlRegex = overrideUrlRegex
- ).getStrResponse().body
- }
- }
- /**
- * 使用内置浏览器打开链接,手动验证网站防爬
- * @param url 要打开的链接
- * @param title 浏览器页面的标题
- */
- fun startBrowser(url: String, title: String) {
- rhinoContext.ensureActive()
- SourceVerificationHelp.startBrowser(getSource(), url, title)
- }
- /**
- * 使用内置浏览器打开链接,并等待网页结果
- */
- fun startBrowserAwait(url: String, title: String, refetchAfterSuccess: Boolean): StrResponse {
- rhinoContext.ensureActive()
- val body = SourceVerificationHelp.getVerificationResult(
- getSource(), url, title, true, refetchAfterSuccess
- )
- return StrResponse(url, body)
- }
- fun startBrowserAwait(url: String, title: String): StrResponse {
- return startBrowserAwait(url, title, true)
- }
- /**
- * 打开图片验证码对话框,等待返回验证结果
- */
- fun getVerificationCode(imageUrl: String): String {
- rhinoContext.ensureActive()
- return SourceVerificationHelp.getVerificationResult(getSource(), imageUrl, "", false)
- }
- /**
- * 可从网络,本地文件(阅读私有数据目录相对路径)导入JavaScript脚本
- */
- fun importScript(path: String): String {
- val result = when {
- path.startsWith("http") -> cacheFile(path)
- else -> readTxtFile(path)
- }
- if (result.isBlank()) throw NoStackTraceException("$path 内容获取失败或者为空")
- return result
- }
- /**
- * 缓存以文本方式保存的文件 如.js .txt等
- * @param urlStr 网络文件的链接
- * @return 返回缓存后的文件内容
- */
- fun cacheFile(urlStr: String): String {
- return cacheFile(urlStr, 0)
- }
- /**
- * 缓存以文本方式保存的文件 如.js .txt等
- * @param saveTime 缓存时间,单位:秒
- */
- fun cacheFile(urlStr: String, saveTime: Int): String {
- val key = md5Encode16(urlStr)
- val cachePath = CacheManager.get(key)
- return if (
- cachePath.isNullOrBlank() ||
- !getFile(cachePath).exists()
- ) {
- val path = downloadFile(urlStr)
- log("首次下载 $urlStr >> $path")
- CacheManager.put(key, path, saveTime)
- readTxtFile(path)
- } else {
- readTxtFile(cachePath)
- }
- }
- /**
- *js实现读取cookie
- */
- fun getCookie(tag: String): String {
- return getCookie(tag, null)
- }
- fun getCookie(tag: String, key: String?): String {
- return if (key != null) {
- CookieStore.getKey(tag, key)
- } else {
- CookieStore.getCookie(tag)
- }
- }
- /**
- * 下载文件
- * @param url 下载地址:可带参数type
- * @return 下载的文件相对路径
- */
- fun downloadFile(url: String): String {
- rhinoContext.ensureActive()
- val analyzeUrl = AnalyzeUrl(url, source = getSource(), coroutineContext = context)
- val type = UrlUtil.getSuffix(url, analyzeUrl.type)
- val path = FileUtils.getPath(
- File(FileUtils.getCachePath()),
- "${MD5Utils.md5Encode16(url)}.${type}"
- )
- val file = File(path)
- file.delete()
- analyzeUrl.getInputStream().use { iStream ->
- file.createFileReplace()
- try {
- file.outputStream().buffered().use { oStream ->
- iStream.copyTo(oStream)
- }
- } catch (e: Throwable) {
- file.delete()
- throw e
- }
- }
- return path.substring(FileUtils.getCachePath().length)
- }
- /**
- * 实现16进制字符串转文件
- * @param content 需要转成文件的16进制字符串
- * @param url 通过url里的参数来判断文件类型
- * @return 相对路径
- */
- @Deprecated(
- "Deprecated",
- ReplaceWith("downloadFile(url)")
- )
- fun downloadFile(content: String, url: String): String {
- rhinoContext.ensureActive()
- val type = AnalyzeUrl(url, source = getSource(), coroutineContext = context).type
- ?: return ""
- val path = FileUtils.getPath(
- FileUtils.createFolderIfNotExist(FileUtils.getCachePath()),
- "${MD5Utils.md5Encode16(url)}.${type}"
- )
- val file = File(path)
- file.createFileReplace()
- HexUtil.decodeHex(content).let {
- if (it.isNotEmpty()) {
- file.writeBytes(it)
- }
- }
- return path.substring(FileUtils.getCachePath().length)
- }
- /**
- * js实现重定向拦截,网络访问get
- */
- fun get(urlStr: String, headers: Map<String, String>): Connection.Response {
- val requestHeaders = if (getSource()?.enabledCookieJar == true) {
- headers.toMutableMap().apply { put(cookieJarHeader, "1") }
- } else headers
- val rateLimiter = ConcurrentRateLimiter(getSource())
- val response = rateLimiter.withLimitBlocking {
- rhinoContext.ensureActive()
- Jsoup.connect(urlStr)
- .sslSocketFactory(SSLHelper.unsafeSSLSocketFactory)
- .ignoreContentType(true)
- .followRedirects(false)
- .headers(requestHeaders)
- .method(Connection.Method.GET)
- .execute()
- }
- return response
- }
- /**
- * js实现重定向拦截,网络访问head,不返回Response Body更省流量
- */
- fun head(urlStr: String, headers: Map<String, String>): Connection.Response {
- val requestHeaders = if (getSource()?.enabledCookieJar == true) {
- headers.toMutableMap().apply { put(cookieJarHeader, "1") }
- } else headers
- val rateLimiter = ConcurrentRateLimiter(getSource())
- val response = rateLimiter.withLimitBlocking {
- rhinoContext.ensureActive()
- Jsoup.connect(urlStr)
- .sslSocketFactory(SSLHelper.unsafeSSLSocketFactory)
- .ignoreContentType(true)
- .followRedirects(false)
- .headers(requestHeaders)
- .method(Connection.Method.HEAD)
- .execute()
- }
- return response
- }
- /**
- * 网络访问post
- */
- fun post(urlStr: String, body: String, headers: Map<String, String>): Connection.Response {
- val requestHeaders = if (getSource()?.enabledCookieJar == true) {
- headers.toMutableMap().apply { put(cookieJarHeader, "1") }
- } else headers
- val rateLimiter = ConcurrentRateLimiter(getSource())
- val response = rateLimiter.withLimitBlocking {
- rhinoContext.ensureActive()
- Jsoup.connect(urlStr)
- .sslSocketFactory(SSLHelper.unsafeSSLSocketFactory)
- .ignoreContentType(true)
- .followRedirects(false)
- .requestBody(body)
- .headers(requestHeaders)
- .method(Connection.Method.POST)
- .execute()
- }
- return response
- }
- /* Str转ByteArray */
- fun strToBytes(str: String): ByteArray {
- return str.toByteArray(charset("UTF-8"))
- }
- fun strToBytes(str: String, charset: String): ByteArray {
- return str.toByteArray(charset(charset))
- }
- /* ByteArray转Str */
- fun bytesToStr(bytes: ByteArray): String {
- return String(bytes, charset("UTF-8"))
- }
- fun bytesToStr(bytes: ByteArray, charset: String): String {
- return String(bytes, charset(charset))
- }
- /**
- * js实现base64解码,不能删
- */
- fun base64Decode(str: String?): String {
- return Base64.decodeStr(str)
- }
- fun base64Decode(str: String?, charset: String): String {
- return Base64.decodeStr(str, charset(charset))
- }
- fun base64Decode(str: String, flags: Int): String {
- return EncoderUtils.base64Decode(str, flags)
- }
- fun base64DecodeToByteArray(str: String?): ByteArray? {
- if (str.isNullOrBlank()) {
- return null
- }
- return EncoderUtils.base64DecodeToByteArray(str, 0)
- }
- fun base64DecodeToByteArray(str: String?, flags: Int): ByteArray? {
- if (str.isNullOrBlank()) {
- return null
- }
- return EncoderUtils.base64DecodeToByteArray(str, flags)
- }
- fun base64Encode(str: String): String? {
- return EncoderUtils.base64Encode(str, 2)
- }
- fun base64Encode(str: String, flags: Int): String? {
- return EncoderUtils.base64Encode(str, flags)
- }
- /* HexString 解码为字节数组 */
- fun hexDecodeToByteArray(hex: String): ByteArray? {
- return HexUtil.decodeHex(hex)
- }
- /* hexString 解码为utf8String*/
- fun hexDecodeToString(hex: String): String? {
- return HexUtil.decodeHexStr(hex)
- }
- /* utf8 编码为hexString */
- fun hexEncodeToString(utf8: String): String? {
- return HexUtil.encodeHexStr(utf8)
- }
- /**
- * 格式化时间
- */
- fun timeFormatUTC(time: Long, format: String, sh: Int): String? {
- val utc = SimpleTimeZone(sh, "UTC")
- return SimpleDateFormat(format, Locale.getDefault()).run {
- timeZone = utc
- format(Date(time))
- }
- }
- /**
- * 时间格式化
- */
- fun timeFormat(time: Long): String {
- return dateFormat.format(Date(time))
- }
- fun encodeURI(str: String): String {
- return try {
- URLEncoder.encode(str, "UTF-8")
- } catch (e: Exception) {
- ""
- }
- }
- fun encodeURI(str: String, enc: String): String {
- return try {
- URLEncoder.encode(str, enc)
- } catch (e: Exception) {
- ""
- }
- }
- fun htmlFormat(str: String): String {
- return HtmlFormatter.formatKeepImg(str)
- }
- fun t2s(text: String): String {
- return ChineseUtils.t2s(text)
- }
- fun s2t(text: String): String {
- return ChineseUtils.s2t(text)
- }
- fun getWebViewUA(): String {
- return WebSettings.getDefaultUserAgent(appCtx)
- }
- //****************文件操作******************//
- /**
- * 获取本地文件
- * @param path 相对路径
- * @return File
- */
- fun getFile(path: String): File {
- val cachePath = appCtx.externalCache.absolutePath
- val aPath = if (path.startsWith(File.separator)) {
- cachePath + path
- } else {
- cachePath + File.separator + path
- }
- val file = File(aPath)
- val safePath = appCtx.externalCache.parent!!
- if (!file.canonicalPath.startsWith(safePath)) {
- throw SecurityException("非法路径")
- }
- return file
- }
- fun readFile(path: String): ByteArray? {
- val file = getFile(path)
- if (file.exists()) {
- return file.readBytes()
- }
- return null
- }
- fun readTxtFile(path: String): String {
- val file = getFile(path)
- if (file.exists()) {
- val charsetName = EncodingDetect.getEncode(file)
- return String(file.readBytes(), charset(charsetName))
- }
- return ""
- }
- fun readTxtFile(path: String, charsetName: String): String {
- val file = getFile(path)
- if (file.exists()) {
- return String(file.readBytes(), charset(charsetName))
- }
- return ""
- }
- /**
- * 删除本地文件
- */
- fun deleteFile(path: String): Boolean {
- val file = getFile(path)
- return FileUtils.delete(file, true)
- }
- /**
- * js实现Zip压缩文件解压
- * @param zipPath 相对路径
- * @return 相对路径
- */
- fun unzipFile(zipPath: String): String {
- return unArchiveFile(zipPath)
- }
- /**
- * js实现7Zip压缩文件解压
- * @param zipPath 相对路径
- * @return 相对路径
- */
- fun un7zFile(zipPath: String): String {
- return unArchiveFile(zipPath)
- }
- /**
- * js实现Rar压缩文件解压
- * @param zipPath 相对路径
- * @return 相对路径
- */
- fun unrarFile(zipPath: String): String {
- return unArchiveFile(zipPath)
- }
- /**
- * js实现压缩文件解压
- * @param zipPath 相对路径
- * @return 相对路径
- */
- fun unArchiveFile(zipPath: String): String {
- if (zipPath.isEmpty()) return ""
- val zipFile = getFile(zipPath)
- return ArchiveUtils.deCompress(zipFile.absolutePath).let {
- ArchiveUtils.TEMP_FOLDER_NAME + File.separator + MD5Utils.md5Encode16(zipFile.name)
- }
- }
- /**
- * js实现文件夹内所有文本文件读取
- * @param path 文件夹相对路径
- * @return 所有文件字符串换行连接
- */
- fun getTxtInFolder(path: String): String {
- if (path.isEmpty()) return ""
- val folder = getFile(path)
- val contents = StringBuilder()
- folder.listFiles().let {
- if (it != null) {
- for (f in it) {
- val charsetName = EncodingDetect.getEncode(f)
- contents.append(String(f.readBytes(), charset(charsetName)))
- .append("\n")
- }
- contents.deleteCharAt(contents.length - 1)
- }
- }
- FileUtils.delete(folder.absolutePath)
- return contents.toString()
- }
- /**
- * 获取网络zip文件里面的数据
- * @param url zip文件的链接或十六进制字符串
- * @param path 所需获取文件在zip内的路径
- * @return zip指定文件的数据
- */
- fun getZipStringContent(url: String, path: String): String {
- val byteArray = getZipByteArrayContent(url, path) ?: return ""
- val charsetName = EncodingDetect.getEncode(byteArray)
- return String(byteArray, Charset.forName(charsetName))
- }
- fun getZipStringContent(url: String, path: String, charsetName: String): String {
- val byteArray = getZipByteArrayContent(url, path) ?: return ""
- return String(byteArray, Charset.forName(charsetName))
- }
- /**
- * 获取网络zip文件里面的数据
- * @param url zip文件的链接或十六进制字符串
- * @param path 所需获取文件在zip内的路径
- * @return zip指定文件的数据
- */
- fun getRarStringContent(url: String, path: String): String {
- val byteArray = getRarByteArrayContent(url, path) ?: return ""
- val charsetName = EncodingDetect.getEncode(byteArray)
- return String(byteArray, Charset.forName(charsetName))
- }
- fun getRarStringContent(url: String, path: String, charsetName: String): String {
- val byteArray = getRarByteArrayContent(url, path) ?: return ""
- return String(byteArray, Charset.forName(charsetName))
- }
- /**
- * 获取网络7zip文件里面的数据
- * @param url 7zip文件的链接或十六进制字符串
- * @param path 所需获取文件在7zip内的路径
- * @return zip指定文件的数据
- */
- fun get7zStringContent(url: String, path: String): String {
- val byteArray = get7zByteArrayContent(url, path) ?: return ""
- val charsetName = EncodingDetect.getEncode(byteArray)
- return String(byteArray, Charset.forName(charsetName))
- }
- fun get7zStringContent(url: String, path: String, charsetName: String): String {
- val byteArray = get7zByteArrayContent(url, path) ?: return ""
- return String(byteArray, Charset.forName(charsetName))
- }
- /**
- * 获取网络zip文件里面的数据
- * @param url zip文件的链接或十六进制字符串
- * @param path 所需获取文件在zip内的路径
- * @return zip指定文件的数据
- */
- fun getZipByteArrayContent(url: String, path: String): ByteArray? {
- val bytes = if (url.isAbsUrl()) {
- AnalyzeUrl(url, source = getSource(), coroutineContext = context).getByteArray()
- } else {
- HexUtil.decodeHex(url)
- }
- val bos = ByteArrayOutputStream()
- ZipInputStream(ByteArrayInputStream(bytes)).use { zis ->
- var entry: ZipEntry
- while (zis.nextEntry.also { entry = it } != null) {
- if (entry.name.equals(path)) {
- zis.use { it.copyTo(bos) }
- return bos.toByteArray()
- }
- entry = zis.nextEntry
- }
- }
- log("getZipContent 未发现内容")
- return null
- }
- /**
- * 获取网络Rar文件里面的数据
- * @param url Rar文件的链接或十六进制字符串
- * @param path 所需获取文件在Rar内的路径
- * @return Rar指定文件的数据
- */
- fun getRarByteArrayContent(url: String, path: String): ByteArray? {
- val bytes = if (url.isAbsUrl()) {
- AnalyzeUrl(url, source = getSource(), coroutineContext = context).getByteArray()
- } else {
- HexUtil.decodeHex(url)
- }
- return ByteArrayInputStream(bytes).use {
- LibArchiveUtils.getByteArrayContent(it, path)
- }
- }
- /**
- * 获取网络7zip文件里面的数据
- * @param url 7zip文件的链接或十六进制字符串
- * @param path 所需获取文件在7zip内的路径
- * @return 7zip指定文件的数据
- */
- fun get7zByteArrayContent(url: String, path: String): ByteArray? {
- val bytes = if (url.isAbsUrl()) {
- AnalyzeUrl(url, source = getSource(), coroutineContext = context).getByteArray()
- } else {
- HexUtil.decodeHex(url)
- }
- return ByteArrayInputStream(bytes).use {
- LibArchiveUtils.getByteArrayContent(it, path)
- }
- }
- //******************文件操作************************//
- /**
- * 解析字体Base64数据,返回字体解析类
- */
- @Deprecated(
- "Deprecated",
- ReplaceWith("queryTTF(data)")
- )
- fun queryBase64TTF(data: String?): QueryTTF? {
- log("queryBase64TTF(String)方法已过时,并将在未来删除;请无脑使用queryTTF(Any)替代,新方法支持传入 url、本地文件、base64、ByteArray 自动判断&自动缓存,特殊情况需禁用缓存请传入第二可选参数false:Boolean")
- return queryTTF(data)
- }
- /**
- * 返回字体解析类
- * @param data 支持url,本地文件,base64,ByteArray,自动判断,自动缓存
- * @param useCache 可选开关缓存,不传入该值默认开启缓存
- */
- @OptIn(ExperimentalStdlibApi::class)
- fun queryTTF(data: Any?, useCache: Boolean): QueryTTF? {
- try {
- var key: String? = null
- var qTTF: QueryTTF?
- when (data) {
- is String -> {
- if (useCache) {
- key = MessageDigest.getInstance("SHA-256").digest(data.toByteArray())
- .toHexString()
- qTTF = AppCacheManager.getQueryTTF(key)
- if (qTTF != null) return qTTF
- }
- val font: ByteArray? = when {
- data.isAbsUrl() -> AnalyzeUrl(
- data,
- source = getSource(),
- coroutineContext = context
- ).getByteArray()
- else -> base64DecodeToByteArray(data)
- }
- font ?: return null
- qTTF = QueryTTF(font)
- }
- is ByteArray -> {
- if (useCache) {
- key = MessageDigest.getInstance("SHA-256").digest(data).toHexString()
- qTTF = AppCacheManager.getQueryTTF(key)
- if (qTTF != null) return qTTF
- }
- qTTF = QueryTTF(data)
- }
- else -> return null
- }
- if (key != null) AppCacheManager.put(key, qTTF)
- return qTTF
- } catch (e: Exception) {
- AppLog.put("[queryTTF] 获取字体处理类出错", e)
- throw e
- }
- }
- fun queryTTF(data: Any?): QueryTTF? {
- return queryTTF(data, true)
- }
- /**
- * @param text 包含错误字体的内容
- * @param errorQueryTTF 错误的字体
- * @param correctQueryTTF 正确的字体
- * @param filter 删除 errorQueryTTF 中不存在的字符
- */
- fun replaceFont(
- text: String,
- errorQueryTTF: QueryTTF?,
- correctQueryTTF: QueryTTF?,
- filter: Boolean
- ): String {
- if (errorQueryTTF == null || correctQueryTTF == null) return text
- val contentArray = text.toStringArray() //这里不能用toCharArray,因为有些文字占多个字节
- val intArray = IntArray(1)
- contentArray.forEachIndexed { index, s ->
- val oldCode = s.codePointAt(0)
- // 忽略正常的空白字符
- if (errorQueryTTF.isBlankUnicode(oldCode)) {
- return@forEachIndexed
- }
- // 删除轮廓数据不存在的字符
- var glyf = errorQueryTTF.getGlyfByUnicode(oldCode) // 轮廓数据不存在
- if (errorQueryTTF.getGlyfIdByUnicode(oldCode) == 0) glyf = null // 轮廓数据指向保留索引0
- if (filter && (glyf == null)) {
- contentArray[index] = ""
- return@forEachIndexed
- }
- // 使用轮廓数据反查Unicode
- val code = correctQueryTTF.getUnicodeByGlyf(glyf)
- if (code != 0) {
- intArray[0] = code
- contentArray[index] = String(intArray, 0, 1)
- }
- }
- return contentArray.joinToString("")
- }
- /**
- * @param text 包含错误字体的内容
- * @param errorQueryTTF 错误的字体
- * @param correctQueryTTF 正确的字体
- */
- fun replaceFont(
- text: String,
- errorQueryTTF: QueryTTF?,
- correctQueryTTF: QueryTTF?
- ): String {
- return replaceFont(text, errorQueryTTF, correctQueryTTF, false)
- }
- /**
- * 章节数转数字
- */
- fun toNumChapter(s: String?): String? {
- s ?: return null
- val matcher = AppPattern.titleNumPattern.matcher(s)
- if (matcher.find()) {
- val intStr = StringUtils.stringToInt(matcher.group(2))
- return "${matcher.group(1)}${intStr}${matcher.group(3)}"
- }
- return s
- }
- fun toURL(urlStr: String): JsURL {
- return JsURL(urlStr)
- }
- fun toURL(url: String, baseUrl: String? = null): JsURL {
- return JsURL(url, baseUrl)
- }
- /**
- * 弹窗提示
- */
- fun toast(msg: Any?) {
- rhinoContext.ensureActive()
- appCtx.toastForJs("${getSource()?.getTag()}: ${msg.toString()}")
- }
- /**
- * 弹窗提示 停留时间较长
- */
- fun longToast(msg: Any?) {
- rhinoContext.ensureActive()
- appCtx.longToastForJs("${getSource()?.getTag()}: ${msg.toString()}")
- }
- /**
- * 输出调试日志
- */
- fun log(msg: Any?): Any? {
- rhinoContextOrNull?.ensureActive()
- getSource()?.let {
- Debug.log(it.getKey(), msg.toString())
- } ?: Debug.log(msg.toString())
- AppLog.putDebug("${getSource()?.getTag() ?: "源"}调试输出: $msg")
- return msg
- }
- /**
- * 输出对象类型
- */
- fun logType(any: Any?) {
- if (any == null) {
- log("null")
- } else {
- log(any.javaClass.name)
- }
- }
- /**
- * 生成UUID
- */
- fun randomUUID(): String {
- return UUID.randomUUID().toString()
- }
- fun androidId(): String {
- return AppConst.androidId
- }
- fun openUrl(url: String) {
- openUrl(url, null)
- }
- // 新增 mimeType 参数,默认为 null(保持兼容性)
- fun openUrl(url: String, mimeType: String? = null) {
- rhinoContext.ensureActive()
- val source = getSource() ?: throw NoStackTraceException("openUrl source cannot be null")
- appCtx.startActivity<OpenUrlConfirmActivity> {
- putExtra("uri", url)
- putExtra("mimeType", mimeType)
- putExtra("sourceOrigin", source.getKey())
- putExtra("sourceName", source.getTag())
- putExtra("sourceType", source.getSourceType())
- }
- }
- }
|