[WIP] music platform user data scraper
teal-fm atproto

Updated to the new lexicons

authored by baileytownsend.dev and committed by Kyle Loveless 9f38a3a6 9117c91a

Changed files
+402 -202
api
lexicons
teal
util
gencbor
+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
··· 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
··· 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
··· 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
··· 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
··· 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",
+1
util/gencbor/gencbor.go
··· 28 28 teal.AlphaActorStatus{}, 29 29 teal.AlphaActorProfile_FeaturedItem{}, 30 30 teal.AlphaFeedDefs_PlayView{}, 31 + teal.AlphaFeedDefs_Artist{}, 31 32 ); err != nil { 32 33 panic(err) 33 34 }