From c8cc5222b088ba1dc583207fda5ffce986b12f42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BA=90=E6=96=87=E9=9B=A8?= <41315874+fumiama@users.noreply.github.com> Date: Fri, 28 Mar 2025 17:14:55 +0900 Subject: [PATCH] =?UTF-8?q?v2.4.2=20=E6=B3=A8=E6=84=8F=20>=20=E7=94=B1?= =?UTF-8?q?=E4=BA=8E=E5=A4=A7=E7=89=88=E6=9C=AC=E6=9B=B4=E6=96=B0,=20?= =?UTF-8?q?=E9=97=AA=E9=80=80=E9=97=AE=E9=A2=98=E5=8F=AF=E8=83=BD=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit >由于修复 bug, 更新可能比较频繁, 如无 API 代理需求可以暂缓更新. 新增 1. 更安全的 API 代理, 旧版代理将无法使用 2. 关于显示插件版本 3. 更改默认API (close #113) 修复 1. 无法搜索汉字漫画 2. 加载API组件无法显示进度 3. 可能的误触发网络增强 4. 不使用API密钥无法加载 (fix #117) 优化 1. 代码组织架构 --- app/build.gradle | 4 +- .../top/fumiama/copymanga/api/manga/Book.kt | 4 +- .../top/fumiama/copymanga/api/manga/Shelf.kt | 17 ++- .../top/fumiama/copymanga/api/user/Member.kt | 111 +++++++++--------- .../top/fumiama/copymanga/lib/Comancry.kt | 2 +- .../java/top/fumiama/copymanga/lib/Comandy.kt | 2 +- .../fumiama/copymanga/lib/ComandyMethods.kt | 2 + .../copymanga/lib/template/LazyLibrary.kt | 4 +- .../copymanga/net/ComandyGlideModule.kt | 6 +- .../fumiama/copymanga/net/DownloadTools.kt | 86 +++++++++++--- .../net/template/AutoDownloadHandler.kt | 6 +- .../net/template/PausableDownloader.kt | 8 +- app/src/main/res/values/strings.xml | 8 +- 13 files changed, 158 insertions(+), 102 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d21c903..20fb6a4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -11,8 +11,8 @@ android { applicationId 'top.fumiama.copymanga' minSdkVersion 23 targetSdkVersion 34 - versionCode 68 - versionName '2.4.1' + versionCode 69 + versionName '2.4.2' resourceConfigurations += ['zh', 'zh-rCN'] testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/java/top/fumiama/copymanga/api/manga/Book.kt b/app/src/main/java/top/fumiama/copymanga/api/manga/Book.kt index 9e787ee..3979006 100644 --- a/app/src/main/java/top/fumiama/copymanga/api/manga/Book.kt +++ b/app/src/main/java/top/fumiama/copymanga/api/manga/Book.kt @@ -68,13 +68,13 @@ class Book(val path: String, private val getString: (Int) -> String, private val isDownload = true Config.apiProxy?.comancry(mBookApiUrl) { url -> DownloadTools.getHttpContent(url, null, mUserAgent) - } + }?:DownloadTools.getHttpContent(mBookApiUrl, null, mUserAgent) } } else { isDownload = true Config.apiProxy?.comancry(mBookApiUrl) { url -> DownloadTools.getHttpContent(url, null, mUserAgent) - } + }?:DownloadTools.getHttpContent(mBookApiUrl, null, mUserAgent) }?:DownloadTools.getHttpContent(mBookApiUrl, null, mUserAgent) mBook = data.inputStream().use { Gson().fromJson(it.reader(), BookInfoStructure::class.java) diff --git a/app/src/main/java/top/fumiama/copymanga/api/manga/Shelf.kt b/app/src/main/java/top/fumiama/copymanga/api/manga/Shelf.kt index ce2dbf9..8cfbbc2 100644 --- a/app/src/main/java/top/fumiama/copymanga/api/manga/Shelf.kt +++ b/app/src/main/java/top/fumiama/copymanga/api/manga/Shelf.kt @@ -25,11 +25,13 @@ class Shelf(private val getString: (Int) -> String) { append("") append(Config.token.value) } - val re = Config.apiProxy?.comancry(addApiUrl) { url -> + val re = (Config.apiProxy?.comancry(addApiUrl) { url -> DownloadTools.requestWithBody( url, "POST", body.encodeToByteArray() ) - }?.decodeToString() ?: return@withContext "空回应" + }?:DownloadTools.requestWithBody( + addApiUrl, "POST", body.encodeToByteArray() + ))?.decodeToString() ?: return@withContext "空回应" return@withContext try { Gson().fromJson(re, ReturnBase::class.java).message } catch (e: Exception) { @@ -50,11 +52,13 @@ class Shelf(private val getString: (Int) -> String) { append("authorization=Token+") append(Config.token.value) } - val re = Config.apiProxy?.comancry(delApiUrl) { url -> + val re = (Config.apiProxy?.comancry(delApiUrl) { url -> DownloadTools.requestWithBody( url, "DELETE", body.encodeToByteArray() ) - }?.decodeToString() ?: return@withContext "空回应" + }?:DownloadTools.requestWithBody( + delApiUrl, "DELETE", body.encodeToByteArray() + ))?.decodeToString() ?: return@withContext "空回应" return@withContext try { Gson().fromJson(re, ReturnBase::class.java).message } catch (e: Exception) { @@ -64,9 +68,10 @@ class Shelf(private val getString: (Int) -> String) { suspend fun query(pathWord: String): BookQueryStructure? = withContext(Dispatchers.IO) { try { - Config.apiProxy?.comancry(queryApiUrlTemplate.format(Config.myHostApiUrl.value, pathWord)) { url -> + val queryUrl = queryApiUrlTemplate.format(Config.myHostApiUrl.value, pathWord) + (Config.apiProxy?.comancry(queryUrl) { url -> DownloadTools.getHttpContent(url, Config.referer) - }?.let { + }?:DownloadTools.getHttpContent(queryUrl, Config.referer)).let { Gson().fromJson(it.decodeToString(), BookQueryStructure::class.java) } } catch (e: Exception) { diff --git a/app/src/main/java/top/fumiama/copymanga/api/user/Member.kt b/app/src/main/java/top/fumiama/copymanga/api/user/Member.kt index 022d43c..b23d011 100644 --- a/app/src/main/java/top/fumiama/copymanga/api/user/Member.kt +++ b/app/src/main/java/top/fumiama/copymanga/api/user/Member.kt @@ -48,12 +48,11 @@ class Member(private val getString: (Int) -> String) { return@withContext l } try { - val data = Config.apiProxy?.comancry( - getString(R.string.memberInfoApiUrl) - .format(Config.myHostApiUrl.value) - ) { + val u = getString(R.string.memberInfoApiUrl) + .format(Config.myHostApiUrl.value) + val data = (Config.apiProxy?.comancry(u) { DownloadTools.getHttpContent(it) - }?.decodeToString() + }?:DownloadTools.getHttpContent(u)).decodeToString() try { val l = Gson().fromJson(data, LoginInfoStructure::class.java) if (l.code == 200) Config.avatar.value = l.results.avatar @@ -98,62 +97,68 @@ class Member(private val getString: (Int) -> String) { } private suspend fun postLogin(username: String, pwd: String, salt: Int): ByteArray? = - Config.apiProxy?.comancry(getString(R.string.loginApiUrl).format(Config.myHostApiUrl.value)) { - DownloadTools.getApiConnection(it, "POST").let { c -> - c.doOutput = true - c.setRequestProperty( - "content-type", - "application/x-www-form-urlencoded;charset=utf-8" - ) - c.setRequestProperty("platform", "3") - c.setRequestProperty("accept", "application/json") - val r = if (!Config.net_use_foreign.value) "1" else "0" - val pwdEncoded = - Base64.encode("$pwd-$salt".toByteArray(), Base64.DEFAULT).decodeToString() - c.outputStream.write( - "username=${ - URLEncoder.encode( - username, - Charset.defaultCharset().name() - ) - }&password=$pwdEncoded&salt=$salt&platform=3&authorization=Token+&version=${Config.app_ver.value}&source=copyApp®ion=$r&webp=1".toByteArray() - ) - c.outputStream.close() - val b = c.inputStream.readBytes() - c.inputStream.close() - b + getString(R.string.loginApiUrl).format(Config.myHostApiUrl.value).let { u -> + val use: suspend (String) -> ByteArray? = { it: String -> + DownloadTools.getApiConnection(it, "POST").let { c -> + c.doOutput = true + c.setRequestProperty( + "content-type", + "application/x-www-form-urlencoded;charset=utf-8" + ) + c.setRequestProperty("platform", "3") + c.setRequestProperty("accept", "application/json") + val r = if (!Config.net_use_foreign.value) "1" else "0" + val pwdEncoded = + Base64.encode("$pwd-$salt".toByteArray(), Base64.DEFAULT).decodeToString() + c.outputStream.write( + "username=${ + URLEncoder.encode( + username, + Charset.defaultCharset().name() + ) + }&password=$pwdEncoded&salt=$salt&platform=3&authorization=Token+&version=${Config.app_ver.value}&source=copyApp®ion=$r&webp=1".toByteArray() + ) + c.outputStream.close() + val b = c.inputStream.readBytes() + c.inputStream.close() + b + } } + Config.apiProxy?.comancry(u, use)?:use(u) } private suspend fun postComandyLogin(username: String, pwd: String, salt: Int) = - Config.apiProxy?.comancry(getString(R.string.loginApiUrl).format(Config.myHostApiUrl.value)) { - DownloadTools.getComandyApiConnection(it, "POST", null, Config.pc_ua).apply { - headers["content-type"] = "application/x-www-form-urlencoded;charset=utf-8" - headers["platform"] = "3" - headers["accept"] = "application/json" - val r = if (!Config.net_use_foreign.value) "1" else "0" - val pwdEncoded = - Base64.encode("$pwd-$salt".toByteArray(), Base64.DEFAULT).decodeToString() - data = "username=${ - URLEncoder.encode( - username, - Charset.defaultCharset().name() - ) - }&password=$pwdEncoded&salt=$salt&platform=3&authorization=Token+&version=${Config.app_ver.value}&source=copyApp®ion=$r&webp=1" - }.let { capsule -> - try { - val para = Gson().toJson(capsule) - Comandy.instance.getInstance()?.request(para)?.let { result -> - Gson().fromJson(result, ComandyCapsule::class.java)!!.let { - if (it.code != 200) null - else Base64.decode(it.data, Base64.DEFAULT) + getString(R.string.loginApiUrl).format(Config.myHostApiUrl.value).let { u -> + val use: suspend (String) -> ByteArray? = { it: String -> + DownloadTools.getComandyApiConnection(it, "POST", null, Config.pc_ua).apply { + headers["content-type"] = "application/x-www-form-urlencoded;charset=utf-8" + headers["platform"] = "3" + headers["accept"] = "application/json" + val r = if (!Config.net_use_foreign.value) "1" else "0" + val pwdEncoded = + Base64.encode("$pwd-$salt".toByteArray(), Base64.DEFAULT).decodeToString() + data = "username=${ + URLEncoder.encode( + username, + Charset.defaultCharset().name() + ) + }&password=$pwdEncoded&salt=$salt&platform=3&authorization=Token+&version=${Config.app_ver.value}&source=copyApp®ion=$r&webp=1" + }.let { capsule -> + try { + val para = Gson().toJson(capsule) + Comandy.instance.getInstance()?.request(para)?.let { result -> + Gson().fromJson(result, ComandyCapsule::class.java)!!.let { + if (it.code != 200) null + else Base64.decode(it.data, Base64.DEFAULT) + } } + } catch (e: Exception) { + e.printStackTrace() + null } - } catch (e: Exception) { - e.printStackTrace() - null } } + Config.apiProxy?.comancry(u, use)?:use(u) } } diff --git a/app/src/main/java/top/fumiama/copymanga/lib/Comancry.kt b/app/src/main/java/top/fumiama/copymanga/lib/Comancry.kt index 7dc7d3b..ae467d0 100644 --- a/app/src/main/java/top/fumiama/copymanga/lib/Comancry.kt +++ b/app/src/main/java/top/fumiama/copymanga/lib/Comancry.kt @@ -9,7 +9,7 @@ class Comancry: LazyLibrary( ComancryMethods::class.java, "libcomancry.so", "API代理", Config.net_use_api_proxy, Config.comancry_version ) { - val enabled: Boolean + private val enabled: Boolean get() { if (isInInit.get()) { Log.d("MyComancry", "$name block enabled for isInInit") diff --git a/app/src/main/java/top/fumiama/copymanga/lib/Comandy.kt b/app/src/main/java/top/fumiama/copymanga/lib/Comandy.kt index 42ecc10..4c808a8 100644 --- a/app/src/main/java/top/fumiama/copymanga/lib/Comandy.kt +++ b/app/src/main/java/top/fumiama/copymanga/lib/Comandy.kt @@ -16,7 +16,7 @@ class Comandy: LazyLibrary( Log.d("MyComandy", "$name block enabled for isInInit") return false } - if (mEnabled != true && DownloadTools.failTimes.get() >= 2) { + if (mEnabled != true && DownloadTools.failTimes.get() >= 3) { mEnabled = true return true } diff --git a/app/src/main/java/top/fumiama/copymanga/lib/ComandyMethods.kt b/app/src/main/java/top/fumiama/copymanga/lib/ComandyMethods.kt index 13e18c5..95c6630 100644 --- a/app/src/main/java/top/fumiama/copymanga/lib/ComandyMethods.kt +++ b/app/src/main/java/top/fumiama/copymanga/lib/ComandyMethods.kt @@ -6,4 +6,6 @@ interface ComandyMethods : Library { // fun add_dns(para: String?, is_ipv6: Int): String? fun request(para: String): String? + + fun progress(para: String): Int } diff --git a/app/src/main/java/top/fumiama/copymanga/lib/template/LazyLibrary.kt b/app/src/main/java/top/fumiama/copymanga/lib/template/LazyLibrary.kt index 4628a08..6afa9c3 100644 --- a/app/src/main/java/top/fumiama/copymanga/lib/template/LazyLibrary.kt +++ b/app/src/main/java/top/fumiama/copymanga/lib/template/LazyLibrary.kt @@ -102,7 +102,9 @@ open class LazyLibrary( if (remoteVersion > 0) version.value = remoteVersion Log.d("MyLazyLibrary", "update success") isInInit.set(false) - info.dismiss() + withContext(Dispatchers.Main) { + info.dismiss() + } } catch (e: Exception) { e.printStackTrace() if(f.exists()) f.delete() diff --git a/app/src/main/java/top/fumiama/copymanga/net/ComandyGlideModule.kt b/app/src/main/java/top/fumiama/copymanga/net/ComandyGlideModule.kt index 76c9b99..50cea24 100644 --- a/app/src/main/java/top/fumiama/copymanga/net/ComandyGlideModule.kt +++ b/app/src/main/java/top/fumiama/copymanga/net/ComandyGlideModule.kt @@ -85,7 +85,7 @@ class ComandyGlideModule: AppGlideModule() { } override fun handles(model: GlideUrl): Boolean { - return Comandy.instance.enabled && runBlocking { Comandy.instance.getInstance() } != null && model.toURL().let { + return Comandy.instance.enabled && model.toURL().let { it.protocol == "https" && it.host != "copymanga.azurewebsites.net" } } @@ -103,7 +103,9 @@ class ComandyGlideModule: AppGlideModule() { } override fun handles(model: String): Boolean { - return Comandy.instance.enabled && runBlocking { Comandy.instance.getInstance() } != null && model.startsWith("https://") + return Comandy.instance.enabled && + model.startsWith("https://") && + !model.startsWith("https://copymanga.azurewebsites.net") } } diff --git a/app/src/main/java/top/fumiama/copymanga/net/DownloadTools.kt b/app/src/main/java/top/fumiama/copymanga/net/DownloadTools.kt index 74286e9..9be6c39 100644 --- a/app/src/main/java/top/fumiama/copymanga/net/DownloadTools.kt +++ b/app/src/main/java/top/fumiama/copymanga/net/DownloadTools.kt @@ -9,11 +9,11 @@ import kotlinx.coroutines.withContext import top.fumiama.copymanga.MainActivity import top.fumiama.copymanga.api.Config import top.fumiama.copymanga.json.ComandyCapsule -import top.fumiama.copymanga.lib.Comancry import top.fumiama.copymanga.lib.Comandy import java.io.ByteArrayOutputStream import java.io.InputStream import java.io.OutputStream +import java.lang.Thread.sleep import java.net.HttpURLConnection import java.net.URL import java.util.concurrent.Callable @@ -123,18 +123,43 @@ object DownloadTools { getComandyApiConnection(u, "GET", refer, ua).let { capsule -> val para = Gson().toJson(capsule) //Log.d("MyDT", "comandy request: $para") - Comandy.instance.getInstance()?.request(para)?.let { result -> - //Log.d("MyDT", "comandy reply: $result") - Gson().fromJson(result, ComandyCapsule::class.java)!!.let { - if (it.code != 200) throw IllegalArgumentException("HTTP${it.code} ${ - it.data?.let { d -> Base64.decode(d, Base64.DEFAULT).decodeToString() } - }") - Base64.decode(it.data, Base64.DEFAULT) + Comandy.instance.getInstance()?.let { ins -> + var completed = false + p?.let { + Thread { + Log.d("MyDT", "launch comandy get progress, completed: $completed for url $u") + var prev = 0 + while (!completed) { + sleep(50) + val progress = ins.progress(para) + Log.d("MyDT", "comandy get progress $progress for url $u") + if (progress > prev) { + it.notify(progress) + prev = progress + if (progress >= 100) break + } + } + Log.d("MyDT", "quit comandy get progress, completed: $completed for url $u") + }.start() } + val r = ins.request(para)?.let { result -> + completed = true + p?.notify(100) + //Log.d("MyDT", "comandy reply: $result") + Gson().fromJson(result, ComandyCapsule::class.java)!!.let { + if (it.code != 200) throw IllegalArgumentException("HTTP${it.code} ${ + it.data?.let { d -> Base64.decode(d, Base64.DEFAULT).decodeToString() } + }") + Base64.decode(it.data, Base64.DEFAULT) + } + } + completed = true + p?.notify(100) + r } }.let { if(it?.isNotEmpty() == true ) return@withContext it } + failTimes.incrementAndGet() } - failTimes.incrementAndGet() getApiConnection(u, "GET", refer, ua).let { val sz = it.getHeaderFieldInt("Content-Length", 0) val ret = if (sz > 0 && p != null) { @@ -144,7 +169,6 @@ object DownloadTools { } it.disconnect() Log.d("MyDT", "getHttpContent: ${ret.size} bytes") - failTimes.decrementAndGet() ret } } @@ -157,7 +181,6 @@ object DownloadTools { task.get() } catch (ex: Exception) { ex.printStackTrace() - if (Comandy.instance.enabled) failTimes.incrementAndGet() null } } @@ -167,12 +190,38 @@ object DownloadTools { Log.d("MyDT", "prepareHttp: $u") FutureTask(if (!u.startsWith("https://copymanga.azurewebsites.net") && Comandy.instance.enabled) Callable{ try { - runBlocking { Comandy.instance.getInstance() }?.request(Gson().toJson( - getComandyNormalConnection(u, "GET", Config.pc_ua)) - )?.let { result -> - Gson().fromJson(result, ComandyCapsule::class.java)?.let { - if (it.code != 200) null - else it.data?.let { d -> Base64.decode(d, Base64.DEFAULT) } + runBlocking { Comandy.instance.getInstance() }?.let { ins -> + runBlocking { + val para = Gson().toJson(getComandyNormalConnection(u, "GET", Config.pc_ua)) + var completed = false + p?.let { + Thread { + Log.d("MyDT", "launch comandy get progress, completed: $completed for url $u") + var prev = 0 + while (!completed) { + sleep(50) + val progress = ins.progress(para) + Log.d("MyDT", "comandy get progress $progress for url $u") + if (progress > prev) { + it.notify(progress) + prev = progress + if (progress >= 100) break + } + } + Log.d("MyDT", "quit comandy get progress, completed: $completed for url $u") + }.start() + } + val r = ins.request(para)?.let { result -> + completed = true + p?.notify(100) + Gson().fromJson(result, ComandyCapsule::class.java)?.let { + if (it.code != 200) null + else it.data?.let { d -> Base64.decode(d, Base64.DEFAULT) } + } + } + completed = true + p?.notify(100) + r } } } catch (ex: Exception) { @@ -224,17 +273,16 @@ object DownloadTools { } } catch (ex: Exception) { ex.printStackTrace() + failTimes.incrementAndGet() ex.message?.encodeToByteArray() } } else Callable { - failTimes.incrementAndGet() try { getApiConnection(url, method, refer, ua).apply { outputStream.write(body) ret = inputStream.readBytes() disconnect() - failTimes.decrementAndGet() } } catch (ex: Exception) { ex.printStackTrace() diff --git a/app/src/main/java/top/fumiama/copymanga/net/template/AutoDownloadHandler.kt b/app/src/main/java/top/fumiama/copymanga/net/template/AutoDownloadHandler.kt index 7384167..cf566c5 100644 --- a/app/src/main/java/top/fumiama/copymanga/net/template/AutoDownloadHandler.kt +++ b/app/src/main/java/top/fumiama/copymanga/net/template/AutoDownloadHandler.kt @@ -79,11 +79,7 @@ open class AutoDownloadHandler( try { val data = Config.apiProxy?.comancry(url) { DownloadTools.getHttpContent(it) - } - if (data == null) { - delay(2000) - continue - } + }?:DownloadTools.getHttpContent(url) if(exit) return@withContext val fi = data.inputStream() val pass = setGsonItem(Gson().fromJson(fi.reader(), jsonClass)) diff --git a/app/src/main/java/top/fumiama/copymanga/net/template/PausableDownloader.kt b/app/src/main/java/top/fumiama/copymanga/net/template/PausableDownloader.kt index 3588363..b78c0ea 100644 --- a/app/src/main/java/top/fumiama/copymanga/net/template/PausableDownloader.kt +++ b/app/src/main/java/top/fumiama/copymanga/net/template/PausableDownloader.kt @@ -14,13 +14,9 @@ class PausableDownloader(private val url: String, private val waitMilliseconds: var c = 0 while (!exit && c++ < 3) { try { - val data = if (isApi) Config.apiProxy?.comancry(url) { + val data = (if (isApi) Config.apiProxy?.comancry(url) { DownloadTools.getHttpContent(it, Config.referer) - } else DownloadTools.getHttpContent(url, Config.referer) - if (data == null) { - delay(3000) - continue - } + } else null)?:DownloadTools.getHttpContent(url, Config.referer) whenFinish?.let { it(data) } return@withContext true } catch (e: Exception) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b6884ce..11a8b4e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,6 +1,6 @@ + ]> @@ -164,11 +164,11 @@ 使用经过优化的请求方法访问服务器 请求API网址 一般无需更改,除非拷贝漫画官方更改网址,默认:&hosturl; - 使用API代理(重启生效) + 使用API代理(需要密钥) 作者自建的API代理,可缓解国内图书详情加载问题,但不保证100%解决,也不保证一直可用 - 使用图床代理(重启生效) + 使用图床代理(需要密钥) 作者自建的图床代理,可缓解国内图片无法加载问题,但不保证100%解决,也不保证一直可用 - 代理密钥(重启生效) + 代理密钥 为避免滥用,该密钥需加群(559748702)获得,且随时可能会刷新 漫画浏览