diff --git a/pkg/channels/buzz/buzz.go b/pkg/channels/buzz/buzz.go index a71feb26..4bc4386e 100644 --- a/pkg/channels/buzz/buzz.go +++ b/pkg/channels/buzz/buzz.go @@ -280,6 +280,47 @@ func (c *BuzzChannel) Send(ctx context.Context, msg bus.OutboundMessage) ([]stri return []string{evt.ID}, nil } +// kindReaction is the NIP-25 reaction event kind. +const kindReaction = 7 + +// ReactToMessage implements channels.ReactionCapable. +// It publishes a NIP-25 kind:7 reaction event with content 👀 scoped to the +// same channel as the reacted message. The undo function is a no-op because +// NIP-25 does not define a standard way to remove a reaction. +func (c *BuzzChannel) ReactToMessage(ctx context.Context, chatID, messageID string) (func(), error) { + if !c.IsRunning() { + return func() {}, channels.ErrNotRunning + } + if messageID == "" { + return func() {}, nil + } + + evt := nostr.Event{ + PubKey: c.publicKey, + CreatedAt: nostr.Now(), + Kind: kindReaction, + Tags: nostr.Tags{ + nostr.Tag{"e", messageID}, + nostr.Tag{"h", chatID}, + }, + Content: "👀", + } + if err := evt.Sign(c.secretKey); err != nil { + return func() {}, fmt.Errorf("buzz reaction sign failed: %w", err) + } + + if err := c.relay.Publish(ctx, evt); err != nil { + return func() {}, fmt.Errorf("buzz reaction publish failed: %w", err) + } + + logger.DebugCF("buzz", "Reaction sent", map[string]any{ + "channel": chatID, + "event_id": evt.ID, + "target_id": messageID, + }) + return func() {}, nil +} + // errJoin wraps err so that errors.Is(result, sentinel) holds for the sentinel // while preserving the underlying cause in the message. func errJoin(err, sentinel error) error {