1
0
mirror of https://github.com/fumiama/copymanga.git synced 2026-06-08 00:40:29 +08:00
注意
> 由于大版本更新, 闪退问题可能增加.

>由于修复 bug, 更新可能比较频繁, 如无 API 代理需求可以暂缓更新.

新增
1. 更安全的 API 代理, 旧版代理将无法使用
2. 关于显示插件版本
3. 更改默认API (close #113)
修复
1. 无法搜索汉字漫画
2. 加载API组件无法显示进度
3. 可能的误触发网络增强
4. 不使用API密钥无法加载 (fix #117)
优化
1. 代码组织架构
This commit is contained in:
源文雨
2025-03-28 17:14:55 +09:00
parent d169c51153
commit c8cc5222b0
13 changed files with 158 additions and 102 deletions

View File

@@ -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"

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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&region=$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&region=$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&region=$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&region=$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)
}
}

View File

@@ -9,7 +9,7 @@ class Comancry: LazyLibrary<ComancryMethods>(
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")

View File

@@ -16,7 +16,7 @@ class Comandy: LazyLibrary<ComandyMethods>(
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
}

View File

@@ -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
}

View File

@@ -102,7 +102,9 @@ open class LazyLibrary<T: Library>(
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()

View File

@@ -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")
}
}

View File

@@ -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()

View File

@@ -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))

View File

@@ -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) {

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE resources [
<!ENTITY hosturl "api.mangacopy.com">
<!ENTITY hosturl "www.copy-manga.com">
<!ENTITY appver "2.2.6">
]>
<resources>
@@ -164,11 +164,11 @@
<string name="settings_cat_net_sm_use_comandy">使用经过优化的请求方法访问服务器</string>
<string name="settings_cat_net_et_title_api_url">请求API网址</string>
<string name="settings_cat_net_et_summary_api_url">一般无需更改,除非拷贝漫画官方更改网址,默认:&hosturl;</string>
<string name="settings_cat_net_sw_use_api_proxy">使用API代理重启生效</string>
<string name="settings_cat_net_sw_use_api_proxy">使用API代理需要密钥</string>
<string name="settings_cat_net_sm_use_api_proxy">作者自建的API代理可缓解国内图书详情加载问题但不保证100%解决,也不保证一直可用</string>
<string name="settings_cat_net_sw_use_img_proxy">使用图床代理(重启生效</string>
<string name="settings_cat_net_sw_use_img_proxy">使用图床代理(需要密钥</string>
<string name="settings_cat_net_sm_use_img_proxy">作者自建的图床代理可缓解国内图片无法加载问题但不保证100%解决,也不保证一直可用</string>
<string name="settings_cat_net_et_title_img_proxy">代理密钥(重启生效)</string>
<string name="settings_cat_net_et_title_img_proxy">代理密钥</string>
<string name="settings_cat_net_et_summary_img_proxy">为避免滥用该密钥需加群559748702获得且随时可能会刷新</string>
<string name="settings_cat_vm">漫画浏览</string>