A decentralized music tracking and discovery platform built on AT Protocol 🎵

Create songs and ensure entities on scrobble

+195 -1
+195 -1
apps/cli/src/cmd/sync.ts
··· 624 624 ) => { 625 625 const { title, artist, album } = record; 626 626 logger.info` New song: ${title} by ${artist} from ${album}`; 627 + await createSongs( 628 + [ 629 + { 630 + cid, 631 + uri, 632 + value: record, 633 + }, 634 + ], 635 + user, 636 + ); 627 637 }; 628 638 629 639 const onNewAlbum = async ( ··· 672 682 uri: string, 673 683 user: SelectUser, 674 684 ) => { 675 - const { title, createdAt } = record; 685 + const { title, createdAt, artist, album, albumArtist } = record; 676 686 logger.info` New scrobble: ${title} at ${createdAt}`; 687 + 688 + // Check if the artist exists, create if not 689 + let [artistRecord] = await ctx.db 690 + .select() 691 + .from(schema.artists) 692 + .where(eq(schema.artists.name, record.albumArtist)) 693 + .execute(); 694 + 695 + if (!artistRecord) { 696 + logger.info` ⚙️ Artist not found, creating: "${albumArtist}"`; 697 + 698 + // Create a synthetic artist record from scrobble data 699 + const artistUri = `at://${user.did}/app.rocksky.artist/${createId()}`; 700 + const artistCid = createId(); 701 + 702 + await createArtists( 703 + [ 704 + { 705 + cid: artistCid, 706 + uri: artistUri, 707 + value: { 708 + $type: "app.rocksky.artist", 709 + name: record.albumArtist, 710 + createdAt: new Date().toISOString(), 711 + tags: record.tags || [], 712 + } as Artist.Record, 713 + }, 714 + ], 715 + user, 716 + ); 717 + 718 + [artistRecord] = await ctx.db 719 + .select() 720 + .from(schema.artists) 721 + .where(eq(schema.artists.name, record.albumArtist)) 722 + .execute(); 723 + 724 + if (!artistRecord) { 725 + logger.error` ❌ Failed to create artist. Skipping scrobble.`; 726 + return; 727 + } 728 + } 729 + 730 + // Check if the album exists, create if not 731 + let [albumRecord] = await ctx.db 732 + .select() 733 + .from(schema.albums) 734 + .where( 735 + and( 736 + eq(schema.albums.title, record.album), 737 + eq(schema.albums.artist, record.albumArtist), 738 + ), 739 + ) 740 + .execute(); 741 + 742 + if (!albumRecord) { 743 + logger.info` ⚙️ Album not found, creating: "${album}" by ${albumArtist}`; 744 + 745 + // Create a synthetic album record from scrobble data 746 + const albumUri = `at://${user.did}/app.rocksky.album/${createId()}`; 747 + const albumCid = createId(); 748 + 749 + await createAlbums( 750 + [ 751 + { 752 + cid: albumCid, 753 + uri: albumUri, 754 + value: { 755 + $type: "app.rocksky.album", 756 + title: record.album, 757 + artist: record.albumArtist, 758 + createdAt: new Date().toISOString(), 759 + releaseDate: record.releaseDate, 760 + year: record.year, 761 + albumArt: record.albumArt, 762 + artistUri: artistRecord.uri, 763 + spotifyLink: record.spotifyLink, 764 + appleMusicLink: record.appleMusicLink, 765 + tidalLink: record.tidalLink, 766 + youtubeLink: record.youtubeLink, 767 + } as Album.Record, 768 + }, 769 + ], 770 + user, 771 + ); 772 + 773 + // Fetch the newly created album 774 + [albumRecord] = await ctx.db 775 + .select() 776 + .from(schema.albums) 777 + .where( 778 + and( 779 + eq(schema.albums.title, record.album), 780 + eq(schema.albums.artist, record.albumArtist), 781 + ), 782 + ) 783 + .execute(); 784 + 785 + if (!albumRecord) { 786 + logger.error` ❌ Failed to create album. Skipping scrobble.`; 787 + return; 788 + } 789 + } 790 + 791 + // Check if the track exists, create if not 792 + let [track] = await ctx.db 793 + .select() 794 + .from(schema.tracks) 795 + .where( 796 + and( 797 + eq(schema.tracks.title, record.title), 798 + eq(schema.tracks.artist, record.artist), 799 + eq(schema.tracks.album, record.album), 800 + eq(schema.tracks.albumArtist, record.albumArtist), 801 + ), 802 + ) 803 + .execute(); 804 + 805 + if (!track) { 806 + logger.info` ⚙️ Track not found, creating: "${title}" by ${artist} from ${album}`; 807 + 808 + // Create a synthetic track record from scrobble data 809 + const trackUri = `at://${user.did}/app.rocksky.song/${createId()}`; 810 + const trackCid = createId(); 811 + 812 + await createSongs( 813 + [ 814 + { 815 + cid: trackCid, 816 + uri: trackUri, 817 + value: { 818 + $type: "app.rocksky.song", 819 + title: record.title, 820 + artist: record.artist, 821 + albumArtist: record.albumArtist, 822 + album: record.album, 823 + duration: record.duration, 824 + trackNumber: record.trackNumber, 825 + discNumber: record.discNumber, 826 + releaseDate: record.releaseDate, 827 + year: record.year, 828 + genre: record.genre, 829 + tags: record.tags, 830 + composer: record.composer, 831 + lyrics: record.lyrics, 832 + copyrightMessage: record.copyrightMessage, 833 + albumArt: record.albumArt, 834 + youtubeLink: record.youtubeLink, 835 + spotifyLink: record.spotifyLink, 836 + tidalLink: record.tidalLink, 837 + appleMusicLink: record.appleMusicLink, 838 + createdAt: new Date().toISOString(), 839 + mbId: record.mbid, 840 + label: record.label, 841 + albumUri: albumRecord.uri, 842 + artistUri: artistRecord.uri, 843 + } as Song.Record, 844 + }, 845 + ], 846 + user, 847 + ); 848 + 849 + // Fetch the newly created track 850 + [track] = await ctx.db 851 + .select() 852 + .from(schema.tracks) 853 + .where( 854 + and( 855 + eq(schema.tracks.title, record.title), 856 + eq(schema.tracks.artist, record.artist), 857 + eq(schema.tracks.album, record.album), 858 + eq(schema.tracks.albumArtist, record.albumArtist), 859 + ), 860 + ) 861 + .execute(); 862 + 863 + if (!track) { 864 + logger.error` ❌ Failed to create track. Skipping scrobble.`; 865 + return; 866 + } 867 + } 868 + 869 + logger.info` ✓ All required entities ready. Creating scrobble...`; 870 + 677 871 await createScrobbles( 678 872 [ 679 873 {