mirror of
https://github.com/fumiama/copymanga.git
synced 2026-06-13 12:20:26 +08:00
v2.5.2
优化 1. API访问流程 2. 更多访问报错信息 升级 1. appcompat -> 1.7.1
This commit is contained in:
2
.idea/dictionaries/fumiama.xml
generated
2
.idea/dictionaries/fumiama.xml
generated
@@ -5,6 +5,7 @@
|
|||||||
<w>azurewebsites</w>
|
<w>azurewebsites</w>
|
||||||
<w>comancry</w>
|
<w>comancry</w>
|
||||||
<w>comandy</w>
|
<w>comandy</w>
|
||||||
|
<w>deviceinfo</w>
|
||||||
<w>downloaders</w>
|
<w>downloaders</w>
|
||||||
<w>grps</w>
|
<w>grps</w>
|
||||||
<w>hotmanga</w>
|
<w>hotmanga</w>
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
<w>reclass</w>
|
<w>reclass</w>
|
||||||
<w>reilia</w>
|
<w>reilia</w>
|
||||||
<w>systembar</w>
|
<w>systembar</w>
|
||||||
|
<w>umstring</w>
|
||||||
</words>
|
</words>
|
||||||
</dictionary>
|
</dictionary>
|
||||||
</component>
|
</component>
|
||||||
@@ -10,9 +10,10 @@ android {
|
|||||||
compileSdk 34
|
compileSdk 34
|
||||||
applicationId 'top.fumiama.copymanga'
|
applicationId 'top.fumiama.copymanga'
|
||||||
minSdkVersion 23
|
minSdkVersion 23
|
||||||
|
//noinspection OldTargetApi
|
||||||
targetSdkVersion 34
|
targetSdkVersion 34
|
||||||
versionCode 72
|
versionCode 73
|
||||||
versionName '2.5.1'
|
versionName '2.5.2'
|
||||||
resourceConfigurations += ['zh', 'zh-rCN']
|
resourceConfigurations += ['zh', 'zh-rCN']
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
@@ -47,7 +48,7 @@ android {
|
|||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
signingConfig signingConfigs.release
|
signingConfig signingConfigs.release
|
||||||
}
|
}
|
||||||
/*winrelease {
|
/*winrelease {r
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
shrinkResources true
|
shrinkResources true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
@@ -98,7 +99,7 @@ dependencies {
|
|||||||
|
|
||||||
//noinspection GradleDependency
|
//noinspection GradleDependency
|
||||||
implementation 'androidx.core:core-ktx:1.12.0'
|
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 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
implementation 'com.google.android.material:material:1.12.0'
|
implementation 'com.google.android.material:material:1.12.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
|
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
|
||||||
|
|||||||
@@ -157,6 +157,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
isMenuWaiting = true
|
isMenuWaiting = true
|
||||||
Log.d("MyMain", "start menu waiting")
|
Log.d("MyMain", "start menu waiting")
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
Config.myHostApiUrl.init()
|
||||||
|
}
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
delay(1000)
|
delay(1000)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
@@ -430,7 +433,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
dl.setMessage("${getString(R.string.app_description)}\n" +
|
dl.setMessage("${getString(R.string.app_description)}\n" +
|
||||||
"\n$comandy\n" +
|
"\n$comandy\n" +
|
||||||
"$comancry\n\n"+ File("/proc/self/cmdline").readText() + "\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.setTitle("${getString(R.string.action_info)} ${BuildConfig.VERSION_NAME}")
|
||||||
dl.setIcon(R.mipmap.ic_launcher)
|
dl.setIcon(R.mipmap.ic_launcher)
|
||||||
dl.setPositiveButton(android.R.string.ok) { _, _ -> }
|
dl.setPositiveButton(android.R.string.ok) { _, _ -> }
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
package top.fumiama.copymanga.api
|
package top.fumiama.copymanga.api
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import com.bumptech.glide.load.model.LazyHeaders
|
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.MainActivity
|
||||||
import top.fumiama.copymanga.json.NetworkStructure
|
import top.fumiama.copymanga.api.network.Api
|
||||||
import top.fumiama.copymanga.net.DownloadTools
|
|
||||||
import top.fumiama.copymanga.net.Proxy
|
import top.fumiama.copymanga.net.Proxy
|
||||||
import top.fumiama.copymanga.net.Resolution
|
import top.fumiama.copymanga.net.Resolution
|
||||||
import top.fumiama.copymanga.storage.PreferenceBoolean
|
import top.fumiama.copymanga.storage.PreferenceBoolean
|
||||||
@@ -61,49 +55,9 @@ object Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val proxyUrl = MainActivity.mainWeakReference?.get()?.getString(R.string.proxyUrl)!!
|
val proxyUrl = MainActivity.mainWeakReference?.get()?.getString(R.string.proxyUrl)!!
|
||||||
private val reverseProxyUrl = PreferenceString(R.string.reverseProxyKeyID)
|
val reverseProxyUrl = PreferenceString(R.string.reverseProxyKeyID)
|
||||||
private val networkApiUrl = PreferenceString("settings_cat_net_et_api_url", R.string.hostUrl)
|
val networkApiUrl = PreferenceString("settings_cat_net_et_api_url", R.string.hostUrl)
|
||||||
private var mHostApiUrls: Array<String> = arrayOf()
|
val myHostApiUrl = Api()
|
||||||
private var mHostApiUrlsMutex = Mutex()
|
|
||||||
val myHostApiUrl: Array<String>
|
|
||||||
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 navTextInfo = UserPreferenceString("navTextInfo", R.string.navTextInfo)
|
val navTextInfo = UserPreferenceString("navTextInfo", R.string.navTextInfo)
|
||||||
val proxy_key = PreferenceString(R.string.imgProxyCodeKeyID)
|
val proxy_key = PreferenceString(R.string.imgProxyCodeKeyID)
|
||||||
val app_ver = PreferenceString("settings_cat_general_et_app_version", R.string.app_ver)
|
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)
|
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_use_api_proxy = PreferenceBoolean("settings_cat_net_sw_use_api_proxy", false)
|
||||||
val net_img_resolution = PreferenceString(R.string.imgResolutionKeyID)
|
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_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)
|
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) =
|
fun getChapterInfoApiUrl(path: String?, uuid: String?, version: Int) =
|
||||||
MainActivity.mainWeakReference?.get()?.getString(R.string.chapterInfoApiUrl)
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package top.fumiama.copymanga.api.manga
|
package top.fumiama.copymanga.api.manga
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import kotlinx.android.synthetic.main.card_book.*
|
import kotlinx.android.synthetic.main.card_book.*
|
||||||
import kotlinx.android.synthetic.main.line_booktandb.*
|
import kotlinx.android.synthetic.main.line_booktandb.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import top.fumiama.copymanga.MainActivity
|
||||||
import top.fumiama.copymanga.json.BookInfoStructure
|
import top.fumiama.copymanga.json.BookInfoStructure
|
||||||
import top.fumiama.copymanga.json.ThemeStructure
|
import top.fumiama.copymanga.json.ThemeStructure
|
||||||
import top.fumiama.copymanga.json.VolumeStructure
|
import top.fumiama.copymanga.json.VolumeStructure
|
||||||
@@ -15,8 +17,7 @@ import top.fumiama.dmzj.copymanga.R
|
|||||||
import java.io.File
|
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) {
|
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 mBookApiUrl = getString(R.string.bookInfoApiUrl).format(path, Config.platform.value)
|
||||||
private val mUserAgent = getString(R.string.pc_ua).format(Config.app_ver.value)
|
|
||||||
private var mBook: BookInfoStructure? = null
|
private var mBook: BookInfoStructure? = null
|
||||||
private var mGroupPathWords = arrayOf<String>()
|
private var mGroupPathWords = arrayOf<String>()
|
||||||
private var mKeys = arrayOf<String>()
|
private var mKeys = arrayOf<String>()
|
||||||
@@ -57,22 +58,16 @@ class Book(val path: String, private val getString: (Int) -> String, private val
|
|||||||
suspend fun updateInfo() = withContext(Dispatchers.IO) {
|
suspend fun updateInfo() = withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
var isDownload = false
|
var isDownload = false
|
||||||
val data: ByteArray = if (loadCache) {
|
val data: String = if (loadCache) {
|
||||||
name?.let { loadInfo(it) } ?: run {
|
name?.let { loadInfo(it) } ?: run {
|
||||||
isDownload = true
|
isDownload = true
|
||||||
Config.apiProxy?.comancry(mBookApiUrl) { url ->
|
Config.myHostApiUrl.get(mBookApiUrl)
|
||||||
DownloadTools.getHttpContent(url, null, mUserAgent)
|
|
||||||
}?:DownloadTools.getHttpContent(mBookApiUrl, null, mUserAgent)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
isDownload = true
|
isDownload = true
|
||||||
Config.apiProxy?.comancry(mBookApiUrl) { url ->
|
Config.myHostApiUrl.get(mBookApiUrl)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
mBook = Gson().fromJson(data, BookInfoStructure::class.java)
|
||||||
if (isDownload) saveInfo(data)
|
if (isDownload) saveInfo(data)
|
||||||
mGroupPathWords = arrayOf()
|
mGroupPathWords = arrayOf()
|
||||||
mKeys = arrayOf()
|
mKeys = arrayOf()
|
||||||
@@ -88,6 +83,9 @@ class Book(val path: String, private val getString: (Int) -> String, private val
|
|||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
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
|
}?:false
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun saveInfo(data: ByteArray) = withContext(Dispatchers.IO) {
|
private suspend fun saveInfo(data: String) = withContext(Dispatchers.IO) {
|
||||||
name?.let { name ->
|
name?.let { name ->
|
||||||
val mangaFolder = File(exDir, name)
|
val mangaFolder = File(exDir, name)
|
||||||
if(!mangaFolder.exists()) mangaFolder.mkdirs()
|
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)
|
val mangaFolder = File(exDir, name)
|
||||||
if(!mangaFolder.exists()) mangaFolder.mkdirs()
|
if(!mangaFolder.exists()) mangaFolder.mkdirs()
|
||||||
val f = File(mangaFolder, "meta.json")
|
val f = File(mangaFolder, "meta.json")
|
||||||
if (!f.exists()) return@withContext null
|
if (!f.exists()) return@withContext null
|
||||||
return@withContext f.readBytes()
|
return@withContext f.readBytes().decodeToString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,13 @@ package top.fumiama.copymanga.api.manga
|
|||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import top.fumiama.copymanga.api.Config
|
||||||
import top.fumiama.copymanga.json.BookQueryStructure
|
import top.fumiama.copymanga.json.BookQueryStructure
|
||||||
import top.fumiama.copymanga.json.ReturnBase
|
import top.fumiama.copymanga.json.ReturnBase
|
||||||
import top.fumiama.copymanga.api.Config
|
|
||||||
import top.fumiama.copymanga.net.DownloadTools
|
|
||||||
import top.fumiama.dmzj.copymanga.R
|
import top.fumiama.dmzj.copymanga.R
|
||||||
|
|
||||||
class Shelf(private val getString: (Int) -> String) {
|
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 queryApiUrlTemplate = getString(R.string.bookUserQueryApiUrl)
|
||||||
private val addApiUrl get() = "$apiUrl?platform=${Config.platform.value}"
|
private val addApiUrl get() = "$apiUrl?platform=${Config.platform.value}"
|
||||||
private val delApiUrl get() = "${apiUrl}s?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("")
|
||||||
append(Config.token.value)
|
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 {
|
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
|
Gson().fromJson(re, ReturnBase::class.java).message
|
||||||
} catch (e: Exception) {
|
} 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("authorization=Token+")
|
||||||
append(Config.token.value)
|
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 {
|
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
|
Gson().fromJson(re, ReturnBase::class.java).message
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
"$re ${e.message}"
|
e.message?:e::class.simpleName?:e.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun query(pathWord: String): BookQueryStructure? = withContext(Dispatchers.IO) {
|
suspend fun query(pathWord: String): BookQueryStructure? = withContext(Dispatchers.IO) {
|
||||||
try {
|
val queryUrl = queryApiUrlTemplate.format(pathWord, Config.platform.value)
|
||||||
val queryUrl = queryApiUrlTemplate.format(Config.myHostApiUrl.random(), pathWord, Config.platform.value)
|
Config.myHostApiUrl.get(queryUrl).let {
|
||||||
(Config.apiProxy?.comancry(queryUrl) { url ->
|
Gson().fromJson(it, BookQueryStructure::class.java)
|
||||||
DownloadTools.getHttpContent(url, Config.referer)
|
|
||||||
}?:DownloadTools.getHttpContent(queryUrl, Config.referer)).let {
|
|
||||||
Gson().fromJson(it.decodeToString(), BookQueryStructure::class.java)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class Volume(private val path: String, private val groupPathWord: String, getStr
|
|||||||
return@withContext mVolume
|
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<VolumeStructure?>, offset: Int, c: Int) = withContext(Dispatchers.IO) {
|
private suspend fun download(re: Array<VolumeStructure?>, offset: Int, c: Int) = withContext(Dispatchers.IO) {
|
||||||
Log.d("MyV", "下载偏移: $offset")
|
Log.d("MyV", "下载偏移: $offset")
|
||||||
getApiUrl(offset).let {
|
getApiUrl(offset).let {
|
||||||
|
|||||||
112
app/src/main/java/top/fumiama/copymanga/api/network/Api.kt
Normal file
112
app/src/main/java/top/fumiama/copymanga/api/network/Api.kt
Normal file
@@ -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<String>()
|
||||||
|
private var mu = ReentrantReadWriteLock()
|
||||||
|
|
||||||
|
fun getApis(): Array<String> {
|
||||||
|
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}")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,10 +5,7 @@ import com.google.gson.Gson
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import top.fumiama.copymanga.api.Config
|
import top.fumiama.copymanga.api.Config
|
||||||
import top.fumiama.copymanga.json.ComandyCapsule
|
|
||||||
import top.fumiama.copymanga.json.LoginInfoStructure
|
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 top.fumiama.dmzj.copymanga.R
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
import java.nio.charset.Charset
|
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
|
val hasLogin: Boolean get() = Config.token.value?.isNotEmpty() ?: false
|
||||||
suspend fun login(username: String, pwd: String, salt: Int): LoginInfoStructure =
|
suspend fun login(username: String, pwd: String, salt: Int): LoginInfoStructure =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
var err = ""
|
return@withContext try {
|
||||||
(if (!Config.net_use_api_proxy.value && Comandy.instance.enabled)
|
saveInfo(postLogin(username, pwd, salt))
|
||||||
postComandyLogin(username, pwd, salt)
|
} catch (e: Exception) {
|
||||||
else postLogin(username, pwd, salt))?.let { data ->
|
val l = LoginInfoStructure()
|
||||||
try {
|
l.code = 400
|
||||||
return@withContext saveInfo(data)
|
l.message = e.message.toString()
|
||||||
} catch (e: Exception) {
|
l
|
||||||
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 登录态
|
* @return 登录态
|
||||||
@@ -49,24 +39,21 @@ class Member(private val getString: (Int) -> String) {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
val u = getString(R.string.memberInfoApiUrl)
|
val u = getString(R.string.memberInfoApiUrl)
|
||||||
.format(Config.myHostApiUrl.random(), Config.platform.value)
|
.format(Config.platform.value)
|
||||||
val data = (Config.apiProxy?.comancry(u) {
|
|
||||||
DownloadTools.getHttpContent(it)
|
|
||||||
}?:DownloadTools.getHttpContent(u)).decodeToString()
|
|
||||||
try {
|
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
|
if (l.code == 200) Config.avatar.value = l.results.avatar
|
||||||
l
|
l
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
val l = LoginInfoStructure()
|
val l = LoginInfoStructure()
|
||||||
l.code = 450
|
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
|
l
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
val l = LoginInfoStructure()
|
val l = LoginInfoStructure()
|
||||||
l.code = 450
|
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
|
l
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,69 +83,17 @@ class Member(private val getString: (Int) -> String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun postLogin(username: String, pwd: String, salt: Int): ByteArray? =
|
private suspend fun postLogin(username: String, pwd: String, salt: Int): ByteArray =
|
||||||
getString(R.string.loginApiUrl).format(Config.myHostApiUrl.random(), Config.platform.value).let { u ->
|
getString(R.string.loginApiUrl).format(Config.platform.value).let { u ->
|
||||||
val use: suspend (String) -> ByteArray? = { it: String ->
|
val r = if (!Config.net_use_foreign.value) "1" else "0"
|
||||||
DownloadTools.getApiConnection(it, "POST").let { c ->
|
val pwdEncoded =
|
||||||
c.doOutput = true
|
Base64.encode("$pwd-$salt".toByteArray(), Base64.DEFAULT).decodeToString()
|
||||||
c.setRequestProperty(
|
Config.myHostApiUrl.request(u, "username=${
|
||||||
"content-type",
|
URLEncoder.encode(
|
||||||
"application/x-www-form-urlencoded;charset=utf-8"
|
username,
|
||||||
)
|
Charset.defaultCharset().name()
|
||||||
c.setRequestProperty("platform", Config.platform.value)
|
)
|
||||||
c.setRequestProperty("accept", "application/json")
|
}&password=$pwdEncoded&salt=$salt&platform=${Config.platform.value}&authorization=Token+&version=${Config.app_ver.value}&source=copyApp®ion=$r&webp=1".encodeToByteArray(),
|
||||||
val r = if (!Config.net_use_foreign.value) "1" else "0"
|
"POST", "application/x-www-form-urlencoded;charset=utf-8")
|
||||||
val pwdEncoded =
|
}.encodeToByteArray()
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,27 +3,18 @@ package top.fumiama.copymanga.lib
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import top.fumiama.copymanga.api.Config
|
import top.fumiama.copymanga.api.Config
|
||||||
import top.fumiama.copymanga.lib.template.LazyLibrary
|
import top.fumiama.copymanga.lib.template.LazyLibrary
|
||||||
import top.fumiama.copymanga.net.DownloadTools
|
|
||||||
|
|
||||||
class Comandy: LazyLibrary<ComandyMethods>(
|
class Comandy: LazyLibrary<ComandyMethods>(
|
||||||
ComandyMethods::class.java, "libcomandy.so", "网络增强",
|
ComandyMethods::class.java, "libcomandy.so", "网络增强",
|
||||||
Config.net_use_comandy, Config.comandy_version
|
Config.net_use_comandy, Config.comandy_version
|
||||||
) {
|
) {
|
||||||
private var mEnabled: Boolean? = null
|
|
||||||
val enabled: Boolean
|
val enabled: Boolean
|
||||||
get() {
|
get() {
|
||||||
if (isInInit.get()) {
|
if (isInInit.get()) {
|
||||||
Log.d("MyComandy", "$name block enabled for isInInit")
|
Log.d("MyComandy", "$name block enabled for isInInit")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (mEnabled != true && DownloadTools.failTimes.get() >= 16) {
|
return isInUse.value
|
||||||
mEnabled = true
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if (mEnabled != null) return mEnabled!!
|
|
||||||
val v = isInUse.value
|
|
||||||
mEnabled = v
|
|
||||||
return v
|
|
||||||
}
|
}
|
||||||
val status: String get() = if(enabled) {
|
val status: String get() = if(enabled) {
|
||||||
if (isInUse.value) "生效(手动)" else "生效(自动)"
|
if (isInUse.value) "生效(手动)" else "生效(自动)"
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import com.google.gson.Gson
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import top.fumiama.copymanga.MainActivity
|
|
||||||
import top.fumiama.copymanga.api.Config
|
import top.fumiama.copymanga.api.Config
|
||||||
import top.fumiama.copymanga.api.Config.proxyUrl
|
import top.fumiama.copymanga.api.Config.proxyUrl
|
||||||
import top.fumiama.copymanga.json.ComandyCapsule
|
import top.fumiama.copymanga.json.ComandyCapsule
|
||||||
import top.fumiama.copymanga.lib.Comandy
|
import top.fumiama.copymanga.lib.Comandy
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
@@ -21,61 +21,63 @@ import java.text.SimpleDateFormat
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.Callable
|
import java.util.concurrent.Callable
|
||||||
import java.util.concurrent.FutureTask
|
import java.util.concurrent.FutureTask
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.zip.GZIPInputStream
|
||||||
|
|
||||||
object DownloadTools {
|
object DownloadTools {
|
||||||
val failTimes = AtomicInteger(0)
|
private fun getApiConnection(url: String, method: String = "GET", timeout: Int = 20000): HttpURLConnection {
|
||||||
fun getApiConnection(url: String, method: String = "GET", refer: String? = null, ua: String? = null, timeout: Int = 20000): HttpURLConnection {
|
|
||||||
val connection = URL(url).openConnection() as HttpURLConnection
|
val connection = URL(url).openConnection() as HttpURLConnection
|
||||||
connection.requestMethod = method
|
connection.requestMethod = method
|
||||||
connection.connectTimeout = timeout
|
connection.connectTimeout = timeout
|
||||||
connection.readTimeout = timeout
|
connection.readTimeout = timeout
|
||||||
connection.apply {
|
connection.apply {
|
||||||
/*setRequestProperty("host", if (url.startsWith("https://copymanga.azurewebsites.net")) {
|
setRequestProperty("user-agent", Config.pc_ua)
|
||||||
Uri.parse(url).getQueryParameter("url")?.substringAfter("://")?.substringBefore("/")?:""
|
|
||||||
} else {
|
|
||||||
url.substringAfter("://").substringBefore("/")
|
|
||||||
})*/
|
|
||||||
ua?.let { setRequestProperty("user-agent", it) }
|
|
||||||
refer?.let { setRequestProperty("referer", it) }
|
|
||||||
setRequestProperty("source", "copyApp")
|
setRequestProperty("source", "copyApp")
|
||||||
|
// deviceinfo
|
||||||
setRequestProperty("webp", "1")
|
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))
|
setRequestProperty("dt", SimpleDateFormat("yyyy.MM.dd", Locale.getDefault()).format(Calendar.getInstance().time))
|
||||||
Config.token.value?.let { tk ->
|
setRequestProperty("accept-encoding", "gzip")
|
||||||
setRequestProperty("authorization", "Token $tk")
|
setRequestProperty("authorization", "Token${Config.token.value?.let { tk ->
|
||||||
}
|
if (tk.isNotEmpty()) " $tk" else ""
|
||||||
|
}}")
|
||||||
setRequestProperty("platform", Config.platform.value)
|
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")}")
|
Log.d("MyDT", "getConnection: $url\n${connection.requestProperties.map { "${it.key}: ${it.value}" }.joinToString("\n")}")
|
||||||
return connection
|
return connection
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getComandyApiConnection(url: String, method: String = "GET", refer: String? = null, ua: String? = null) =
|
private fun getComandyApiConnection(url: String, method: String = "GET") =
|
||||||
run {
|
run {
|
||||||
val capsule = ComandyCapsule()
|
val capsule = ComandyCapsule()
|
||||||
capsule.url = url
|
capsule.url = url
|
||||||
capsule.method = method
|
capsule.method = method
|
||||||
capsule.headers = hashMapOf()
|
capsule.headers = hashMapOf()
|
||||||
/*capsule.headers["host"] = if (url.startsWith("https://copymanga.azurewebsites.net")) {
|
capsule.headers["user-agent"] = Config.pc_ua
|
||||||
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["source"] = "copyApp"
|
capsule.headers["source"] = "copyApp"
|
||||||
|
// deviceinfo
|
||||||
capsule.headers["webp"] = "1"
|
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["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")}")
|
Log.d("MyDT", "getComandyConnection: $url\n${capsule.headers.map { "${it.key}: ${it.value}" }.joinToString("\n")}")
|
||||||
capsule
|
capsule
|
||||||
}
|
}
|
||||||
@@ -123,16 +125,31 @@ object DownloadTools {
|
|||||||
return bytesCopied
|
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 {
|
private fun InputStream.readBytesWithProgress(sz: Int, p: Client.Progress): ByteArray {
|
||||||
val buffer = ByteArrayOutputStream(maxOf(DEFAULT_BUFFER_SIZE, this.available()))
|
val buffer = ByteArrayOutputStream(maxOf(DEFAULT_BUFFER_SIZE, this.available()))
|
||||||
copyToWithProgress(buffer, sz, p)
|
copyToWithProgress(buffer, sz, p)
|
||||||
return buffer.toByteArray()
|
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) {
|
withContext(Dispatchers.IO) {
|
||||||
if (!u.startsWith("https://$proxyUrl") && Comandy.instance.enabled) {
|
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)
|
val para = Gson().toJson(capsule)
|
||||||
//Log.d("MyDT", "comandy request: $para")
|
//Log.d("MyDT", "comandy request: $para")
|
||||||
Comandy.instance.getInstance()?.let { ins ->
|
Comandy.instance.getInstance()?.let { ins ->
|
||||||
@@ -154,6 +171,7 @@ object DownloadTools {
|
|||||||
Log.d("MyDT", "quit comandy get progress, completed: $completed for url $u")
|
Log.d("MyDT", "quit comandy get progress, completed: $completed for url $u")
|
||||||
}.start()
|
}.start()
|
||||||
}
|
}
|
||||||
|
var coding: String? = null
|
||||||
val r = ins.request(para)?.let { result ->
|
val r = ins.request(para)?.let { result ->
|
||||||
completed = true
|
completed = true
|
||||||
p?.notify(100)
|
p?.notify(100)
|
||||||
@@ -162,26 +180,29 @@ object DownloadTools {
|
|||||||
if (it.code != 200) throw IllegalArgumentException("HTTP${it.code} ${
|
if (it.code != 200) throw IllegalArgumentException("HTTP${it.code} ${
|
||||||
it.data?.let { d -> Base64.decode(d, Base64.DEFAULT).decodeToString() }
|
it.data?.let { d -> Base64.decode(d, Base64.DEFAULT).decodeToString() }
|
||||||
}")
|
}")
|
||||||
|
coding = it.headers["Content-Encoding"] as String?
|
||||||
Base64.decode(it.data, Base64.DEFAULT)
|
Base64.decode(it.data, Base64.DEFAULT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
completed = true
|
completed = true
|
||||||
p?.notify(100)
|
p?.notify(100)
|
||||||
r
|
r?.let { ret -> coding?.let { decodeBody(ret, it) } }
|
||||||
}
|
}
|
||||||
}.let { if(it?.isNotEmpty() == true ) return@withContext it }
|
}.let { if(it?.isNotEmpty() == true ) return@withContext it }
|
||||||
failTimes.incrementAndGet()
|
|
||||||
}
|
}
|
||||||
getApiConnection(u, "GET", refer, ua).let {
|
getApiConnection(u, "GET").let { conn ->
|
||||||
val sz = it.getHeaderFieldInt("Content-Length", 0)
|
val sz = conn.getHeaderFieldInt("Content-Length", 0)
|
||||||
val ret = if (sz > 0 && p != null) {
|
val ret = if (sz > 0 && p != null) {
|
||||||
it.inputStream.readBytesWithProgress(sz, p)
|
conn.inputStream.readBytesWithProgress(sz, p)
|
||||||
} else {
|
} else {
|
||||||
it.inputStream.readBytes()
|
conn.inputStream.readBytes()
|
||||||
}
|
}
|
||||||
it.disconnect()
|
conn.disconnect()
|
||||||
Log.d("MyDT", "getHttpContent: ${ret.size} bytes")
|
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? {
|
fun requestApiWithBody(url: String, method: String, body: ByteArray, contentType: String): ByteArray {
|
||||||
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? {
|
|
||||||
Log.d("MyDT", "$method Http: $url")
|
Log.d("MyDT", "$method Http: $url")
|
||||||
var ret: ByteArray? = null
|
return if(!url.startsWith("https://$proxyUrl") && Comandy.instance.enabled) {
|
||||||
val task = FutureTask(if(!url.startsWith("https://$proxyUrl") && Comandy.instance.enabled) Callable{
|
val capsule = getComandyApiConnection(url, method)
|
||||||
try {
|
capsule.headers["content-type"] = contentType
|
||||||
val capsule = getComandyApiConnection(url, method, refer, ua)
|
capsule.data = body.decodeToString()
|
||||||
contentType?.let { capsule.headers["content-type"] = it }
|
runBlocking { Comandy.instance.getInstance() }?.request(Gson().toJson(capsule))?.let { result ->
|
||||||
capsule.data = body.decodeToString()
|
Gson().fromJson(result, ComandyCapsule::class.java)?.let { c ->
|
||||||
runBlocking { Comandy.instance.getInstance() }?.request(Gson().toJson(capsule))?.let { result ->
|
c.data?.let { d ->
|
||||||
Gson().fromJson(result, ComandyCapsule::class.java)?.let {
|
Base64.decode(d, Base64.DEFAULT).let {
|
||||||
it.data?.let { d -> Base64.decode(d, Base64.DEFAULT) }?:"empty comandy data".encodeToByteArray()
|
(c.headers["Content-Encoding"] as String?)?.let { coding ->
|
||||||
}
|
decodeBody(it, coding)
|
||||||
|
}?:it
|
||||||
|
}
|
||||||
|
}?: throw IllegalStateException("empty comandy data")
|
||||||
}
|
}
|
||||||
} catch (ex: Exception) {
|
}?: throw IllegalStateException("no comandy")
|
||||||
ex.printStackTrace()
|
} else {
|
||||||
failTimes.incrementAndGet()
|
var ret: ByteArray
|
||||||
ex.message?.encodeToByteArray()
|
var coding = ""
|
||||||
|
getApiConnection(url, method).apply {
|
||||||
|
outputStream.write(body)
|
||||||
|
ret = inputStream.readBytes()
|
||||||
|
disconnect()
|
||||||
|
coding = getHeaderField("Content-Encoding")?:""
|
||||||
}
|
}
|
||||||
}
|
decodeBody(ret, coding)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,8 @@ import kotlinx.coroutines.delay
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import top.fumiama.copymanga.MainActivity.Companion.mainWeakReference
|
import top.fumiama.copymanga.MainActivity.Companion.mainWeakReference
|
||||||
import top.fumiama.copymanga.json.ReturnBase
|
|
||||||
import top.fumiama.copymanga.api.Config
|
import top.fumiama.copymanga.api.Config
|
||||||
import top.fumiama.copymanga.net.DownloadTools
|
import top.fumiama.copymanga.json.ReturnBase
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
|
||||||
@@ -77,16 +76,12 @@ open class AutoDownloadHandler(
|
|||||||
var cnt = 0
|
var cnt = 0
|
||||||
while (cnt++ <= 3) {
|
while (cnt++ <= 3) {
|
||||||
try {
|
try {
|
||||||
val data = Config.apiProxy?.comancry(url()) {
|
val data = Config.myHostApiUrl.get(url())
|
||||||
DownloadTools.getHttpContent(it)
|
|
||||||
}?:DownloadTools.getHttpContent(url())
|
|
||||||
if(exit) return@withContext
|
if(exit) return@withContext
|
||||||
val fi = data.inputStream()
|
val pass = setGsonItem(Gson().fromJson(data, jsonClass))
|
||||||
val pass = setGsonItem(Gson().fromJson(fi.reader(), jsonClass))
|
|
||||||
if (pass && loadFromCache) {
|
if (pass && loadFromCache) {
|
||||||
cacheFile?.writeBytes(data)
|
cacheFile?.writeText(data)
|
||||||
}
|
}
|
||||||
fi.close()
|
|
||||||
if(!pass) {
|
if(!pass) {
|
||||||
delay(2000)
|
delay(2000)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import top.fumiama.copymanga.api.Config
|
import top.fumiama.copymanga.api.Config
|
||||||
import top.fumiama.copymanga.net.DownloadTools
|
|
||||||
import kotlin.random.Random
|
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) {
|
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
|
var c = 0
|
||||||
while (!exit && c++ < 3) {
|
while (!exit && c++ < 3) {
|
||||||
try {
|
try {
|
||||||
val data = (if (isApi) Config.apiProxy?.comancry(url) {
|
val data = Config.myHostApiUrl.get(url)
|
||||||
DownloadTools.getHttpContent(it, Config.referer)
|
whenFinish?.let { it(data.encodeToByteArray()) }
|
||||||
} else null)?:DownloadTools.getHttpContent(url, Config.referer)
|
|
||||||
whenFinish?.let { it(data) }
|
|
||||||
return@withContext true
|
return@withContext true
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ data class PreferenceString(private val key: String, private var default: String
|
|||||||
constructor(key: Int, default: String?, defaultID: Int): this(
|
constructor(key: Int, default: String?, defaultID: Int): this(
|
||||||
MainActivity.mainWeakReference?.get()?.getString(key) ?:"", default, defaultID)
|
MainActivity.mainWeakReference?.get()?.getString(key) ?:"", default, defaultID)
|
||||||
constructor(key: String, default: Int): this(key, null, default)
|
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)
|
constructor(key: Int): this(key, "", 0)
|
||||||
|
|
||||||
private val defaultField: String
|
private val defaultField: String
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ class BookFragment: NoBackRefreshFragment(R.layout.fragment_book) {
|
|||||||
if (getBoolean("loadJson")) {
|
if (getBoolean("loadJson")) {
|
||||||
getString("name")?.let { name ->
|
getString("name")?.let { name ->
|
||||||
try {
|
try {
|
||||||
|
Log.d("MyBF", "loadFromCache name $name")
|
||||||
book = Book(name, {
|
book = Book(name, {
|
||||||
return@Book getString(it)
|
return@Book getString(it)
|
||||||
}, activity?.getExternalFilesDir("")!!)
|
}, activity?.getExternalFilesDir("")!!)
|
||||||
@@ -184,11 +185,17 @@ class BookFragment: NoBackRefreshFragment(R.layout.fragment_book) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun queryCollect() {
|
private suspend fun queryCollect() {
|
||||||
MainActivity.shelf?.query(book?.path!!)?.let { b ->
|
try {
|
||||||
mBookHandler?.collect = b.results?.collect?:-2
|
MainActivity.shelf?.query(book?.path!!)?.let { b ->
|
||||||
Log.d("MyBF", "get collect of ${book?.path} = ${mBookHandler?.collect}")
|
mBookHandler?.collect = b.results?.collect?:-2
|
||||||
tic.text = b.results?.browse?.chapter_name?.let { name ->
|
Log.d("MyBF", "get collect of ${book?.path} = ${mBookHandler?.collect}")
|
||||||
getString(R.string.text_format_cloud_read_to).format(Chinese.fixEncodingIfNeeded(name))
|
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 ->
|
book?.uuid?.let { uuid ->
|
||||||
this@BookFragment.lbbsub.setOnClickListener {
|
this@BookFragment.lbbsub?.setOnClickListener {
|
||||||
lifecycleScope.launch clickLaunch@ {
|
lifecycleScope.launch clickLaunch@ {
|
||||||
if (this@BookFragment.lbbsub.text != getString(R.string.button_sub)) {
|
if (this@BookFragment.lbbsub.text != getString(R.string.button_sub)) {
|
||||||
mBookHandler?.collect?.let { collect ->
|
mBookHandler?.collect?.let { collect ->
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import top.fumiama.dmzj.copymanga.R
|
|||||||
@OptIn(ExperimentalStdlibApi::class)
|
@OptIn(ExperimentalStdlibApi::class)
|
||||||
class HistoryFragment : InfoCardLoader(R.layout.fragment_history, R.id.action_nav_history_to_nav_book, isHistoryBook = true) {
|
class HistoryFragment : InfoCardLoader(R.layout.fragment_history, R.id.action_nav_history_to_nav_book, isHistoryBook = true) {
|
||||||
override fun getApiUrl() =
|
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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
if (MainActivity.member?.hasLogin != true) {
|
if (MainActivity.member?.hasLogin != true) {
|
||||||
|
|||||||
@@ -7,5 +7,5 @@ import top.fumiama.dmzj.copymanga.R
|
|||||||
@ExperimentalStdlibApi
|
@ExperimentalStdlibApi
|
||||||
class NewestFragment : InfoCardLoader(R.layout.fragment_newest, R.id.action_nav_newest_to_nav_book, true) {
|
class NewestFragment : InfoCardLoader(R.layout.fragment_newest, R.id.action_nav_newest_to_nav_book, true) {
|
||||||
override fun getApiUrl() =
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ class RankFragment : InfoCardLoader(R.layout.fragment_rank, R.id.action_nav_rank
|
|||||||
|
|
||||||
override fun getApiUrl() =
|
override fun getApiUrl() =
|
||||||
getString(R.string.rankApiUrl).format(
|
getString(R.string.rankApiUrl).format(
|
||||||
Config.myHostApiUrl.random(),
|
|
||||||
page * 21,
|
page * 21,
|
||||||
sortWay[sortValue],
|
sortWay[sortValue],
|
||||||
audienceWay[audience],
|
audienceWay[audience],
|
||||||
|
|||||||
@@ -7,5 +7,5 @@ import top.fumiama.dmzj.copymanga.R
|
|||||||
@ExperimentalStdlibApi
|
@ExperimentalStdlibApi
|
||||||
class RecFragment : InfoCardLoader(R.layout.fragment_recommend, R.id.action_nav_recommend_to_nav_book, true) {
|
class RecFragment : InfoCardLoader(R.layout.fragment_recommend, R.id.action_nav_recommend_to_nav_book, true) {
|
||||||
override fun getApiUrl() =
|
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)
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ class SearchFragment : InfoCardLoader(R.layout.fragment_search, R.id.action_nav_
|
|||||||
private var query: String? = null
|
private var query: String? = null
|
||||||
private var type: String? = null
|
private var type: String? = null
|
||||||
override fun getApiUrl() =
|
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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ class ShelfFragment : InfoCardLoader(R.layout.fragment_shelf, R.id.action_nav_su
|
|||||||
|
|
||||||
override fun getApiUrl() =
|
override fun getApiUrl() =
|
||||||
getString(R.string.shelfApiUrl).format(
|
getString(R.string.shelfApiUrl).format(
|
||||||
Config.myHostApiUrl.random(),
|
|
||||||
page * 21,
|
page * 21,
|
||||||
sortWay[sortValue],
|
sortWay[sortValue],
|
||||||
Config.platform.value,
|
Config.platform.value,
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ class SortFragment : StatusCardFlow(0, R.id.action_nav_sort_to_nav_book, R.layou
|
|||||||
|
|
||||||
override fun getApiUrl() =
|
override fun getApiUrl() =
|
||||||
getString(R.string.sortApiUrl).format(
|
getString(R.string.sortApiUrl).format(
|
||||||
Config.myHostApiUrl.random(),
|
|
||||||
page * 21,
|
page * 21,
|
||||||
sortWay[sortValue],
|
sortWay[sortValue],
|
||||||
if(theme >= 0 && theme < (filter?.results?.theme?.size ?: 0)) (filter?.results?.theme?.get(theme)?.path_word ?: "") else "",
|
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()
|
super.setListeners()
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
setProgress(5)
|
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
|
if(ad?.exit == true) return@PausableDownloader
|
||||||
it.let {
|
it.let {
|
||||||
it.inputStream().use { i ->
|
it.inputStream().use { i ->
|
||||||
|
|||||||
@@ -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) {
|
class TopicFragment : InfoCardLoader(R.layout.fragment_topic, R.id.action_nav_topic_to_nav_book) {
|
||||||
private var type = 1
|
private var type = 1
|
||||||
override fun getApiUrl() =
|
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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
setProgress(5)
|
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)
|
setProgress(10)
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
if(ad?.exit == true) return@withContext
|
if(ad?.exit == true) return@withContext
|
||||||
|
|||||||
@@ -118,7 +118,6 @@ class DownloadFragment: NoBackRefreshFragment(R.layout.fragment_download) {
|
|||||||
bundle.putBoolean("callFromOldDL", true)
|
bundle.putBoolean("callFromOldDL", true)
|
||||||
}
|
}
|
||||||
bundle.putString("name", jsonFile.parentFile?.name?:"Null")
|
bundle.putString("name", jsonFile.parentFile?.name?:"Null")
|
||||||
Log.d("MyDF", "root view: $rootView")
|
|
||||||
Log.d("MyDF", "action_nav_download_to_nav_group")
|
Log.d("MyDF", "action_nav_download_to_nav_group")
|
||||||
Navigate.safeNavigateTo(findNavController(), R.id.action_nav_download_to_nav_group, bundle)
|
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("title", title)
|
||||||
bundle.putString("file", file.absolutePath)
|
bundle.putString("file", file.absolutePath)
|
||||||
Log.d("MyDF", "Call self to $title")
|
Log.d("MyDF", "Call self to $title")
|
||||||
Log.d("MyDF", "root view: $rootView")
|
|
||||||
Log.d("MyDF", "action_nav_download_self")
|
Log.d("MyDF", "action_nav_download_self")
|
||||||
Navigate.safeNavigateTo(findNavController(), R.id.action_nav_download_self, bundle)
|
Navigate.safeNavigateTo(findNavController(), R.id.action_nav_download_self, bundle)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -244,7 +244,6 @@ class NewDownloadFragment: MangaPagesFragmentTemplate(R.layout.fragment_newdownl
|
|||||||
Log.d("MyNDF", "Call dl and is new.")
|
Log.d("MyNDF", "Call dl and is new.")
|
||||||
bundle.putString("loadJson", File(File(extDir, name), "info.json").readText())
|
bundle.putString("loadJson", File(File(extDir, name), "info.json").readText())
|
||||||
bundle.putString("name", name)
|
bundle.putString("name", name)
|
||||||
Log.d("MyNDF", "root view: $rootView")
|
|
||||||
Log.d("MyNDF", "action_nav_new_download_to_nav_group")
|
Log.d("MyNDF", "action_nav_new_download_to_nav_group")
|
||||||
Navigate.safeNavigateTo(findNavController(), R.id.action_nav_new_download_to_nav_group, bundle)
|
Navigate.safeNavigateTo(findNavController(), R.id.action_nav_new_download_to_nav_group, bundle)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ class HomeFragment : NoBackRefreshFragment(R.layout.fragment_home) {
|
|||||||
val netInfo = tb.netInfo
|
val netInfo = tb.netInfo
|
||||||
if(netInfo != tb.transportStringNull && netInfo != tb.transportStringError)
|
if(netInfo != tb.transportStringNull && netInfo != tb.transportStringError)
|
||||||
MainActivity.member?.apply { lifecycleScope.launch {
|
MainActivity.member?.apply { lifecycleScope.launch {
|
||||||
|
Config.myHostApiUrl.init()
|
||||||
info().let { l ->
|
info().let { l ->
|
||||||
if (l.code != 200 && l.code != 449) {
|
if (l.code != 200 && l.code != 449) {
|
||||||
Toast.makeText(context, l.message, Toast.LENGTH_SHORT).show()
|
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) {
|
suspend fun refresh(q: CharSequence) = withContext(Dispatchers.IO) {
|
||||||
query = q.toString()
|
query = q.toString()
|
||||||
activity?.apply {
|
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)) {
|
URLEncoder.encode(q.toString(), Charset.defaultCharset().name()), type, Config.platform.value)) {
|
||||||
results = Gson().fromJson(it.decodeToString(), BookListStructure::class.java)
|
results = Gson().fromJson(it.decodeToString(), BookListStructure::class.java)
|
||||||
count = results?.results?.total?:0
|
count = results?.results?.total?:0
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import java.lang.ref.WeakReference
|
|||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
class HomeHandler(private val that: WeakReference<HomeFragment>) : AutoDownloadHandler({
|
class HomeHandler(private val that: WeakReference<HomeFragment>) : 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,
|
IndexStructure::class.java,
|
||||||
that.get()
|
that.get()
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ open class NoBackRefreshFragment(private val layoutToLoad: Int): Fragment() {
|
|||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
|
//TODO: 支持自动重建
|
||||||
if(_rootView == null) {
|
if(_rootView == null) {
|
||||||
isFirstInflate = true
|
isFirstInflate = true
|
||||||
_rootView = inflater.inflate(layoutToLoad, container, false)
|
_rootView = inflater.inflate(layoutToLoad, container, false)
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ open class StatusCardFlow(private val api: Int, nav: Int, inflateRes: Int,
|
|||||||
|
|
||||||
override fun getApiUrl() =
|
override fun getApiUrl() =
|
||||||
getString(api).format(
|
getString(api).format(
|
||||||
Config.myHostApiUrl.random(),
|
|
||||||
page * 21,
|
page * 21,
|
||||||
sortWay[sortValue],
|
sortWay[sortValue],
|
||||||
Config.platform.value,
|
Config.platform.value,
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ open class ThemeCardFlow(private val api: Int, nav: Int) : StatusCardFlow(0, nav
|
|||||||
private var theme = ""
|
private var theme = ""
|
||||||
override fun getApiUrl() =
|
override fun getApiUrl() =
|
||||||
getString(api).format(
|
getString(api).format(
|
||||||
Config.myHostApiUrl.random(),
|
|
||||||
page * 21,
|
page * 21,
|
||||||
sortWay[sortValue],
|
sortWay[sortValue],
|
||||||
theme,
|
theme,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<!ENTITY hosturl "api.copy-manga.com">
|
<!ENTITY hosturl "api.copy-manga.com">
|
||||||
<!ENTITY appver "2.3.0">
|
<!ENTITY appver "2.3.0">
|
||||||
<!ENTITY proxyurl "copymanga.azurewebsites.net">
|
<!ENTITY proxyurl "copymanga.azurewebsites.net">
|
||||||
<!ENTITY platform "1">
|
<!ENTITY platform "3">
|
||||||
]>
|
]>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">拷贝漫画</string>
|
<string name="app_name">拷贝漫画</string>
|
||||||
@@ -64,31 +64,30 @@
|
|||||||
<string name="touch_img_error">预载图片头失败</string>
|
<string name="touch_img_error">预载图片头失败</string>
|
||||||
<string name="analyze_img_size_error">读取图片大小失败</string>
|
<string name="analyze_img_size_error">读取图片大小失败</string>
|
||||||
|
|
||||||
<string name="networkApiUrl">https://%1$s/api/v3/system/network2?platform=%2$s</string>
|
<string name="networkApiUrl">/api/v3/system/network2?platform=%1$s</string>
|
||||||
<string name="mainPageApiUrl">https://%1$s/api/v3/h5/homeIndex?platform=%2$s</string>
|
<string name="mainPageApiUrl">/api/v3/h5/homeIndex?platform=%1$s</string>
|
||||||
<string name="referUrl">https://%1$s</string>
|
|
||||||
<string name="hostUrl">&hosturl;</string>
|
<string name="hostUrl">&hosturl;</string>
|
||||||
<string name="proxyUrl">&proxyurl;</string>
|
<string name="proxyUrl">&proxyurl;</string>
|
||||||
<string name="rankApiUrl">https://%1$s/api/v3/ranks?limit=21&offset=%2$d&date_type=%3$s&audience_type=%4$s&platform=%5$s</string>
|
<string name="rankApiUrl">/api/v3/ranks?limit=21&offset=%1$d&date_type=%2$s&audience_type=%3$s&platform=%4$s</string>
|
||||||
<string name="searchApiUrl">https://%1$s/api/v3/search/comic?limit=21&offset=%2$d&q=%3$s&q_type=%4$s&platform=%5$s</string>
|
<string name="searchApiUrl">/api/v3/search/comic?limit=21&offset=%1$d&q=%2$s&q_type=%3$s&platform=%4$s</string>
|
||||||
<string name="filterApiUrl">https://%1$s/api/v3/h5/filter/comic/tags?platform=%2$s</string>
|
<string name="filterApiUrl">/api/v3/h5/filter/comic/tags?platform=%1$s</string>
|
||||||
<string name="sortApiUrl">https://%1$s/api/v3/comics?limit=21&offset=%2$d&ordering=%3$s&theme=%4$s&top=%5$s&platform=%6$s</string>
|
<string name="sortApiUrl">/api/v3/comics?limit=21&offset=%1$d&ordering=%2$s&theme=%3$s&top=%4$s&platform=%5$s</string>
|
||||||
<string name="bookInfoApiUrl">https://%1$s/api/v3/comic2/%2$s?platform=%3$s</string>
|
<string name="bookInfoApiUrl">/api/v3/comic2/%1$s?platform=%2$s</string>
|
||||||
<string name="bookUserQueryApiUrl">https://%1$s/api/v3/comic2/%2$s/query?platform=%3$s</string>
|
<string name="bookUserQueryApiUrl">/api/v3/comic2/%1$s/query?platform=%2$s</string>
|
||||||
<string name="groupInfoApiUrl">https://%1$s/api/v3/comic/%2$s/group/%3$s/chapters?limit=100&offset=%4$d&platform=%5$s</string>
|
<string name="groupInfoApiUrl">/api/v3/comic/%1$s/group/%2$s/chapters?limit=100&offset=%3$d&platform=%4$s</string>
|
||||||
<string name="chapterInfoApiUrl">https://%1$s/api/v3/comic/%2$s/chapter%3$s/%4$s?platform=%5$s</string>
|
<string name="chapterInfoApiUrl">/api/v3/comic/%1$s/chapter%2$s/%3$s?platform=%4$s</string>
|
||||||
<string name="topicApiUrl">https://%1$s/api/v3/topic/%2$s?platform=%3$s</string>
|
<string name="topicApiUrl">/api/v3/topic/%1$s?platform=%2$s</string>
|
||||||
<string name="topicContentApiUrl">https://%1$s/api/v3/topic/%2$s/contents?type=%3$d&limit=21&offset=%4$d&platform=%5$s</string>
|
<string name="topicContentApiUrl">/api/v3/topic/%1$s/contents?type=%2$d&limit=21&offset=%3$d&platform=%4$s</string>
|
||||||
<string name="recommendApiUrl">https://%1$s/api/v3/recs?pos=3200102&limit=21&offset=%2$d&platform=%3$s</string>
|
<string name="recommendApiUrl">/api/v3/recs?pos=3200102&limit=21&offset=%1$d&platform=%2$s</string>
|
||||||
<string name="newestApiUrl">https://%1$s/api/v3/update/newest?limit=21&offset=%2$d&platform=%3$s</string>
|
<string name="newestApiUrl">/api/v3/update/newest?limit=21&offset=%1$d&platform=%2$s</string>
|
||||||
<string name="finishApiUrl">https://%1$s/api/v3/comics?limit=21&offset=%2$d&ordering=%3$s&top=finish&platform=%4$s</string>
|
<string name="finishApiUrl">/api/v3/comics?limit=21&offset=%1$d&ordering=%2$s&top=finish&platform=%3$s</string>
|
||||||
<string name="authorApiUrl">https://%1$s/api/v3/comics?limit=21&offset=%2$d&ordering=%3$s&author=%4$s&platform=%5$s</string>
|
<string name="authorApiUrl">/api/v3/comics?limit=21&offset=%1$d&ordering=%2$s&author=%3$s&platform=%4$s</string>
|
||||||
<string name="captionApiUrl">https://%1$s/api/v3/comics?limit=21&offset=%2$d&ordering=%3$s&theme=%4$s&platform=%5$s</string>
|
<string name="captionApiUrl">/api/v3/comics?limit=21&offset=%1$d&ordering=%2$s&theme=%3$s&platform=%4$s</string>
|
||||||
<string name="loginApiUrl">https://%1$s/api/v3/login?platform=%2$s</string>
|
<string name="loginApiUrl">/api/v3/login?platform=%1$s</string>
|
||||||
<string name="memberInfoApiUrl">https://%1$s/api/v3/member/info?platform=%2$s</string>
|
<string name="memberInfoApiUrl">/api/v3/member/info?platform=%1$s</string>
|
||||||
<string name="historyApiUrl">https://%1$s/api/v3/member/browse/comics?limit=21&offset=%2$d&platform=%3$s</string>
|
<string name="historyApiUrl">/api/v3/member/browse/comics?limit=21&offset=%1$d&platform=%2$s</string>
|
||||||
<string name="shelfApiUrl">https://%1$s/api/v3/member/collect/comics?limit=21&offset=%2$d&free_type=1&ordering=%3$s&platform=%4$s</string>
|
<string name="shelfApiUrl">/api/v3/member/collect/comics?limit=21&offset=%1$d&free_type=1&ordering=%2$s&platform=%3$s</string>
|
||||||
<string name="shelfOperateApiUrl">https://%1$s/api/v3/member/collect/comic</string>
|
<string name="shelfOperateApiUrl">/api/v3/member/collect/comic</string>
|
||||||
|
|
||||||
<string name="imgProxyApiUrl">https://&proxyurl;/api/img?code=%1$s&url=%2$s</string>
|
<string name="imgProxyApiUrl">https://&proxyurl;/api/img?code=%1$s&url=%2$s</string>
|
||||||
<string name="imgProxyCodeKeyID">settings_cat_net_et_img_proxy_code</string>
|
<string name="imgProxyCodeKeyID">settings_cat_net_et_img_proxy_code</string>
|
||||||
@@ -178,6 +177,8 @@
|
|||||||
<string name="settings_cat_net_et_summary_api_url">一般无需更改,除非拷贝漫画官方更改网址,默认:&hosturl;</string>
|
<string name="settings_cat_net_et_summary_api_url">一般无需更改,除非拷贝漫画官方更改网址,默认:&hosturl;</string>
|
||||||
<string name="settings_cat_net_et_title_reverse_proxy">反向代理</string>
|
<string name="settings_cat_net_et_title_reverse_proxy">反向代理</string>
|
||||||
<string name="settings_cat_net_et_summary_reverse_proxy">您可以自建反向代理并填写在此处以解决网络不畅</string>
|
<string name="settings_cat_net_et_summary_reverse_proxy">您可以自建反向代理并填写在此处以解决网络不畅</string>
|
||||||
|
<string name="settings_cat_net_et_title_umstring">友盟ID</string>
|
||||||
|
<string name="settings_cat_net_et_summary_umstring">填写您分配到的友盟ID</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_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>
|
||||||
@@ -207,7 +208,6 @@
|
|||||||
|
|
||||||
<string name="login_null_username">用户名为空</string>
|
<string name="login_null_username">用户名为空</string>
|
||||||
<string name="login_null_pwd">密码为空</string>
|
<string name="login_null_pwd">密码为空</string>
|
||||||
<string name="login_get_conn_failed">登录失败</string>
|
|
||||||
<string name="login_parse_json_error">解析返回数据失败</string>
|
<string name="login_parse_json_error">解析返回数据失败</string>
|
||||||
<string name="login_get_avatar_failed">获取用户信息失败</string>
|
<string name="login_get_avatar_failed">获取用户信息失败</string>
|
||||||
<string name="login_restart_to_apply">重启应用以彻底退出登录</string>
|
<string name="login_restart_to_apply">重启应用以彻底退出登录</string>
|
||||||
|
|||||||
@@ -120,6 +120,16 @@
|
|||||||
app:enableCopying="true"
|
app:enableCopying="true"
|
||||||
app:iconSpaceReserved="false"
|
app:iconSpaceReserved="false"
|
||||||
app:key="@string/reverseProxyKeyID" />
|
app:key="@string/reverseProxyKeyID" />
|
||||||
|
<EditTextPreference
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:selectAllOnFocus="false"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:title="@string/settings_cat_net_et_title_umstring"
|
||||||
|
android:summary="@string/settings_cat_net_et_summary_umstring"
|
||||||
|
app:enableCopying="true"
|
||||||
|
app:iconSpaceReserved="false"
|
||||||
|
app:key="settings_cat_net_et_umstring" />
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
app:iconSpaceReserved="false"
|
app:iconSpaceReserved="false"
|
||||||
app:key="@string/apiProxyKeyID"
|
app:key="@string/apiProxyKeyID"
|
||||||
|
|||||||
Reference in New Issue
Block a user