imapflow 1.0.117 → 1.0.119
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.
- package/CHANGELOG.md +4 -0
- package/lib/commands/list.js +71 -18
- package/lib/imap-flow.js +5 -0
- package/lib/special-use.js +15 -3
- package/lib/types.d.ts +11 -0
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
package/lib/commands/list.js
CHANGED
|
@@ -7,11 +7,13 @@ const { specialUse } = require('../special-use');
|
|
|
7
7
|
module.exports = async (connection, reference, mailbox, options) => {
|
|
8
8
|
options = options || {};
|
|
9
9
|
|
|
10
|
+
const FLAG_SORT_ORDER = ['\\Inbox', '\\Flagged', '\\Sent', '\\Drafts', '\\All', '\\Archive', '\\Junk', '\\Trash'];
|
|
11
|
+
const SOURCE_SORT_ORDER = ['user', 'extension', 'name'];
|
|
12
|
+
|
|
10
13
|
let listCommand = connection.capabilities.has('XLIST') && !connection.capabilities.has('SPECIAL-USE') ? 'XLIST' : 'LIST';
|
|
11
14
|
|
|
12
15
|
let response;
|
|
13
16
|
try {
|
|
14
|
-
let flagsSeen = new Set();
|
|
15
17
|
let entries = [];
|
|
16
18
|
|
|
17
19
|
let statusMap = new Map();
|
|
@@ -49,6 +51,23 @@ module.exports = async (connection, reference, mailbox, options) => {
|
|
|
49
51
|
}
|
|
50
52
|
}
|
|
51
53
|
|
|
54
|
+
let specialUseMatches = {};
|
|
55
|
+
let addSpecialUseMatch = (entry, type, source) => {
|
|
56
|
+
if (!specialUseMatches[type]) {
|
|
57
|
+
specialUseMatches[type] = [];
|
|
58
|
+
}
|
|
59
|
+
specialUseMatches[type].push({ entry, source });
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
let specialUseHints = {};
|
|
63
|
+
if (options.specialUseHints && typeof options.specialUseHints === 'object') {
|
|
64
|
+
for (let type of Object.keys(options.specialUseHints)) {
|
|
65
|
+
if (['sent', 'junk', 'trash', 'drafts'].includes(type) && options.specialUseHints[type] && typeof options.specialUseHints[type] === 'string') {
|
|
66
|
+
specialUseHints[normalizePath(connection, options.specialUseHints[type])] = `\\${type.replace(/^./, c => c.toUpperCase())}`;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
52
71
|
let runList = async (reference, mailbox) => {
|
|
53
72
|
const cmdArgs = [encodePath(connection, reference), encodePath(connection, mailbox)];
|
|
54
73
|
|
|
@@ -71,17 +90,21 @@ module.exports = async (connection, reference, mailbox, options) => {
|
|
|
71
90
|
listed: true
|
|
72
91
|
};
|
|
73
92
|
|
|
93
|
+
if (specialUseHints[entry.path]) {
|
|
94
|
+
addSpecialUseMatch(entry, specialUseHints[entry.path], 'user');
|
|
95
|
+
}
|
|
96
|
+
|
|
74
97
|
if (listCommand === 'XLIST' && entry.flags.has('\\Inbox')) {
|
|
75
98
|
// XLIST specific flag, ignore
|
|
76
99
|
entry.flags.delete('\\Inbox');
|
|
77
100
|
if (entry.path !== 'INBOX') {
|
|
78
101
|
// XLIST may use localised inbox name
|
|
79
|
-
entry
|
|
102
|
+
addSpecialUseMatch(entry, '\\Inbox', 'extension');
|
|
80
103
|
}
|
|
81
104
|
}
|
|
82
105
|
|
|
83
106
|
if (entry.path.toUpperCase() === 'INBOX') {
|
|
84
|
-
entry
|
|
107
|
+
addSpecialUseMatch(entry, '\\Inbox', 'name');
|
|
85
108
|
}
|
|
86
109
|
|
|
87
110
|
if (entry.delimiter && entry.path.charAt(0) === entry.delimiter) {
|
|
@@ -92,9 +115,13 @@ module.exports = async (connection, reference, mailbox, options) => {
|
|
|
92
115
|
entry.parent = entry.delimiter ? entry.path.split(entry.delimiter) : [entry.path];
|
|
93
116
|
entry.name = entry.parent.pop();
|
|
94
117
|
|
|
95
|
-
let specialUseFlag = specialUse(
|
|
96
|
-
|
|
97
|
-
entry
|
|
118
|
+
let { flag: specialUseFlag, source: flagSource } = specialUse(
|
|
119
|
+
connection.capabilities.has('XLIST') || connection.capabilities.has('SPECIAL-USE'),
|
|
120
|
+
entry
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
if (specialUseFlag) {
|
|
124
|
+
addSpecialUseMatch(entry, specialUseFlag, flagSource);
|
|
98
125
|
}
|
|
99
126
|
|
|
100
127
|
entries.push(entry);
|
|
@@ -172,7 +199,7 @@ module.exports = async (connection, reference, mailbox, options) => {
|
|
|
172
199
|
return entries;
|
|
173
200
|
}
|
|
174
201
|
|
|
175
|
-
if (normalizedReference && !
|
|
202
|
+
if (normalizedReference && !specialUseMatches['\\Inbox']) {
|
|
176
203
|
// INBOX was most probably not included in the listing if namespace was used
|
|
177
204
|
await runList('', 'INBOX');
|
|
178
205
|
}
|
|
@@ -213,7 +240,7 @@ module.exports = async (connection, reference, mailbox, options) => {
|
|
|
213
240
|
};
|
|
214
241
|
|
|
215
242
|
if (entry.path.toUpperCase() === 'INBOX') {
|
|
216
|
-
entry
|
|
243
|
+
addSpecialUseMatch(entry, '\\Inbox', 'name');
|
|
217
244
|
}
|
|
218
245
|
|
|
219
246
|
if (entry.delimiter && entry.path.charAt(0) === entry.delimiter) {
|
|
@@ -244,26 +271,52 @@ module.exports = async (connection, reference, mailbox, options) => {
|
|
|
244
271
|
);
|
|
245
272
|
response.next();
|
|
246
273
|
|
|
274
|
+
for (let type of Object.keys(specialUseMatches)) {
|
|
275
|
+
let sortedEntries = specialUseMatches[type].sort((a, b) => {
|
|
276
|
+
let aSource = SOURCE_SORT_ORDER.indexOf(a.source);
|
|
277
|
+
let bSource = SOURCE_SORT_ORDER.indexOf(b.source);
|
|
278
|
+
if (aSource === bSource) {
|
|
279
|
+
return a.entry.path.localeCompare(b.entry.path);
|
|
280
|
+
}
|
|
281
|
+
return aSource - bSource;
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
if (!sortedEntries[0].entry.specialUse) {
|
|
285
|
+
sortedEntries[0].entry.specialUse = type;
|
|
286
|
+
sortedEntries[0].entry.specialUseSource = sortedEntries[0].source;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
247
290
|
let inboxEntry = entries.find(entry => entry.specialUse === '\\Inbox');
|
|
248
291
|
if (inboxEntry && !inboxEntry.subscribed) {
|
|
249
292
|
// override server settings and make INBOX always as subscribed
|
|
250
293
|
inboxEntry.subscribed = true;
|
|
251
294
|
}
|
|
252
295
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
296
|
+
return entries.sort((a, b) => {
|
|
297
|
+
if (a.specialUse && !b.specialUse) {
|
|
298
|
+
return -1;
|
|
299
|
+
}
|
|
300
|
+
if (!a.specialUse && b.specialUse) {
|
|
301
|
+
return 1;
|
|
257
302
|
}
|
|
303
|
+
if (a.specialUse && b.specialUse) {
|
|
304
|
+
return FLAG_SORT_ORDER.indexOf(a.specialUse) - FLAG_SORT_ORDER.indexOf(b.specialUse);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
let aList = [].concat(a.parent).concat(a.name);
|
|
308
|
+
let bList = [].concat(b.parent).concat(b.name);
|
|
258
309
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
310
|
+
for (let i = 0; i < aList.length; i++) {
|
|
311
|
+
let aPart = aList[i];
|
|
312
|
+
let bPart = bList[i];
|
|
313
|
+
if (aPart !== bPart) {
|
|
314
|
+
return aPart.localeCompare(bPart || '');
|
|
315
|
+
}
|
|
263
316
|
}
|
|
264
|
-
});
|
|
265
317
|
|
|
266
|
-
|
|
318
|
+
return a.path.localeCompare(b.path);
|
|
319
|
+
});
|
|
267
320
|
} catch (err) {
|
|
268
321
|
connection.log.warn({ msg: 'Failed to list folders', err, cid: connection.id });
|
|
269
322
|
throw err;
|
package/lib/imap-flow.js
CHANGED
|
@@ -1452,6 +1452,11 @@ class ImapFlow extends EventEmitter {
|
|
|
1452
1452
|
* @param {Boolean} [options.statusQuery.uidValidity] if `true` request mailbox `UIDVALIDITY` value
|
|
1453
1453
|
* @param {Boolean} [options.statusQuery.unseen] if `true` request count of unseen messages
|
|
1454
1454
|
* @param {Boolean} [options.statusQuery.highestModseq] if `true` request last known modseq value
|
|
1455
|
+
* @param {Object} [options.specialUseHints] set specific paths as special use folders, this would override special use flags provided from the server
|
|
1456
|
+
* @param {String} [options.specialUseHints.sent] Path to "Sent Mail" folder
|
|
1457
|
+
* @param {String} [options.specialUseHints.trash] Path to "Trash" folder
|
|
1458
|
+
* @param {String} [options.specialUseHints.junk] Path to "Junk Mail" folder
|
|
1459
|
+
* @param {String} [options.specialUseHints.drafts] Path to "Drafts" folder
|
|
1455
1460
|
* @returns {Promise<ListResponse[]>} An array of ListResponse objects
|
|
1456
1461
|
*
|
|
1457
1462
|
* @example
|
package/lib/special-use.js
CHANGED
|
@@ -269,11 +269,23 @@ module.exports = {
|
|
|
269
269
|
},
|
|
270
270
|
|
|
271
271
|
specialUse(hasSpecialUseExtension, folder) {
|
|
272
|
+
let result;
|
|
273
|
+
|
|
272
274
|
if (hasSpecialUseExtension) {
|
|
273
|
-
|
|
275
|
+
result = {
|
|
276
|
+
flag: module.exports.flags.find(flag => folder.flags.has(flag)),
|
|
277
|
+
source: 'extension'
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (!result || !result.flag) {
|
|
282
|
+
let name = folder.name.toLowerCase().trim();
|
|
283
|
+
result = {
|
|
284
|
+
flag: Object.keys(module.exports.names).find(flag => module.exports.names[flag].includes(name)),
|
|
285
|
+
source: 'name'
|
|
286
|
+
};
|
|
274
287
|
}
|
|
275
288
|
|
|
276
|
-
|
|
277
|
-
return Object.keys(module.exports.names).find(flag => module.exports.names[flag].includes(name));
|
|
289
|
+
return result && result.flag ? result : { flag: null };
|
|
278
290
|
}
|
|
279
291
|
};
|
package/lib/types.d.ts
CHANGED
|
@@ -167,6 +167,11 @@ declare module "imapflow" {
|
|
|
167
167
|
* @param [options.statusQuery.uidValidity] - if `true` request mailbox `UIDVALIDITY` value
|
|
168
168
|
* @param [options.statusQuery.unseen] - if `true` request count of unseen messages
|
|
169
169
|
* @param [options.statusQuery.highestModseq] - if `true` request last known modseq value
|
|
170
|
+
* @param [options.specialUseHints] - set specific paths as special use folders, this would override special use flags provided from the server
|
|
171
|
+
* @param [options.specialUseHints.sent] - Path to "Sent Mail" folder
|
|
172
|
+
* @param [options.specialUseHints.trash] - Path to "Trash" folder
|
|
173
|
+
* @param [options.specialUseHints.junk] - Path to "Junk Mail" folder
|
|
174
|
+
* @param [options.specialUseHints.drafts] - Path to "Drafts" folder
|
|
170
175
|
* @returns An array of ListResponse objects
|
|
171
176
|
*/
|
|
172
177
|
list(options?: {
|
|
@@ -178,6 +183,12 @@ declare module "imapflow" {
|
|
|
178
183
|
unseen?: boolean;
|
|
179
184
|
highestModseq?: boolean;
|
|
180
185
|
};
|
|
186
|
+
specialUseHints?: {
|
|
187
|
+
sent?: string;
|
|
188
|
+
trash?: string;
|
|
189
|
+
junk?: string;
|
|
190
|
+
drafts?: string;
|
|
191
|
+
};
|
|
181
192
|
}): Promise<ListResponse[]>;
|
|
182
193
|
/**
|
|
183
194
|
* Lists available mailboxes as a tree structured object
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imapflow",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.119",
|
|
4
4
|
"description": "IMAP Client for Node",
|
|
5
5
|
"main": "./lib/imap-flow.js",
|
|
6
6
|
"scripts": {
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"@babel/eslint-plugin": "7.19.1",
|
|
32
32
|
"@babel/plugin-syntax-class-properties": "7.12.13",
|
|
33
33
|
"@babel/preset-env": "7.20.2",
|
|
34
|
-
"@types/node": "18.11.
|
|
34
|
+
"@types/node": "18.11.17",
|
|
35
35
|
"braintree-jsdoc-template": "3.3.0",
|
|
36
|
-
"eslint": "8.
|
|
36
|
+
"eslint": "8.30.0",
|
|
37
37
|
"eslint-config-nodemailer": "1.2.0",
|
|
38
38
|
"eslint-config-prettier": "8.5.0",
|
|
39
39
|
"grunt": "1.5.3",
|
|
@@ -48,11 +48,11 @@
|
|
|
48
48
|
"encoding-japanese": "2.0.0",
|
|
49
49
|
"iconv-lite": "0.6.3",
|
|
50
50
|
"libbase64": "1.2.1",
|
|
51
|
-
"libmime": "5.
|
|
51
|
+
"libmime": "5.2.0",
|
|
52
52
|
"libqp": "2.0.1",
|
|
53
|
-
"mailsplit": "5.
|
|
53
|
+
"mailsplit": "5.4.0",
|
|
54
54
|
"nodemailer": "6.8.0",
|
|
55
|
-
"pino": "8.
|
|
55
|
+
"pino": "8.8.0",
|
|
56
56
|
"socks": "2.7.1"
|
|
57
57
|
}
|
|
58
58
|
}
|