package nostr import ( "context" "fmt" "log" "github.com/nbd-wtf/go-nostr" ) // EBooksService handles fetching top-level 30040 events type EBooksService struct { client *Client indexKind int relayURL string } // NewEBooksService creates a new e-books service func NewEBooksService(client *Client, indexKind int, relayURL string) *EBooksService { return &EBooksService{ client: client, indexKind: indexKind, relayURL: relayURL, } } // EBookInfo represents information about a top-level index event type EBookInfo struct { EventID string Title string DTag string Author string Summary string Type string CreatedAt int64 Naddr string } // FetchTopLevelIndexEvents fetches all top-level 30040 events from the specified relay // Top-level means the event is not referenced in any other 30040 event's 'a' tags func (es *EBooksService) FetchTopLevelIndexEvents(ctx context.Context) ([]EBookInfo, error) { // Connect to the specific relay relay, err := es.client.ConnectToRelay(ctx, es.relayURL) if err != nil { return nil, fmt.Errorf("failed to connect to relay %s: %w", es.relayURL, err) } defer relay.Close() // Fetch all 30040 events (limit 10k) filter := nostr.Filter{ Kinds: []int{es.indexKind}, Limit: 10000, } logFilter(filter, fmt.Sprintf("all index events (kind %d) from %s", es.indexKind, es.relayURL)) events, err := relay.QuerySync(ctx, filter) if err != nil { return nil, fmt.Errorf("failed to query events: %w", err) } log.Printf("Fetched %d index events from %s", len(events), es.relayURL) // Build a set of all referenced kind:pubkey:dtag from 'a' tags referencedSet := make(map[string]bool) for _, event := range events { for _, tag := range event.Tags { if len(tag) > 0 && tag[0] == "a" && len(tag) > 1 { // tag[1] is the kind:pubkey:dtag reference referencedSet[tag[1]] = true } } } log.Printf("Found %d referenced index events", len(referencedSet)) // Filter to only top-level events (not referenced in any other event) var topLevelEvents []*nostr.Event for _, event := range events { // Build the kind:pubkey:dtag identifier for this event var dTag string for _, tag := range event.Tags { if len(tag) > 0 && tag[0] == "d" && len(tag) > 1 { dTag = tag[1] break } } if dTag == "" { continue // Skip events without d tag } // Check if this event is referenced by any other event eventRef := fmt.Sprintf("%d:%s:%s", es.indexKind, event.PubKey, dTag) if !referencedSet[eventRef] { topLevelEvents = append(topLevelEvents, event) } } log.Printf("Found %d top-level index events", len(topLevelEvents)) // Convert to EBookInfo ebooks := make([]EBookInfo, 0, len(topLevelEvents)) for _, event := range topLevelEvents { ebook := EBookInfo{ EventID: event.ID, Author: event.PubKey, CreatedAt: int64(event.CreatedAt), } // Extract d tag for _, tag := range event.Tags { if len(tag) > 0 && tag[0] == "d" && len(tag) > 1 { ebook.DTag = tag[1] break } } // Extract title for _, tag := range event.Tags { if len(tag) > 0 && tag[0] == "title" && len(tag) > 1 { ebook.Title = tag[1] break } } if ebook.Title == "" { ebook.Title = ebook.DTag // Fallback to d tag } // Extract summary for _, tag := range event.Tags { if len(tag) > 0 && tag[0] == "summary" && len(tag) > 1 { ebook.Summary = tag[1] break } } // Extract type for _, tag := range event.Tags { if len(tag) > 0 && tag[0] == "type" && len(tag) > 1 { ebook.Type = tag[1] break } } // Build naddr - create proper bech32-encoded naddr // Extract relay hints from event tags if available var relays []string for _, tag := range event.Tags { if len(tag) > 0 && tag[0] == "relays" { relays = append(relays, tag[1:]...) } } // If no relays in tags, use the relay we fetched from if len(relays) == 0 { relays = []string{es.relayURL} } naddr, err := CreateNaddr(es.indexKind, ebook.Author, ebook.DTag, relays) if err != nil { log.Printf("Failed to create naddr for ebook %s: %v", ebook.DTag, err) // Fallback to kind:pubkey:dtag format ebook.Naddr = fmt.Sprintf("%d:%s:%s", es.indexKind, ebook.Author, ebook.DTag) } else { ebook.Naddr = naddr } ebooks = append(ebooks, ebook) } return ebooks, nil }