fix: Prevent snap replay & fix remove audio note duration & transcription(thanks to RSR)

This commit is contained in:
particle-box
2026-01-25 14:28:03 +05:30
parent 327efafbe5
commit 7fe49f2fe4
2 changed files with 63 additions and 48 deletions

View File

@@ -2,7 +2,6 @@ package me.eternal.purrfectsnap.core.features.impl.messaging
import me.eternal.purrfectsnap.common.data.NotificationType
import me.eternal.purrfectsnap.common.util.protobuf.ProtoEditor
import me.eternal.purrfectsnap.common.util.protobuf.ProtoReader
import me.eternal.purrfectsnap.core.event.events.impl.NativeUnaryCallEvent
import me.eternal.purrfectsnap.core.event.events.impl.UnaryCallEvent
import me.eternal.purrfectsnap.core.event.events.impl.SendMessageWithContentEvent
@@ -27,42 +26,12 @@ class PreventMessageSending : Feature("Prevent message sending") {
}.toByteArray()
}
fun handleCreateContentMessage(uri: String, buffer: ByteArray): Boolean {
if (uri != "/messagingcoreservice.MessagingCoreService/CreateContentMessage") return false
val reader = ProtoReader(buffer)
// check for missed audio/video call in MessageContent (field 4)
val contentReader = reader.followPath(4) ?: return false
// try both possible field IDs based on SnapEnums and ContentType.java
val contentType = contentReader.getVarInt(2)
val isMissedAudio = contentType == 13L || contentType == 18L
val isMissedVideo = contentType == 12L || contentType == 17L
if (isMissedAudio && preventMessageSending.contains("abandon_audio")) return true
if (isMissedVideo && preventMessageSending.contains("abandon_video")) return true
return false
context.event.subscribe(NativeUnaryCallEvent::class, { preventMessageSending.contains("snap_replay") }) { event ->
handleUpdateContentMessage(event.uri, event.buffer)?.let { event.buffer = it }
}
context.event.subscribe(NativeUnaryCallEvent::class) { event ->
val uri = event.uri
if (!uri.startsWith("/messagingcoreservice.MessagingCoreService/")) return@subscribe
if (handleCreateContentMessage(uri, event.buffer)) {
event.canceled = true
}
handleUpdateContentMessage(uri, event.buffer)?.let { event.buffer = it }
}
context.event.subscribe(UnaryCallEvent::class) { event ->
val uri = event.uri
if (!uri.startsWith("/messagingcoreservice.MessagingCoreService/")) return@subscribe
if (handleCreateContentMessage(uri, event.buffer)) {
event.canceled = true
}
handleUpdateContentMessage(uri, event.buffer)?.let { event.buffer = it }
context.event.subscribe(UnaryCallEvent::class, { preventMessageSending.contains("snap_replay") }) { event ->
handleUpdateContentMessage(event.uri, event.buffer)?.let { event.buffer = it }
}
context.classCache.conversationManager.hook("updateMessage", HookStage.BEFORE) { param ->
@@ -75,7 +44,7 @@ class PreventMessageSending : Feature("Prevent message sending") {
param.setResult(null)
}
if ((messageUpdate == "REPLAY" || messageUpdate == "replay") && preventMessageSending.contains("snap_replay")) {
if (messageUpdate == "REPLAY" && preventMessageSending.contains("snap_replay")) {
param.setResult(null)
}
}
@@ -85,6 +54,7 @@ class PreventMessageSending : Feature("Prevent message sending") {
val associatedType = NotificationType.fromContentType(contentType ?: return@subscribe) ?: return@subscribe
if (preventMessageSending.contains(associatedType.key)) {
context.log.verbose("Preventing message sending for $associatedType")
event.canceled = true
}
}

View File

@@ -159,6 +159,63 @@ class SendOverride : Feature("Send Override") {
if (configOverrideType == null && stripMediaMetadata.isEmpty()) return
context.event.subscribe(MediaUploadEvent::class) { event ->
// Handle audio notes separately since they don't have path 11, 5
if (stripMediaMetadata.isNotEmpty() &&
(event.localMessageContent.contentType == ContentType.NOTE ||
stripMediaMetadata.contains("remove_audio_note_duration") ||
stripMediaMetadata.contains("remove_audio_note_transcript_capability"))) {
event.onMediaUploaded { result ->
if (result.messageContent.contentType == ContentType.NOTE) {
val contentReader = ProtoReader(result.messageContent.content!!)
result.messageContent.content = ProtoEditor(result.messageContent.content!!).apply {
// Check which path structure exists - try both to be safe
val hasFullPath = contentReader.followPath(4, 4, 6, 1, 1) != null
val hasDirectPath = contentReader.followPath(6, 1, 1) != null
if (stripMediaMetadata.contains("remove_audio_note_duration")) {
// Audio note duration is at field 13 (confirmed from MessageDecoder line 94 and MessageSender line 27)
if (hasFullPath) {
edit(4, 4, 6, 1, 1) {
remove(13)
}
}
if (hasDirectPath || !hasFullPath) {
edit(6, 1, 1) {
remove(13)
}
}
}
if (stripMediaMetadata.contains("remove_audio_note_transcript_capability")) {
// Remove locale string (field 3) which may be used for transcript capability
// Also try other potential fields
if (hasFullPath) {
edit(4, 4, 6, 1, 1) {
remove(2) // potential transcript-related field
remove(3) // locale string (may affect transcript capability)
remove(4) // potential transcript-related field
}
edit(4, 4, 6, 1) {
remove(2) // possible transcript capability flag at parent level
remove(3) // locale at parent level
}
}
if (hasDirectPath || !hasFullPath) {
edit(6, 1, 1) {
remove(2) // potential transcript-related field
remove(3) // locale string (may affect transcript capability)
remove(4) // potential transcript-related field
}
edit(6, 1) {
remove(2) // possible transcript capability flag at parent level
remove(3) // locale at parent level (field 3 at 6,1 is locale per MessageSender)
}
}
}
}.toByteArray()
}
}
}
ProtoReader(event.localMessageContent.content!!).followPath(11, 5)?.let { snapDocPlayback ->
event.onMediaUploaded { result ->
result.messageContent.content = ProtoEditor(result.messageContent.content!!).apply {
@@ -213,18 +270,6 @@ class SendOverride : Feature("Send Override") {
}
}
}
ContentType.NOTE -> {
if (stripMediaMetadata.contains("remove_audio_note_duration")) {
edit(6, 1, 1) {
remove(13)
}
}
if (stripMediaMetadata.contains("remove_audio_note_transcript_capability")) {
edit(6, 1) {
remove(3)
}
}
}
else -> {}
}
}