pointers.go raw
1 package nostr
2
3 import (
4 "fmt"
5 "strconv"
6 "strings"
7 )
8
9 // Pointer is an interface for different types of Nostr pointers.
10 //
11 // In this context, a "pointer" is a reference to an event or profile potentially including
12 // relays and other metadata that might help find it.
13 type Pointer interface {
14 // AsTagReference returns the pointer as a string as it would be seen in the value of a tag (i.e. the tag's second item).
15 AsTagReference() string
16
17 // AsTag converts the pointer with all the information available to a tag that can be included in events.
18 AsTag() Tag
19
20 // AsFilter converts the pointer to a Filter that can be used to query for it on relays.
21 AsFilter() Filter
22 MatchesEvent(Event) bool
23 }
24
25 var (
26 _ Pointer = (*ProfilePointer)(nil)
27 _ Pointer = (*EventPointer)(nil)
28 _ Pointer = (*EntityPointer)(nil)
29 )
30
31 // ProfilePointer represents a pointer to a Nostr profile.
32 type ProfilePointer struct {
33 PublicKey string `json:"pubkey"`
34 Relays []string `json:"relays,omitempty"`
35 }
36
37 // ProfilePointerFromTag creates a ProfilePointer from a "p" tag (but it doesn't have to be necessarily a "p" tag, could be something else).
38 func ProfilePointerFromTag(refTag Tag) (ProfilePointer, error) {
39 pk := refTag[1]
40 if !IsValidPublicKey(pk) {
41 return ProfilePointer{}, fmt.Errorf("invalid pubkey '%s'", pk)
42 }
43
44 pointer := ProfilePointer{
45 PublicKey: pk,
46 }
47 if len(refTag) > 2 {
48 if relay := (refTag)[2]; IsValidRelayURL(relay) {
49 pointer.Relays = []string{relay}
50 }
51 }
52 return pointer, nil
53 }
54
55 // MatchesEvent checks if the pointer matches an event.
56 func (ep ProfilePointer) MatchesEvent(_ Event) bool { return false }
57 func (ep ProfilePointer) AsTagReference() string { return ep.PublicKey }
58 func (ep ProfilePointer) AsFilter() Filter { return Filter{Authors: []string{ep.PublicKey}} }
59
60 func (ep ProfilePointer) AsTag() Tag {
61 if len(ep.Relays) > 0 {
62 return Tag{"p", ep.PublicKey, ep.Relays[0]}
63 }
64 return Tag{"p", ep.PublicKey}
65 }
66
67 // EventPointer represents a pointer to a nostr event.
68 type EventPointer struct {
69 ID string `json:"id"`
70 Relays []string `json:"relays,omitempty"`
71 Author string `json:"author,omitempty"`
72 Kind int `json:"kind,omitempty"`
73 }
74
75 // EventPointerFromTag creates an EventPointer from an "e" tag (but it could be other tag name, it isn't checked).
76 func EventPointerFromTag(refTag Tag) (EventPointer, error) {
77 id := refTag[1]
78 if !IsValid32ByteHex(id) {
79 return EventPointer{}, fmt.Errorf("invalid id '%s'", id)
80 }
81
82 pointer := EventPointer{
83 ID: id,
84 }
85 if len(refTag) > 2 {
86 if relay := (refTag)[2]; IsValidRelayURL(relay) {
87 pointer.Relays = []string{relay}
88 }
89 if len(refTag) > 3 && IsValidPublicKey(refTag[3]) {
90 pointer.Author = (refTag)[3]
91 } else if len(refTag) > 4 && IsValidPublicKey(refTag[4]) {
92 pointer.Author = (refTag)[4]
93 }
94 }
95 return pointer, nil
96 }
97
98 func (ep EventPointer) MatchesEvent(evt Event) bool { return evt.ID == ep.ID }
99 func (ep EventPointer) AsTagReference() string { return ep.ID }
100 func (ep EventPointer) AsFilter() Filter { return Filter{IDs: []string{ep.ID}} }
101
102 // AsTag converts the pointer to a Tag.
103 func (ep EventPointer) AsTag() Tag {
104 if len(ep.Relays) > 0 {
105 if ep.Author != "" {
106 return Tag{"e", ep.ID, ep.Relays[0], ep.Author}
107 } else {
108 return Tag{"e", ep.ID, ep.Relays[0]}
109 }
110 }
111 return Tag{"e", ep.ID}
112 }
113
114 // EntityPointer represents a pointer to a nostr entity (addressable event).
115 type EntityPointer struct {
116 PublicKey string `json:"pubkey"`
117 Kind int `json:"kind,omitempty"`
118 Identifier string `json:"identifier,omitempty"`
119 Relays []string `json:"relays,omitempty"`
120 }
121
122 // EntityPointerFromTag creates an EntityPointer from an "a" tag (but it doesn't check if the tag is really "a", it could be anything).
123 func EntityPointerFromTag(refTag Tag) (EntityPointer, error) {
124 spl := strings.SplitN(refTag[1], ":", 3)
125 if len(spl) != 3 {
126 return EntityPointer{}, fmt.Errorf("invalid addr ref '%s'", refTag[1])
127 }
128 if !IsValidPublicKey(spl[1]) {
129 return EntityPointer{}, fmt.Errorf("invalid addr pubkey '%s'", spl[1])
130 }
131
132 kind, err := strconv.Atoi(spl[0])
133 if err != nil || kind > (1<<16) {
134 return EntityPointer{}, fmt.Errorf("invalid addr kind '%s'", spl[0])
135 }
136
137 pointer := EntityPointer{
138 Kind: kind,
139 PublicKey: spl[1],
140 Identifier: spl[2],
141 }
142 if len(refTag) > 2 {
143 if relay := (refTag)[2]; IsValidRelayURL(relay) {
144 pointer.Relays = []string{relay}
145 }
146 }
147
148 return pointer, nil
149 }
150
151 // MatchesEvent checks if the pointer matches an event.
152 func (ep EntityPointer) MatchesEvent(evt Event) bool {
153 return ep.PublicKey == evt.PubKey &&
154 ep.Kind == evt.Kind &&
155 evt.Tags.GetD() == ep.Identifier
156 }
157
158 func (ep EntityPointer) AsTagReference() string {
159 return fmt.Sprintf("%d:%s:%s", ep.Kind, ep.PublicKey, ep.Identifier)
160 }
161
162 func (ep EntityPointer) AsFilter() Filter {
163 return Filter{
164 Kinds: []int{ep.Kind},
165 Authors: []string{ep.PublicKey},
166 Tags: TagMap{"d": []string{ep.Identifier}},
167 }
168 }
169
170 func (ep EntityPointer) AsTag() Tag {
171 if len(ep.Relays) > 0 {
172 return Tag{"a", ep.AsTagReference(), ep.Relays[0]}
173 }
174 return Tag{"a", ep.AsTagReference()}
175 }
176