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.
296 lines
9.6 KiB
296 lines
9.6 KiB
package nostr |
|
|
|
import ( |
|
"context" |
|
"fmt" |
|
"log" |
|
|
|
"github.com/nbd-wtf/go-nostr" |
|
) |
|
|
|
// logFilter logs the exact filter being used for debugging |
|
func logFilter(filter nostr.Filter, context string) { |
|
log.Printf("FILTER [%s]: Kinds=%v, Authors=%v, IDs=%v, Tags=%v, Limit=%d", |
|
context, filter.Kinds, filter.Authors, filter.IDs, filter.Tags, filter.Limit) |
|
} |
|
|
|
// WikiService handles wiki-specific operations |
|
type WikiService struct { |
|
client *Client |
|
articleKinds []int // Allowed article kinds (from config) |
|
wikiKind int // Primary wiki kind constant (first from wiki_kinds config) |
|
blogKind int // Primary blog kind constant (first from blog_kinds config) |
|
additionalFallback string // Additional fallback relay URL (from config) |
|
indexKind int // Index event kind (from config) |
|
} |
|
|
|
// NewWikiService creates a new wiki service |
|
func NewWikiService(client *Client, articleKinds []int, wikiKind int, additionalFallback string, indexKind int, blogKind int) *WikiService { |
|
return &WikiService{ |
|
client: client, |
|
articleKinds: articleKinds, |
|
wikiKind: wikiKind, |
|
blogKind: blogKind, |
|
additionalFallback: additionalFallback, |
|
indexKind: indexKind, |
|
} |
|
} |
|
|
|
// FetchWikiIndex fetches a wiki index by naddr |
|
func (ws *WikiService) FetchWikiIndex(ctx context.Context, naddrStr string) (*IndexEvent, error) { |
|
// Parse naddr |
|
naddr, err := ParseNaddr(naddrStr) |
|
if err != nil { |
|
return nil, fmt.Errorf("failed to parse naddr: %w", err) |
|
} |
|
|
|
// Create filter for the index event |
|
filter := naddr.ToFilter() |
|
logFilter(filter, fmt.Sprintf("wiki index (kind %d)", ws.indexKind)) |
|
|
|
// Fetch the event |
|
event, err := ws.client.FetchEvent(ctx, filter) |
|
if err != nil { |
|
return nil, fmt.Errorf("failed to fetch index event: %w", err) |
|
} |
|
|
|
// Parse the index event |
|
index, err := ParseIndexEvent(event, ws.indexKind) |
|
if err != nil { |
|
return nil, fmt.Errorf("failed to parse index event: %w", err) |
|
} |
|
|
|
return index, nil |
|
} |
|
|
|
// FetchWikiEvents fetches all wiki events referenced in an index |
|
func (ws *WikiService) FetchWikiEvents(ctx context.Context, index *IndexEvent) ([]*WikiEvent, error) { |
|
var wikiEvents []*WikiEvent |
|
|
|
for _, item := range index.Items { |
|
if item.Kind != ws.wikiKind { |
|
continue // Skip non-wiki items |
|
} |
|
|
|
// Create filter for this wiki event |
|
filter := nostr.Filter{ |
|
Kinds: []int{ws.wikiKind}, |
|
Authors: []string{item.Pubkey}, |
|
Tags: map[string][]string{ |
|
"d": {item.DTag}, |
|
}, |
|
} |
|
|
|
// If event ID is specified, use it for more reliable fetching |
|
if item.EventID != "" { |
|
filter.IDs = []string{item.EventID} |
|
log.Printf("Using event ID %s for %s", item.EventID[:16]+"...", item.DTag) |
|
} |
|
|
|
logFilter(filter, fmt.Sprintf("wiki event %s", item.DTag)) |
|
|
|
// Use relay hint if available, otherwise use default client relays |
|
var event *nostr.Event |
|
var err error |
|
if item.RelayHint != "" { |
|
// Connect to the relay hint |
|
log.Printf("Trying relay hint %s for %s", item.RelayHint, item.DTag) |
|
relay, relayErr := ws.client.ConnectToRelay(ctx, item.RelayHint) |
|
if relayErr == nil { |
|
events, queryErr := relay.QuerySync(ctx, filter) |
|
relay.Close() |
|
if queryErr == nil { |
|
if len(events) > 0 { |
|
event = events[0] |
|
log.Printf("Successfully fetched %s from relay hint %s", item.DTag, item.RelayHint) |
|
} else { |
|
log.Printf("Relay hint %s returned 0 events for %s (event ID: %v)", item.RelayHint, item.DTag, item.EventID != "") |
|
} |
|
} else { |
|
log.Printf("Error querying relay hint %s for %s: %v", item.RelayHint, item.DTag, queryErr) |
|
} |
|
} else { |
|
log.Printf("Error connecting to relay hint %s for %s: %v", item.RelayHint, item.DTag, relayErr) |
|
} |
|
} |
|
|
|
// Fallback to default client relays if relay hint failed or wasn't provided |
|
// Client now queries all three relays automatically |
|
if event == nil { |
|
log.Printf("Querying all relays for %s", item.DTag) |
|
event, err = ws.client.FetchEvent(ctx, filter) |
|
if err != nil { |
|
// If still not found and event ID was specified, try without event ID to get latest replaceable event |
|
if item.EventID != "" { |
|
log.Printf("Trying without event ID for %s to get latest replaceable event", item.DTag) |
|
filterWithoutID := nostr.Filter{ |
|
Kinds: []int{ws.wikiKind}, |
|
Authors: []string{item.Pubkey}, |
|
Tags: map[string][]string{ |
|
"d": {item.DTag}, |
|
}, |
|
} |
|
logFilter(filterWithoutID, fmt.Sprintf("wiki event %s (without event ID)", item.DTag)) |
|
event, err = ws.client.FetchEvent(ctx, filterWithoutID) |
|
if err == nil { |
|
log.Printf("Successfully fetched %s (latest) from all relays", item.DTag) |
|
} |
|
} |
|
} else { |
|
log.Printf("Successfully fetched %s from all relays", item.DTag) |
|
} |
|
|
|
if event == nil { |
|
// Log error but continue with other events |
|
log.Printf("Error fetching wiki event for %s (pubkey: %s, event ID: %v): %v", item.DTag, item.Pubkey, item.EventID != "", err) |
|
continue |
|
} |
|
} |
|
|
|
// Parse the wiki event |
|
wiki, err := ParseWikiEvent(event, ws.wikiKind) |
|
if err != nil { |
|
log.Printf("Error parsing wiki event for %s: %v", item.DTag, err) |
|
continue |
|
} |
|
|
|
wikiEvents = append(wikiEvents, wiki) |
|
} |
|
|
|
if len(wikiEvents) == 0 && len(index.Items) > 0 { |
|
log.Printf("Warning: No wiki events could be fetched from %d index items", len(index.Items)) |
|
// Return empty slice instead of error to allow landing page generation |
|
} |
|
|
|
return wikiEvents, nil |
|
} |
|
|
|
// GetBlogKind returns the blog kind configured in this service |
|
func (ws *WikiService) GetBlogKind() int { |
|
return ws.blogKind |
|
} |
|
|
|
// FetchIndexEvents fetches all events of a specific kind referenced in an index |
|
// Only supports article kinds configured in the service |
|
func (ws *WikiService) FetchIndexEvents(ctx context.Context, index *IndexEvent, targetKind int) ([]*nostr.Event, error) { |
|
// Check if the target kind is in the allowed article kinds |
|
allowed := false |
|
for _, kind := range ws.articleKinds { |
|
if kind == targetKind { |
|
allowed = true |
|
break |
|
} |
|
} |
|
if !allowed { |
|
return nil, fmt.Errorf("unsupported event kind: %d (only %v are supported)", targetKind, ws.articleKinds) |
|
} |
|
|
|
var events []*nostr.Event |
|
|
|
for _, item := range index.Items { |
|
// Only process items of the target kind |
|
if item.Kind != targetKind { |
|
continue // Skip items that don't match the target kind |
|
} |
|
|
|
// Create filter for this event |
|
filter := nostr.Filter{ |
|
Kinds: []int{targetKind}, |
|
Authors: []string{item.Pubkey}, |
|
Tags: map[string][]string{ |
|
"d": {item.DTag}, |
|
}, |
|
} |
|
|
|
// If event ID is specified, use it for more reliable fetching |
|
if item.EventID != "" { |
|
filter.IDs = []string{item.EventID} |
|
} |
|
|
|
logFilter(filter, fmt.Sprintf("index event kind %d %s", targetKind, item.DTag)) |
|
|
|
// Use relay hint if available, otherwise use default client relays |
|
var event *nostr.Event |
|
var err error |
|
if item.RelayHint != "" { |
|
// Connect to the relay hint |
|
relay, relayErr := ws.client.ConnectToRelay(ctx, item.RelayHint) |
|
if relayErr == nil { |
|
relayEvents, queryErr := relay.QuerySync(ctx, filter) |
|
relay.Close() |
|
if queryErr == nil { |
|
if len(relayEvents) > 0 { |
|
event = relayEvents[0] |
|
} else { |
|
log.Printf("Relay hint %s returned 0 events for %s (kind %d, event ID: %v)", item.RelayHint, item.DTag, targetKind, item.EventID != "") |
|
} |
|
} else { |
|
log.Printf("Error querying relay hint %s for %s (kind %d): %v", item.RelayHint, item.DTag, targetKind, queryErr) |
|
} |
|
} else { |
|
log.Printf("Error connecting to relay hint %s for %s (kind %d): %v", item.RelayHint, item.DTag, targetKind, relayErr) |
|
} |
|
} |
|
|
|
// Fallback to default client relays if relay hint failed or wasn't provided |
|
// Client now queries all three relays automatically |
|
if event == nil { |
|
log.Printf("Querying all relays for %s (kind %d)", item.DTag, targetKind) |
|
event, err = ws.client.FetchEvent(ctx, filter) |
|
if err != nil { |
|
// If still not found and event ID was specified, try without event ID to get latest replaceable event |
|
if item.EventID != "" { |
|
log.Printf("Trying without event ID for %s (kind %d) to get latest replaceable event", item.DTag, targetKind) |
|
filterWithoutID := nostr.Filter{ |
|
Kinds: []int{targetKind}, |
|
Authors: []string{item.Pubkey}, |
|
Tags: map[string][]string{ |
|
"d": {item.DTag}, |
|
}, |
|
} |
|
logFilter(filterWithoutID, fmt.Sprintf("index event kind %d %s (without event ID)", targetKind, item.DTag)) |
|
event, err = ws.client.FetchEvent(ctx, filterWithoutID) |
|
if err == nil { |
|
log.Printf("Successfully fetched %s (kind %d, latest) from all relays", item.DTag, targetKind) |
|
} |
|
} |
|
} else { |
|
log.Printf("Successfully fetched %s (kind %d) from all relays", item.DTag, targetKind) |
|
} |
|
|
|
if event == nil { |
|
// Log error but continue with other events |
|
log.Printf("Error fetching event for %s (kind %d, pubkey: %s, event ID: %v): %v", item.DTag, targetKind, item.Pubkey, item.EventID != "", err) |
|
continue |
|
} |
|
} |
|
|
|
events = append(events, event) |
|
} |
|
|
|
if len(events) == 0 && len(index.Items) > 0 { |
|
log.Printf("Warning: No events of kind %d could be fetched from %d index items", targetKind, len(index.Items)) |
|
} |
|
|
|
return events, nil |
|
} |
|
|
|
// FetchWikiEventByDTag fetches a single wiki event by d tag |
|
func (ws *WikiService) FetchWikiEventByDTag(ctx context.Context, pubkey, dTag string) (*WikiEvent, error) { |
|
filter := nostr.Filter{ |
|
Kinds: []int{ws.wikiKind}, |
|
Authors: []string{pubkey}, |
|
Tags: map[string][]string{ |
|
"d": {dTag}, |
|
}, |
|
Limit: 1, |
|
} |
|
logFilter(filter, fmt.Sprintf("wiki by d-tag %s", dTag)) |
|
|
|
event, err := ws.client.FetchEvent(ctx, filter) |
|
if err != nil { |
|
return nil, fmt.Errorf("failed to fetch wiki event: %w", err) |
|
} |
|
|
|
return ParseWikiEvent(event, ws.wikiKind) |
|
}
|
|
|