diff --git a/main.ts b/main.ts index df029ad..b3e8382 100644 --- a/main.ts +++ b/main.ts @@ -1,6 +1,6 @@ import { Command } from "@cliffy/command"; import denoJSON from "./deno.json" with { type: "json" }; -import { defaultOptions, textToPlaylist } from './src/text-to-playlist.ts'; +import { defaultOptions, textToPlaylist } from "./src/text-to-playlist.ts"; export { textToPlaylist } from "./src/text-to-playlist.ts"; export type { Options, Result } from "./src/text-to-playlist.ts"; @@ -31,15 +31,19 @@ if (import.meta.main) { .arguments("") .parse(Deno.args); - const { tracksAdded, tracksRemoved } = await textToPlaylist(args[0], options.playlist, { - ...options, + const [input] = args; + const { playlist, removeDuplicates, removeOtherTracks, debug } = options; + const { tracksAdded, tracksRemoved } = await textToPlaylist(input, playlist, { + removeDuplicates, + removeOtherTracks, + debug, }); console.log( - `Added ${tracksAdded.length} tracks to playlist: ${options.playlist}`, + `Added ${tracksAdded.length} tracks to playlist: ${playlist}`, ); console.log( - `Added ${tracksRemoved.length} tracks to playlist: ${options.playlist}`, + `Added ${tracksRemoved.length} tracks to playlist: ${playlist}`, ); } diff --git a/src/text-to-playlist.ts b/src/text-to-playlist.ts index 3d0fe89..10d2fe2 100644 --- a/src/text-to-playlist.ts +++ b/src/text-to-playlist.ts @@ -13,96 +13,94 @@ import { retrieveAccessToken } from "./utils/spotify-authorization.ts"; * @param options Additional options. */ export async function textToPlaylist( - textOrFile: string, - playlistUrl: string, - options: Partial = {}, + textOrFile: string, + playlistUrl: string, + options: Partial = {}, ): Promise { - const input = await exists(textOrFile) ? await Deno.readTextFile(textOrFile) : textOrFile; - const mergedOptions: Options = { ...defaultOptions, ...options, playlistUrl }; - const accessToken = await retrieveAccessToken(); - const playlist = Playlist.fromUrl(mergedOptions.playlistUrl); - const spotifyClient = new SpotifyClient(accessToken); - - // Read input and extract Spotify track links - const SPOTIFY_TRACK_PATTERN = /https:\/\/open\.spotify\.com\/track\/\w+/g; - const trackUrls = mergedOptions.removeDuplicates - ? Array.from( - new Set(input.match(SPOTIFY_TRACK_PATTERN) ?? []), - ) - : input.match(SPOTIFY_TRACK_PATTERN) ?? []; - - const tracks = trackUrls.map(Track.fromUrl); - - if (mergedOptions.debug) { - console.debug("Found tracks:"); - console.table(tracks.map((it) => it.toUrl())); - } - - const currentTracks = await new PageIterator( - (offset) => getPlaylistTracks(spotifyClient, playlist.id, { limit: 50, offset }), - ).collect().then((tracks) => tracks.map(({ track }) => new Track(track.id))); - - // add everything that is in `tracks` but not in `currentTracks` - const trackUrisToAdd = withoutAll( - tracks.map((it) => it.toUri()), - currentTracks.map((it) => it.toUri()), - ); - - for (const batch of chunk(trackUrisToAdd, 50)) { - await addItemsToPlaylist(spotifyClient, playlist.id, batch); - } - - // delete everything that is in `currentTrackURIs` but not in `trackURIs` - const trackURIsToRemove = options.removeOtherTracks - ? withoutAll( - currentTracks.map((it) => it.toUri()), - tracks.map((it) => it.toUri()), - ) - : []; - - for (const batch of chunk(trackURIsToRemove, 50)) { - await removePlaylistItems(spotifyClient, playlist.id, batch); - } - - return { - tracksAdded: trackUrisToAdd, - tracksRemoved: trackURIsToRemove, - }; + const input = await exists(textOrFile) ? await Deno.readTextFile(textOrFile) : textOrFile; + const mergedOptions: Options = { ...defaultOptions, ...options }; + const accessToken = await retrieveAccessToken(); + const playlist = Playlist.fromUrl(playlistUrl); + const spotifyClient = new SpotifyClient(accessToken); + + // Read input and extract Spotify track links + const SPOTIFY_TRACK_PATTERN = /https:\/\/open\.spotify\.com\/track\/\w+/g; + const trackUrls = mergedOptions.removeDuplicates + ? Array.from( + new Set(input.match(SPOTIFY_TRACK_PATTERN) ?? []), + ) + : input.match(SPOTIFY_TRACK_PATTERN) ?? []; + + const tracks = trackUrls.map(Track.fromUrl); + + if (mergedOptions.debug) { + console.debug("Found tracks:"); + console.table(tracks.map((it) => it.toUrl())); + } + + const currentTracks = await new PageIterator( + (offset) => getPlaylistTracks(spotifyClient, playlist.id, { limit: 50, offset }), + ).collect().then((tracks) => tracks.map(({ track }) => new Track(track.id))); + + // add everything that is in `tracks` but not in `currentTracks` + const trackUrisToAdd = withoutAll( + tracks.map((it) => it.toUri()), + currentTracks.map((it) => it.toUri()), + ); + + for (const batch of chunk(trackUrisToAdd, 50)) { + await addItemsToPlaylist(spotifyClient, playlist.id, batch); + } + + // delete everything that is in `currentTrackURIs` but not in `trackURIs` + const trackURIsToRemove = options.removeOtherTracks + ? withoutAll( + currentTracks.map((it) => it.toUri()), + tracks.map((it) => it.toUri()), + ) + : []; + + for (const batch of chunk(trackURIsToRemove, 50)) { + await removePlaylistItems(spotifyClient, playlist.id, batch); + } + + return { + tracksAdded: trackUrisToAdd, + tracksRemoved: trackURIsToRemove, + }; } export type Options = { - /** - * URL to Spotify playlist. - */ - playlistUrl: string; - - /** - * Whether to filter out duplicates from input. - */ - removeDuplicates: boolean; - - /** - * Whether to remove tracks from playlist that do not exit in input - */ - removeOtherTracks: boolean; - - debug: boolean; + /** + * Whether to filter out duplicates from input. + */ + removeDuplicates: boolean; + + /** + * Whether to remove tracks from playlist that do not exit in input. + */ + removeOtherTracks: boolean; + + /** + * Outputs debugging logs. + */ + debug: boolean; }; export type Result = { - /** - * Track URIs that were added. - */ - tracksAdded: string[]; - - /** - * Track URIs that were removed. - */ - tracksRemoved: string[]; + /** + * Track URIs that were added. + */ + tracksAdded: string[]; + + /** + * Track URIs that were removed. + */ + tracksRemoved: string[]; }; export const defaultOptions = { - debug: false, - removeDuplicates: true, - removeOtherTracks: false, + debug: false, + removeDuplicates: true, + removeOtherTracks: false, } satisfies Partial;