You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
187 lines
4.7 KiB
187 lines
4.7 KiB
package nostr |
|
|
|
import ( |
|
"context" |
|
"fmt" |
|
|
|
"gitcitadel-online/internal/logger" |
|
|
|
"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 |
|
Image 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 1000 for e-books) |
|
filter := nostr.Filter{ |
|
Kinds: []int{es.indexKind}, |
|
Limit: 1000, |
|
} |
|
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) |
|
} |
|
|
|
logger.WithFields(map[string]interface{}{ |
|
"events": len(events), |
|
"relay": es.relayURL, |
|
}).Debug("Fetched index events") |
|
|
|
// 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 |
|
} |
|
} |
|
} |
|
|
|
logger.WithField("count", len(referencedSet)).Debug("Found referenced index events") |
|
|
|
// 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) |
|
} |
|
} |
|
|
|
logger.WithField("count", len(topLevelEvents)).Info("Found top-level index events") |
|
|
|
// 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 |
|
} |
|
} |
|
|
|
// Extract image |
|
for _, tag := range event.Tags { |
|
if len(tag) > 0 && tag[0] == "image" && len(tag) > 1 { |
|
ebook.Image = 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 { |
|
logger.WithFields(map[string]interface{}{ |
|
"ebook": ebook.DTag, |
|
"error": err, |
|
}).Warn("Failed to create naddr for ebook, using fallback format") |
|
// 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 |
|
}
|
|
|