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