feat(gmail): overhaul Gmail node + create gmail trigger (#3734)

This commit is contained in:
Michael Kret
2022-09-08 15:44:34 +03:00
committed by GitHub
parent ca8c2d6577
commit 74304db4e2
24 changed files with 3858 additions and 927 deletions

View File

@@ -0,0 +1,370 @@
import { INodeProperties } from 'n8n-workflow';
export const draftOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['draft'],
},
},
options: [
{
name: 'Create',
value: 'create',
action: 'Create a draft',
},
{
name: 'Delete',
value: 'delete',
action: 'Delete a draft',
},
{
name: 'Get',
value: 'get',
action: 'Get a draft',
},
{
name: 'Get Many',
value: 'getAll',
action: 'Get all drafts',
},
],
default: 'create',
},
];
export const draftFields: INodeProperties[] = [
{
displayName: 'Draft ID',
name: 'messageId',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: ['draft'],
operation: ['delete', 'get'],
},
},
placeholder: 'r-3254521568507167962',
},
{
displayName: 'Subject',
name: 'subject',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: ['draft'],
operation: ['create'],
},
},
placeholder: 'Hello World!',
},
{
displayName: 'HTML',
name: 'includeHtml',
type: 'boolean',
displayOptions: {
show: {
resource: ['draft'],
operation: ['create'],
},
},
default: false,
description: 'Whether the message should also be included as HTML',
},
{
displayName: 'HTML Message',
name: 'htmlMessage',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
includeHtml: [true],
resource: ['draft'],
operation: ['create'],
},
},
description: 'The HTML message body',
},
{
displayName: 'Message',
name: 'message',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: ['draft'],
operation: ['create'],
},
},
placeholder: 'Hello World!',
description:
'The message body. If HTML formatted, then you have to add and activate the option "HTML content" in the "Additional Options" section.',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
displayOptions: {
show: {
resource: ['draft'],
operation: ['create'],
},
},
default: {},
options: [
{
displayName: 'To Email',
name: 'toList',
type: 'string',
default: [],
typeOptions: {
multipleValues: true,
multipleValueButtonText: 'Add To Email',
},
placeholder: 'info@example.com',
description: 'The email addresses of the recipients',
},
{
displayName: 'CC Email',
name: 'ccList',
type: 'string',
description: 'The email addresses of the copy recipients',
typeOptions: {
multipleValues: true,
multipleValueButtonText: 'Add CC Email',
},
placeholder: 'info@example.com',
default: [],
},
{
displayName: 'BCC Email',
name: 'bccList',
type: 'string',
description: 'The email addresses of the blind copy recipients',
typeOptions: {
multipleValues: true,
multipleValueButtonText: 'Add BCC Email',
},
placeholder: 'info@example.com',
default: [],
},
{
displayName: 'Attachment',
name: 'attachmentsUi',
placeholder: 'Add Attachment',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
options: [
{
name: 'attachmentsBinary',
displayName: 'Attachment Binary',
values: [
{
displayName: 'Attachment Field Name (in Input)',
name: 'property',
type: 'string',
default: '',
description:
'Name of the binary property containing the data to be added to the email as an attachment. Multiple properties can be set separated by comma.',
},
],
},
],
default: {},
description: 'Array of supported attachments to add to the message',
},
],
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
displayOptions: {
show: {
resource: ['draft'],
operation: ['get'],
},
},
default: {},
options: [
{
displayName: 'Attachment Prefix',
name: 'dataPropertyAttachmentsPrefixName',
type: 'string',
default: 'attachment_',
displayOptions: {
hide: {
format: ['full', 'metadata', 'minimal', 'raw'],
},
},
description:
'Prefix for name of the binary property to which to write the attachments. An index starting with 0 will be added. So if name is "attachment_" the first attachment is saved to "attachment_0"',
},
{
displayName: 'Format',
name: 'format',
type: 'options',
options: [
{
name: 'Full',
value: 'full',
description:
'Returns the full email message data with body content parsed in the payload field',
},
{
name: 'Metadata',
value: 'metadata',
description: 'Returns only email message ID, labels, and email headers',
},
{
name: 'Minimal',
value: 'minimal',
description:
'Returns only email message ID and labels; does not return the email headers, body, or payload',
},
{
name: 'RAW',
value: 'raw',
description:
'Returns the full email message data with body content in the raw field as a base64url encoded string; the payload field is not used',
},
{
name: 'Resolved',
value: 'resolved',
description:
'Returns the full email with all data resolved and attachments saved as binary data',
},
],
default: 'resolved',
description: 'The format to return the message in',
},
],
},
/* -------------------------------------------------------------------------- */
/* draft:getAll */
/* -------------------------------------------------------------------------- */
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
operation: ['getAll'],
resource: ['draft'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
operation: ['getAll'],
resource: ['draft'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
maxValue: 500,
},
default: 10,
description: 'Max number of results to return',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
operation: ['getAll'],
resource: ['draft'],
},
},
options: [
{
displayName: 'Attachment Prefix',
name: 'dataPropertyAttachmentsPrefixName',
type: 'string',
default: 'attachment_',
displayOptions: {
hide: {
format: ['full', 'ids', 'metadata', 'minimal', 'raw'],
},
},
description:
'Prefix for name of the binary property to which to write the attachments. An index starting with 0 will be added. So if name is "attachment_" the first attachment is saved to "attachment_0"',
},
{
displayName: 'Format',
name: 'format',
type: 'options',
options: [
{
name: 'Full',
value: 'full',
description:
'Returns the full email message data with body content parsed in the payload field',
},
{
name: 'IDs',
value: 'ids',
description: 'Returns only the IDs of the emails',
},
{
name: 'Metadata',
value: 'metadata',
description: 'Returns only email message ID, labels, and email headers',
},
{
name: 'Minimal',
value: 'minimal',
description:
'Returns only email message ID and labels; does not return the email headers, body, or payload',
},
{
name: 'RAW',
value: 'raw',
description:
'Returns the full email message data with body content in the raw field as a base64url encoded string; the payload field is not used',
},
{
name: 'Resolved',
value: 'resolved',
description:
'Returns the full email with all data resolved and attachments saved as binary data',
},
],
default: 'resolved',
description: 'The format to return the message in',
},
{
displayName: 'Include Spam and Trash',
name: 'includeSpamTrash',
type: 'boolean',
default: false,
description: 'Whether to include messages from SPAM and TRASH in the results',
},
],
},
];

