285 lines
6.0 KiB
Go
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)
|
||
|
}
|