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.
 
 
 
 
 

361 lines
11 KiB

package nostr
import (
"context"
"fmt"
"gitcitadel-online/internal/logger"
"github.com/nbd-wtf/go-nostr"
)
// logFilter logs the exact filter being used for debugging
func logFilter(filter nostr.Filter, context string) {
logger.WithFields(map[string]interface{}{
"context": context,
"kinds": filter.Kinds,
"authors": filter.Authors,
"ids": filter.IDs,
"tags": filter.Tags,
"limit": filter.Limit,
}).Debug("Nostr filter")
}
// 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}
logger.WithFields(map[string]interface{}{
"event_id": item.EventID[:16] + "...",
"dtag": item.DTag,
}).Debug("Using event ID for wiki event")
}
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
logger.WithFields(map[string]interface{}{
"relay_hint": item.RelayHint,
"dtag": item.DTag,
}).Debug("Trying relay hint")
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]
logger.WithFields(map[string]interface{}{
"dtag": item.DTag,
"relay_hint": item.RelayHint,
}).Debug("Successfully fetched from relay hint")
} else {
logger.WithFields(map[string]interface{}{
"relay_hint": item.RelayHint,
"dtag": item.DTag,
"has_event_id": item.EventID != "",
}).Debug("Relay hint returned 0 events")
}
} else {
logger.WithFields(map[string]interface{}{
"relay_hint": item.RelayHint,
"dtag": item.DTag,
}).Debugf("Error querying relay hint: %v", queryErr)
}
} else {
logger.WithFields(map[string]interface{}{
"relay_hint": item.RelayHint,
"dtag": item.DTag,
}).Debugf("Error connecting to relay hint: %v", relayErr)
}
}
// Fallback to default client relays if relay hint failed or wasn't provided
// Client now queries all three relays automatically
if event == nil {
logger.WithField("dtag", item.DTag).Debug("Querying all relays")
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 != "" {
logger.WithField("dtag", item.DTag).Debug("Trying without event ID to get latest replaceable event")
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 {
logger.WithField("dtag", item.DTag).Debug("Successfully fetched latest from all relays")
}
}
} else {
logger.WithField("dtag", item.DTag).Debug("Successfully fetched from all relays")
}
if event == nil {
// Log error but continue with other events
logger.WithFields(map[string]interface{}{
"dtag": item.DTag,
"pubkey": item.Pubkey,
"has_event_id": item.EventID != "",
}).Warnf("Error fetching wiki event: %v", err)
continue
}
}
// Parse the wiki event
wiki, err := ParseWikiEvent(event, ws.wikiKind)
if err != nil {
logger.WithFields(map[string]interface{}{
"dtag": item.DTag,
}).Warnf("Error parsing wiki event: %v", err)
continue
}
wikiEvents = append(wikiEvents, wiki)
}
if len(wikiEvents) == 0 && len(index.Items) > 0 {
logger.WithField("items", len(index.Items)).Warn("No wiki events could be fetched from 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 {
logger.WithFields(map[string]interface{}{
"relay_hint": item.RelayHint,
"dtag": item.DTag,
"kind": targetKind,
"has_event_id": item.EventID != "",
}).Debug("Relay hint returned 0 events")
}
} else {
logger.WithFields(map[string]interface{}{
"relay_hint": item.RelayHint,
"dtag": item.DTag,
"kind": targetKind,
}).Debugf("Error querying relay hint: %v", queryErr)
}
} else {
logger.WithFields(map[string]interface{}{
"relay_hint": item.RelayHint,
"dtag": item.DTag,
"kind": targetKind,
}).Debugf("Error connecting to relay hint: %v", relayErr)
}
}
// Fallback to default client relays if relay hint failed or wasn't provided
// Client now queries all three relays automatically
if event == nil {
logger.WithFields(map[string]interface{}{
"dtag": item.DTag,
"kind": targetKind,
}).Debug("Querying all relays")
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 != "" {
logger.WithFields(map[string]interface{}{
"dtag": item.DTag,
"kind": targetKind,
}).Debug("Trying without event ID to get latest replaceable event")
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 {
logger.WithFields(map[string]interface{}{
"dtag": item.DTag,
"kind": targetKind,
}).Debug("Successfully fetched latest from all relays")
}
}
} else {
logger.WithFields(map[string]interface{}{
"dtag": item.DTag,
"kind": targetKind,
}).Debug("Successfully fetched from all relays")
}
if event == nil {
// Log error but continue with other events
logger.WithFields(map[string]interface{}{
"dtag": item.DTag,
"kind": targetKind,
"pubkey": item.Pubkey,
"has_event_id": item.EventID != "",
}).Warnf("Error fetching event: %v", err)
continue
}
}
events = append(events, event)
}
if len(events) == 0 && len(index.Items) > 0 {
logger.WithFields(map[string]interface{}{
"kind": targetKind,
"items": len(index.Items),
}).Warn("No events could be fetched from 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)
}