copy tokens directly

This commit is contained in:
HolographicHat 2022-11-22 21:29:50 +08:00
parent 95dcb5e919
commit bf8073a331
No known key found for this signature in database
GPG Key ID: 12C8B5B85E5CE5C3
6 changed files with 23 additions and 297 deletions

View File

@ -2,8 +2,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:label="GetToken"

View File

@ -9,10 +9,7 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.Keep
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
@ -27,7 +24,6 @@ import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@ -35,13 +31,7 @@ import androidx.constraintlayout.compose.ConstraintLayout
import androidx.core.content.getSystemService
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import hat.holo.token.models.AccountInfo
import hat.holo.token.models.BaseResponse
import hat.holo.token.models.FetchRsp
import hat.holo.token.utils.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
val textColor = Color(0xFF424242)
@ -79,55 +69,6 @@ private fun TokenActivity.showDialog(msg: String) = runOnUiThread {
}.create().show()
}
private suspend fun TokenActivity.genAuthCode(
acc: AccountInfo,
useSToken: Boolean
): String? = runCatching {
val params = mutableMapOf<String, Any?>("app_id" to "4", "device" to "0")
val fetchResult = buildHttpRequest {
url("https://hk4e-sdk.mihoyo.com/hk4e_cn/combo/panda/qrcode/fetch")
params.post(this)
}.getAsJson<BaseResponse>()
if (fetchResult.retcode != 0) {
showDialog("请求失败: ${fetchResult.message}")
return null
}
val ticket = fetchResult.data<FetchRsp>().getUri().getQueryParameter("ticket")
val scanResult = buildHttpRequest {
url("https://api-sdk.mihoyo.com/hk4e_cn/combo/panda/qrcode/scan")
params.apply {
put("ticket", ticket)
}.post(this)
}.getAsJson<BaseResponse>()
if (scanResult.retcode != 0) {
showDialog("请求失败: ${scanResult.message}")
return@runCatching null
}
val confirmResult = buildHttpRequest {
url("https://api-sdk.mihoyo.com/hk4e_cn/combo/panda/qrcode/confirm")
params.apply {
put("payload", buildMap {
put("proto", "Account")
put("raw", buildMap {
put("uid", acc.uid)
put("ltoken", acc.lToken)
if (useSToken) {
put("mid", acc.mid)
put("stoken", acc.sToken)
}
}.toJson())
})
}.post(this)
}.getAsJson<BaseResponse>()
if (confirmResult.retcode != 0) {
showDialog("请求失败: ${confirmResult.message}")
return null
}
return ticket
}.onFailure {
showDialog("网络异常,请稍后重试")
}.getOrNull()
@Composable
private fun TokenActivity.Content(accountInfo: AccountInfo) = Column(
modifier = Modifier.fillMaxSize()
@ -136,9 +77,8 @@ private fun TokenActivity.Content(accountInfo: AccountInfo) = Column(
Column(
modifier = Modifier.padding(15.dp)
) {
var isLoading by remember { mutableStateOf(false) }
var authTicket by remember { mutableStateOf("") }
var grantSToken by remember { mutableStateOf(false) }
var showDoneIcon by remember { mutableStateOf(false) }
CustomCheckBox(
checked = true,
onCheckedChange = {},
@ -161,106 +101,40 @@ private fun TokenActivity.Content(accountInfo: AccountInfo) = Column(
} // TODO: More description
)
Divider()
AnimatedVisibility(
visible = authTicket.isNotEmpty(),
modifier = Modifier.fillMaxWidth()
) {
AuthTicketView(authTicket)
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End
) {
if (BuildConfig.DEBUG) {
TextButton(
onClick = {
CoroutineScope(Dispatchers.IO).launch {
val confirmResult = buildHttpRequest {
url("https://hk4e-sdk.mihoyo.com/hk4e_cn/combo/panda/qrcode/query")
buildMap {
put("app_id", "4")
put("device", "0")
put("ticket", authTicket.removePrefix("ma_"))
}.post(this)
}.getAsJson<BaseResponse>()
if (confirmResult.retcode != 0) {
showDialog("请求失败: ${confirmResult.message}")
return@launch
} else {
showDialog(confirmResult.data?.toJson() ?: "success, but data is null")
}
}
}
) {
Text(text = "query")
}
}
TextButton(
onClick = {
CoroutineScope(Dispatchers.IO).launch {
authTicket = ""
isLoading = true
authTicket = genAuthCode(accountInfo, grantSToken)?.let { s -> "ma_${s}" } ?: ""
isLoading = false
runCatching {
val authStr = buildMap {
put("uid", accountInfo.uid)
put("ltoken", accountInfo.lToken)
if (grantSToken) {
put("mid", accountInfo.mid)
put("stoken", accountInfo.sToken)
}
}.map { (k, v) -> "$k=$v" }.joinToString(";")
val clip = ClipData.newPlainText(null, authStr)
getSystemService<ClipboardManager>()!!.setPrimaryClip(clip)
}.onFailure {
showDialog("复制失败")
}.onSuccess {
showDoneIcon = true
}
},
enabled = !isLoading
}
) {
AnimatedVisibility(visible = isLoading) {
CircularProgressIndicator(
modifier = Modifier.size(24.dp),
strokeWidth = 3.dp
AnimatedVisibility(visible = showDoneIcon) {
Image(
imageVector = Icons.Outlined.Done,
contentDescription = null,
colorFilter = ColorFilter.tint(Color(0xFF4CAF50))
)
}
AnimatedVisibility(visible = !isLoading) {
// 120s available
Text("生成登录代码")
}
Text("复制登录信息")
}
}
}
}
@OptIn(ExperimentalAnimationApi::class)
@Composable
private fun TokenActivity.AuthTicketView(authTicket: String) = ConstraintLayout(
modifier = Modifier.fillMaxWidth()
) {
val (divider, code, copyBtn) = createRefs()
Text(
text = authTicket,
modifier = Modifier.constrainAs(code) {
top.linkTo(parent.top, 12.dp)
start.linkTo(parent.start, 12.dp)
},
fontFamily = FontFamily.Monospace
)
var showDoneIcon by remember { mutableStateOf(false) }
val iconColor by animateColorAsState(if (showDoneIcon) Color(0xFF4CAF50) else textColor)
IconButton(
modifier = Modifier.constrainAs(copyBtn) {
top.linkTo(parent.top)
end.linkTo(parent.end)
bottom.linkTo(parent.bottom)
},
onClick = {
runCatching {
val clip = ClipData.newPlainText(null, authTicket)
getSystemService<ClipboardManager>()!!.setPrimaryClip(clip)
}.onFailure {
showDialog("复制失败")
}.onSuccess {
showDoneIcon = true
}
}
) {
AnimatedContent(targetState = if (showDoneIcon) Icons.Outlined.Done else Res.iconCopy) { targetIcon ->
Image(
imageVector = targetIcon,
contentDescription = "Copy auth ticket",
colorFilter = ColorFilter.tint(iconColor)
)
}
LaunchedEffect(showDoneIcon) {
if (showDoneIcon) {
delay(1500)
@ -268,12 +142,6 @@ private fun TokenActivity.AuthTicketView(authTicket: String) = ConstraintLayout(
}
}
}
Divider(
modifier = Modifier.constrainAs(divider) {
top.linkTo(code.bottom, 12.dp)
bottom.linkTo(parent.bottom)
}
)
}
@Composable

View File

@ -1,16 +0,0 @@
package hat.holo.token.models
import androidx.annotation.Keep
import com.google.gson.JsonElement
import hat.holo.token.utils.convertTo
@Keep
data class BaseResponse(
val retcode: Int = 0,
val message: String = "ok",
val data: JsonElement? = null
) {
inline fun <reified T : Any> data(): T {
return data!!.convertTo()
}
}

View File

@ -1,11 +0,0 @@
package hat.holo.token.models
import android.net.Uri
import androidx.annotation.Keep
@Keep
data class FetchRsp(
val url: String
) {
fun getUri(): Uri = Uri.parse(url)
}

View File

@ -1,70 +0,0 @@
package hat.holo.token.utils
import android.util.Log
import com.google.gson.Gson
import com.google.gson.JsonElement
import hat.holo.token.BuildConfig
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.logging.HttpLoggingInterceptor
import java.io.IOException
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
private val gson by lazy { Gson() }
fun buildHttpRequest(block: Request.Builder.() -> Unit) = Request.Builder().apply(block).build()
fun <K,V> Map<K,V>.toJson() = gson.toJson(this)!!
fun JsonElement.toJson() = gson.toJson(this)!!
fun <K,V> Map<K,V>.post(builder: Request.Builder) {
builder.post(toJson().toRequestBody("application/json".toMediaType()))
}
val defaultOkClient = OkHttpClient.Builder().apply {
if (BuildConfig.DEBUG) {
val i = HttpLoggingInterceptor {
Log.d("LSPosed-Bridge", it)
}
i.level = HttpLoggingInterceptor.Level.BODY
addInterceptor(i)
}
}.build()
suspend inline fun <reified T : Any> Request.getAsJson(client: OkHttpClient = defaultOkClient): T {
return Gson().fromJson(this@getAsJson.getAsText(client), T::class.java)!!
}
suspend fun Request.getAsText(client: OkHttpClient = defaultOkClient) = withContext(Dispatchers.IO) {
client.newCall(this@getAsText).await().use { r->
checkNotNull(r.body).use { b ->
b.string()
}
}
}
inline fun <reified T: Any> JsonElement.convertTo(): T = Gson().fromJson(this, T::class.java)
suspend fun Call.await(): Response = suspendCancellableCoroutine { continuation ->
enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
continuation.resume(response)
}
override fun onFailure(call: Call, e: IOException) {
if (continuation.isCancelled) return
continuation.resumeWithException(e)
}
})
continuation.invokeOnCancellation {
try {
cancel()
} catch (_: Throwable) {
}
}
}

View File

@ -1,9 +1,6 @@
package hat.holo.token.utils
import android.graphics.drawable.Drawable
import androidx.compose.material.icons.materialIcon
import androidx.compose.material.icons.materialPath
import androidx.compose.ui.graphics.vector.ImageVector
import java.util.*
@Suppress("SpellCheckingInspection")
@ -16,44 +13,4 @@ object Res {
}
}
val iconCopy: ImageVector
get() {
if (_contentCopy != null) {
return _contentCopy!!
}
_contentCopy = materialIcon(name = "Outlined.ContentCopy") {
materialPath {
moveTo(16.0f, 1.0f)
lineTo(4.0f, 1.0f)
curveToRelative(-1.1f, 0.0f, -2.0f, 0.9f, -2.0f, 2.0f)
verticalLineToRelative(14.0f)
horizontalLineToRelative(2.0f)
lineTo(4.0f, 3.0f)
horizontalLineToRelative(12.0f)
lineTo(16.0f, 1.0f)
close()
moveTo(19.0f, 5.0f)
lineTo(8.0f, 5.0f)
curveToRelative(-1.1f, 0.0f, -2.0f, 0.9f, -2.0f, 2.0f)
verticalLineToRelative(14.0f)
curveToRelative(0.0f, 1.1f, 0.9f, 2.0f, 2.0f, 2.0f)
horizontalLineToRelative(11.0f)
curveToRelative(1.1f, 0.0f, 2.0f, -0.9f, 2.0f, -2.0f)
lineTo(21.0f, 7.0f)
curveToRelative(0.0f, -1.1f, -0.9f, -2.0f, -2.0f, -2.0f)
close()
moveTo(19.0f, 21.0f)
lineTo(8.0f, 21.0f)
lineTo(8.0f, 7.0f)
horizontalLineToRelative(11.0f)
verticalLineToRelative(14.0f)
close()
}
}
return _contentCopy!!
}
@Suppress("ObjectPropertyName")
private var _contentCopy: ImageVector? = null
}