feat(core): stealth mode save snap in chat
Signed-off-by: rhunk <101876869+rhunk@users.noreply.github.com>
This commit is contained in:
@@ -1355,7 +1355,8 @@
|
|||||||
},
|
},
|
||||||
"auto_mark_as_read": {
|
"auto_mark_as_read": {
|
||||||
"conversation_read": "Mark conversation as read when sending a message",
|
"conversation_read": "Mark conversation as read when sending a message",
|
||||||
"snap_reply": "Mark snaps as read when replying to them"
|
"snap_reply": "Mark snaps as read when replying to them",
|
||||||
|
"save_snap_in_chat": "Mark snaps as read when saving them in chat while in Stealth Mode"
|
||||||
},
|
},
|
||||||
"friend_mutation_notifier": {
|
"friend_mutation_notifier": {
|
||||||
"remove_friend": "Notify when someone removes you as a friend",
|
"remove_friend": "Notify when someone removes you as a friend",
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class MessagingTweaks : ConfigContainer() {
|
|||||||
val hideBitmojiPresence = boolean("hide_bitmoji_presence")
|
val hideBitmojiPresence = boolean("hide_bitmoji_presence")
|
||||||
val hideTypingNotifications = boolean("hide_typing_notifications")
|
val hideTypingNotifications = boolean("hide_typing_notifications")
|
||||||
val unlimitedSnapViewTime = boolean("unlimited_snap_view_time")
|
val unlimitedSnapViewTime = boolean("unlimited_snap_view_time")
|
||||||
val autoMarkAsRead = multiple("auto_mark_as_read", "snap_reply", "conversation_read") { requireRestart() }
|
val autoMarkAsRead = multiple("auto_mark_as_read", "snap_reply", "conversation_read", "save_snap_in_chat") { requireRestart() }
|
||||||
val markSnapAsSeenButton = boolean("mark_snap_as_seen_button") { requireRestart() }
|
val markSnapAsSeenButton = boolean("mark_snap_as_seen_button") { requireRestart() }
|
||||||
val skipWhenMarkingAsSeen = boolean("skip_when_marking_as_seen") { requireRestart() }
|
val skipWhenMarkingAsSeen = boolean("skip_when_marking_as_seen") { requireRestart() }
|
||||||
val loopMediaPlayback = boolean("loop_media_playback") { requireRestart() }
|
val loopMediaPlayback = boolean("loop_media_playback") { requireRestart() }
|
||||||
|
|||||||
@@ -9,11 +9,17 @@ import kotlinx.coroutines.delay
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.rhunk.snapenhance.common.data.ContentType
|
import me.rhunk.snapenhance.common.data.ContentType
|
||||||
import me.rhunk.snapenhance.common.data.MessageUpdate
|
import me.rhunk.snapenhance.common.data.MessageUpdate
|
||||||
|
import me.rhunk.snapenhance.core.event.events.impl.OnSnapInteractionEvent
|
||||||
import me.rhunk.snapenhance.core.event.events.impl.SendMessageWithContentEvent
|
import me.rhunk.snapenhance.core.event.events.impl.SendMessageWithContentEvent
|
||||||
import me.rhunk.snapenhance.core.features.Feature
|
import me.rhunk.snapenhance.core.features.Feature
|
||||||
import me.rhunk.snapenhance.core.features.impl.spying.StealthMode
|
import me.rhunk.snapenhance.core.features.impl.spying.StealthMode
|
||||||
import me.rhunk.snapenhance.core.ui.ViewAppearanceHelper
|
import me.rhunk.snapenhance.core.ui.ViewAppearanceHelper
|
||||||
|
import me.rhunk.snapenhance.core.util.CallbackBuilder
|
||||||
|
import me.rhunk.snapenhance.core.util.hook.HookStage
|
||||||
|
import me.rhunk.snapenhance.core.util.hook.hook
|
||||||
import me.rhunk.snapenhance.core.util.ktx.getObjectFieldOrNull
|
import me.rhunk.snapenhance.core.util.ktx.getObjectFieldOrNull
|
||||||
|
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
|
||||||
|
import me.rhunk.snapenhance.mapper.impl.CallbackMapper
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
@@ -82,6 +88,52 @@ class AutoMarkAsRead : Feature("Auto Mark As Read") {
|
|||||||
val config by context.config.messaging.autoMarkAsRead
|
val config by context.config.messaging.autoMarkAsRead
|
||||||
if (config.isEmpty()) return
|
if (config.isEmpty()) return
|
||||||
|
|
||||||
|
if (config.contains("save_snap_in_chat")) {
|
||||||
|
var lastInteractedSnapClientMessageId = -1L
|
||||||
|
|
||||||
|
context.event.subscribe(OnSnapInteractionEvent::class) { event ->
|
||||||
|
lastInteractedSnapClientMessageId = event.messageId
|
||||||
|
}
|
||||||
|
|
||||||
|
context.classCache.conversationManager.hook("updateMessage", HookStage.BEFORE) { param ->
|
||||||
|
if (param.arg<Any>(2).toString() != "SAVE") return@hook
|
||||||
|
|
||||||
|
val clientMessageId = param.arg<Long>(1)
|
||||||
|
if (lastInteractedSnapClientMessageId != clientMessageId) return@hook
|
||||||
|
|
||||||
|
val conversationId = SnapUUID(param.arg(0))
|
||||||
|
|
||||||
|
param.setResult(null)
|
||||||
|
|
||||||
|
val snapManager = context.feature(Messaging::class).snapManager ?: return@hook
|
||||||
|
val stealthMode = context.feature(StealthMode::class)
|
||||||
|
|
||||||
|
// ignore non-stealth mode conversations
|
||||||
|
if (!stealthMode.canUseRule(conversationId.toString())) return@hook
|
||||||
|
|
||||||
|
stealthMode.addSnapInteractionException(clientMessageId)
|
||||||
|
|
||||||
|
val onSnapInteraction = snapManager.javaClass.methods.firstOrNull { it.name == "onSnapInteraction" } ?: return@hook
|
||||||
|
|
||||||
|
context.mappings.useMapper(CallbackMapper::class) {
|
||||||
|
onSnapInteraction.invoke(snapManager,
|
||||||
|
findClass("com.snapchat.client.messaging.SnapInteractionType").enumConstants!!.first { it.toString() == "VIEWING_INITIATED" },
|
||||||
|
conversationId.instanceNonNull(),
|
||||||
|
clientMessageId,
|
||||||
|
CallbackBuilder(callbacks.getClass("SnapInteractionCallback")!!)
|
||||||
|
.override("onSuccess") {
|
||||||
|
param.invokeOriginal()
|
||||||
|
stealthMode.addSnapInteractionException(clientMessageId)
|
||||||
|
}
|
||||||
|
.override("onError") {
|
||||||
|
context.log.verbose("error ${it.arg<Any>(0)}")
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
context.event.subscribe(SendMessageWithContentEvent::class) { event ->
|
context.event.subscribe(SendMessageWithContentEvent::class) { event ->
|
||||||
event.addCallbackResult("onSuccess") {
|
event.addCallbackResult("onSuccess") {
|
||||||
if (canMarkConversationAsRead) {
|
if (canMarkConversationAsRead) {
|
||||||
|
|||||||
@@ -14,11 +14,7 @@ import me.rhunk.snapenhance.core.util.hook.hook
|
|||||||
import me.rhunk.snapenhance.core.util.hook.hookConstructor
|
import me.rhunk.snapenhance.core.util.hook.hookConstructor
|
||||||
import me.rhunk.snapenhance.core.util.ktx.getObjectField
|
import me.rhunk.snapenhance.core.util.ktx.getObjectField
|
||||||
import me.rhunk.snapenhance.core.util.ktx.getObjectFieldOrNull
|
import me.rhunk.snapenhance.core.util.ktx.getObjectFieldOrNull
|
||||||
import me.rhunk.snapenhance.core.wrapper.impl.ConversationManager
|
import me.rhunk.snapenhance.core.wrapper.impl.*
|
||||||
import me.rhunk.snapenhance.core.wrapper.impl.Message
|
|
||||||
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
|
|
||||||
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.CallbackMapper
|
||||||
import me.rhunk.snapenhance.mapper.impl.FriendsFeedEventDispatcherMapper
|
import me.rhunk.snapenhance.mapper.impl.FriendsFeedEventDispatcherMapper
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
@@ -27,6 +23,9 @@ import java.util.concurrent.Future
|
|||||||
class Messaging : Feature("Messaging") {
|
class Messaging : Feature("Messaging") {
|
||||||
var conversationManager: ConversationManager? = null
|
var conversationManager: ConversationManager? = null
|
||||||
private set
|
private set
|
||||||
|
var snapManager: Any? = null
|
||||||
|
private set
|
||||||
|
|
||||||
private var conversationManagerDelegate: Any? = null
|
private var conversationManagerDelegate: Any? = null
|
||||||
private var identityDelegate: Any? = null
|
private var identityDelegate: Any? = null
|
||||||
|
|
||||||
@@ -69,6 +68,10 @@ class Messaging : Feature("Messaging") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.classCache.snapManager.hookConstructor(HookStage.BEFORE) { param ->
|
||||||
|
snapManager = param.thisObject()
|
||||||
|
}
|
||||||
|
|
||||||
context.mappings.useMapper(CallbackMapper::class) {
|
context.mappings.useMapper(CallbackMapper::class) {
|
||||||
callbacks.getClass("ConversationManagerDelegate")?.apply {
|
callbacks.getClass("ConversationManagerDelegate")?.apply {
|
||||||
hookConstructor(HookStage.AFTER) { param ->
|
hookConstructor(HookStage.AFTER) { param ->
|
||||||
|
|||||||
@@ -10,15 +10,19 @@ import java.util.concurrent.CopyOnWriteArraySet
|
|||||||
|
|
||||||
class StealthMode : MessagingRuleFeature("StealthMode", MessagingRuleType.STEALTH) {
|
class StealthMode : MessagingRuleFeature("StealthMode", MessagingRuleType.STEALTH) {
|
||||||
private val displayedMessageQueue = CopyOnWriteArraySet<Long>()
|
private val displayedMessageQueue = CopyOnWriteArraySet<Long>()
|
||||||
|
private val snapInteractionQueue = CopyOnWriteArraySet<Long>()
|
||||||
|
|
||||||
fun addDisplayedMessageException(clientMessageId: Long) {
|
fun addDisplayedMessageException(clientMessageId: Long) {
|
||||||
displayedMessageQueue.add(clientMessageId)
|
displayedMessageQueue.add(clientMessageId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addSnapInteractionException(messageId: Long) {
|
||||||
|
snapInteractionQueue.add(messageId)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun init() {
|
override fun init() {
|
||||||
val isConversationInStealthMode: (SnapUUID) -> Boolean = hook@{
|
val isConversationInStealthMode: (SnapUUID) -> Boolean = { canUseRule(it.toString()) }
|
||||||
context.feature(StealthMode::class).canUseRule(it.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
arrayOf("mediaMessagesDisplayed", "displayedMessages").forEach { methodName: String ->
|
arrayOf("mediaMessagesDisplayed", "displayedMessages").forEach { methodName: String ->
|
||||||
context.classCache.conversationManager.hook(methodName, HookStage.BEFORE) { param ->
|
context.classCache.conversationManager.hook(methodName, HookStage.BEFORE) { param ->
|
||||||
@@ -30,6 +34,7 @@ class StealthMode : MessagingRuleFeature("StealthMode", MessagingRuleType.STEALT
|
|||||||
}
|
}
|
||||||
|
|
||||||
context.event.subscribe(OnSnapInteractionEvent::class) { event ->
|
context.event.subscribe(OnSnapInteractionEvent::class) { event ->
|
||||||
|
if (snapInteractionQueue.removeIf { event.messageId == it }) return@subscribe
|
||||||
if (isConversationInStealthMode(event.conversationId)) {
|
if (isConversationInStealthMode(event.conversationId)) {
|
||||||
event.canceled = true
|
event.canceled = true
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user