// internal/nostr/poster/poster.go package poster import ( "context" "fmt" "path/filepath" "strings" "time" "github.com/nbd-wtf/go-nostr" "git.sovbit.dev/Enki/nostr-poster/internal/media/prepare" "git.sovbit.dev/Enki/nostr-poster/internal/nostr/events" "git.sovbit.dev/Enki/nostr-poster/internal/nostr/relay" "go.uber.org/zap" ) // Poster handles posting content to Nostr type Poster struct { eventMgr *events.EventManager relayMgr *relay.Manager mediaPrep *prepare.Manager logger *zap.Logger } // NewPoster creates a new content poster func NewPoster( eventMgr *events.EventManager, relayMgr *relay.Manager, mediaPrep *prepare.Manager, logger *zap.Logger, ) *Poster { if logger == nil { // Create a default logger if none is provided var err error logger, err = zap.NewProduction() if err != nil { // If we can't create a logger, use a no-op logger logger = zap.NewNop() } } return &Poster{ eventMgr: eventMgr, relayMgr: relayMgr, mediaPrep: mediaPrep, logger: logger, } } // PostContent posts content to Nostr func (p *Poster) PostContent( pubkey string, contentPath string, contentType string, mediaURL string, mediaHash string, caption string, hashtags []string, ) error { // Determine the type of content isImage := strings.HasPrefix(contentType, "image/") isVideo := strings.HasPrefix(contentType, "video/") // Create alt text if not provided (initialize it here) altText := caption if altText == "" { // Use the filename without extension as a fallback altText = strings.TrimSuffix(filepath.Base(contentPath), filepath.Ext(contentPath)) } // Extract media dimensions if it's an image if isImage { dims, err := p.mediaPrep.GetMediaDimensions(contentPath) if err != nil { p.logger.Warn("Failed to get image dimensions, continuing anyway", zap.String("file", contentPath), zap.Error(err)) } else { // Log the dimensions p.logger.Debug("Image dimensions", zap.Int("width", dims.Width), zap.Int("height", dims.Height)) // Add dimensions to alt text if available if altText != "" { altText = fmt.Sprintf("%s [%dx%d]", altText, dims.Width, dims.Height) } } } // Create a context with timeout ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() var event *nostr.Event var err error // Determine the appropriate event kind and create the event if isImage { // Use kind 1 (text note) for images // Create hashtag string for post content var hashtagStr string if len(hashtags) > 0 { hashtagArr := make([]string, len(hashtags)) for i, tag := range hashtags { hashtagArr[i] = "#" + tag } hashtagStr = "\n\n" + strings.Join(hashtagArr, " ") } content := caption + hashtagStr event, err = p.eventMgr.CreateAndSignMediaEvent( pubkey, content, mediaURL, contentType, mediaHash, altText, hashtags, ) } else if isVideo { // For videos, determine if it's a short video isShortVideo := false // Just a placeholder, would need logic to determine // Create the video event event, err = p.eventMgr.CreateAndSignVideoEvent( pubkey, caption, // Title caption, // Description mediaURL, contentType, mediaHash, "", // Preview image URL 0, // Duration altText, hashtags, isShortVideo, ) } else { // For other types, use a regular text note with attachment event, err = p.eventMgr.CreateAndSignMediaEvent( pubkey, caption, mediaURL, contentType, mediaHash, altText, hashtags, ) } if err != nil { return fmt.Errorf("failed to create event: %w", err) } // Publish the event relays, err := p.relayMgr.PublishEvent(ctx, event) if err != nil { return fmt.Errorf("failed to publish event: %w", err) } p.logger.Info("Published content to relays", zap.String("event_id", event.ID), zap.Strings("relays", relays)) return nil } // CreatePostContentFunc creates a function for posting content func CreatePostContentFunc( eventMgr *events.EventManager, relayMgr *relay.Manager, mediaPrep *prepare.Manager, logger *zap.Logger, ) func(string, string, string, string, string, string, []string) error { poster := NewPoster(eventMgr, relayMgr, mediaPrep, logger) return func(pubkey, contentPath, contentType, mediaURL, mediaHash, caption string, hashtags []string) error { return poster.PostContent(pubkey, contentPath, contentType, mediaURL, mediaHash, caption, hashtags) } }