82 lines
2.0 KiB
Go
82 lines
2.0 KiB
Go
package nostr
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"time"
|
|
|
|
gn "github.com/nbd-wtf/go-nostr"
|
|
)
|
|
|
|
// Metadata mirrors the kind:0 profile JSON. We accept both snake_case
|
|
// (`display_name`, NIP-24) and the deprecated camelCase `displayName` since
|
|
// some clients still publish only the latter. `Username` is also deprecated
|
|
// but appears in older profiles; per NIP-24 it should be ignored in favor of
|
|
// `Name`, but we surface it as a last-resort fallback.
|
|
type Metadata struct {
|
|
Name string `json:"name"`
|
|
DisplayName string `json:"display_name"`
|
|
DisplayNameAlt string `json:"displayName"`
|
|
Username string `json:"username"`
|
|
NIP05 string `json:"nip05"`
|
|
About string `json:"about"`
|
|
Picture string `json:"picture"`
|
|
}
|
|
|
|
// ParseMetadata decodes a kind:0 content payload.
|
|
func ParseMetadata(content string) (*Metadata, error) {
|
|
var md Metadata
|
|
if err := json.Unmarshal([]byte(content), &md); err != nil {
|
|
return nil, err
|
|
}
|
|
return &md, nil
|
|
}
|
|
|
|
// FetchMetadata pulls the most recent kind:0 event for a hex pubkey across the pool.
|
|
// Each relay may return multiple replacement events; we keep the one with the
|
|
// highest CreatedAt across every relay reached before timeout.
|
|
func FetchMetadata(ctx context.Context, p *Pool, hexpk string) (*Metadata, error) {
|
|
filter := gn.Filter{
|
|
Kinds: []int{0},
|
|
Authors: []string{hexpk},
|
|
Limit: 100,
|
|
}
|
|
|
|
var newest *gn.Event
|
|
for _, url := range p.URLs() {
|
|
r, err := p.Connect(ctx, url)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
subCtx, cancel := context.WithTimeout(ctx, 8*time.Second)
|
|
sub, err := r.Subscribe(subCtx, gn.Filters{filter})
|
|
if err != nil {
|
|
cancel()
|
|
continue
|
|
}
|
|
loop:
|
|
for {
|
|
select {
|
|
case ev, ok := <-sub.Events:
|
|
if !ok {
|
|
break loop
|
|
}
|
|
if newest == nil || ev.CreatedAt > newest.CreatedAt {
|
|
newest = ev
|
|
}
|
|
case <-sub.EndOfStoredEvents:
|
|
break loop
|
|
case <-subCtx.Done():
|
|
break loop
|
|
}
|
|
}
|
|
sub.Unsub()
|
|
cancel()
|
|
}
|
|
|
|
if newest == nil {
|
|
return nil, nil
|
|
}
|
|
return ParseMetadata(newest.Content)
|
|
}
|