feat(scripting): better debug ui

This commit is contained in:
rhunk 2024-01-17 19:03:54 +01:00
parent 0f3fb7bcd0
commit 94b519614d
3 changed files with 138 additions and 68 deletions

View File

@ -4,10 +4,8 @@ import android.annotation.SuppressLint
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import android.text.InputType
import android.view.Gravity import android.view.Gravity
import android.view.ViewGroup.MarginLayoutParams import android.view.ViewGroup.MarginLayoutParams
import android.widget.EditText
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.ProgressBar import android.widget.ProgressBar
@ -35,6 +33,7 @@ import me.rhunk.snapenhance.core.features.impl.downloader.decoder.MessageDecoder
import me.rhunk.snapenhance.core.features.impl.messaging.Messaging import me.rhunk.snapenhance.core.features.impl.messaging.Messaging
import me.rhunk.snapenhance.core.features.impl.spying.MessageLogger import me.rhunk.snapenhance.core.features.impl.spying.MessageLogger
import me.rhunk.snapenhance.core.ui.ViewAppearanceHelper import me.rhunk.snapenhance.core.ui.ViewAppearanceHelper
import me.rhunk.snapenhance.core.ui.debugEditText
import me.rhunk.snapenhance.core.util.hook.HookAdapter import me.rhunk.snapenhance.core.util.hook.HookAdapter
import me.rhunk.snapenhance.core.util.hook.HookStage import me.rhunk.snapenhance.core.util.hook.HookStage
import me.rhunk.snapenhance.core.util.hook.hook import me.rhunk.snapenhance.core.util.hook.hook
@ -159,15 +158,7 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
ViewAppearanceHelper.newAlertDialogBuilder(context.mainActivity!!).apply { ViewAppearanceHelper.newAlertDialogBuilder(context.mainActivity!!).apply {
setTitle("Debug Media Info") setTitle("Debug Media Info")
setView(EditText(context).apply { setView(debugEditText(context, mediaInfoText))
inputType = InputType.TYPE_NULL
setTextIsSelectable(true)
isSingleLine = false
textSize = 12f
setPadding(20, 20, 20, 20)
setText(mediaInfoText)
setTextColor(context.resources.getColor(android.R.color.white, context.theme))
})
setNeutralButton("Copy") { _, _ -> setNeutralButton("Copy") { _, _ ->
this@MediaDownloader.context.copyToClipboard(mediaInfoText) this@MediaDownloader.context.copyToClipboard(mediaInfoText)
} }

View File

@ -0,0 +1,21 @@
package me.rhunk.snapenhance.core.ui
import android.content.Context
import android.text.InputType
import android.view.View
import android.widget.EditText
import android.widget.ScrollView
fun debugEditText(context: Context, initialText: String): View {
return ScrollView(context).apply {
isSmoothScrollingEnabled = true
addView(EditText(context).apply {
inputType = InputType.TYPE_NULL
isSingleLine = false
setTextIsSelectable(true)
textSize = 12f
setPadding(20, 20, 20, 20)
setText(initialText)
})
}
}

View File

@ -7,22 +7,34 @@ import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams import android.view.ViewGroup.MarginLayoutParams
import android.widget.Button import android.widget.Button
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import me.rhunk.snapenhance.common.data.ContentType import me.rhunk.snapenhance.common.data.ContentType
import me.rhunk.snapenhance.common.ui.createComposeView
import me.rhunk.snapenhance.common.util.protobuf.ProtoReader import me.rhunk.snapenhance.common.util.protobuf.ProtoReader
import me.rhunk.snapenhance.core.features.impl.downloader.MediaDownloader import me.rhunk.snapenhance.core.features.impl.downloader.MediaDownloader
import me.rhunk.snapenhance.core.features.impl.downloader.decoder.MessageDecoder
import me.rhunk.snapenhance.core.features.impl.experiments.ConvertMessageLocally import me.rhunk.snapenhance.core.features.impl.experiments.ConvertMessageLocally
import me.rhunk.snapenhance.core.features.impl.messaging.Messaging import me.rhunk.snapenhance.core.features.impl.messaging.Messaging
import me.rhunk.snapenhance.core.features.impl.spying.MessageLogger import me.rhunk.snapenhance.core.features.impl.spying.MessageLogger
import me.rhunk.snapenhance.core.ui.ViewAppearanceHelper import me.rhunk.snapenhance.core.ui.ViewAppearanceHelper
import me.rhunk.snapenhance.core.ui.ViewTagState import me.rhunk.snapenhance.core.ui.ViewTagState
import me.rhunk.snapenhance.core.ui.applyTheme import me.rhunk.snapenhance.core.ui.applyTheme
import me.rhunk.snapenhance.core.ui.debugEditText
import me.rhunk.snapenhance.core.ui.menu.AbstractMenu import me.rhunk.snapenhance.core.ui.menu.AbstractMenu
import me.rhunk.snapenhance.core.ui.triggerCloseTouchEvent import me.rhunk.snapenhance.core.ui.triggerCloseTouchEvent
import me.rhunk.snapenhance.core.util.hook.HookStage import me.rhunk.snapenhance.core.util.hook.HookStage
import me.rhunk.snapenhance.core.util.hook.hook import me.rhunk.snapenhance.core.util.hook.hook
import me.rhunk.snapenhance.core.util.ktx.getDimens import me.rhunk.snapenhance.core.util.ktx.getDimens
import java.time.Instant import java.text.SimpleDateFormat
import java.util.Date
@SuppressLint("DiscouragedApi") @SuppressLint("DiscouragedApi")
@ -50,15 +62,17 @@ class ChatActionMenu : AbstractMenu() {
} }
} }
private fun copyAlertDialog(context: Context, title: String, text: String) { private fun debugAlertDialog(context: Context, title: String, text: String) {
ViewAppearanceHelper.newAlertDialogBuilder(context).apply { this@ChatActionMenu.context.runOnUiThread {
setTitle(title) ViewAppearanceHelper.newAlertDialogBuilder(context).apply {
setMessage(text) setTitle(title)
setPositiveButton("OK") { dialog, _ -> dialog.dismiss() } setView(debugEditText(context, text))
setNegativeButton("Copy") { _, _ -> setPositiveButton("OK") { dialog, _ -> dialog.dismiss() }
this@ChatActionMenu.context.copyToClipboard(text, title) setNegativeButton("Copy") { _, _ ->
} this@ChatActionMenu.context.copyToClipboard(text, title)
}.show() }
}.show()
}
} }
private val lastFocusedMessage private val lastFocusedMessage
@ -78,6 +92,7 @@ class ChatActionMenu : AbstractMenu() {
} }
} }
@OptIn(ExperimentalLayoutApi::class)
@SuppressLint("SetTextI18n", "DiscouragedApi", "ClickableViewAccessibility") @SuppressLint("SetTextI18n", "DiscouragedApi", "ClickableViewAccessibility")
override fun inject(parent: ViewGroup, view: View, viewConsumer: (View) -> Unit) { override fun inject(parent: ViewGroup, view: View, viewConsumer: (View) -> Unit) {
val viewGroup = parent.parent.parent as? ViewGroup ?: return val viewGroup = parent.parent.parent as? ViewGroup ?: return
@ -176,56 +191,99 @@ class ChatActionMenu : AbstractMenu() {
} }
if (context.isDeveloper) { if (context.isDeveloper) {
viewGroup.addView(createContainer(viewGroup).apply { val composeDebugView = createComposeView(viewGroup.context) {
val debugText = StringBuilder() FlowRow(
modifier = Modifier.fillMaxWidth().padding(5.dp),
setOnClickListener { horizontalArrangement = Arrangement.spacedBy(3.dp)
this@ChatActionMenu.context.copyToClipboard(debugText.toString(), "debug") ) {
} Button(onClick = {
val arroyoMessage = lastFocusedMessage ?: return@Button
addView(TextView(viewGroup.context).apply { debugAlertDialog(viewGroup.context,
setPadding(20, 20, 20, 20) "Message Info",
textSize = 10f StringBuilder().apply {
addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> runCatching {
val arroyoMessage = lastFocusedMessage ?: return@addOnLayoutChangeListener append("conversation_id: ${arroyoMessage.clientConversationId}\n")
text = debugText.apply { append("sender_id: ${arroyoMessage.senderId}\n")
runCatching { append("client_id: ${arroyoMessage.clientMessageId}, server_id: ${arroyoMessage.serverMessageId}\n")
clear() append("content_type: ${ContentType.fromId(arroyoMessage.contentType)} (${arroyoMessage.contentType})\n")
append("sender_id: ${arroyoMessage.senderId}\n") append("parsed_content_type: ${
append("client_id: ${arroyoMessage.clientMessageId}, server_id: ${arroyoMessage.serverMessageId}\n") ContentType.fromMessageContainer(
append("conversation_id: ${arroyoMessage.clientConversationId}\n") ProtoReader(arroyoMessage.messageContent!!).followPath(4, 4)
append("arroyo_content_type: ${ContentType.fromId(arroyoMessage.contentType)} (${arroyoMessage.contentType})\n") ).let { "$it (${it?.id})" }}\n")
append("parsed_content_type: ${ append("creation_timestamp: ${
ContentType.fromMessageContainer( SimpleDateFormat.getDateTimeInstance().format(
ProtoReader(arroyoMessage.messageContent!!).followPath(4, 4) Date(arroyoMessage.creationTimestamp)
).let { "$it (${it?.id})" }}\n") )} (${arroyoMessage.creationTimestamp})\n")
append("creation_timestamp: ${arroyoMessage.creationTimestamp} (${Instant.ofEpochMilli(arroyoMessage.creationTimestamp)})\n") append("read_timestamp: ${SimpleDateFormat.getDateTimeInstance().format(
append("read_timestamp: ${arroyoMessage.readTimestamp} (${Instant.ofEpochMilli(arroyoMessage.readTimestamp)})\n") Date(arroyoMessage.readTimestamp)
append("is_messagelogger_deleted: ${messageLogger.isMessageDeleted(arroyoMessage.clientConversationId!!, arroyoMessage.clientMessageId.toLong())}\n") )} (${arroyoMessage.readTimestamp})\n")
append("is_messagelogger_stored: ${messageLogger.getMessageObject(arroyoMessage.clientConversationId!!, arroyoMessage.clientMessageId.toLong()) != null}\n") append("ml_deleted: ${messageLogger.isMessageDeleted(arroyoMessage.clientConversationId!!, arroyoMessage.clientMessageId.toLong())}, ")
}.onFailure { append("ml_stored: ${messageLogger.getMessageObject(arroyoMessage.clientConversationId!!, arroyoMessage.clientMessageId.toLong()) != null}\n")
debugText.append("Error: $it\n") }
} }.toString()
}.toString().trimEnd() )
}) {
Text("Info")
} }
}) Button(onClick = {
val arroyoMessage = lastFocusedMessage ?: return@Button
messaging.conversationManager?.fetchMessage(arroyoMessage.clientConversationId!!, arroyoMessage.clientMessageId.toLong(), onSuccess = { message ->
val decodedAttachments = MessageDecoder.decode(message.messageContent!!)
// action buttons debugAlertDialog(
addView(LinearLayout(viewGroup.context).apply {
orientation = LinearLayout.HORIZONTAL
addView(Button(viewGroup.context).apply {
text = "Show Deleted Message Object"
setOnClickListener {
val message = lastFocusedMessage ?: return@setOnClickListener
copyAlertDialog(
viewGroup.context, viewGroup.context,
"Deleted Message Object", "Media References",
messageLogger.getMessageObject(message.clientConversationId!!, message.clientMessageId.toLong())?.toString() decodedAttachments.mapIndexed { index, attachment ->
?: "null" StringBuilder().apply {
append("---- media $index ----\n")
append("resolveProto: ${attachment.mediaUrlKey}\n")
append("type: ${attachment.type}\n")
attachment.attachmentInfo?.apply {
encryption?.let {
append("encryption:\n - key: ${it.key}\n - iv: ${it.iv}\n")
}
resolution?.let {
append("resolution: ${it.first}x${it.second}\n")
}
duration?.let {
append("duration: $it\n")
}
}
}.toString()
}.joinToString("\n\n")
) )
} })
}) }) {
}) Text("Refs")
}
Button(onClick = {
val message = lastFocusedMessage ?: return@Button
debugAlertDialog(
viewGroup.context,
"Arroyo proto",
message.messageContent?.let { ProtoReader(it) }?.toString() ?: "empty"
)
}) {
Text("Arroyo proto")
}
Button(onClick = {
val arroyoMessage = lastFocusedMessage ?: return@Button
messaging.conversationManager?.fetchMessage(arroyoMessage.clientConversationId!!, arroyoMessage.clientMessageId.toLong(), onSuccess = { message ->
debugAlertDialog(
viewGroup.context,
"Message proto",
message.messageContent?.content?.let { ProtoReader(it) }?.toString() ?: "empty"
)
}, onError = {
this@ChatActionMenu.context.shortToast("Failed to fetch message: $it")
})
}) {
Text("Message proto")
}
}
}
viewGroup.addView(createContainer(viewGroup).apply {
addView(composeDebugView)
}) })
} }