View File

@@ -0,0 +1,825 @@
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
import {
IExecuteFunctions,
} from 'n8n-core';
import {
IBinaryKeyData,
IDataObject,
ILoadOptionsFunctions,
INodeExecutionData,
INodePropertyOptions,
INodeType,
INodeTypeBaseDescription,
INodeTypeDescription,
} from 'n8n-workflow';
import {
encodeEmail,
extractEmail,
googleApiRequest,
googleApiRequestAllItems,
IEmail,
parseRawEmail,
} from '../GenericFunctions';
import {
messageFields,
messageOperations,
} from './MessageDescription';
import {
messageLabelFields,
messageLabelOperations,
} from './MessageLabelDescription';
import {
labelFields,
labelOperations,
} from './LabelDescription';
import {
draftFields,
draftOperations,
} from './DraftDescription';
import {
isEmpty,
} from 'lodash';
const versionDescription: INodeTypeDescription = {
displayName: 'Gmail',
name: 'gmail',
icon: 'file:gmail.svg',
group: ['transform'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume the Gmail API',
defaults: {
name: 'Gmail',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'googleApi',
required: true,
displayOptions: {
show: {
authentication: [
'serviceAccount',
],
},
},
},
{
name: 'gmailOAuth2',
required: true,
displayOptions: {
show: {
authentication: [
'oAuth2',
],
},
},
},
],
properties: [
{
displayName: 'Authentication',
name: 'authentication',
type: 'options',
options: [
{
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
name: 'OAuth2 (recommended)',
value: 'oAuth2',
},
{
name: 'Service Account',
value: 'serviceAccount',
},
],
default: 'oAuth2',
},
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Draft',
value: 'draft',
},
{
name: 'Label',
value: 'label',
},
{
name: 'Message',
value: 'message',
},
{
name: 'Message Label',
value: 'messageLabel',
},
],
default: 'draft',
},
//-------------------------------
// Draft Operations
//-------------------------------
...draftOperations,
...draftFields,
//-------------------------------
// Label Operations
//-------------------------------
...labelOperations,
...labelFields,
//-------------------------------
// Message Operations
//-------------------------------
...messageOperations,
...messageFields,
//-------------------------------
// MessageLabel Operations
//-------------------------------
...messageLabelOperations,
...messageLabelFields,
//-------------------------------
],
};
export class GmailV1 implements INodeType {
description: INodeTypeDescription;
constructor(baseDescription: INodeTypeBaseDescription) {
this.description = {
...baseDescription,
...versionDescription,
};
}
methods = {
loadOptions: {
// Get all the labels to display them to user so that he can
// select them easily
async getLabels(
this: ILoadOptionsFunctions,
): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [];
const labels = await googleApiRequestAllItems.call(
this,
'labels',
'GET',
'/gmail/v1/users/me/labels',
);
for (const label of labels) {
returnData.push({
name: label.name,
value: label.id,
});
}
return returnData.sort((a, b) => {
if (a.name < b.name) { return -1; }
if (a.name > b.name) { return 1; }
return 0;
});
},
},
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: INodeExecutionData[] = [];
const resource = this.getNodeParameter('resource', 0) as string;
const operation = this.getNodeParameter('operation', 0) as string;
let method = '';
let body: IDataObject = {};
let qs: IDataObject = {};
let endpoint = '';
let responseData;
for (let i = 0; i < items.length; i++) {
try {
if (resource === 'label') {
if (operation === 'create') {
//https://developers.google.com/gmail/api/v1/reference/users/labels/create
const labelName = this.getNodeParameter('name', i) as string;
const labelListVisibility = this.getNodeParameter('labelListVisibility', i) as string;
const messageListVisibility = this.getNodeParameter('messageListVisibility', i) as string;
method = 'POST';
endpoint = '/gmail/v1/users/me/labels';
body = {
labelListVisibility,
messageListVisibility,
name: labelName,
};
responseData = await googleApiRequest.call(this, method, endpoint, body, qs);
}
if (operation === 'delete') {
//https://developers.google.com/gmail/api/v1/reference/users/labels/delete
const labelId = this.getNodeParameter('labelId', i) as string[];
method = 'DELETE';
endpoint = `/gmail/v1/users/me/labels/${labelId}`;
responseData = await googleApiRequest.call(this, method, endpoint, body, qs);
responseData = { success: true };
}
if (operation === 'get') {
// https://developers.google.com/gmail/api/v1/reference/users/labels/get
const labelId = this.getNodeParameter('labelId', i);
method = 'GET';
endpoint = `/gmail/v1/users/me/labels/${labelId}`;
responseData = await googleApiRequest.call(this, method, endpoint, body, qs);
}
if (operation === 'getAll') {
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
responseData = await googleApiRequest.call(
this,
'GET',
`/gmail/v1/users/me/labels`,
{},
qs,
);
responseData = responseData.labels;
if (!returnAll) {
const limit = this.getNodeParameter('limit', i) as number;
responseData = responseData.splice(0, limit);
}
}
}
if (resource === 'messageLabel') {
if (operation === 'remove') {
//https://developers.google.com/gmail/api/v1/reference/users/messages/modify
const messageID = this.getNodeParameter('messageId', i);
const labelIds = this.getNodeParameter('labelIds', i) as string[];
method = 'POST';
endpoint = `/gmail/v1/users/me/messages/${messageID}/modify`;
body = {
removeLabelIds: labelIds,
};
responseData = await googleApiRequest.call(this, method, endpoint, body, qs);
}
if (operation === 'add') {
// https://developers.google.com/gmail/api/v1/reference/users/messages/modify
const messageID = this.getNodeParameter('messageId', i);
const labelIds = this.getNodeParameter('labelIds', i) as string[];
method = 'POST';
endpoint = `/gmail/v1/users/me/messages/${messageID}/modify`;
body = {
addLabelIds: labelIds,
};
responseData = await googleApiRequest.call(this, method, endpoint, body, qs);
}
}
if (resource === 'message') {
if (operation === 'send') {
// https://developers.google.com/gmail/api/v1/reference/users/messages/send
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
let toStr = '';
let ccStr = '';
let bccStr = '';
let attachmentsList: IDataObject[] = [];
const toList = this.getNodeParameter('toList', i) as IDataObject[];
toList.forEach((email) => {
toStr += `<${email}>, `;
});
if (additionalFields.ccList) {
const ccList = additionalFields.ccList as IDataObject[];
ccList.forEach((email) => {
ccStr += `<${email}>, `;
});
}
if (additionalFields.bccList) {
const bccList = additionalFields.bccList as IDataObject[];
bccList.forEach((email) => {
bccStr += `<${email}>, `;
});
}
if (additionalFields.attachmentsUi) {
const attachmentsUi = additionalFields.attachmentsUi as IDataObject;
const attachmentsBinary = [];
if (!isEmpty(attachmentsUi)) {
if (attachmentsUi.hasOwnProperty('attachmentsBinary')
&& !isEmpty(attachmentsUi.attachmentsBinary)
&& items[i].binary) {
// @ts-ignore
for (const { property } of attachmentsUi.attachmentsBinary as IDataObject[]) {
for (const binaryProperty of (property as string).split(',')) {
if (items[i].binary![binaryProperty] !== undefined) {
const binaryData = items[i].binary![binaryProperty];
const binaryDataBuffer = await this.helpers.getBinaryDataBuffer(i, binaryProperty);
attachmentsBinary.push({
name: binaryData.fileName || 'unknown',
content: binaryDataBuffer,
type: binaryData.mimeType,
});
}
}
}
}
qs = {
userId: 'me',
uploadType: 'media',
};
attachmentsList = attachmentsBinary;
}
}
const email: IEmail = {
from: additionalFields.senderName as string || '',
to: toStr,
cc: ccStr,
bcc: bccStr,
subject: this.getNodeParameter('subject', i) as string,
body: this.getNodeParameter('message', i) as string,
attachments: attachmentsList,
};
if (this.getNodeParameter('includeHtml', i, false) as boolean === true) {
email.htmlBody = this.getNodeParameter('htmlMessage', i) as string;
}
endpoint = '/gmail/v1/users/me/messages/send';
method = 'POST';
body = {
raw: await encodeEmail(email),
};
responseData = await googleApiRequest.call(this, method, endpoint, body, qs);
}
if (operation === 'reply') {
const id = this.getNodeParameter('messageId', i) as string;
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
let toStr = '';
let ccStr = '';
let bccStr = '';
let attachmentsList: IDataObject[] = [];
const toList = this.getNodeParameter('toList', i) as IDataObject[];
toList.forEach((email) => {
toStr += `<${email}>, `;
});
if (additionalFields.ccList) {
const ccList = additionalFields.ccList as IDataObject[];
ccList.forEach((email) => {
ccStr += `<${email}>, `;
});
}
if (additionalFields.bccList) {
const bccList = additionalFields.bccList as IDataObject[];
bccList.forEach((email) => {
bccStr += `<${email}>, `;
});
}
if (additionalFields.attachmentsUi) {
const attachmentsUi = additionalFields.attachmentsUi as IDataObject;
const attachmentsBinary = [];
if (!isEmpty(attachmentsUi)) {
if (attachmentsUi.hasOwnProperty('attachmentsBinary')
&& !isEmpty(attachmentsUi.attachmentsBinary)
&& items[i].binary) {
// @ts-ignore
for (const { property } of attachmentsUi.attachmentsBinary as IDataObject[]) {
for (const binaryProperty of (property as string).split(',')) {
if (items[i].binary![binaryProperty] !== undefined) {
const binaryData = items[i].binary![binaryProperty];
const binaryDataBuffer = await this.helpers.getBinaryDataBuffer(i, binaryProperty);
attachmentsBinary.push({
name: binaryData.fileName || 'unknown',
content: binaryDataBuffer,
type: binaryData.mimeType,
});
}
}
}
}
qs = {
userId: 'me',
uploadType: 'media',
};
attachmentsList = attachmentsBinary;
}
}
endpoint = `/gmail/v1/users/me/messages/${id}`;
qs.format = 'metadata';
const { payload } = await googleApiRequest.call(this, method, endpoint, body, qs);
if (toStr === '') {
for (const header of payload.headers as IDataObject[]) {
if (header.name === 'From') {
toStr = `<${extractEmail(header.value as string)}>,`;
break;
}
}
}
const subject = payload.headers.filter((data: { [key: string]: string }) => data.name === 'Subject')[0]?.value || '';
const references = payload.headers.filter((data: { [key: string]: string }) => data.name === 'References')[0]?.value || '';
const email: IEmail = {
from: additionalFields.senderName as string || '',
to: toStr,
cc: ccStr,
bcc: bccStr,
subject,
body: this.getNodeParameter('message', i) as string,
attachments: attachmentsList,
};
if (this.getNodeParameter('includeHtml', i, false) as boolean === true) {
email.htmlBody = this.getNodeParameter('htmlMessage', i) as string;
}
endpoint = '/gmail/v1/users/me/messages/send';
method = 'POST';
email.inReplyTo = id;
email.reference = references;
body = {
raw: await encodeEmail(email),
threadId: this.getNodeParameter('threadId', i) as string,
};
responseData = await googleApiRequest.call(this, method, endpoint, body, qs);
}
if (operation === 'get') {
//https://developers.google.com/gmail/api/v1/reference/users/messages/get
method = 'GET';
const id = this.getNodeParameter('messageId', i);
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
const format = additionalFields.format || 'resolved';
if (format === 'resolved') {
qs.format = 'raw';
} else {
qs.format = format;
}
endpoint = `/gmail/v1/users/me/messages/${id}`;
responseData = await googleApiRequest.call(this, method, endpoint, body, qs);
let nodeExecutionData: INodeExecutionData;
if (format === 'resolved') {
const dataPropertyNameDownload = additionalFields.dataPropertyAttachmentsPrefixName as string || 'attachment_';
nodeExecutionData = await parseRawEmail.call(this, responseData, dataPropertyNameDownload);
} else {
nodeExecutionData = {
json: responseData,
};
}
responseData = nodeExecutionData;
}
if (operation === 'getAll') {
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
Object.assign(qs, additionalFields);
if (qs.labelIds) {
// tslint:disable-next-line: triple-equals
if (qs.labelIds == '') {
delete qs.labelIds;
} else {
qs.labelIds = qs.labelIds as string[];
}
}
if (returnAll) {
responseData = await googleApiRequestAllItems.call(
this,
'messages',
'GET',
`/gmail/v1/users/me/messages`,
{},
qs,
);
} else {
qs.maxResults = this.getNodeParameter('limit', i) as number;
responseData = await googleApiRequest.call(
this,
'GET',
`/gmail/v1/users/me/messages`,
{},
qs,
);
responseData = responseData.messages;
}
if (responseData === undefined) {
responseData = [];
}
const format = additionalFields.format || 'resolved';
if (format !== 'ids') {
if (format === 'resolved') {
qs.format = 'raw';
} else {
qs.format = format;
}
for (let i = 0; i < responseData.length; i++) {
responseData[i] = await googleApiRequest.call(
this,
'GET',
`/gmail/v1/users/me/messages/${responseData[i].id}`,
body,
qs,
);
if (format === 'resolved') {
const dataPropertyNameDownload = additionalFields.dataPropertyAttachmentsPrefixName as string || 'attachment_';
responseData[i] = await parseRawEmail.call(this, responseData[i], dataPropertyNameDownload);
}
}
}
if (format !== 'resolved') {
responseData = this.helpers.returnJsonArray(responseData);
}
}
if (operation === 'delete') {
// https://developers.google.com/gmail/api/v1/reference/users/messages/delete
method = 'DELETE';
const id = this.getNodeParameter('messageId', i);
endpoint = `/gmail/v1/users/me/messages/${id}`;
responseData = await googleApiRequest.call(this, method, endpoint, body, qs);
responseData = { success: true };
}
}
if (resource === 'draft') {
if (operation === 'create') {
// https://developers.google.com/gmail/api/v1/reference/users/drafts/create
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
let toStr = '';
let ccStr = '';
let bccStr = '';
let attachmentsList: IDataObject[] = [];
if (additionalFields.toList) {
const toList = additionalFields.toList as IDataObject[];
toList.forEach((email) => {
toStr += `<${email}>, `;
});
}
if (additionalFields.ccList) {
const ccList = additionalFields.ccList as IDataObject[];
ccList.forEach((email) => {
ccStr += `<${email}>, `;
});
}
if (additionalFields.bccList) {
const bccList = additionalFields.bccList as IDataObject[];
bccList.forEach((email) => {
bccStr += `<${email}>, `;
});
}
if (additionalFields.attachmentsUi) {
const attachmentsUi = additionalFields.attachmentsUi as IDataObject;
const attachmentsBinary = [];
if (!isEmpty(attachmentsUi)) {
if (!isEmpty(attachmentsUi)) {
if (attachmentsUi.hasOwnProperty('attachmentsBinary')
&& !isEmpty(attachmentsUi.attachmentsBinary)
&& items[i].binary) {
for (const { property } of attachmentsUi.attachmentsBinary as IDataObject[]) {
for (const binaryProperty of (property as string).split(',')) {
if (items[i].binary![binaryProperty] !== undefined) {
const binaryData = items[i].binary![binaryProperty];
const binaryDataBuffer = await this.helpers.getBinaryDataBuffer(i, binaryProperty);
attachmentsBinary.push({
name: binaryData.fileName || 'unknown',
content: binaryDataBuffer,
type: binaryData.mimeType,
});
}
}
}
}
}
qs = {
userId: 'me',
uploadType: 'media',
};
attachmentsList = attachmentsBinary;
}
}
const email: IEmail = {
to: toStr,
cc: ccStr,
bcc: bccStr,
subject: this.getNodeParameter('subject', i) as string,
body: this.getNodeParameter('message', i) as string,
attachments: attachmentsList,
};
if (this.getNodeParameter('includeHtml', i, false) as boolean === true) {
email.htmlBody = this.getNodeParameter('htmlMessage', i) as string;
}
endpoint = '/gmail/v1/users/me/drafts';
method = 'POST';
body = {
message: {
raw: await encodeEmail(email),
},
};
responseData = await googleApiRequest.call(this, method, endpoint, body, qs);
}
if (operation === 'get') {
// https://developers.google.com/gmail/api/v1/reference/users/drafts/get
method = 'GET';
const id = this.getNodeParameter('messageId', i);
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
const format = additionalFields.format || 'resolved';
if (format === 'resolved') {
qs.format = 'raw';
} else {
qs.format = format;
}
endpoint = `/gmail/v1/users/me/drafts/${id}`;
responseData = await googleApiRequest.call(this, method, endpoint, body, qs);
const binaryData: IBinaryKeyData = {};
let nodeExecutionData: INodeExecutionData;
if (format === 'resolved') {
const dataPropertyNameDownload = additionalFields.dataPropertyAttachmentsPrefixName as string || 'attachment_';
nodeExecutionData = await parseRawEmail.call(this, responseData.message, dataPropertyNameDownload);
// Add the draft-id
nodeExecutionData.json.messageId = nodeExecutionData.json.id;
nodeExecutionData.json.id = responseData.id;
} else {
nodeExecutionData = {
json: responseData,
binary: Object.keys(binaryData).length ? binaryData : undefined,
};
}
responseData = nodeExecutionData;
}
if (operation === 'delete') {
// https://developers.google.com/gmail/api/v1/reference/users/drafts/delete
method = 'DELETE';
const id = this.getNodeParameter('messageId', i);
endpoint = `/gmail/v1/users/me/drafts/${id}`;
responseData = await googleApiRequest.call(this, method, endpoint, body, qs);
responseData = { success: true };
}
if (operation === 'getAll') {
const returnAll = this.getNodeParameter('returnAll', i) as boolean;
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
Object.assign(qs, additionalFields);
if (returnAll) {
responseData = await googleApiRequestAllItems.call(
this,
'drafts',
'GET',
`/gmail/v1/users/me/drafts`,
{},
qs,
);
} else {
qs.maxResults = this.getNodeParameter('limit', i) as number;
responseData = await googleApiRequest.call(
this,
'GET',
`/gmail/v1/users/me/drafts`,
{},
qs,
);
responseData = responseData.drafts;
}
if (responseData === undefined) {
responseData = [];
}
const format = additionalFields.format || 'resolved';
if (format !== 'ids') {
if (format === 'resolved') {
qs.format = 'raw';
} else {
qs.format = format;
}
for (let i = 0; i < responseData.length; i++) {
responseData[i] = await googleApiRequest.call(
this,
'GET',
`/gmail/v1/users/me/drafts/${responseData[i].id}`,
body,
qs,
);
if (format === 'resolved') {
const dataPropertyNameDownload = additionalFields.dataPropertyAttachmentsPrefixName as string || 'attachment_';
const id = responseData[i].id;
responseData[i] = await parseRawEmail.call(this, responseData[i].message, dataPropertyNameDownload);
// Add the draft-id
responseData[i].json.messageId = responseData[i].json.id;
responseData[i].json.id = id;
}
}
}
if (format !== 'resolved') {
responseData = this.helpers.returnJsonArray(responseData);
}
}
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData),
{ itemData: { item: i } },
);
returnData.push(...executionData);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({ json: { error: error.message } });
continue;
}
throw error;
}
}
return this.prepareOutputData(returnData);
}
}

