diff --git a/src/api/albums/AlbumsApi.ts b/src/api/albums/AlbumsApi.ts new file mode 100644 index 0000000..187b9bd --- /dev/null +++ b/src/api/albums/AlbumsApi.ts @@ -0,0 +1,149 @@ +import { AxiosInstance } from "axios"; +import { Track } from "../tracks"; +import { PaginateResult } from "../types"; +import { Album } from "./types"; + +export class AlbumsApi { + private constructor(private readonly spotifyClient: AxiosInstance) {} + + public static fromClient(spotifyClient: AxiosInstance) { + return new AlbumsApi(spotifyClient); + } + + /** + * Get Spotify catalog information for a single album. + * @param {string} id - The Spotify ID of the album. + * @param {string=} market - An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. +If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. +Note: If neither market or user country are provided, the content is considered unavailable for the client. +Users can view the country that is associated with their account in the account settings. + * @returns {Album} An album + */ + getAlbum = async ({ id, ...params }: { id: string; market?: string }) => { + const response = await this.spotifyClient.get(`/albums/${id}`, { + params, + }); + return response.data as Album; + }; + + /** + * Get Spotify catalog information for multiple albums identified by their Spotify IDs. + * @param {string[]} ids - List of the Spotify IDs for the albums. Maximum: 20 IDs. + * @param {string=} market - An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. +If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. +Note: If neither market or user country are provided, the content is considered unavailable for the client. +Users can view the country that is associated with their account in the account settings. + * @returns {{ albums: Album[] }} A set of albums + */ + getAlbums = async ({ ids, market }: { ids: string[]; market?: string }) => { + const response = await this.spotifyClient.get(`/albums`, { + params: { + market, + ids: ids.join(","), + }, + }); + return response.data as { albums: Album[] }; + }; + + /** + * Get Spotify catalog information about an album’s tracks. Optional parameters can be used to limit the number of tracks returned. + * @param {string} id - The Spotify ID of the album. + * @param {number=} limit - The maximum number of items to return. Default: 20. Minimum: 1. Maximum: 50. + * @param {string=} market - An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. + If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. + Note: If neither market or user country are provided, the content is considered unavailable for the client. + Users can view the country that is associated with their account in the account settings. + * @param {number=} offset - The index of the first item to return. Default: 0 (the first item). Use with limit to get the next set of items. + * @returns {PaginateResult} Pages of content + */ + getAlbumTracks = async ({ + id, + ...params + }: { + id: string; + limit?: number; + offset?: number; + market?: string; + }) => { + const response = await this.spotifyClient.get(`/albums/${id}/tracks`, { + params, + }); + return response.data as PaginateResult; + }; + + /** + * Get a list of the albums saved in the current Spotify user's 'Your Music' library. + * @param {number=} limit - The maximum number of items to return. Default: 20. Minimum: 1. Maximum: 50. + * @param {string=} market - An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. + If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. + Note: If neither market or user country are provided, the content is considered unavailable for the client. + Users can view the country that is associated with their account in the account settings. + * @param {number=} offset - The index of the first item to return. Default: 0 (the first item). Use with limit to get the next set of items. + * @returns {PaginateResult} Pages of content + */ + getSavedAlbum = async ( + params: { + limit?: number; + offset?: number; + market?: string; + } = {} + ) => { + const response = await this.spotifyClient.get(`/me/albums`, { + params, + }); + return response.data as PaginateResult; + }; + + /** + * Save one or more albums to the current user's 'Your Music' library. + * @param {string[]} ids List of the Spotify IDs for the albums. Maximum: 20 IDs. + */ + saveAlbums = async (ids: string[]) => { + const response = await this.spotifyClient.put(`/me/albums`, { + ids, + }); + return response.data as void; + }; + + /** + * Remove one or more albums from the current user's 'Your Music' library. + * @param {string[]} ids List of the Spotify IDs for the albums. Maximum: 20 IDs. + */ + removeAlbums = async (ids: string[]) => { + const response = await this.spotifyClient.delete(`/me/albums`, { + data: { ids }, + }); + return response.data as void; + }; + + /** + * Check if one or more albums is already saved in the current Spotify user's 'Your Music' library. + * @param {string[]} ids List of the Spotify IDs for the albums. Maximum: 20 IDs. + */ + checkSavedAlbums = async (ids: string[]) => { + const response = await this.spotifyClient.get(`/me/albums`, { + params: { ids }, + }); + return response.data as boolean[]; + }; + + /** + * Get a list of new album releases featured in Spotify (shown, for example, on a Spotify player’s “Browse” tab). + * @param {number=} limit - The maximum number of items to return. Default: 20. Minimum: 1. Maximum: 50. + * @param {string=} country - A country: an ISO 3166-1 alpha-2 country code. Provide this parameter if you want the list of returned items to be relevant to a particular country. If omitted, the returned items will be relevant to all countries. + * @param {number=} offset - The index of the first item to return. Default: 0 (the first item). Use with limit to get the next set of items. + * @returns {PaginateResult} Pages of content + */ + getNewReleases = async ( + params: { + country?: string; + offset?: number; + limit?: number; + } = {} + ) => { + const response = await this.spotifyClient.get(`/browse/new-releases`, { + params, + }); + return response.data as PaginateResult; + }; +} diff --git a/src/api/albums/api.ts b/src/api/albums/api.ts deleted file mode 100644 index 4228482..0000000 --- a/src/api/albums/api.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { spotifyClient } from "../client"; -import { PaginateResult } from "../types"; -import { Track } from "../tracks"; -import { Album } from "./types"; - -/** - * Get Spotify catalog information for a single album. - * @param {string} id - The Spotify ID of the album. - * @param {string=} market - An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. -If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. -Note: If neither market or user country are provided, the content is considered unavailable for the client. -Users can view the country that is associated with their account in the account settings. - * @returns {Album} An album - */ -export const getAlbum = async ({ - id, - ...params -}: { - id: string; - market?: string; -}) => { - const response = await spotifyClient.get(`/albums/${id}`, { - params, - }); - return response.data as Album; -}; - -/** - * Get Spotify catalog information for multiple albums identified by their Spotify IDs. - * @param {string[]} ids - List of the Spotify IDs for the albums. Maximum: 20 IDs. - * @param {string=} market - An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. -If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. -Note: If neither market or user country are provided, the content is considered unavailable for the client. -Users can view the country that is associated with their account in the account settings. - * @returns {{ albums: Album[] }} A set of albums - */ -export const getAlbums = async ({ - ids, - market, -}: { - ids: string[]; - market?: string; -}) => { - const response = await spotifyClient.get(`/albums`, { - params: { - market, - ids: ids.join(","), - }, - }); - return response.data as { albums: Album[] }; -}; - -/** - * Get Spotify catalog information about an album’s tracks. Optional parameters can be used to limit the number of tracks returned. - * @param {string} id - The Spotify ID of the album. - * @param {number=} limit - The maximum number of items to return. Default: 20. Minimum: 1. Maximum: 50. - * @param {string=} market - An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. -If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. -Note: If neither market or user country are provided, the content is considered unavailable for the client. -Users can view the country that is associated with their account in the account settings. - * @param {number=} offset - The index of the first item to return. Default: 0 (the first item). Use with limit to get the next set of items. - * @returns {PaginateResult} Pages of content - */ -export const getAlbumTracks = async ({ - id, - ...params -}: { - id: string; - limit?: number; - offset?: number; - market?: string; -}) => { - const response = await spotifyClient.get(`/albums/${id}/tracks`, { - params, - }); - return response.data as PaginateResult; -}; - -/** - * Get a list of the albums saved in the current Spotify user's 'Your Music' library. - * @param {number=} limit - The maximum number of items to return. Default: 20. Minimum: 1. Maximum: 50. - * @param {string=} market - An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. -If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. -Note: If neither market or user country are provided, the content is considered unavailable for the client. -Users can view the country that is associated with their account in the account settings. - * @param {number=} offset - The index of the first item to return. Default: 0 (the first item). Use with limit to get the next set of items. - * @returns {PaginateResult} Pages of content - */ -export const getSavedAlbum = async ( - params: { - limit?: number; - offset?: number; - market?: string; - } = {} -) => { - const response = await spotifyClient.get(`/me/albums`, { - params, - }); - return response.data as PaginateResult; -}; - -/** - * Save one or more albums to the current user's 'Your Music' library. - * @param {string[]} ids List of the Spotify IDs for the albums. Maximum: 20 IDs. - */ -export const saveAlbums = async (ids: string[]) => { - const response = await spotifyClient.put(`/me/albums`, { - ids, - }); - return response.data as void; -}; - -/** - * Remove one or more albums from the current user's 'Your Music' library. - * @param {string[]} ids List of the Spotify IDs for the albums. Maximum: 20 IDs. - */ -export const removeAlbums = async (ids: string[]) => { - const response = await spotifyClient.delete(`/me/albums`, { - data: { ids }, - }); - return response.data as void; -}; - -/** - * Check if one or more albums is already saved in the current Spotify user's 'Your Music' library. - * @param {string[]} ids List of the Spotify IDs for the albums. Maximum: 20 IDs. - */ -export const checkSavedAlbums = async (ids: string[]) => { - const response = await spotifyClient.get(`/me/albums`, { - params: { ids }, - }); - return response.data as boolean[]; -}; - -/** - * Get a list of new album releases featured in Spotify (shown, for example, on a Spotify player’s “Browse” tab). - * @param {number=} limit - The maximum number of items to return. Default: 20. Minimum: 1. Maximum: 50. - * @param {string=} country - A country: an ISO 3166-1 alpha-2 country code. Provide this parameter if you want the list of returned items to be relevant to a particular country. If omitted, the returned items will be relevant to all countries. - * @param {number=} offset - The index of the first item to return. Default: 0 (the first item). Use with limit to get the next set of items. - * @returns {PaginateResult} Pages of content - */ -export const getNewReleases = async ( - params: { - country?: string; - offset?: number; - limit?: number; - } = {} -) => { - const response = await spotifyClient.get(`/browse/new-releases`, { - params, - }); - return response.data as PaginateResult; -}; diff --git a/src/api/albums/hooks.tsx b/src/api/albums/hooks.tsx new file mode 100644 index 0000000..5873f43 --- /dev/null +++ b/src/api/albums/hooks.tsx @@ -0,0 +1,8 @@ +import { useMemo } from "react"; +import { useSpotifyClient } from "../../hooks"; +import { AlbumsApi } from "./AlbumsApi"; + +export const useAlbumsApi = () => { + const spotifyClient = useSpotifyClient(); + return useMemo(() => AlbumsApi.fromClient(spotifyClient), [spotifyClient]); +}; diff --git a/src/api/albums/index.ts b/src/api/albums/index.ts index 1d58e1a..dc5ded8 100644 --- a/src/api/albums/index.ts +++ b/src/api/albums/index.ts @@ -1,2 +1,3 @@ -export * from "./api"; +export * from "./AlbumsApi"; +export * from "./hooks"; export * from "./types"; diff --git a/src/api/artists/ArtistsApi.ts b/src/api/artists/ArtistsApi.ts new file mode 100644 index 0000000..6136c73 --- /dev/null +++ b/src/api/artists/ArtistsApi.ts @@ -0,0 +1,108 @@ +import { AxiosInstance } from "axios"; +import { Album } from "../albums"; +import { Track } from "../tracks"; +import { PaginateResult } from "../types"; +import { Artist, IncludeGroup } from "./types"; + +export class ArtistsApi { + private constructor(private readonly spotifyClient: AxiosInstance) {} + + public static fromClient(spotifyClient: AxiosInstance) { + return new ArtistsApi(spotifyClient); + } + + /** + * Get Spotify catalog information for a single artist identified by their unique Spotify ID. + * @param {string} id - The Spotify ID of the artist. + * @returns {Artist} An artist + */ + getArtist = async ({ id }: { id: string }) => { + const response = await this.spotifyClient.get(`/artists/${id}`); + return response.data as Artist; + }; + + /** + * Get Spotify catalog information for several artists based on their Spotify IDs. + * @param {string[]} ids - List of the Spotify IDs for the artists. Maximum: 50 IDs. + * @returns {Artist[]} A set of artists + */ + getArtists = async ({ ids }: { ids: string[] }) => { + const response = await this.spotifyClient.get(`/artists`, { + params: { + ids: ids.join(","), + }, + }); + return response.data as Artist[]; + }; + + /** + * Get Spotify catalog information about an artist's albums. + * @param {string} id - The Spotify ID of the artist. + * @param {IncludeGroups=} include_groups - A comma-separated list of keywords that will be used to filter the response. If not supplied, all album types will be returned. + Valid values are: + - album + - single + - appears_on + - compilation + * @param {number=} limit - The maximum number of items to return. Default: 20. Minimum: 1. Maximum: 50. + * @param {string=} market - An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. + If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. + Note: If neither market or user country are provided, the content is considered unavailable for the client. + Users can view the country that is associated with their account in the account settings. + * @param {number=} offset - The index of the first item to return. Default: 0 (the first item). Use with limit to get the next set of items. + * @returns {PaginateResult} Pages of content + */ + getArtistAlbums = async ({ + id, + include_groups = [], + ...params + }: { + id: string; + include_groups?: IncludeGroup[]; + limit?: number; + market?: string; + offset?: number; + }) => { + const response = await this.spotifyClient.get(`/artists/${id}/albums`, { + params: { + include_groups, + ...params, + }, + }); + return response.data as PaginateResult; + }; + + /** + * Get Spotify catalog information about an artist's top tracks by country. + * @param {string} id - The Spotify ID of the artist. + * @param {string=} market - An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. + If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. + Note: If neither market or user country are provided, the content is considered unavailable for the client. + Users can view the country that is associated with their account in the account settings. + * @returns {{tracks: Track[]}} A set of tracks + */ + getArtistTopTracks = async ({ + id, + ...params + }: { + id: string; + market?: string; + }) => { + const response = await this.spotifyClient.get(`/artists/${id}/top-tracks`, { + params, + }); + return response.data as { tracks: Track[] }; + }; + + /** + * Get Spotify catalog information about artists similar to a given artist. Similarity is based on analysis of the Spotify community's listening history. + * @param {string} id - The Spotify ID of the artist. + * @returns {{artists: Artist[]}} A set of artists + */ + getRelatedArtists = async ({ id }: { id: string }) => { + const response = await this.spotifyClient.get( + `/artists/${id}/related-artists` + ); + return response.data as Artist[]; + }; +} diff --git a/src/api/artists/api.ts b/src/api/artists/api.ts deleted file mode 100644 index c562baf..0000000 --- a/src/api/artists/api.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { Album } from "../albums"; -import { spotifyClient } from "../client"; -import { Track } from "../tracks"; -import { PaginateResult } from "../types"; -import { Artist } from "./types"; - -/** - * Get Spotify catalog information for a single artist identified by their unique Spotify ID. - * @param {string} id - The Spotify ID of the artist. - * @returns {Artist} An artist - */ -export const getArtist = async ({ id }: { id: string }) => { - const response = await spotifyClient.get(`/artists/${id}`); - return response.data as Artist; -}; - -/** - * Get Spotify catalog information for several artists based on their Spotify IDs. - * @param {string[]} ids - List of the Spotify IDs for the artists. Maximum: 50 IDs. - * @returns {Artist[]} A set of artists - */ -export const getArtists = async ({ ids }: { ids: string[] }) => { - const response = await spotifyClient.get(`/artists`, { - params: { - ids: ids.join(","), - }, - }); - return response.data as Artist[]; -}; - -type IncludeGroup = "album" | "single" | "appears_on" | "compilation"; - -/** - * Get Spotify catalog information about an artist's albums. - * @param {string} id - The Spotify ID of the artist. - * @param {IncludeGroups=} include_groups - A comma-separated list of keywords that will be used to filter the response. If not supplied, all album types will be returned. -Valid values are: -- album -- single -- appears_on -- compilation - * @param {number=} limit - The maximum number of items to return. Default: 20. Minimum: 1. Maximum: 50. - * @param {string=} market - An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. -If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. -Note: If neither market or user country are provided, the content is considered unavailable for the client. -Users can view the country that is associated with their account in the account settings. - * @param {number=} offset - The index of the first item to return. Default: 0 (the first item). Use with limit to get the next set of items. - * @returns {PaginateResult} Pages of content - */ -export const getArtistAlbums = async ({ - id, - include_groups = [], - ...params -}: { - id: string; - include_groups?: IncludeGroup[]; - limit?: number; - market?: string; - offset?: number; -}) => { - const response = await spotifyClient.get(`/artists/${id}/albums`, { - params: { - include_groups, - ...params, - }, - }); - return response.data as PaginateResult; -}; - -/** - * Get Spotify catalog information about an artist's top tracks by country. - * @param {string} id - The Spotify ID of the artist. - * @param {string=} market - An ISO 3166-1 alpha-2 country code. If a country code is specified, only content that is available in that market will be returned. -If a valid user access token is specified in the request header, the country associated with the user account will take priority over this parameter. -Note: If neither market or user country are provided, the content is considered unavailable for the client. -Users can view the country that is associated with their account in the account settings. - * @returns {{tracks: Track[]}} A set of tracks - */ -export const getArtistTopTracks = async ({ - id, - ...params -}: { - id: string; - market?: string; -}) => { - const response = await spotifyClient.get(`/artists/${id}/top-tracks`, { - params, - }); - return response.data as { tracks: Track[] }; -}; - -/** - * Get Spotify catalog information about artists similar to a given artist. Similarity is based on analysis of the Spotify community's listening history. - * @param {string} id - The Spotify ID of the artist. - * @returns {{artists: Artist[]}} A set of artists - */ -export const getRelatedArtists = async ({ id }: { id: string }) => { - const response = await spotifyClient.get(`/artists/${id}/related-artists`); - return response.data as Artist[]; -}; diff --git a/src/api/artists/hooks.tsx b/src/api/artists/hooks.tsx new file mode 100644 index 0000000..5f45e15 --- /dev/null +++ b/src/api/artists/hooks.tsx @@ -0,0 +1,8 @@ +import { useMemo } from "react"; +import { useSpotifyClient } from "../../hooks"; +import { ArtistsApi } from "./ArtistsApi"; + +export const useArtistsApi = () => { + const spotifyClient = useSpotifyClient(); + return useMemo(() => ArtistsApi.fromClient(spotifyClient), [spotifyClient]); +}; diff --git a/src/api/artists/index.ts b/src/api/artists/index.ts index 1d58e1a..dfb2232 100644 --- a/src/api/artists/index.ts +++ b/src/api/artists/index.ts @@ -1,2 +1,3 @@ -export * from "./api"; +export * from "./ArtistsApi"; +export * from "./hooks"; export * from "./types"; diff --git a/src/api/artists/types.ts b/src/api/artists/types.ts index 14eb0c6..fb7006c 100644 --- a/src/api/artists/types.ts +++ b/src/api/artists/types.ts @@ -1,5 +1,7 @@ import { ExternalUrls, Image } from "../types"; +export type IncludeGroup = "album" | "single" | "appears_on" | "compilation"; + /** * @property {string} href - This will always be set to null, as the Web API does not support it at the moment. * @property {number} total - The total number of followers. diff --git a/src/api/categories/CategoriesApi.ts b/src/api/categories/CategoriesApi.ts new file mode 100644 index 0000000..2aba03b --- /dev/null +++ b/src/api/categories/CategoriesApi.ts @@ -0,0 +1,58 @@ +import { AxiosInstance } from "axios"; +import { PaginateResult } from "../types"; +import { Category } from "./types"; + +export class CategoriesApi { + private constructor(private readonly spotifyClient: AxiosInstance) {} + + public static fromClient(spotifyClient: AxiosInstance) { + return new CategoriesApi(spotifyClient); + } + + /** + * Get a list of categories used to tag items in Spotify (on, for example, the Spotify player’s “Browse” tab). + * @param {string} country - A country: an ISO 3166-1 alpha-2 country code. Provide this parameter if you want to narrow the list of returned categories to those relevant to a particular country. If omitted, the returned items will be globally relevant. + * @param {number} limit - The maximum number of items to return. Default: 20. Minimum: 1. Maximum: 50. + * @param {string} locale - The desired language, consisting of an ISO 639-1 language code and an ISO 3166-1 alpha-2 country code, joined by an underscore. For example: es_MX, meaning "Spanish (Mexico)". Provide this parameter if you want the category metadata returned in a particular language. + Note: if locale is not supplied, or if the specified language is not available, all strings will be returned in the Spotify default language (American English). The locale parameter, combined with the country parameter, may give odd results if not carefully matched. For example country=SE&locale=de_DE will return a list of categories relevant to Sweden but as German language strings. + * @param {number} offset - The index of the first item to return. Default: 0 (the first item). Use with limit to get the next set of items. + * @returns {{ categories: PaginateResult }} A paged set of categories + */ + getSeveralBrowseCategories = async ( + params: { + country?: string; + limit?: number; + locale?: string; + offset?: number; + } = {} + ) => { + const response = await this.spotifyClient.get(`/browse/categories`, { + params, + }); + return response.data as { categories: PaginateResult }; + }; + + /** + * Get a single category used to tag items in Spotify (on, for example, the Spotify player’s “Browse” tab). + * @param {string} country - A country: an ISO 3166-1 alpha-2 country code. Provide this parameter if you want to narrow the list of returned categories to those relevant to a particular country. If omitted, the returned items will be globally relevant. + * @param {string} locale - The desired language, consisting of an ISO 639-1 language code and an ISO 3166-1 alpha-2 country code, joined by an underscore. For example: es_MX, meaning "Spanish (Mexico)". Provide this parameter if you want the category metadata returned in a particular language. + Note: if locale is not supplied, or if the specified language is not available, all strings will be returned in the Spotify default language (American English). The locale parameter, combined with the country parameter, may give odd results if not carefully matched. For example country=SE&locale=de_DE will return a list of categories relevant to Sweden but as German language strings. + * @returns {Category} A category + */ + getSingleBrowseCategories = async ({ + categoryId, + ...params + }: { + categoryId: string; + country?: string; + locale?: string; + }) => { + const response = await this.spotifyClient.get( + `/browse/category/${categoryId}`, + { + params, + } + ); + return response.data as Category; + }; +} diff --git a/src/api/categories/api.ts b/src/api/categories/api.ts deleted file mode 100644 index e6b9f9c..0000000 --- a/src/api/categories/api.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { spotifyClient } from "../client"; -import { PaginateResult } from "../types"; -import { Category } from "./types"; - -/** - * Get a list of categories used to tag items in Spotify (on, for example, the Spotify player’s “Browse” tab). - * @param {string} country - A country: an ISO 3166-1 alpha-2 country code. Provide this parameter if you want to narrow the list of returned categories to those relevant to a particular country. If omitted, the returned items will be globally relevant. - * @param {number} limit - The maximum number of items to return. Default: 20. Minimum: 1. Maximum: 50. - * @param {string} locale - The desired language, consisting of an ISO 639-1 language code and an ISO 3166-1 alpha-2 country code, joined by an underscore. For example: es_MX, meaning "Spanish (Mexico)". Provide this parameter if you want the category metadata returned in a particular language. -Note: if locale is not supplied, or if the specified language is not available, all strings will be returned in the Spotify default language (American English). The locale parameter, combined with the country parameter, may give odd results if not carefully matched. For example country=SE&locale=de_DE will return a list of categories relevant to Sweden but as German language strings. - * @param {number} offset - The index of the first item to return. Default: 0 (the first item). Use with limit to get the next set of items. - * @returns {{ categories: PaginateResult }} A paged set of categories - */ -export const getSeveralBrowseCategories = async ( - params: { - country?: string; - limit?: number; - locale?: string; - offset?: number; - } = {} -) => { - const response = await spotifyClient.get(`/browse/categories`, { params }); - return response.data as { categories: PaginateResult }; -}; - -/** - * Get a single category used to tag items in Spotify (on, for example, the Spotify player’s “Browse” tab). - * @param {string} country - A country: an ISO 3166-1 alpha-2 country code. Provide this parameter if you want to narrow the list of returned categories to those relevant to a particular country. If omitted, the returned items will be globally relevant. - * @param {string} locale - The desired language, consisting of an ISO 639-1 language code and an ISO 3166-1 alpha-2 country code, joined by an underscore. For example: es_MX, meaning "Spanish (Mexico)". Provide this parameter if you want the category metadata returned in a particular language. -Note: if locale is not supplied, or if the specified language is not available, all strings will be returned in the Spotify default language (American English). The locale parameter, combined with the country parameter, may give odd results if not carefully matched. For example country=SE&locale=de_DE will return a list of categories relevant to Sweden but as German language strings. - * @returns {Category} A category - */ -export const getSingleBrowseCategories = async ({ - categoryId, - ...params -}: { - categoryId: string; - country?: string; - locale?: string; -}) => { - const response = await spotifyClient.get(`/browse/category/${categoryId}`, { - params, - }); - return response.data as Category; -}; diff --git a/src/api/categories/hooks.tsx b/src/api/categories/hooks.tsx new file mode 100644 index 0000000..b6ed5e3 --- /dev/null +++ b/src/api/categories/hooks.tsx @@ -0,0 +1,11 @@ +import { useMemo } from "react"; +import { useSpotifyClient } from "../../hooks"; +import { CategoriesApi } from "./CategoriesApi"; + +export const useCategoriesApi = () => { + const spotifyClient = useSpotifyClient(); + return useMemo( + () => CategoriesApi.fromClient(spotifyClient), + [spotifyClient] + ); +}; diff --git a/src/api/categories/index.ts b/src/api/categories/index.ts index 1d58e1a..f6abbb9 100644 --- a/src/api/categories/index.ts +++ b/src/api/categories/index.ts @@ -1,2 +1,3 @@ -export * from "./api"; +export * from "./CategoriesApi"; +export * from "./hooks"; export * from "./types"; diff --git a/src/api/client.ts b/src/api/client.ts deleted file mode 100644 index ba10956..0000000 --- a/src/api/client.ts +++ /dev/null @@ -1,9 +0,0 @@ -import axios from "axios"; - -export const spotifyClient = axios.create({ - baseURL: "https://api.spotify.com/v1", - responseType: "json", - headers: { - "Content-Type": "application/json", - }, -}); diff --git a/src/api/genres/GenresApi.ts b/src/api/genres/GenresApi.ts new file mode 100644 index 0000000..24aea97 --- /dev/null +++ b/src/api/genres/GenresApi.ts @@ -0,0 +1,20 @@ +import { AxiosInstance } from "axios"; + +export class GenresApi { + private constructor(private readonly spotifyClient: AxiosInstance) {} + + public static fromClient(spotifyClient: AxiosInstance) { + return new GenresApi(spotifyClient); + } + + /** + * Retrieve a list of available genres seed parameter values for recommendations. + * @returns {{ genres: string[] }} A set of genres + */ + getGenres = async () => { + const response = await this.spotifyClient.get( + `/recommendations/available-genre-seeds` + ); + return response.data as { genres: string[] }; + }; +} diff --git a/src/api/genres/api.ts b/src/api/genres/api.ts deleted file mode 100644 index b49d742..0000000 --- a/src/api/genres/api.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { spotifyClient } from "../client"; - -/** - * Retrieve a list of available genres seed parameter values for recommendations. - * @returns {{ genres: string[] }} A set of genres - */ -export const getAvailableMarkets = async () => { - const response = await spotifyClient.get( - `/recommendations/available-genre-seeds` - ); - return response.data as { genres: string[] }; -}; diff --git a/src/api/genres/hooks.tsx b/src/api/genres/hooks.tsx new file mode 100644 index 0000000..6772db7 --- /dev/null +++ b/src/api/genres/hooks.tsx @@ -0,0 +1,8 @@ +import { useMemo } from "react"; +import { useSpotifyClient } from "../../hooks"; +import { GenresApi } from "./GenresApi"; + +export const useGenresApi = () => { + const spotifyClient = useSpotifyClient(); + return useMemo(() => GenresApi.fromClient(spotifyClient), [spotifyClient]); +}; diff --git a/src/api/genres/index.ts b/src/api/genres/index.ts index d158c57..2d3c045 100644 --- a/src/api/genres/index.ts +++ b/src/api/genres/index.ts @@ -1 +1,2 @@ -export * from "./api"; +export * from "./GenresApi"; +export * from "./hooks"; diff --git a/src/api/index.ts b/src/api/index.ts index af50e5e..302536a 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -7,6 +7,4 @@ import * as player from "./player"; import * as tracks from "./tracks"; export * from "./types"; -export { spotifyClient } from "./client"; - export { albums, artists, categories, genres, markets, player, tracks }; diff --git a/src/api/markets/MarketsApi.ts b/src/api/markets/MarketsApi.ts new file mode 100644 index 0000000..f8c4450 --- /dev/null +++ b/src/api/markets/MarketsApi.ts @@ -0,0 +1,14 @@ +import { AxiosInstance } from "axios"; + +export class MarketsApi { + private constructor(private readonly spotifyClient: AxiosInstance) {} + + public static fromClient(spotifyClient: AxiosInstance) { + return new MarketsApi(spotifyClient); + } + + getAvailableMarkets = async () => { + const response = await this.spotifyClient.get(`/markets`); + return response.data as { markets: string[] }; + }; +} diff --git a/src/api/markets/api.ts b/src/api/markets/api.ts deleted file mode 100644 index 7817350..0000000 --- a/src/api/markets/api.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { spotifyClient } from "../client"; - -/** - * Get the list of markets where Spotify is available. - * @returns {{ markets: string[] }} A markets object with an array of country codes - */ -export const getAvailableMarkets = async () => { - const response = await spotifyClient.get(`/markets`); - return response.data as { markets: string[] }; -}; diff --git a/src/api/markets/hooks.tsx b/src/api/markets/hooks.tsx new file mode 100644 index 0000000..0b49e16 --- /dev/null +++ b/src/api/markets/hooks.tsx @@ -0,0 +1,8 @@ +import { useMemo } from "react"; +import { useSpotifyClient } from "../../hooks"; +import { MarketsApi } from "./MarketsApi"; + +export const useMarketsApi = () => { + const spotifyClient = useSpotifyClient(); + return useMemo(() => MarketsApi.fromClient(spotifyClient), [spotifyClient]); +}; diff --git a/src/api/markets/index.ts b/src/api/markets/index.ts index d158c57..83a8137 100644 --- a/src/api/markets/index.ts +++ b/src/api/markets/index.ts @@ -1 +1,2 @@ -export * from "./api"; +export * from "./MarketsApi"; +export * from "./hooks"; diff --git a/src/api/player/PlayerApi.ts b/src/api/player/PlayerApi.ts new file mode 100644 index 0000000..69a645a --- /dev/null +++ b/src/api/player/PlayerApi.ts @@ -0,0 +1,178 @@ +import { AxiosInstance } from "axios"; +import { Device, Offset, RepeatState } from "./types"; + +export class PlayerApi { + private constructor(private readonly spotifyClient: AxiosInstance) {} + + public static fromClient(spotifyClient: AxiosInstance) { + return new PlayerApi(spotifyClient); + } + + /** + * Transfer playback to a new device and determine if it should start playing. + * @param {string} device_id - ID of the device on which playback should be started/transferred. + * @param {boolean=} play - true: ensure playback happens on new device. +false or not provided: keep the current playback state. + */ + transferPlayback = async ({ + device_id, + ...data + }: { + device_id: string; + play?: boolean; + }) => { + const response = await this.spotifyClient.put("/me/player", { + data: { + device_ids: [device_id], + ...data, + }, + }); + return response.data as void; + }; + + /** + * Get information about a user’s available devices. + * @returns {{devices: Device[]}} A set of devices + */ + getAvailableDevices = async () => { + const response = await this.spotifyClient.get(`/me/player/devices`); + return response.data as { devices: Device[] }; + }; + + /** + * Start a new context or resume current playback on the user's active device. + * @param {string=} device_id - The id of the device this command is targeting. If not supplied, the user's currently active device is the target. + * @param {string=} context_uri - Optional. Spotify URI of the context to play. Valid contexts are albums, artists & playlists. + * @param {string[]=} uris - Optional. List of the Spotify track URIs to play. + * @param {Offset=} offset - Optional. Indicates from where in the context playback should start. Only available when context_uri corresponds to an album or playlist object "position" is zero based and can’t be negative. + * @param {number=} position_ms - The position in milliseconds to seek to. Must be a positive number. Passing in a position that is greater than the length of the track will cause the player to start playing the next song. + */ + startOrResumePlayback = async ({ + device_id, + ...data + }: { + device_id?: string; + context_uri?: string; + uris?: string[]; + offset?: Offset; + position_ms?: number; + }) => { + const response = await this.spotifyClient.put(`/me/player/play`, { + params: { device_id }, + data, + }); + return response.data as void; + }; + + /** + * Pause playback on the user's account. + * @param {string=} device_id - The id of the device this command is targeting. If not supplied, the user's currently active device is the target. + */ + pausePlayback = async (params: { device_id?: string }) => { + const response = await this.spotifyClient.put(`/me/player/pause`, { + params, + }); + return response.data as void; + }; + + /** + * Skips to next track in the user’s queue. + * @param {string=} device_id - The id of the device this command is targeting. If not supplied, the user's currently active device is the target. + */ + skipToNext = async (params: { device_id?: string }) => { + const response = await this.spotifyClient.post(`/me/player/next`, { + params, + }); + return response.data as void; + }; + + /** + * Skips to previous track in the user’s queue. + * @param {string=} device_id - The id of the device this command is targeting. If not supplied, the user's currently active device is the target. + */ + skipToPrevious = async (params: { device_id?: string }) => { + const response = await this.spotifyClient.post(`/me/player/previous`, { + params, + }); + return response.data as void; + }; + + /** + * Seeks to the given position in the user’s currently playing track. + * @param {string=} device_id - The id of the device this command is targeting. If not supplied, the user's currently active device is the target. + * @param {number} position_ms - The position in milliseconds to seek to. Must be a positive number. Passing in a position that is greater than the length of the track will cause the player to start playing the next song. + */ + seekToPosition = async (params: { + position_ms: number; + device_id?: string; + }) => { + const response = await this.spotifyClient.put(`/me/player/seek`, { + params, + }); + return response.data as void; + }; + + /** + * Set the repeat mode for the user's playback. Options are repeat-track, repeat-context, and off. + * @param {RepeatState} state track, context or off. + track will repeat the current track. + context will repeat the current context. + off will turn repeat off. + * @param {string=} device_id - The id of the device this command is targeting. If not supplied, the user's currently active device is the target. + */ + setRepeatMode = async (params: { + state: RepeatState; + device_id?: string; + }) => { + const response = await this.spotifyClient.put(`/me/player/repeat`, { + params, + }); + return response.data as void; + }; + + /** + * Set the volume for the user’s current playback device. + * @param {boolean} volume_percent - The volume to set. Must be a value from 0 to 100 inclusive. + * @param {string=} device_id - The id of the device this command is targeting. If not supplied, the user's currently active device is the target. + */ + setPlaybackVolume = async (params: { + volume_percent: number; + device_id?: string; + }) => { + const response = await this.spotifyClient.put(`/me/player/volume`, { + params, + }); + return response.data as void; + }; + + /** + * Toggle shuffle on or off for user’s playback. + * @param {boolean} state - true : Shuffle user's playback. + false : Do not shuffle user's playback. + * @param {string=} device_id - The id of the device this command is targeting. If not supplied, the user's currently active device is the target. + */ + toggleShufflePlayback = async (params: { + state: boolean; + device_id?: string; + }) => { + const response = await this.spotifyClient.put(`/me/player/shuffle`, { + params, + }); + return response.data as void; + }; + + /** + * Add an item to the end of the user's current playback queue. + * @param {string} uri - The uri of the item to add to the queue. Must be a track or an episode uri. + * @param {string=} device_id - The id of the device this command is targeting. If not supplied, the user's currently active device is the target. + */ + addItemToPlaybackQueue = async (params: { + uri: string; + device_id?: string; + }) => { + const response = await this.spotifyClient.post(`/me/player/queue`, { + params, + }); + return response.data as void; + }; +} diff --git a/src/api/player/api.ts b/src/api/player/api.ts deleted file mode 100644 index 771539e..0000000 --- a/src/api/player/api.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { spotifyClient } from "../client"; -import { Device, RepeatState } from "./types"; - -/** - * Transfer playback to a new device and determine if it should start playing. - * @param {string} device_id - ID of the device on which playback should be started/transferred. - * @param {boolean=} play - true: ensure playback happens on new device. -false or not provided: keep the current playback state. - */ -export const transferPlayback = async ({ - device_id, - ...data -}: { - device_id: string; - play?: boolean; -}) => { - const response = await spotifyClient.put("/me/player", { - data: { - device_ids: [device_id], - ...data, - }, - }); - return response.data as void; -}; - -/** - * Get information about a user’s available devices. - * @returns {{devices: Device[]}} A set of devices - */ -export const getAvailableDevices = async () => { - const response = await spotifyClient.get(`/me/player/devices`); - return response.data as { devices: Device[] }; -}; - -export type Offset = { position: number} | { uri: string} - -/** - * Start a new context or resume current playback on the user's active device. - * @param {string=} device_id - The id of the device this command is targeting. If not supplied, the user's currently active device is the target. - * @param {string=} context_uri - Optional. Spotify URI of the context to play. Valid contexts are albums, artists & playlists. - * @param {string[]=} uris - Optional. List of the Spotify track URIs to play. - * @param {Offset=} offset - Optional. Indicates from where in the context playback should start. Only available when context_uri corresponds to an album or playlist object "position" is zero based and can’t be negative. - * @param {number=} position_ms - The position in milliseconds to seek to. Must be a positive number. Passing in a position that is greater than the length of the track will cause the player to start playing the next song. - */ -export const startOrResumePlayback = async ({device_id, ...data}: { device_id?: string; context_uri?: string; uris?: string[]; offset?: Offset; position_ms?: number }) => { - const response = await spotifyClient.put(`/me/player/play`, { params: { device_id }, data }); - return response.data as void; -}; - -/** - * Pause playback on the user's account. - * @param {string=} device_id - The id of the device this command is targeting. If not supplied, the user's currently active device is the target. - */ -export const pausePlayback = async (params: { device_id?: string }) => { - const response = await spotifyClient.put(`/me/player/pause`, { params }); - return response.data as void; -}; - -/** - * Skips to next track in the user’s queue. - * @param {string=} device_id - The id of the device this command is targeting. If not supplied, the user's currently active device is the target. - */ -export const skipToNext = async (params: { device_id?: string }) => { - const response = await spotifyClient.post(`/me/player/next`, { params }); - return response.data as void; -}; - -/** - * Skips to previous track in the user’s queue. - * @param {string=} device_id - The id of the device this command is targeting. If not supplied, the user's currently active device is the target. - */ -export const skipToPrevious = async (params: { device_id?: string }) => { - const response = await spotifyClient.post(`/me/player/previous`, { params }); - return response.data as void; -}; - -/** - * Seeks to the given position in the user’s currently playing track. - * @param {string=} device_id - The id of the device this command is targeting. If not supplied, the user's currently active device is the target. - * @param {number} position_ms - The position in milliseconds to seek to. Must be a positive number. Passing in a position that is greater than the length of the track will cause the player to start playing the next song. - */ -export const seekToPosition = async (params: { - position_ms: number; - device_id?: string; -}) => { - const response = await spotifyClient.put(`/me/player/seek`, { params }); - return response.data as void; -}; - -/** - * Set the repeat mode for the user's playback. Options are repeat-track, repeat-context, and off. - * @param {RepeatState} state track, context or off. -track will repeat the current track. -context will repeat the current context. -off will turn repeat off. - * @param {string=} device_id - The id of the device this command is targeting. If not supplied, the user's currently active device is the target. - */ -export const setRepeatMode = async (params: { - state: RepeatState; - device_id?: string; -}) => { - const response = await spotifyClient.put(`/me/player/repeat`, { params }); - return response.data as void; -}; - -/** - * Set the volume for the user’s current playback device. - * @param {boolean} volume_percent - The volume to set. Must be a value from 0 to 100 inclusive. - * @param {string=} device_id - The id of the device this command is targeting. If not supplied, the user's currently active device is the target. - */ -export const setPlaybackVolume = async (params: { - volume_percent: number; - device_id?: string; -}) => { - const response = await spotifyClient.put(`/me/player/volume`, { params }); - return response.data as void; -}; - -/** - * Toggle shuffle on or off for user’s playback. - * @param {boolean} state - true : Shuffle user's playback. -false : Do not shuffle user's playback. - * @param {string=} device_id - The id of the device this command is targeting. If not supplied, the user's currently active device is the target. - */ -export const toggleShufflePlayback = async (params: { - state: boolean; - device_id?: string; -}) => { - const response = await spotifyClient.put(`/me/player/shuffle`, { params }); - return response.data as void; -}; - -/** - * Add an item to the end of the user's current playback queue. - * @param {string} uri - The uri of the item to add to the queue. Must be a track or an episode uri. - * @param {string=} device_id - The id of the device this command is targeting. If not supplied, the user's currently active device is the target. - */ -export const addItemToPlaybackQueue = async (params: { - uri: string; - device_id?: string; -}) => { - const response = await spotifyClient.post(`/me/player/queue`, { params }); - return response.data as void; -}; diff --git a/src/api/player/hooks.tsx b/src/api/player/hooks.tsx new file mode 100644 index 0000000..5dc7697 --- /dev/null +++ b/src/api/player/hooks.tsx @@ -0,0 +1,8 @@ +import { useMemo } from "react"; +import { useSpotifyClient } from "../../hooks"; +import { PlayerApi } from "./PlayerApi"; + +export const usePlayerApi = () => { + const spotifyClient = useSpotifyClient(); + return useMemo(() => PlayerApi.fromClient(spotifyClient), [spotifyClient]); +}; diff --git a/src/api/player/index.ts b/src/api/player/index.ts index 1d58e1a..001f8f3 100644 --- a/src/api/player/index.ts +++ b/src/api/player/index.ts @@ -1,2 +1,3 @@ -export * from "./api"; +export * from "./PlayerApi"; +export * from "./hooks"; export * from "./types"; diff --git a/src/api/player/types.ts b/src/api/player/types.ts index eb150fa..a6b7570 100644 --- a/src/api/player/types.ts +++ b/src/api/player/types.ts @@ -20,3 +20,4 @@ export type Device = { export type RepeatState = "off" | "track" | "context"; export type ShuffleState = "on" | "off"; +export type Offset = { position: number} | { uri: string} diff --git a/src/client.ts b/src/client.ts new file mode 100644 index 0000000..ba5f985 --- /dev/null +++ b/src/client.ts @@ -0,0 +1,12 @@ +import axios, { AxiosRequestConfig } from "axios"; + +export const createSpotifyClient = (accessToken: string, overrides: AxiosRequestConfig = {}) => + axios.create({ + baseURL: "https://api.spotify.com/v1", + responseType: "json", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}`, + }, + ...overrides + }); diff --git a/src/hook.ts b/src/hook.ts deleted file mode 100644 index 5381f81..0000000 --- a/src/hook.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { useEffect } from "react"; -import * as spotifyApi from "./api"; - -export const useSpotify = ({ authorization }: { authorization: string }) => { - useEffect(() => { - spotifyApi.spotifyClient.defaults.headers.common["Authorization"] = - authorization; - }, [authorization]); - return spotifyApi; -}; diff --git a/src/hooks.tsx b/src/hooks.tsx new file mode 100644 index 0000000..f213425 --- /dev/null +++ b/src/hooks.tsx @@ -0,0 +1,14 @@ +import { AxiosRequestConfig } from "axios"; +import { useContext, useMemo } from "react"; +import { createSpotifyClient } from "./client"; +import { SpotifyApiContext } from "./provider"; + +export const useSpotifyClient = (overrides?: AxiosRequestConfig) => { + const spotifyApiContext = useContext(SpotifyApiContext); + if (!spotifyApiContext) + throw new Error("useSpotifyClient must be used within a SpotifyApiContext"); + const { accessToken } = spotifyApiContext; + return useMemo(() => { + return createSpotifyClient(accessToken, overrides); + }, [accessToken, overrides]); +}; diff --git a/src/index.ts b/src/index.ts index b261343..17c6d32 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,4 @@ -export * as api from "./api" -export { useSpotify } from "./hook"; \ No newline at end of file +export * from "./api"; +export * from "./provider"; +export * from "./hooks"; +export { createSpotifyClient } from "./client"; diff --git a/src/provider.tsx b/src/provider.tsx new file mode 100644 index 0000000..1b3b89c --- /dev/null +++ b/src/provider.tsx @@ -0,0 +1,11 @@ +import React, { FC, PropsWithChildren } from "react"; + +export const SpotifyApiContext = React.createContext({ accessToken: "" }); + +export const SpotifyApiProvider: FC< + PropsWithChildren<{ accessToken: string }> +> = ({ accessToken, children }) => ( + + {children} + +);