protocolwire.mx raw

   1  // Package protocolwire binds the musiquay protocol vocabulary to the web
   2  // client's nostr event type. It is the app-side half of what
   3  // morly/pkg/relay/protowire is for the native relay: render a payload's tags
   4  // as the JSON the signer expects, and read an event's payload back out by
   5  // kind.
   6  //
   7  // The vocabulary is a leaf (protocol imports only runtime, errorf and text),
   8  // so this package may import it without dragging anything native into wasm.
   9  // nostr/pkg/core is the wasm-safe half of the nostr module; nostr/pkg/event
  10  // is not imported here on purpose.
  11  package protocolwire
  12  
  13  import (
  14  	"git.smesh.lol/musiquay/pkg/protocol"
  15  	"git.smesh.lol/musiquay/web/common/helpers"
  16  	"git.smesh.lol/nostr/pkg/core"
  17  	"git.smesh.lol/nostr/pkg/lol/errorf"
  18  )
  19  
  20  // TagsJSON renders a tag list as a JSON array of arrays. An empty or nil list
  21  // renders [] rather than nothing, because it sits in an event object beside a
  22  // mandatory "tags" key. Every element is escaped by helpers.JsonString, the
  23  // same escaper the app's publishers use.
  24  func TagsJSON(tags [][]string) (s string) {
  25  	out := "["
  26  	for i := range tags {
  27  		if i > 0 {
  28  			out = out | ","
  29  		}
  30  		row := tags[i]
  31  		out = out | "["
  32  		for j := range row {
  33  			if j > 0 {
  34  				out = out | ","
  35  			}
  36  			out = out | helpers.JsonString(row[j])
  37  		}
  38  		out = out | "]"
  39  	}
  40  	return out | "]"
  41  }
  42  
  43  // UnsignedJSON renders the object the signer signs, in the exact key order
  44  // every existing publisher uses: kind, content, tags, created_at, pubkey.
  45  // tagsJSON is already-rendered JSON, so a caller can pass TagsJSON(tags)
  46  // directly.
  47  func UnsignedJSON(kind uint16, tagsJSON, content, pubkey string, createdAt int64) (s string) {
  48  	return "{\"kind\":" | helpers.Itoa(int64(kind)) |
  49  		",\"content\":" | helpers.JsonString(content) |
  50  		",\"tags\":" | tagsJSON |
  51  		",\"created_at\":" | helpers.Itoa(createdAt) |
  52  		",\"pubkey\":" | helpers.JsonString(pubkey) | "}"
  53  }
  54  
  55  // coreTags copies a nostr tag list into the [][]string shape the protocol
  56  // parsers take. The core event tag list is already [][]string; each row is
  57  // still copied explicitly so the parser gets its own storage.
  58  func coreTags(tags [][]string) (out [][]string) {
  59  	out = [][]string{:len(tags)}
  60  	for i := range tags {
  61  		row := []string{:len(tags[i])}
  62  		for j := range tags[i] {
  63  			row[j] = tags[i][j]
  64  		}
  65  		out[i] = row
  66  	}
  67  	return
  68  }
  69  
  70  // TrackFromCore reads and validates a kind 32210 track payload.
  71  func TrackFromCore(ev *nostr.Event) (v *protocol.Track, err error) {
  72  	if ev == nil {
  73  		err = errorf.E("nil event")
  74  		return
  75  	}
  76  	if ev.Kind != uint32(protocol.KindTrack) {
  77  		err = errorf.E("track: event kind %d, want %d", ev.Kind, protocol.KindTrack)
  78  		return
  79  	}
  80  	v, err = protocol.ParseTrack(coreTags(ev.Tags), ev.Content)
  81  	if err != nil {
  82  		return
  83  	}
  84  	err = v.Validate()
  85  	return
  86  }
  87  
  88  // AlbumFromCore reads and validates a kind 32211 album payload.
  89  func AlbumFromCore(ev *nostr.Event) (v *protocol.Album, err error) {
  90  	if ev == nil {
  91  		err = errorf.E("nil event")
  92  		return
  93  	}
  94  	if ev.Kind != uint32(protocol.KindAlbum) {
  95  		err = errorf.E("album: event kind %d, want %d", ev.Kind, protocol.KindAlbum)
  96  		return
  97  	}
  98  	v, err = protocol.ParseAlbum(coreTags(ev.Tags), ev.Content)
  99  	if err != nil {
 100  		return
 101  	}
 102  	err = v.Validate()
 103  	return
 104  }
 105  
 106  // PlaylistFromCore reads and validates a kind 32212 playlist payload.
 107  func PlaylistFromCore(ev *nostr.Event) (v *protocol.Playlist, err error) {
 108  	if ev == nil {
 109  		err = errorf.E("nil event")
 110  		return
 111  	}
 112  	if ev.Kind != uint32(protocol.KindPlaylist) {
 113  		err = errorf.E("playlist: event kind %d, want %d", ev.Kind, protocol.KindPlaylist)
 114  		return
 115  	}
 116  	v, err = protocol.ParsePlaylist(coreTags(ev.Tags), ev.Content)
 117  	if err != nil {
 118  		return
 119  	}
 120  	err = v.Validate()
 121  	return
 122  }
 123  
 124  // SetlistFromCore reads and validates a kind 32213 setlist payload.
 125  func SetlistFromCore(ev *nostr.Event) (v *protocol.Setlist, err error) {
 126  	if ev == nil {
 127  		err = errorf.E("nil event")
 128  		return
 129  	}
 130  	if ev.Kind != uint32(protocol.KindSetlist) {
 131  		err = errorf.E("setlist: event kind %d, want %d", ev.Kind, protocol.KindSetlist)
 132  		return
 133  	}
 134  	v, err = protocol.ParseSetlist(coreTags(ev.Tags), ev.Content)
 135  	if err != nil {
 136  		return
 137  	}
 138  	err = v.Validate()
 139  	return
 140  }
 141  
 142  // ReleaseCreditsFromCore reads and validates a kind 32214 release-credits payload.
 143  func ReleaseCreditsFromCore(ev *nostr.Event) (v *protocol.ReleaseCredits, err error) {
 144  	if ev == nil {
 145  		err = errorf.E("nil event")
 146  		return
 147  	}
 148  	if ev.Kind != uint32(protocol.KindReleaseCredits) {
 149  		err = errorf.E("release credits: event kind %d, want %d", ev.Kind, protocol.KindReleaseCredits)
 150  		return
 151  	}
 152  	v, err = protocol.ParseReleaseCredits(coreTags(ev.Tags), ev.Content)
 153  	if err != nil {
 154  		return
 155  	}
 156  	err = v.Validate()
 157  	return
 158  }
 159  
 160  // TrackCreditsFromCore reads and validates a kind 32215 track-credits payload.
 161  func TrackCreditsFromCore(ev *nostr.Event) (v *protocol.TrackCredits, err error) {
 162  	if ev == nil {
 163  		err = errorf.E("nil event")
 164  		return
 165  	}
 166  	if ev.Kind != uint32(protocol.KindTrackCredits) {
 167  		err = errorf.E("track credits: event kind %d, want %d", ev.Kind, protocol.KindTrackCredits)
 168  		return
 169  	}
 170  	v, err = protocol.ParseTrackCredits(coreTags(ev.Tags), ev.Content)
 171  	if err != nil {
 172  		return
 173  	}
 174  	err = v.Validate()
 175  	return
 176  }
 177  
 178  // EditionFromCore reads and validates a kind 32216 edition payload.
 179  func EditionFromCore(ev *nostr.Event) (v *protocol.Edition, err error) {
 180  	if ev == nil {
 181  		err = errorf.E("nil event")
 182  		return
 183  	}
 184  	if ev.Kind != uint32(protocol.KindEdition) {
 185  		err = errorf.E("edition: event kind %d, want %d", ev.Kind, protocol.KindEdition)
 186  		return
 187  	}
 188  	v, err = protocol.ParseEdition(coreTags(ev.Tags), ev.Content)
 189  	if err != nil {
 190  		return
 191  	}
 192  	err = v.Validate()
 193  	return
 194  }
 195  
 196  // AccessFilterFromCore reads and validates a kind 32217 access-filter payload.
 197  func AccessFilterFromCore(ev *nostr.Event) (v *protocol.AccessFilter, err error) {
 198  	if ev == nil {
 199  		err = errorf.E("nil event")
 200  		return
 201  	}
 202  	if ev.Kind != uint32(protocol.KindAccessFilter) {
 203  		err = errorf.E("access filter: event kind %d, want %d", ev.Kind, protocol.KindAccessFilter)
 204  		return
 205  	}
 206  	v, err = protocol.ParseAccessFilter(coreTags(ev.Tags), ev.Content)
 207  	if err != nil {
 208  		return
 209  	}
 210  	err = v.Validate()
 211  	return
 212  }
 213  
 214  // PlayReportFromCore reads and validates a kind 3221 play-report payload.
 215  func PlayReportFromCore(ev *nostr.Event) (v *protocol.PlayReport, err error) {
 216  	if ev == nil {
 217  		err = errorf.E("nil event")
 218  		return
 219  	}
 220  	if ev.Kind != uint32(protocol.KindPlayReport) {
 221  		err = errorf.E("play report: event kind %d, want %d", ev.Kind, protocol.KindPlayReport)
 222  		return
 223  	}
 224  	v, err = protocol.ParsePlayReport(coreTags(ev.Tags), ev.Content)
 225  	if err != nil {
 226  		return
 227  	}
 228  	err = v.Validate()
 229  	return
 230  }
 231  
 232  // PurchaseOrderFromCore reads and validates a kind 3222 purchase order. The
 233  // order travels only inside a NIP-59 wrap, so it is never an event's own kind
 234  // on a relay; the caller unwraps first.
 235  func PurchaseOrderFromCore(ev *nostr.Event) (v *protocol.PurchaseOrder, err error) {
 236  	if ev == nil {
 237  		err = errorf.E("nil event")
 238  		return
 239  	}
 240  	if ev.Kind != uint32(protocol.KindPurchaseOrder) {
 241  		err = errorf.E("purchase order: event kind %d, want %d", ev.Kind, protocol.KindPurchaseOrder)
 242  		return
 243  	}
 244  	v, err = protocol.ParsePurchaseOrder(coreTags(ev.Tags), ev.Content)
 245  	if err != nil {
 246  		return
 247  	}
 248  	err = v.Validate()
 249  	return
 250  }
 251  
 252  // DeliveryReceiptFromCore reads and validates a kind 32218 delivery receipt.
 253  // Like the purchase order it arrives inside a wrap, not as a bare event.
 254  func DeliveryReceiptFromCore(ev *nostr.Event) (v *protocol.DeliveryReceipt, err error) {
 255  	if ev == nil {
 256  		err = errorf.E("nil event")
 257  		return
 258  	}
 259  	if ev.Kind != uint32(protocol.KindDeliveryReceipt) {
 260  		err = errorf.E("delivery receipt: event kind %d, want %d", ev.Kind, protocol.KindDeliveryReceipt)
 261  		return
 262  	}
 263  	v, err = protocol.ParseDeliveryReceipt(coreTags(ev.Tags), ev.Content)
 264  	if err != nil {
 265  		return
 266  	}
 267  	err = v.Validate()
 268  	return
 269  }
 270  
 271  // HostAggregateFromCore reads and validates a kind 32219 host aggregate.
 272  func HostAggregateFromCore(ev *nostr.Event) (v *protocol.HostAggregate, err error) {
 273  	if ev == nil {
 274  		err = errorf.E("nil event")
 275  		return
 276  	}
 277  	if ev.Kind != uint32(protocol.KindHostAggregate) {
 278  		err = errorf.E("host aggregate: event kind %d, want %d", ev.Kind, protocol.KindHostAggregate)
 279  		return
 280  	}
 281  	v, err = protocol.ParseHostAggregate(coreTags(ev.Tags), ev.Content)
 282  	if err != nil {
 283  		return
 284  	}
 285  	err = v.Validate()
 286  	return
 287  }
 288  
 289  // PublisherRollupFromCore reads and validates a kind 32220 publisher rollup.
 290  func PublisherRollupFromCore(ev *nostr.Event) (v *protocol.PublisherRollup, err error) {
 291  	if ev == nil {
 292  		err = errorf.E("nil event")
 293  		return
 294  	}
 295  	if ev.Kind != uint32(protocol.KindPublisherRollup) {
 296  		err = errorf.E("publisher rollup: event kind %d, want %d", ev.Kind, protocol.KindPublisherRollup)
 297  		return
 298  	}
 299  	v, err = protocol.ParsePublisherRollup(coreTags(ev.Tags), ev.Content)
 300  	if err != nil {
 301  		return
 302  	}
 303  	err = v.Validate()
 304  	return
 305  }
 306  
 307  // CreditLabelFromCore reads and validates a NIP-32 kind 1985 credit label.
 308  func CreditLabelFromCore(ev *nostr.Event) (v *protocol.CreditLabel, err error) {
 309  	if ev == nil {
 310  		err = errorf.E("nil event")
 311  		return
 312  	}
 313  	if ev.Kind != uint32(protocol.KindCreditLabel) {
 314  		err = errorf.E("credit label: event kind %d, want %d", ev.Kind, protocol.KindCreditLabel)
 315  		return
 316  	}
 317  	v, err = protocol.ParseCreditLabel(coreTags(ev.Tags), ev.Content)
 318  	if err != nil {
 319  		return
 320  	}
 321  	err = v.Validate()
 322  	return
 323  }
 324  
 325  // ContributionFromCore reads and validates a NIP-22 kind 1111 contribution.
 326  func ContributionFromCore(ev *nostr.Event) (v *protocol.Contribution, err error) {
 327  	if ev == nil {
 328  		err = errorf.E("nil event")
 329  		return
 330  	}
 331  	if ev.Kind != uint32(protocol.KindComment) {
 332  		err = errorf.E("contribution: event kind %d, want %d", ev.Kind, protocol.KindComment)
 333  		return
 334  	}
 335  	v, err = protocol.ParseContribution(coreTags(ev.Tags), ev.Content)
 336  	if err != nil {
 337  		return
 338  	}
 339  	err = v.Validate()
 340  	return
 341  }
 342  
 343  // CalendarEventFromCore reads and validates a NIP-52 calendar event. Kind
 344  // 31923 is the time-based form and kind 31922 the date-based form; the same
 345  // struct serves both, told apart by whether start/end are set.
 346  func CalendarEventFromCore(ev *nostr.Event) (v *protocol.CalendarEvent, err error) {
 347  	if ev == nil {
 348  		err = errorf.E("nil event")
 349  		return
 350  	}
 351  	if ev.Kind != uint32(protocol.KindCalendar) && ev.Kind != uint32(protocol.KindCalendarDay) {
 352  		err = errorf.E("calendar event: event kind %d, want %d or %d", ev.Kind, protocol.KindCalendar, protocol.KindCalendarDay)
 353  		return
 354  	}
 355  	v, err = protocol.ParseCalendarEvent(coreTags(ev.Tags), ev.Content)
 356  	if err != nil {
 357  		return
 358  	}
 359  	err = v.Validate()
 360  	return
 361  }
 362  
 363  // RSVPFromCore reads and validates a NIP-52 kind 31925 RSVP or waitlist entry.
 364  func RSVPFromCore(ev *nostr.Event) (v *protocol.RSVP, err error) {
 365  	if ev == nil {
 366  		err = errorf.E("nil event")
 367  		return
 368  	}
 369  	if ev.Kind != uint32(protocol.KindRSVP) {
 370  		err = errorf.E("rsvp: event kind %d, want %d", ev.Kind, protocol.KindRSVP)
 371  		return
 372  	}
 373  	v, err = protocol.ParseRSVP(coreTags(ev.Tags), ev.Content)
 374  	if err != nil {
 375  		return
 376  	}
 377  	err = v.Validate()
 378  	return
 379  }
 380  
 381  // StallFromCore reads and validates a NIP-15 kind 30017 stall. The payload is
 382  // JSON in content, so the tag list carries nothing.
 383  func StallFromCore(ev *nostr.Event) (v *protocol.Stall, err error) {
 384  	if ev == nil {
 385  		err = errorf.E("nil event")
 386  		return
 387  	}
 388  	if ev.Kind != uint32(protocol.KindStall) {
 389  		err = errorf.E("stall: event kind %d, want %d", ev.Kind, protocol.KindStall)
 390  		return
 391  	}
 392  	v, err = protocol.ParseStall(ev.Content)
 393  	if err != nil {
 394  		return
 395  	}
 396  	err = v.Validate()
 397  	return
 398  }
 399  
 400  // ProductFromCore reads and validates a NIP-15 kind 30018 product. The payload
 401  // is JSON in content, so the tag list carries only the d tag.
 402  func ProductFromCore(ev *nostr.Event) (v *protocol.Product, err error) {
 403  	if ev == nil {
 404  		err = errorf.E("nil event")
 405  		return
 406  	}
 407  	if ev.Kind != uint32(protocol.KindProduct) {
 408  		err = errorf.E("product: event kind %d, want %d", ev.Kind, protocol.KindProduct)
 409  		return
 410  	}
 411  	v, err = protocol.ParseProduct(ev.Content)
 412  	if err != nil {
 413  		return
 414  	}
 415  	err = v.Validate()
 416  	return
 417  }
 418  
 419  // ListingFromCore reads and validates a NIP-99 classified listing. Kind 30403
 420  // is the draft/closed form of the same payload.
 421  func ListingFromCore(ev *nostr.Event) (v *protocol.Listing, err error) {
 422  	if ev == nil {
 423  		err = errorf.E("nil event")
 424  		return
 425  	}
 426  	if ev.Kind != uint32(protocol.KindListing) && ev.Kind != uint32(protocol.KindListingOff) {
 427  		err = errorf.E("listing: event kind %d, want %d or %d", ev.Kind, protocol.KindListing, protocol.KindListingOff)
 428  		return
 429  	}
 430  	v, err = protocol.ParseListing(coreTags(ev.Tags), ev.Content)
 431  	if err != nil {
 432  		return
 433  	}
 434  	err = v.Validate()
 435  	return
 436  }
 437