View File

@@ -0,0 +1,157 @@
import { INodeProperties } from 'n8n-workflow';
export const labelOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['label'],
},
},
options: [
{
name: 'Create',
value: 'create',
action: 'Create a label',
},
{
name: 'Delete',
value: 'delete',
action: 'Delete a label',
},
{
name: 'Get',
value: 'get',
action: 'Get a label',
},
{
name: 'Get Many',
value: 'getAll',
action: 'Get all labels',
},
],
default: 'create',
},
];
export const labelFields: INodeProperties[] = [
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: ['label'],
operation: ['create'],
},
},
placeholder: 'invoices',
description: 'Label Name',
},
{
displayName: 'Label ID',
name: 'labelId',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: ['label'],
operation: ['get', 'delete'],
},
},
description: 'The ID of the label',
},
{
displayName: 'Label List Visibility',
name: 'labelListVisibility',
type: 'options',
options: [
{
name: 'Hide',
value: 'labelHide',
},
{
name: 'Show',
value: 'labelShow',
},
{
name: 'Show If Unread',
value: 'labelShowIfUnread',
},
],
default: 'labelShow',
required: true,
displayOptions: {
show: {
resource: ['label'],
operation: ['create'],
},
},
description: 'The visibility of the label in the label list in the Gmail web interface',
},
{
displayName: 'Message List Visibility',
name: 'messageListVisibility',
type: 'options',
options: [
{
name: 'Hide',
value: 'hide',
},
{
name: 'Show',
value: 'show',
},
],
default: 'show',
required: true,
displayOptions: {
show: {
resource: ['label'],
operation: ['create'],
},
},
description:
'The visibility of messages with this label in the message list in the Gmail web interface',
},
/* -------------------------------------------------------------------------- */
/* label:getAll */
/* -------------------------------------------------------------------------- */
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
operation: ['getAll'],
resource: ['label'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
operation: ['getAll'],
resource: ['label'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
maxValue: 500,
},
default: 50,
description: 'Max number of results to return',
},
];

