2023-02-05 16:25:00 -03:00
|
|
|
package nostr
|
|
|
|
|
2025-01-01 15:04:54 -03:00
|
|
|
import (
|
|
|
|
"fmt"
|
2025-02-13 23:05:39 -03:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
2025-01-01 15:04:54 -03:00
|
|
|
)
|
|
|
|
|
2025-03-04 11:08:31 -03:00
|
|
|
// Pointer is an interface for different types of Nostr pointers.
|
|
|
|
//
|
|
|
|
// In this context, a "pointer" is a reference to an event or profile potentially including
|
|
|
|
// relays and other metadata that might help find it.
|
2025-01-01 15:04:54 -03:00
|
|
|
type Pointer interface {
|
2025-03-04 11:08:31 -03:00
|
|
|
// 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).
|
2025-01-01 15:04:54 -03:00
|
|
|
AsTagReference() string
|
2025-03-04 11:08:31 -03:00
|
|
|
|
|
|
|
// AsTag converts the pointer with all the information available to a tag that can be included in events.
|
2025-01-01 15:04:54 -03:00
|
|
|
AsTag() Tag
|
2025-03-04 11:08:31 -03:00
|
|
|
|
|
|
|
// AsFilter converts the pointer to a Filter that can be used to query for it on relays.
|
2025-02-16 11:53:26 -03:00
|
|
|
AsFilter() Filter
|
2025-01-23 16:53:15 -03:00
|
|
|
MatchesEvent(Event) bool
|
2025-01-01 15:04:54 -03:00
|
|
|
}
|
|
|
|
|
2025-02-16 11:53:26 -03:00
|
|
|
var (
|
|
|
|
_ Pointer = (*ProfilePointer)(nil)
|
|
|
|
_ Pointer = (*EventPointer)(nil)
|
|
|
|
_ Pointer = (*EntityPointer)(nil)
|
2025-03-10 02:35:02 -03:00
|
|
|
_ Pointer = (*ExternalPointer)(nil)
|
2025-02-16 11:53:26 -03:00
|
|
|
)
|
|
|
|
|
2025-03-04 11:08:31 -03:00
|
|
|
// ProfilePointer represents a pointer to a Nostr profile.
|
2023-02-05 16:25:00 -03:00
|
|
|
type ProfilePointer struct {
|
2023-05-04 08:22:17 -03:00
|
|
|
PublicKey string `json:"pubkey"`
|
|
|
|
Relays []string `json:"relays,omitempty"`
|
2023-02-05 16:25:00 -03:00
|
|
|
}
|
|
|
|
|
2025-03-04 11:08:31 -03:00
|
|
|
// ProfilePointerFromTag creates a ProfilePointer from a "p" tag (but it doesn't have to be necessarily a "p" tag, could be something else).
|
2025-02-13 23:05:39 -03:00
|
|
|
func ProfilePointerFromTag(refTag Tag) (ProfilePointer, error) {
|
2025-03-10 02:35:02 -03:00
|
|
|
pk := refTag[1]
|
2025-02-13 23:05:39 -03:00
|
|
|
if !IsValidPublicKey(pk) {
|
|
|
|
return ProfilePointer{}, fmt.Errorf("invalid pubkey '%s'", pk)
|
|
|
|
}
|
|
|
|
|
|
|
|
pointer := ProfilePointer{
|
|
|
|
PublicKey: pk,
|
|
|
|
}
|
|
|
|
if len(refTag) > 2 {
|
|
|
|
if relay := (refTag)[2]; IsValidRelayURL(relay) {
|
|
|
|
pointer.Relays = []string{relay}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return pointer, nil
|
|
|
|
}
|
|
|
|
|
2025-03-04 11:08:31 -03:00
|
|
|
// MatchesEvent checks if the pointer matches an event.
|
2025-01-23 16:53:15 -03:00
|
|
|
func (ep ProfilePointer) MatchesEvent(_ Event) bool { return false }
|
|
|
|
func (ep ProfilePointer) AsTagReference() string { return ep.PublicKey }
|
2025-02-16 11:53:26 -03:00
|
|
|
func (ep ProfilePointer) AsFilter() Filter { return Filter{Authors: []string{ep.PublicKey}} }
|
2025-01-01 15:04:54 -03:00
|
|
|
|
|
|
|
func (ep ProfilePointer) AsTag() Tag {
|
|
|
|
if len(ep.Relays) > 0 {
|
|
|
|
return Tag{"p", ep.PublicKey, ep.Relays[0]}
|
|
|
|
}
|
|
|
|
return Tag{"p", ep.PublicKey}
|
|
|
|
}
|
|
|
|
|
2025-03-04 11:08:31 -03:00
|
|
|
// EventPointer represents a pointer to a nostr event.
|
2023-02-05 16:25:00 -03:00
|
|
|
type EventPointer struct {
|
2023-05-04 08:22:17 -03:00
|
|
|
ID string `json:"id"`
|
|
|
|
Relays []string `json:"relays,omitempty"`
|
|
|
|
Author string `json:"author,omitempty"`
|
|
|
|
Kind int `json:"kind,omitempty"`
|
2023-02-05 16:25:00 -03:00
|
|
|
}
|
2023-02-27 16:15:04 -03:00
|
|
|
|
2025-03-04 11:08:31 -03:00
|
|
|
// EventPointerFromTag creates an EventPointer from an "e" tag (but it could be other tag name, it isn't checked).
|
2025-02-13 23:05:39 -03:00
|
|
|
func EventPointerFromTag(refTag Tag) (EventPointer, error) {
|
2025-03-10 02:35:02 -03:00
|
|
|
id := refTag[1]
|
2025-02-13 23:05:39 -03:00
|
|
|
if !IsValid32ByteHex(id) {
|
|
|
|
return EventPointer{}, fmt.Errorf("invalid id '%s'", id)
|
|
|
|
}
|
|
|
|
|
|
|
|
pointer := EventPointer{
|
|
|
|
ID: id,
|
|
|
|
}
|
|
|
|
if len(refTag) > 2 {
|
|
|
|
if relay := (refTag)[2]; IsValidRelayURL(relay) {
|
|
|
|
pointer.Relays = []string{relay}
|
|
|
|
}
|
2025-03-10 02:35:02 -03:00
|
|
|
if len(refTag) > 3 && IsValidPublicKey(refTag[3]) {
|
2025-02-13 23:05:39 -03:00
|
|
|
pointer.Author = (refTag)[3]
|
2025-03-10 02:35:02 -03:00
|
|
|
} else if len(refTag) > 4 && IsValidPublicKey(refTag[4]) {
|
|
|
|
pointer.Author = (refTag)[4]
|
2025-02-13 23:05:39 -03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return pointer, nil
|
|
|
|
}
|
|
|
|
|
2025-01-23 16:53:15 -03:00
|
|
|
func (ep EventPointer) MatchesEvent(evt Event) bool { return evt.ID == ep.ID }
|
|
|
|
func (ep EventPointer) AsTagReference() string { return ep.ID }
|
2025-02-16 11:53:26 -03:00
|
|
|
func (ep EventPointer) AsFilter() Filter { return Filter{IDs: []string{ep.ID}} }
|
2025-01-01 15:04:54 -03:00
|
|
|
|
2025-03-04 11:08:31 -03:00
|
|
|
// AsTag converts the pointer to a Tag.
|
2025-01-01 15:04:54 -03:00
|
|
|
func (ep EventPointer) AsTag() Tag {
|
|
|
|
if len(ep.Relays) > 0 {
|
|
|
|
if ep.Author != "" {
|
|
|
|
return Tag{"e", ep.ID, ep.Relays[0], ep.Author}
|
|
|
|
} else {
|
|
|
|
return Tag{"e", ep.ID, ep.Relays[0]}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Tag{"e", ep.ID}
|
|
|
|
}
|
|
|
|
|
2025-03-04 11:08:31 -03:00
|
|
|
// EntityPointer represents a pointer to a nostr entity (addressable event).
|
2023-02-27 16:15:04 -03:00
|
|
|
type EntityPointer struct {
|
2023-05-04 08:22:17 -03:00
|
|
|
PublicKey string `json:"pubkey"`
|
|
|
|
Kind int `json:"kind,omitempty"`
|
|
|
|
Identifier string `json:"identifier,omitempty"`
|
|
|
|
Relays []string `json:"relays,omitempty"`
|
2023-02-27 16:15:04 -03:00
|
|
|
}
|
2025-01-01 15:04:54 -03:00
|
|
|
|
2025-03-04 11:08:31 -03:00
|
|
|
// EntityPointerFromTag creates an EntityPointer from an "a" tag (but it doesn't check if the tag is really "a", it could be anything).
|
2025-02-13 23:05:39 -03:00
|
|
|
func EntityPointerFromTag(refTag Tag) (EntityPointer, error) {
|
|
|
|
spl := strings.SplitN(refTag[1], ":", 3)
|
|
|
|
if len(spl) != 3 {
|
|
|
|
return EntityPointer{}, fmt.Errorf("invalid addr ref '%s'", refTag[1])
|
|
|
|
}
|
|
|
|
if !IsValidPublicKey(spl[1]) {
|
|
|
|
return EntityPointer{}, fmt.Errorf("invalid addr pubkey '%s'", spl[1])
|
|
|
|
}
|
|
|
|
|
|
|
|
kind, err := strconv.Atoi(spl[0])
|
|
|
|
if err != nil || kind > (1<<16) {
|
|
|
|
return EntityPointer{}, fmt.Errorf("invalid addr kind '%s'", spl[0])
|
|
|
|
}
|
|
|
|
|
|
|
|
pointer := EntityPointer{
|
|
|
|
Kind: kind,
|
|
|
|
PublicKey: spl[1],
|
|
|
|
Identifier: spl[2],
|
|
|
|
}
|
|
|
|
if len(refTag) > 2 {
|
|
|
|
if relay := (refTag)[2]; IsValidRelayURL(relay) {
|
|
|
|
pointer.Relays = []string{relay}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return pointer, nil
|
|
|
|
}
|
|
|
|
|
2025-03-04 11:08:31 -03:00
|
|
|
// MatchesEvent checks if the pointer matches an event.
|
2025-01-23 16:53:15 -03:00
|
|
|
func (ep EntityPointer) MatchesEvent(evt Event) bool {
|
|
|
|
return ep.PublicKey == evt.PubKey &&
|
|
|
|
ep.Kind == evt.Kind &&
|
|
|
|
evt.Tags.GetD() == ep.Identifier
|
|
|
|
}
|
|
|
|
|
2025-01-01 15:04:54 -03:00
|
|
|
func (ep EntityPointer) AsTagReference() string {
|
|
|
|
return fmt.Sprintf("%d:%s:%s", ep.Kind, ep.PublicKey, ep.Identifier)
|
|
|
|
}
|
|
|
|
|
2025-02-16 11:53:26 -03:00
|
|
|
func (ep EntityPointer) AsFilter() Filter {
|
|
|
|
return Filter{
|
|
|
|
Kinds: []int{ep.Kind},
|
|
|
|
Authors: []string{ep.PublicKey},
|
|
|
|
Tags: TagMap{"d": []string{ep.Identifier}},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-01 15:04:54 -03:00
|
|
|
func (ep EntityPointer) AsTag() Tag {
|
|
|
|
if len(ep.Relays) > 0 {
|
|
|
|
return Tag{"a", ep.AsTagReference(), ep.Relays[0]}
|
|
|
|
}
|
|
|
|
return Tag{"a", ep.AsTagReference()}
|
|
|
|
}
|
2025-03-10 02:35:02 -03:00
|
|
|
|
|
|
|
// ExternalPointer represents a pointer to a Nostr profile.
|
|
|
|
type ExternalPointer struct {
|
|
|
|
Thing string
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExternalPointerFromTag creates a ExternalPointer from an "i" tag
|
|
|
|
func ExternalPointerFromTag(refTag Tag) (ExternalPointer, error) {
|
|
|
|
return ExternalPointer{refTag[1]}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ep ExternalPointer) MatchesEvent(_ Event) bool { return false }
|
|
|
|
func (ep ExternalPointer) AsTagReference() string { return ep.Thing }
|
|
|
|
func (ep ExternalPointer) AsFilter() Filter { return Filter{} }
|
|
|
|
|
|
|
|
func (ep ExternalPointer) AsTag() Tag {
|
|
|
|
return Tag{"i", ep.Thing}
|
|
|
|
}
|