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