Merge pull request #139 from schrodingerspet/fix/message-logger-import-persistence

fix(ui): enforce Hide Friend Feed Entry on main friend feed
This commit is contained in:
ΞTΞRNAL
2026-04-20 00:04:09 +05:30
committed by GitHub

View File

@@ -1,17 +1,26 @@
package me.eternal.purrfectsnap.core.features.impl.ui
import android.view.View
import me.eternal.purrfectsnap.common.data.MessagingRuleType
import me.eternal.purrfectsnap.common.data.RuleState
import me.eternal.purrfectsnap.core.event.events.impl.BindViewEvent
import me.eternal.purrfectsnap.core.features.MessagingRuleFeature
import me.eternal.purrfectsnap.core.ui.hideViewCompletely
import me.eternal.purrfectsnap.core.util.dataBuilder
import me.eternal.purrfectsnap.core.util.hook.HookStage
import me.eternal.purrfectsnap.core.util.hook.hook
import me.eternal.purrfectsnap.core.util.ktx.getObjectField
import me.eternal.purrfectsnap.core.wrapper.impl.SnapUUID
import me.eternal.purrfectsnap.mapper.impl.CallbackMapper
import java.util.ArrayList
class HideFriendFeedEntry : MessagingRuleFeature("HideFriendFeedEntry", ruleType = MessagingRuleType.HIDE_FRIEND_FEED) {
@Volatile
private var cachedRuleIds: Set<String> = emptySet()
@Volatile
private var cachedRuleIdsAt = 0L
private fun createDeletedFeedEntry(conversationIdInstance: Any) = findClass("com.snapchat.client.messaging.DeletedFeedEntry").dataBuilder {
from("mFeedEntryIdentifier") {
set("mConversationId", conversationIdInstance)
@@ -19,10 +28,47 @@ class HideFriendFeedEntry : MessagingRuleFeature("HideFriendFeedEntry", ruleType
set("mReason", "CLEAR_CONVERSATION")
}
private fun filterFriendFeed(entries: ArrayList<Any>, deletedEntries: ArrayList<Any>? = null) {
private fun getRuleIdsSnapshot(): Set<String> {
val now = System.currentTimeMillis()
if (now - cachedRuleIdsAt <= 1_000L) return cachedRuleIds
return context.bridgeClient.getRuleIds(ruleType).toSet().also {
cachedRuleIds = it
cachedRuleIdsAt = now
}
}
private fun resolveRuleTargets(conversationId: String): Set<String> {
val targets = linkedSetOf(conversationId)
context.database.getDMOtherParticipant(conversationId)?.let { targets.add(it) }
context.database.getFeedEntryByConversationId(conversationId)?.let { entry ->
entry.friendUserId?.let { targets.add(it) }
entry.participants?.forEach { targets.add(it) }
}
return targets
}
private fun shouldHideConversation(
conversationId: String,
ruleIds: Set<String>,
ruleState: RuleState?
): Boolean {
if (ruleState == null) return false
val isExplicitRuleMatch = resolveRuleTargets(conversationId).any { it in ruleIds }
return if (ruleState == RuleState.BLACKLIST) !isExplicitRuleMatch else isExplicitRuleMatch
}
private fun filterFriendFeed(
entries: ArrayList<Any>,
ruleIds: Set<String>,
ruleState: RuleState?,
deletedEntries: ArrayList<Any>? = null
) {
if (ruleState == null || entries.isEmpty()) return
entries.removeIf { feedEntry ->
val conversationIdInstance = feedEntry.getObjectField("mConversationId") ?: return@removeIf false
if (canUseRule(SnapUUID(conversationIdInstance).toString())) {
val conversationId = SnapUUID(conversationIdInstance).toString()
if (shouldHideConversation(conversationId, ruleIds, ruleState)) {
deletedEntries?.add(createDeletedFeedEntry(conversationIdInstance)!!)
true
} else {
@@ -31,6 +77,12 @@ class HideFriendFeedEntry : MessagingRuleFeature("HideFriendFeedEntry", ruleType
}
}
private fun hideBoundChatFeedRow(view: View) {
view.hideViewCompletely()
(view.parent as? View)?.hideViewCompletely()
(view.parent?.parent as? View)?.hideViewCompletely()
}
private fun hookCallbackMethod(
hookedCallbacks: MutableSet<String>,
callbackClassName: String,
@@ -51,6 +103,14 @@ class HideFriendFeedEntry : MessagingRuleFeature("HideFriendFeedEntry", ruleType
override fun init() {
if (!context.config.userInterface.hideFriendFeedEntry.get()) return
context.event.subscribe(BindViewEvent::class) { event ->
event.friendFeedItem { conversationId ->
if (shouldHideConversation(conversationId, getRuleIdsSnapshot(), getRuleState())) {
hideBoundChatFeedRow(event.view)
}
}
}
context.mappings.useMapper(CallbackMapper::class) {
classLoader = context.androidContext.classLoader
val hasFetchAndSyncCallback = callbacks.getAsMap()?.entries?.any {
@@ -69,13 +129,13 @@ class HideFriendFeedEntry : MessagingRuleFeature("HideFriendFeedEntry", ruleType
when {
callbackName.startsWith("FetchAndSyncFeed") && callbackName.endsWith("Callback") -> {
hookCallbackMethod(hookedCallbacks, callbackClassName ?: return@forEach, "onFetchAndSyncFeedComplete") { param ->
val deletedConversations: ArrayList<Any> = param.arg(2)
filterFriendFeed(param.arg(0), deletedConversations)
val entries = param.argNullable<ArrayList<Any>>(0) ?: return@hookCallbackMethod
val deletedConversations = param.argNullable<ArrayList<Any>>(2)
val ruleIds = getRuleIdsSnapshot()
val ruleState = getRuleState()
filterFriendFeed(entries, ruleIds, ruleState, deletedConversations)
if (deletedConversations.any {
val uuid = SnapUUID(it.getObjectField("mFeedEntryIdentifier")?.getObjectField("mConversationId")).toString()
context.database.getFeedEntryByConversationId(uuid) != null
}) {
if (deletedConversations?.isNotEmpty() == true) {
param.setArg(4, true)
}
}
@@ -83,34 +143,40 @@ class HideFriendFeedEntry : MessagingRuleFeature("HideFriendFeedEntry", ruleType
callbackName.contains("SyncFeed") && callbackName.endsWith("Callback") -> {
hookCallbackMethod(hookedCallbacks, callbackClassName ?: return@forEach, "onSyncFeedComplete") { param ->
filterFriendFeed(param.arg(0), param.argNullable(2))
val entries = param.argNullable<ArrayList<Any>>(0) ?: return@hookCallbackMethod
filterFriendFeed(entries, getRuleIdsSnapshot(), getRuleState(), param.argNullable(2))
}
}
callbackName == "FetchFeedCallback" || callbackName.contains("FetchFeedCallback") -> {
hookCallbackMethod(hookedCallbacks, callbackClassName ?: return@forEach, "onFetchFeedComplete") { param ->
filterFriendFeed(param.arg(0))
val entries = param.argNullable<ArrayList<Any>>(0) ?: return@hookCallbackMethod
filterFriendFeed(entries, getRuleIdsSnapshot(), getRuleState())
}
}
callbackName == "FetchFeedEntriesCallback" || callbackName.contains("FetchFeedEntriesCallback") -> {
hookCallbackMethod(hookedCallbacks, callbackClassName ?: return@forEach, "onFetchFeedEntriesComplete") { param ->
filterFriendFeed(param.arg(0))
val entries = param.argNullable<ArrayList<Any>>(0) ?: return@hookCallbackMethod
filterFriendFeed(entries, getRuleIdsSnapshot(), getRuleState())
}
}
callbackName == "QueryFeedCallback" || callbackName.contains("QueryFeedCallback") -> {
hookCallbackMethod(hookedCallbacks, callbackClassName ?: return@forEach, "onQueryFeedComplete") { param ->
filterFriendFeed(param.arg(0))
val entries = param.argNullable<ArrayList<Any>>(0) ?: return@hookCallbackMethod
filterFriendFeed(entries, getRuleIdsSnapshot(), getRuleState())
}
}
callbackName == "FeedManagerDelegate" -> {
hookCallbackMethod(hookedCallbacks, callbackClassName ?: return@forEach, "onFeedEntriesUpdated") { param ->
filterFriendFeed(param.arg(0))
val entries = param.argNullable<ArrayList<Any>>(0) ?: return@hookCallbackMethod
filterFriendFeed(entries, getRuleIdsSnapshot(), getRuleState())
}
hookCallbackMethod(hookedCallbacks, callbackClassName ?: return@forEach, "onInternalSyncFeed") { param ->
filterFriendFeed(param.arg(0))
val entries = param.argNullable<ArrayList<Any>>(0) ?: return@hookCallbackMethod
filterFriendFeed(entries, getRuleIdsSnapshot(), getRuleState())
}
}
}