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') {