+7
-1
README.md
+7
-1
README.md
···
72
72
73
73
air should automatically build and run piper, and watch for changes on relevant files.
74
74
75
+
#### Lexicon changes
76
+
1. Copy the new or changed json schema files to the [lexicon folders](./lexicons)
77
+
2. run `make go-lexicons`
78
+
79
+
Go types should be updated and should have the changes to the schemas
80
+
75
81
#### docker
76
82
We also provide a docker compose file to use to run piper locally. There are a few edits to the [.env](.env) to make it run smoother in a container
77
83
`SERVER_HOST`- `0.0.0.0`
78
84
`DB_PATH` = `/db/piper.db` to persist your piper db through container restarts
79
85
80
-
Make sure you have docker and docker compose installed, then you can run piper with `docker compose up`
86
+
Make sure you have docker and docker compose installed, then you can run piper with `docker compose up`
+346
-177
api/teal/cbor_gen.go
+346
-177
api/teal/cbor_gen.go
···
27
27
}
28
28
29
29
cw := cbg.NewCborWriter(w)
30
-
fieldCount := 14
30
+
fieldCount := 15
31
31
32
32
if t.ArtistMbIds == nil {
33
+
fieldCount--
34
+
}
35
+
36
+
if t.ArtistNames == nil {
37
+
fieldCount--
38
+
}
39
+
40
+
if t.Artists == nil {
33
41
fieldCount--
34
42
}
35
43
···
126
134
}
127
135
if _, err := cw.WriteString(string("fm.teal.alpha.feed.play")); err != nil {
128
136
return err
137
+
}
138
+
139
+
// t.Artists ([]*teal.AlphaFeedDefs_Artist) (slice)
140
+
if t.Artists != nil {
141
+
142
+
if len("artists") > 1000000 {
143
+
return xerrors.Errorf("Value in field \"artists\" was too long")
144
+
}
145
+
146
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("artists"))); err != nil {
147
+
return err
148
+
}
149
+
if _, err := cw.WriteString(string("artists")); err != nil {
150
+
return err
151
+
}
152
+
153
+
if len(t.Artists) > 8192 {
154
+
return xerrors.Errorf("Slice value in field t.Artists was too long")
155
+
}
156
+
157
+
if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Artists))); err != nil {
158
+
return err
159
+
}
160
+
for _, v := range t.Artists {
161
+
if err := v.MarshalCBOR(cw); err != nil {
162
+
return err
163
+
}
164
+
165
+
}
129
166
}
130
167
131
168
// t.Duration (int64) (int64)
···
316
353
}
317
354
318
355
// t.ArtistNames ([]string) (slice)
319
-
if len("artistNames") > 1000000 {
320
-
return xerrors.Errorf("Value in field \"artistNames\" was too long")
321
-
}
356
+
if t.ArtistNames != nil {
322
357
323
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("artistNames"))); err != nil {
324
-
return err
325
-
}
326
-
if _, err := cw.WriteString(string("artistNames")); err != nil {
327
-
return err
328
-
}
358
+
if len("artistNames") > 1000000 {
359
+
return xerrors.Errorf("Value in field \"artistNames\" was too long")
360
+
}
329
361
330
-
if len(t.ArtistNames) > 8192 {
331
-
return xerrors.Errorf("Slice value in field t.ArtistNames was too long")
332
-
}
362
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("artistNames"))); err != nil {
363
+
return err
364
+
}
365
+
if _, err := cw.WriteString(string("artistNames")); err != nil {
366
+
return err
367
+
}
333
368
334
-
if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.ArtistNames))); err != nil {
335
-
return err
336
-
}
337
-
for _, v := range t.ArtistNames {
338
-
if len(v) > 1000000 {
339
-
return xerrors.Errorf("Value in field v was too long")
369
+
if len(t.ArtistNames) > 8192 {
370
+
return xerrors.Errorf("Slice value in field t.ArtistNames was too long")
340
371
}
341
372
342
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil {
373
+
if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.ArtistNames))); err != nil {
343
374
return err
344
375
}
345
-
if _, err := cw.WriteString(string(v)); err != nil {
346
-
return err
347
-
}
376
+
for _, v := range t.ArtistNames {
377
+
if len(v) > 1000000 {
378
+
return xerrors.Errorf("Value in field v was too long")
379
+
}
348
380
381
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil {
382
+
return err
383
+
}
384
+
if _, err := cw.WriteString(string(v)); err != nil {
385
+
return err
386
+
}
387
+
388
+
}
349
389
}
350
390
351
391
// t.ReleaseMbId (string) (string)
···
582
622
}
583
623
584
624
t.LexiconTypeID = string(sval)
625
+
}
626
+
// t.Artists ([]*teal.AlphaFeedDefs_Artist) (slice)
627
+
case "artists":
628
+
629
+
maj, extra, err = cr.ReadHeader()
630
+
if err != nil {
631
+
return err
632
+
}
633
+
634
+
if extra > 8192 {
635
+
return fmt.Errorf("t.Artists: array too large (%d)", extra)
636
+
}
637
+
638
+
if maj != cbg.MajArray {
639
+
return fmt.Errorf("expected cbor array")
640
+
}
641
+
642
+
if extra > 0 {
643
+
t.Artists = make([]*AlphaFeedDefs_Artist, extra)
644
+
}
645
+
646
+
for i := 0; i < int(extra); i++ {
647
+
{
648
+
var maj byte
649
+
var extra uint64
650
+
var err error
651
+
_ = maj
652
+
_ = extra
653
+
_ = err
654
+
655
+
{
656
+
657
+
b, err := cr.ReadByte()
658
+
if err != nil {
659
+
return err
660
+
}
661
+
if b != cbg.CborNull[0] {
662
+
if err := cr.UnreadByte(); err != nil {
663
+
return err
664
+
}
665
+
t.Artists[i] = new(AlphaFeedDefs_Artist)
666
+
if err := t.Artists[i].UnmarshalCBOR(cr); err != nil {
667
+
return xerrors.Errorf("unmarshaling t.Artists[i] pointer: %w", err)
668
+
}
669
+
}
670
+
671
+
}
672
+
673
+
}
585
674
}
586
675
// t.Duration (int64) (int64)
587
676
case "duration":
···
1733
1822
}
1734
1823
1735
1824
cw := cbg.NewCborWriter(w)
1736
-
fieldCount := 13
1737
-
1738
-
if t.ArtistMbIds == nil {
1739
-
fieldCount--
1740
-
}
1825
+
fieldCount := 12
1741
1826
1742
1827
if t.Duration == nil {
1743
1828
fieldCount--
···
1813
1898
return err
1814
1899
}
1815
1900
}
1901
+
}
1902
+
1903
+
// t.Artists ([]*teal.AlphaFeedDefs_Artist) (slice)
1904
+
if len("artists") > 1000000 {
1905
+
return xerrors.Errorf("Value in field \"artists\" was too long")
1906
+
}
1907
+
1908
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("artists"))); err != nil {
1909
+
return err
1910
+
}
1911
+
if _, err := cw.WriteString(string("artists")); err != nil {
1912
+
return err
1913
+
}
1914
+
1915
+
if len(t.Artists) > 8192 {
1916
+
return xerrors.Errorf("Slice value in field t.Artists was too long")
1917
+
}
1918
+
1919
+
if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Artists))); err != nil {
1920
+
return err
1921
+
}
1922
+
for _, v := range t.Artists {
1923
+
if err := v.MarshalCBOR(cw); err != nil {
1924
+
return err
1925
+
}
1926
+
1816
1927
}
1817
1928
1818
1929
// t.Duration (int64) (int64)
···
1966
2077
}
1967
2078
}
1968
2079
1969
-
// t.ArtistMbIds ([]string) (slice)
1970
-
if t.ArtistMbIds != nil {
1971
-
1972
-
if len("artistMbIds") > 1000000 {
1973
-
return xerrors.Errorf("Value in field \"artistMbIds\" was too long")
1974
-
}
1975
-
1976
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("artistMbIds"))); err != nil {
1977
-
return err
1978
-
}
1979
-
if _, err := cw.WriteString(string("artistMbIds")); err != nil {
1980
-
return err
1981
-
}
1982
-
1983
-
if len(t.ArtistMbIds) > 8192 {
1984
-
return xerrors.Errorf("Slice value in field t.ArtistMbIds was too long")
1985
-
}
1986
-
1987
-
if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.ArtistMbIds))); err != nil {
1988
-
return err
1989
-
}
1990
-
for _, v := range t.ArtistMbIds {
1991
-
if len(v) > 1000000 {
1992
-
return xerrors.Errorf("Value in field v was too long")
1993
-
}
1994
-
1995
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil {
1996
-
return err
1997
-
}
1998
-
if _, err := cw.WriteString(string(v)); err != nil {
1999
-
return err
2000
-
}
2001
-
2002
-
}
2003
-
}
2004
-
2005
-
// t.ArtistNames ([]string) (slice)
2006
-
if len("artistNames") > 1000000 {
2007
-
return xerrors.Errorf("Value in field \"artistNames\" was too long")
2008
-
}
2009
-
2010
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("artistNames"))); err != nil {
2011
-
return err
2012
-
}
2013
-
if _, err := cw.WriteString(string("artistNames")); err != nil {
2014
-
return err
2015
-
}
2016
-
2017
-
if len(t.ArtistNames) > 8192 {
2018
-
return xerrors.Errorf("Slice value in field t.ArtistNames was too long")
2019
-
}
2020
-
2021
-
if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.ArtistNames))); err != nil {
2022
-
return err
2023
-
}
2024
-
for _, v := range t.ArtistNames {
2025
-
if len(v) > 1000000 {
2026
-
return xerrors.Errorf("Value in field v was too long")
2027
-
}
2028
-
2029
-
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil {
2030
-
return err
2031
-
}
2032
-
if _, err := cw.WriteString(string(v)); err != nil {
2033
-
return err
2034
-
}
2035
-
2036
-
}
2037
-
2038
2080
// t.ReleaseMbId (string) (string)
2039
2081
if t.ReleaseMbId != nil {
2040
2082
···
2259
2301
t.Isrc = (*string)(&sval)
2260
2302
}
2261
2303
}
2304
+
// t.Artists ([]*teal.AlphaFeedDefs_Artist) (slice)
2305
+
case "artists":
2306
+
2307
+
maj, extra, err = cr.ReadHeader()
2308
+
if err != nil {
2309
+
return err
2310
+
}
2311
+
2312
+
if extra > 8192 {
2313
+
return fmt.Errorf("t.Artists: array too large (%d)", extra)
2314
+
}
2315
+
2316
+
if maj != cbg.MajArray {
2317
+
return fmt.Errorf("expected cbor array")
2318
+
}
2319
+
2320
+
if extra > 0 {
2321
+
t.Artists = make([]*AlphaFeedDefs_Artist, extra)
2322
+
}
2323
+
2324
+
for i := 0; i < int(extra); i++ {
2325
+
{
2326
+
var maj byte
2327
+
var extra uint64
2328
+
var err error
2329
+
_ = maj
2330
+
_ = extra
2331
+
_ = err
2332
+
2333
+
{
2334
+
2335
+
b, err := cr.ReadByte()
2336
+
if err != nil {
2337
+
return err
2338
+
}
2339
+
if b != cbg.CborNull[0] {
2340
+
if err := cr.UnreadByte(); err != nil {
2341
+
return err
2342
+
}
2343
+
t.Artists[i] = new(AlphaFeedDefs_Artist)
2344
+
if err := t.Artists[i].UnmarshalCBOR(cr); err != nil {
2345
+
return xerrors.Errorf("unmarshaling t.Artists[i] pointer: %w", err)
2346
+
}
2347
+
}
2348
+
2349
+
}
2350
+
2351
+
}
2352
+
}
2262
2353
// t.Duration (int64) (int64)
2263
2354
case "duration":
2264
2355
{
···
2369
2460
t.PlayedTime = (*string)(&sval)
2370
2461
}
2371
2462
}
2372
-
// t.ArtistMbIds ([]string) (slice)
2373
-
case "artistMbIds":
2374
-
2375
-
maj, extra, err = cr.ReadHeader()
2376
-
if err != nil {
2377
-
return err
2378
-
}
2379
-
2380
-
if extra > 8192 {
2381
-
return fmt.Errorf("t.ArtistMbIds: array too large (%d)", extra)
2382
-
}
2383
-
2384
-
if maj != cbg.MajArray {
2385
-
return fmt.Errorf("expected cbor array")
2386
-
}
2387
-
2388
-
if extra > 0 {
2389
-
t.ArtistMbIds = make([]string, extra)
2390
-
}
2391
-
2392
-
for i := 0; i < int(extra); i++ {
2393
-
{
2394
-
var maj byte
2395
-
var extra uint64
2396
-
var err error
2397
-
_ = maj
2398
-
_ = extra
2399
-
_ = err
2400
-
2401
-
{
2402
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
2403
-
if err != nil {
2404
-
return err
2405
-
}
2406
-
2407
-
t.ArtistMbIds[i] = string(sval)
2408
-
}
2409
-
2410
-
}
2411
-
}
2412
-
// t.ArtistNames ([]string) (slice)
2413
-
case "artistNames":
2414
-
2415
-
maj, extra, err = cr.ReadHeader()
2416
-
if err != nil {
2417
-
return err
2418
-
}
2419
-
2420
-
if extra > 8192 {
2421
-
return fmt.Errorf("t.ArtistNames: array too large (%d)", extra)
2422
-
}
2423
-
2424
-
if maj != cbg.MajArray {
2425
-
return fmt.Errorf("expected cbor array")
2426
-
}
2427
-
2428
-
if extra > 0 {
2429
-
t.ArtistNames = make([]string, extra)
2430
-
}
2431
-
2432
-
for i := 0; i < int(extra); i++ {
2433
-
{
2434
-
var maj byte
2435
-
var extra uint64
2436
-
var err error
2437
-
_ = maj
2438
-
_ = extra
2439
-
_ = err
2440
-
2441
-
{
2442
-
sval, err := cbg.ReadStringWithMax(cr, 1000000)
2443
-
if err != nil {
2444
-
return err
2445
-
}
2446
-
2447
-
t.ArtistNames[i] = string(sval)
2448
-
}
2449
-
2450
-
}
2451
-
}
2452
2463
// t.ReleaseMbId (string) (string)
2453
2464
case "releaseMbId":
2454
2465
···
2565
2576
2566
2577
return nil
2567
2578
}
2579
+
func (t *AlphaFeedDefs_Artist) MarshalCBOR(w io.Writer) error {
2580
+
if t == nil {
2581
+
_, err := w.Write(cbg.CborNull)
2582
+
return err
2583
+
}
2584
+
2585
+
cw := cbg.NewCborWriter(w)
2586
+
fieldCount := 2
2587
+
2588
+
if t.ArtistMbId == nil {
2589
+
fieldCount--
2590
+
}
2591
+
2592
+
if _, err := cw.Write(cbg.CborEncodeMajorType(cbg.MajMap, uint64(fieldCount))); err != nil {
2593
+
return err
2594
+
}
2595
+
2596
+
// t.ArtistMbId (string) (string)
2597
+
if t.ArtistMbId != nil {
2598
+
2599
+
if len("artistMbId") > 1000000 {
2600
+
return xerrors.Errorf("Value in field \"artistMbId\" was too long")
2601
+
}
2602
+
2603
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("artistMbId"))); err != nil {
2604
+
return err
2605
+
}
2606
+
if _, err := cw.WriteString(string("artistMbId")); err != nil {
2607
+
return err
2608
+
}
2609
+
2610
+
if t.ArtistMbId == nil {
2611
+
if _, err := cw.Write(cbg.CborNull); err != nil {
2612
+
return err
2613
+
}
2614
+
} else {
2615
+
if len(*t.ArtistMbId) > 1000000 {
2616
+
return xerrors.Errorf("Value in field t.ArtistMbId was too long")
2617
+
}
2618
+
2619
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(*t.ArtistMbId))); err != nil {
2620
+
return err
2621
+
}
2622
+
if _, err := cw.WriteString(string(*t.ArtistMbId)); err != nil {
2623
+
return err
2624
+
}
2625
+
}
2626
+
}
2627
+
2628
+
// t.ArtistName (string) (string)
2629
+
if len("artistName") > 1000000 {
2630
+
return xerrors.Errorf("Value in field \"artistName\" was too long")
2631
+
}
2632
+
2633
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len("artistName"))); err != nil {
2634
+
return err
2635
+
}
2636
+
if _, err := cw.WriteString(string("artistName")); err != nil {
2637
+
return err
2638
+
}
2639
+
2640
+
if len(t.ArtistName) > 1000000 {
2641
+
return xerrors.Errorf("Value in field t.ArtistName was too long")
2642
+
}
2643
+
2644
+
if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(t.ArtistName))); err != nil {
2645
+
return err
2646
+
}
2647
+
if _, err := cw.WriteString(string(t.ArtistName)); err != nil {
2648
+
return err
2649
+
}
2650
+
return nil
2651
+
}
2652
+
2653
+
func (t *AlphaFeedDefs_Artist) UnmarshalCBOR(r io.Reader) (err error) {
2654
+
*t = AlphaFeedDefs_Artist{}
2655
+
2656
+
cr := cbg.NewCborReader(r)
2657
+
2658
+
maj, extra, err := cr.ReadHeader()
2659
+
if err != nil {
2660
+
return err
2661
+
}
2662
+
defer func() {
2663
+
if err == io.EOF {
2664
+
err = io.ErrUnexpectedEOF
2665
+
}
2666
+
}()
2667
+
2668
+
if maj != cbg.MajMap {
2669
+
return fmt.Errorf("cbor input should be of type map")
2670
+
}
2671
+
2672
+
if extra > cbg.MaxLength {
2673
+
return fmt.Errorf("AlphaFeedDefs_Artist: map struct too large (%d)", extra)
2674
+
}
2675
+
2676
+
n := extra
2677
+
2678
+
nameBuf := make([]byte, 10)
2679
+
for i := uint64(0); i < n; i++ {
2680
+
nameLen, ok, err := cbg.ReadFullStringIntoBuf(cr, nameBuf, 1000000)
2681
+
if err != nil {
2682
+
return err
2683
+
}
2684
+
2685
+
if !ok {
2686
+
// Field doesn't exist on this type, so ignore it
2687
+
if err := cbg.ScanForLinks(cr, func(cid.Cid) {}); err != nil {
2688
+
return err
2689
+
}
2690
+
continue
2691
+
}
2692
+
2693
+
switch string(nameBuf[:nameLen]) {
2694
+
// t.ArtistMbId (string) (string)
2695
+
case "artistMbId":
2696
+
2697
+
{
2698
+
b, err := cr.ReadByte()
2699
+
if err != nil {
2700
+
return err
2701
+
}
2702
+
if b != cbg.CborNull[0] {
2703
+
if err := cr.UnreadByte(); err != nil {
2704
+
return err
2705
+
}
2706
+
2707
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
2708
+
if err != nil {
2709
+
return err
2710
+
}
2711
+
2712
+
t.ArtistMbId = (*string)(&sval)
2713
+
}
2714
+
}
2715
+
// t.ArtistName (string) (string)
2716
+
case "artistName":
2717
+
2718
+
{
2719
+
sval, err := cbg.ReadStringWithMax(cr, 1000000)
2720
+
if err != nil {
2721
+
return err
2722
+
}
2723
+
2724
+
t.ArtistName = string(sval)
2725
+
}
2726
+
2727
+
default:
2728
+
// Field doesn't exist on this type, so ignore it
2729
+
if err := cbg.ScanForLinks(r, func(cid.Cid) {}); err != nil {
2730
+
return err
2731
+
}
2732
+
}
2733
+
}
2734
+
2735
+
return nil
2736
+
}
+10
-4
api/teal/feeddefs.go
+10
-4
api/teal/feeddefs.go
···
4
4
5
5
// schema: fm.teal.alpha.feed.defs
6
6
7
+
// AlphaFeedDefs_Artist is a "artist" in the fm.teal.alpha.feed.defs schema.
8
+
type AlphaFeedDefs_Artist struct {
9
+
// artistMbId: The Musicbrainz ID of the artist
10
+
ArtistMbId *string `json:"artistMbId,omitempty" cborgen:"artistMbId,omitempty"`
11
+
// artistName: The name of the artist
12
+
ArtistName string `json:"artistName" cborgen:"artistName"`
13
+
}
14
+
7
15
// AlphaFeedDefs_PlayView is a "playView" in the fm.teal.alpha.feed.defs schema.
8
16
type AlphaFeedDefs_PlayView struct {
9
-
// artistMbIds: Array of Musicbrainz artist IDs
10
-
ArtistMbIds []string `json:"artistMbIds,omitempty" cborgen:"artistMbIds,omitempty"`
11
-
// artistNames: Array of artist names in order of original appearance.
12
-
ArtistNames []string `json:"artistNames" cborgen:"artistNames"`
17
+
// artists: Array of artists in order of original appearance.
18
+
Artists []*AlphaFeedDefs_Artist `json:"artists" cborgen:"artists"`
13
19
// duration: The length of the track in seconds
14
20
Duration *int64 `json:"duration,omitempty" cborgen:"duration,omitempty"`
15
21
// isrc: The ISRC code associated with the recording
+5
-3
api/teal/feedplay.go
+5
-3
api/teal/feedplay.go
···
14
14
// RECORDTYPE: AlphaFeedPlay
15
15
type AlphaFeedPlay struct {
16
16
LexiconTypeID string `json:"$type,const=fm.teal.alpha.feed.play" cborgen:"$type,const=fm.teal.alpha.feed.play"`
17
-
// artistMbIds: Array of Musicbrainz artist IDs
17
+
// artistMbIds: Array of Musicbrainz artist IDs. Prefer using 'artists'.
18
18
ArtistMbIds []string `json:"artistMbIds,omitempty" cborgen:"artistMbIds,omitempty"`
19
-
// artistNames: Array of artist names in order of original appearance.
20
-
ArtistNames []string `json:"artistNames" cborgen:"artistNames"`
19
+
// artistNames: Array of artist names in order of original appearance. Prefer using 'artists'.
20
+
ArtistNames []string `json:"artistNames,omitempty" cborgen:"artistNames,omitempty"`
21
+
// artists: Array of artists in order of original appearance.
22
+
Artists []*AlphaFeedDefs_Artist `json:"artists,omitempty" cborgen:"artists,omitempty"`
21
23
// duration: The length of the track in seconds
22
24
Duration *int64 `json:"duration,omitempty" cborgen:"duration,omitempty"`
23
25
// isrc: The ISRC code associated with the recording
+22
-14
lexicons/teal/feed/defs.json
+22
-14
lexicons/teal/feed/defs.json
···
5
5
"defs": {
6
6
"playView": {
7
7
"type": "object",
8
-
"required": ["trackName", "artistNames"],
8
+
"required": ["trackName", "artists"],
9
9
"properties": {
10
10
"trackName": {
11
11
"type": "string",
···
26
26
"type": "integer",
27
27
"description": "The length of the track in seconds"
28
28
},
29
-
"artistNames": {
30
-
"type": "array",
31
-
"items": {
32
-
"type": "string",
33
-
"minLength": 1,
34
-
"maxLength": 256,
35
-
"maxGraphemes": 2560
36
-
},
37
-
"description": "Array of artist names in order of original appearance."
38
-
},
39
-
"artistMbIds": {
29
+
"artists": {
40
30
"type": "array",
41
31
"items": {
42
-
"type": "string"
32
+
"type": "ref",
33
+
"ref": "#artist"
43
34
},
44
-
"description": "Array of Musicbrainz artist IDs"
35
+
"description": "Array of artists in order of original appearance."
45
36
},
46
37
"releaseName": {
47
38
"type": "string",
···
75
66
"type": "string",
76
67
"format": "datetime",
77
68
"description": "The unix timestamp of when the track was played"
69
+
}
70
+
}
71
+
},
72
+
"artist": {
73
+
"type": "object",
74
+
"required": ["artistName"],
75
+
"properties": {
76
+
"artistName": {
77
+
"type": "string",
78
+
"minLength": 1,
79
+
"maxLength": 256,
80
+
"maxGraphemes": 2560,
81
+
"description": "The name of the artist"
82
+
},
83
+
"artistMbId": {
84
+
"type": "string",
85
+
"description": "The Musicbrainz ID of the artist"
78
86
}
79
87
}
80
88
}
+11
-3
lexicons/teal/feed/play.json
+11
-3
lexicons/teal/feed/play.json
···
8
8
"key": "tid",
9
9
"record": {
10
10
"type": "object",
11
-
"required": ["trackName", "artistNames"],
11
+
"required": ["trackName"],
12
12
"properties": {
13
13
"trackName": {
14
14
"type": "string",
···
38
38
"maxLength": 256,
39
39
"maxGraphemes": 2560
40
40
},
41
-
"description": "Array of artist names in order of original appearance."
41
+
"description": "Array of artist names in order of original appearance. Prefer using 'artists'."
42
42
},
43
43
"artistMbIds": {
44
44
"type": "array",
45
45
"items": {
46
46
"type": "string"
47
47
},
48
-
"description": "Array of Musicbrainz artist IDs"
48
+
"description": "Array of Musicbrainz artist IDs. Prefer using 'artists'."
49
+
},
50
+
"artists": {
51
+
"type": "array",
52
+
"items": {
53
+
"type": "ref",
54
+
"ref": "fm.teal.alpha.feed.defs#artist"
55
+
},
56
+
"description": "Array of artists in order of original appearance."
49
57
},
50
58
"releaseName": {
51
59
"type": "string",