n8n-nodes-base
Version:
Base nodes of n8n
412 lines • 16 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.prepareTimestamp = void 0;
exports.googleApiRequest = googleApiRequest;
exports.parseRawEmail = parseRawEmail;
exports.encodeEmail = encodeEmail;
exports.googleApiRequestAllItems = googleApiRequestAllItems;
exports.extractEmail = extractEmail;
exports.prepareQuery = prepareQuery;
exports.prepareEmailsInput = prepareEmailsInput;
exports.prepareEmailBody = prepareEmailBody;
exports.prepareEmailAttachments = prepareEmailAttachments;
exports.unescapeSnippets = unescapeSnippets;
exports.simplifyOutput = simplifyOutput;
exports.getLabels = getLabels;
const isEmpty_1 = __importDefault(require("lodash/isEmpty"));
const luxon_1 = require("luxon");
const mailparser_1 = require("mailparser");
const n8n_workflow_1 = require("n8n-workflow");
const mail_composer_1 = __importDefault(require("nodemailer/lib/mail-composer"));
const utilities_1 = require("../../../utils/utilities");
const GenericFunctions_1 = require("../GenericFunctions");
async function googleApiRequest(method, endpoint, body = {}, qs = {}, uri, option = {}) {
let options = {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
method,
body,
qs,
uri: uri || `https://www.googleapis.com${endpoint}`,
qsStringifyOptions: {
arrayFormat: 'repeat',
},
json: true,
};
options = Object.assign({}, options, option);
try {
if (Object.keys(body).length === 0) {
delete options.body;
}
let credentialType = 'gmailOAuth2';
const authentication = this.getNodeParameter('authentication', 0);
if (authentication === 'serviceAccount') {
const credentials = await this.getCredentials('googleApi');
credentialType = 'googleApi';
const { access_token } = await GenericFunctions_1.getGoogleAccessToken.call(this, credentials, 'gmail');
options.headers.Authorization = `Bearer ${access_token}`;
}
const response = await this.helpers.requestWithAuthentication.call(this, credentialType, options);
return response;
}
catch (error) {
if (error.code === 'ERR_OSSL_PEM_NO_START_LINE') {
error.statusCode = '401';
}
if (error.httpCode === '400') {
if (error.cause && (error.cause.message || '').includes('Invalid id value')) {
const resource = this.getNodeParameter('resource', 0);
const errorOptions = {
message: `Invalid ${resource} ID`,
description: `${resource.charAt(0).toUpperCase() + resource.slice(1)} IDs should look something like this: 182b676d244938bd`,
};
throw new n8n_workflow_1.NodeApiError(this.getNode(), error, errorOptions);
}
}
if (error.httpCode === '404') {
let resource = this.getNodeParameter('resource', 0);
if (resource === 'label') {
resource = 'label ID';
}
const errorOptions = {
message: `${resource.charAt(0).toUpperCase() + resource.slice(1)} not found`,
description: '',
};
throw new n8n_workflow_1.NodeApiError(this.getNode(), error, errorOptions);
}
if (error.httpCode === '409') {
const resource = this.getNodeParameter('resource', 0);
if (resource === 'label') {
const errorOptions = {
message: 'Label name exists already',
description: '',
};
throw new n8n_workflow_1.NodeApiError(this.getNode(), error, errorOptions);
}
}
if (error.code === 'EAUTH') {
const errorOptions = {
message: error?.body?.error_description || 'Authorization error',
description: error.message,
};
throw new n8n_workflow_1.NodeApiError(this.getNode(), error, errorOptions);
}
if ((error.message || '').includes('Bad request - please check your parameters') &&
error.description) {
const errorOptions = {
message: error.description,
description: '',
};
throw new n8n_workflow_1.NodeApiError(this.getNode(), error, errorOptions);
}
throw new n8n_workflow_1.NodeApiError(this.getNode(), error, {
message: error.message,
description: error.description,
});
}
}
async function parseRawEmail(messageData, dataPropertyNameDownload) {
const messageEncoded = Buffer.from(messageData.raw, 'base64').toString('utf8');
const responseData = await (0, mailparser_1.simpleParser)(messageEncoded);
const headers = {};
for (const header of responseData.headerLines) {
headers[header.key] = header.line;
}
const binaryData = {};
if (responseData.attachments) {
const downloadAttachments = this.getNodeParameter('options.downloadAttachments', 0, false);
if (downloadAttachments) {
for (let i = 0; i < responseData.attachments.length; i++) {
const attachment = responseData.attachments[i];
binaryData[`${dataPropertyNameDownload}${i}`] = await this.helpers.prepareBinaryData(attachment.content, attachment.filename, attachment.contentType);
}
}
}
const mailBaseData = {};
const resolvedModeAddProperties = ['id', 'threadId', 'labelIds', 'sizeEstimate'];
for (const key of resolvedModeAddProperties) {
mailBaseData[key] = messageData[key];
}
const json = Object.assign({}, mailBaseData, responseData, {
headers,
headerLines: undefined,
attachments: undefined,
// Having data in IDataObjects that is not representable in JSON leads to
// inconsistencies between test executions and production executions.
// During a manual execution this would be stringified and during a
// production execution the next node would receive a date instance.
date: responseData.date ? responseData.date.toISOString() : responseData.date,
});
return {
json,
binary: Object.keys(binaryData).length ? binaryData : undefined,
};
}
//------------------------------------------------------------------------------------------------------------------------------------------
// This function converts an email object into a MIME encoded email and then converts that string into base64 encoding
// for more info on MIME, https://docs.microsoft.com/en-us/previous-versions/office/developer/exchange-server-2010/aa494197(v%3Dexchg.140)
//------------------------------------------------------------------------------------------------------------------------------------------
async function encodeEmail(email) {
// https://nodemailer.com/extras/mailcomposer/#e-mail-message-fields
const mailOptions = {
from: email.from,
to: email.to,
cc: email.cc,
bcc: email.bcc,
replyTo: email.replyTo,
inReplyTo: email.inReplyTo,
references: email.reference,
subject: email.subject,
text: email.body,
keepBcc: true,
};
if (email.htmlBody) {
mailOptions.html = email.htmlBody;
}
if (email.attachments !== undefined &&
Array.isArray(email.attachments) &&
email.attachments.length > 0) {
const attachments = email.attachments.map((attachment) => ({
filename: attachment.name,
content: attachment.content,
contentType: attachment.type,
encoding: 'base64',
}));
mailOptions.attachments = attachments;
}
const mail = new mail_composer_1.default(mailOptions).compile();
// by default the bcc headers are deleted when the mail is built.
// So add keepBcc flag to override such behaviour. Only works when
// the flag is set after the compilation.
mail.keepBcc = true;
const mailBody = await mail.build();
return mailBody.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
async function googleApiRequestAllItems(propertyName, method, endpoint, body = {}, query = {}) {
const returnData = [];
let responseData;
query.maxResults = 100;
do {
responseData = await googleApiRequest.call(this, method, endpoint, body, query);
query.pageToken = responseData.nextPageToken;
returnData.push.apply(returnData, responseData[propertyName]);
} while (responseData.nextPageToken !== undefined && responseData.nextPageToken !== '');
return returnData;
}
function extractEmail(s) {
if (s.includes('<')) {
const data = s.split('<')[1];
return data.substring(0, data.length - 1);
}
return s;
}
const prepareTimestamp = (node, itemIndex, query, dateValue, label) => {
if (dateValue instanceof luxon_1.DateTime) {
dateValue = dateValue.toISO();
}
let timestamp = luxon_1.DateTime.fromISO(dateValue).toSeconds();
const timestampLengthInMilliseconds1990 = 12;
if (typeof timestamp === 'number') {
timestamp = Math.round(timestamp);
}
if (!timestamp &&
typeof dateValue === 'number' &&
dateValue.toString().length < timestampLengthInMilliseconds1990) {
timestamp = dateValue;
}
if (!timestamp && dateValue.length < timestampLengthInMilliseconds1990) {
timestamp = parseInt(dateValue, 10);
}
if (!timestamp) {
timestamp = Math.floor(luxon_1.DateTime.fromMillis(parseInt(dateValue, 10)).toSeconds());
}
if (!timestamp) {
const description = `'${dateValue}' isn't a valid date and time. If you're using an expression, be sure to set an ISO date string or a timestamp.`;
throw new n8n_workflow_1.NodeOperationError(node, `Invalid date/time in 'Received ${label[0].toUpperCase() + label.slice(1)}' field`, {
description,
itemIndex,
});
}
if (query) {
query += ` ${label}:${timestamp}`;
}
else {
query = `${label}:${timestamp}`;
}
return query;
};
exports.prepareTimestamp = prepareTimestamp;
function prepareQuery(fields, itemIndex) {
const qs = { ...fields };
if (qs.labelIds) {
if (qs.labelIds === '') {
delete qs.labelIds;
}
else {
qs.labelIds = qs.labelIds;
}
}
if (qs.sender) {
if (qs.q) {
qs.q += ` from:${qs.sender}`;
}
else {
qs.q = `from:${qs.sender}`;
}
delete qs.sender;
}
if (qs.readStatus && qs.readStatus !== 'both') {
if (qs.q) {
qs.q += ` is:${qs.readStatus}`;
}
else {
qs.q = `is:${qs.readStatus}`;
}
delete qs.readStatus;
}
if (qs.receivedAfter) {
qs.q = (0, exports.prepareTimestamp)(this.getNode(), itemIndex, qs.q, qs.receivedAfter, 'after');
delete qs.receivedAfter;
}
if (qs.receivedBefore) {
qs.q = (0, exports.prepareTimestamp)(this.getNode(), itemIndex, qs.q, qs.receivedBefore, 'before');
delete qs.receivedBefore;
}
return qs;
}
function prepareEmailsInput(input, fieldName, itemIndex) {
let emails = '';
input.split(',').forEach((entry) => {
const email = entry.trim();
if (email.indexOf('@') === -1) {
const description = `The email address '${email}' in the '${fieldName}' field isn't valid`;
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid email address', {
description,
itemIndex,
});
}
if (email.includes('<') && email.includes('>')) {
emails += `${email},`;
}
else {
emails += `<${email}>, `;
}
});
return emails;
}
function prepareEmailBody(itemIndex, appendAttribution = false, instanceId) {
const emailType = this.getNodeParameter('emailType', itemIndex);
let message = this.getNodeParameter('message', itemIndex, '').trim();
if (appendAttribution) {
const attributionText = 'This email was sent automatically with ';
const link = (0, utilities_1.createUtmCampaignLink)('n8n-nodes-base.gmail', instanceId);
if (emailType === 'html') {
message = `
${message}
<br>
<br>
---
<br>
<em>${attributionText}<a href="${link}" target="_blank">n8n</a></em>
`;
}
else {
message = `${message}\n\n---\n${attributionText}n8n\n${'https://n8n.io'}`;
}
}
const body = {
body: '',
htmlBody: '',
};
if (emailType === 'html') {
body.htmlBody = message;
}
else {
body.body = message;
}
return body;
}
async function prepareEmailAttachments(options, itemIndex) {
const attachmentsList = [];
const attachments = options.attachmentsBinary;
if (attachments && !(0, isEmpty_1.default)(attachments)) {
for (const { property } of attachments) {
for (const name of property.split(',')) {
const binaryData = this.helpers.assertBinaryData(itemIndex, name);
const binaryDataBuffer = await this.helpers.getBinaryDataBuffer(itemIndex, name);
if (!Buffer.isBuffer(binaryDataBuffer)) {
const description = `The input field '${name}' doesn't contain an attachment. Please make sure you specify a field containing binary data`;
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Attachment not found', {
description,
itemIndex,
});
}
attachmentsList.push({
name: binaryData.fileName || 'unknown',
content: binaryDataBuffer,
type: binaryData.mimeType,
});
}
}
}
return attachmentsList;
}
function unescapeSnippets(items) {
const result = items.map((item) => {
const snippet = item.json.snippet;
if (snippet) {
item.json.snippet = (0, utilities_1.escapeHtml)(snippet);
}
return item;
});
return result;
}
async function simplifyOutput(data) {
const labelsData = await googleApiRequest.call(this, 'GET', '/gmail/v1/users/me/labels');
const labels = (labelsData.labels || []).map(({ id, name }) => ({
id,
name,
}));
return (data || []).map((item) => {
if (item.labelIds) {
item.labels = labels.filter((label) => item.labelIds.includes(label.id));
delete item.labelIds;
}
if (item.payload && item.payload.headers) {
const { headers } = item.payload;
(headers || []).forEach((header) => {
item[header.name] = header.value;
});
delete item.payload.headers;
}
return item;
});
}
/**
* Get all the labels to display them to user so that they can select them easily
*/
async function getLabels() {
const returnData = [];
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;
});
}
//# sourceMappingURL=GenericFunctions.js.map