feat(experimental): edit messages

This commit is contained in:
rhunk
2024-03-11 22:47:13 +01:00
parent 9f084afebe
commit 29f7e2aa1c
7 changed files with 120 additions and 3 deletions

View File

@@ -704,6 +704,10 @@
}
}
},
"edit_message": {
"name": "Edit Messages",
"description": "Allows you to edit messages in conversations"
},
"app_passcode": {
"name": "App Passcode",
"description": "Sets a passcode to lock the app"

View File

@@ -31,6 +31,7 @@ class Experimental : ConfigContainer() {
val storyLogger = boolean("story_logger") { requireRestart(); addNotices(FeatureNotice.UNSTABLE); }
val callRecorder = boolean("call_recorder") { requireRestart(); addNotices(FeatureNotice.UNSTABLE); }
val accountSwitcher = container("account_switcher", AccountSwitcherConfig()) { requireRestart(); addNotices(FeatureNotice.UNSTABLE) }
val editMessage = boolean("edit_message") { requireRestart(); addNotices(FeatureNotice.BAN_RISK) }
val appPasscode = string("app_passcode")
val appLockOnResume = boolean("app_lock_on_resume")
val infiniteStoryBoost = boolean("infinite_story_boost")

View File

@@ -20,6 +20,7 @@ import me.rhunk.snapenhance.core.wrapper.impl.Snapchatter
import me.rhunk.snapenhance.core.wrapper.impl.toSnapUUID
import me.rhunk.snapenhance.mapper.impl.CallbackMapper
import me.rhunk.snapenhance.mapper.impl.FriendsFeedEventDispatcherMapper
import java.util.UUID
import java.util.concurrent.Future
class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC or FeatureLoadParams.INIT_ASYNC or FeatureLoadParams.INIT_SYNC) {
@@ -93,7 +94,12 @@ class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_C
})
}
fun localUpdateMessage(conversationId: String, message: Message) {
fun localUpdateMessage(conversationId: String, message: Message, forceUpdate: Boolean = false) {
if (forceUpdate) {
message.messageMetadata?.screenRecordedBy = ArrayList<SnapUUID>(message.messageMetadata?.screenRecordedBy ?: emptyList()).apply {
add(SnapUUID(UUID.randomUUID().toString()))
}
}
conversationManagerDelegate?.let {
it::class.java.methods.first { method ->
method.name == "onConversationUpdated"

View File

@@ -4,11 +4,17 @@ import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.widget.Button
import android.widget.EditText
import android.widget.LinearLayout
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import me.rhunk.snapenhance.common.util.protobuf.ProtoReader
import me.rhunk.snapenhance.common.util.protobuf.ProtoWriter
import me.rhunk.snapenhance.core.features.impl.downloader.MediaDownloader
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.spying.MessageLogger
import me.rhunk.snapenhance.core.ui.ViewAppearanceHelper
import me.rhunk.snapenhance.core.ui.ViewTagState
import me.rhunk.snapenhance.core.ui.applyTheme
import me.rhunk.snapenhance.core.ui.menu.AbstractMenu
@@ -58,7 +64,11 @@ class ChatActionMenu : AbstractMenu() {
val viewGroup = parent.parent.parent as? ViewGroup ?: return
if (viewTagState[viewGroup]) return
//close the action menu using a touch event
val closeActionMenu = { parent.triggerCloseTouchEvent() }
val closeActionMenu = {
context.runOnUiThread {
parent.triggerCloseTouchEvent()
}
}
val messaging = context.feature(Messaging::class)
val messageLogger = context.feature(MessageLogger::class)
@@ -129,6 +139,72 @@ class ChatActionMenu : AbstractMenu() {
})
}
if (context.config.experimental.editMessage.get() && messaging.conversationManager?.isEditMessageSupported() == true) {
injectButton(Button(viewGroup.context).apply button@{
text = "Edit Message"
setOnClickListener {
messaging.conversationManager?.fetchMessage(
messaging.openedConversationUUID.toString(),
messaging.lastFocusedMessageId,
onSuccess = onSuccess@{ message ->
closeActionMenu()
if (message.senderId.toString() != this@ChatActionMenu.context.database.myUserId) {
this@ChatActionMenu.context.shortToast("You can only edit your own messages")
return@onSuccess
}
val editText = EditText(viewGroup.context).apply {
setText(ProtoReader(message.messageContent?.content ?: return@apply).getString(2, 1) ?: run {
this@ChatActionMenu.context.shortToast("You can only edit text messages")
return@onSuccess
})
setTextColor(resources.getColor(android.R.color.white, context.theme))
postDelayed({
requestFocus()
setSelection(text.length)
context.getSystemService(android.content.Context.INPUT_METHOD_SERVICE)
.let { it as android.view.inputmethod.InputMethodManager }
.showSoftInput(this, android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT)
}, 200)
}
this@ChatActionMenu.context.runOnUiThread {
ViewAppearanceHelper.newAlertDialogBuilder(this@ChatActionMenu.context.mainActivity!!)
.setPositiveButton("Save") { _, _ ->
val newMessageContent = ProtoWriter().apply {
from(2) { addString(1, editText.text.toString()) }
}.toByteArray()
message.messageContent?.content = newMessageContent
messaging.conversationManager?.editMessage(
message.messageDescriptor?.conversationId.toString(),
message.messageDescriptor?.messageId ?: return@setPositiveButton,
newMessageContent,
onSuccess = {
this@ChatActionMenu.context.coroutineScope.launch(Dispatchers.Main) {
message.messageMetadata?.isEdited = true
messaging.localUpdateMessage(
message.messageDescriptor?.conversationId.toString(),
message,
forceUpdate = true
)
}
},
onError = {
this@ChatActionMenu.context.shortToast("Failed to edit message: $it")
}
)
}
.setNegativeButton("Cancel") { dialog, _ -> dialog.dismiss() }
.setView(editText)
.setTitle("Edit message content")
.show()
}
}
)
}
})
}
if (context.config.experimental.convertMessageLocally.get()) {
injectButton(Button(viewGroup.context).apply {
text = this@ChatActionMenu.context.translation["chat_action_menu.convert_message"]

View File

@@ -80,7 +80,7 @@ class NewChatActionMenu : AbstractMenu() {
FlowRow(
modifier = Modifier
.fillMaxWidth()
.padding(5.dp),
.padding(2.dp),
horizontalArrangement = Arrangement.SpaceEvenly,
) {
Button(onClick = {

View File

@@ -159,4 +159,20 @@ class ConversationManager(
.override("onError") { onError(it.arg<Any>(0).toString()) }.build()
getOneOnOneConversationIds.invoke(instanceNonNull(), userIds.map { it.toSnapUUID().instanceNonNull() }.toMutableList(), callback)
}
fun editMessage(conversationId: String, messageId: Long, content: ByteArray, onSuccess: () -> Unit, onError: (error: String) -> Unit) {
val editMessageMethod = instanceNonNull()::class.java.methods.first { it.name == "editMessage" }
editMessageMethod.invoke(instanceNonNull(), editMessageMethod.parameterTypes[0].dataBuilder {
set("mConversationId", conversationId.toSnapUUID().instanceNonNull())
set("mMessageId", messageId)
}, editMessageMethod.parameterTypes[1].dataBuilder {
set("mContent", content)
set("mMentionInfo", null)
}, CallbackBuilder(getCallbackClass("Callback"))
.override("onSuccess") { onSuccess() }
.override("onError") { onError(it.arg<Any>(0).toString()) }.build()
)
}
fun isEditMessageSupported() = instanceNonNull()::class.java.methods.any { it.name == "editMessage" }
}

View File

@@ -20,9 +20,23 @@ class MessageMetadata(obj: Any?) : AbstractWrapper(obj){
@get:JSGetter @set:JSSetter
var seenBy by field("mSeenBy", uuidArrayListMapper)
@get:JSGetter @set:JSSetter
var screenRecordedBy by field("mScreenRecordedBy", uuidArrayListMapper)
@get:JSGetter @set:JSSetter
var screenShottedBy by field("mScreenShottedBy", uuidArrayListMapper)
@get:JSGetter @set:JSSetter
var reactions by field("mReactions") {
(it as ArrayList<*>).map { i -> UserIdToReaction(i) }.toMutableList()
}
@get:JSGetter @set:JSSetter
var isSaveable by field<Boolean>("mIsSaveable")
@get:JSGetter @set:JSSetter
var isEditable by field<Boolean>("mIsEditable")
@get:JSGetter @set:JSSetter
var isEdited by field<Boolean>("mIsEdited")
@get:JSGetter @set:JSSetter
var isErasable by field<Boolean>("mIsErasable")
@get:JSGetter @set:JSSetter
var isFriendLinkPending by field<Boolean>("mIsFriendLinkPending")
@get:JSGetter @set:JSSetter
var isReactable by field<Boolean>("mIsReactable")
}