// internal/nostr/events/events.go package events import ( "encoding/json" "fmt" "time" "github.com/nbd-wtf/go-nostr" "git.sovbit.dev/Enki/nostr-poster/internal/models" ) // EventManager handles creation and signing of Nostr events type EventManager struct { // The private key getter function returns a private key for the given pubkey getPrivateKey func(pubkey string) (string, error) } // NewEventManager creates a new EventManager func NewEventManager(getPrivateKey func(pubkey string) (string, error)) *EventManager { return &EventManager{ getPrivateKey: getPrivateKey, } } // CreateAndSignMetadataEvent creates and signs a kind 0 metadata event for the bot func (em *EventManager) CreateAndSignMetadataEvent(bot *models.Bot) (*nostr.Event, error) { // Create the metadata structure metadata := map[string]interface{}{ "name": bot.Name, "display_name": bot.DisplayName, "about": bot.Bio, } // Add optional fields if they exist if bot.Nip05 != "" { metadata["nip05"] = bot.Nip05 } if bot.ProfilePicture != "" { metadata["picture"] = bot.ProfilePicture } if bot.Banner != "" { metadata["banner"] = bot.Banner } // Convert metadata to JSON content, err := json.Marshal(metadata) if err != nil { return nil, fmt.Errorf("failed to marshal metadata: %w", err) } // Create the event ev := nostr.Event{ Kind: 0, CreatedAt: nostr.Timestamp(time.Now().Unix()), Tags: []nostr.Tag{}, Content: string(content), } // Sign the event if err := em.SignEvent(&ev, bot.Pubkey); err != nil { return nil, err } return &ev, nil } // CreateAndSignTextNoteEvent creates and signs a kind 1 text note event func (em *EventManager) CreateAndSignTextNoteEvent( pubkey string, content string, tags []nostr.Tag, ) (*nostr.Event, error) { // Create the event ev := nostr.Event{ Kind: 1, CreatedAt: nostr.Timestamp(time.Now().Unix()), Tags: tags, Content: content, } // Sign the event if err := em.SignEvent(&ev, pubkey); err != nil { return nil, err } return &ev, nil } // CreateAndSignMediaEvent creates and signs a kind 1 event with media attachment func (em *EventManager) CreateAndSignMediaEvent( pubkey string, content string, mediaURL string, mediaType string, mediaHash string, altText string, hashtags []string, ) (*nostr.Event, error) { // Create the imeta tag for the media imeta := []string{"imeta", "url " + mediaURL, "m " + mediaType} // Add hash if available if mediaHash != "" { imeta = append(imeta, "x "+mediaHash) } // Add alt text if available if altText != "" { imeta = append(imeta, "alt "+altText) } // Create tags tags := []nostr.Tag{imeta} // Add hashtags for _, tag := range hashtags { tags = append(tags, nostr.Tag{"t", tag}) } // Create the event ev := nostr.Event{ Kind: 1, CreatedAt: nostr.Timestamp(time.Now().Unix()), Tags: tags, Content: content, } // Sign the event if err := em.SignEvent(&ev, pubkey); err != nil { return nil, err } return &ev, nil } // CreateAndSignPictureEvent creates and signs a kind 20 picture event (NIP-68) func (em *EventManager) CreateAndSignPictureEvent( pubkey string, title string, description string, mediaURL string, mediaType string, mediaHash string, altText string, hashtags []string, ) (*nostr.Event, error) { // Create the imeta tag for the media imeta := []string{"imeta", "url " + mediaURL, "m " + mediaType} // Add hash if available if mediaHash != "" { imeta = append(imeta, "x "+mediaHash) } // Add alt text if available if altText != "" { imeta = append(imeta, "alt "+altText) } // Create tags tags := []nostr.Tag{ {"title", title}, imeta, } // Add media type tag tags = append(tags, nostr.Tag{"m", mediaType}) // Add media hash tag if mediaHash != "" { tags = append(tags, nostr.Tag{"x", mediaHash}) } // Add hashtags for _, tag := range hashtags { tags = append(tags, nostr.Tag{"t", tag}) } // Create the event ev := nostr.Event{ Kind: 20, // NIP-68 Picture Event CreatedAt: nostr.Timestamp(time.Now().Unix()), Tags: tags, Content: description, } // Sign the event if err := em.SignEvent(&ev, pubkey); err != nil { return nil, err } return &ev, nil } // CreateAndSignVideoEvent creates and signs a kind 21/22 video event (NIP-71) func (em *EventManager) CreateAndSignVideoEvent( pubkey string, title string, description string, mediaURL string, mediaType string, mediaHash string, previewImageURL string, duration int, altText string, hashtags []string, isShortVideo bool, ) (*nostr.Event, error) { // Create the imeta tag for the media imeta := []string{ "imeta", "url " + mediaURL, "m " + mediaType, } // Add hash if available if mediaHash != "" { imeta = append(imeta, "x "+mediaHash) } // Add preview image if available if previewImageURL != "" { imeta = append(imeta, "image "+previewImageURL) } // Create tags tags := []nostr.Tag{ {"title", title}, imeta, } // Add duration if available if duration > 0 { tags = append(tags, nostr.Tag{"duration", fmt.Sprintf("%d", duration)}) } // Add alt text if available if altText != "" { tags = append(tags, nostr.Tag{"alt", altText}) } // Add hashtags for _, tag := range hashtags { tags = append(tags, nostr.Tag{"t", tag}) } // Choose the right kind based on video type kind := 21 // Regular video if isShortVideo { kind = 22 // Short video } // Create the event ev := nostr.Event{ Kind: kind, CreatedAt: nostr.Timestamp(time.Now().Unix()), Tags: tags, Content: description, } // Sign the event if err := em.SignEvent(&ev, pubkey); err != nil { return nil, err } return &ev, nil } // SignEvent signs a Nostr event using the private key for the given pubkey func (em *EventManager) SignEvent(ev *nostr.Event, pubkey string) error { // Set the public key ev.PubKey = pubkey // Get the private key privkey, err := em.getPrivateKey(pubkey) if err != nil { return fmt.Errorf("failed to get private key: %w", err) } // Sign the event return ev.Sign(privkey) }