-
Notifications
You must be signed in to change notification settings - Fork 2
/
metadata.go
156 lines (134 loc) · 4.4 KB
/
metadata.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package sdk
import (
"context"
"encoding/json"
"fmt"
"sync"
"time"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19"
)
type ProfileMetadata struct {
PubKey string `json:"-"` // must always be set otherwise things will break
Event *nostr.Event `json:"-"` // may be empty if a profile metadata event wasn't found
// every one of these may be empty
Name string `json:"name,omitempty"`
DisplayName string `json:"display_name,omitempty"`
About string `json:"about,omitempty"`
Website string `json:"website,omitempty"`
Picture string `json:"picture,omitempty"`
Banner string `json:"banner,omitempty"`
NIP05 string `json:"nip05,omitempty"`
LUD16 string `json:"lud16,omitempty"`
}
func (p ProfileMetadata) Npub() string {
v, _ := nip19.EncodePublicKey(p.PubKey)
return v
}
func (p ProfileMetadata) NpubShort() string {
npub := p.Npub()
return npub[0:7] + "…" + npub[58:]
}
func (p ProfileMetadata) Nprofile(ctx context.Context, sys *System, nrelays int) string {
v, _ := nip19.EncodeProfile(p.PubKey, sys.FetchOutboxRelays(ctx, p.PubKey))
return v
}
func (p ProfileMetadata) ShortName() string {
if p.Name != "" {
return p.Name
}
if p.DisplayName != "" {
return p.DisplayName
}
return p.NpubShort()
}
// FetchProfileMetadata fetches metadata for a given user from the local cache, or from the local store,
// or, failing these, from the target user's defined outbox relays -- then caches the result.
func (sys *System) FetchProfileMetadata(ctx context.Context, pubkey string) ProfileMetadata {
pm, _ := sys.fetchProfileMetadata(ctx, pubkey)
return pm
}
// FetchOrStoreProfileMetadata is like FetchProfileMetadata, but also saves the result to the sys.Store
func (sys *System) FetchOrStoreProfileMetadata(ctx context.Context, pubkey string) ProfileMetadata {
pm, fromInternal := sys.fetchProfileMetadata(ctx, pubkey)
if !fromInternal && pm.Event != nil {
sys.StoreRelay.Publish(ctx, *pm.Event)
}
return pm
}
func (sys *System) fetchProfileMetadata(ctx context.Context, pubkey string) (pm ProfileMetadata, fromInternal bool) {
if pm, fromInternal = sys.LoadProfileMetadataFromCache(ctx, pubkey); fromInternal {
return pm, fromInternal
}
meta := ProfileMetadata{PubKey: pubkey}
thunk0 := sys.replaceableLoaders[0].Load(ctx, pubkey)
evt, err := thunk0()
if err == nil {
meta, err = ParseMetadata(evt)
if err == nil {
sys.MetadataCache.SetWithTTL(pubkey, meta, time.Hour*6)
}
}
return meta, false
}
func (sys *System) LoadProfileMetadataFromCache(ctx context.Context, pubkey string) (ProfileMetadata, bool) {
if v, ok := sys.MetadataCache.Get(pubkey); ok {
return v, true
}
if sys.Store != nil {
res, _ := sys.StoreRelay.QuerySync(ctx, nostr.Filter{Kinds: []int{0}, Authors: []string{pubkey}})
if len(res) != 0 {
if m, err := ParseMetadata(res[0]); err == nil {
m.PubKey = pubkey
m.Event = res[0]
sys.MetadataCache.SetWithTTL(pubkey, m, time.Hour*6)
return m, true
}
}
}
return ProfileMetadata{PubKey: pubkey}, false
}
// FetchUserEvents fetches events from each users' outbox relays, grouping queries when possible.
func (sys *System) FetchUserEvents(ctx context.Context, filter nostr.Filter) (map[string][]*nostr.Event, error) {
filters, err := sys.ExpandQueriesByAuthorAndRelays(ctx, filter)
if err != nil {
return nil, fmt.Errorf("failed to expand queries: %w", err)
}
results := make(map[string][]*nostr.Event)
wg := sync.WaitGroup{}
wg.Add(len(filters))
for relay, filter := range filters {
go func(relay *nostr.Relay, filter nostr.Filter) {
defer wg.Done()
filter.Limit = filter.Limit * len(filter.Authors) // hack
sub, err := relay.Subscribe(ctx, nostr.Filters{filter})
if err != nil {
return
}
for {
select {
case evt := <-sub.Events:
results[evt.PubKey] = append(results[evt.PubKey], evt)
case <-sub.EndOfStoredEvents:
return
}
}
}(relay, filter)
}
wg.Wait()
return results, nil
}
func ParseMetadata(event *nostr.Event) (meta ProfileMetadata, err error) {
if event.Kind != 0 {
err = fmt.Errorf("event %s is kind %d, not 0", event.ID, event.Kind)
} else if err := json.Unmarshal([]byte(event.Content), &meta); err != nil {
cont := event.Content
if len(cont) > 100 {
cont = cont[0:99]
}
err = fmt.Errorf("failed to parse metadata (%s) from event %s: %w", cont, event.ID, err)
}
meta.PubKey = event.PubKey
meta.Event = event
return meta, err
}