View File

@@ -0,0 +1,439 @@
import { INodeProperties } from 'n8n-workflow';
export const messageOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['message'],
},
},
options: [
{
name: 'Delete',
value: 'delete',
action: 'Delete a message',
},
{
name: 'Get',
value: 'get',
action: 'Get a message',
},
{
name: 'Get Many',
value: 'getAll',
action: 'Get all messages',
},
{
name: 'Reply',
value: 'reply',
action: 'Reply to a message',
},
{
name: 'Send',
value: 'send',
action: 'Send a message',
},
],
default: 'send',
},
];
export const messageFields: INodeProperties[] = [
{
displayName: 'Message ID',
name: 'messageId',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: ['message'],
operation: ['get', 'delete'],
},
},
placeholder: '172ce2c4a72cc243',
},
{
displayName: 'Thread ID',
name: 'threadId',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: ['message'],
operation: ['reply'],
},
},
placeholder: '172ce2c4a72cc243',
},
{
displayName: 'Message ID',
name: 'messageId',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: ['message'],
operation: ['reply'],
},
},
placeholder: 'CAHNQoFsC6JMMbOBJgtjsqN0eEc+gDg2a=SQj-tWUebQeHMDgqQ@mail.gmail.com',
},
{
displayName: 'Subject',
name: 'subject',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: ['message'],
operation: ['reply', 'send'],
},
},
placeholder: 'Hello World!',
},
{
displayName: 'HTML',
name: 'includeHtml',
type: 'boolean',
displayOptions: {
show: {
resource: ['message'],
operation: ['send', 'reply'],
},
},
default: false,
description: 'Whether the message should also be included as HTML',
},
{
displayName: 'HTML Message',
name: 'htmlMessage',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
includeHtml: [true],
resource: ['message'],
operation: ['reply', 'send'],
},
},
description: 'The HTML message body',
},
{
displayName: 'Message',
name: 'message',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: ['message'],
operation: ['reply', 'send'],
},
},
description: 'Plain text message body',
},
{
displayName: 'To Email',
name: 'toList',
type: 'string',
default: [],
required: true,
typeOptions: {
multipleValues: true,
multipleValueButtonText: 'Add To Email',
},
displayOptions: {
show: {
resource: ['message'],
operation: ['reply', 'send'],
},
},
placeholder: 'info@example.com',
description: 'The email addresses of the recipients',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
displayOptions: {
show: {
resource: ['message'],
operation: ['send', 'reply'],
},
},
default: {},
options: [
{
displayName: 'Attachment',
name: 'attachmentsUi',
placeholder: 'Add Attachment',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
options: [
{
name: 'attachmentsBinary',
displayName: 'Attachment Binary',
values: [
{
displayName: 'Attachment Field Name (in Input)',
name: 'property',
type: 'string',
default: '',
description:
'Add the field name from the input node. Multiple properties can be set separated by comma.',
},
],
},
],
default: {},
description: 'Array of supported attachments to add to the message',
},
{
displayName: 'BCC Email',
name: 'bccList',
type: 'string',
description: 'The email addresses of the blind copy recipients',
typeOptions: {
multipleValues: true,
multipleValueButtonText: 'Add BCC Email',
},
placeholder: 'info@example.com',
default: [],
},
{
displayName: 'CC Email',
name: 'ccList',
type: 'string',
description: 'The email addresses of the copy recipients',
typeOptions: {
multipleValues: true,
multipleValueButtonText: 'Add CC Email',
},
placeholder: 'info@example.com',
default: [],
},
{
displayName: 'Override Sender Name',
name: 'senderName',
type: 'string',
placeholder: 'Name <test@gmail.com>',
default: '',
description:
'The name displayed in your contacts inboxes. It has to be in the format: "Display-Name &#60;name@gmail.com&#62;". The email address has to match the email address of the logged in user for the API.',
},
],
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
displayOptions: {
show: {
resource: ['message'],
operation: ['get'],
},
},
default: {},
options: [
{
displayName: 'Format',
name: 'format',
type: 'options',
options: [
{
name: 'Full',
value: 'full',
description:
'Returns the full email message data with body content parsed in the payload field',
},
{
name: 'Metadata',
value: 'metadata',
description: 'Returns only email message ID, labels, and email headers',
},
{
name: 'Minimal',
value: 'minimal',
description:
'Returns only email message ID and labels; does not return the email headers, body, or payload',
},
{
name: 'RAW',
value: 'raw',
description:
'Returns the full email message data with body content in the raw field as a base64url encoded string; the payload field is not used',
},
{
name: 'Resolved',
value: 'resolved',
description:
'Returns the full email with all data resolved and attachments saved as binary data',
},
],
default: 'resolved',
description: 'The format to return the message in',
},
{
displayName: 'Attachment Prefix',
name: 'dataPropertyAttachmentsPrefixName',
type: 'string',
default: 'attachment_',
displayOptions: {
hide: {
format: ['full', 'metadata', 'minimal', 'raw'],
},
},
description:
'Prefix for name of the binary property to which to write the attachments. An index starting with 0 will be added. So if name is "attachment_" the first attachment is saved to "attachment_0"',
},
],
},
/* -------------------------------------------------------------------------- */
/* message:getAll */
/* -------------------------------------------------------------------------- */
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
operation: ['getAll'],
resource: ['message'],
},
},
default: false,
description: 'Whether to return all results or only up to a given limit',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
operation: ['getAll'],
resource: ['message'],
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
maxValue: 500,
},
default: 10,
description: 'Max number of results to return',
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
operation: ['getAll'],
resource: ['message'],
},
},
options: [
{
displayName: 'Attachment Prefix',
name: 'dataPropertyAttachmentsPrefixName',
type: 'string',
default: 'attachment_',
displayOptions: {
hide: {
format: ['full', 'ids', 'metadata', 'minimal', 'raw'],
},
},
description:
'Prefix for name of the binary property to which to write the attachment. An index starting with 0 will be added. So if name is "attachment_" the first attachment is saved to "attachment_0".',
},
{
displayName: 'Format',
name: 'format',
type: 'options',
options: [
{
name: 'Full',
value: 'full',
description:
'Returns the full email message data with body content parsed in the payload field',
},
{
name: 'IDs',
value: 'ids',
description: 'Returns only the IDs of the emails',
},
{
name: 'Metadata',
value: 'metadata',
description: 'Returns only email message ID, labels, and email headers',
},
{
name: 'Minimal',
value: 'minimal',
description:
'Returns only email message ID and labels; does not return the email headers, body, or payload',
},
{
name: 'RAW',
value: 'raw',
description:
'Returns the full email message data with body content in the raw field as a base64url encoded string; the payload field is not used',
},
{
name: 'Resolved',
value: 'resolved',
description:
'Returns the full email with all data resolved and attachments saved as binary data',
},
],
default: 'resolved',
description: 'The format to return the message in',
},
{
displayName: 'Include Spam and Trash',
name: 'includeSpamTrash',
type: 'boolean',
default: false,
description: 'Whether to include messages from SPAM and TRASH in the results',
},
{
displayName: 'Label Names or IDs',
name: 'labelIds',
type: 'multiOptions',
typeOptions: {
loadOptionsMethod: 'getLabels',
},
default: [],
description:
'Only return messages with labels that match all of the specified label IDs. Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
},
{
displayName: 'Query',
name: 'q',
type: 'string',
typeOptions: {
alwaysOpenEditWindow: true,
},
default: '',
description:
'Only return messages matching the specified query. Supports the same query format as the Gmail search box. For example, "from:someuser@example.com rfc822msgid:&lt;somemsgid@example.com&gt; is:unread". Parameter cannot be used when accessing the api using the gmail.metadata scope.',
},
],
},
];

View File

@@ -0,0 +1,63 @@
import { INodeProperties } from 'n8n-workflow';
export const messageLabelOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['messageLabel'],
},
},
options: [
{
name: 'Add',
value: 'add',
action: 'Add a label to a message',
},
{
name: 'Remove',
value: 'remove',
action: 'Remove a label from a message',
},
],
default: 'add',
},
];
export const messageLabelFields: INodeProperties[] = [
{
displayName: 'Message ID',
name: 'messageId',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: ['messageLabel'],
operation: ['add', 'remove'],
},
},
placeholder: '172ce2c4a72cc243',
},
{
displayName: 'Label Names or IDs',
name: 'labelIds',
type: 'multiOptions',
typeOptions: {
loadOptionsMethod: 'getLabels',
},
default: [],
required: true,
displayOptions: {
show: {
resource: ['messageLabel'],
operation: ['add', 'remove'],
},
},
description:
'The ID of the label. Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
},
];