diff --git a/.idea/dictionaries/fumiama.xml b/.idea/dictionaries/fumiama.xml index 3070a10..c80f935 100644 --- a/.idea/dictionaries/fumiama.xml +++ b/.idea/dictionaries/fumiama.xml @@ -5,6 +5,7 @@ azurewebsites comancry comandy + deviceinfo downloaders grps hotmanga @@ -20,6 +21,7 @@ reclass reilia systembar + umstring \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index a8a0bd5..759a97d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,9 +10,10 @@ android { compileSdk 34 applicationId 'top.fumiama.copymanga' minSdkVersion 23 + //noinspection OldTargetApi targetSdkVersion 34 - versionCode 72 - versionName '2.5.1' + versionCode 73 + versionName '2.5.2' resourceConfigurations += ['zh', 'zh-rCN'] testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -47,7 +48,7 @@ android { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release } - /*winrelease { + /*winrelease {r minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' @@ -98,7 +99,7 @@ dependencies { //noinspection GradleDependency implementation 'androidx.core:core-ktx:1.12.0' - implementation 'androidx.appcompat:appcompat:1.7.0' + implementation 'androidx.appcompat:appcompat:1.7.1' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'com.google.android.material:material:1.12.0' implementation 'androidx.constraintlayout:constraintlayout:2.2.1' diff --git a/app/src/main/java/top/fumiama/copymanga/MainActivity.kt b/app/src/main/java/top/fumiama/copymanga/MainActivity.kt index 337fc87..fb18378 100644 --- a/app/src/main/java/top/fumiama/copymanga/MainActivity.kt +++ b/app/src/main/java/top/fumiama/copymanga/MainActivity.kt @@ -157,6 +157,9 @@ class MainActivity : AppCompatActivity() { isMenuWaiting = true Log.d("MyMain", "start menu waiting") lifecycleScope.launch { + withContext(Dispatchers.IO) { + Config.myHostApiUrl.init() + } withContext(Dispatchers.IO) { delay(1000) withContext(Dispatchers.Main) { @@ -430,7 +433,7 @@ class MainActivity : AppCompatActivity() { dl.setMessage("${getString(R.string.app_description)}\n" + "\n$comandy\n" + "$comancry\n\n"+ File("/proc/self/cmdline").readText() + "\n" + - "当前API: ${Config.myHostApiUrl.joinToString(", ")}") + "当前API: ${Config.myHostApiUrl.getApis().joinToString(", ")}") dl.setTitle("${getString(R.string.action_info)} ${BuildConfig.VERSION_NAME}") dl.setIcon(R.mipmap.ic_launcher) dl.setPositiveButton(android.R.string.ok) { _, _ -> } diff --git a/app/src/main/java/top/fumiama/copymanga/api/Config.kt b/app/src/main/java/top/fumiama/copymanga/api/Config.kt index 0e8a9b8..3fc03be 100644 --- a/app/src/main/java/top/fumiama/copymanga/api/Config.kt +++ b/app/src/main/java/top/fumiama/copymanga/api/Config.kt @@ -1,14 +1,8 @@ package top.fumiama.copymanga.api -import android.util.Log import com.bumptech.glide.load.model.LazyHeaders -import com.google.gson.Gson -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock import top.fumiama.copymanga.MainActivity -import top.fumiama.copymanga.json.NetworkStructure -import top.fumiama.copymanga.net.DownloadTools +import top.fumiama.copymanga.api.network.Api import top.fumiama.copymanga.net.Proxy import top.fumiama.copymanga.net.Resolution import top.fumiama.copymanga.storage.PreferenceBoolean @@ -61,49 +55,9 @@ object Config { } val proxyUrl = MainActivity.mainWeakReference?.get()?.getString(R.string.proxyUrl)!! - private val reverseProxyUrl = PreferenceString(R.string.reverseProxyKeyID) - private val networkApiUrl = PreferenceString("settings_cat_net_et_api_url", R.string.hostUrl) - private var mHostApiUrls: Array = arrayOf() - private var mHostApiUrlsMutex = Mutex() - val myHostApiUrl: Array - get() { - if (mHostApiUrls.isNotEmpty()) return mHostApiUrls - if (reverseProxyUrl.value.isNotEmpty() && reverseProxyUrl.value != proxyUrl) { - mHostApiUrls = arrayOf(reverseProxyUrl.value) - Log.d("MyC", "myHostApiUrl set reverse proxy to ${mHostApiUrls[0]}") - return mHostApiUrls - } - MainActivity.mainWeakReference?.get()?.apply { - runBlocking { - mHostApiUrlsMutex.withLock { - if (mHostApiUrls.isNotEmpty()) return@runBlocking - try { - val u = getString(R.string.networkApiUrl).format(networkApiUrl.value, platform.value) - val r = Gson().fromJson((apiProxy?.comancry(u) { - DownloadTools.getHttpContent(it, referer, pc_ua) - }?:DownloadTools.getHttpContent(u, referer, pc_ua)).decodeToString(), NetworkStructure::class.java) - if (r != null) { - Log.d("MyC", "myHostApiUrl get code ${r.code} msg ${r.message}") - if (r.code == 200 && r.results != null) { - // need header umstring - // r.results.api.forEach { it.forEach { api -> if (!api.isNullOrEmpty() && api !in field) field += api } } - r.results.share.forEach { api -> if (!api.isNullOrEmpty() && api !in mHostApiUrls) mHostApiUrls += api } - } - } - } catch (e: Exception) { - e.printStackTrace() - mHostApiUrls = arrayOf(networkApiUrl.value) - } - if (mHostApiUrls.isEmpty()) { - mHostApiUrls = arrayOf(networkApiUrl.value) - Log.d("MyC", "myHostApiUrl set default ${mHostApiUrls[0]}") - } - } - } - } - Log.d("MyC", "myHostApiUrl get hosts ${mHostApiUrls.joinToString(", ")}") - return mHostApiUrls - } + val reverseProxyUrl = PreferenceString(R.string.reverseProxyKeyID) + val networkApiUrl = PreferenceString("settings_cat_net_et_api_url", R.string.hostUrl) + val myHostApiUrl = Api() val navTextInfo = UserPreferenceString("navTextInfo", R.string.navTextInfo) val proxy_key = PreferenceString(R.string.imgProxyCodeKeyID) val app_ver = PreferenceString("settings_cat_general_et_app_version", R.string.app_ver) @@ -130,6 +84,7 @@ object Config { private val net_use_img_proxy = PreferenceBoolean("settings_cat_net_sw_use_img_proxy", false) val net_use_api_proxy = PreferenceBoolean("settings_cat_net_sw_use_api_proxy", false) val net_img_resolution = PreferenceString(R.string.imgResolutionKeyID) + val net_umstring = PreferenceString("settings_cat_net_et_umstring") val view_manga_inverse_chapters = PreferenceBoolean("settings_cat_vm_sw_inverse_chapters", false) val view_manga_always_dark_bg = PreferenceBoolean("settings_cat_vm_sw_always_dark_bg", false) @@ -144,5 +99,5 @@ object Config { fun getChapterInfoApiUrl(path: String?, uuid: String?, version: Int) = MainActivity.mainWeakReference?.get()?.getString(R.string.chapterInfoApiUrl) - ?.format(myHostApiUrl.random(), path, if (version >= 2) "$version" else "" , uuid, platform.value) + ?.format(path, if (version >= 2) "$version" else "" , uuid, platform.value) } 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 1eb276b..f8b03ff 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 @@ -1,11 +1,13 @@ package top.fumiama.copymanga.api.manga import android.util.Log +import android.widget.Toast import com.google.gson.Gson import kotlinx.android.synthetic.main.card_book.* import kotlinx.android.synthetic.main.line_booktandb.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import top.fumiama.copymanga.MainActivity import top.fumiama.copymanga.json.BookInfoStructure import top.fumiama.copymanga.json.ThemeStructure import top.fumiama.copymanga.json.VolumeStructure @@ -15,8 +17,7 @@ import top.fumiama.dmzj.copymanga.R import java.io.File class Book(val path: String, private val getString: (Int) -> String, private val exDir: File, private val loadCache: Boolean = false, private val mPassName: String? = null) { - private val mBookApiUrl = getString(R.string.bookInfoApiUrl).format(Config.myHostApiUrl.random(), path, Config.platform.value) - private val mUserAgent = getString(R.string.pc_ua).format(Config.app_ver.value) + private val mBookApiUrl = getString(R.string.bookInfoApiUrl).format(path, Config.platform.value) private var mBook: BookInfoStructure? = null private var mGroupPathWords = arrayOf() private var mKeys = arrayOf() @@ -57,22 +58,16 @@ class Book(val path: String, private val getString: (Int) -> String, private val suspend fun updateInfo() = withContext(Dispatchers.IO) { try { var isDownload = false - val data: ByteArray = if (loadCache) { + val data: String = if (loadCache) { name?.let { loadInfo(it) } ?: run { isDownload = true - Config.apiProxy?.comancry(mBookApiUrl) { url -> - DownloadTools.getHttpContent(url, null, mUserAgent) - }?:DownloadTools.getHttpContent(mBookApiUrl, null, mUserAgent) + Config.myHostApiUrl.get(mBookApiUrl) } } 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) + Config.myHostApiUrl.get(mBookApiUrl) } + mBook = Gson().fromJson(data, BookInfoStructure::class.java) if (isDownload) saveInfo(data) mGroupPathWords = arrayOf() mKeys = arrayOf() @@ -88,6 +83,9 @@ class Book(val path: String, private val getString: (Int) -> String, private val } } catch (e: Exception) { e.printStackTrace() + MainActivity.mainWeakReference?.get()?.apply { + Toast.makeText(this, e.message, Toast.LENGTH_SHORT).show() + } } } @@ -162,19 +160,19 @@ class Book(val path: String, private val getString: (Int) -> String, private val }?:false } - private suspend fun saveInfo(data: ByteArray) = withContext(Dispatchers.IO) { + private suspend fun saveInfo(data: String) = withContext(Dispatchers.IO) { name?.let { name -> val mangaFolder = File(exDir, name) if(!mangaFolder.exists()) mangaFolder.mkdirs() - File(mangaFolder, "meta.json").writeBytes(data) + File(mangaFolder, "meta.json").writeText(data) } } - private suspend fun loadInfo(name: String): ByteArray? = withContext(Dispatchers.IO) { + private suspend fun loadInfo(name: String): String? = withContext(Dispatchers.IO) { val mangaFolder = File(exDir, name) if(!mangaFolder.exists()) mangaFolder.mkdirs() val f = File(mangaFolder, "meta.json") if (!f.exists()) return@withContext null - return@withContext f.readBytes() + return@withContext f.readBytes().decodeToString() } } 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 9ecb5a8..ec850e2 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 @@ -3,14 +3,13 @@ package top.fumiama.copymanga.api.manga import com.google.gson.Gson import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import top.fumiama.copymanga.api.Config import top.fumiama.copymanga.json.BookQueryStructure import top.fumiama.copymanga.json.ReturnBase -import top.fumiama.copymanga.api.Config -import top.fumiama.copymanga.net.DownloadTools import top.fumiama.dmzj.copymanga.R class Shelf(private val getString: (Int) -> String) { - private val apiUrl: String get() = getString(R.string.shelfOperateApiUrl).format(Config.myHostApiUrl.random()) + private val apiUrl: String get() = getString(R.string.shelfOperateApiUrl) private val queryApiUrlTemplate = getString(R.string.bookUserQueryApiUrl) private val addApiUrl get() = "$apiUrl?platform=${Config.platform.value}" private val delApiUrl get() = "${apiUrl}s?platform=${Config.platform.value}" @@ -25,17 +24,13 @@ class Shelf(private val getString: (Int) -> String) { append("") append(Config.token.value) } - val re = (Config.apiProxy?.comancry(addApiUrl) { url -> - DownloadTools.requestWithBody( - url, "POST", body.encodeToByteArray() - ) - }?:DownloadTools.requestWithBody( - addApiUrl, "POST", body.encodeToByteArray() - ))?.decodeToString() ?: return@withContext "空回应" return@withContext try { + val re = Config.myHostApiUrl.request( + addApiUrl, body.encodeToByteArray(), "POST", + "application/x-www-form-urlencoded;charset=utf-8") Gson().fromJson(re, ReturnBase::class.java).message } catch (e: Exception) { - "$re ${e.message}" + e.message?:e::class.simpleName?:e.toString() } } @@ -52,31 +47,20 @@ class Shelf(private val getString: (Int) -> String) { append("authorization=Token+") append(Config.token.value) } - val re = (Config.apiProxy?.comancry(delApiUrl) { url -> - DownloadTools.requestWithBody( - url, "DELETE", body.encodeToByteArray() - ) - }?:DownloadTools.requestWithBody( - delApiUrl, "DELETE", body.encodeToByteArray() - ))?.decodeToString() ?: return@withContext "空回应" return@withContext try { + val re = Config.myHostApiUrl.request( + delApiUrl, body.encodeToByteArray(), + "DELETE", "application/x-www-form-urlencoded;charset=utf-8") Gson().fromJson(re, ReturnBase::class.java).message } catch (e: Exception) { - "$re ${e.message}" + e.message?:e::class.simpleName?:e.toString() } } suspend fun query(pathWord: String): BookQueryStructure? = withContext(Dispatchers.IO) { - try { - val queryUrl = queryApiUrlTemplate.format(Config.myHostApiUrl.random(), pathWord, Config.platform.value) - (Config.apiProxy?.comancry(queryUrl) { url -> - DownloadTools.getHttpContent(url, Config.referer) - }?:DownloadTools.getHttpContent(queryUrl, Config.referer)).let { - Gson().fromJson(it.decodeToString(), BookQueryStructure::class.java) - } - } catch (e: Exception) { - e.printStackTrace() - null + val queryUrl = queryApiUrlTemplate.format(pathWord, Config.platform.value) + Config.myHostApiUrl.get(queryUrl).let { + Gson().fromJson(it, BookQueryStructure::class.java) } } } diff --git a/app/src/main/java/top/fumiama/copymanga/api/manga/Volume.kt b/app/src/main/java/top/fumiama/copymanga/api/manga/Volume.kt index 5da20dc..0eb38c8 100644 --- a/app/src/main/java/top/fumiama/copymanga/api/manga/Volume.kt +++ b/app/src/main/java/top/fumiama/copymanga/api/manga/Volume.kt @@ -39,7 +39,7 @@ class Volume(private val path: String, private val groupPathWord: String, getStr return@withContext mVolume } - private fun getApiUrl(offset: Int) = mGroupInfoApiUrlTemplate.format(Config.myHostApiUrl.random(), path, groupPathWord, offset, Config.platform.value) + private fun getApiUrl(offset: Int) = mGroupInfoApiUrlTemplate.format(path, groupPathWord, offset, Config.platform.value) private suspend fun download(re: Array, offset: Int, c: Int) = withContext(Dispatchers.IO) { Log.d("MyV", "下载偏移: $offset") getApiUrl(offset).let { diff --git a/app/src/main/java/top/fumiama/copymanga/api/network/Api.kt b/app/src/main/java/top/fumiama/copymanga/api/network/Api.kt new file mode 100644 index 0000000..577b6fa --- /dev/null +++ b/app/src/main/java/top/fumiama/copymanga/api/network/Api.kt @@ -0,0 +1,112 @@ +package top.fumiama.copymanga.api.network + +import android.util.Log +import android.widget.Toast +import com.google.gson.Gson +import top.fumiama.copymanga.MainActivity +import top.fumiama.copymanga.api.Config.apiProxy +import top.fumiama.copymanga.api.Config.networkApiUrl +import top.fumiama.copymanga.api.Config.platform +import top.fumiama.copymanga.api.Config.proxyUrl +import top.fumiama.copymanga.api.Config.reverseProxyUrl +import top.fumiama.copymanga.json.NetworkStructure +import top.fumiama.copymanga.json.ReturnBase +import top.fumiama.copymanga.net.DownloadTools +import top.fumiama.dmzj.copymanga.R +import java.util.concurrent.locks.ReentrantReadWriteLock +import kotlin.concurrent.read +import kotlin.concurrent.write + +class Api { + private var mHostApiUrls = mutableListOf() + private var mu = ReentrantReadWriteLock() + + fun getApis(): Array { + return mHostApiUrls.toTypedArray() + } + + suspend fun init() { + if (mHostApiUrls.isNotEmpty()) return + if (reverseProxyUrl.value.isNotEmpty() && reverseProxyUrl.value != proxyUrl) { + mu.write { mHostApiUrls = mutableListOf(reverseProxyUrl.value) } + Log.d("MyApi", "myHostApiUrl set reverse proxy to ${reverseProxyUrl.value}") + return + } + MainActivity.mainWeakReference?.get()?.apply { + mu.write { + if (mHostApiUrls.isNotEmpty()) return + try { + val d = get(getString(R.string.networkApiUrl).format(platform.value), networkApiUrl.value) + val r = Gson().fromJson(d, NetworkStructure::class.java) + if (r != null) { + Log.d("MyApi", "myHostApiUrl get code ${r.code} msg ${r.message}") + if (r.results != null) { + r.results.api.forEach { it.forEach { api -> if (!api.isNullOrEmpty() && api !in mHostApiUrls) mHostApiUrls += api } } + r.results.share.forEach { api -> if (!api.isNullOrEmpty() && api !in mHostApiUrls) mHostApiUrls += api } + } + } + } catch (e: Exception) { + e.printStackTrace() + mHostApiUrls = mutableListOf(networkApiUrl.value) + Toast.makeText(this, e.message, Toast.LENGTH_SHORT).show() + } + if (mHostApiUrls.isEmpty()) { + mHostApiUrls = mutableListOf(networkApiUrl.value) + Log.d("MyApi", "myHostApiUrl set default ${mHostApiUrls[0]}") + Toast.makeText(this, "无法获取API列表", Toast.LENGTH_SHORT).show() + } + } + } + Log.d("MyApi", "myHostApiUrl get hosts ${mHostApiUrls.joinToString(", ")}") + } + // get throw error on non-json or non-200 or empty apis, path: /api/v3/xxx, return json string + suspend fun get(path: String, forceApi: String? = null): String { + val apis = if (forceApi == null) mu.read { mHostApiUrls } else mutableListOf(forceApi) + if (apis.isEmpty()) { + throw NoSuchElementException("API列表为空") + } + var r: ReturnBase? = null + apis.forEach { api -> + val u = "https://$api$path" + try { + val ret = (apiProxy?.comancry(u) { + DownloadTools.getApiContent(it) + }?: DownloadTools.getApiContent(u)).decodeToString() + r = Gson().fromJson(ret, ReturnBase::class.java) + if (r!!.code != 200) { + mu.write { mHostApiUrls.remove(api) } + } else { + return ret + } + } catch (e: Exception) { + mu.write { mHostApiUrls.remove(api) } + } + } + throw IllegalStateException("错误码${r!!.code}, 信息: ${r!!.message}") + } + // request throw error on non-json or non-200 or empty apis, path: /api/v3/xxx, return json string + suspend fun request(path: String, body: ByteArray, method: String, contentType: String, forceApi: String? = null): String { + val apis = if (forceApi == null) mu.read { mHostApiUrls } else mutableListOf(forceApi) + if (apis.isEmpty()) { + throw NoSuchElementException("API列表为空") + } + var r: ReturnBase? = null + apis.forEach { api -> + val u = "https://$api$path" + try { + val ret = (apiProxy?.comancry(u) { + DownloadTools.requestApiWithBody(u, method, body, contentType) + }?: DownloadTools.requestApiWithBody(u, method, body, contentType)).decodeToString() + r = Gson().fromJson(ret, ReturnBase::class.java) + if (r!!.code != 200) { + mu.write { mHostApiUrls.remove(api) } + } else { + return ret + } + } catch (e: Exception) { + mu.write { mHostApiUrls.remove(api) } + } + } + throw IllegalStateException("错误码${r!!.code}, 信息: ${r!!.message}") + } +} 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 3925574..637c494 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 @@ -5,10 +5,7 @@ import com.google.gson.Gson import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import top.fumiama.copymanga.api.Config -import top.fumiama.copymanga.json.ComandyCapsule import top.fumiama.copymanga.json.LoginInfoStructure -import top.fumiama.copymanga.lib.Comandy -import top.fumiama.copymanga.net.DownloadTools import top.fumiama.dmzj.copymanga.R import java.net.URLEncoder import java.nio.charset.Charset @@ -17,23 +14,16 @@ class Member(private val getString: (Int) -> String) { val hasLogin: Boolean get() = Config.token.value?.isNotEmpty() ?: false suspend fun login(username: String, pwd: String, salt: Int): LoginInfoStructure = withContext(Dispatchers.IO) { - var err = "" - (if (!Config.net_use_api_proxy.value && Comandy.instance.enabled) - postComandyLogin(username, pwd, salt) - else postLogin(username, pwd, salt))?.let { data -> - try { - return@withContext saveInfo(data) - } catch (e: Exception) { - err = e.message.toString() - } - } ?: run { err = getString(R.string.login_get_conn_failed) } - val l = LoginInfoStructure() - l.code = 400 - l.message = err - return@withContext l + return@withContext try { + saveInfo(postLogin(username, pwd, salt)) + } catch (e: Exception) { + val l = LoginInfoStructure() + l.code = 400 + l.message = e.message.toString() + l + } } - /** * 获得登录信息并更新头像 * @return 登录态 @@ -49,24 +39,21 @@ class Member(private val getString: (Int) -> String) { } try { val u = getString(R.string.memberInfoApiUrl) - .format(Config.myHostApiUrl.random(), Config.platform.value) - val data = (Config.apiProxy?.comancry(u) { - DownloadTools.getHttpContent(it) - }?:DownloadTools.getHttpContent(u)).decodeToString() + .format(Config.platform.value) try { - val l = Gson().fromJson(data, LoginInfoStructure::class.java) + val l = Gson().fromJson(Config.myHostApiUrl.get(u), LoginInfoStructure::class.java) if (l.code == 200) Config.avatar.value = l.results.avatar l } catch (e: Exception) { val l = LoginInfoStructure() l.code = 450 - l.message = "${getString(R.string.login_get_avatar_failed)}: $data" + l.message = "${getString(R.string.login_get_avatar_failed)}: ${e.message}" l } } catch (e: Exception) { val l = LoginInfoStructure() l.code = 450 - l.message = "${getString(R.string.login_get_avatar_failed)}: $e" + l.message = "${getString(R.string.login_get_avatar_failed)}: ${e.message}" l } } @@ -96,69 +83,17 @@ class Member(private val getString: (Int) -> String) { } } - private suspend fun postLogin(username: String, pwd: String, salt: Int): ByteArray? = - getString(R.string.loginApiUrl).format(Config.myHostApiUrl.random(), Config.platform.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", Config.platform.value) - 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=${Config.platform.value}&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) = - getString(R.string.loginApiUrl).format(Config.myHostApiUrl.random(), Config.platform.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"] = Config.platform.value - 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=${Config.platform.value}&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 - } - } - } - Config.apiProxy?.comancry(u, use)?:use(u) - } + private suspend fun postLogin(username: String, pwd: String, salt: Int): ByteArray = + getString(R.string.loginApiUrl).format(Config.platform.value).let { u -> + val r = if (!Config.net_use_foreign.value) "1" else "0" + val pwdEncoded = + Base64.encode("$pwd-$salt".toByteArray(), Base64.DEFAULT).decodeToString() + Config.myHostApiUrl.request(u, "username=${ + URLEncoder.encode( + username, + Charset.defaultCharset().name() + ) + }&password=$pwdEncoded&salt=$salt&platform=${Config.platform.value}&authorization=Token+&version=${Config.app_ver.value}&source=copyApp®ion=$r&webp=1".encodeToByteArray(), + "POST", "application/x-www-form-urlencoded;charset=utf-8") + }.encodeToByteArray() } 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 ad302a8..ed354d4 100644 --- a/app/src/main/java/top/fumiama/copymanga/lib/Comandy.kt +++ b/app/src/main/java/top/fumiama/copymanga/lib/Comandy.kt @@ -3,27 +3,18 @@ package top.fumiama.copymanga.lib import android.util.Log import top.fumiama.copymanga.api.Config import top.fumiama.copymanga.lib.template.LazyLibrary -import top.fumiama.copymanga.net.DownloadTools class Comandy: LazyLibrary( ComandyMethods::class.java, "libcomandy.so", "网络增强", Config.net_use_comandy, Config.comandy_version ) { - private var mEnabled: Boolean? = null val enabled: Boolean get() { if (isInInit.get()) { Log.d("MyComandy", "$name block enabled for isInInit") return false } - if (mEnabled != true && DownloadTools.failTimes.get() >= 16) { - mEnabled = true - return true - } - if (mEnabled != null) return mEnabled!! - val v = isInUse.value - mEnabled = v - return v + return isInUse.value } val status: String get() = if(enabled) { if (isInUse.value) "生效(手动)" else "生效(自动)" 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 348d3f5..52266a9 100644 --- a/app/src/main/java/top/fumiama/copymanga/net/DownloadTools.kt +++ b/app/src/main/java/top/fumiama/copymanga/net/DownloadTools.kt @@ -6,11 +6,11 @@ import com.google.gson.Gson import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext -import top.fumiama.copymanga.MainActivity import top.fumiama.copymanga.api.Config import top.fumiama.copymanga.api.Config.proxyUrl import top.fumiama.copymanga.json.ComandyCapsule import top.fumiama.copymanga.lib.Comandy +import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.InputStream import java.io.OutputStream @@ -21,61 +21,63 @@ import java.text.SimpleDateFormat import java.util.* import java.util.concurrent.Callable import java.util.concurrent.FutureTask -import java.util.concurrent.atomic.AtomicInteger +import java.util.zip.GZIPInputStream object DownloadTools { - val failTimes = AtomicInteger(0) - fun getApiConnection(url: String, method: String = "GET", refer: String? = null, ua: String? = null, timeout: Int = 20000): HttpURLConnection { + private fun getApiConnection(url: String, method: String = "GET", timeout: Int = 20000): HttpURLConnection { val connection = URL(url).openConnection() as HttpURLConnection connection.requestMethod = method connection.connectTimeout = timeout connection.readTimeout = timeout connection.apply { - /*setRequestProperty("host", if (url.startsWith("https://copymanga.azurewebsites.net")) { - Uri.parse(url).getQueryParameter("url")?.substringAfter("://")?.substringBefore("/")?:"" - } else { - url.substringAfter("://").substringBefore("/") - })*/ - ua?.let { setRequestProperty("user-agent", it) } - refer?.let { setRequestProperty("referer", it) } + setRequestProperty("user-agent", Config.pc_ua) setRequestProperty("source", "copyApp") + // deviceinfo setRequestProperty("webp", "1") - setRequestProperty("region", if(!Config.net_use_foreign.value) "1" else "0") - setRequestProperty("version", Config.app_ver.value) setRequestProperty("dt", SimpleDateFormat("yyyy.MM.dd", Locale.getDefault()).format(Calendar.getInstance().time)) - Config.token.value?.let { tk -> - setRequestProperty("authorization", "Token $tk") - } + setRequestProperty("accept-encoding", "gzip") + setRequestProperty("authorization", "Token${Config.token.value?.let { tk -> + if (tk.isNotEmpty()) " $tk" else "" + }}") setRequestProperty("platform", Config.platform.value) + setRequestProperty("referer", Config.referer) + setRequestProperty("accept", "application/json") + setRequestProperty("version", Config.app_ver.value) + setRequestProperty("region", if(!Config.net_use_foreign.value) "1" else "0") + // device + // host + Config.net_umstring.value.let { if (it.isNotEmpty()) setRequestProperty("umstring", it) } + setRequestProperty("connection", "close") } Log.d("MyDT", "getConnection: $url\n${connection.requestProperties.map { "${it.key}: ${it.value}" }.joinToString("\n")}") return connection } - fun getComandyApiConnection(url: String, method: String = "GET", refer: String? = null, ua: String? = null) = + private fun getComandyApiConnection(url: String, method: String = "GET") = run { val capsule = ComandyCapsule() capsule.url = url capsule.method = method capsule.headers = hashMapOf() - /*capsule.headers["host"] = if (url.startsWith("https://copymanga.azurewebsites.net")) { - Uri.parse(url).getQueryParameter("url")?.substringAfter("://")?.substringBefore("/")?:"" - } else { - url.substringAfter("://").substringBefore("/") - }*/ - ua?.let { capsule.headers["user-agent"] = it } - refer?.let { capsule.headers["referer"] = it } + capsule.headers["user-agent"] = Config.pc_ua capsule.headers["source"] = "copyApp" + // deviceinfo capsule.headers["webp"] = "1" - MainActivity.mainWeakReference?.get()?.let { - capsule.headers["region"] = if(!Config.net_use_foreign.value) "1" else "0" - capsule.headers["version"] = Config.app_ver.value - Config.token.value?.let { tk -> - capsule.headers["authorization"] = "Token $tk" - } - } - capsule.headers["platform"] = Config.platform.value capsule.headers["dt"] = SimpleDateFormat("yyyy.MM.dd", Locale.getDefault()).format(Calendar.getInstance().time) + capsule.headers["accept-encoding"] = "gzip" + capsule.headers["authorization"] = "Token${Config.token.value?.let { tk -> + if (tk.isNotEmpty()) " $tk" else "" + }}" + capsule.headers["platform"] = Config.platform.value + capsule.headers["referer"] = Config.referer + capsule.headers["accept"] = "application/json" + capsule.headers["version"] = Config.app_ver.value + capsule.headers["region"] = if(!Config.net_use_foreign.value) "1" else "0" + // device + // host + Config.net_umstring.value.let { if (it.isNotEmpty()) capsule.headers["umstring"] = it } + capsule.headers["connection"] = "close" + Log.d("MyDT", "getComandyConnection: $url\n${capsule.headers.map { "${it.key}: ${it.value}" }.joinToString("\n")}") capsule } @@ -123,16 +125,31 @@ object DownloadTools { return bytesCopied } + private fun decodeBody(ret: ByteArray, coding: String) : ByteArray { + return if (coding == "gzip") ByteArrayInputStream(ret).use { byteIn -> + GZIPInputStream(byteIn).use useGzip@ { gzipIn -> + ByteArrayOutputStream().use { byteOut -> + val buffer = ByteArray(4096) + var len: Int + while (gzipIn.read(buffer).also { len = it } != -1) { + byteOut.write(buffer, 0, len) + } + return@useGzip byteOut.toByteArray() + } + } + } else ret + } + private fun InputStream.readBytesWithProgress(sz: Int, p: Client.Progress): ByteArray { val buffer = ByteArrayOutputStream(maxOf(DEFAULT_BUFFER_SIZE, this.available())) copyToWithProgress(buffer, sz, p) return buffer.toByteArray() } - suspend fun getHttpContent(u: String, refer: String? = null, ua: String? = Config.pc_ua, p: Client.Progress? = null): ByteArray = + suspend fun getApiContent(u: String, p: Client.Progress? = null): ByteArray = withContext(Dispatchers.IO) { if (!u.startsWith("https://$proxyUrl") && Comandy.instance.enabled) { - getComandyApiConnection(u, "GET", refer, ua).let { capsule -> + getComandyApiConnection(u, "GET").let { capsule -> val para = Gson().toJson(capsule) //Log.d("MyDT", "comandy request: $para") Comandy.instance.getInstance()?.let { ins -> @@ -154,6 +171,7 @@ object DownloadTools { Log.d("MyDT", "quit comandy get progress, completed: $completed for url $u") }.start() } + var coding: String? = null val r = ins.request(para)?.let { result -> completed = true p?.notify(100) @@ -162,26 +180,29 @@ object DownloadTools { if (it.code != 200) throw IllegalArgumentException("HTTP${it.code} ${ it.data?.let { d -> Base64.decode(d, Base64.DEFAULT).decodeToString() } }") + coding = it.headers["Content-Encoding"] as String? Base64.decode(it.data, Base64.DEFAULT) } } completed = true p?.notify(100) - r + r?.let { ret -> coding?.let { decodeBody(ret, it) } } } }.let { if(it?.isNotEmpty() == true ) return@withContext it } - failTimes.incrementAndGet() } - getApiConnection(u, "GET", refer, ua).let { - val sz = it.getHeaderFieldInt("Content-Length", 0) + getApiConnection(u, "GET").let { conn -> + val sz = conn.getHeaderFieldInt("Content-Length", 0) val ret = if (sz > 0 && p != null) { - it.inputStream.readBytesWithProgress(sz, p) + conn.inputStream.readBytesWithProgress(sz, p) } else { - it.inputStream.readBytes() + conn.inputStream.readBytes() } - it.disconnect() + conn.disconnect() Log.d("MyDT", "getHttpContent: ${ret.size} bytes") - ret + if (conn.getHeaderField("Content-type") != "application/json") { + throw IllegalStateException("请求错误: ${ret.decodeToString()}") + } + decodeBody(ret, conn.getHeaderField("Content-Encoding")?:"") } } @@ -263,50 +284,33 @@ object DownloadTools { }) } - /*private fun replaceChineseCharacters(string: String?) : String? { - if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.M) return string - else return string?.replace(Regex("(?<=/)[\\w\\s\\d\\u4e00-\\u9fa5.-]+(?=/?)")) { match -> - return@replace URLEncoder.encode(match.value, "UTF-8") - } - }*/ - - fun requestWithBody(url: String, method: String, body: ByteArray, refer: String? = Config.referer, ua: String? = Config.pc_ua, contentType: String? = "application/x-www-form-urlencoded;charset=utf-8"): ByteArray? { + fun requestApiWithBody(url: String, method: String, body: ByteArray, contentType: String): ByteArray { Log.d("MyDT", "$method Http: $url") - var ret: ByteArray? = null - val task = FutureTask(if(!url.startsWith("https://$proxyUrl") && Comandy.instance.enabled) Callable{ - try { - val capsule = getComandyApiConnection(url, method, refer, ua) - contentType?.let { capsule.headers["content-type"] = it } - capsule.data = body.decodeToString() - runBlocking { Comandy.instance.getInstance() }?.request(Gson().toJson(capsule))?.let { result -> - Gson().fromJson(result, ComandyCapsule::class.java)?.let { - it.data?.let { d -> Base64.decode(d, Base64.DEFAULT) }?:"empty comandy data".encodeToByteArray() - } + return if(!url.startsWith("https://$proxyUrl") && Comandy.instance.enabled) { + val capsule = getComandyApiConnection(url, method) + capsule.headers["content-type"] = contentType + capsule.data = body.decodeToString() + runBlocking { Comandy.instance.getInstance() }?.request(Gson().toJson(capsule))?.let { result -> + Gson().fromJson(result, ComandyCapsule::class.java)?.let { c -> + c.data?.let { d -> + Base64.decode(d, Base64.DEFAULT).let { + (c.headers["Content-Encoding"] as String?)?.let { coding -> + decodeBody(it, coding) + }?:it + } + }?: throw IllegalStateException("empty comandy data") } - } catch (ex: Exception) { - ex.printStackTrace() - failTimes.incrementAndGet() - ex.message?.encodeToByteArray() + }?: throw IllegalStateException("no comandy") + } else { + var ret: ByteArray + var coding = "" + getApiConnection(url, method).apply { + outputStream.write(body) + ret = inputStream.readBytes() + disconnect() + coding = getHeaderField("Content-Encoding")?:"" } - } - else Callable { - try { - getApiConnection(url, method, refer, ua).apply { - outputStream.write(body) - ret = inputStream.readBytes() - disconnect() - } - } catch (ex: Exception) { - ex.printStackTrace() - } - return@Callable ret - }) - Thread(task).start() - return try { - task.get() - } catch (ex: Exception) { - ex.printStackTrace() - null + decodeBody(ret, coding) } } } 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 50309c1..789d698 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 @@ -12,9 +12,8 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import top.fumiama.copymanga.MainActivity.Companion.mainWeakReference -import top.fumiama.copymanga.json.ReturnBase import top.fumiama.copymanga.api.Config -import top.fumiama.copymanga.net.DownloadTools +import top.fumiama.copymanga.json.ReturnBase import java.io.File import java.security.MessageDigest @@ -77,16 +76,12 @@ open class AutoDownloadHandler( var cnt = 0 while (cnt++ <= 3) { try { - val data = Config.apiProxy?.comancry(url()) { - DownloadTools.getHttpContent(it) - }?:DownloadTools.getHttpContent(url()) + val data = Config.myHostApiUrl.get(url()) if(exit) return@withContext - val fi = data.inputStream() - val pass = setGsonItem(Gson().fromJson(fi.reader(), jsonClass)) + val pass = setGsonItem(Gson().fromJson(data, jsonClass)) if (pass && loadFromCache) { - cacheFile?.writeBytes(data) + cacheFile?.writeText(data) } - fi.close() if(!pass) { delay(2000) continue 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 b78c0ea..5f3cd76 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 @@ -5,7 +5,6 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.withContext import top.fumiama.copymanga.api.Config -import top.fumiama.copymanga.net.DownloadTools import kotlin.random.Random class PausableDownloader(private val url: String, private val waitMilliseconds: Long = 0, private val isApi: Boolean = true, private val whenFinish: (suspend (result: ByteArray)->Unit)? = null) { @@ -14,10 +13,8 @@ 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) { - DownloadTools.getHttpContent(it, Config.referer) - } else null)?:DownloadTools.getHttpContent(url, Config.referer) - whenFinish?.let { it(data) } + val data = Config.myHostApiUrl.get(url) + whenFinish?.let { it(data.encodeToByteArray()) } return@withContext true } catch (e: Exception) { e.printStackTrace() diff --git a/app/src/main/java/top/fumiama/copymanga/storage/PreferenceString.kt b/app/src/main/java/top/fumiama/copymanga/storage/PreferenceString.kt index bdde1bd..0ca6c8b 100644 --- a/app/src/main/java/top/fumiama/copymanga/storage/PreferenceString.kt +++ b/app/src/main/java/top/fumiama/copymanga/storage/PreferenceString.kt @@ -8,6 +8,8 @@ data class PreferenceString(private val key: String, private var default: String constructor(key: Int, default: String?, defaultID: Int): this( MainActivity.mainWeakReference?.get()?.getString(key) ?:"", default, defaultID) constructor(key: String, default: Int): this(key, null, default) + constructor(key: String, default: String): this(key, default, 0) + constructor(key: String): this(key, "") constructor(key: Int): this(key, "", 0) private val defaultField: String diff --git a/app/src/main/java/top/fumiama/copymanga/ui/book/BookFragment.kt b/app/src/main/java/top/fumiama/copymanga/ui/book/BookFragment.kt index e109bc7..93d70fa 100644 --- a/app/src/main/java/top/fumiama/copymanga/ui/book/BookFragment.kt +++ b/app/src/main/java/top/fumiama/copymanga/ui/book/BookFragment.kt @@ -153,6 +153,7 @@ class BookFragment: NoBackRefreshFragment(R.layout.fragment_book) { if (getBoolean("loadJson")) { getString("name")?.let { name -> try { + Log.d("MyBF", "loadFromCache name $name") book = Book(name, { return@Book getString(it) }, activity?.getExternalFilesDir("")!!) @@ -184,11 +185,17 @@ class BookFragment: NoBackRefreshFragment(R.layout.fragment_book) { } private suspend fun queryCollect() { - MainActivity.shelf?.query(book?.path!!)?.let { b -> - mBookHandler?.collect = b.results?.collect?:-2 - Log.d("MyBF", "get collect of ${book?.path} = ${mBookHandler?.collect}") - tic.text = b.results?.browse?.chapter_name?.let { name -> - getString(R.string.text_format_cloud_read_to).format(Chinese.fixEncodingIfNeeded(name)) + try { + MainActivity.shelf?.query(book?.path!!)?.let { b -> + mBookHandler?.collect = b.results?.collect?:-2 + Log.d("MyBF", "get collect of ${book?.path} = ${mBookHandler?.collect}") + tic.text = b.results?.browse?.chapter_name?.let { name -> + getString(R.string.text_format_cloud_read_to).format(Chinese.fixEncodingIfNeeded(name)) + } + } + } catch (e: Exception) { + activity?.runOnUiThread { + Toast.makeText(context, e.message, Toast.LENGTH_SHORT).show() } } } @@ -208,7 +215,7 @@ class BookFragment: NoBackRefreshFragment(R.layout.fragment_book) { } } book?.uuid?.let { uuid -> - this@BookFragment.lbbsub.setOnClickListener { + this@BookFragment.lbbsub?.setOnClickListener { lifecycleScope.launch clickLaunch@ { if (this@BookFragment.lbbsub.text != getString(R.string.button_sub)) { mBookHandler?.collect?.let { collect -> diff --git a/app/src/main/java/top/fumiama/copymanga/ui/cardflow/history/HistoryFragment.kt b/app/src/main/java/top/fumiama/copymanga/ui/cardflow/history/HistoryFragment.kt index 2728eff..de226b6 100644 --- a/app/src/main/java/top/fumiama/copymanga/ui/cardflow/history/HistoryFragment.kt +++ b/app/src/main/java/top/fumiama/copymanga/ui/cardflow/history/HistoryFragment.kt @@ -11,7 +11,7 @@ import top.fumiama.dmzj.copymanga.R @OptIn(ExperimentalStdlibApi::class) class HistoryFragment : InfoCardLoader(R.layout.fragment_history, R.id.action_nav_history_to_nav_book, isHistoryBook = true) { override fun getApiUrl() = - getString(R.string.historyApiUrl).format(Config.myHostApiUrl.random(), page * 21, Config.platform.value) + getString(R.string.historyApiUrl).format(page * 21, Config.platform.value) override fun onCreate(savedInstanceState: Bundle?) { if (MainActivity.member?.hasLogin != true) { diff --git a/app/src/main/java/top/fumiama/copymanga/ui/cardflow/newest/NewestFragment.kt b/app/src/main/java/top/fumiama/copymanga/ui/cardflow/newest/NewestFragment.kt index 4c79b06..7dd44c6 100644 --- a/app/src/main/java/top/fumiama/copymanga/ui/cardflow/newest/NewestFragment.kt +++ b/app/src/main/java/top/fumiama/copymanga/ui/cardflow/newest/NewestFragment.kt @@ -7,5 +7,5 @@ import top.fumiama.dmzj.copymanga.R @ExperimentalStdlibApi class NewestFragment : InfoCardLoader(R.layout.fragment_newest, R.id.action_nav_newest_to_nav_book, true) { override fun getApiUrl() = - getString(R.string.newestApiUrl).format(Config.myHostApiUrl.random(), page * 21, Config.platform.value) + getString(R.string.newestApiUrl).format(page * 21, Config.platform.value) } diff --git a/app/src/main/java/top/fumiama/copymanga/ui/cardflow/rank/RankFragment.kt b/app/src/main/java/top/fumiama/copymanga/ui/cardflow/rank/RankFragment.kt index 2c66f5b..90818bb 100644 --- a/app/src/main/java/top/fumiama/copymanga/ui/cardflow/rank/RankFragment.kt +++ b/app/src/main/java/top/fumiama/copymanga/ui/cardflow/rank/RankFragment.kt @@ -48,7 +48,6 @@ class RankFragment : InfoCardLoader(R.layout.fragment_rank, R.id.action_nav_rank override fun getApiUrl() = getString(R.string.rankApiUrl).format( - Config.myHostApiUrl.random(), page * 21, sortWay[sortValue], audienceWay[audience], diff --git a/app/src/main/java/top/fumiama/copymanga/ui/cardflow/recommend/RecFragment.kt b/app/src/main/java/top/fumiama/copymanga/ui/cardflow/recommend/RecFragment.kt index 89f7450..10d6115 100644 --- a/app/src/main/java/top/fumiama/copymanga/ui/cardflow/recommend/RecFragment.kt +++ b/app/src/main/java/top/fumiama/copymanga/ui/cardflow/recommend/RecFragment.kt @@ -7,5 +7,5 @@ import top.fumiama.dmzj.copymanga.R @ExperimentalStdlibApi class RecFragment : InfoCardLoader(R.layout.fragment_recommend, R.id.action_nav_recommend_to_nav_book, true) { override fun getApiUrl() = - getString(R.string.recommendApiUrl).format(Config.myHostApiUrl.random(), page * 21, Config.platform.value) + getString(R.string.recommendApiUrl).format(page * 21, Config.platform.value) } \ No newline at end of file diff --git a/app/src/main/java/top/fumiama/copymanga/ui/cardflow/search/SearchFragment.kt b/app/src/main/java/top/fumiama/copymanga/ui/cardflow/search/SearchFragment.kt index eb1999d..df7df04 100644 --- a/app/src/main/java/top/fumiama/copymanga/ui/cardflow/search/SearchFragment.kt +++ b/app/src/main/java/top/fumiama/copymanga/ui/cardflow/search/SearchFragment.kt @@ -13,7 +13,7 @@ class SearchFragment : InfoCardLoader(R.layout.fragment_search, R.id.action_nav_ private var query: String? = null private var type: String? = null override fun getApiUrl() = - getString(R.string.searchApiUrl).format(Config.myHostApiUrl.random(), page * 21, query, type, Config.platform.value) + getString(R.string.searchApiUrl).format(page * 21, query, type, Config.platform.value) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/app/src/main/java/top/fumiama/copymanga/ui/cardflow/shelf/ShelfFragment.kt b/app/src/main/java/top/fumiama/copymanga/ui/cardflow/shelf/ShelfFragment.kt index 90a682d..2e9d74a 100644 --- a/app/src/main/java/top/fumiama/copymanga/ui/cardflow/shelf/ShelfFragment.kt +++ b/app/src/main/java/top/fumiama/copymanga/ui/cardflow/shelf/ShelfFragment.kt @@ -26,7 +26,6 @@ class ShelfFragment : InfoCardLoader(R.layout.fragment_shelf, R.id.action_nav_su override fun getApiUrl() = getString(R.string.shelfApiUrl).format( - Config.myHostApiUrl.random(), page * 21, sortWay[sortValue], Config.platform.value, diff --git a/app/src/main/java/top/fumiama/copymanga/ui/cardflow/sort/SortFragment.kt b/app/src/main/java/top/fumiama/copymanga/ui/cardflow/sort/SortFragment.kt index 6a758ee..1ea46c2 100644 --- a/app/src/main/java/top/fumiama/copymanga/ui/cardflow/sort/SortFragment.kt +++ b/app/src/main/java/top/fumiama/copymanga/ui/cardflow/sort/SortFragment.kt @@ -25,7 +25,6 @@ class SortFragment : StatusCardFlow(0, R.id.action_nav_sort_to_nav_book, R.layou override fun getApiUrl() = getString(R.string.sortApiUrl).format( - Config.myHostApiUrl.random(), page * 21, sortWay[sortValue], if(theme >= 0 && theme < (filter?.results?.theme?.size ?: 0)) (filter?.results?.theme?.get(theme)?.path_word ?: "") else "", @@ -43,7 +42,7 @@ class SortFragment : StatusCardFlow(0, R.id.action_nav_sort_to_nav_book, R.layou super.setListeners() lifecycleScope.launch { setProgress(5) - PausableDownloader(getString(R.string.filterApiUrl).format(Config.myHostApiUrl.random(), Config.platform.value)) { + PausableDownloader(getString(R.string.filterApiUrl).format(Config.platform.value)) { if(ad?.exit == true) return@PausableDownloader it.let { it.inputStream().use { i -> diff --git a/app/src/main/java/top/fumiama/copymanga/ui/cardflow/topic/TopicFragment.kt b/app/src/main/java/top/fumiama/copymanga/ui/cardflow/topic/TopicFragment.kt index 15521b5..99be0c0 100644 --- a/app/src/main/java/top/fumiama/copymanga/ui/cardflow/topic/TopicFragment.kt +++ b/app/src/main/java/top/fumiama/copymanga/ui/cardflow/topic/TopicFragment.kt @@ -18,13 +18,13 @@ import top.fumiama.dmzj.copymanga.R class TopicFragment : InfoCardLoader(R.layout.fragment_topic, R.id.action_nav_topic_to_nav_book) { private var type = 1 override fun getApiUrl() = - getString(R.string.topicContentApiUrl).format(Config.myHostApiUrl.random(), arguments?.getString("path"), type, offset, Config.platform.value) + getString(R.string.topicContentApiUrl).format(arguments?.getString("path"), type, offset, Config.platform.value) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) lifecycleScope.launch { setProgress(5) - PausableDownloader(getString(R.string.topicApiUrl).format(Config.myHostApiUrl.random(), arguments?.getString("path"), Config.platform.value)) { data -> + PausableDownloader(getString(R.string.topicApiUrl).format(arguments?.getString("path"), Config.platform.value)) { data -> setProgress(10) withContext(Dispatchers.IO) { if(ad?.exit == true) return@withContext diff --git a/app/src/main/java/top/fumiama/copymanga/ui/download/DownloadFragment.kt b/app/src/main/java/top/fumiama/copymanga/ui/download/DownloadFragment.kt index 1a28c37..5409876 100644 --- a/app/src/main/java/top/fumiama/copymanga/ui/download/DownloadFragment.kt +++ b/app/src/main/java/top/fumiama/copymanga/ui/download/DownloadFragment.kt @@ -118,7 +118,6 @@ class DownloadFragment: NoBackRefreshFragment(R.layout.fragment_download) { bundle.putBoolean("callFromOldDL", true) } bundle.putString("name", jsonFile.parentFile?.name?:"Null") - Log.d("MyDF", "root view: $rootView") Log.d("MyDF", "action_nav_download_to_nav_group") Navigate.safeNavigateTo(findNavController(), R.id.action_nav_download_to_nav_group, bundle) } @@ -128,7 +127,6 @@ class DownloadFragment: NoBackRefreshFragment(R.layout.fragment_download) { bundle.putString("title", title) bundle.putString("file", file.absolutePath) Log.d("MyDF", "Call self to $title") - Log.d("MyDF", "root view: $rootView") Log.d("MyDF", "action_nav_download_self") Navigate.safeNavigateTo(findNavController(), R.id.action_nav_download_self, bundle) } diff --git a/app/src/main/java/top/fumiama/copymanga/ui/download/NewDownloadFragment.kt b/app/src/main/java/top/fumiama/copymanga/ui/download/NewDownloadFragment.kt index 3a9a2da..abbbf8a 100644 --- a/app/src/main/java/top/fumiama/copymanga/ui/download/NewDownloadFragment.kt +++ b/app/src/main/java/top/fumiama/copymanga/ui/download/NewDownloadFragment.kt @@ -244,7 +244,6 @@ class NewDownloadFragment: MangaPagesFragmentTemplate(R.layout.fragment_newdownl Log.d("MyNDF", "Call dl and is new.") bundle.putString("loadJson", File(File(extDir, name), "info.json").readText()) bundle.putString("name", name) - Log.d("MyNDF", "root view: $rootView") Log.d("MyNDF", "action_nav_new_download_to_nav_group") Navigate.safeNavigateTo(findNavController(), R.id.action_nav_new_download_to_nav_group, bundle) } diff --git a/app/src/main/java/top/fumiama/copymanga/ui/home/HomeFragment.kt b/app/src/main/java/top/fumiama/copymanga/ui/home/HomeFragment.kt index bce45a5..a05694c 100644 --- a/app/src/main/java/top/fumiama/copymanga/ui/home/HomeFragment.kt +++ b/app/src/main/java/top/fumiama/copymanga/ui/home/HomeFragment.kt @@ -51,6 +51,7 @@ class HomeFragment : NoBackRefreshFragment(R.layout.fragment_home) { val netInfo = tb.netInfo if(netInfo != tb.transportStringNull && netInfo != tb.transportStringError) MainActivity.member?.apply { lifecycleScope.launch { + Config.myHostApiUrl.init() info().let { l -> if (l.code != 200 && l.code != 449) { Toast.makeText(context, l.message, Toast.LENGTH_SHORT).show() @@ -298,7 +299,7 @@ class HomeFragment : NoBackRefreshFragment(R.layout.fragment_home) { suspend fun refresh(q: CharSequence) = withContext(Dispatchers.IO) { query = q.toString() activity?.apply { - PausableDownloader(getString(R.string.searchApiUrl).format(Config.myHostApiUrl.random(), 0, + PausableDownloader(getString(R.string.searchApiUrl).format(0, URLEncoder.encode(q.toString(), Charset.defaultCharset().name()), type, Config.platform.value)) { results = Gson().fromJson(it.decodeToString(), BookListStructure::class.java) count = results?.results?.total?:0 diff --git a/app/src/main/java/top/fumiama/copymanga/ui/home/HomeHandler.kt b/app/src/main/java/top/fumiama/copymanga/ui/home/HomeHandler.kt index fb711bc..044cfe7 100644 --- a/app/src/main/java/top/fumiama/copymanga/ui/home/HomeHandler.kt +++ b/app/src/main/java/top/fumiama/copymanga/ui/home/HomeHandler.kt @@ -40,7 +40,7 @@ import java.lang.ref.WeakReference import java.util.concurrent.atomic.AtomicInteger class HomeHandler(private val that: WeakReference) : AutoDownloadHandler({ - that.get()?.getString(R.string.mainPageApiUrl)!!.format(Config.myHostApiUrl.random(), Config.platform.value) + that.get()?.getString(R.string.mainPageApiUrl)!!.format(Config.platform.value) }, IndexStructure::class.java, that.get() diff --git a/app/src/main/java/top/fumiama/copymanga/view/template/NoBackRefreshFragment.kt b/app/src/main/java/top/fumiama/copymanga/view/template/NoBackRefreshFragment.kt index d9ec5cb..3e2f899 100644 --- a/app/src/main/java/top/fumiama/copymanga/view/template/NoBackRefreshFragment.kt +++ b/app/src/main/java/top/fumiama/copymanga/view/template/NoBackRefreshFragment.kt @@ -25,6 +25,7 @@ open class NoBackRefreshFragment(private val layoutToLoad: Int): Fragment() { container: ViewGroup?, savedInstanceState: Bundle? ): View? { + //TODO: 支持自动重建 if(_rootView == null) { isFirstInflate = true _rootView = inflater.inflate(layoutToLoad, container, false) diff --git a/app/src/main/java/top/fumiama/copymanga/view/template/StatusCardFlow.kt b/app/src/main/java/top/fumiama/copymanga/view/template/StatusCardFlow.kt index ca9f636..89cfd11 100644 --- a/app/src/main/java/top/fumiama/copymanga/view/template/StatusCardFlow.kt +++ b/app/src/main/java/top/fumiama/copymanga/view/template/StatusCardFlow.kt @@ -18,7 +18,6 @@ open class StatusCardFlow(private val api: Int, nav: Int, inflateRes: Int, override fun getApiUrl() = getString(api).format( - Config.myHostApiUrl.random(), page * 21, sortWay[sortValue], Config.platform.value, diff --git a/app/src/main/java/top/fumiama/copymanga/view/template/ThemeCardFlow.kt b/app/src/main/java/top/fumiama/copymanga/view/template/ThemeCardFlow.kt index 8b14658..fbcab19 100644 --- a/app/src/main/java/top/fumiama/copymanga/view/template/ThemeCardFlow.kt +++ b/app/src/main/java/top/fumiama/copymanga/view/template/ThemeCardFlow.kt @@ -12,7 +12,6 @@ open class ThemeCardFlow(private val api: Int, nav: Int) : StatusCardFlow(0, nav private var theme = "" override fun getApiUrl() = getString(api).format( - Config.myHostApiUrl.random(), page * 21, sortWay[sortValue], theme, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0ee0956..5d2e005 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3,7 +3,7 @@ - + ]> 拷贝漫画 @@ -64,31 +64,30 @@ 预载图片头失败 读取图片大小失败 - https://%1$s/api/v3/system/network2?platform=%2$s - https://%1$s/api/v3/h5/homeIndex?platform=%2$s - https://%1$s + /api/v3/system/network2?platform=%1$s + /api/v3/h5/homeIndex?platform=%1$s &hosturl; &proxyurl; - https://%1$s/api/v3/ranks?limit=21&offset=%2$d&date_type=%3$s&audience_type=%4$s&platform=%5$s - https://%1$s/api/v3/search/comic?limit=21&offset=%2$d&q=%3$s&q_type=%4$s&platform=%5$s - https://%1$s/api/v3/h5/filter/comic/tags?platform=%2$s - https://%1$s/api/v3/comics?limit=21&offset=%2$d&ordering=%3$s&theme=%4$s&top=%5$s&platform=%6$s - https://%1$s/api/v3/comic2/%2$s?platform=%3$s - https://%1$s/api/v3/comic2/%2$s/query?platform=%3$s - https://%1$s/api/v3/comic/%2$s/group/%3$s/chapters?limit=100&offset=%4$d&platform=%5$s - https://%1$s/api/v3/comic/%2$s/chapter%3$s/%4$s?platform=%5$s - https://%1$s/api/v3/topic/%2$s?platform=%3$s - https://%1$s/api/v3/topic/%2$s/contents?type=%3$d&limit=21&offset=%4$d&platform=%5$s - https://%1$s/api/v3/recs?pos=3200102&limit=21&offset=%2$d&platform=%3$s - https://%1$s/api/v3/update/newest?limit=21&offset=%2$d&platform=%3$s - https://%1$s/api/v3/comics?limit=21&offset=%2$d&ordering=%3$s&top=finish&platform=%4$s - https://%1$s/api/v3/comics?limit=21&offset=%2$d&ordering=%3$s&author=%4$s&platform=%5$s - https://%1$s/api/v3/comics?limit=21&offset=%2$d&ordering=%3$s&theme=%4$s&platform=%5$s - https://%1$s/api/v3/login?platform=%2$s - https://%1$s/api/v3/member/info?platform=%2$s - https://%1$s/api/v3/member/browse/comics?limit=21&offset=%2$d&platform=%3$s - https://%1$s/api/v3/member/collect/comics?limit=21&offset=%2$d&free_type=1&ordering=%3$s&platform=%4$s - https://%1$s/api/v3/member/collect/comic + /api/v3/ranks?limit=21&offset=%1$d&date_type=%2$s&audience_type=%3$s&platform=%4$s + /api/v3/search/comic?limit=21&offset=%1$d&q=%2$s&q_type=%3$s&platform=%4$s + /api/v3/h5/filter/comic/tags?platform=%1$s + /api/v3/comics?limit=21&offset=%1$d&ordering=%2$s&theme=%3$s&top=%4$s&platform=%5$s + /api/v3/comic2/%1$s?platform=%2$s + /api/v3/comic2/%1$s/query?platform=%2$s + /api/v3/comic/%1$s/group/%2$s/chapters?limit=100&offset=%3$d&platform=%4$s + /api/v3/comic/%1$s/chapter%2$s/%3$s?platform=%4$s + /api/v3/topic/%1$s?platform=%2$s + /api/v3/topic/%1$s/contents?type=%2$d&limit=21&offset=%3$d&platform=%4$s + /api/v3/recs?pos=3200102&limit=21&offset=%1$d&platform=%2$s + /api/v3/update/newest?limit=21&offset=%1$d&platform=%2$s + /api/v3/comics?limit=21&offset=%1$d&ordering=%2$s&top=finish&platform=%3$s + /api/v3/comics?limit=21&offset=%1$d&ordering=%2$s&author=%3$s&platform=%4$s + /api/v3/comics?limit=21&offset=%1$d&ordering=%2$s&theme=%3$s&platform=%4$s + /api/v3/login?platform=%1$s + /api/v3/member/info?platform=%1$s + /api/v3/member/browse/comics?limit=21&offset=%1$d&platform=%2$s + /api/v3/member/collect/comics?limit=21&offset=%1$d&free_type=1&ordering=%2$s&platform=%3$s + /api/v3/member/collect/comic https://&proxyurl;/api/img?code=%1$s&url=%2$s settings_cat_net_et_img_proxy_code @@ -178,6 +177,8 @@ 一般无需更改,除非拷贝漫画官方更改网址,默认:&hosturl; 反向代理 您可以自建反向代理并填写在此处以解决网络不畅 + 友盟ID + 填写您分配到的友盟ID 使用API代理(需要密钥) 作者自建的API代理,可缓解国内图书详情加载问题,但不保证100%解决,也不保证一直可用 使用图床代理(需要密钥) @@ -207,7 +208,6 @@ 用户名为空 密码为空 - 登录失败 解析返回数据失败 获取用户信息失败 重启应用以彻底退出登录 diff --git a/app/src/main/res/xml/pref_setting.xml b/app/src/main/res/xml/pref_setting.xml index 9c3153e..6236b87 100644 --- a/app/src/main/res/xml/pref_setting.xml +++ b/app/src/main/res/xml/pref_setting.xml @@ -120,6 +120,16 @@ app:enableCopying="true" app:iconSpaceReserved="false" app:key="@string/reverseProxyKeyID" /> +