mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-23 02:21:42 -08:00
✨ Spotify improvements (#1884)
* Add search resource * Add resume, volume functions to player resource * ⚡ Improvements to #1870 * ⚡ Improvements * ⚡ Minor improvements Co-authored-by: smamudhan <sm.amudhan@live.com> Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
This commit is contained in:
parent
a49624a17c
commit
8c693ba6e3
|
@ -13,6 +13,10 @@ import {
|
|||
NodeOperationError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
get,
|
||||
} from 'lodash';
|
||||
|
||||
/**
|
||||
* Make an API request to Spotify
|
||||
*
|
||||
|
@ -40,7 +44,6 @@ export async function spotifyApiRequest(this: IHookFunctions | IExecuteFunctions
|
|||
if (Object.keys(body).length > 0) {
|
||||
options.body = body;
|
||||
}
|
||||
|
||||
try {
|
||||
return await this.helpers.requestOAuth2.call(this, 'spotifyOAuth2Api', options);
|
||||
} catch (error) {
|
||||
|
@ -59,11 +62,16 @@ export async function spotifyApiRequestAllItems(this: IHookFunctions | IExecuteF
|
|||
|
||||
do {
|
||||
responseData = await spotifyApiRequest.call(this, method, endpoint, body, query, uri);
|
||||
returnData.push.apply(returnData, responseData[propertyName]);
|
||||
uri = responseData.next;
|
||||
|
||||
returnData.push.apply(returnData, get(responseData, propertyName));
|
||||
uri = responseData.next || responseData[propertyName.split('.')[0]].next;
|
||||
//remove the query as the query parameters are already included in the next, else api throws error.
|
||||
query = {};
|
||||
if (uri?.includes('offset=1000')) {
|
||||
return returnData;
|
||||
}
|
||||
} while (
|
||||
responseData['next'] !== null
|
||||
(responseData['next'] !== null && responseData['next'] !== undefined) ||
|
||||
responseData[propertyName.split('.')[0]].next !== null
|
||||
);
|
||||
|
||||
return returnData;
|
||||
|
|
|
@ -84,7 +84,8 @@ export class Spotify implements INodeType {
|
|||
|
||||
// --------------------------------------------------------------------------------------------------------
|
||||
// Player Operations
|
||||
// Pause, Play, Get Recently Played, Get Currently Playing, Next Song, Previous Song, Add to Queue
|
||||
// Pause, Play, Resume, Get Recently Played, Get Currently Playing, Next Song, Previous Song,
|
||||
// Add to Queue, Set Volume
|
||||
// --------------------------------------------------------------------------------------------------------
|
||||
{
|
||||
displayName: 'Operation',
|
||||
|
@ -128,6 +129,16 @@ export class Spotify implements INodeType {
|
|||
value: 'recentlyPlayed',
|
||||
description: 'Get your recently played tracks.',
|
||||
},
|
||||
{
|
||||
name: 'Resume',
|
||||
value: 'resume',
|
||||
description: 'Resume playback on the current active device.',
|
||||
},
|
||||
{
|
||||
name: 'Set Volume',
|
||||
value: 'volume',
|
||||
description: 'Set volume on the current active device.',
|
||||
},
|
||||
{
|
||||
name: 'Start Music',
|
||||
value: 'startMusic',
|
||||
|
@ -207,6 +218,11 @@ export class Spotify implements INodeType {
|
|||
value: 'getTracks',
|
||||
description: `Get an album's tracks by URI or ID.`,
|
||||
},
|
||||
{
|
||||
name: `Search`,
|
||||
value: 'search',
|
||||
description: `Search albums by keyword.`,
|
||||
},
|
||||
],
|
||||
default: 'get',
|
||||
description: 'The operation to perform.',
|
||||
|
@ -227,10 +243,33 @@ export class Spotify implements INodeType {
|
|||
'getTracks',
|
||||
],
|
||||
},
|
||||
hide: {
|
||||
operation: [
|
||||
'search',
|
||||
],
|
||||
},
|
||||
},
|
||||
placeholder: 'spotify:album:1YZ3k65Mqw3G8FzYlW1mmp',
|
||||
description: `The album's Spotify URI or ID.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Search Keyword',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'The keyword term to search for.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'album',
|
||||
],
|
||||
operation: [
|
||||
'search',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------
|
||||
// Artist Operations
|
||||
|
@ -268,6 +307,11 @@ export class Spotify implements INodeType {
|
|||
value: 'getTopTracks',
|
||||
description: `Get an artist's top tracks by URI or ID.`,
|
||||
},
|
||||
{
|
||||
name: `Search`,
|
||||
value: 'search',
|
||||
description: `Search artists by keyword.`,
|
||||
},
|
||||
],
|
||||
default: 'get',
|
||||
description: 'The operation to perform.',
|
||||
|
@ -284,6 +328,11 @@ export class Spotify implements INodeType {
|
|||
'artist',
|
||||
],
|
||||
},
|
||||
hide: {
|
||||
operation: [
|
||||
'search',
|
||||
],
|
||||
},
|
||||
},
|
||||
placeholder: 'spotify:artist:4LLpKhyESsyAXpc4laK94U',
|
||||
description: `The artist's Spotify URI or ID.`,
|
||||
|
@ -308,6 +357,25 @@ export class Spotify implements INodeType {
|
|||
description: `Top tracks in which country? Enter the postal abbriviation.`,
|
||||
},
|
||||
|
||||
{
|
||||
displayName: 'Search Keyword',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'The keyword term to search for.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'artist',
|
||||
],
|
||||
operation: [
|
||||
'search',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------
|
||||
// Playlist Operations
|
||||
// Get a Playlist, Get a Playlist's Tracks, Add/Remove a Song from a Playlist, Get a User's Playlists
|
||||
|
@ -354,6 +422,11 @@ export class Spotify implements INodeType {
|
|||
value: 'delete',
|
||||
description: 'Remove tracks from a playlist by track and playlist URI or ID.',
|
||||
},
|
||||
{
|
||||
name: `Search`,
|
||||
value: 'search',
|
||||
description: `Search playlists by keyword.`,
|
||||
},
|
||||
],
|
||||
default: 'add',
|
||||
description: 'The operation to perform.',
|
||||
|
@ -483,6 +556,24 @@ export class Spotify implements INodeType {
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Search Keyword',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'The keyword term to search for.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'playlist',
|
||||
],
|
||||
operation: [
|
||||
'search',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// -----------------------------------------------------
|
||||
// Track Operations
|
||||
|
@ -510,6 +601,11 @@ export class Spotify implements INodeType {
|
|||
value: 'getAudioFeatures',
|
||||
description: 'Get audio features for a track by URI or ID.',
|
||||
},
|
||||
{
|
||||
name: 'Search',
|
||||
value: 'search',
|
||||
description: `Search tracks by keyword.`,
|
||||
},
|
||||
],
|
||||
default: 'track',
|
||||
description: 'The operation to perform.',
|
||||
|
@ -526,10 +622,33 @@ export class Spotify implements INodeType {
|
|||
'track',
|
||||
],
|
||||
},
|
||||
hide: {
|
||||
operation: [
|
||||
'search',
|
||||
],
|
||||
},
|
||||
},
|
||||
placeholder: 'spotify:track:0xE4LEFzSNGsz1F6kvXsHU',
|
||||
description: `The track's Spotify URI or ID.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Search Keyword',
|
||||
name: 'query',
|
||||
type: 'string',
|
||||
required: true,
|
||||
default: '',
|
||||
description: 'The keyword term to search for.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'track',
|
||||
],
|
||||
operation: [
|
||||
'search',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// -----------------------------------------------------
|
||||
// Library Operations
|
||||
|
@ -595,6 +714,7 @@ export class Spotify implements INodeType {
|
|||
'library',
|
||||
'myData',
|
||||
'playlist',
|
||||
'track',
|
||||
],
|
||||
operation: [
|
||||
'getTracks',
|
||||
|
@ -603,6 +723,7 @@ export class Spotify implements INodeType {
|
|||
'getNewReleases',
|
||||
'getLikedTracks',
|
||||
'getFollowingArtists',
|
||||
'search',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -621,6 +742,7 @@ export class Spotify implements INodeType {
|
|||
'artist',
|
||||
'library',
|
||||
'playlist',
|
||||
'track',
|
||||
],
|
||||
operation: [
|
||||
'getTracks',
|
||||
|
@ -628,6 +750,7 @@ export class Spotify implements INodeType {
|
|||
'getUserPlaylists',
|
||||
'getNewReleases',
|
||||
'getLikedTracks',
|
||||
'search',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
|
@ -664,6 +787,28 @@ export class Spotify implements INodeType {
|
|||
},
|
||||
description: `The number of items to return.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Volume',
|
||||
name: 'volumePercent',
|
||||
type: 'number',
|
||||
default: 50,
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'player',
|
||||
],
|
||||
operation: [
|
||||
'volume',
|
||||
],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
maxValue: 100,
|
||||
},
|
||||
description: `The volume percentage to set.`,
|
||||
},
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filters',
|
||||
|
@ -691,10 +836,39 @@ export class Spotify implements INodeType {
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filters',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Filter',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'playlist',
|
||||
'artist',
|
||||
'track',
|
||||
'album',
|
||||
],
|
||||
operation: [
|
||||
'search',
|
||||
],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Country',
|
||||
name: 'market',
|
||||
type: 'options',
|
||||
options: isoCountryCodes.map(({ name, alpha2 }) => ({ name, value: alpha2 })),
|
||||
default: '',
|
||||
description: `If a country code is specified, only content that is playable in that market is returned.`,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
// Get all of the incoming input data to loop through
|
||||
const items = this.getInputData();
|
||||
|
@ -803,6 +977,28 @@ export class Spotify implements INodeType {
|
|||
|
||||
responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||
|
||||
responseData = { success: true };
|
||||
} else if (operation === 'resume') {
|
||||
requestMethod = 'PUT';
|
||||
|
||||
endpoint = `/me/player/play`;
|
||||
|
||||
responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||
|
||||
responseData = { success: true };
|
||||
} else if (operation === 'volume') {
|
||||
requestMethod = 'PUT';
|
||||
|
||||
endpoint = `/me/player/volume`;
|
||||
|
||||
const volumePercent = this.getNodeParameter('volumePercent', i) as number;
|
||||
|
||||
qs = {
|
||||
volume_percent: volumePercent,
|
||||
};
|
||||
|
||||
responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||
|
||||
responseData = { success: true };
|
||||
}
|
||||
|
||||
|
@ -868,6 +1064,29 @@ export class Spotify implements INodeType {
|
|||
|
||||
responseData = responseData.items;
|
||||
}
|
||||
} else if (operation === 'search') {
|
||||
requestMethod = 'GET';
|
||||
|
||||
endpoint = '/search';
|
||||
|
||||
propertyName = 'albums.items';
|
||||
|
||||
returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
const q = this.getNodeParameter('query', i) as string;
|
||||
const filters = this.getNodeParameter('filters', i) as IDataObject;
|
||||
|
||||
qs = {
|
||||
q,
|
||||
type: 'album',
|
||||
...filters,
|
||||
};
|
||||
|
||||
if (returnAll === false) {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
qs.limit = limit;
|
||||
responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||
responseData = responseData.albums.items;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (resource === 'artist') {
|
||||
|
@ -876,7 +1095,7 @@ export class Spotify implements INodeType {
|
|||
// Artist Operations
|
||||
// -----------------------------
|
||||
|
||||
const uri = this.getNodeParameter('id', i) as string;
|
||||
const uri = this.getNodeParameter('id', i, '') as string;
|
||||
|
||||
const id = uri.replace('spotify:artist:', '');
|
||||
|
||||
|
@ -928,6 +1147,30 @@ export class Spotify implements INodeType {
|
|||
endpoint = `/artists/${id}`;
|
||||
|
||||
responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||
} else if (operation === 'search') {
|
||||
requestMethod = 'GET';
|
||||
|
||||
endpoint = '/search';
|
||||
|
||||
propertyName = 'artists.items';
|
||||
|
||||
returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
const q = this.getNodeParameter('query', i) as string;
|
||||
const filters = this.getNodeParameter('filters', i) as IDataObject;
|
||||
|
||||
qs = {
|
||||
q,
|
||||
limit: 50,
|
||||
type: 'artist',
|
||||
...filters,
|
||||
};
|
||||
|
||||
if (returnAll === false) {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
qs.limit = limit;
|
||||
responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||
responseData = responseData.artists.items;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (resource === 'playlist') {
|
||||
|
@ -1036,6 +1279,30 @@ export class Spotify implements INodeType {
|
|||
}
|
||||
|
||||
responseData = await spotifyApiRequest.call(this, 'POST', '/me/playlists', body, qs);
|
||||
} else if (operation === 'search') {
|
||||
requestMethod = 'GET';
|
||||
|
||||
endpoint = '/search';
|
||||
|
||||
propertyName = 'playlists.items';
|
||||
|
||||
returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
const q = this.getNodeParameter('query', i) as string;
|
||||
const filters = this.getNodeParameter('filters', i) as IDataObject;
|
||||
|
||||
qs = {
|
||||
q,
|
||||
type: 'playlist',
|
||||
limit: 50,
|
||||
...filters,
|
||||
};
|
||||
|
||||
if (returnAll === false) {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
qs.limit = limit;
|
||||
responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||
responseData = responseData.playlists.items;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (resource === 'track') {
|
||||
|
@ -1044,7 +1311,7 @@ export class Spotify implements INodeType {
|
|||
// Track Operations
|
||||
// -----------------------------
|
||||
|
||||
const uri = this.getNodeParameter('id', i) as string;
|
||||
const uri = this.getNodeParameter('id', i, '') as string;
|
||||
|
||||
const id = uri.replace('spotify:track:', '');
|
||||
|
||||
|
@ -1052,11 +1319,35 @@ export class Spotify implements INodeType {
|
|||
|
||||
if (operation === 'getAudioFeatures') {
|
||||
endpoint = `/audio-features/${id}`;
|
||||
responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||
} else if (operation === 'get') {
|
||||
endpoint = `/tracks/${id}`;
|
||||
}
|
||||
responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||
} else if (operation === 'search') {
|
||||
requestMethod = 'GET';
|
||||
|
||||
responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||
endpoint = '/search';
|
||||
|
||||
propertyName = 'tracks.items';
|
||||
|
||||
returnAll = this.getNodeParameter('returnAll', i) as boolean;
|
||||
const q = this.getNodeParameter('query', i) as string;
|
||||
const filters = this.getNodeParameter('filters', i) as IDataObject;
|
||||
|
||||
qs = {
|
||||
q,
|
||||
type: 'track',
|
||||
limit: 50,
|
||||
...filters,
|
||||
};
|
||||
|
||||
if (returnAll === false) {
|
||||
const limit = this.getNodeParameter('limit', i) as number;
|
||||
qs.limit = limit;
|
||||
responseData = await spotifyApiRequest.call(this, requestMethod, endpoint, body, qs);
|
||||
responseData = responseData.tracks.items;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (resource === 'library') {
|
||||
|
||||
|
|
Loading…
Reference in a new issue