mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
fix(marmot): an admin removal that decrypts before its target still removes it
A kind-4891 only dropped messages already on screen and was not remembered. A catch-up returns newest first and both sit in one epoch, so the removal often decrypts first, removes nothing, and the target is then added and stays, on every restart too (the replay keeps arrival order). Unlike a kind-5 there is no LocalCache deletion index behind it. MarmotGroupList now keeps the removed ids per group and refuses them on add. Tests cover removal-before-target, the per-group scope, and removal of a shown message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
0e1bd6e810
commit
3615caabcc
@@ -309,7 +309,7 @@ class AccountMarmotActions(
|
||||
) {
|
||||
val manager = account.marmotManager ?: return
|
||||
manager.adminRemovalTargets(nostrGroupId, innerEvent).forEach { targetId ->
|
||||
account.cache.getNoteIfExists(targetId)?.let { account.marmotGroupList.removeMessage(nostrGroupId, it) }
|
||||
account.marmotGroupList.applyAdminRemoval(nostrGroupId, targetId, account.cache.getNoteIfExists(targetId))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+20
@@ -38,6 +38,12 @@ class MarmotGroupList(
|
||||
|
||||
private val noteToGroupIndex = LargeCache<HexKey, HexKey>()
|
||||
|
||||
// Message id -> group, for every message a group admin removed (kind 4891). A removal can
|
||||
// decrypt before its target: a catch-up returns newest first, and both sit in one epoch.
|
||||
// Unlike a kind-5 there is no LocalCache deletion index behind it, so without this record a
|
||||
// target arriving second would be added and stay, restarts included.
|
||||
private val adminRemovedIds = LargeCache<HexKey, HexKey>()
|
||||
|
||||
private val _groupListChanges = MutableSharedFlow<HexKey>(0, 20, BufferOverflow.DROP_OLDEST)
|
||||
val groupListChanges = _groupListChanges
|
||||
|
||||
@@ -48,6 +54,7 @@ class MarmotGroupList(
|
||||
msg: Note,
|
||||
) {
|
||||
if (!isDisplayableFeedMessage(msg)) return
|
||||
if (adminRemovedIds.get(msg.idHex) == nostrGroupId) return
|
||||
val chatroom = getOrCreateGroup(nostrGroupId)
|
||||
if (chatroom.addMessageSync(msg)) {
|
||||
noteToGroupIndex.getOrCreate(msg.idHex) { nostrGroupId }
|
||||
@@ -99,6 +106,19 @@ class MarmotGroupList(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies an admin removal of [targetId] in [nostrGroupId]: drops [target] if it is already
|
||||
* shown, and keeps the removal so the message is refused if it arrives later.
|
||||
*/
|
||||
fun applyAdminRemoval(
|
||||
nostrGroupId: HexKey,
|
||||
targetId: HexKey,
|
||||
target: Note?,
|
||||
) {
|
||||
adminRemovedIds.put(targetId, nostrGroupId)
|
||||
if (target != null) removeMessage(nostrGroupId, target)
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop a group from the in-memory list. Also clears the chatroom's own
|
||||
* message set and the note→group index: LocalCache holds notes weakly, so
|
||||
|
||||
+26
@@ -113,4 +113,30 @@ class MarmotGroupFeedVisibilityTest {
|
||||
// note skipped the kind rules above and rendered "Event is loading or can't be found".
|
||||
assertEquals(0, visibleCount(list(), Note("d".repeat(64))))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an admin removal that arrives before its target still removes it`() {
|
||||
// A catch-up returns newest first, so the kind-4891 can decrypt before the message.
|
||||
val list = list()
|
||||
val target = note(9, peer, content = "spam")
|
||||
list.applyAdminRemoval(groupId, target.idHex, null)
|
||||
assertEquals(0, visibleCount(list, target))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an admin removal in one group does not hide the same id in another`() {
|
||||
val list = list()
|
||||
val target = note(9, peer, content = "hello")
|
||||
list.applyAdminRemoval("e".repeat(64), target.idHex, null)
|
||||
assertEquals(1, visibleCount(list, target))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `an admin removal drops a message already shown`() {
|
||||
val list = list()
|
||||
val target = note(9, peer, content = "spam")
|
||||
assertEquals(1, visibleCount(list, target))
|
||||
list.applyAdminRemoval(groupId, target.idHex, target)
|
||||
assertEquals(0, list.getOrCreateGroup(groupId).messages.size)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user