285 lines
6.0 KiB
Go

// 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)
}