···7273air should automatically build and run piper, and watch for changes on relevant files.
7400000075#### docker
76We 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`SERVER_HOST`- `0.0.0.0`
78`DB_PATH` = `/db/piper.db` to persist your piper db through container restarts
7980-Make sure you have docker and docker compose installed, then you can run piper with `docker compose up`
···7273air should automatically build and run piper, and watch for changes on relevant files.
7475+#### 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+81#### docker
82We 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
83`SERVER_HOST`- `0.0.0.0`
84`DB_PATH` = `/db/piper.db` to persist your piper db through container restarts
8586+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
···27 }
2829 cw := cbg.NewCborWriter(w)
30- fieldCount := 14
3132 if t.ArtistMbIds == nil {
0000000033 fieldCount--
34 }
35···126 }
127 if _, err := cw.WriteString(string("fm.teal.alpha.feed.play")); err != nil {
128 return err
00000000000000000000000000000129 }
130131 // t.Duration (int64) (int64)
···316 }
317318 // t.ArtistNames ([]string) (slice)
319- if len("artistNames") > 1000000 {
320- return xerrors.Errorf("Value in field \"artistNames\" was too long")
321- }
322323- 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- }
329330- if len(t.ArtistNames) > 8192 {
331- return xerrors.Errorf("Slice value in field t.ArtistNames was too long")
332- }
000333334- 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")
340 }
341342- if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil {
343 return err
344 }
345- if _, err := cw.WriteString(string(v)); err != nil {
346- return err
347- }
034800000000349 }
350351 // t.ReleaseMbId (string) (string)
···582 }
583584 t.LexiconTypeID = string(sval)
0000000000000000000000000000000000000000000000000585 }
586 // t.Duration (int64) (int64)
587 case "duration":
···1733 }
17341735 cw := cbg.NewCborWriter(w)
1736- fieldCount := 13
1737-1738- if t.ArtistMbIds == nil {
1739- fieldCount--
1740- }
17411742 if t.Duration == nil {
1743 fieldCount--
···1813 return err
1814 }
1815 }
000000000000000000000000001816 }
18171818 // t.Duration (int64) (int64)
···1966 }
1967 }
19681969- // 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 // t.ReleaseMbId (string) (string)
2039 if t.ReleaseMbId != nil {
2040···2259 t.Isrc = (*string)(&sval)
2260 }
2261 }
00000000000000000000000000000000000000000000000002262 // t.Duration (int64) (int64)
2263 case "duration":
2264 {
···2369 t.PlayedTime = (*string)(&sval)
2370 }
2371 }
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 // t.ReleaseMbId (string) (string)
2453 case "releaseMbId":
2454···25652566 return nil
2567}
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
···27 }
2829 cw := cbg.NewCborWriter(w)
30+ fieldCount := 15
3132 if t.ArtistMbIds == nil {
33+ fieldCount--
34+ }
35+36+ if t.ArtistNames == nil {
37+ fieldCount--
38+ }
39+40+ if t.Artists == nil {
41 fieldCount--
42 }
43···134 }
135 if _, err := cw.WriteString(string("fm.teal.alpha.feed.play")); err != nil {
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+ }
166 }
167168 // t.Duration (int64) (int64)
···353 }
354355 // t.ArtistNames ([]string) (slice)
356+ if t.ArtistNames != nil {
00357358+ if len("artistNames") > 1000000 {
359+ return xerrors.Errorf("Value in field \"artistNames\" was too long")
360+ }
000361362+ 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+ }
368369+ if len(t.ArtistNames) > 8192 {
370+ return xerrors.Errorf("Slice value in field t.ArtistNames was too long")
0000371 }
372373+ if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.ArtistNames))); err != nil {
374 return err
375 }
376+ for _, v := range t.ArtistNames {
377+ if len(v) > 1000000 {
378+ return xerrors.Errorf("Value in field v was too long")
379+ }
380381+ 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+ }
389 }
390391 // t.ReleaseMbId (string) (string)
···622 }
623624 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+ }
674 }
675 // t.Duration (int64) (int64)
676 case "duration":
···1822 }
18231824 cw := cbg.NewCborWriter(w)
1825+ fieldCount := 12
000018261827 if t.Duration == nil {
1828 fieldCount--
···1898 return err
1899 }
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+1927 }
19281929 // t.Duration (int64) (int64)
···2077 }
2078 }
20790000000000000000000000000000000000000000000000000000000000000000000002080 // t.ReleaseMbId (string) (string)
2081 if t.ReleaseMbId != nil {
2082···2301 t.Isrc = (*string)(&sval)
2302 }
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+ }
2353 // t.Duration (int64) (int64)
2354 case "duration":
2355 {
···2460 t.PlayedTime = (*string)(&sval)
2461 }
2462 }
000000000000000000000000000000000000000000000000000000000000000000000000000000002463 // t.ReleaseMbId (string) (string)
2464 case "releaseMbId":
2465···25762577 return nil
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
···45// schema: fm.teal.alpha.feed.defs
6000000007// AlphaFeedDefs_PlayView is a "playView" in the fm.teal.alpha.feed.defs schema.
8type 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"`
13 // duration: The length of the track in seconds
14 Duration *int64 `json:"duration,omitempty" cborgen:"duration,omitempty"`
15 // isrc: The ISRC code associated with the recording
···45// schema: fm.teal.alpha.feed.defs
67+// 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+15// AlphaFeedDefs_PlayView is a "playView" in the fm.teal.alpha.feed.defs schema.
16type AlphaFeedDefs_PlayView struct {
17+ // artists: Array of artists in order of original appearance.
18+ Artists []*AlphaFeedDefs_Artist `json:"artists" cborgen:"artists"`
0019 // duration: The length of the track in seconds
20 Duration *int64 `json:"duration,omitempty" cborgen:"duration,omitempty"`
21 // isrc: The ISRC code associated with the recording
+5-3
api/teal/feedplay.go
···14// RECORDTYPE: AlphaFeedPlay
15type AlphaFeedPlay struct {
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
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"`
0021 // duration: The length of the track in seconds
22 Duration *int64 `json:"duration,omitempty" cborgen:"duration,omitempty"`
23 // isrc: The ISRC code associated with the recording
···14// RECORDTYPE: AlphaFeedPlay
15type AlphaFeedPlay struct {
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. Prefer using 'artists'.
18 ArtistMbIds []string `json:"artistMbIds,omitempty" cborgen:"artistMbIds,omitempty"`
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"`
23 // duration: The length of the track in seconds
24 Duration *int64 `json:"duration,omitempty" cborgen:"duration,omitempty"`
25 // isrc: The ISRC code associated with the recording
+22-14
lexicons/teal/feed/defs.json
···5 "defs": {
6 "playView": {
7 "type": "object",
8- "required": ["trackName", "artistNames"],
9 "properties": {
10 "trackName": {
11 "type": "string",
···26 "type": "integer",
27 "description": "The length of the track in seconds"
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": {
40 "type": "array",
41 "items": {
42- "type": "string"
043 },
44- "description": "Array of Musicbrainz artist IDs"
45 },
46 "releaseName": {
47 "type": "string",
···75 "type": "string",
76 "format": "datetime",
77 "description": "The unix timestamp of when the track was played"
0000000000000000078 }
79 }
80 }
···5 "defs": {
6 "playView": {
7 "type": "object",
8+ "required": ["trackName", "artists"],
9 "properties": {
10 "trackName": {
11 "type": "string",
···26 "type": "integer",
27 "description": "The length of the track in seconds"
28 },
29+ "artists": {
000000000030 "type": "array",
31 "items": {
32+ "type": "ref",
33+ "ref": "#artist"
34 },
35+ "description": "Array of artists in order of original appearance."
36 },
37 "releaseName": {
38 "type": "string",
···66 "type": "string",
67 "format": "datetime",
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"
86 }
87 }
88 }
+11-3
lexicons/teal/feed/play.json
···8 "key": "tid",
9 "record": {
10 "type": "object",
11- "required": ["trackName", "artistNames"],
12 "properties": {
13 "trackName": {
14 "type": "string",
···38 "maxLength": 256,
39 "maxGraphemes": 2560
40 },
41- "description": "Array of artist names in order of original appearance."
42 },
43 "artistMbIds": {
44 "type": "array",
45 "items": {
46 "type": "string"
47 },
48- "description": "Array of Musicbrainz artist IDs"
0000000049 },
50 "releaseName": {
51 "type": "string",
···8 "key": "tid",
9 "record": {
10 "type": "object",
11+ "required": ["trackName"],
12 "properties": {
13 "trackName": {
14 "type": "string",
···38 "maxLength": 256,
39 "maxGraphemes": 2560
40 },
41+ "description": "Array of artist names in order of original appearance. Prefer using 'artists'."
42 },
43 "artistMbIds": {
44 "type": "array",
45 "items": {
46 "type": "string"
47 },
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."
57 },
58 "releaseName": {
59 "type": "string",