mirror of
https://github.com/fumiama/copymanga.git
synced 2026-06-08 00:40:29 +08:00
v2.4.2
注意 > 由于大版本更新, 闪退问题可能增加. >由于修复 bug, 更新可能比较频繁, 如无 API 代理需求可以暂缓更新. 新增 1. 更安全的 API 代理, 旧版代理将无法使用 2. 关于显示插件版本 3. 更改默认API (close #113) 修复 1. 无法搜索汉字漫画 2. 加载API组件无法显示进度 3. 可能的误触发网络增强 4. 不使用API密钥无法加载 (fix #117) 优化 1. 代码组织架构
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user