types.go raw

   1  package relayinfo
   2  
   3  import (
   4  	"encoding/json"
   5  	"errors"
   6  	"os"
   7  	"sort"
   8  	"sync"
   9  
  10  	"next.orly.dev/pkg/nostr/encoders/kind"
  11  	"next.orly.dev/pkg/nostr/encoders/timestamp"
  12  	"next.orly.dev/pkg/nostr/utils/number"
  13  	"next.orly.dev/pkg/lol/chk"
  14  	"next.orly.dev/pkg/lol/log"
  15  )
  16  
  17  // NIP is a number and description of a nostr "improvement" possibility.
  18  type NIP struct {
  19  	Description string
  20  	Number      int
  21  }
  22  
  23  // N returns the number of a nostr "improvement" possibility.
  24  func (n NIP) N() int { return n.Number }
  25  
  26  // GetList converts a NIP into a number.List of simple numbers, sorted in
  27  // ascending order.
  28  func GetList(items ...NIP) (n number.List) {
  29  	for _, item := range items {
  30  		n = append(n, item.N())
  31  	}
  32  	sort.Sort(n)
  33  	return
  34  }
  35  
  36  // this is the list of all nips and their titles for use in the supported_nips
  37  // field
  38  var (
  39  	BasicProtocol              = NIP{"Basic protocol flow description", 1}
  40  	NIP1                       = BasicProtocol
  41  	FollowList                 = NIP{"Follow List", 2}
  42  	NIP2                       = FollowList
  43  	OpenTimestampsAttestations = NIP{
  44  		"OpenTimestamps Attestations for Events", 3,
  45  	}
  46  	NIP3                   = OpenTimestampsAttestations
  47  	EncryptedDirectMessage = NIP{
  48  		"Encrypted Direct Message -- unrecommended: deprecated in favor of NIP-17",
  49  		4,
  50  	}
  51  	NIP4                  = EncryptedDirectMessage
  52  	MappingNostrKeysToDNS = NIP{
  53  		"Mapping Nostr keys to DNS-based internet identifiers", 5,
  54  	}
  55  	NIP5               = MappingNostrKeysToDNS
  56  	BasicKeyDerivation = NIP{
  57  		"Basic key derivation from mnemonic seed phrase", 6,
  58  	}
  59  	NIP6                  = BasicKeyDerivation
  60  	WindowNostrCapability = NIP{
  61  		"window.nostr capability for web browsers", 7,
  62  	}
  63  	NIP7             = WindowNostrCapability
  64  	HandlingMentions = NIP{
  65  		"Handling Mentions -- unrecommended: deprecated in favor of NIP-27", 8,
  66  	}
  67  	NIP8                     = HandlingMentions
  68  	EventDeletion            = NIP{"Event Deletion Request", 9}
  69  	NIP9                     = EventDeletion
  70  	TextNotesAndThreads      = NIP{"Text Notes and Threads", 10}
  71  	NIP10                    = TextNotesAndThreads
  72  	RelayInformationDocument = NIP{"Relay Information Document", 11}
  73  	NIP11                    = RelayInformationDocument
  74  	GenericTagQueries        = NIP{"Generic Tag Queries", 12}
  75  	NIP12                    = GenericTagQueries
  76  	ProofOfWork              = NIP{"Proof of Work", 13}
  77  	NIP13                    = ProofOfWork
  78  	SubjectTag               = NIP{"Subject tag in text events", 14}
  79  	NIP14                    = SubjectTag
  80  	NostrMarketplace         = NIP{
  81  		"Nostr Marketplace (for resilient marketplaces)", 15,
  82  	}
  83  	NIP15                 = NostrMarketplace
  84  	EventTreatment        = NIP{"Event Treatment", 16}
  85  	NIP16                 = EventTreatment
  86  	PrivateDirectMessages = NIP{"Private Direct Messages", 17}
  87  	NIP17                 = PrivateDirectMessages
  88  	Reposts               = NIP{"Reposts", 18}
  89  	NIP18                 = Reposts
  90  	Bech32EncodedEntities = NIP{"bech32-encoded entities", 19}
  91  	NIP19                 = Bech32EncodedEntities
  92  	CommandResults        = NIP{"Command Results", 20}
  93  	NIP20                 = CommandResults
  94  	NostrURIScheme        = NIP{"nostr: URI scheme", 21}
  95  	NIP21                 = NostrURIScheme
  96  	Comment               = NIP{"Comment", 22}
  97  	NIP22                 = Comment
  98  	LongFormContent       = NIP{"Long-form Content", 23}
  99  	NIP23                 = LongFormContent
 100  	ExtraMetadata         = NIP{"Extra metadata fields and tags", 24}
 101  	NIP24                 = ExtraMetadata
 102  	Reactions             = NIP{"Reactions", 25}
 103  	NIP25                 = Reactions
 104  	DelegatedEventSigning = NIP{
 105  		"Delegated Event Signing -- unrecommended: adds unnecessary burden for little gain",
 106  		26,
 107  	}
 108  	NIP26                          = DelegatedEventSigning
 109  	TextNoteReferences             = NIP{"Text Note References", 27}
 110  	NIP27                          = TextNoteReferences
 111  	PublicChat                     = NIP{"Public Chat", 28}
 112  	NIP28                          = PublicChat
 113  	RelayBasedGroups               = NIP{"Relay-based Groups", 29}
 114  	NIP29                          = RelayBasedGroups
 115  	CustomEmoji                    = NIP{"Custom Emoji", 30}
 116  	NIP30                          = CustomEmoji
 117  	DealingWithUnknownEvents       = NIP{"Dealing with Unknown Events", 31}
 118  	NIP31                          = DealingWithUnknownEvents
 119  	Labeling                       = NIP{"Labeling", 32}
 120  	NIP32                          = Labeling
 121  	ParameterizedReplaceableEvents = NIP{"Parameterized Replaceable Events", 33}
 122  	NIP33                          = ParameterizedReplaceableEvents
 123  	GitStuff                       = NIP{"git stuff", 34}
 124  	NIP34                          = GitStuff
 125  	Torrents                       = NIP{"Torrents", 35}
 126  	NIP35                          = Torrents
 127  	SensitiveContent               = NIP{"Sensitive Content", 36}
 128  	NIP36                          = SensitiveContent
 129  	DraftEvents                    = NIP{"Draft Events", 37}
 130  	NIP37                          = DraftEvents
 131  	UserStatuses                   = NIP{"User Statuses", 38}
 132  	NIP38                          = UserStatuses
 133  	ExternalIdentitiesInProfiles   = NIP{"External Identities in Profiles", 39}
 134  	NIP39                          = ExternalIdentitiesInProfiles
 135  	ExpirationTimestamp            = NIP{"Expiration Timestamp", 40}
 136  	NIP40                          = ExpirationTimestamp
 137  	Authentication                 = NIP{
 138  		"Authentication of clients to relays", 42,
 139  	}
 140  	NIP42               = Authentication
 141  	RelayAccessMetadata = NIP{
 142  		"Relay Access Metadata and Requests", 43,
 143  	}
 144  	NIP43                     = RelayAccessMetadata
 145  	VersionedEncryption       = NIP{"Encrypted Payloads (Versioned)", 44}
 146  	NIP44                     = VersionedEncryption
 147  	CountingResults           = NIP{"Counting results", 45}
 148  	NIP45                     = CountingResults
 149  	NostrRemoteSigning        = NIP{"Nostr Remote Signing", 46}
 150  	NIP46                     = NostrRemoteSigning
 151  	WalletConnect             = NIP{"Nostr Wallet Connect", 47}
 152  	NIP47                     = WalletConnect
 153  	ProxyTags                 = NIP{"Proxy Tags", 48}
 154  	NIP48                     = ProxyTags
 155  	PrivateKeyEncryption      = NIP{"Private Key Encryption", 49}
 156  	NIP49                     = PrivateKeyEncryption
 157  	SearchCapability          = NIP{"Search Capability", 50}
 158  	NIP50                     = SearchCapability
 159  	Lists                     = NIP{"Lists", 51}
 160  	NIP51                     = Lists
 161  	CalendarEvents            = NIP{"Calendar Events", 52}
 162  	NIP52                     = CalendarEvents
 163  	LiveActivities            = NIP{"Live Activities", 53}
 164  	NIP53                     = LiveActivities
 165  	Wiki                      = NIP{"Wiki", 54}
 166  	NIP54                     = Wiki
 167  	AndroidSignerApplication  = NIP{"Android Signer Application", 55}
 168  	NIP55                     = AndroidSignerApplication
 169  	Reporting                 = NIP{"Reporting", 56}
 170  	NIP56                     = Reporting
 171  	LightningZaps             = NIP{"Lightning Zaps", 57}
 172  	NIP57                     = LightningZaps
 173  	Badges                    = NIP{"Badges", 58}
 174  	NIP58                     = Badges
 175  	GiftWrap                  = NIP{"Gift Wrap", 59}
 176  	NIP59                     = GiftWrap
 177  	CashuWallet               = NIP{"Cashu Wallet", 60}
 178  	NIP60                     = CashuWallet
 179  	Nutzaps                   = NIP{"Nutzaps", 61}
 180  	NIP61                     = Nutzaps
 181  	RequestToVanish           = NIP{"Request to Vanish", 62}
 182  	NIP62                     = RequestToVanish
 183  	ChessPGN                  = NIP{"Chess (PGN)", 64}
 184  	NIP64                     = ChessPGN
 185  	RelayListMetadata         = NIP{"Relay List Metadata", 65}
 186  	NIP65                     = RelayListMetadata
 187  	RelayDiscoveryAndLiveness = NIP{
 188  		"Relay Discovery and Liveness Monitoring", 66,
 189  	}
 190  	NIP66                          = RelayDiscoveryAndLiveness
 191  	PictureFirstFeeds              = NIP{"Picture-first feeds", 68}
 192  	NIP68                          = PictureFirstFeeds
 193  	PeerToPeerOrderEvents          = NIP{"Peer-to-peer Order events", 69}
 194  	NIP69                          = PeerToPeerOrderEvents
 195  	ProtectedEvents                = NIP{"Protected Events", 70}
 196  	NIP70                          = ProtectedEvents
 197  	VideoEvents                    = NIP{"Video Events", 71}
 198  	NIP71                          = VideoEvents
 199  	ModeratedCommunities           = NIP{"Moderated Communities", 72}
 200  	NIP72                          = ModeratedCommunities
 201  	ExternalContentIDs             = NIP{"External Content IDs", 73}
 202  	NIP73                          = ExternalContentIDs
 203  	ZapGoals                       = NIP{"Zap Goals", 75}
 204  	NIP75                          = ZapGoals
 205  	NegentropySync                 = NIP{"Negentropy Syncing", 77}
 206  	NIP77                          = NegentropySync
 207  	ApplicationSpecificData        = NIP{"Application-specific data", 78}
 208  	NIP78                          = ApplicationSpecificData
 209  	Threads                        = NIP{"Threads", 0x7D}
 210  	NIP7D                          = Threads
 211  	Highlights                     = NIP{"Highlights", 84}
 212  	NIP84                          = Highlights
 213  	RelayManagementAPI             = NIP{"Relay Management API", 86}
 214  	NIP86                          = RelayManagementAPI
 215  	EcashMintDiscoverability       = NIP{"Ecash Mint Discoverability", 87}
 216  	NIP87                          = EcashMintDiscoverability
 217  	Polls                          = NIP{"Polls", 88}
 218  	NIP88                          = Polls
 219  	RecommendedApplicationHandlers = NIP{"Recommended Application Handlers", 89}
 220  	NIP89                          = RecommendedApplicationHandlers
 221  	DataVendingMachines            = NIP{"Data Vending Machines", 90}
 222  	NIP90                          = DataVendingMachines
 223  	MediaAttachments               = NIP{"Media Attachments", 92}
 224  	NIP92                          = MediaAttachments
 225  	FileMetadata                   = NIP{"File Metadata", 94}
 226  	NIP94                          = FileMetadata
 227  	HTTPFileStorageIntegration     = NIP{
 228  		"HTTP File Storage Integration -- unrecommended: replaced by blossom APIs",
 229  		96,
 230  	}
 231  	NIP96                         = HTTPFileStorageIntegration
 232  	HTTPAuth                      = NIP{"HTTP Auth", 98}
 233  	NIP98                         = HTTPAuth
 234  	ClassifiedListings            = NIP{"Classified Listings", 99}
 235  	NIP99                         = ClassifiedListings
 236  	VoiceMessages                 = NIP{"Voice Messages", 0xA0}
 237  	NIPA0                         = VoiceMessages
 238  	WebBookmarks                  = NIP{"Web Bookmarks", 0xB0}
 239  	NIPB0                         = WebBookmarks
 240  	Blossom                       = NIP{"Blossom", 0xB7}
 241  	NIPB7                         = Blossom
 242  	CodeSnippets                  = NIP{"Code Snippets", 0xC0}
 243  	NIPC0                         = CodeSnippets
 244  	Chats                         = NIP{"Chats", 0xC7}
 245  	NIPC7                         = Chats
 246  	E2EEMessagingUsingMLSProtocol = NIP{
 247  		"E2EE Messaging using MLS Protocol", 0xEE,
 248  	}
 249  	NIPEE = E2EEMessagingUsingMLSProtocol
 250  )
 251  
 252  var NIPMap = map[int]NIP{
 253  	1: NIP1, 2: NIP2, 3: NIP3, 4: NIP4, 5: NIP5, 6: NIP6, 7: NIP7, 8: NIP8,
 254  	9: NIP9, 10: NIP10,
 255  	11: NIP11, 12: NIP12, 13: NIP13, 14: NIP14, 15: NIP15, 16: NIP16, 17: NIP17,
 256  	18: NIP18, 19: NIP19, 20: NIP20,
 257  	21: NIP21, 22: NIP22, 23: NIP23, 24: NIP24, 25: NIP25, 26: NIP26, 27: NIP27,
 258  	28: NIP28, 29: NIP29, 30: NIP30,
 259  	31: NIP31, 32: NIP32, 33: NIP33, 34: NIP34, 35: NIP35, 36: NIP36, 37: NIP37,
 260  	38: NIP38, 39: NIP39, 40: NIP40,
 261  	42: NIP42, 44: NIP44, 45: NIP45, 46: NIP46, 47: NIP47, 48: NIP48, 49: NIP49,
 262  	50: NIP50,
 263  	51: NIP51, 52: NIP52, 53: NIP53, 54: NIP54, 55: NIP55, 56: NIP56, 57: NIP57,
 264  	58: NIP58, 59: NIP59, 60: NIP60,
 265  	61: NIP61, 62: NIP62, 64: NIP64, 65: NIP65, 66: NIP66, 68: NIP68, 69: NIP69,
 266  	70: NIP70,
 267  	71: NIP71, 72: NIP72, 73: NIP73, 75: NIP75, 77: NIP77, 78: NIP78,
 268  	0x7D: NIP7D, 84: NIP84,
 269  	86: NIP86, 87: NIP87, 88: NIP88, 89: NIP89, 90: NIP90, 92: NIP92, 94: NIP94,
 270  	96: NIP96, 98: NIP98, 99: NIP99,
 271  	0xA0: NIPA0, 0xB0: NIPB0, 0xB7: NIPB7, 0xC0: NIPC0, 0xC7: NIPC7,
 272  	0xEE: NIPEE,
 273  }
 274  
 275  // Limits are rules about what is acceptable for events and filters on a relay.
 276  type Limits struct {
 277  	// MaxMessageLength is the maximum number of bytes for incoming JSON that
 278  	// the relay will attempt to decode and act upon. When you send large
 279  	// subscriptions, you will be limited by this value. It also effectively
 280  	// limits the maximum size of any event. Value is calculated from [ to ] and
 281  	// is after UTF-8 serialization (so some Unicode characters will cost 2-3
 282  	// bytes). It is equal to the maximum size of the WebSocket message frame.
 283  	MaxMessageLength int `json:"max_message_length,omitempty"`
 284  	// MaxSubscriptions is the total number of subscriptions that may be active
 285  	// on a single websocket connection to this relay. It's possible that
 286  	// authenticated clients with a (paid) relationship to the relay may have
 287  	// higher limits.
 288  	MaxSubscriptions int `json:"max_subscriptions,omitempty"`
 289  	// MaxFilter is the maximum number of filter values in each subscription.
 290  	// Must be one or higher.
 291  	MaxFilters int `json:"max_filters,omitempty"`
 292  	// MaxLimit is the relay server will clamp each filter's limit value to this
 293  	// number. This means the client won't be able to get more than this number
 294  	// of events from a single subscription filter. This clamping is typically
 295  	// done silently by the relay, but with this number, you can know that there
 296  	// are additional results if you narrowed your filter's time range or other
 297  	// parameters.
 298  	MaxLimit int `json:"max_limit,omitempty"`
 299  	// MaxSubidLength is the maximum length of subscription id as a string.
 300  	MaxSubidLength int `json:"max_subid_length,omitempty"`
 301  	// MaxEventTags in any event, this is the maximum number of elements in the
 302  	// tags list.
 303  	MaxEventTags int `json:"max_event_tags,omitempty"`
 304  	// MaxContentLength maximum number of characters in the content field of any
 305  	// event. This is a count of Unicode characters. After serializing into JSON
 306  	// it may be larger (in bytes), and is still subject to the
 307  	// max_message_length, if defined.
 308  	MaxContentLength int `json:"max_content_length,omitempty"`
 309  	// MinPowDifficulty new events will require at least this difficulty of PoW,
 310  	// based on NIP-13, or they will be rejected by this server.
 311  	MinPowDifficulty int `json:"min_pow_difficulty,omitempty"`
 312  	// AuthRequired means the relay requires NIP-42 authentication to happen
 313  	// before a new connection may perform any other action. Even if set to
 314  	// False, authentication may be required for specific actions.
 315  	AuthRequired bool `json:"auth_required"`
 316  	// PaymentRequired this relay requires payment before a new connection may
 317  	// perform any action.
 318  	PaymentRequired bool `json:"payment_required"`
 319  	// RestrictedWrites means this relay requires some kind of condition to be
 320  	// fulfilled to accept events (not necessarily, but including
 321  	// payment_required and min_pow_difficulty). This should only be set to true
 322  	// when users are expected to know the relay policy before trying to write
 323  	// to it -- like belonging to a special pubkey-based whitelist or writing
 324  	// only events of a specific niche kind or content. Normal anti-spam
 325  	// heuristics, for example, do not qualify.q
 326  	RestrictedWrites bool         `json:"restricted_writes"`
 327  	Oldest           *timestamp.T `json:"created_at_lower_limit,omitempty"`
 328  	Newest           *timestamp.T `json:"created_at_upper_limit,omitempty"`
 329  }
 330  
 331  // Payment is an amount and currency unit name.
 332  type Payment struct {
 333  	Amount int    `json:"amount"`
 334  	Unit   string `json:"unit"`
 335  }
 336  
 337  // Sub is a subscription, with the Payment and the period it yields.
 338  type Sub struct {
 339  	Payment
 340  	Period int `json:"period"`
 341  }
 342  
 343  // Pub is a limitation for what you can store on the relay as a kinds.S and the
 344  // cost (for???).
 345  type Pub struct {
 346  	Kinds kind.S `json:"kinds"`
 347  	Payment
 348  }
 349  
 350  // T is the relay information document.
 351  type T struct {
 352  	Name           string      `json:"name"`
 353  	Description    string      `json:"description,omitempty"`
 354  	PubKey         string      `json:"pubkey,omitempty"`
 355  	Contact        string      `json:"contact,omitempty"`
 356  	Nips           number.List `json:"supported_nips"`
 357  	Software       string      `json:"software"`
 358  	Version        string      `json:"version"`
 359  	Limitation     Limits      `json:"limitation,omitempty"`
 360  	Retention      any         `json:"retention,omitempty"`
 361  	RelayCountries []string    `json:"relay_countries,omitempty"`
 362  	LanguageTags   []string    `json:"language_tags,omitempty"`
 363  	Tags           []string    `json:"tags,omitempty"`
 364  	PostingPolicy  string      `json:"posting_policy,omitempty"`
 365  	PaymentsURL    string      `json:"payments_url,omitempty"`
 366  	Fees           *Fees       `json:"fees,omitempty"`
 367  	Icon           string      `json:"icon"`
 368  	sync.Mutex
 369  }
 370  
 371  // NewInfo populates the nips map, and if an Info structure is provided, it is
 372  // used and its nips map is populated if it isn't already.
 373  func NewInfo(inf *T) (info *T) {
 374  	if inf != nil {
 375  		info = inf
 376  	} else {
 377  		info = &T{
 378  			Limitation: Limits{
 379  				MaxLimit: 500,
 380  			},
 381  		}
 382  	}
 383  	return
 384  }
 385  
 386  // Clone replicates a relayinfo.T.
 387  // todo: this could be done better, but i don't think it's in use.
 388  func (ri *T) Clone() (r2 *T, err error) {
 389  	r2 = new(T)
 390  	var b []byte
 391  	// beware, this will escape <, > and & to unicode escapes but that should be
 392  	// ok since this data is not signed in events until after it is marshaled.
 393  	if b, err = json.Marshal(ri); chk.E(err) {
 394  		return
 395  	}
 396  	if err = json.Unmarshal(b, r2); chk.E(err) {
 397  		return
 398  	}
 399  	return
 400  }
 401  
 402  // AddNIPs adds one or more numbers to the list of NIPs.
 403  func (ri *T) AddNIPs(n ...int) {
 404  	ri.Lock()
 405  	for _, num := range n {
 406  		ri.Nips = append(ri.Nips, num)
 407  	}
 408  	ri.Unlock()
 409  }
 410  
 411  // HasNIP returns true if the given number is found in the list.
 412  func (ri *T) HasNIP(n int) (ok bool) {
 413  	ri.Lock()
 414  	for i := range ri.Nips {
 415  		if ri.Nips[i] == n {
 416  			ok = true
 417  			break
 418  		}
 419  	}
 420  	ri.Unlock()
 421  	return
 422  }
 423  
 424  // Save the relayinfo.T to a given file as JSON.
 425  func (ri *T) Save(filename string) (err error) {
 426  	if ri == nil {
 427  		err = errors.New("cannot save nil relay info document")
 428  		log.E.Ln(err)
 429  		return
 430  	}
 431  	var b []byte
 432  	// beware, this will escape <, > and & to unicode escapes but that should be
 433  	// ok since this data is not signed in events until after it is marshaled.
 434  	if b, err = json.MarshalIndent(ri, "", "    "); chk.E(err) {
 435  		return
 436  	}
 437  	if err = os.WriteFile(filename, b, 0600); chk.E(err) {
 438  		return
 439  	}
 440  	return
 441  }
 442  
 443  // Load a given file and decode the JSON relayinfo.T encoded in it.
 444  func (ri *T) Load(filename string) (err error) {
 445  	if ri == nil {
 446  		err = errors.New("cannot load into nil config")
 447  		log.E.Ln(err)
 448  		return
 449  	}
 450  	var b []byte
 451  	if b, err = os.ReadFile(filename); chk.E(err) {
 452  		return
 453  	}
 454  	// log.S.ToSliceOfBytes("realy information document\n%s", string(b))
 455  	if err = json.Unmarshal(b, ri); chk.E(err) {
 456  		return
 457  	}
 458  	return
 459  }
 460