feat(cli): add amy marmot message react / delete verbs
Adds two inner-event verbs on top of the existing kind:9 text send path: amy marmot message react GID TARGET_EVENT_ID EMOJI amy marmot message delete GID TARGET_EVENT_ID... Both build an unsigned rumor per MIP-03 (RumorAssembler) and wrap it in the usual kind:445 outer via MarmotOutboundProcessor — the same shape as text messages, just a different inner kind. The target event is looked up in the account's decrypted inner-message log (persisted by the inbound pipeline) so the NIP-25 / NIP-09 templates can populate the target's pubkey and kind tags without making the caller re-derive them. Test 09 previously noted that react round-trip verification was blocked on a missing CLI verb. Expanded it to react via amy and confirm B sees the kind:7 surface in its messages stream. https://claude.ai/code/session_01Unm6uLHGLj9UcBY7hWfJVW
This commit is contained in:
+55
@@ -191,6 +191,61 @@ class MarmotManager(
|
||||
return TextMessageBundle(outbound = outbound, innerEvent = innerEvent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a kind:7 reaction inner event targeting another inner event in
|
||||
* the same group. Like [buildTextMessage], the inner event is an
|
||||
* unsigned rumor (MIP-03). The reaction uses NIP-25 conventions: content
|
||||
* is the emoji / `+` / `-`; e-tag + p-tag + k-tag reference the target.
|
||||
*/
|
||||
suspend fun buildReactionMessage(
|
||||
nostrGroupId: HexKey,
|
||||
targetEvent: Event,
|
||||
reaction: String,
|
||||
persistOwn: Boolean = true,
|
||||
): TextMessageBundle {
|
||||
val template =
|
||||
com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||
.build(
|
||||
reaction,
|
||||
com.vitorpamplona.quartz.nip01Core.hints
|
||||
.EventHintBundle(targetEvent),
|
||||
)
|
||||
val innerEvent =
|
||||
com.vitorpamplona.quartz.nip59Giftwrap.rumors.RumorAssembler
|
||||
.assembleRumor<com.vitorpamplona.quartz.nip25Reactions.ReactionEvent>(
|
||||
signer.pubKey,
|
||||
template,
|
||||
)
|
||||
val outbound = buildGroupMessage(nostrGroupId, innerEvent)
|
||||
if (persistOwn) persistDecryptedMessage(nostrGroupId, innerEvent.toJson())
|
||||
return TextMessageBundle(outbound = outbound, innerEvent = innerEvent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a kind:5 deletion inner event targeting one or more prior inner
|
||||
* events in the same group. Unsigned rumor (MIP-03); e-tag + k-tag for
|
||||
* each target per NIP-09.
|
||||
*/
|
||||
suspend fun buildDeletionMessage(
|
||||
nostrGroupId: HexKey,
|
||||
targetEvents: List<Event>,
|
||||
persistOwn: Boolean = true,
|
||||
): TextMessageBundle {
|
||||
require(targetEvents.isNotEmpty()) { "buildDeletionMessage: targetEvents must not be empty" }
|
||||
val template =
|
||||
com.vitorpamplona.quartz.nip09Deletions.DeletionEvent
|
||||
.build(targetEvents)
|
||||
val innerEvent =
|
||||
com.vitorpamplona.quartz.nip59Giftwrap.rumors.RumorAssembler
|
||||
.assembleRumor<com.vitorpamplona.quartz.nip09Deletions.DeletionEvent>(
|
||||
signer.pubKey,
|
||||
template,
|
||||
)
|
||||
val outbound = buildGroupMessage(nostrGroupId, innerEvent)
|
||||
if (persistOwn) persistDecryptedMessage(nostrGroupId, innerEvent.toJson())
|
||||
return TextMessageBundle(outbound = outbound, innerEvent = innerEvent)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a member to a group by consuming their published [KeyPackageEvent].
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user