From 3d41048ba6107c8b82ea4d89215d51d510fa9bc6 Mon Sep 17 00:00:00 2001 From: Ben Keys Date: Wed, 5 Jul 2023 11:39:19 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=A5=85=20Check=20if=20user=20deleted=20pl?= =?UTF-8?q?aylist=20without=20403=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- functions/src/spotify/error.ts | 38 +++++++++++------------ functions/src/spotify/index.ts | 27 ++++++++-------- functions/src/tools/public-liked-songs.ts | 35 ++++++++++----------- functions/src/utils.ts | 9 ------ 4 files changed, 49 insertions(+), 60 deletions(-) diff --git a/functions/src/spotify/error.ts b/functions/src/spotify/error.ts index 68ecbfc..596e324 100644 --- a/functions/src/spotify/error.ts +++ b/functions/src/spotify/error.ts @@ -1,24 +1,22 @@ import type { Response } from 'node-fetch' export const handleRepsonse = async (method: () => Promise): Promise => { - try { - const res = await method() - if (res.status < 300) { - if (res.headers.get('content-type')?.startsWith('application/json')) - return await res.json() - else return true as T - } else { - let error: any - try { - error = (await res.json()).error - } catch { - error = { status: res.status, statusText: res.statusText } - } - throw error; - } - } catch (error) { - throw error - } + try { + const res = await method() + if (res.status < 300) { + if (res.headers.get('content-type')?.startsWith('application/json')) + return await res.json() + else return true as T + } else { + let error: any + try { + error = (await res.json()).error + } catch { + error = { status: res.status, statusText: res.statusText } + } + throw error + } + } catch (error) { + throw error + } } - -export class PlaylistDeletedError extends Error {} \ No newline at end of file diff --git a/functions/src/spotify/index.ts b/functions/src/spotify/index.ts index 81c05e2..9d61d3c 100644 --- a/functions/src/spotify/index.ts +++ b/functions/src/spotify/index.ts @@ -1,8 +1,5 @@ -import { handleRepsonse, PlaylistDeletedError } from './error' +import { handleRepsonse } from './error' import fetch, { BodyInit } from 'node-fetch' -import { isObjWith } from '../utils' - -export { PlaylistDeletedError } export class Spotify { private credentials: Credentials @@ -119,20 +116,18 @@ export class Spotify { } createPlaylist(userId: string, details: PlaylistDetails) { - return this.request(`users/${userId}/playlists`, 'POST', details) + return this.request( + `users/${userId}/playlists`, + 'POST', + details + ) } - async changePlaylistDetails(playlistId: string, details: Partial) { - try { - return await this.request('playlists/' + playlistId, 'PUT', details) - } catch (error) { - if (isObjWith(error, 'status') && error.status == 403) - throw new PlaylistDeletedError() - } + changePlaylistDetails(playlistId: string, details: Partial) { + return this.request('playlists/' + playlistId, 'PUT', details) } getPlaylistTracks(playlistId: string) { - return this.requestAll(`playlists/${playlistId}/tracks`) } @@ -151,6 +146,12 @@ export class Spotify { { uris } ) } + + usersFollowPlaylist(playlistId: string, userIds: string[]) { + return this.request(`playlists/${playlistId}/followers/contains`, 'GET', { + ids: userIds.join() + }) + } } type Primative = string | number | boolean | undefined diff --git a/functions/src/tools/public-liked-songs.ts b/functions/src/tools/public-liked-songs.ts index 9848414..3df27df 100644 --- a/functions/src/tools/public-liked-songs.ts +++ b/functions/src/tools/public-liked-songs.ts @@ -1,6 +1,6 @@ import * as functions from 'firebase-functions' import { db } from '../firestore' -import { Spotify, PlaylistDeletedError } from '../spotify' +import { Spotify } from '../spotify' import { forEvery } from '../utils' import { secrets } from '../env' @@ -36,10 +36,12 @@ export const createPublicLikedSongs = functions docData = doc.data() as Document } + const user = await spotify.getMe() + // Don't have playlist id if (!docData.playlist_id) { // check to see if playlist already exists - const [playlists, user] = await Promise.all([spotify.getMyPlaylists(), spotify.getMe()]) + const playlists = await spotify.getMyPlaylists() const playlistName = name(user.display_name) for (const playlist of playlists) { if (playlist.name == playlistName) docData.playlist_id = playlist.id @@ -56,16 +58,17 @@ export const createPublicLikedSongs = functions await ref.update({ playlist_id: docData.playlist_id }) } + if (!(await spotify.usersFollowPlaylist(docData.playlist_id, [user.id]))[0]) { + await ref.delete() + throw new functions.https.HttpsError( + 'not-found', + 'You may have deleted the synced playlist. Refresh to restore it.' + ) + } + try { return await update(spotify, docData.playlist_id) } catch (error) { - if (error instanceof PlaylistDeletedError) { - await ref.delete() - throw new functions.https.HttpsError( - 'not-found', - 'Synced playlist was deleted. Refresh to Create a new one.' - ) - } let msg = 'Spotify Error' functions.logger.warn(error) if (typeof error == 'object' && error && 'statusCode' in error) { @@ -96,15 +99,11 @@ export const syncPublicLikedSongs = functions }) await spotify.refreshAccessToken() if (data.playlist_id) { - try { - return await update(spotify, data.playlist_id) - } catch (error) { - if (error instanceof PlaylistDeletedError) - return await ref.delete() - else throw error - } - } - else return + const user = await spotify.getMe() + if (!(await spotify.usersFollowPlaylist(data.playlist_id, [user.id]))[0]) + return await ref.delete() + else return await update(spotify, data.playlist_id) + } else return await ref.delete() }) ) }) diff --git a/functions/src/utils.ts b/functions/src/utils.ts index 81115ad..b6bac0b 100644 --- a/functions/src/utils.ts +++ b/functions/src/utils.ts @@ -7,12 +7,3 @@ export async function forEvery( await method(items.slice(i, i + limit)) } } - -export function isObjWith(value: unknown, ...keys: K[]): value is { [k in K]: any } { - if (typeof value == 'object' && value) { - for (const key of keys) - if (!(key in value)) return false - return true - } - return false; -} \ No newline at end of file