From 54f103776e83ee25907bbb6cec98a7313e64dc18 Mon Sep 17 00:00:00 2001 From: ricardo Date: Sat, 15 Aug 2020 22:36:11 -0400 Subject: [PATCH] :zap: add playlist-item resource --- .../Google/YouTube/ChannelDescription.ts | 4 +- .../Google/YouTube/PlaylistDescription.ts | 4 +- .../Google/YouTube/PlaylistItemDescription.ts | 408 ++++++++++++++++++ .../nodes/Google/YouTube/VideoDescription.ts | 3 +- .../nodes/Google/YouTube/YouTube.node.ts | 193 ++++++++- 5 files changed, 605 insertions(+), 7 deletions(-) create mode 100644 packages/nodes-base/nodes/Google/YouTube/PlaylistItemDescription.ts diff --git a/packages/nodes-base/nodes/Google/YouTube/ChannelDescription.ts b/packages/nodes-base/nodes/Google/YouTube/ChannelDescription.ts index 6a45d7ec69..4c6a5188f7 100644 --- a/packages/nodes-base/nodes/Google/YouTube/ChannelDescription.ts +++ b/packages/nodes-base/nodes/Google/YouTube/ChannelDescription.ts @@ -103,7 +103,7 @@ export const channelFields = [ }, }, description: 'The fields parameter specifies a comma-separated list of one or more channel resource properties that the API response will include.', - default: '' + default: ['*'], }, { displayName: 'Return All', @@ -308,7 +308,7 @@ export const channelFields = [ }, }, description: 'The fields parameter specifies a comma-separated list of one or more channel resource properties that the API response will include.', - default: '' + default: ['*'], }, /* -------------------------------------------------------------------------- */ /* channel:update */ diff --git a/packages/nodes-base/nodes/Google/YouTube/PlaylistDescription.ts b/packages/nodes-base/nodes/Google/YouTube/PlaylistDescription.ts index 8383ec6ef1..cb6508e458 100644 --- a/packages/nodes-base/nodes/Google/YouTube/PlaylistDescription.ts +++ b/packages/nodes-base/nodes/Google/YouTube/PlaylistDescription.ts @@ -214,7 +214,7 @@ export const playlistFields = [ }, }, description: 'The fields parameter specifies a comma-separated list of one or more playlist resource properties that the API response will include.', - default: '' + default: ['*'], }, { displayName: 'Options', @@ -346,7 +346,7 @@ export const playlistFields = [ }, }, description: 'The fields parameter specifies a comma-separated list of one or more playlist resource properties that the API response will include.', - default: '' + default: ['*'], }, { displayName: 'Return All', diff --git a/packages/nodes-base/nodes/Google/YouTube/PlaylistItemDescription.ts b/packages/nodes-base/nodes/Google/YouTube/PlaylistItemDescription.ts new file mode 100644 index 0000000000..88efa29acc --- /dev/null +++ b/packages/nodes-base/nodes/Google/YouTube/PlaylistItemDescription.ts @@ -0,0 +1,408 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const playlistItemOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + displayOptions: { + show: { + resource: [ + 'playlistItem', + ], + }, + }, + options: [ + { + name: 'Add', + value: 'add', + description: 'Add an item to a playlist', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a item from a playlist', + }, + { + name: 'Get', + value: 'get', + description: `Get a playlist's item`, + }, + { + name: 'Get All', + value: 'getAll', + description: 'Retrieve all playlist items', + }, + ], + default: 'add', + description: 'The operation to perform.' + } +] as INodeProperties[]; + +export const playlistItemFields = [ + /* -------------------------------------------------------------------------- */ + /* playlistItem:add */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Playlist ID', + name: 'playlistId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getPlaylists', + }, + required: true, + displayOptions: { + show: { + operation: [ + 'add', + ], + resource: [ + 'playlistItem', + ], + }, + }, + default: '', + }, + { + displayName: 'Video ID', + name: 'videoId', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'add', + ], + resource: [ + 'playlistItem', + ], + }, + }, + default: '', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + operation: [ + 'add', + ], + resource: [ + 'playlistItem', + ], + }, + }, + options: [ + { + displayName: 'Position', + name: 'position', + type: 'number', + typeOptions: { + minValue: 0, + }, + default: '', + description: `The order in which the item appears in the playlist. The value uses a zero-based index, so the first item has a position of 0, the second item has a position of 1, and so forth.`, + }, + { + displayName: 'Note', + name: 'note', + type: 'string', + default: '', + description: `A user-generated note for this item. The property value has a maximum length of 280 characters.`, + }, + { + displayName: 'Start At', + name: 'startAt', + type: 'dateTime', + default: '', + description: `The time, measured in seconds from the start of the video, when the video should start playing.`, + }, + { + displayName: 'End At', + name: 'endAt', + type: 'dateTime', + default: '', + description: `The time, measured in seconds from the start of the video, when the video should stop playing.`, + }, + { + displayName: 'On Behalf Of Content Owner', + name: 'onBehalfOfContentOwner', + type: 'string', + default: '', + description: `The onBehalfOfContentOwner parameter indicates that the request's authorization credentials identify
+ a YouTube CMS user who is acting on behalf of the content owner specified in the parameter value`, + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* playlistItem:get */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Playlist Item ID', + name: 'playlistItemId', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'playlistItem', + ], + }, + }, + default: '', + }, + { + displayName: 'Fields', + name: 'part', + type: 'multiOptions', + options: [ + { + name: '*', + value: '*', + }, + { + name: 'Content Details', + value: 'contentDetails', + }, + { + name: 'ID', + value: 'id', + }, + { + name: 'Snippet', + value: 'snippet', + }, + { + name: 'Status', + value: 'status', + }, + ], + required: true, + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'playlistItem', + ], + }, + }, + description: 'The fields parameter specifies a comma-separated list of one or more playlistItem resource properties that the API response will include.', + default: ['*'], + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + operation: [ + 'get', + ], + resource: [ + 'playlistItem', + ], + }, + }, + options: [ + { + displayName: 'On Behalf Of Content Owner', + name: 'onBehalfOfContentOwner', + type: 'string', + default: '', + description: `The onBehalfOfContentOwner parameter indicates that the request's authorization credentials identify
+ a YouTube CMS user who is acting on behalf of the content owner specified in the parameter value`, + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* playlistItem:delete */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Playlist Item ID', + name: 'playlistItemId', + type: 'string', + required: true, + displayOptions: { + show: { + operation: [ + 'delete', + ], + resource: [ + 'playlistItem', + ], + }, + }, + default: '', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + operation: [ + 'delete', + ], + resource: [ + 'playlistItem', + ], + }, + }, + options: [ + { + displayName: 'On Behalf Of Content Owner', + name: 'onBehalfOfContentOwner', + type: 'string', + default: '', + description: `The onBehalfOfContentOwner parameter indicates that the request's authorization credentials identify
+ a YouTube CMS user who is acting on behalf of the content owner specified in the parameter value`, + }, + ], + }, + /* -------------------------------------------------------------------------- */ + /* playlistItem:getAll */ + /* -------------------------------------------------------------------------- */ + { + displayName: 'Playlist ID', + name: 'playlistId', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getPlaylists', + }, + required: true, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'playlistItem', + ], + }, + }, + default: '' + }, + { + displayName: 'Fields', + name: 'part', + type: 'multiOptions', + options: [ + { + name: '*', + value: '*', + }, + { + name: 'Content Details', + value: 'contentDetails', + }, + { + name: 'ID', + value: 'id', + }, + { + name: 'Snippet', + value: 'snippet', + }, + { + name: 'Status', + value: 'status', + }, + ], + required: true, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'playlistItem', + ], + }, + }, + description: 'The fields parameter specifies a comma-separated list of one or more playlistItem resource properties that the API response will include.', + default: ['*'], + }, + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'playlistItem', + ], + }, + }, + default: false, + description: 'If all results should be returned or only up to a given limit.', + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'playlistItem', + ], + returnAll: [ + false, + ], + }, + }, + typeOptions: { + minValue: 1, + maxValue: 50, + }, + default: 25, + description: 'How many results to return.', + }, + { + displayName: 'Options', + name: 'options', + type: 'collection', + placeholder: 'Add Option', + default: {}, + displayOptions: { + show: { + operation: [ + 'getAll', + ], + resource: [ + 'playlistItem', + ], + }, + }, + options: [ + { + displayName: 'On Behalf Of Content Owner', + name: 'onBehalfOfContentOwner', + type: 'string', + default: '', + description: `The onBehalfOfContentOwner parameter indicates that the request's authorization credentials identify
+ a YouTube CMS user who is acting on behalf of the content owner specified in the parameter value`, + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Google/YouTube/VideoDescription.ts b/packages/nodes-base/nodes/Google/YouTube/VideoDescription.ts index aee1d0bc66..1f5f90902e 100644 --- a/packages/nodes-base/nodes/Google/YouTube/VideoDescription.ts +++ b/packages/nodes-base/nodes/Google/YouTube/VideoDescription.ts @@ -1,7 +1,6 @@ import { INodeProperties, } from 'n8n-workflow'; -import { id } from 'rhea'; export const videoOperations = [ { @@ -384,7 +383,7 @@ export const videoFields = [ }, }, description: 'The fields parameter specifies a comma-separated list of one or more video resource properties that the API response will include.', - default: '' + default: ['*'], }, { displayName: 'Options', diff --git a/packages/nodes-base/nodes/Google/YouTube/YouTube.node.ts b/packages/nodes-base/nodes/Google/YouTube/YouTube.node.ts index e838bdad46..f64d4aeb11 100644 --- a/packages/nodes-base/nodes/Google/YouTube/YouTube.node.ts +++ b/packages/nodes-base/nodes/Google/YouTube/YouTube.node.ts @@ -27,6 +27,11 @@ import { playlistFields, } from './PlaylistDescription'; +import { + playlistItemOperations, + playlistItemFields, +} from './PlaylistItemDescription'; + import { videoOperations, videoFields, @@ -60,7 +65,7 @@ export class YouTube implements INodeType { { name: 'youTubeOAuth2Api', required: true, - } + }, ], properties: [ { @@ -76,6 +81,10 @@ export class YouTube implements INodeType { name: 'Playlist', value: 'playlist', }, + { + name: 'Playlist Item', + value: 'playlistItem', + }, { name: 'Video', value: 'video', @@ -94,6 +103,9 @@ export class YouTube implements INodeType { ...playlistOperations, ...playlistFields, + ...playlistItemOperations, + ...playlistItemFields, + ...videoOperations, ...videoFields, @@ -169,6 +181,33 @@ export class YouTube implements INodeType { } return returnData; }, + // Get all the playlists to display them to user so that he can + // select them easily + async getPlaylists( + this: ILoadOptionsFunctions + ): Promise { + const returnData: INodePropertyOptions[] = []; + const qs: IDataObject = {}; + qs.part = 'snippet'; + qs.mine = true; + const playlists = await googleApiRequestAllItems.call( + this, + 'items', + 'GET', + '/youtube/v3/playlists', + {}, + qs, + ); + for (const playlist of playlists) { + const playlistName = playlist.snippet.title; + const playlistId = playlist.id; + returnData.push({ + name: playlistName, + value: playlistId + }); + } + return returnData; + }, } }; @@ -616,6 +655,158 @@ export class YouTube implements INodeType { responseData = { success: true }; } } + if (resource === 'playlistItem') { + //https://developers.google.com/youtube/v3/docs/playlistItems/list + if (operation === 'get') { + let part = this.getNodeParameter('part', i) as string[]; + const playlistItemId = this.getNodeParameter('playlistItemId', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + + if (part.includes('*')) { + part = [ + 'contentDetails', + 'id', + 'snippet', + 'status', + ]; + } + + qs.part = part.join(','); + + qs.id = playlistItemId; + + Object.assign(qs, options); + + responseData = await googleApiRequest.call( + this, + 'GET', + `/youtube/v3/playlistItems`, + {}, + qs + ); + + responseData = responseData.items; + } + //https://developers.google.com/youtube/v3/docs/playlistItems/list + if (operation === 'getAll') { + const returnAll = this.getNodeParameter('returnAll', i) as boolean; + let part = this.getNodeParameter('part', i) as string[]; + const options = this.getNodeParameter('options', i) as IDataObject; + const playlistId = this.getNodeParameter('playlistId', i) as string; + //const filters = this.getNodeParameter('filters', i) as IDataObject; + + if (part.includes('*')) { + part = [ + 'contentDetails', + 'id', + 'snippet', + 'status', + ]; + } + + qs.playlistId = playlistId; + + qs.part = part.join(','); + + Object.assign(qs, options); + + if (returnAll) { + responseData = await googleApiRequestAllItems.call( + this, + 'items', + 'GET', + `/youtube/v3/playlistItems`, + {}, + qs + ); + } else { + qs.maxResults = this.getNodeParameter('limit', i) as number; + responseData = await googleApiRequest.call( + this, + 'GET', + `/youtube/v3/playlistItems`, + {}, + qs + ); + responseData = responseData.items; + } + } + //https://developers.google.com/youtube/v3/docs/playlistItems/insert + if (operation === 'add') { + const playlistId = this.getNodeParameter('playlistId', i) as string; + const videoId = this.getNodeParameter('videoId', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + + qs.part = 'snippet, contentDetails'; + + const body: IDataObject = { + snippet: { + playlistId, + resourceId: { + kind: 'youtube#video', + videoId: videoId, + }, + }, + contentDetails: { + }, + + }; + + if (options.position) { + //@ts-ignore + body.snippet.position = options.position as number; + } + + if (options.note) { + //@ts-ignore + body.contentDetails.note = options.note as string; + } + + if (options.startAt) { + //@ts-ignore + body.contentDetails.startAt = options.startAt as string; + } + + if (options.endAt) { + //@ts-ignore + body.contentDetails.endAt = options.endAt as string; + } + + if (options.onBehalfOfContentOwner) { + qs.onBehalfOfContentOwner = options.onBehalfOfContentOwner as string; + } + + responseData = await googleApiRequest.call( + this, + 'POST', + '/youtube/v3/playlistItems', + body, + qs + ); + } + //https://developers.google.com/youtube/v3/docs/playlistItems/delete + if (operation === 'delete') { + const playlistItemId = this.getNodeParameter('playlistItemId', i) as string; + const options = this.getNodeParameter('options', i) as IDataObject; + + const body: IDataObject = { + id: playlistItemId, + }; + + if (options.onBehalfOfContentOwner) { + qs.onBehalfOfContentOwner = options.onBehalfOfContentOwner as string; + } + + responseData = await googleApiRequest.call( + this, + 'DELETE', + '/youtube/v3/playlistItems', + body, + ); + + responseData = { success: true }; + } + } if (resource === 'video') { //https://developers.google.com/youtube/v3/docs/search/list if (operation === 'getAll') {