n8n-nodes-jmap 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IExecuteFunctions, ILoadOptionsFunctions, IPollFunctions, IDataObject } from 'n8n-workflow';
|
|
1
|
+
import type { IExecuteFunctions, ILoadOptionsFunctions, IPollFunctions, IDataObject, INodeExecutionData } from 'n8n-workflow';
|
|
2
2
|
export interface IJmapSession {
|
|
3
3
|
accounts: {
|
|
4
4
|
[key: string]: IJmapAccount;
|
|
@@ -119,3 +119,15 @@ export declare function getThreads(this: IExecuteFunctions | ILoadOptionsFunctio
|
|
|
119
119
|
* Download an attachment blob
|
|
120
120
|
*/
|
|
121
121
|
export declare function downloadBlob(this: IExecuteFunctions, accountId: string, blobId: string, name: string, type: string): Promise<Buffer>;
|
|
122
|
+
/**
|
|
123
|
+
* Interface for attachment options
|
|
124
|
+
*/
|
|
125
|
+
export interface IAttachmentOptions {
|
|
126
|
+
extractArchives?: boolean;
|
|
127
|
+
includeInline?: boolean;
|
|
128
|
+
mimeTypeFilter?: string;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Get attachments from an email and return them as binary data
|
|
132
|
+
*/
|
|
133
|
+
export declare function getAttachments(this: IExecuteFunctions, accountId: string, emailId: string, options?: IAttachmentOptions): Promise<INodeExecutionData[]>;
|
|
@@ -1,4 +1,40 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
2
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
39
|
exports.JMAP_CAPABILITIES = void 0;
|
|
4
40
|
exports.getJmapSession = getJmapSession;
|
|
@@ -20,7 +56,10 @@ exports.getLabels = getLabels;
|
|
|
20
56
|
exports.deleteEmails = deleteEmails;
|
|
21
57
|
exports.getThreads = getThreads;
|
|
22
58
|
exports.downloadBlob = downloadBlob;
|
|
59
|
+
exports.getAttachments = getAttachments;
|
|
23
60
|
const n8n_workflow_1 = require("n8n-workflow");
|
|
61
|
+
const adm_zip_1 = __importDefault(require("adm-zip"));
|
|
62
|
+
const tar = __importStar(require("tar"));
|
|
24
63
|
// Standard JMAP capabilities
|
|
25
64
|
exports.JMAP_CAPABILITIES = {
|
|
26
65
|
CORE: 'urn:ietf:params:jmap:core',
|
|
@@ -64,7 +103,7 @@ async function makeJmapRequest(context, method, endpoint, body) {
|
|
|
64
103
|
const url = endpoint.startsWith('http') ? endpoint : `${serverUrl}${endpoint}`;
|
|
65
104
|
if (authType === 'jmapOAuth2Api') {
|
|
66
105
|
// Use n8n's built-in OAuth2 authentication
|
|
67
|
-
const response = await context.helpers.
|
|
106
|
+
const response = await context.helpers.httpRequestWithAuthentication.call(context, 'jmapOAuth2Api', {
|
|
68
107
|
method,
|
|
69
108
|
url,
|
|
70
109
|
headers: {
|
|
@@ -80,29 +119,25 @@ async function makeJmapRequest(context, method, endpoint, body) {
|
|
|
80
119
|
// Use Basic Auth or Bearer Token
|
|
81
120
|
const credentials = await context.getCredentials('jmapApi');
|
|
82
121
|
const authMethod = credentials.authMethod || 'basicAuth';
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
headers: {
|
|
87
|
-
'Content-Type': 'application/json',
|
|
88
|
-
Accept: 'application/json',
|
|
89
|
-
},
|
|
90
|
-
body,
|
|
91
|
-
json: true,
|
|
122
|
+
const headers = {
|
|
123
|
+
'Content-Type': 'application/json',
|
|
124
|
+
Accept: 'application/json',
|
|
92
125
|
};
|
|
93
126
|
if (authMethod === 'basicAuth') {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
pass: credentials.password,
|
|
97
|
-
};
|
|
127
|
+
const authString = Buffer.from(`${credentials.email}:${credentials.password}`).toString('base64');
|
|
128
|
+
headers.Authorization = `Basic ${authString}`;
|
|
98
129
|
}
|
|
99
130
|
else if (authMethod === 'bearerToken') {
|
|
100
|
-
|
|
101
|
-
...options.headers,
|
|
102
|
-
Authorization: `Bearer ${credentials.accessToken}`,
|
|
103
|
-
};
|
|
131
|
+
headers.Authorization = `Bearer ${credentials.accessToken}`;
|
|
104
132
|
}
|
|
105
|
-
const
|
|
133
|
+
const options = {
|
|
134
|
+
method,
|
|
135
|
+
url,
|
|
136
|
+
headers,
|
|
137
|
+
body,
|
|
138
|
+
json: true,
|
|
139
|
+
};
|
|
140
|
+
const response = await context.helpers.httpRequest(options);
|
|
106
141
|
return response;
|
|
107
142
|
}
|
|
108
143
|
}
|
|
@@ -435,32 +470,285 @@ async function downloadBlob(accountId, blobId, name, type) {
|
|
|
435
470
|
.replace('{name}', encodeURIComponent(name))
|
|
436
471
|
.replace('{type}', encodeURIComponent(type));
|
|
437
472
|
if (authType === 'jmapOAuth2Api') {
|
|
438
|
-
const response = await this.helpers.
|
|
473
|
+
const response = await this.helpers.httpRequestWithAuthentication.call(this, 'jmapOAuth2Api', {
|
|
439
474
|
method: 'GET',
|
|
440
475
|
url: downloadUrl,
|
|
441
|
-
encoding:
|
|
476
|
+
encoding: 'arraybuffer',
|
|
442
477
|
});
|
|
443
|
-
return response;
|
|
478
|
+
return Buffer.from(response);
|
|
444
479
|
}
|
|
445
480
|
else {
|
|
446
481
|
const credentials = await this.getCredentials('jmapApi');
|
|
447
482
|
const authMethod = credentials.authMethod || 'basicAuth';
|
|
483
|
+
const headers = {};
|
|
484
|
+
if (authMethod === 'basicAuth') {
|
|
485
|
+
const authString = Buffer.from(`${credentials.email}:${credentials.password}`).toString('base64');
|
|
486
|
+
headers.Authorization = `Basic ${authString}`;
|
|
487
|
+
}
|
|
488
|
+
else if (authMethod === 'bearerToken') {
|
|
489
|
+
headers.Authorization = `Bearer ${credentials.accessToken}`;
|
|
490
|
+
}
|
|
448
491
|
const options = {
|
|
449
492
|
method: 'GET',
|
|
450
|
-
|
|
451
|
-
|
|
493
|
+
url: downloadUrl,
|
|
494
|
+
headers,
|
|
495
|
+
encoding: 'arraybuffer',
|
|
452
496
|
};
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
497
|
+
const response = await this.helpers.httpRequest(options);
|
|
498
|
+
return Buffer.from(response);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Check if a MIME type matches a filter pattern
|
|
503
|
+
*/
|
|
504
|
+
function matchesMimeType(mimeType, filter) {
|
|
505
|
+
const normalizedMime = mimeType.toLowerCase();
|
|
506
|
+
const normalizedFilter = filter.toLowerCase().trim();
|
|
507
|
+
if (normalizedFilter.endsWith('/*')) {
|
|
508
|
+
const prefix = normalizedFilter.slice(0, -1);
|
|
509
|
+
return normalizedMime.startsWith(prefix);
|
|
510
|
+
}
|
|
511
|
+
return normalizedMime === normalizedFilter;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Check if a MIME type is a ZIP archive
|
|
515
|
+
*/
|
|
516
|
+
function isZipMimeType(mimeType) {
|
|
517
|
+
const zipTypes = [
|
|
518
|
+
'application/zip',
|
|
519
|
+
'application/x-zip-compressed',
|
|
520
|
+
'application/x-zip',
|
|
521
|
+
];
|
|
522
|
+
return zipTypes.includes(mimeType.toLowerCase());
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Check if a MIME type is a tar.gz archive
|
|
526
|
+
*/
|
|
527
|
+
function isTarGzMimeType(mimeType) {
|
|
528
|
+
const tarGzTypes = [
|
|
529
|
+
'application/gzip',
|
|
530
|
+
'application/x-gzip',
|
|
531
|
+
'application/x-tar',
|
|
532
|
+
'application/x-compressed-tar',
|
|
533
|
+
];
|
|
534
|
+
return tarGzTypes.includes(mimeType.toLowerCase());
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Check if filename suggests tar.gz
|
|
538
|
+
*/
|
|
539
|
+
function isTarGzFilename(filename) {
|
|
540
|
+
const lower = filename.toLowerCase();
|
|
541
|
+
return lower.endsWith('.tar.gz') || lower.endsWith('.tgz');
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Get file extension from filename
|
|
545
|
+
*/
|
|
546
|
+
function getFileExtension(filename) {
|
|
547
|
+
const parts = filename.split('.');
|
|
548
|
+
if (parts.length > 1) {
|
|
549
|
+
return parts[parts.length - 1].toLowerCase();
|
|
550
|
+
}
|
|
551
|
+
return '';
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Guess MIME type from filename extension
|
|
555
|
+
*/
|
|
556
|
+
function guessMimeType(filename) {
|
|
557
|
+
const ext = getFileExtension(filename).toLowerCase();
|
|
558
|
+
const mimeTypes = {
|
|
559
|
+
pdf: 'application/pdf',
|
|
560
|
+
doc: 'application/msword',
|
|
561
|
+
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
562
|
+
xls: 'application/vnd.ms-excel',
|
|
563
|
+
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
564
|
+
ppt: 'application/vnd.ms-powerpoint',
|
|
565
|
+
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
566
|
+
txt: 'text/plain',
|
|
567
|
+
csv: 'text/csv',
|
|
568
|
+
json: 'application/json',
|
|
569
|
+
xml: 'application/xml',
|
|
570
|
+
html: 'text/html',
|
|
571
|
+
htm: 'text/html',
|
|
572
|
+
jpg: 'image/jpeg',
|
|
573
|
+
jpeg: 'image/jpeg',
|
|
574
|
+
png: 'image/png',
|
|
575
|
+
gif: 'image/gif',
|
|
576
|
+
svg: 'image/svg+xml',
|
|
577
|
+
webp: 'image/webp',
|
|
578
|
+
mp3: 'audio/mpeg',
|
|
579
|
+
wav: 'audio/wav',
|
|
580
|
+
mp4: 'video/mp4',
|
|
581
|
+
avi: 'video/x-msvideo',
|
|
582
|
+
zip: 'application/zip',
|
|
583
|
+
tar: 'application/x-tar',
|
|
584
|
+
gz: 'application/gzip',
|
|
585
|
+
};
|
|
586
|
+
return mimeTypes[ext] || 'application/octet-stream';
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Extract files from a ZIP archive
|
|
590
|
+
*/
|
|
591
|
+
function extractZip(buffer) {
|
|
592
|
+
const files = [];
|
|
593
|
+
const zip = new adm_zip_1.default(buffer);
|
|
594
|
+
const entries = zip.getEntries();
|
|
595
|
+
for (const entry of entries) {
|
|
596
|
+
if (!entry.isDirectory) {
|
|
597
|
+
const data = entry.getData();
|
|
598
|
+
const name = entry.entryName.split('/').pop() || entry.entryName;
|
|
599
|
+
files.push({
|
|
600
|
+
name,
|
|
601
|
+
data,
|
|
602
|
+
mimeType: guessMimeType(name),
|
|
603
|
+
});
|
|
458
604
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
605
|
+
}
|
|
606
|
+
return files;
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Extract files from a tar.gz archive
|
|
610
|
+
*/
|
|
611
|
+
async function extractTarGz(buffer) {
|
|
612
|
+
const files = [];
|
|
613
|
+
return new Promise((resolve, reject) => {
|
|
614
|
+
const chunks = new Map();
|
|
615
|
+
const parser = new tar.Parser();
|
|
616
|
+
parser.on('entry', (entry) => {
|
|
617
|
+
if (entry.type === 'File') {
|
|
618
|
+
const entryChunks = [];
|
|
619
|
+
entry.on('data', (chunk) => {
|
|
620
|
+
entryChunks.push(chunk);
|
|
621
|
+
});
|
|
622
|
+
entry.on('end', () => {
|
|
623
|
+
const name = entry.path.split('/').pop() || entry.path;
|
|
624
|
+
chunks.set(name, entryChunks);
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
entry.resume();
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
parser.on('end', () => {
|
|
632
|
+
for (const [name, entryChunks] of chunks) {
|
|
633
|
+
const data = Buffer.concat(entryChunks);
|
|
634
|
+
files.push({
|
|
635
|
+
name,
|
|
636
|
+
data,
|
|
637
|
+
mimeType: guessMimeType(name),
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
resolve(files);
|
|
641
|
+
});
|
|
642
|
+
parser.on('error', reject);
|
|
643
|
+
const { Gunzip } = require('zlib');
|
|
644
|
+
const gunzip = new Gunzip();
|
|
645
|
+
gunzip.pipe(parser);
|
|
646
|
+
gunzip.write(buffer);
|
|
647
|
+
gunzip.end();
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Get attachments from an email and return them as binary data
|
|
652
|
+
*/
|
|
653
|
+
async function getAttachments(accountId, emailId, options = {}) {
|
|
654
|
+
const { extractArchives = false, includeInline = false, mimeTypeFilter = '' } = options;
|
|
655
|
+
// Get email with attachments metadata
|
|
656
|
+
const emails = await getEmails.call(this, accountId, [emailId], [
|
|
657
|
+
'id',
|
|
658
|
+
'subject',
|
|
659
|
+
'attachments',
|
|
660
|
+
]);
|
|
661
|
+
if (emails.length === 0) {
|
|
662
|
+
throw new Error(`Email with ID ${emailId} not found`);
|
|
663
|
+
}
|
|
664
|
+
const email = emails[0];
|
|
665
|
+
const attachments = email.attachments || [];
|
|
666
|
+
if (attachments.length === 0) {
|
|
667
|
+
return [];
|
|
668
|
+
}
|
|
669
|
+
// Parse MIME type filters
|
|
670
|
+
const mimeFilters = mimeTypeFilter
|
|
671
|
+
? mimeTypeFilter.split(',').map((f) => f.trim()).filter((f) => f)
|
|
672
|
+
: [];
|
|
673
|
+
const results = [];
|
|
674
|
+
let attachmentIndex = 0;
|
|
675
|
+
for (const attachment of attachments) {
|
|
676
|
+
// Filter by inline status
|
|
677
|
+
if (attachment.isInline && !includeInline) {
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
// Filter by MIME type
|
|
681
|
+
if (mimeFilters.length > 0) {
|
|
682
|
+
const matches = mimeFilters.some((filter) => matchesMimeType(attachment.type, filter));
|
|
683
|
+
if (!matches) {
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
463
686
|
}
|
|
464
|
-
|
|
687
|
+
// Download the attachment
|
|
688
|
+
const buffer = await downloadBlob.call(this, accountId, attachment.blobId, attachment.name, attachment.type);
|
|
689
|
+
// Check if this is an archive that should be extracted
|
|
690
|
+
const isZip = isZipMimeType(attachment.type);
|
|
691
|
+
const isTarGz = isTarGzMimeType(attachment.type) || isTarGzFilename(attachment.name);
|
|
692
|
+
if (extractArchives && (isZip || isTarGz)) {
|
|
693
|
+
// Extract archive contents
|
|
694
|
+
let extractedFiles = [];
|
|
695
|
+
try {
|
|
696
|
+
if (isZip) {
|
|
697
|
+
extractedFiles = extractZip(buffer);
|
|
698
|
+
}
|
|
699
|
+
else if (isTarGz) {
|
|
700
|
+
extractedFiles = await extractTarGz(buffer);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
catch (error) {
|
|
704
|
+
// If extraction fails, return the archive as-is
|
|
705
|
+
extractedFiles = [];
|
|
706
|
+
}
|
|
707
|
+
if (extractedFiles.length > 0) {
|
|
708
|
+
// Return each extracted file as a separate item
|
|
709
|
+
for (const file of extractedFiles) {
|
|
710
|
+
const binaryData = await this.helpers.prepareBinaryData(file.data, file.name, file.mimeType);
|
|
711
|
+
results.push({
|
|
712
|
+
json: {
|
|
713
|
+
emailId: email.id,
|
|
714
|
+
emailSubject: email.subject,
|
|
715
|
+
attachmentIndex,
|
|
716
|
+
originalFileName: attachment.name,
|
|
717
|
+
fileName: file.name,
|
|
718
|
+
mimeType: file.mimeType,
|
|
719
|
+
fileSize: file.data.length,
|
|
720
|
+
wasExtractedFromArchive: true,
|
|
721
|
+
sourceArchiveName: attachment.name,
|
|
722
|
+
},
|
|
723
|
+
binary: {
|
|
724
|
+
file: binaryData,
|
|
725
|
+
},
|
|
726
|
+
});
|
|
727
|
+
attachmentIndex++;
|
|
728
|
+
}
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
// If extraction failed or no files, fall through to return the archive as-is
|
|
732
|
+
}
|
|
733
|
+
// Return the attachment as-is (not an archive, or extraction disabled/failed)
|
|
734
|
+
const binaryData = await this.helpers.prepareBinaryData(buffer, attachment.name, attachment.type);
|
|
735
|
+
results.push({
|
|
736
|
+
json: {
|
|
737
|
+
emailId: email.id,
|
|
738
|
+
emailSubject: email.subject,
|
|
739
|
+
attachmentIndex,
|
|
740
|
+
originalFileName: attachment.name,
|
|
741
|
+
fileName: attachment.name,
|
|
742
|
+
mimeType: attachment.type,
|
|
743
|
+
fileSize: attachment.size,
|
|
744
|
+
wasExtractedFromArchive: false,
|
|
745
|
+
sourceArchiveName: null,
|
|
746
|
+
},
|
|
747
|
+
binary: {
|
|
748
|
+
file: binaryData,
|
|
749
|
+
},
|
|
750
|
+
});
|
|
751
|
+
attachmentIndex++;
|
|
465
752
|
}
|
|
753
|
+
return results;
|
|
466
754
|
}
|
|
@@ -115,6 +115,12 @@ class Jmap {
|
|
|
115
115
|
description: 'Get an email by ID',
|
|
116
116
|
action: 'Get an email',
|
|
117
117
|
},
|
|
118
|
+
{
|
|
119
|
+
name: 'Get Attachments',
|
|
120
|
+
value: 'getAttachments',
|
|
121
|
+
description: 'Download attachments from an email',
|
|
122
|
+
action: 'Get attachments from an email',
|
|
123
|
+
},
|
|
118
124
|
{
|
|
119
125
|
name: 'Get Labels',
|
|
120
126
|
value: 'getLabels',
|
|
@@ -221,7 +227,7 @@ class Jmap {
|
|
|
221
227
|
default: 'get',
|
|
222
228
|
},
|
|
223
229
|
// ==================== EMAIL PARAMETERS ====================
|
|
224
|
-
// Email ID (for get, delete, markAsRead, markAsUnread, move, reply, addLabel, removeLabel, getLabels)
|
|
230
|
+
// Email ID (for get, delete, markAsRead, markAsUnread, move, reply, addLabel, removeLabel, getLabels, getAttachments)
|
|
225
231
|
{
|
|
226
232
|
displayName: 'Email ID',
|
|
227
233
|
name: 'emailId',
|
|
@@ -230,7 +236,7 @@ class Jmap {
|
|
|
230
236
|
displayOptions: {
|
|
231
237
|
show: {
|
|
232
238
|
resource: ['email'],
|
|
233
|
-
operation: ['get', 'delete', 'markAsRead', 'markAsUnread', 'move', 'reply', 'addLabel', 'removeLabel', 'getLabels'],
|
|
239
|
+
operation: ['get', 'delete', 'markAsRead', 'markAsUnread', 'move', 'reply', 'addLabel', 'removeLabel', 'getLabels', 'getAttachments'],
|
|
234
240
|
},
|
|
235
241
|
},
|
|
236
242
|
default: '',
|
|
@@ -409,6 +415,44 @@ class Jmap {
|
|
|
409
415
|
},
|
|
410
416
|
],
|
|
411
417
|
},
|
|
418
|
+
// Options for getAttachments
|
|
419
|
+
{
|
|
420
|
+
displayName: 'Options',
|
|
421
|
+
name: 'attachmentOptions',
|
|
422
|
+
type: 'collection',
|
|
423
|
+
placeholder: 'Add Option',
|
|
424
|
+
default: {},
|
|
425
|
+
displayOptions: {
|
|
426
|
+
show: {
|
|
427
|
+
resource: ['email'],
|
|
428
|
+
operation: ['getAttachments'],
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
options: [
|
|
432
|
+
{
|
|
433
|
+
displayName: 'Extract Archives',
|
|
434
|
+
name: 'extractArchives',
|
|
435
|
+
type: 'boolean',
|
|
436
|
+
default: false,
|
|
437
|
+
description: 'Whether to extract ZIP and tar.gz archives and return their contents as individual files',
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
displayName: 'Include Inline Images',
|
|
441
|
+
name: 'includeInline',
|
|
442
|
+
type: 'boolean',
|
|
443
|
+
default: false,
|
|
444
|
+
description: 'Whether to include inline images (embedded in the email body) in addition to regular attachments',
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
displayName: 'Filter by MIME Type',
|
|
448
|
+
name: 'mimeTypeFilter',
|
|
449
|
+
type: 'string',
|
|
450
|
+
default: '',
|
|
451
|
+
placeholder: 'application/pdf, image/*',
|
|
452
|
+
description: 'Comma-separated list of MIME types to include. Supports wildcards (e.g., image/*). Leave empty to include all.',
|
|
453
|
+
},
|
|
454
|
+
],
|
|
455
|
+
},
|
|
412
456
|
// Limit for getMany
|
|
413
457
|
{
|
|
414
458
|
displayName: 'Limit',
|
|
@@ -576,6 +620,20 @@ class Jmap {
|
|
|
576
620
|
count: labels.length,
|
|
577
621
|
};
|
|
578
622
|
}
|
|
623
|
+
if (operation === 'getAttachments') {
|
|
624
|
+
const emailId = this.getNodeParameter('emailId', i);
|
|
625
|
+
const attachmentOptions = this.getNodeParameter('attachmentOptions', i);
|
|
626
|
+
const attachmentResults = await GenericFunctions_1.getAttachments.call(this, accountId, emailId, attachmentOptions);
|
|
627
|
+
// getAttachments returns INodeExecutionData[] directly with binary data
|
|
628
|
+
// Add them directly to returnData instead of using responseData
|
|
629
|
+
for (const result of attachmentResults) {
|
|
630
|
+
returnData.push({
|
|
631
|
+
...result,
|
|
632
|
+
pairedItem: { item: i },
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
continue; // Skip the normal responseData handling
|
|
636
|
+
}
|
|
579
637
|
if (operation === 'getMany') {
|
|
580
638
|
const mailbox = this.getNodeParameter('mailbox', i);
|
|
581
639
|
const limit = this.getNodeParameter('limit', i);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "n8n-nodes-jmap",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "n8n community node for JMAP email protocol (RFC 8620/8621) - Works with Apache James, Twake Mail, Fastmail, and other JMAP-compatible servers",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"n8n-community-node-package",
|
|
@@ -46,7 +46,9 @@
|
|
|
46
46
|
]
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
+
"@types/adm-zip": "^0.5.7",
|
|
49
50
|
"@types/node": "^20.0.0",
|
|
51
|
+
"@types/tar": "^6.1.13",
|
|
50
52
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
51
53
|
"@typescript-eslint/parser": "^7.0.0",
|
|
52
54
|
"eslint": "^8.56.0",
|
|
@@ -57,5 +59,9 @@
|
|
|
57
59
|
},
|
|
58
60
|
"peerDependencies": {
|
|
59
61
|
"n8n-workflow": "*"
|
|
62
|
+
},
|
|
63
|
+
"dependencies": {
|
|
64
|
+
"adm-zip": "^0.5.16",
|
|
65
|
+
"tar": "^7.5.2"
|
|
60
66
|
}
|
|
61
67
|
}
|