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" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:label="GetToken" android:label="GetToken"

View File

@ -9,10 +9,7 @@ import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.annotation.Keep import androidx.annotation.Keep
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.animateColorAsState
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource 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.AnnotatedString
import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@ -35,13 +31,7 @@ import androidx.constraintlayout.compose.ConstraintLayout
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.accompanist.systemuicontroller.rememberSystemUiController
import hat.holo.token.models.AccountInfo 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.delay
import kotlinx.coroutines.launch
val textColor = Color(0xFF424242) val textColor = Color(0xFF424242)
@ -79,55 +69,6 @@ private fun TokenActivity.showDialog(msg: String) = runOnUiThread {
}.create().show() }.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 @Composable
private fun TokenActivity.Content(accountInfo: AccountInfo) = Column( private fun TokenActivity.Content(accountInfo: AccountInfo) = Column(
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
@ -136,9 +77,8 @@ private fun TokenActivity.Content(accountInfo: AccountInfo) = Column(
Column( Column(
modifier = Modifier.padding(15.dp) modifier = Modifier.padding(15.dp)
) { ) {
var isLoading by remember { mutableStateOf(false) }
var authTicket by remember { mutableStateOf("") }
var grantSToken by remember { mutableStateOf(false) } var grantSToken by remember { mutableStateOf(false) }
var showDoneIcon by remember { mutableStateOf(false) }
CustomCheckBox( CustomCheckBox(
checked = true, checked = true,
onCheckedChange = {}, onCheckedChange = {},
@ -161,91 +101,22 @@ private fun TokenActivity.Content(accountInfo: AccountInfo) = Column(
} // TODO: More description } // TODO: More description
) )
Divider() Divider()
AnimatedVisibility(
visible = authTicket.isNotEmpty(),
modifier = Modifier.fillMaxWidth()
) {
AuthTicketView(authTicket)
}
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End horizontalArrangement = Arrangement.End
) { ) {
if (BuildConfig.DEBUG) {
TextButton( 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
}
},
enabled = !isLoading
) {
AnimatedVisibility(visible = isLoading) {
CircularProgressIndicator(
modifier = Modifier.size(24.dp),
strokeWidth = 3.dp
)
}
AnimatedVisibility(visible = !isLoading) {
// 120s available
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 = { onClick = {
runCatching { runCatching {
val clip = ClipData.newPlainText(null, authTicket) 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) getSystemService<ClipboardManager>()!!.setPrimaryClip(clip)
}.onFailure { }.onFailure {
showDialog("复制失败") showDialog("复制失败")
@ -254,13 +125,16 @@ private fun TokenActivity.AuthTicketView(authTicket: String) = ConstraintLayout(
} }
} }
) { ) {
AnimatedContent(targetState = if (showDoneIcon) Icons.Outlined.Done else Res.iconCopy) { targetIcon -> AnimatedVisibility(visible = showDoneIcon) {
Image( Image(
imageVector = targetIcon, imageVector = Icons.Outlined.Done,
contentDescription = "Copy auth ticket", contentDescription = null,
colorFilter = ColorFilter.tint(iconColor) colorFilter = ColorFilter.tint(Color(0xFF4CAF50))
) )
} }
Text("复制登录信息")
}
}
LaunchedEffect(showDoneIcon) { LaunchedEffect(showDoneIcon) {
if (showDoneIcon) { if (showDoneIcon) {
delay(1500) 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 @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 package hat.holo.token.utils
import android.graphics.drawable.Drawable 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.* import java.util.*
@Suppress("SpellCheckingInspection") @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
} }