// Package protocolwire binds the musiquay protocol vocabulary to the web // client's nostr event type. It is the app-side half of what // morly/pkg/relay/protowire is for the native relay: render a payload's tags // as the JSON the signer expects, and read an event's payload back out by // kind. // // The vocabulary is a leaf (protocol imports only runtime, errorf and text), // so this package may import it without dragging anything native into wasm. // nostr/pkg/core is the wasm-safe half of the nostr module; nostr/pkg/event // is not imported here on purpose. package protocolwire import ( "git.smesh.lol/musiquay/pkg/protocol" "git.smesh.lol/musiquay/web/common/helpers" "git.smesh.lol/nostr/pkg/core" "git.smesh.lol/nostr/pkg/lol/errorf" ) // TagsJSON renders a tag list as a JSON array of arrays. An empty or nil list // renders [] rather than nothing, because it sits in an event object beside a // mandatory "tags" key. Every element is escaped by helpers.JsonString, the // same escaper the app's publishers use. func TagsJSON(tags [][]string) (s string) { out := "[" for i := range tags { if i > 0 { out = out | "," } row := tags[i] out = out | "[" for j := range row { if j > 0 { out = out | "," } out = out | helpers.JsonString(row[j]) } out = out | "]" } return out | "]" } // UnsignedJSON renders the object the signer signs, in the exact key order // every existing publisher uses: kind, content, tags, created_at, pubkey. // tagsJSON is already-rendered JSON, so a caller can pass TagsJSON(tags) // directly. func UnsignedJSON(kind uint16, tagsJSON, content, pubkey string, createdAt int64) (s string) { return "{\"kind\":" | helpers.Itoa(int64(kind)) | ",\"content\":" | helpers.JsonString(content) | ",\"tags\":" | tagsJSON | ",\"created_at\":" | helpers.Itoa(createdAt) | ",\"pubkey\":" | helpers.JsonString(pubkey) | "}" } // coreTags copies a nostr tag list into the [][]string shape the protocol // parsers take. The core event tag list is already [][]string; each row is // still copied explicitly so the parser gets its own storage. func coreTags(tags [][]string) (out [][]string) { out = [][]string{:len(tags)} for i := range tags { row := []string{:len(tags[i])} for j := range tags[i] { row[j] = tags[i][j] } out[i] = row } return } // TrackFromCore reads and validates a kind 32210 track payload. func TrackFromCore(ev *nostr.Event) (v *protocol.Track, err error) { if ev == nil { err = errorf.E("nil event") return } if ev.Kind != uint32(protocol.KindTrack) { err = errorf.E("track: event kind %d, want %d", ev.Kind, protocol.KindTrack) return } v, err = protocol.ParseTrack(coreTags(ev.Tags), ev.Content) if err != nil { return } err = v.Validate() return } // AlbumFromCore reads and validates a kind 32211 album payload. func AlbumFromCore(ev *nostr.Event) (v *protocol.Album, err error) { if ev == nil { err = errorf.E("nil event") return } if ev.Kind != uint32(protocol.KindAlbum) { err = errorf.E("album: event kind %d, want %d", ev.Kind, protocol.KindAlbum) return } v, err = protocol.ParseAlbum(coreTags(ev.Tags), ev.Content) if err != nil { return } err = v.Validate() return } // PlaylistFromCore reads and validates a kind 32212 playlist payload. func PlaylistFromCore(ev *nostr.Event) (v *protocol.Playlist, err error) { if ev == nil { err = errorf.E("nil event") return } if ev.Kind != uint32(protocol.KindPlaylist) { err = errorf.E("playlist: event kind %d, want %d", ev.Kind, protocol.KindPlaylist) return } v, err = protocol.ParsePlaylist(coreTags(ev.Tags), ev.Content) if err != nil { return } err = v.Validate() return } // SetlistFromCore reads and validates a kind 32213 setlist payload. func SetlistFromCore(ev *nostr.Event) (v *protocol.Setlist, err error) { if ev == nil { err = errorf.E("nil event") return } if ev.Kind != uint32(protocol.KindSetlist) { err = errorf.E("setlist: event kind %d, want %d", ev.Kind, protocol.KindSetlist) return } v, err = protocol.ParseSetlist(coreTags(ev.Tags), ev.Content) if err != nil { return } err = v.Validate() return } // ReleaseCreditsFromCore reads and validates a kind 32214 release-credits payload. func ReleaseCreditsFromCore(ev *nostr.Event) (v *protocol.ReleaseCredits, err error) { if ev == nil { err = errorf.E("nil event") return } if ev.Kind != uint32(protocol.KindReleaseCredits) { err = errorf.E("release credits: event kind %d, want %d", ev.Kind, protocol.KindReleaseCredits) return } v, err = protocol.ParseReleaseCredits(coreTags(ev.Tags), ev.Content) if err != nil { return } err = v.Validate() return } // TrackCreditsFromCore reads and validates a kind 32215 track-credits payload. func TrackCreditsFromCore(ev *nostr.Event) (v *protocol.TrackCredits, err error) { if ev == nil { err = errorf.E("nil event") return } if ev.Kind != uint32(protocol.KindTrackCredits) { err = errorf.E("track credits: event kind %d, want %d", ev.Kind, protocol.KindTrackCredits) return } v, err = protocol.ParseTrackCredits(coreTags(ev.Tags), ev.Content) if err != nil { return } err = v.Validate() return } // EditionFromCore reads and validates a kind 32216 edition payload. func EditionFromCore(ev *nostr.Event) (v *protocol.Edition, err error) { if ev == nil { err = errorf.E("nil event") return } if ev.Kind != uint32(protocol.KindEdition) { err = errorf.E("edition: event kind %d, want %d", ev.Kind, protocol.KindEdition) return } v, err = protocol.ParseEdition(coreTags(ev.Tags), ev.Content) if err != nil { return } err = v.Validate() return } // AccessFilterFromCore reads and validates a kind 32217 access-filter payload. func AccessFilterFromCore(ev *nostr.Event) (v *protocol.AccessFilter, err error) { if ev == nil { err = errorf.E("nil event") return } if ev.Kind != uint32(protocol.KindAccessFilter) { err = errorf.E("access filter: event kind %d, want %d", ev.Kind, protocol.KindAccessFilter) return } v, err = protocol.ParseAccessFilter(coreTags(ev.Tags), ev.Content) if err != nil { return } err = v.Validate() return } // PlayReportFromCore reads and validates a kind 3221 play-report payload. func PlayReportFromCore(ev *nostr.Event) (v *protocol.PlayReport, err error) { if ev == nil { err = errorf.E("nil event") return } if ev.Kind != uint32(protocol.KindPlayReport) { err = errorf.E("play report: event kind %d, want %d", ev.Kind, protocol.KindPlayReport) return } v, err = protocol.ParsePlayReport(coreTags(ev.Tags), ev.Content) if err != nil { return } err = v.Validate() return } // PurchaseOrderFromCore reads and validates a kind 3222 purchase order. The // order travels only inside a NIP-59 wrap, so it is never an event's own kind // on a relay; the caller unwraps first. func PurchaseOrderFromCore(ev *nostr.Event) (v *protocol.PurchaseOrder, err error) { if ev == nil { err = errorf.E("nil event") return } if ev.Kind != uint32(protocol.KindPurchaseOrder) { err = errorf.E("purchase order: event kind %d, want %d", ev.Kind, protocol.KindPurchaseOrder) return } v, err = protocol.ParsePurchaseOrder(coreTags(ev.Tags), ev.Content) if err != nil { return } err = v.Validate() return } // DeliveryReceiptFromCore reads and validates a kind 32218 delivery receipt. // Like the purchase order it arrives inside a wrap, not as a bare event. func DeliveryReceiptFromCore(ev *nostr.Event) (v *protocol.DeliveryReceipt, err error) { if ev == nil { err = errorf.E("nil event") return } if ev.Kind != uint32(protocol.KindDeliveryReceipt) { err = errorf.E("delivery receipt: event kind %d, want %d", ev.Kind, protocol.KindDeliveryReceipt) return } v, err = protocol.ParseDeliveryReceipt(coreTags(ev.Tags), ev.Content) if err != nil { return } err = v.Validate() return } // HostAggregateFromCore reads and validates a kind 32219 host aggregate. func HostAggregateFromCore(ev *nostr.Event) (v *protocol.HostAggregate, err error) { if ev == nil { err = errorf.E("nil event") return } if ev.Kind != uint32(protocol.KindHostAggregate) { err = errorf.E("host aggregate: event kind %d, want %d", ev.Kind, protocol.KindHostAggregate) return } v, err = protocol.ParseHostAggregate(coreTags(ev.Tags), ev.Content) if err != nil { return } err = v.Validate() return } // PublisherRollupFromCore reads and validates a kind 32220 publisher rollup. func PublisherRollupFromCore(ev *nostr.Event) (v *protocol.PublisherRollup, err error) { if ev == nil { err = errorf.E("nil event") return } if ev.Kind != uint32(protocol.KindPublisherRollup) { err = errorf.E("publisher rollup: event kind %d, want %d", ev.Kind, protocol.KindPublisherRollup) return } v, err = protocol.ParsePublisherRollup(coreTags(ev.Tags), ev.Content) if err != nil { return } err = v.Validate() return } // CreditLabelFromCore reads and validates a NIP-32 kind 1985 credit label. func CreditLabelFromCore(ev *nostr.Event) (v *protocol.CreditLabel, err error) { if ev == nil { err = errorf.E("nil event") return } if ev.Kind != uint32(protocol.KindCreditLabel) { err = errorf.E("credit label: event kind %d, want %d", ev.Kind, protocol.KindCreditLabel) return } v, err = protocol.ParseCreditLabel(coreTags(ev.Tags), ev.Content) if err != nil { return } err = v.Validate() return } // ContributionFromCore reads and validates a NIP-22 kind 1111 contribution. func ContributionFromCore(ev *nostr.Event) (v *protocol.Contribution, err error) { if ev == nil { err = errorf.E("nil event") return } if ev.Kind != uint32(protocol.KindComment) { err = errorf.E("contribution: event kind %d, want %d", ev.Kind, protocol.KindComment) return } v, err = protocol.ParseContribution(coreTags(ev.Tags), ev.Content) if err != nil { return } err = v.Validate() return } // CalendarEventFromCore reads and validates a NIP-52 calendar event. Kind // 31923 is the time-based form and kind 31922 the date-based form; the same // struct serves both, told apart by whether start/end are set. func CalendarEventFromCore(ev *nostr.Event) (v *protocol.CalendarEvent, err error) { if ev == nil { err = errorf.E("nil event") return } if ev.Kind != uint32(protocol.KindCalendar) && ev.Kind != uint32(protocol.KindCalendarDay) { err = errorf.E("calendar event: event kind %d, want %d or %d", ev.Kind, protocol.KindCalendar, protocol.KindCalendarDay) return } v, err = protocol.ParseCalendarEvent(coreTags(ev.Tags), ev.Content) if err != nil { return } err = v.Validate() return } // RSVPFromCore reads and validates a NIP-52 kind 31925 RSVP or waitlist entry. func RSVPFromCore(ev *nostr.Event) (v *protocol.RSVP, err error) { if ev == nil { err = errorf.E("nil event") return } if ev.Kind != uint32(protocol.KindRSVP) { err = errorf.E("rsvp: event kind %d, want %d", ev.Kind, protocol.KindRSVP) return } v, err = protocol.ParseRSVP(coreTags(ev.Tags), ev.Content) if err != nil { return } err = v.Validate() return } // StallFromCore reads and validates a NIP-15 kind 30017 stall. The payload is // JSON in content, so the tag list carries nothing. func StallFromCore(ev *nostr.Event) (v *protocol.Stall, err error) { if ev == nil { err = errorf.E("nil event") return } if ev.Kind != uint32(protocol.KindStall) { err = errorf.E("stall: event kind %d, want %d", ev.Kind, protocol.KindStall) return } v, err = protocol.ParseStall(ev.Content) if err != nil { return } err = v.Validate() return } // ProductFromCore reads and validates a NIP-15 kind 30018 product. The payload // is JSON in content, so the tag list carries only the d tag. func ProductFromCore(ev *nostr.Event) (v *protocol.Product, err error) { if ev == nil { err = errorf.E("nil event") return } if ev.Kind != uint32(protocol.KindProduct) { err = errorf.E("product: event kind %d, want %d", ev.Kind, protocol.KindProduct) return } v, err = protocol.ParseProduct(ev.Content) if err != nil { return } err = v.Validate() return } // ListingFromCore reads and validates a NIP-99 classified listing. Kind 30403 // is the draft/closed form of the same payload. func ListingFromCore(ev *nostr.Event) (v *protocol.Listing, err error) { if ev == nil { err = errorf.E("nil event") return } if ev.Kind != uint32(protocol.KindListing) && ev.Kind != uint32(protocol.KindListingOff) { err = errorf.E("listing: event kind %d, want %d or %d", ev.Kind, protocol.KindListing, protocol.KindListingOff) return } v, err = protocol.ParseListing(coreTags(ev.Tags), ev.Content) if err != nil { return } err = v.Validate() return }