Skip to content

Commit

Permalink
refactor(combine-playlists): optimize and remove duplicate code
Browse files Browse the repository at this point in the history
  • Loading branch information
jeroentvb committed Oct 24, 2023
1 parent 0fd40ab commit 0c6f495
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 46 deletions.
23 changes: 17 additions & 6 deletions src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,11 @@ class App extends React.Component<Record<string, unknown>, State> {
? await this.createPlaylist(formData.sources)
: this.findPlaylist(formData.target);

await combinePlaylists(sourcePlaylists, targetPlaylist);
await combinePlaylists(sourcePlaylists, targetPlaylist)
.catch((err) => {
console.error('An error ocurred while combining playlists', err);
Spicetify.showNotification('An error ocurred while combining playlists', true);
});
this.saveCombinedPlaylist(sourcePlaylists, targetPlaylist);

Spicetify.PopupModal.hide();
Expand Down Expand Up @@ -128,7 +132,17 @@ class App extends React.Component<Record<string, unknown>, State> {
const { sources } = this.state.combinedPlaylists.find((combinedPlaylist) => combinedPlaylist.target.id === playlistToSync.id) as CombinedPlaylist;
const sourcePlaylists = sources.map((sourcePlaylist) => this.findPlaylist(sourcePlaylist.id));

await combinePlaylists(sourcePlaylists, playlistToSync);
await combinePlaylists(sourcePlaylists, playlistToSync)
.catch((err) => {
console.error('An error ocurred while syncing playlists', err);
Spicetify.showNotification('An error ocurred while syncing playlists', true);
});
}

@TrackState('isLoading')
async syncAllPlaylists() {
Spicetify.showNotification('Synchronizing all combined playlists');
await synchronizeCombinedPlaylists();
}

findPlaylist(id: string): SpotifyPlaylist {
Expand Down Expand Up @@ -219,10 +233,7 @@ class App extends React.Component<Record<string, unknown>, State> {
<SpotifyComponents.MenuItem
label="Synchronize all combined playlists"
leadingIcon={<SpicetifySvgIcon iconName="repeat-once" />}
onClick={() => {
synchronizeCombinedPlaylists();
Spicetify.showNotification('Synchronizing all combined playlists');
}}
onClick={() => !this.state.isLoading && this.syncAllPlaylists()}
>
Synchronize all
</SpotifyComponents.MenuItem>
Expand Down
3 changes: 2 additions & 1 deletion src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export const TRACKS_FROM_PLAYLIST_URL_FILTER = '?fields=items(track(uri)),next';

export const GET_PLAYLIST_TRACKS_URL = (uri: string) => `sp://core-playlist/v1/playlist/${uri}/rows`;

export const GET_LIKED_SONGS_LIST_URL = 'sp://core-collection/unstable/@/list/tracks/all?responseFormat=protobufJson';

export const ADD_TRACKS_TO_PLAYLIST_URL = (id: string) => `https://api.spotify.com/v1/playlists/${id}/tracks`;

export const LS_KEY = 'combined-playlists';
Expand Down Expand Up @@ -42,5 +44,4 @@ export const LIKED_SONGS_PLAYLIST_FACADE: SpotifyPlaylist = {
},
type: 'playlist',
uri: 'spotify:playlist:liked-songs-facade'

};
32 changes: 8 additions & 24 deletions src/extensions/auto-sync.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { GET_PLAYLIST_TRACKS_URL, LS_KEY } from '../constants';
import type { CombinedPlaylist, PlaylistRowsResponse } from '../types';
import { addTracksToPlaylist, getCombinedPlaylistsSettings } from '../utils';
import { LS_KEY } from '../constants';
import type { CombinedPlaylist } from '../types';
import { combinePlaylists, getCombinedPlaylistsSettings } from '../utils';

(async () => {
while (!Spicetify?.Platform || !Spicetify?.CosmosAsync) {
Expand All @@ -14,25 +14,9 @@ import { addTracksToPlaylist, getCombinedPlaylistsSettings } from '../utils';
export function synchronizeCombinedPlaylists() {
const combinedPlaylists: CombinedPlaylist[] = JSON.parse(Spicetify.LocalStorage.get(LS_KEY) as string) ?? [];

Promise.all(combinedPlaylists.map(async ({ sources, target }) => {
const sourceUris = await Promise.all(sources.map(async (source) => {
const res: PlaylistRowsResponse = await Spicetify.CosmosAsync.get(GET_PLAYLIST_TRACKS_URL(source.uri));
return res.rows.map(({ link }) => link);
// Flatten result and remove duplicates
})).then(res => Array.from(new Set(res.flat())));

const targetUris = await Spicetify.CosmosAsync.get(GET_PLAYLIST_TRACKS_URL(target.uri))
.then((res: PlaylistRowsResponse) => res.rows.map(({ link }) => link));

const missingUris = sourceUris.filter(uri => !targetUris.includes(uri));

if (missingUris.length) {
Spicetify.showNotification(`Auto-syncing ${missingUris.length} missing tracks to playlist ${target.name}`);
// Endpoint only wants the id, not the full uri
await addTracksToPlaylist(target.uri.split(':').at(-1) as string, missingUris);
Spicetify.showNotification(`Auto-synced ${missingUris.length} missing tracks to playlist ${target.name} 🔥`);
}
})).catch((_err) => {
Spicetify.showNotification('An error while auto-syncing playlists', true);
});
return Promise.all(combinedPlaylists.map(({ sources, target }) => combinePlaylists(sources, target, true)))
.catch((err) => {
console.error('An error ocurred while auto-syncing playlists', err);
Spicetify.showNotification('An error ocurred while auto-syncing playlists', true);
});
}
39 changes: 24 additions & 15 deletions src/utils/combine-playlists.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,42 @@
import { ADD_TRACKS_TO_PLAYLIST_URL, LIKED_SONGS_PLAYLIST_FACADE, TRACKS_FROM_PLAYLIST_URL_FILTER } from '../constants';
import { SpotifyCollectionCallResponse } from '../types';
import { getPaginatedSpotifyData, splitArrayInChunks } from './';
import { ADD_TRACKS_TO_PLAYLIST_URL, GET_LIKED_SONGS_LIST_URL, GET_PLAYLIST_TRACKS_URL, LIKED_SONGS_PLAYLIST_FACADE } from '../constants';
import { PlaylistInfo, PlaylistRowsResponse, SpotifyCollectionCallResponse } from '../types';
import { splitArrayInChunks } from './';

export async function combinePlaylists(sourcePlaylists: SpotifyApi.PlaylistObjectSimplified[], targetPlaylist: SpotifyApi.PlaylistObjectSimplified) {
const allTrackUris = await Promise.all(sourcePlaylists.map(async (playlist): Promise<string[]> => {
export async function combinePlaylists(sourcePlaylists: PlaylistInfo[], targetPlaylist: PlaylistInfo, autoSync = false) {
const sourceUris = await Promise.all(sourcePlaylists.map(async (playlist): Promise<string[]> => {
if (playlist.id === LIKED_SONGS_PLAYLIST_FACADE.id) {
return await Spicetify.CosmosAsync.get('sp://core-collection/unstable/@/list/tracks/all?responseFormat=protobufJson')
return Spicetify.CosmosAsync.get(GET_LIKED_SONGS_LIST_URL)
.then((res: SpotifyCollectionCallResponse) => res.item.map(item => item.trackMetadata.link));
} else {
return await getPaginatedSpotifyData<{ track: { uri: string } }>(playlist.tracks.href + TRACKS_FROM_PLAYLIST_URL_FILTER)
.then(items => items.map(item => item.track.uri));
return Spicetify.CosmosAsync.get(GET_PLAYLIST_TRACKS_URL(playlist.uri))
.then((res: PlaylistRowsResponse) => res.rows.map((row) => row.link));
}
// Flatten responses and remove duplicates
})).then(arrays => Array.from(new Set(arrays.flat())));

const targetTrackUris = await getPaginatedSpotifyData<{ track: { uri: string } }>(targetPlaylist.tracks.href + TRACKS_FROM_PLAYLIST_URL_FILTER)
.then(items => items.map(item => item.track.uri));
const targetUris = await Spicetify.CosmosAsync.get(GET_PLAYLIST_TRACKS_URL(targetPlaylist.uri))
.then((res: PlaylistRowsResponse) => res.rows.map(({ link }) => link));

// Filter duplicates from souces usig new Set, then filter duplicates from targetPlaylist using .filter
const sourcesTrackUris = allTrackUris.filter((sourceUri) => !targetTrackUris.includes(sourceUri));
const splittedTrackUris = splitArrayInChunks(sourcesTrackUris);
const missingUris = sourceUris.filter((sourceUri) => !targetUris.includes(sourceUri));
const uriChunks = splitArrayInChunks(missingUris);

await Promise.all(splittedTrackUris.map((trackUris) => {
if (missingUris.length > 0 && autoSync) {
Spicetify.showNotification(`Auto-syncing ${missingUris.length} missing tracks to playlist ${targetPlaylist.name}`);
}

await Promise.all(uriChunks.map((trackUris) => {
return addTracksToPlaylist(targetPlaylist.id, trackUris);
}));

Spicetify.showNotification(`Added ${sourcesTrackUris.length} tracks to playlist: ${targetPlaylist.name}`);
if (missingUris.length > 0) {
const msg = autoSync
? `Auto-synced ${missingUris.length} missing tracks to playlist ${targetPlaylist.name} 🔥`
: `Added ${missingUris.length} tracks to playlist: ${targetPlaylist.name}`;
Spicetify.showNotification(msg);
}
}

export function addTracksToPlaylist(playlistId: string, trackUris: string[]) {
return Spicetify.CosmosAsync.post(ADD_TRACKS_TO_PLAYLIST_URL(playlistId), { uris: trackUris });
}
}

0 comments on commit 0c6f495

Please sign in to comment.