mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 01:56:46 +00:00
test(Email Trigger (IMAP) Node): Improve email imap testing (#13255)
Co-authored-by: Michael Kret <michael.k@radency.com>
This commit is contained in:
@@ -0,0 +1,66 @@
|
|||||||
|
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
|
||||||
|
import { mock } from 'jest-mock-extended';
|
||||||
|
import { type INodeTypeBaseDescription, type ITriggerFunctions } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { type ICredentialsDataImap } from '../../../../credentials/Imap.credentials';
|
||||||
|
import { EmailReadImapV2 } from '../../v2/EmailReadImapV2.node';
|
||||||
|
|
||||||
|
jest.mock('@n8n/imap', () => {
|
||||||
|
const originalModule = jest.requireActual('@n8n/imap');
|
||||||
|
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
connect: jest.fn().mockImplementation(() => ({
|
||||||
|
then: jest.fn().mockImplementation(() => ({
|
||||||
|
openBox: jest.fn().mockResolvedValue({}),
|
||||||
|
})),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test IMap V2', () => {
|
||||||
|
const triggerFunctions = mock<ITriggerFunctions>({
|
||||||
|
helpers: {
|
||||||
|
createDeferredPromise: jest.fn().mockImplementation(() => {
|
||||||
|
let resolve, reject;
|
||||||
|
const promise = new Promise((res, rej) => {
|
||||||
|
resolve = res;
|
||||||
|
reject = rej;
|
||||||
|
});
|
||||||
|
return { promise, resolve, reject };
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const credentials: ICredentialsDataImap = {
|
||||||
|
host: 'imap.gmail.com',
|
||||||
|
port: 993,
|
||||||
|
user: 'user',
|
||||||
|
password: 'password',
|
||||||
|
secure: false,
|
||||||
|
allowUnauthorizedCerts: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
triggerFunctions.getCredentials.calledWith('imap').mockResolvedValue(credentials);
|
||||||
|
triggerFunctions.logger.debug = jest.fn();
|
||||||
|
triggerFunctions.getNodeParameter.calledWith('options').mockReturnValue({
|
||||||
|
name: 'Mark as Read',
|
||||||
|
value: 'read',
|
||||||
|
});
|
||||||
|
|
||||||
|
const baseDescription: INodeTypeBaseDescription = {
|
||||||
|
displayName: 'EmailReadImapV2',
|
||||||
|
name: 'emailReadImapV2',
|
||||||
|
icon: 'file:removeDuplicates.svg',
|
||||||
|
group: ['transform'],
|
||||||
|
description: 'Delete items with matching field values',
|
||||||
|
};
|
||||||
|
|
||||||
|
afterEach(() => jest.resetAllMocks());
|
||||||
|
|
||||||
|
it('should run return a close function on success', async () => {
|
||||||
|
const result = await new EmailReadImapV2(baseDescription).trigger.call(triggerFunctions);
|
||||||
|
|
||||||
|
expect(result.closeFunction).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import { type ImapSimple } from '@n8n/imap';
|
||||||
|
import { mock } from 'jest-mock-extended';
|
||||||
|
import { returnJsonArray } from 'n8n-core';
|
||||||
|
import { type IDataObject, type ITriggerFunctions } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { getNewEmails } from '../../v2/utils';
|
||||||
|
|
||||||
|
describe('Test IMap V2 utils', () => {
|
||||||
|
afterEach(() => jest.resetAllMocks());
|
||||||
|
|
||||||
|
describe('getNewEmails', () => {
|
||||||
|
const triggerFunctions = mock<ITriggerFunctions>({
|
||||||
|
helpers: { returnJsonArray },
|
||||||
|
});
|
||||||
|
|
||||||
|
const message = {
|
||||||
|
attributes: {
|
||||||
|
uuid: 1,
|
||||||
|
struct: {},
|
||||||
|
},
|
||||||
|
parts: [
|
||||||
|
{ which: '', body: 'Body content' },
|
||||||
|
{ which: 'HEADER', body: 'h' },
|
||||||
|
{ which: 'TEXT', body: 'txt' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const staticData: IDataObject = {};
|
||||||
|
const imapConnection = mock<ImapSimple>({
|
||||||
|
search: jest.fn().mockReturnValue(Promise.resolve([message])),
|
||||||
|
});
|
||||||
|
const getText = jest.fn().mockReturnValue('text');
|
||||||
|
const getAttachment = jest.fn().mockReturnValue(['attachment']);
|
||||||
|
|
||||||
|
it('should return new emails', async () => {
|
||||||
|
const expectedResults = [
|
||||||
|
{
|
||||||
|
format: 'resolved',
|
||||||
|
expected: {
|
||||||
|
json: {
|
||||||
|
attachments: undefined,
|
||||||
|
headers: { '': 'Body content' },
|
||||||
|
headerLines: undefined,
|
||||||
|
html: false,
|
||||||
|
},
|
||||||
|
binary: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format: 'simple',
|
||||||
|
expected: {
|
||||||
|
json: {
|
||||||
|
textHtml: 'text',
|
||||||
|
textPlain: 'text',
|
||||||
|
metadata: {
|
||||||
|
'0': 'h',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format: 'raw',
|
||||||
|
expected: {
|
||||||
|
json: { raw: 'txt' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
expectedResults.forEach(async (expectedResult) => {
|
||||||
|
triggerFunctions.getNodeParameter
|
||||||
|
.calledWith('format')
|
||||||
|
.mockReturnValue(expectedResult.format);
|
||||||
|
triggerFunctions.getNodeParameter
|
||||||
|
.calledWith('dataPropertyAttachmentsPrefixName')
|
||||||
|
.mockReturnValue('resolved');
|
||||||
|
|
||||||
|
const result = getNewEmails.call(
|
||||||
|
triggerFunctions,
|
||||||
|
imapConnection,
|
||||||
|
[],
|
||||||
|
staticData,
|
||||||
|
'',
|
||||||
|
getText,
|
||||||
|
getAttachment,
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(result).resolves.toEqual([expectedResult.expected]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,18 +1,13 @@
|
|||||||
import type { ImapSimple, ImapSimpleOptions, Message, MessagePart } from '@n8n/imap';
|
import type { ImapSimple, ImapSimpleOptions, Message, MessagePart } from '@n8n/imap';
|
||||||
import { connect as imapConnect, getParts } from '@n8n/imap';
|
import { connect as imapConnect } from '@n8n/imap';
|
||||||
import find from 'lodash/find';
|
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
import type { Source as ParserSource } from 'mailparser';
|
|
||||||
import { simpleParser } from 'mailparser';
|
|
||||||
import type {
|
import type {
|
||||||
ITriggerFunctions,
|
ITriggerFunctions,
|
||||||
IBinaryData,
|
IBinaryData,
|
||||||
IBinaryKeyData,
|
|
||||||
ICredentialsDecrypted,
|
ICredentialsDecrypted,
|
||||||
ICredentialTestFunctions,
|
ICredentialTestFunctions,
|
||||||
IDataObject,
|
IDataObject,
|
||||||
INodeCredentialTestResult,
|
INodeCredentialTestResult,
|
||||||
INodeExecutionData,
|
|
||||||
INodeType,
|
INodeType,
|
||||||
INodeTypeBaseDescription,
|
INodeTypeBaseDescription,
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
@@ -22,45 +17,10 @@ import type {
|
|||||||
import { NodeConnectionType, NodeOperationError, TriggerCloseError } from 'n8n-workflow';
|
import { NodeConnectionType, NodeOperationError, TriggerCloseError } from 'n8n-workflow';
|
||||||
import rfc2047 from 'rfc2047';
|
import rfc2047 from 'rfc2047';
|
||||||
|
|
||||||
|
import { getNewEmails } from './utils';
|
||||||
import type { ICredentialsDataImap } from '../../../credentials/Imap.credentials';
|
import type { ICredentialsDataImap } from '../../../credentials/Imap.credentials';
|
||||||
import { isCredentialsDataImap } from '../../../credentials/Imap.credentials';
|
import { isCredentialsDataImap } from '../../../credentials/Imap.credentials';
|
||||||
|
|
||||||
export async function parseRawEmail(
|
|
||||||
this: ITriggerFunctions,
|
|
||||||
messageEncoded: ParserSource,
|
|
||||||
dataPropertyNameDownload: string,
|
|
||||||
): Promise<INodeExecutionData> {
|
|
||||||
const responseData = await simpleParser(messageEncoded);
|
|
||||||
const headers: IDataObject = {};
|
|
||||||
const additionalData: IDataObject = {};
|
|
||||||
|
|
||||||
for (const header of responseData.headerLines) {
|
|
||||||
headers[header.key] = header.line;
|
|
||||||
}
|
|
||||||
|
|
||||||
additionalData.headers = headers;
|
|
||||||
additionalData.headerLines = undefined;
|
|
||||||
|
|
||||||
const binaryData: IBinaryKeyData = {};
|
|
||||||
if (responseData.attachments) {
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
additionalData.attachments = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
json: { ...responseData, ...additionalData },
|
|
||||||
binary: Object.keys(binaryData).length ? binaryData : undefined,
|
|
||||||
} as INodeExecutionData;
|
|
||||||
}
|
|
||||||
|
|
||||||
const versionDescription: INodeTypeDescription = {
|
const versionDescription: INodeTypeDescription = {
|
||||||
displayName: 'Email Trigger (IMAP)',
|
displayName: 'Email Trigger (IMAP)',
|
||||||
name: 'emailReadImap',
|
name: 'emailReadImap',
|
||||||
@@ -369,171 +329,6 @@ export class EmailReadImapV2 implements INodeType {
|
|||||||
return await Promise.all(attachmentPromises);
|
return await Promise.all(attachmentPromises);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns all the new unseen messages
|
|
||||||
const getNewEmails = async (
|
|
||||||
imapConnection: ImapSimple,
|
|
||||||
searchCriteria: Array<string | string[]>,
|
|
||||||
): Promise<INodeExecutionData[]> => {
|
|
||||||
const format = this.getNodeParameter('format', 0) as string;
|
|
||||||
|
|
||||||
let fetchOptions = {};
|
|
||||||
|
|
||||||
if (format === 'simple' || format === 'raw') {
|
|
||||||
fetchOptions = {
|
|
||||||
bodies: ['TEXT', 'HEADER'],
|
|
||||||
markSeen: false,
|
|
||||||
struct: true,
|
|
||||||
};
|
|
||||||
} else if (format === 'resolved') {
|
|
||||||
fetchOptions = {
|
|
||||||
bodies: [''],
|
|
||||||
markSeen: false,
|
|
||||||
struct: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const results = await imapConnection.search(searchCriteria, fetchOptions);
|
|
||||||
|
|
||||||
const newEmails: INodeExecutionData[] = [];
|
|
||||||
let newEmail: INodeExecutionData;
|
|
||||||
let attachments: IBinaryData[];
|
|
||||||
let propertyName: string;
|
|
||||||
|
|
||||||
// All properties get by default moved to metadata except the ones
|
|
||||||
// which are defined here which get set on the top level.
|
|
||||||
const topLevelProperties = ['cc', 'date', 'from', 'subject', 'to'];
|
|
||||||
|
|
||||||
if (format === 'resolved') {
|
|
||||||
const dataPropertyAttachmentsPrefixName = this.getNodeParameter(
|
|
||||||
'dataPropertyAttachmentsPrefixName',
|
|
||||||
) as string;
|
|
||||||
|
|
||||||
for (const message of results) {
|
|
||||||
if (
|
|
||||||
staticData.lastMessageUid !== undefined &&
|
|
||||||
message.attributes.uid <= (staticData.lastMessageUid as number)
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
staticData.lastMessageUid === undefined ||
|
|
||||||
(staticData.lastMessageUid as number) < message.attributes.uid
|
|
||||||
) {
|
|
||||||
staticData.lastMessageUid = message.attributes.uid;
|
|
||||||
}
|
|
||||||
const part = find(message.parts, { which: '' });
|
|
||||||
|
|
||||||
if (part === undefined) {
|
|
||||||
throw new NodeOperationError(this.getNode(), 'Email part could not be parsed.');
|
|
||||||
}
|
|
||||||
const parsedEmail = await parseRawEmail.call(
|
|
||||||
this,
|
|
||||||
part.body as Buffer,
|
|
||||||
dataPropertyAttachmentsPrefixName,
|
|
||||||
);
|
|
||||||
|
|
||||||
newEmails.push(parsedEmail);
|
|
||||||
}
|
|
||||||
} else if (format === 'simple') {
|
|
||||||
const downloadAttachments = this.getNodeParameter('downloadAttachments') as boolean;
|
|
||||||
|
|
||||||
let dataPropertyAttachmentsPrefixName = '';
|
|
||||||
if (downloadAttachments) {
|
|
||||||
dataPropertyAttachmentsPrefixName = this.getNodeParameter(
|
|
||||||
'dataPropertyAttachmentsPrefixName',
|
|
||||||
) as string;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const message of results) {
|
|
||||||
if (
|
|
||||||
staticData.lastMessageUid !== undefined &&
|
|
||||||
message.attributes.uid <= (staticData.lastMessageUid as number)
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
staticData.lastMessageUid === undefined ||
|
|
||||||
(staticData.lastMessageUid as number) < message.attributes.uid
|
|
||||||
) {
|
|
||||||
staticData.lastMessageUid = message.attributes.uid;
|
|
||||||
}
|
|
||||||
const parts = getParts(message.attributes.struct as IDataObject[]);
|
|
||||||
|
|
||||||
newEmail = {
|
|
||||||
json: {
|
|
||||||
textHtml: await getText(parts, message, 'html'),
|
|
||||||
textPlain: await getText(parts, message, 'plain'),
|
|
||||||
metadata: {} as IDataObject,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const messageHeader = message.parts.filter((part) => part.which === 'HEADER');
|
|
||||||
|
|
||||||
const messageBody = messageHeader[0].body as Record<string, string[]>;
|
|
||||||
for (propertyName of Object.keys(messageBody)) {
|
|
||||||
if (messageBody[propertyName].length) {
|
|
||||||
if (topLevelProperties.includes(propertyName)) {
|
|
||||||
newEmail.json[propertyName] = messageBody[propertyName][0];
|
|
||||||
} else {
|
|
||||||
(newEmail.json.metadata as IDataObject)[propertyName] =
|
|
||||||
messageBody[propertyName][0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (downloadAttachments) {
|
|
||||||
// Get attachments and add them if any get found
|
|
||||||
attachments = await getAttachment(imapConnection, parts, message);
|
|
||||||
if (attachments.length) {
|
|
||||||
newEmail.binary = {};
|
|
||||||
for (let i = 0; i < attachments.length; i++) {
|
|
||||||
newEmail.binary[`${dataPropertyAttachmentsPrefixName}${i}`] = attachments[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newEmails.push(newEmail);
|
|
||||||
}
|
|
||||||
} else if (format === 'raw') {
|
|
||||||
for (const message of results) {
|
|
||||||
if (
|
|
||||||
staticData.lastMessageUid !== undefined &&
|
|
||||||
message.attributes.uid <= (staticData.lastMessageUid as number)
|
|
||||||
) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
staticData.lastMessageUid === undefined ||
|
|
||||||
(staticData.lastMessageUid as number) < message.attributes.uid
|
|
||||||
) {
|
|
||||||
staticData.lastMessageUid = message.attributes.uid;
|
|
||||||
}
|
|
||||||
const part = find(message.parts, { which: 'TEXT' });
|
|
||||||
|
|
||||||
if (part === undefined) {
|
|
||||||
throw new NodeOperationError(this.getNode(), 'Email part could not be parsed.');
|
|
||||||
}
|
|
||||||
// Return base64 string
|
|
||||||
newEmail = {
|
|
||||||
json: {
|
|
||||||
raw: part.body as string,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
newEmails.push(newEmail);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// only mark messages as seen once processing has finished
|
|
||||||
if (postProcessAction === 'read') {
|
|
||||||
const uidList = results.map((e) => e.attributes.uid);
|
|
||||||
if (uidList.length > 0) {
|
|
||||||
await imapConnection.addFlags(uidList, '\\SEEN');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newEmails;
|
|
||||||
};
|
|
||||||
|
|
||||||
const returnedPromise = this.helpers.createDeferredPromise();
|
const returnedPromise = this.helpers.createDeferredPromise();
|
||||||
|
|
||||||
const establishConnection = async (): Promise<ImapSimple> => {
|
const establishConnection = async (): Promise<ImapSimple> => {
|
||||||
@@ -579,7 +374,15 @@ export class EmailReadImapV2 implements INodeType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const returnData = await getNewEmails(connection, searchCriteria);
|
const returnData = await getNewEmails.call(
|
||||||
|
this,
|
||||||
|
connection,
|
||||||
|
searchCriteria,
|
||||||
|
staticData,
|
||||||
|
postProcessAction,
|
||||||
|
getText,
|
||||||
|
getAttachment,
|
||||||
|
);
|
||||||
if (returnData.length) {
|
if (returnData.length) {
|
||||||
this.emit([returnData]);
|
this.emit([returnData]);
|
||||||
}
|
}
|
||||||
|
|||||||
219
packages/nodes-base/nodes/EmailReadImap/v2/utils.ts
Normal file
219
packages/nodes-base/nodes/EmailReadImap/v2/utils.ts
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
import { getParts, type ImapSimple, type Message, type MessagePart } from '@n8n/imap';
|
||||||
|
import { find } from 'lodash';
|
||||||
|
import { simpleParser, type Source as ParserSource } from 'mailparser';
|
||||||
|
import {
|
||||||
|
type IBinaryData,
|
||||||
|
type INodeExecutionData,
|
||||||
|
type IDataObject,
|
||||||
|
type ITriggerFunctions,
|
||||||
|
NodeOperationError,
|
||||||
|
type IBinaryKeyData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
async function parseRawEmail(
|
||||||
|
this: ITriggerFunctions,
|
||||||
|
messageEncoded: ParserSource,
|
||||||
|
dataPropertyNameDownload: string,
|
||||||
|
): Promise<INodeExecutionData> {
|
||||||
|
const responseData = await simpleParser(messageEncoded);
|
||||||
|
const headers: IDataObject = {};
|
||||||
|
const additionalData: IDataObject = {};
|
||||||
|
|
||||||
|
for (const header of responseData.headerLines) {
|
||||||
|
headers[header.key] = header.line;
|
||||||
|
}
|
||||||
|
|
||||||
|
additionalData.headers = headers;
|
||||||
|
additionalData.headerLines = undefined;
|
||||||
|
|
||||||
|
const binaryData: IBinaryKeyData = {};
|
||||||
|
if (responseData.attachments) {
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
additionalData.attachments = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
json: { ...responseData, ...additionalData },
|
||||||
|
binary: Object.keys(binaryData).length ? binaryData : undefined,
|
||||||
|
} as INodeExecutionData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getNewEmails(
|
||||||
|
this: ITriggerFunctions,
|
||||||
|
imapConnection: ImapSimple,
|
||||||
|
searchCriteria: Array<string | string[]>,
|
||||||
|
staticData: IDataObject,
|
||||||
|
postProcessAction: string,
|
||||||
|
getText: (parts: MessagePart[], message: Message, subtype: string) => Promise<string>,
|
||||||
|
getAttachment: (
|
||||||
|
imapConnection: ImapSimple,
|
||||||
|
parts: MessagePart[],
|
||||||
|
message: Message,
|
||||||
|
) => Promise<IBinaryData[]>,
|
||||||
|
): Promise<INodeExecutionData[]> {
|
||||||
|
const format = this.getNodeParameter('format', 0) as string;
|
||||||
|
|
||||||
|
let fetchOptions = {};
|
||||||
|
|
||||||
|
if (format === 'simple' || format === 'raw') {
|
||||||
|
fetchOptions = {
|
||||||
|
bodies: ['TEXT', 'HEADER'],
|
||||||
|
markSeen: false,
|
||||||
|
struct: true,
|
||||||
|
};
|
||||||
|
} else if (format === 'resolved') {
|
||||||
|
fetchOptions = {
|
||||||
|
bodies: [''],
|
||||||
|
markSeen: false,
|
||||||
|
struct: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = await imapConnection.search(searchCriteria, fetchOptions);
|
||||||
|
|
||||||
|
const newEmails: INodeExecutionData[] = [];
|
||||||
|
let newEmail: INodeExecutionData;
|
||||||
|
let attachments: IBinaryData[];
|
||||||
|
let propertyName: string;
|
||||||
|
|
||||||
|
// All properties get by default moved to metadata except the ones
|
||||||
|
// which are defined here which get set on the top level.
|
||||||
|
const topLevelProperties = ['cc', 'date', 'from', 'subject', 'to'];
|
||||||
|
|
||||||
|
if (format === 'resolved') {
|
||||||
|
const dataPropertyAttachmentsPrefixName = this.getNodeParameter(
|
||||||
|
'dataPropertyAttachmentsPrefixName',
|
||||||
|
) as string;
|
||||||
|
|
||||||
|
for (const message of results) {
|
||||||
|
if (
|
||||||
|
staticData.lastMessageUid !== undefined &&
|
||||||
|
message.attributes.uid <= (staticData.lastMessageUid as number)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
staticData.lastMessageUid === undefined ||
|
||||||
|
(staticData.lastMessageUid as number) < message.attributes.uid
|
||||||
|
) {
|
||||||
|
staticData.lastMessageUid = message.attributes.uid;
|
||||||
|
}
|
||||||
|
const part = find(message.parts, { which: '' });
|
||||||
|
|
||||||
|
if (part === undefined) {
|
||||||
|
throw new NodeOperationError(this.getNode(), 'Email part could not be parsed.');
|
||||||
|
}
|
||||||
|
const parsedEmail = await parseRawEmail.call(
|
||||||
|
this,
|
||||||
|
part.body as Buffer,
|
||||||
|
dataPropertyAttachmentsPrefixName,
|
||||||
|
);
|
||||||
|
|
||||||
|
newEmails.push(parsedEmail);
|
||||||
|
}
|
||||||
|
} else if (format === 'simple') {
|
||||||
|
const downloadAttachments = this.getNodeParameter('downloadAttachments') as boolean;
|
||||||
|
|
||||||
|
let dataPropertyAttachmentsPrefixName = '';
|
||||||
|
if (downloadAttachments) {
|
||||||
|
dataPropertyAttachmentsPrefixName = this.getNodeParameter(
|
||||||
|
'dataPropertyAttachmentsPrefixName',
|
||||||
|
) as string;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const message of results) {
|
||||||
|
if (
|
||||||
|
staticData.lastMessageUid !== undefined &&
|
||||||
|
message.attributes.uid <= (staticData.lastMessageUid as number)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
staticData.lastMessageUid === undefined ||
|
||||||
|
(staticData.lastMessageUid as number) < message.attributes.uid
|
||||||
|
) {
|
||||||
|
staticData.lastMessageUid = message.attributes.uid;
|
||||||
|
}
|
||||||
|
const parts = getParts(message.attributes.struct as IDataObject[]);
|
||||||
|
|
||||||
|
newEmail = {
|
||||||
|
json: {
|
||||||
|
textHtml: await getText(parts, message, 'html'),
|
||||||
|
textPlain: await getText(parts, message, 'plain'),
|
||||||
|
metadata: {} as IDataObject,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const messageHeader = message.parts.filter((part) => part.which === 'HEADER');
|
||||||
|
|
||||||
|
const messageBody = messageHeader[0].body as Record<string, string[]>;
|
||||||
|
for (propertyName of Object.keys(messageBody)) {
|
||||||
|
if (messageBody[propertyName].length) {
|
||||||
|
if (topLevelProperties.includes(propertyName)) {
|
||||||
|
newEmail.json[propertyName] = messageBody[propertyName][0];
|
||||||
|
} else {
|
||||||
|
(newEmail.json.metadata as IDataObject)[propertyName] = messageBody[propertyName][0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloadAttachments) {
|
||||||
|
// Get attachments and add them if any get found
|
||||||
|
attachments = await getAttachment(imapConnection, parts, message);
|
||||||
|
if (attachments.length) {
|
||||||
|
newEmail.binary = {};
|
||||||
|
for (let i = 0; i < attachments.length; i++) {
|
||||||
|
newEmail.binary[`${dataPropertyAttachmentsPrefixName}${i}`] = attachments[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newEmails.push(newEmail);
|
||||||
|
}
|
||||||
|
} else if (format === 'raw') {
|
||||||
|
for (const message of results) {
|
||||||
|
if (
|
||||||
|
staticData.lastMessageUid !== undefined &&
|
||||||
|
message.attributes.uid <= (staticData.lastMessageUid as number)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
staticData.lastMessageUid === undefined ||
|
||||||
|
(staticData.lastMessageUid as number) < message.attributes.uid
|
||||||
|
) {
|
||||||
|
staticData.lastMessageUid = message.attributes.uid;
|
||||||
|
}
|
||||||
|
const part = find(message.parts, { which: 'TEXT' });
|
||||||
|
|
||||||
|
if (part === undefined) {
|
||||||
|
throw new NodeOperationError(this.getNode(), 'Email part could not be parsed.');
|
||||||
|
}
|
||||||
|
// Return base64 string
|
||||||
|
newEmail = {
|
||||||
|
json: {
|
||||||
|
raw: part.body as string,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
newEmails.push(newEmail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// only mark messages as seen once processing has finished
|
||||||
|
if (postProcessAction === 'read') {
|
||||||
|
const uidList = results.map((e) => e.attributes.uid);
|
||||||
|
if (uidList.length > 0) {
|
||||||
|
await imapConnection.addFlags(uidList, '\\SEEN');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newEmails;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user