diff --git a/.idea/dictionaries/fumiama.xml b/.idea/dictionaries/fumiama.xml index 51ac377..e9c54e3 100644 --- a/.idea/dictionaries/fumiama.xml +++ b/.idea/dictionaries/fumiama.xml @@ -2,10 +2,12 @@ alphae + comandy downloaders grps imgs kohima + libcomandy lowpan mangacopy mangafuna diff --git a/app/build.gradle b/app/build.gradle index 2134a1c..b216064 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,7 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-kapt' android { defaultConfig { @@ -8,8 +9,8 @@ android { applicationId 'top.fumiama.copymanga' minSdkVersion 23 targetSdkVersion 34 - versionCode 57 - versionName '2.2.9' + versionCode 58 + versionName '2.3.0' resourceConfigurations += ['zh', 'zh-rCN'] testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -46,6 +47,11 @@ android { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' }*/ } + sourceSets { + main { + jniLibs.srcDirs = ['libs'] + } + } compileOptions { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 @@ -78,7 +84,7 @@ dependencies { implementation 'com.github.yalantis:ucrop:2.2.6' implementation 'com.to.aboomy:pager2banner:1.0.1' implementation 'com.github.bumptech.glide:glide:4.16.0' - annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0' + kapt 'com.github.bumptech.glide:compiler:4.16.0' implementation 'com.google.code.gson:gson:2.10.1' implementation 'com.github.vovaksenov99:OverscrollableScrollView:1.0' implementation 'com.liaoinstan.springview:library:1.7.0' @@ -86,4 +92,5 @@ dependencies { implementation 'com.lapism:search:2.4.1@aar' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0' implementation 'com.airbnb.android:lottie:6.4.0' + implementation 'net.java.dev.jna:jna:5.14.0' } diff --git a/app/libs/arm64-v8a/libjnidispatch.so b/app/libs/arm64-v8a/libjnidispatch.so new file mode 100644 index 0000000..cd7e671 Binary files /dev/null and b/app/libs/arm64-v8a/libjnidispatch.so differ diff --git a/app/libs/armeabi-v7a/libjnidispatch.so b/app/libs/armeabi-v7a/libjnidispatch.so new file mode 100644 index 0000000..603883e Binary files /dev/null and b/app/libs/armeabi-v7a/libjnidispatch.so differ diff --git a/app/libs/x86/libjnidispatch.so b/app/libs/x86/libjnidispatch.so new file mode 100644 index 0000000..e9094b6 Binary files /dev/null and b/app/libs/x86/libjnidispatch.so differ diff --git a/app/libs/x86_64/libjnidispatch.so b/app/libs/x86_64/libjnidispatch.so new file mode 100644 index 0000000..e594b88 Binary files /dev/null and b/app/libs/x86_64/libjnidispatch.so differ diff --git a/app/src/main/java/top/fumiama/copymanga/json/ComandyCapsule.java b/app/src/main/java/top/fumiama/copymanga/json/ComandyCapsule.java new file mode 100644 index 0000000..c397ad4 --- /dev/null +++ b/app/src/main/java/top/fumiama/copymanga/json/ComandyCapsule.java @@ -0,0 +1,11 @@ +package top.fumiama.copymanga.json; + +import java.util.HashMap; + +public class ComandyCapsule { + public int code; + public String method; + public String url; + public HashMap headers; + public String data; +} diff --git a/app/src/main/java/top/fumiama/copymanga/json/ComandyVersion.java b/app/src/main/java/top/fumiama/copymanga/json/ComandyVersion.java new file mode 100644 index 0000000..3f6c9c7 --- /dev/null +++ b/app/src/main/java/top/fumiama/copymanga/json/ComandyVersion.java @@ -0,0 +1,5 @@ +package top.fumiama.copymanga.json; + +public class ComandyVersion { + public String body; +} diff --git a/app/src/main/java/top/fumiama/copymanga/template/general/NoBackRefreshFragment.kt b/app/src/main/java/top/fumiama/copymanga/template/general/NoBackRefreshFragment.kt index b3aaaff..e77c513 100644 --- a/app/src/main/java/top/fumiama/copymanga/template/general/NoBackRefreshFragment.kt +++ b/app/src/main/java/top/fumiama/copymanga/template/general/NoBackRefreshFragment.kt @@ -10,9 +10,7 @@ import androidx.core.animation.doOnEnd import androidx.fragment.app.Fragment import androidx.preference.PreferenceManager import kotlinx.android.synthetic.main.content_main.* -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withContext import top.fumiama.copymanga.MainActivity import top.fumiama.copymanga.tools.ui.UITools import java.util.concurrent.atomic.AtomicBoolean @@ -48,8 +46,8 @@ open class NoBackRefreshFragment(private val layoutToLoad: Int): Fragment() { isFirstInflate = true Log.d("MyNBRF", "destroyed") } - suspend fun showKanban() = withContext(Dispatchers.Main) { - if (disableAnimation) return@withContext + fun showKanban() { + if (disableAnimation) return (activity?:(MainActivity.mainWeakReference?.get()))?.apply {cmaini?.post { if(cmaini?.visibility == View.GONE) { Log.d("MyNBRF", "show: start, set h: ${window?.decorView?.height}") @@ -62,8 +60,8 @@ open class NoBackRefreshFragment(private val layoutToLoad: Int): Fragment() { Log.d("MyNBRF", "show: end") } private var isHideRunning = AtomicBoolean() - suspend fun hideKanban() = withContext(Dispatchers.Main) { - if (disableAnimation) return@withContext + fun hideKanban() { + if (disableAnimation) return (activity?:(MainActivity.mainWeakReference?.get()))?.apply { cmaini?.post { if(!isHideRunning.get() && cmaini?.visibility == View.VISIBLE) { isHideRunning.set(true) diff --git a/app/src/main/java/top/fumiama/copymanga/tools/http/Comandy.kt b/app/src/main/java/top/fumiama/copymanga/tools/http/Comandy.kt new file mode 100644 index 0000000..075b691 --- /dev/null +++ b/app/src/main/java/top/fumiama/copymanga/tools/http/Comandy.kt @@ -0,0 +1,117 @@ +package top.fumiama.copymanga.tools.http + +import android.os.Build +import android.util.Log +import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.edit +import androidx.preference.PreferenceManager +import com.google.gson.Gson +import com.sun.jna.Library +import com.sun.jna.Native +import top.fumiama.copymanga.MainActivity +import top.fumiama.copymanga.json.ComandyVersion +import top.fumiama.dmzj.copymanga.R +import java.io.ByteArrayInputStream +import java.io.File +import java.util.concurrent.atomic.AtomicBoolean +import java.util.zip.GZIPInputStream + +interface Comandy : Library { + // fun add_dns(para: String?, is_ipv6: Int): String? + + fun request(para: String): String? + + companion object { + private var isInInit = AtomicBoolean(false) + var instance: Comandy? = null + get() { + //Log.d("MyComandy", "get instance @$field") + if (field != null) return field + field = libraryFile?.absolutePath?.let { Native.load(it, Comandy::class.java) }?:return null + //Log.d("MyComandy", "init instance @$field") + return field + } + private var mUseComandy: Boolean? = null + val useComandy: Boolean + get() { + if (isInInit.get()) { + Log.d("MyComandy", "block useComandy for isInInit") + return false + } + if (mUseComandy != true && DownloadTools.failTimes.get() >= 2) { + mUseComandy = true + return true + } + if (mUseComandy != null) return mUseComandy!! + MainActivity.mainWeakReference?.get()?.let { + PreferenceManager.getDefaultSharedPreferences(it).apply { + val b = getBoolean("settings_cat_net_sw_use_comandy", false) + Log.d("MyComandy", "use comandy: $b") + mUseComandy = b + return b + } + } + mUseComandy = false + return false + } + private val libraryFile: File? + get() { + if (isInInit.get()) return null + isInInit.set(true) + Log.d("MyComandy", "start to download/check lib") + val prefix = when (Build.SUPPORTED_ABIS[0]) { + "arm64-v8a" -> "aarch64" + "armeabi-v7a" -> "armv7a" + "x86_64" -> "x86_64" + "x86" -> "i686" + else -> null + }?:return null + Log.d("MyComandy", "arch: $prefix") + return MainActivity.mainWeakReference?.get()?.let { ma -> + var f = File(ma.filesDir, "libs") + if (!f.exists()) f.mkdirs() + f = File(f, "libcomandy.so") + var remoteVersion = 0 + if (f.exists()) { + DownloadTools.getHttpContent(ma.getString(R.string.comandy_version_url), -1)?.let dataLet@{ + try { + val body = Gson().fromJson(it.decodeToString(), ComandyVersion::class.java)?.body + if (body?.startsWith("Version: ") == true) { + remoteVersion = body.substring(9).toInt() + } + } catch (e: Exception) { + e.printStackTrace() + } + val myVersion = ma.getPreferences(AppCompatActivity.MODE_PRIVATE).getInt("comandy_version", 0) + if (myVersion >= remoteVersion) { + Log.d("MyComandy", "lib version $myVersion is latest") + isInInit.set(false) + return@let f + } + Log.d("MyComandy", "lib version $myVersion <= latest $remoteVersion, update...") + } + } + DownloadTools.getHttpContent(ma.getString(R.string.comandy_download_url).format(prefix), -1)?.let { + if(f.exists()) f.delete() + try { + GZIPInputStream(ByteArrayInputStream(it)).use { dataIn -> + f.outputStream().use { dataOut -> + dataIn.copyTo(dataOut) + } + } + if (remoteVersion > 0) ma.getPreferences(AppCompatActivity.MODE_PRIVATE).edit { + putInt("comandy_version", remoteVersion) + apply() + } + Log.d("MyComandy", "update success") + isInInit.set(false) + } catch (e: Exception) { + e.printStackTrace() + if(f.exists()) f.delete() + } + } + return@let if(f.exists()) f else null + } + } + } +} diff --git a/app/src/main/java/top/fumiama/copymanga/tools/http/ComandyGlideModule.kt b/app/src/main/java/top/fumiama/copymanga/tools/http/ComandyGlideModule.kt new file mode 100644 index 0000000..fa67fa6 --- /dev/null +++ b/app/src/main/java/top/fumiama/copymanga/tools/http/ComandyGlideModule.kt @@ -0,0 +1,129 @@ +package top.fumiama.copymanga.tools.http + +import android.content.Context +import android.util.Base64 +import android.util.Log +import com.bumptech.glide.Glide +import com.bumptech.glide.Priority +import com.bumptech.glide.Registry +import com.bumptech.glide.annotation.GlideModule +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.Options +import com.bumptech.glide.load.data.DataFetcher +import com.bumptech.glide.load.model.GlideUrl +import com.bumptech.glide.load.model.ModelLoader +import com.bumptech.glide.load.model.ModelLoaderFactory +import com.bumptech.glide.load.model.MultiModelLoaderFactory +import com.bumptech.glide.module.AppGlideModule +import com.bumptech.glide.signature.ObjectKey +import com.google.gson.Gson +import top.fumiama.copymanga.MainActivity +import top.fumiama.copymanga.json.ComandyCapsule +import java.nio.ByteBuffer + +@GlideModule +class ComandyGlideModule: AppGlideModule() { + override fun registerComponents(context: Context, glide: Glide, registry: Registry) { + super.registerComponents(context, glide, registry) + registry.prepend(GlideUrl::class.java, ByteBuffer::class.java, ComandyGlideUrlFactory()) + registry.prepend(String::class.java, ByteBuffer::class.java, ComandyStringFactory()) + } + + inner class ComandyDataFetcher(private val model: GlideUrl): DataFetcher { + constructor(model: String): this(GlideUrl(model)) + override fun loadData( + priority: Priority, + callback: DataFetcher.DataCallback + ) { + val capsule = ComandyCapsule() + capsule.url = model.toStringUrl() + capsule.method = "GET" + if (model.headers.isNotEmpty()) { + capsule.headers = hashMapOf() + model.headers.forEach { (k, v) -> capsule.headers[k] = v } + } + try { + val para = Gson().toJson(capsule) + MainActivity.mainWeakReference?.get()?.runOnUiThread { + Log.d("MyCGM", "request: $para") + } + Comandy.instance!!.request(para).let { result -> + Gson().fromJson(result, ComandyCapsule::class.java)!!.let { + if (it.code != 200) { + callback.onLoadFailed(IllegalArgumentException("HTTP${it.code} ${ + it.data?.let { d -> Base64.decode(d, Base64.DEFAULT).decodeToString() } + }")) + return + } + callback.onDataReady(ByteBuffer.wrap(Base64.decode(it.data, Base64.DEFAULT))) + } + } + } catch (e: Exception) { + callback.onLoadFailed(e) + } + } + + override fun cleanup() { } + + override fun cancel() { } + + override fun getDataClass(): Class { + return ByteBuffer::class.java + } + + override fun getDataSource(): DataSource { + return DataSource.REMOTE + } + + } + + inner class ComandyGlideUrlModelLoader: ModelLoader { + override fun buildLoadData( + model: GlideUrl, + width: Int, + height: Int, + options: Options + ): ModelLoader.LoadData { + return ModelLoader.LoadData(ObjectKey(model), ComandyDataFetcher(model)) + } + + override fun handles(model: GlideUrl): Boolean { + return Comandy.useComandy && Comandy.instance != null && model.toURL().let { it.protocol == "https" } + } + + } + + inner class ComandyStringModelLoader: ModelLoader { + override fun buildLoadData( + model: String, + width: Int, + height: Int, + options: Options + ): ModelLoader.LoadData { + return ModelLoader.LoadData(ObjectKey(model), ComandyDataFetcher(model)) + } + + override fun handles(model: String): Boolean { + return Comandy.useComandy && Comandy.instance != null && model.startsWith("https://") + } + + } + + inner class ComandyGlideUrlFactory: ModelLoaderFactory { + override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader { + return ComandyGlideUrlModelLoader() + } + + override fun teardown() { } + + } + + inner class ComandyStringFactory: ModelLoaderFactory { + override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader { + return ComandyStringModelLoader() + } + + override fun teardown() { } + + } +} \ No newline at end of file diff --git a/app/src/main/java/top/fumiama/copymanga/tools/http/DownloadTools.kt b/app/src/main/java/top/fumiama/copymanga/tools/http/DownloadTools.kt index 32f3ae9..9159906 100644 --- a/app/src/main/java/top/fumiama/copymanga/tools/http/DownloadTools.kt +++ b/app/src/main/java/top/fumiama/copymanga/tools/http/DownloadTools.kt @@ -1,16 +1,21 @@ package top.fumiama.copymanga.tools.http import android.content.Context +import android.util.Base64 import android.util.Log import androidx.preference.PreferenceManager +import com.google.gson.Gson import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import top.fumiama.copymanga.MainActivity +import top.fumiama.copymanga.json.ComandyCapsule import top.fumiama.dmzj.copymanga.R +import java.lang.IllegalArgumentException import java.net.HttpURLConnection import java.net.URL import java.util.concurrent.Callable import java.util.concurrent.FutureTask +import java.util.concurrent.atomic.AtomicInteger object DownloadTools { val app_ver = MainActivity.mainWeakReference?.get()?.let { main -> @@ -20,6 +25,7 @@ object DownloadTools { }!! val pc_ua = MainActivity.mainWeakReference?.get()!!.getString(R.string.pc_ua).format(app_ver) val referer = MainActivity.mainWeakReference?.get()!!.getString(R.string.referer).format(app_ver) + val failTimes = AtomicInteger(0) fun getApiConnection(url: String, method: String = "GET", refer: String? = null, ua: String? = null, timeout: Int = 20000) = url.let { val connection = URL(url).openConnection() as HttpURLConnection @@ -45,10 +51,37 @@ object DownloadTools { } setRequestProperty("platform", "3") } - Log.d("Mydl", "getConnection: $url\n${connection.requestProperties.map { "${it.key}: ${it.value}" }.joinToString("\n")}") + Log.d("MyDT", "getConnection: $url\n${connection.requestProperties.map { "${it.key}: ${it.value}" }.joinToString("\n")}") connection } + fun getComandyApiConnection(url: String, method: String = "GET", refer: String? = null, ua: String? = null) = + run { + val capsule = ComandyCapsule() + capsule.url = url + capsule.method = method + capsule.headers = hashMapOf() + capsule.headers["host"] = url.substringAfter("://").substringBefore("/") + ua?.let { capsule.headers["user-agent"] = it } + refer?.let { capsule.headers["referer"] = it } + capsule.headers["source"] = "copyApp" + capsule.headers["webp"] = "1" + MainActivity.mainWeakReference?.get()?.let { + PreferenceManager.getDefaultSharedPreferences(it).apply { + capsule.headers["region"] = if(!getBoolean("settings_cat_net_sw_use_foreign", false)) "1" else "0" + } + it.getPreferences(Context.MODE_PRIVATE).apply { + capsule.headers["version"] = app_ver + getString("token", "")?.let { tk -> + capsule.headers["authorization"] = "Token $tk" + } + } + } + capsule.headers["platform"] = "3" + Log.d("MyDT", "getComandyConnection: $url\n${capsule.headers.map { "${it.key}: ${it.value}" }.joinToString("\n")}") + capsule + } + private fun getNormalConnection(url: String, method: String = "GET", ua: String? = null, timeout: Int = 20000) = url.let { val connection = URL(url).openConnection() as HttpURLConnection @@ -61,20 +94,75 @@ object DownloadTools { } } + private fun getComandyNormalConnection(url: String, method: String = "GET", ua: String? = null) = + run { + val capsule = ComandyCapsule() + capsule.url = url + capsule.method = method + capsule.headers = hashMapOf() + capsule.headers["host"] = url.substringAfter("://").substringBefore("/") + ua?.let { capsule.headers["user-agent"] = it } + capsule + } + suspend fun getHttpContent(u: String, refer: String? = null, ua: String? = null): ByteArray = withContext(Dispatchers.IO) { + if (Comandy.useComandy) { + getComandyApiConnection(u, "GET", refer, ua).let { capsule -> + val para = Gson().toJson(capsule) + //Log.d("MyDT", "comandy request: $para") + Comandy.instance?.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) + } + } + }.let { if(it?.isNotEmpty() == true ) return@withContext it } + } + failTimes.incrementAndGet() getApiConnection(u, "GET", refer, ua).let { val ret = it.inputStream.readBytes() it.disconnect() - Log.d("Mydl", "getHttpContent: ${ret.size} bytes") + Log.d("MyDT", "getHttpContent: ${ret.size} bytes") + failTimes.decrementAndGet() ret } } fun getHttpContent(u: String, readSize: Int): ByteArray? { - Log.d("Mydl", "getHttp: $u") - var ret: ByteArray? = null - val task = FutureTask(Callable { + Log.d("MyDT", "getHttp: $u") + val task = prepare(u, readSize) + Thread(task).start() + return try { + task.get() + } catch (ex: Exception) { + ex.printStackTrace() + if (Comandy.useComandy) failTimes.incrementAndGet() + null + } + } + + fun prepare(u: String, readSize: Int = -1) = run { + Log.d("MyDT", "prepareHttp: $u") + FutureTask(if (Comandy.useComandy) Callable{ + try { + Comandy.instance?.request(Gson().toJson( + getComandyNormalConnection(u, "GET")) + )?.let { result -> + Gson().fromJson(result, ComandyCapsule::class.java)?.let { + if (it.code != 200) null + else it.data?.let { d -> Base64.decode(d, Base64.DEFAULT) } + } + } + } catch (ex: Exception) { + ex.printStackTrace() + null + } + } else Callable { + var ret: ByteArray? = null try { val connection = getNormalConnection(u, "GET") val ci = connection.inputStream @@ -87,57 +175,10 @@ object DownloadTools { } catch (ex: Exception) { ex.printStackTrace() } - return@Callable ret + ret }) - Thread(task).start() - return try { - task.get() - } catch (ex: Exception) { - ex.printStackTrace() - null - } } - /*fun touch(url: String?): FutureTask? = - url?.let { - Log.d("Mydl", "touchHttp: $it") - var ret: ByteArray? = null - val task = FutureTask(Callable { - try { - val connection = getNormalConnection(it, "GET") - - val ci = connection?.inputStream - ret = ci?.readBytes() - ci?.close() - connection?.disconnect() - } catch (ex: Exception) { - ex.printStackTrace() - } - return@Callable ret - }) - Thread(task).start() - task - }*/ - - fun prepare(url: String?): FutureTask? = - url?.let { - Log.d("Mydl", "prepareHttp: $it") - val task = FutureTask(Callable { - var ret: ByteArray? = null - try { - val connection = getNormalConnection(it, "GET") - connection.inputStream?.use { ci -> - ret = ci.readBytes() - } - connection.disconnect() - } catch (ex: Exception) { - ex.printStackTrace() - } - return@Callable ret - }) - task - } - /*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 -> @@ -146,14 +187,31 @@ object DownloadTools { }*/ fun requestWithBody(url: String, method: String, body: ByteArray, refer: String? = null, ua: String? = null): ByteArray? { - Log.d("Mydl", "$method Http: $url") + Log.d("MyDT", "$method Http: $url") var ret: ByteArray? = null - val task = FutureTask(Callable { + val task = FutureTask(if(Comandy.useComandy) Callable{ try { - getApiConnection(url, method, refer, ua)?.apply { + val capsule = getComandyApiConnection(url, method, refer, ua) + capsule.data = body.decodeToString() + Comandy.instance?.request(Gson().toJson(capsule))?.let { result -> + Gson().fromJson(result, ComandyCapsule::class.java)?.let { + if (it.code != 200) null + else it.data?.let { d -> Base64.decode(d, Base64.DEFAULT) } + } + } + } catch (ex: Exception) { + ex.printStackTrace() + null + } + } + else Callable { + failTimes.incrementAndGet() + try { + getApiConnection(url, method, refer, ua).apply { outputStream.write(body) ret = inputStream.readBytes() disconnect() + failTimes.decrementAndGet() } } catch (ex: Exception) { ex.printStackTrace() diff --git a/app/src/main/java/top/fumiama/copymanga/tools/http/Proxy.kt b/app/src/main/java/top/fumiama/copymanga/tools/http/Proxy.kt index e08a624..cdec538 100644 --- a/app/src/main/java/top/fumiama/copymanga/tools/http/Proxy.kt +++ b/app/src/main/java/top/fumiama/copymanga/tools/http/Proxy.kt @@ -30,26 +30,34 @@ class Proxy(id: Int, private val apiRegex: Regex, keyID: Int? = null) { } companion object { + private var mUseImageProxy: Boolean? = null val useImageProxy: Boolean get() { + if (mUseImageProxy != null) return mUseImageProxy!! MainActivity.mainWeakReference?.get()?.let { PreferenceManager.getDefaultSharedPreferences(it).apply { val b = getBoolean("settings_cat_net_sw_use_img_proxy", false) Log.d("MyProxy", "use image proxy: $b") + mUseImageProxy = b return b } } + mUseImageProxy = false return false } + private var mUseApiProxy: Boolean? = null val useApiProxy: Boolean get() { + if (mUseApiProxy != null) return mUseApiProxy!! MainActivity.mainWeakReference?.get()?.let { PreferenceManager.getDefaultSharedPreferences(it).apply { val b = getBoolean("settings_cat_net_sw_use_api_proxy", false) Log.d("MyProxy", "use api proxy: $b") + mUseApiProxy = b return b } } + mUseApiProxy = false return false } } diff --git a/app/src/main/java/top/fumiama/copymanga/ui/comicdl/ComicDlHandler.kt b/app/src/main/java/top/fumiama/copymanga/ui/comicdl/ComicDlHandler.kt index 9798a22..03db6b4 100644 --- a/app/src/main/java/top/fumiama/copymanga/ui/comicdl/ComicDlHandler.kt +++ b/app/src/main/java/top/fumiama/copymanga/ui/comicdl/ComicDlHandler.kt @@ -11,6 +11,7 @@ import android.view.ViewGroup import android.view.ViewTreeObserver import android.widget.Toast import androidx.lifecycle.lifecycleScope +import androidx.preference.PreferenceManager import com.google.gson.Gson import kotlinx.android.synthetic.main.button_tbutton.* import kotlinx.android.synthetic.main.button_tbutton.view.* @@ -21,6 +22,7 @@ import kotlinx.android.synthetic.main.line_chapter.view.* import kotlinx.android.synthetic.main.line_horizonal_empty.view.* import kotlinx.android.synthetic.main.widget_downloadbar.* import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import top.fumiama.copymanga.json.ChapterStructure @@ -38,6 +40,7 @@ import top.fumiama.copymanga.views.LazyScrollView import top.fumiama.dmzj.copymanga.R import java.io.File import java.lang.ref.WeakReference +import java.util.concurrent.atomic.AtomicInteger class ComicDlHandler(looper: Looper, private val th: WeakReference, private val vols: Array, private val comicName: String, private val groupNames: Array?):Handler(looper) { constructor(looper: Looper, th: WeakReference, comicName: String) : this(looper, th, arrayOf(), comicName, null) { @@ -63,6 +66,7 @@ class ComicDlHandler(looper: Looper, private val th: WeakReference() private var uuidArray = arrayOf() + private val maxBatch = that?.activity?.let { PreferenceManager.getDefaultSharedPreferences(it) }?.getInt("settings_cat_md_sb_max_batch", 16)?:16 @SuppressLint("SetTextI18n") override fun handleMessage(msg: Message) { @@ -265,12 +269,15 @@ class ComicDlHandler(looper: Looper, private val th: WeakReference if(i.isChecked) { withContext(Dispatchers.Main) { i.isEnabled = false } i.url?.let { + while (totalInDownload.get() >= maxBatch) delay(1000) + totalInDownload.incrementAndGet() launch { mangaDlTools.downloadChapterInVol( it, @@ -278,7 +285,7 @@ class ComicDlHandler(looper: Looper, private val th: WeakReference) : AutoDownloadH -1 -> { homeF?.apply { swiperefresh?.isRefreshing = msg.obj as Boolean - lifecycleScope.launch { if(msg.obj as Boolean) showKanban() else hideKanban() } + if(msg.obj as Boolean) showKanban() else hideKanban() } } //0 -> setLayouts() @@ -281,8 +281,8 @@ class HomeHandler(private val that: WeakReference) : AutoDownloadH withContext(Dispatchers.IO) { homeF?.showKanban() fhib?.isAutoPlay = false - fhib?.adapter?.notifyDataSetChanged() index = null + fhib?.adapter?.notifyDataSetChanged() fhib = null indexLines = arrayOf() this@HomeHandler.sendEmptyMessage(6) //removeAllViews diff --git a/app/src/main/java/top/fumiama/copymanga/user/Member.kt b/app/src/main/java/top/fumiama/copymanga/user/Member.kt index 176250d..5ab708c 100644 --- a/app/src/main/java/top/fumiama/copymanga/user/Member.kt +++ b/app/src/main/java/top/fumiama/copymanga/user/Member.kt @@ -5,8 +5,10 @@ import android.util.Base64 import com.google.gson.Gson import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import top.fumiama.copymanga.json.ComandyCapsule import top.fumiama.copymanga.json.LoginInfoStructure import top.fumiama.copymanga.tools.api.CMApi +import top.fumiama.copymanga.tools.http.Comandy import top.fumiama.copymanga.tools.http.DownloadTools import top.fumiama.dmzj.copymanga.R import java.net.URLEncoder @@ -16,29 +18,37 @@ class Member(private val pref: SharedPreferences, private val getString: (Int) - val hasLogin: Boolean get() = pref.getString("token", "")?.isNotEmpty()?:false suspend fun login(username: String, pwd: String, salt: Int): LoginInfoStructure = withContext(Dispatchers.IO) { var err = "" - getLoginConnection(username, pwd, salt).apply { + if (Comandy.useComandy) getComandyLoginConnection(username, pwd, salt).let { capsule -> + try { + Comandy.instance?.request(Gson().toJson(capsule))?.let { result -> + Gson().fromJson(result, ComandyCapsule::class.java)!!.let { + if (it.code != 200) { + val l = LoginInfoStructure() + l.code = it.code + l.message = it.data?.let { d -> Base64.decode(d, Base64.DEFAULT).decodeToString() } + return@withContext l + } + Base64.decode(it.data, Base64.DEFAULT) + } + } + } catch (e: Exception) { + err = e.message.toString() + null + } + }?.let { + try { + saveInfo(it) + } catch (e: Exception) { + err = e.message.toString() + } + } + else getLoginConnection(username, pwd, salt).apply { inputStream.use { it?.readBytes()?.let { data -> - data.inputStream().use { dataIn -> - try { - Gson().fromJson( - dataIn.reader(), LoginInfoStructure::class.java - )?.let { info -> - if(info.code == 200) { - pref.edit()?.apply { - putString("token", info.results?.token) - putString("user_id", info.results?.user_id) - putString("username", info.results?.username) - putString("nickname", info.results?.nickname) - apply() - return@withContext info() - } - } - return@withContext info - }?: run { err = getString(R.string.login_parse_json_error) } - } catch (e: Exception) { - err = data.decodeToString() - } + try { + saveInfo(data) + } catch (e: Exception) { + err = e.message.toString() } }?: run { err = getString(R.string.login_get_conn_failed) } } @@ -101,6 +111,26 @@ class Member(private val pref: SharedPreferences, private val getString: (Int) - } } + private suspend fun saveInfo(data: ByteArray) = data.inputStream().use { dataIn -> + try { + Gson().fromJson(dataIn.reader(), LoginInfoStructure::class.java)?.let { l -> + if(l.code == 200) { + pref.edit()?.apply { + putString("token", l.results?.token) + putString("user_id", l.results?.user_id) + putString("username", l.results?.username) + putString("nickname", l.results?.nickname) + apply() + return@use info() + } + } + return@use l + }?: throw Exception(getString(R.string.login_parse_json_error)) + } catch (e: Exception) { + throw Exception(data.decodeToString(), e) + } + } + private fun getLoginConnection(username: String, pwd: String, salt: Int) = getString(R.string.loginApiUrl).format(CMApi.myHostApiUrl).let { CMApi.apiProxy?.wrap(it)?:it @@ -117,4 +147,20 @@ class Member(private val pref: SharedPreferences, private val getString: (Int) - } } } + + private fun getComandyLoginConnection(username: String, pwd: String, salt: Int) = + getString(R.string.loginApiUrl).format(CMApi.myHostApiUrl).let { + CMApi.apiProxy?.wrap(it)?:it + }.let { + DownloadTools.getComandyApiConnection(it, "POST").apply { + pref.apply { + headers["content-type"] = "application/x-www-form-urlencoded;charset=utf-8" + headers["platform"] = "3" + headers["accept"] = "application/json" + val r = if(!getBoolean("settings_cat_net_sw_use_foreign", false)) "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=1.4.4&source=copyApp®ion=$r&webp=1" + } + } + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 38f9c3e..58aaa25 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -89,6 +89,9 @@ https://copymanga.azurewebsites.net/api/api?code=%1$s&url=%2$s settings_cat_net_sb_image_resolution + https://gitea.seku.su/api/v1/repos/fumiama/comandy/releases/tags/default + "https://gitea.seku.su/fumiama/comandy/releases/download/default/%1$s_libcomandy.so.gz" + 已完结 WIFI @@ -151,6 +154,8 @@ 打开后不再在开始阅读时提示 使用海外线路 不管使用什么线路, API访问均是海外, 只有图片CDN可能会变化(也可能不变), 请酌情选择使用 + 增强型数据访问 + 使用经过优化的请求方法访问服务器 请求API网址 一般无需更改,除非拷贝漫画官方更改网址,默认:&hosturl; 使用API代理(重启生效) @@ -175,6 +180,8 @@ 漫画下载 显示未下载漫画 打开后将在我的下载显示所有浏览过详情页的漫画,旧版下载永远全部显示 + 漫画下载并发限制 + 默认为16 用户名为空 密码为空 diff --git a/app/src/main/res/xml/pref_setting.xml b/app/src/main/res/xml/pref_setting.xml index 86bc984..ae89d32 100644 --- a/app/src/main/res/xml/pref_setting.xml +++ b/app/src/main/res/xml/pref_setting.xml @@ -44,6 +44,12 @@ + + diff --git a/build.gradle b/build.gradle index 5f5470d..e2e7e24 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { maven { url "https://jitpack.io" } } dependencies { - classpath 'com.android.tools.build:gradle:8.2.2' + classpath 'com.android.tools.build:gradle:8.3.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 5563ca9..d6cc089 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip