@bobfrankston/iflow 1.0.2
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/.claude/settings.local.json +10 -0
- package/imaplib/ImapClient.d.ts +35 -0
- package/imaplib/ImapClient.d.ts.map +1 -0
- package/imaplib/ImapClient.js +312 -0
- package/imaplib/ImapClient.js.map +1 -0
- package/imaplib/ImapClient.ts +376 -0
- package/imaplib/types.d.ts +57 -0
- package/imaplib/types.d.ts.map +1 -0
- package/imaplib/types.js +63 -0
- package/imaplib/types.js.map +1 -0
- package/imaplib/types.ts +105 -0
- package/package.json +44 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import imapflow from 'imapflow';
|
|
2
|
+
import { ImapClientConfig, FetchedMessage } from './types.js';
|
|
3
|
+
export interface iFetchOptions {
|
|
4
|
+
source: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare class ImapClient {
|
|
7
|
+
private client;
|
|
8
|
+
private config;
|
|
9
|
+
private isDestroying;
|
|
10
|
+
private connectionPromise;
|
|
11
|
+
constructor(cfg: ImapClientConfig);
|
|
12
|
+
private init;
|
|
13
|
+
private handleReconnection;
|
|
14
|
+
private cleanup;
|
|
15
|
+
private withConnection;
|
|
16
|
+
getMessagesCount(mailbox: string): Promise<number>;
|
|
17
|
+
fetchMessageByDate(mailbox: string, start: Date | number, endingx?: Date | number, options?: iFetchOptions): Promise<FetchedMessage[]>;
|
|
18
|
+
fetchMessages(mailbox: string, end: number, count: number, options?: iFetchOptions): Promise<FetchedMessage[]>;
|
|
19
|
+
moveMessage(msg: FetchedMessage, sourceMailbox: string, targetMailbox: string): Promise<void>;
|
|
20
|
+
getFolderList(): Promise<imapflow.ListResponse[]>;
|
|
21
|
+
getFolderTree(): Promise<imapflow.ListTreeResponse>;
|
|
22
|
+
getSpecialFolders(mailboxes: Awaited<ReturnType<ImapClient["getFolderList"]>>): {
|
|
23
|
+
inbox: string;
|
|
24
|
+
trash: string;
|
|
25
|
+
spam: string;
|
|
26
|
+
sent: string;
|
|
27
|
+
drafts: string;
|
|
28
|
+
archive: string;
|
|
29
|
+
junk: string;
|
|
30
|
+
};
|
|
31
|
+
createmailbox(folder: string[] | string): Promise<imapflow.MailboxCreateResponse>;
|
|
32
|
+
logout(): Promise<void>;
|
|
33
|
+
connect(): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=ImapClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImapClient.d.ts","sourceRoot":"","sources":["ImapClient.ts"],"names":[],"mappings":"AAGA,OAAO,QAAoE,MAAM,UAAU,CAAC;AAC5F,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAc,MAAM,YAAY,CAAC;AAO1E,MAAM,WAAW,aAAa;IAC1B,MAAM,EAAE,OAAO,CAAC;CACnB;AAMD,qBAAa,UAAU;IACnB,OAAO,CAAC,MAAM,CAAW;IACzB,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,YAAY,CAAkB;IACtC,OAAO,CAAC,iBAAiB,CAA8B;gBAE3C,GAAG,EAAE,gBAAgB;IAKjC,OAAO,CAAC,IAAI;YA4BE,kBAAkB;IAgChC,OAAO,CAAC,OAAO;YAQD,cAAc;IAmCtB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAOlD,kBAAkB,CACpB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,IAAI,GAAG,MAAM,EACpB,OAAO,CAAC,EAAE,IAAI,GAAG,MAAM,EACvB,OAAO,CAAC,EAAE,aAAa;IAmCrB,aAAa,CACf,OAAO,EAAE,MAAM,EAEf,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,aAAa;IAsCrB,WAAW,CAAC,GAAG,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM;IAwB7E,aAAa;IAMb,aAAa;IAMnB,iBAAiB,CAAC,SAAS,EAAE,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC;eAIlD,MAAM;eACN,MAAM;cACP,MAAM;cACN,MAAM;gBACJ,MAAM;iBACL,MAAM;cACT,MAAM;;IAgD1B,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM;IAQvC,MAAM;IAWN,OAAO;CAwChB"}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
// copilot: disable all suggestions related to testing frameworks like jest, mocha, etc.
|
|
2
|
+
// copilot: do not include the return type for functions unless necessary
|
|
3
|
+
import { ImapFlow } from 'imapflow';
|
|
4
|
+
import { FetchedMessage } from './types.js';
|
|
5
|
+
import { styleText } from 'util';
|
|
6
|
+
const defaultFetchOptions = {
|
|
7
|
+
source: true
|
|
8
|
+
};
|
|
9
|
+
export class ImapClient {
|
|
10
|
+
client;
|
|
11
|
+
config;
|
|
12
|
+
isDestroying = false;
|
|
13
|
+
connectionPromise = null;
|
|
14
|
+
constructor(cfg) {
|
|
15
|
+
this.config = cfg;
|
|
16
|
+
this.init();
|
|
17
|
+
}
|
|
18
|
+
init() {
|
|
19
|
+
this.client = new ImapFlow({
|
|
20
|
+
host: this.config.server,
|
|
21
|
+
port: this.config.port,
|
|
22
|
+
secure: true,
|
|
23
|
+
auth: {
|
|
24
|
+
user: this.config.username,
|
|
25
|
+
pass: this.config.password
|
|
26
|
+
},
|
|
27
|
+
logger: false, // Disable internal logging
|
|
28
|
+
});
|
|
29
|
+
// Set up error handlers
|
|
30
|
+
this.client.on('error', (err) => {
|
|
31
|
+
console.error(`ImapFlow socket error: ${err.message}`);
|
|
32
|
+
if (!this.isDestroying) {
|
|
33
|
+
this.handleReconnection();
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
this.client.on('close', () => {
|
|
37
|
+
console.error(`ImapFlow connection closed`);
|
|
38
|
+
if (!this.isDestroying) {
|
|
39
|
+
this.handleReconnection();
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async handleReconnection() {
|
|
44
|
+
if (this.connectionPromise)
|
|
45
|
+
return; // Already reconnecting
|
|
46
|
+
this.connectionPromise = new Promise(async (resolve) => {
|
|
47
|
+
try {
|
|
48
|
+
// Clean up old client
|
|
49
|
+
this.cleanup();
|
|
50
|
+
// Wait before reconnecting
|
|
51
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
52
|
+
// Reinitialize
|
|
53
|
+
this.init();
|
|
54
|
+
try {
|
|
55
|
+
await this.client.connect();
|
|
56
|
+
console.log('Reconnected successfully');
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
console.error('Reconnection failed, will retry:', err);
|
|
60
|
+
// Will trigger another reconnection attempt via error handler
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
console.error('Error during reconnection process:', err);
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
this.connectionPromise = null;
|
|
68
|
+
resolve();
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
return this.connectionPromise;
|
|
72
|
+
}
|
|
73
|
+
cleanup() {
|
|
74
|
+
if (this.client) {
|
|
75
|
+
this.client.removeAllListeners('error');
|
|
76
|
+
this.client.removeAllListeners('close');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Wrapper to ensure all operations handle connection errors
|
|
80
|
+
async withConnection(operation) {
|
|
81
|
+
try {
|
|
82
|
+
await this.connect();
|
|
83
|
+
return await operation();
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
console.error(`Operation failed: ${err.message}`);
|
|
87
|
+
// Check if it's a connection-related error
|
|
88
|
+
if (err.code === 'ECONNRESET' || err.code === 'ENOTFOUND' ||
|
|
89
|
+
err.code === 'ETIMEDOUT' || err.code === 'ECONNREFUSED' ||
|
|
90
|
+
err.message.includes('connection') || err.message.includes('socket')) {
|
|
91
|
+
if (!this.isDestroying) {
|
|
92
|
+
console.log('Connection error detected, attempting reconnection...');
|
|
93
|
+
await this.handleReconnection();
|
|
94
|
+
// Retry the operation once after reconnection
|
|
95
|
+
try {
|
|
96
|
+
await this.connect();
|
|
97
|
+
return await operation();
|
|
98
|
+
}
|
|
99
|
+
catch (retryErr) {
|
|
100
|
+
console.error(`Operation failed after reconnection: ${retryErr}`);
|
|
101
|
+
throw retryErr;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// https://imapflow.com/module-imapflow-ImapFlow.html
|
|
109
|
+
// https://imapflow.com/module-imapflow-ImapFlow.html#search
|
|
110
|
+
async getMessagesCount(mailbox) {
|
|
111
|
+
return this.withConnection(async () => {
|
|
112
|
+
const status = await this.client.status(mailbox, { messages: true });
|
|
113
|
+
return status.messages;
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
async fetchMessageByDate(mailbox, start, endingx, options) {
|
|
117
|
+
try {
|
|
118
|
+
options = { ...defaultFetchOptions, ...options }; // Merge with default options
|
|
119
|
+
await this.connect(); // Ensure connected
|
|
120
|
+
const startTime = Date.now();
|
|
121
|
+
const peek = true; // always peek for now
|
|
122
|
+
await this.client.mailboxOpen(mailbox, { readOnly: peek });
|
|
123
|
+
if (typeof start === 'number')
|
|
124
|
+
start = new Date(start);
|
|
125
|
+
if (endingx && typeof endingx === 'number')
|
|
126
|
+
endingx = new Date(endingx);
|
|
127
|
+
const ending = endingx; // Hack for typescript typing
|
|
128
|
+
const searchCriteria = endingx ? { since: start, before: ending } : { on: start };
|
|
129
|
+
const fetchQuery = {
|
|
130
|
+
uid: true,
|
|
131
|
+
envelope: true,
|
|
132
|
+
flags: true,
|
|
133
|
+
headers: true,
|
|
134
|
+
source: options.source
|
|
135
|
+
};
|
|
136
|
+
const messageObjects = await this.client.fetchAll(searchCriteria, fetchQuery);
|
|
137
|
+
// const messageObjects = await this.client.fetchAll(uids, fetchQuery);
|
|
138
|
+
const messages = messageObjects.map(msg => new FetchedMessage(msg));
|
|
139
|
+
// if (this.config.verbose) console.log(`Fetched ${messages.length} messages in ${Date.now() - startTime}ms`);
|
|
140
|
+
return messages;
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
console.error(`Error fetching messages by date`, error);
|
|
144
|
+
debugger;
|
|
145
|
+
throw error; // rethrow to handle it outside
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
await this.client.mailboxClose();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async fetchMessages(mailbox,
|
|
152
|
+
// start: number,
|
|
153
|
+
end, count, options) {
|
|
154
|
+
try {
|
|
155
|
+
options = { ...defaultFetchOptions, ...options }; // Merge with default options
|
|
156
|
+
await this.connect(); // Ensure connected
|
|
157
|
+
const peek = true; // always peek for now
|
|
158
|
+
await this.client.mailboxOpen(mailbox, { readOnly: peek });
|
|
159
|
+
let start = end - count + 1;
|
|
160
|
+
const range = `${start}:${end}`;
|
|
161
|
+
const startTime = Date.now();
|
|
162
|
+
const fetchQuery = {
|
|
163
|
+
uid: true,
|
|
164
|
+
envelope: true,
|
|
165
|
+
flags: true,
|
|
166
|
+
// bodyStructure: true,
|
|
167
|
+
headers: true,
|
|
168
|
+
source: options.source // Fetch the raw RFC822 message
|
|
169
|
+
};
|
|
170
|
+
const messageObjects = await this.client.fetchAll(range, fetchQuery);
|
|
171
|
+
// Testing
|
|
172
|
+
const messages = messageObjects.map(msg => new FetchedMessage(msg));
|
|
173
|
+
// console.log(`Fetched ${messages.length} messages in ${Date.now() - startTime}ms`);
|
|
174
|
+
return messages;
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
console.error(`Error fetching messages: ${error.message}`);
|
|
178
|
+
debugger;
|
|
179
|
+
throw error; // rethrow to handle it outside
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
await this.client.mailboxClose();
|
|
183
|
+
// await this.client.logout();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// async moveMessage(uid: number, sourceMailbox: string, targetMailbox: string) {
|
|
187
|
+
async moveMessage(msg, sourceMailbox, targetMailbox) {
|
|
188
|
+
try {
|
|
189
|
+
await this.connect(); // Ensure connected
|
|
190
|
+
await this.client.mailboxOpen(sourceMailbox, { readOnly: false }); // Open the source mailbox in read-write mode
|
|
191
|
+
const result = await this.client.messageMove([msg.uid], targetMailbox, { uid: true });
|
|
192
|
+
if (result && !result.uidMap) {
|
|
193
|
+
console.log(styleText(['red', 'bgBlack'], `iflow/iMapClient: NO uidMap: Failed to move message UID ${msg.uid} from ${sourceMailbox} to ${targetMailbox}`));
|
|
194
|
+
return; // Treat as nonfatal
|
|
195
|
+
}
|
|
196
|
+
if (!result || !result.uidMap.has(msg.uid)) {
|
|
197
|
+
debugger;
|
|
198
|
+
throw new Error(`Message with UID ${msg.uid} was not moved.`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
const ermsg = `iflow/iMapClient: Error moving message UID ${msg.uid}: ${error.message}`;
|
|
203
|
+
console.log(styleText(['red', 'bgBlack'], ermsg));
|
|
204
|
+
debugger;
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
await this.client.mailboxClose();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async getFolderList() {
|
|
212
|
+
return this.withConnection(async () => {
|
|
213
|
+
return await this.client.list();
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
async getFolderTree() {
|
|
217
|
+
return this.withConnection(async () => {
|
|
218
|
+
return await this.client.listTree();
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
getSpecialFolders(mailboxes) {
|
|
222
|
+
try {
|
|
223
|
+
// await this.client.connect();
|
|
224
|
+
const specialFolders = {
|
|
225
|
+
inbox: null,
|
|
226
|
+
trash: null,
|
|
227
|
+
spam: null,
|
|
228
|
+
sent: null,
|
|
229
|
+
drafts: null,
|
|
230
|
+
archive: null,
|
|
231
|
+
junk: null,
|
|
232
|
+
};
|
|
233
|
+
for (const box of mailboxes) {
|
|
234
|
+
// normalize flags/specialUse
|
|
235
|
+
const rawFlags = Array.isArray(box.flags)
|
|
236
|
+
? box.flags
|
|
237
|
+
: box.flags
|
|
238
|
+
? [box.flags]
|
|
239
|
+
: [];
|
|
240
|
+
const rawSpecial = Array.isArray(box.specialUse)
|
|
241
|
+
? box.specialUse
|
|
242
|
+
: box.specialUse
|
|
243
|
+
? [box.specialUse]
|
|
244
|
+
: [];
|
|
245
|
+
const attrs = [...rawFlags, ...rawSpecial];
|
|
246
|
+
const name = box.path;
|
|
247
|
+
if (attrs.includes('\\Inbox') || name.toLowerCase() === 'inbox')
|
|
248
|
+
specialFolders.inbox = name;
|
|
249
|
+
if (attrs.includes('\\Trash'))
|
|
250
|
+
specialFolders.trash = name;
|
|
251
|
+
if (attrs.includes('\\Junk') || attrs.includes('\\Spam'))
|
|
252
|
+
specialFolders.spam = name;
|
|
253
|
+
if (attrs.includes('\\Sent'))
|
|
254
|
+
specialFolders.sent = name;
|
|
255
|
+
if (attrs.includes('\\Drafts'))
|
|
256
|
+
specialFolders.drafts = name;
|
|
257
|
+
if (attrs.includes('\\Archive'))
|
|
258
|
+
specialFolders.archive = name;
|
|
259
|
+
if (attrs.includes('\\Junk'))
|
|
260
|
+
specialFolders.junk = name;
|
|
261
|
+
// TODO: Figure out why trash isn't trash
|
|
262
|
+
// specialFolders.trash = "TRASH"; // for dovcoat
|
|
263
|
+
7; // Place for breakpoints
|
|
264
|
+
}
|
|
265
|
+
return specialFolders;
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
console.error(`Error getting special folders: ${error.message}`);
|
|
269
|
+
debugger;
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
finally {
|
|
273
|
+
// await this.client.logout();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
async createmailbox(folder) {
|
|
277
|
+
return this.withConnection(async () => {
|
|
278
|
+
let info = await this.client.mailboxCreate(folder);
|
|
279
|
+
console.log(`Created mailbox: ${info.path}`);
|
|
280
|
+
return info;
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
async logout() {
|
|
284
|
+
this.isDestroying = true;
|
|
285
|
+
this.cleanup();
|
|
286
|
+
try {
|
|
287
|
+
await this.client.logout();
|
|
288
|
+
}
|
|
289
|
+
catch (err) {
|
|
290
|
+
// Ignore logout errors - connection might already be closed
|
|
291
|
+
console.log(`Logout error (ignored): ${err.message}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
async connect() {
|
|
295
|
+
try {
|
|
296
|
+
if (this.connectionPromise) {
|
|
297
|
+
await this.connectionPromise;
|
|
298
|
+
}
|
|
299
|
+
if (!this.client.usable) {
|
|
300
|
+
await this.client.connect();
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
catch (err) {
|
|
304
|
+
console.error(`Connection failed: ${err.message}`);
|
|
305
|
+
if (!this.isDestroying) {
|
|
306
|
+
await this.handleReconnection();
|
|
307
|
+
}
|
|
308
|
+
throw err;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
//# sourceMappingURL=ImapClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ImapClient.js","sourceRoot":"","sources":["ImapClient.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,yEAAyE;AAEzE,OAAiB,EAAoB,QAAQ,EAA8B,MAAM,UAAU,CAAC;AAC5F,OAAO,EAAoB,cAAc,EAAc,MAAM,YAAY,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAUjC,MAAM,mBAAmB,GAAkB;IACvC,MAAM,EAAE,IAAI;CACf,CAAA;AAED,MAAM,OAAO,UAAU;IACX,MAAM,CAAW;IACjB,MAAM,CAAmB;IACzB,YAAY,GAAY,KAAK,CAAC;IAC9B,iBAAiB,GAAyB,IAAI,CAAC;IAEvD,YAAY,GAAqB;QAC7B,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC;QAClB,IAAI,CAAC,IAAI,EAAE,CAAC;IAChB,CAAC;IAEO,IAAI;QACR,IAAI,CAAC,MAAM,GAAG,IAAI,QAAQ,CAAC;YACvB,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM;YACxB,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI;YACtB,MAAM,EAAE,IAAI;YACZ,IAAI,EAAE;gBACF,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;gBAC1B,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;aAC7B;YACD,MAAM,EAAE,KAAK,EAAE,2BAA2B;SAC7C,CAAC,CAAC;QAEH,wBAAwB;QACxB,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAQ,EAAE,EAAE;YACjC,OAAO,CAAC,KAAK,CAAC,0BAA0B,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YACvD,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;gBACrB,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC9B,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACzB,OAAO,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;YAC5C,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;gBACrB,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC9B,CAAC;QACL,CAAC,CAAC,CAAC;IACP,CAAC;IAEO,KAAK,CAAC,kBAAkB;QAC5B,IAAI,IAAI,CAAC,iBAAiB;YAAE,OAAO,CAAC,uBAAuB;QAE3D,IAAI,CAAC,iBAAiB,GAAG,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;YACnD,IAAI,CAAC;gBACD,sBAAsB;gBACtB,IAAI,CAAC,OAAO,EAAE,CAAC;gBAEf,2BAA2B;gBAC3B,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;gBAE5C,eAAe;gBACf,IAAI,CAAC,IAAI,EAAE,CAAC;gBAEZ,IAAI,CAAC;oBACD,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;oBAC5B,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC;gBAC5C,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACX,OAAO,CAAC,KAAK,CAAC,kCAAkC,EAAE,GAAG,CAAC,CAAC;oBACvD,8DAA8D;gBAClE,CAAC;YACL,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACX,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,GAAG,CAAC,CAAC;YAC7D,CAAC;oBAAS,CAAC;gBACP,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC;gBAC9B,OAAO,EAAE,CAAC;YACd,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,OAAO,IAAI,CAAC,iBAAiB,CAAC;IAClC,CAAC;IAEO,OAAO;QACX,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACd,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;YACxC,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;QAC5C,CAAC;IACL,CAAC;IAED,4DAA4D;IACpD,KAAK,CAAC,cAAc,CAAI,SAA2B;QACvD,IAAI,CAAC;YACD,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YACrB,OAAO,MAAM,SAAS,EAAE,CAAC;QAC7B,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CAAC,qBAAqB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YAElD,2CAA2C;YAC3C,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,IAAI,GAAG,CAAC,IAAI,KAAK,WAAW;gBACrD,GAAG,CAAC,IAAI,KAAK,WAAW,IAAI,GAAG,CAAC,IAAI,KAAK,cAAc;gBACvD,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAEvE,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;oBACrB,OAAO,CAAC,GAAG,CAAC,uDAAuD,CAAC,CAAC;oBACrE,MAAM,IAAI,CAAC,kBAAkB,EAAE,CAAC;oBAEhC,8CAA8C;oBAC9C,IAAI,CAAC;wBACD,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;wBACrB,OAAO,MAAM,SAAS,EAAE,CAAC;oBAC7B,CAAC;oBAAC,OAAO,QAAQ,EAAE,CAAC;wBAChB,OAAO,CAAC,KAAK,CAAC,wCAAwC,QAAQ,EAAE,CAAC,CAAC;wBAClE,MAAM,QAAQ,CAAC;oBACnB,CAAC;gBACL,CAAC;YACL,CAAC;YAED,MAAM,GAAG,CAAC;QACd,CAAC;IACL,CAAC;IAED,qDAAqD;IAErD,4DAA4D;IAE5D,KAAK,CAAC,gBAAgB,CAAC,OAAe;QAClC,OAAO,IAAI,CAAC,cAAc,CAAC,KAAK,IAAI,EAAE;YAClC,MAAM,MAAM,GAAiB,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;YACnF,OAAO,MAAM,CAAC,QAAQ,CAAC;QAC3B,CAAC,CAAC,CAAC;IACP,CAAC;IAED,KAAK,CAAC,kBAAkB,CACpB,OAAe,EACf,KAAoB,EACpB,OAAuB,EACvB,OAAuB;QACvB,IAAI,CAAC;YACD,OAAO,GAAG,EAAC,GAAG,mBAAmB,EAAE,GAAG,OAAO,EAAC,CAAC,CAAC,6BAA6B;YAC7E,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,mBAAmB;YACzC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAE7B,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,sBAAsB;YACzC,MAAM,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;YAE3D,IAAI,OAAO,KAAK,KAAK,QAAQ;gBAAE,KAAK,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;YACvD,IAAI,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ;gBAAE,OAAO,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;YACxE,MAAM,MAAM,GAAG,OAAe,CAAC,CAAC,6BAA6B;YAC7D,MAAM,cAAc,GAAiB,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAA;YAE/F,MAAM,UAAU,GAAqB;gBACjC,GAAG,EAAE,IAAI;gBACT,QAAQ,EAAE,IAAI;gBACd,KAAK,EAAE,IAAI;gBACX,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,OAAO,CAAC,MAAM;aACzB,CAAC;YACF,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,EAAE,UAAU,CAAC,CAAC;YAC9E,uEAAuE;YACvE,MAAM,QAAQ,GAAG,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC;YACpE,8GAA8G;YAC9G,OAAO,QAAQ,CAAC;QACpB,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YAClB,OAAO,CAAC,KAAK,CAAC,iCAAiC,EAAE,KAAK,CAAC,CAAC;YACxD,QAAQ,CAAC;YACT,MAAM,KAAK,CAAC,CAAC,+BAA+B;QAChD,CAAC;gBAAS,CAAC;YACP,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;QACrC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,aAAa,CACf,OAAe;IACf,iBAAiB;IACjB,GAAW,EACX,KAAa,EACb,OAAuB;QAEvB,IAAI,CAAC;YACD,OAAO,GAAG,EAAC,GAAG,mBAAmB,EAAE,GAAG,OAAO,EAAC,CAAC,CAAC,6BAA6B;YAC7E,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,mBAAmB;YACzC,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,sBAAsB;YACzC,MAAM,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;YAC3D,IAAI,KAAK,GAAG,GAAG,GAAG,KAAK,GAAG,CAAC,CAAC;YAC5B,MAAM,KAAK,GAAG,GAAG,KAAK,IAAI,GAAG,EAAE,CAAC;YAChC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAE7B,MAAM,UAAU,GAAe;gBAC3B,GAAG,EAAE,IAAI;gBACT,QAAQ,EAAE,IAAI;gBACd,KAAK,EAAE,IAAI;gBACX,uBAAuB;gBACvB,OAAO,EAAE,IAAI;gBACb,MAAM,EAAE,OAAO,CAAC,MAAM,CAAE,+BAA+B;aAC1D,CAAC;YAEF,MAAM,cAAc,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;YACrE,UAAU;YACV,MAAM,QAAQ,GAAG,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC;YACpE,qFAAqF;YACrF,OAAO,QAAQ,CAAC;QACpB,CAAC;QACD,OAAO,KAAU,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CAAC,4BAA4B,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YAC3D,QAAQ,CAAC;YACT,MAAM,KAAK,CAAC,CAAC,+BAA+B;QAChD,CAAC;gBACO,CAAC;YACL,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;YACjC,8BAA8B;QAClC,CAAC;IACL,CAAC;IAED,iFAAiF;IACjF,KAAK,CAAC,WAAW,CAAC,GAAmB,EAAE,aAAqB,EAAE,aAAqB;QAC/E,IAAI,CAAC;YACD,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,mBAAmB;YACzC,MAAM,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,aAAa,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,CAAY,6CAA6C;YAC3H,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,aAAa,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;YACtF,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;gBAC3B,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,SAAS,CAAC,EACpC,2DAA2D,GAAG,CAAC,GAAG,SAAS,aAAa,OAAO,aAAa,EAAE,CAAC,CAAC,CAAC;gBACrH,OAAO,CAAK,oBAAoB;YACpC,CAAC;YACD,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACzC,QAAQ,CAAC;gBACT,MAAM,IAAI,KAAK,CAAC,oBAAoB,GAAG,CAAC,GAAG,iBAAiB,CAAC,CAAC;YAClE,CAAC;QACL,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YAClB,MAAM,KAAK,GAAG,8CAA8C,GAAG,CAAC,GAAG,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;YACxF,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;YAClD,QAAQ,CAAC;YACT,MAAM,KAAK,CAAC;QAChB,CAAC;gBAAS,CAAC;YACP,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;QACrC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,aAAa;QACf,OAAO,IAAI,CAAC,cAAc,CAAC,KAAK,IAAI,EAAE;YAClC,OAAO,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACpC,CAAC,CAAC,CAAC;IACP,CAAC;IAED,KAAK,CAAC,aAAa;QACf,OAAO,IAAI,CAAC,cAAc,CAAC,KAAK,IAAI,EAAE;YAClC,OAAO,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;QACxC,CAAC,CAAC,CAAC;IACP,CAAC;IAED,iBAAiB,CAAC,SAA2D;QACzE,IAAI,CAAC;YACD,+BAA+B;YAC/B,MAAM,cAAc,GAAG;gBACnB,KAAK,EAAE,IAAc;gBACrB,KAAK,EAAE,IAAc;gBACrB,IAAI,EAAE,IAAc;gBACpB,IAAI,EAAE,IAAc;gBACpB,MAAM,EAAE,IAAc;gBACtB,OAAO,EAAE,IAAc;gBACvB,IAAI,EAAE,IAAc;aACvB,CAAC;YAEF,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;gBAC1B,6BAA6B;gBAC7B,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;oBACrC,CAAC,CAAC,GAAG,CAAC,KAAK;oBACX,CAAC,CAAC,GAAG,CAAC,KAAK;wBACP,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC;wBACb,CAAC,CAAC,EAAE,CAAC;gBACb,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;oBAC5C,CAAC,CAAC,GAAG,CAAC,UAAU;oBAChB,CAAC,CAAC,GAAG,CAAC,UAAU;wBACZ,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC;wBAClB,CAAC,CAAC,EAAE,CAAC;gBACb,MAAM,KAAK,GAAG,CAAC,GAAG,QAAQ,EAAE,GAAG,UAAU,CAAC,CAAC;gBAC3C,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;gBAEtB,IAAI,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,KAAK,OAAO;oBAC3D,cAAc,CAAC,KAAK,GAAG,IAAI,CAAC;gBAChC,IAAI,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC;oBACzB,cAAc,CAAC,KAAK,GAAG,IAAI,CAAC;gBAChC,IAAI,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC;oBACpD,cAAc,CAAC,IAAI,GAAG,IAAI,CAAC;gBAC/B,IAAI,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC;oBACxB,cAAc,CAAC,IAAI,GAAG,IAAI,CAAC;gBAC/B,IAAI,KAAK,CAAC,QAAQ,CAAC,UAAU,CAAC;oBAC1B,cAAc,CAAC,MAAM,GAAG,IAAI,CAAC;gBACjC,IAAI,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC;oBAC3B,cAAc,CAAC,OAAO,GAAG,IAAI,CAAC;gBAClC,IAAI,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC;oBACxB,cAAc,CAAC,IAAI,GAAG,IAAI,CAAC;gBAE/B,yCAAyC;gBACzC,iDAAiD;gBACjD,CAAC,CAAC,CAAC,wBAAwB;YAC/B,CAAC;YAED,OAAO,cAAc,CAAC;QAC1B,CAAC;QAAC,OAAO,KAAU,EAAE,CAAC;YAClB,OAAO,CAAC,KAAK,CAAC,kCAAkC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;YACjE,QAAQ,CAAC;YACT,MAAM,KAAK,CAAC;QAChB,CAAC;gBAAS,CAAC;YACP,8BAA8B;QAClC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,MAAyB;QACzC,OAAO,IAAI,CAAC,cAAc,CAAC,KAAK,IAAI,EAAE;YAClC,IAAI,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;YACnD,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;YAC7C,OAAO,IAAI,CAAC;QAChB,CAAC,CAAC,CAAC;IACP,CAAC;IAED,KAAK,CAAC,MAAM;QACR,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,IAAI,CAAC;YACD,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QAC/B,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAChB,4DAA4D;YAC5D,OAAO,CAAC,GAAG,CAAC,2BAA2B,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAC1D,CAAC;IACL,CAAC;IAED,KAAK,CAAC,OAAO;QACT,IAAI,CAAC;YACD,IAAI,IAAI,CAAC,iBAAiB,EAAE,CAAC;gBACzB,MAAM,IAAI,CAAC,iBAAiB,CAAC;YACjC,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;gBACtB,MAAM,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YAChC,CAAC;QACL,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CAAC,sBAAsB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YACnD,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;gBACrB,MAAM,IAAI,CAAC,kBAAkB,EAAE,CAAC;YACpC,CAAC;YACD,MAAM,GAAG,CAAC;QACd,CAAC;IACL,CAAC;CAyBJ"}
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
// copilot: disable all suggestions related to testing frameworks like jest, mocha, etc.
|
|
2
|
+
// copilot: do not include the return type for functions unless necessary
|
|
3
|
+
|
|
4
|
+
import imapflow, { FetchQueryObject, ImapFlow, SearchObject, StatusObject } from 'imapflow';
|
|
5
|
+
import { ImapClientConfig, FetchedMessage, FetchQuery } from './types.js';
|
|
6
|
+
import { styleText } from 'util';
|
|
7
|
+
// Removed dependencies on parent project - these should be passed in or handled differently
|
|
8
|
+
|
|
9
|
+
// TODO: Create our own range object and allow a nosource flag
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
export interface iFetchOptions {
|
|
13
|
+
source: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const defaultFetchOptions: iFetchOptions = {
|
|
17
|
+
source: true
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class ImapClient {
|
|
21
|
+
private client: ImapFlow;
|
|
22
|
+
private config: ImapClientConfig;
|
|
23
|
+
private isDestroying: boolean = false;
|
|
24
|
+
private connectionPromise: Promise<void> | null = null;
|
|
25
|
+
|
|
26
|
+
constructor(cfg: ImapClientConfig) {
|
|
27
|
+
this.config = cfg;
|
|
28
|
+
this.init();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private init() {
|
|
32
|
+
this.client = new ImapFlow({
|
|
33
|
+
host: this.config.server,
|
|
34
|
+
port: this.config.port,
|
|
35
|
+
secure: true,
|
|
36
|
+
auth: {
|
|
37
|
+
user: this.config.username,
|
|
38
|
+
pass: this.config.password
|
|
39
|
+
},
|
|
40
|
+
logger: false, // Disable internal logging
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Set up error handlers
|
|
44
|
+
this.client.on('error', (err: any) => {
|
|
45
|
+
console.error(`ImapFlow socket error: ${err.message}`);
|
|
46
|
+
if (!this.isDestroying) {
|
|
47
|
+
this.handleReconnection();
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
this.client.on('close', () => {
|
|
52
|
+
console.error(`ImapFlow connection closed`);
|
|
53
|
+
if (!this.isDestroying) {
|
|
54
|
+
this.handleReconnection();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private async handleReconnection() {
|
|
60
|
+
if (this.connectionPromise) return; // Already reconnecting
|
|
61
|
+
|
|
62
|
+
this.connectionPromise = new Promise(async (resolve) => {
|
|
63
|
+
try {
|
|
64
|
+
// Clean up old client
|
|
65
|
+
this.cleanup();
|
|
66
|
+
|
|
67
|
+
// Wait before reconnecting
|
|
68
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
69
|
+
|
|
70
|
+
// Reinitialize
|
|
71
|
+
this.init();
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
await this.client.connect();
|
|
75
|
+
console.log('Reconnected successfully');
|
|
76
|
+
} catch (err) {
|
|
77
|
+
console.error('Reconnection failed, will retry:', err);
|
|
78
|
+
// Will trigger another reconnection attempt via error handler
|
|
79
|
+
}
|
|
80
|
+
} catch (err) {
|
|
81
|
+
console.error('Error during reconnection process:', err);
|
|
82
|
+
} finally {
|
|
83
|
+
this.connectionPromise = null;
|
|
84
|
+
resolve();
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return this.connectionPromise;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private cleanup() {
|
|
92
|
+
if (this.client) {
|
|
93
|
+
this.client.removeAllListeners('error');
|
|
94
|
+
this.client.removeAllListeners('close');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Wrapper to ensure all operations handle connection errors
|
|
99
|
+
private async withConnection<T>(operation: () => Promise<T>): Promise<T> {
|
|
100
|
+
try {
|
|
101
|
+
await this.connect();
|
|
102
|
+
return await operation();
|
|
103
|
+
} catch (err: any) {
|
|
104
|
+
console.error(`Operation failed: ${err.message}`);
|
|
105
|
+
|
|
106
|
+
// Check if it's a connection-related error
|
|
107
|
+
if (err.code === 'ECONNRESET' || err.code === 'ENOTFOUND' ||
|
|
108
|
+
err.code === 'ETIMEDOUT' || err.code === 'ECONNREFUSED' ||
|
|
109
|
+
err.message.includes('connection') || err.message.includes('socket')) {
|
|
110
|
+
|
|
111
|
+
if (!this.isDestroying) {
|
|
112
|
+
console.log('Connection error detected, attempting reconnection...');
|
|
113
|
+
await this.handleReconnection();
|
|
114
|
+
|
|
115
|
+
// Retry the operation once after reconnection
|
|
116
|
+
try {
|
|
117
|
+
await this.connect();
|
|
118
|
+
return await operation();
|
|
119
|
+
} catch (retryErr) {
|
|
120
|
+
console.error(`Operation failed after reconnection: ${retryErr}`);
|
|
121
|
+
throw retryErr;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
throw err;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// https://imapflow.com/module-imapflow-ImapFlow.html
|
|
131
|
+
|
|
132
|
+
// https://imapflow.com/module-imapflow-ImapFlow.html#search
|
|
133
|
+
|
|
134
|
+
async getMessagesCount(mailbox: string): Promise<number> {
|
|
135
|
+
return this.withConnection(async () => {
|
|
136
|
+
const status: StatusObject = await this.client.status(mailbox, { messages: true });
|
|
137
|
+
return status.messages;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async fetchMessageByDate(
|
|
142
|
+
mailbox: string,
|
|
143
|
+
start: Date | number,
|
|
144
|
+
endingx?: Date | number,
|
|
145
|
+
options?: iFetchOptions) {
|
|
146
|
+
try {
|
|
147
|
+
options = {...defaultFetchOptions, ...options}; // Merge with default options
|
|
148
|
+
await this.connect(); // Ensure connected
|
|
149
|
+
const startTime = Date.now();
|
|
150
|
+
|
|
151
|
+
const peek = true; // always peek for now
|
|
152
|
+
await this.client.mailboxOpen(mailbox, { readOnly: peek });
|
|
153
|
+
|
|
154
|
+
if (typeof start === 'number') start = new Date(start);
|
|
155
|
+
if (endingx && typeof endingx === 'number') endingx = new Date(endingx);
|
|
156
|
+
const ending = endingx as Date; // Hack for typescript typing
|
|
157
|
+
const searchCriteria: SearchObject = endingx ? { since: start, before: ending } : { on: start }
|
|
158
|
+
|
|
159
|
+
const fetchQuery: FetchQueryObject = {
|
|
160
|
+
uid: true,
|
|
161
|
+
envelope: true,
|
|
162
|
+
flags: true,
|
|
163
|
+
headers: true,
|
|
164
|
+
source: options.source
|
|
165
|
+
};
|
|
166
|
+
const messageObjects = await this.client.fetchAll(searchCriteria, fetchQuery);
|
|
167
|
+
// const messageObjects = await this.client.fetchAll(uids, fetchQuery);
|
|
168
|
+
const messages = messageObjects.map(msg => new FetchedMessage(msg));
|
|
169
|
+
// if (this.config.verbose) console.log(`Fetched ${messages.length} messages in ${Date.now() - startTime}ms`);
|
|
170
|
+
return messages;
|
|
171
|
+
} catch (error: any) {
|
|
172
|
+
console.error(`Error fetching messages by date`, error);
|
|
173
|
+
debugger;
|
|
174
|
+
throw error; // rethrow to handle it outside
|
|
175
|
+
} finally {
|
|
176
|
+
await this.client.mailboxClose();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async fetchMessages(
|
|
181
|
+
mailbox: string,
|
|
182
|
+
// start: number,
|
|
183
|
+
end: number,
|
|
184
|
+
count: number,
|
|
185
|
+
options?: iFetchOptions
|
|
186
|
+
) {
|
|
187
|
+
try {
|
|
188
|
+
options = {...defaultFetchOptions, ...options}; // Merge with default options
|
|
189
|
+
await this.connect(); // Ensure connected
|
|
190
|
+
const peek = true; // always peek for now
|
|
191
|
+
await this.client.mailboxOpen(mailbox, { readOnly: peek });
|
|
192
|
+
let start = end - count + 1;
|
|
193
|
+
const range = `${start}:${end}`;
|
|
194
|
+
const startTime = Date.now();
|
|
195
|
+
|
|
196
|
+
const fetchQuery: FetchQuery = {
|
|
197
|
+
uid: true,
|
|
198
|
+
envelope: true,
|
|
199
|
+
flags: true,
|
|
200
|
+
// bodyStructure: true,
|
|
201
|
+
headers: true,
|
|
202
|
+
source: options.source // Fetch the raw RFC822 message
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const messageObjects = await this.client.fetchAll(range, fetchQuery);
|
|
206
|
+
// Testing
|
|
207
|
+
const messages = messageObjects.map(msg => new FetchedMessage(msg));
|
|
208
|
+
// console.log(`Fetched ${messages.length} messages in ${Date.now() - startTime}ms`);
|
|
209
|
+
return messages;
|
|
210
|
+
}
|
|
211
|
+
catch (error: any) {
|
|
212
|
+
console.error(`Error fetching messages: ${error.message}`);
|
|
213
|
+
debugger;
|
|
214
|
+
throw error; // rethrow to handle it outside
|
|
215
|
+
}
|
|
216
|
+
finally {
|
|
217
|
+
await this.client.mailboxClose();
|
|
218
|
+
// await this.client.logout();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// async moveMessage(uid: number, sourceMailbox: string, targetMailbox: string) {
|
|
223
|
+
async moveMessage(msg: FetchedMessage, sourceMailbox: string, targetMailbox: string) {
|
|
224
|
+
try {
|
|
225
|
+
await this.connect(); // Ensure connected
|
|
226
|
+
await this.client.mailboxOpen(sourceMailbox, { readOnly: false }); // Open the source mailbox in read-write mode
|
|
227
|
+
const result = await this.client.messageMove([msg.uid], targetMailbox, { uid: true });
|
|
228
|
+
if (result && !result.uidMap) {
|
|
229
|
+
console.log(styleText(['red', 'bgBlack'],
|
|
230
|
+
`iflow/iMapClient: NO uidMap: Failed to move message UID ${msg.uid} from ${sourceMailbox} to ${targetMailbox}`));
|
|
231
|
+
return; // Treat as nonfatal
|
|
232
|
+
}
|
|
233
|
+
if (!result || !result.uidMap.has(msg.uid)) {
|
|
234
|
+
debugger;
|
|
235
|
+
throw new Error(`Message with UID ${msg.uid} was not moved.`);
|
|
236
|
+
}
|
|
237
|
+
} catch (error: any) {
|
|
238
|
+
const ermsg = `iflow/iMapClient: Error moving message UID ${msg.uid}: ${error.message}`;
|
|
239
|
+
console.log(styleText(['red', 'bgBlack'], ermsg));
|
|
240
|
+
debugger;
|
|
241
|
+
throw error;
|
|
242
|
+
} finally {
|
|
243
|
+
await this.client.mailboxClose();
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async getFolderList() {
|
|
248
|
+
return this.withConnection(async () => {
|
|
249
|
+
return await this.client.list();
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async getFolderTree() {
|
|
254
|
+
return this.withConnection(async () => {
|
|
255
|
+
return await this.client.listTree();
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
getSpecialFolders(mailboxes: Awaited<ReturnType<ImapClient["getFolderList"]>>) {
|
|
260
|
+
try {
|
|
261
|
+
// await this.client.connect();
|
|
262
|
+
const specialFolders = {
|
|
263
|
+
inbox: null as string,
|
|
264
|
+
trash: null as string,
|
|
265
|
+
spam: null as string,
|
|
266
|
+
sent: null as string,
|
|
267
|
+
drafts: null as string,
|
|
268
|
+
archive: null as string,
|
|
269
|
+
junk: null as string,
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
for (const box of mailboxes) {
|
|
273
|
+
// normalize flags/specialUse
|
|
274
|
+
const rawFlags = Array.isArray(box.flags)
|
|
275
|
+
? box.flags
|
|
276
|
+
: box.flags
|
|
277
|
+
? [box.flags]
|
|
278
|
+
: [];
|
|
279
|
+
const rawSpecial = Array.isArray(box.specialUse)
|
|
280
|
+
? box.specialUse
|
|
281
|
+
: box.specialUse
|
|
282
|
+
? [box.specialUse]
|
|
283
|
+
: [];
|
|
284
|
+
const attrs = [...rawFlags, ...rawSpecial];
|
|
285
|
+
const name = box.path;
|
|
286
|
+
|
|
287
|
+
if (attrs.includes('\\Inbox') || name.toLowerCase() === 'inbox')
|
|
288
|
+
specialFolders.inbox = name;
|
|
289
|
+
if (attrs.includes('\\Trash'))
|
|
290
|
+
specialFolders.trash = name;
|
|
291
|
+
if (attrs.includes('\\Junk') || attrs.includes('\\Spam'))
|
|
292
|
+
specialFolders.spam = name;
|
|
293
|
+
if (attrs.includes('\\Sent'))
|
|
294
|
+
specialFolders.sent = name;
|
|
295
|
+
if (attrs.includes('\\Drafts'))
|
|
296
|
+
specialFolders.drafts = name;
|
|
297
|
+
if (attrs.includes('\\Archive'))
|
|
298
|
+
specialFolders.archive = name;
|
|
299
|
+
if (attrs.includes('\\Junk'))
|
|
300
|
+
specialFolders.junk = name;
|
|
301
|
+
|
|
302
|
+
// TODO: Figure out why trash isn't trash
|
|
303
|
+
// specialFolders.trash = "TRASH"; // for dovcoat
|
|
304
|
+
7; // Place for breakpoints
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return specialFolders;
|
|
308
|
+
} catch (error: any) {
|
|
309
|
+
console.error(`Error getting special folders: ${error.message}`);
|
|
310
|
+
debugger;
|
|
311
|
+
throw error;
|
|
312
|
+
} finally {
|
|
313
|
+
// await this.client.logout();
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async createmailbox(folder: string[] | string) {
|
|
318
|
+
return this.withConnection(async () => {
|
|
319
|
+
let info = await this.client.mailboxCreate(folder);
|
|
320
|
+
console.log(`Created mailbox: ${info.path}`);
|
|
321
|
+
return info;
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async logout() {
|
|
326
|
+
this.isDestroying = true;
|
|
327
|
+
this.cleanup();
|
|
328
|
+
try {
|
|
329
|
+
await this.client.logout();
|
|
330
|
+
} catch (err: any) {
|
|
331
|
+
// Ignore logout errors - connection might already be closed
|
|
332
|
+
console.log(`Logout error (ignored): ${err.message}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async connect() {
|
|
337
|
+
try {
|
|
338
|
+
if (this.connectionPromise) {
|
|
339
|
+
await this.connectionPromise;
|
|
340
|
+
}
|
|
341
|
+
if (!this.client.usable) {
|
|
342
|
+
await this.client.connect();
|
|
343
|
+
}
|
|
344
|
+
} catch (err: any) {
|
|
345
|
+
console.error(`Connection failed: ${err.message}`);
|
|
346
|
+
if (!this.isDestroying) {
|
|
347
|
+
await this.handleReconnection();
|
|
348
|
+
}
|
|
349
|
+
throw err;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Getter to access underlying ImapFlow client for advanced operations
|
|
354
|
+
// get imapFlowClient() {
|
|
355
|
+
// if (!this.client) {
|
|
356
|
+
// this.init();
|
|
357
|
+
// }
|
|
358
|
+
// return this.client;
|
|
359
|
+
// }
|
|
360
|
+
|
|
361
|
+
// static async run<T>(config: ImapClientConfig, f: () => Promise<T>) {
|
|
362
|
+
// const imap = new ImapClient(config);
|
|
363
|
+
|
|
364
|
+
// try {
|
|
365
|
+
// await imap.connect();
|
|
366
|
+
// return f();
|
|
367
|
+
// }
|
|
368
|
+
// catch (error: any) {
|
|
369
|
+
// logerr(`Error in ImapClient run`, error);
|
|
370
|
+
// debugger;
|
|
371
|
+
// throw error;
|
|
372
|
+
// } finally {
|
|
373
|
+
// await imap.logout();
|
|
374
|
+
// }
|
|
375
|
+
// }
|
|
376
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import imapflow from 'imapflow';
|
|
2
|
+
declare class SimpleMap<T> {
|
|
3
|
+
private items;
|
|
4
|
+
add(key: string, value: T): void;
|
|
5
|
+
get(key: string): T[] | undefined;
|
|
6
|
+
}
|
|
7
|
+
export type MessageEnvelope = imapflow.MessageEnvelopeObject;
|
|
8
|
+
export interface ImapClientConfig {
|
|
9
|
+
server: string;
|
|
10
|
+
port: number;
|
|
11
|
+
username: string;
|
|
12
|
+
password: string;
|
|
13
|
+
verbose?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface FetchQuery extends imapflow.FetchQueryObject {
|
|
16
|
+
}
|
|
17
|
+
export type FetchPart = keyof imapflow.FetchQueryObject;
|
|
18
|
+
export declare class FetchedMessage {
|
|
19
|
+
private fmo;
|
|
20
|
+
parsedHeader: string[][];
|
|
21
|
+
headerSet: SimpleMap<string>;
|
|
22
|
+
_source: string;
|
|
23
|
+
constructor(init: imapflow.FetchMessageObject);
|
|
24
|
+
get flagged(): boolean;
|
|
25
|
+
get answered(): boolean;
|
|
26
|
+
get deleted(): boolean;
|
|
27
|
+
get seen(): boolean;
|
|
28
|
+
get unread(): boolean;
|
|
29
|
+
get draft(): boolean;
|
|
30
|
+
get hasFlag(): boolean;
|
|
31
|
+
get to(): imapflow.MessageAddressObject[];
|
|
32
|
+
get cc(): imapflow.MessageAddressObject[];
|
|
33
|
+
get bcc(): imapflow.MessageAddressObject[];
|
|
34
|
+
get from(): imapflow.MessageAddressObject[];
|
|
35
|
+
get replyTo(): imapflow.MessageAddressObject[];
|
|
36
|
+
get sender(): imapflow.MessageAddressObject[];
|
|
37
|
+
get date(): Date;
|
|
38
|
+
get ymd(): string;
|
|
39
|
+
get tcsb(): Record<string, string[]>;
|
|
40
|
+
get seq(): number;
|
|
41
|
+
get uid(): number;
|
|
42
|
+
get modseq(): bigint;
|
|
43
|
+
private get envelope();
|
|
44
|
+
private get flags();
|
|
45
|
+
get internalDate(): string | Date;
|
|
46
|
+
get size(): number;
|
|
47
|
+
get bodyStructure(): imapflow.MessageStructureObject;
|
|
48
|
+
get source(): string;
|
|
49
|
+
get messageId(): string;
|
|
50
|
+
get listUnsubscribe(): string;
|
|
51
|
+
get xpriority(): string;
|
|
52
|
+
get importance(): string;
|
|
53
|
+
get deliveredTo(): string;
|
|
54
|
+
get subject(): string;
|
|
55
|
+
}
|
|
56
|
+
export {};
|
|
57
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,UAAU,CAAC;AAGhC,cAAM,SAAS,CAAC,CAAC;IACb,OAAO,CAAC,KAAK,CAA2B;IAExC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAMzB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,EAAE,GAAG,SAAS;CAGpC;AAED,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC,qBAAqB,CAAC;AAE7D,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAgBD,MAAM,WAAW,UAAW,SAAQ,QAAQ,CAAC,gBAAgB;CAC5D;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,gBAAgB,CAAC;AAExD,qBAAa,cAAc;IACvB,OAAO,CAAC,GAAG,CAA8B;IACzC,YAAY,EAAE,MAAM,EAAE,EAAE,CAAC;IACzB,SAAS,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;IAC7B,OAAO,EAAE,MAAM,CAAC;gBAEJ,IAAI,EAAE,QAAQ,CAAC,kBAAkB;IAa7C,IAAI,OAAO,YAA8C;IACzD,IAAI,QAAQ,YAA+C;IAC3D,IAAI,OAAO,YAA8C;IACzD,IAAI,IAAI,YAA2C;IACnD,IAAI,MAAM,YAAyB;IACnC,IAAI,KAAK,YAA4C;IACrD,IAAI,OAAO,YAAsC;IAEjD,IAAI,EAAE,oCAA+B;IACrC,IAAI,EAAE,oCAA+B;IACrC,IAAI,GAAG,oCAAgC;IACvC,IAAI,IAAI,oCAAiC;IACzC,IAAI,OAAO,oCAAoC;IAC/C,IAAI,MAAM,oCAAmC;IAC7C,IAAI,IAAI,SAAiC;IACzC,IAAI,GAAG,WAAkD;IACzD,IAAI,IAAI,IAA4B,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAI;IAEhE,IAAI,GAAG,WAA2B;IAClC,IAAI,GAAG,WAA2B;IAClC,IAAI,MAAM,WAA8B;IACxC,OAAO,KAAK,QAAQ,GAAgC;IACpD,OAAO,KAAK,KAAK,GAA6B;IAC9C,IAAI,YAAY,kBAAoC;IACpD,IAAI,IAAI,WAA4B;IACpC,IAAI,aAAa,oCAAqC;IACtD,IAAI,MAAM,WAA2B;IAErC,IAAI,SAAS,WAAsC;IACnD,IAAI,eAAe,WAA0D;IAC7E,IAAI,SAAS,WAAoD;IACjE,IAAI,UAAU,WAAoD;IAElE,IAAI,WAAW,WAAsD;IACrE,IAAI,OAAO,WAAoC;CAKlD"}
|
package/imaplib/types.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Simple map implementation to replace maplca dependency
|
|
2
|
+
class SimpleMap {
|
|
3
|
+
items = {};
|
|
4
|
+
add(key, value) {
|
|
5
|
+
const k = key.toLowerCase();
|
|
6
|
+
if (!this.items[k])
|
|
7
|
+
this.items[k] = [];
|
|
8
|
+
this.items[k].push(value);
|
|
9
|
+
}
|
|
10
|
+
get(key) {
|
|
11
|
+
return this.items[key.toLowerCase()];
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export class FetchedMessage {
|
|
15
|
+
fmo;
|
|
16
|
+
parsedHeader;
|
|
17
|
+
headerSet;
|
|
18
|
+
_source;
|
|
19
|
+
constructor(init) {
|
|
20
|
+
this.fmo = init;
|
|
21
|
+
this._source = init.source?.toString('utf8');
|
|
22
|
+
this.parsedHeader = this.fmo.headers.toString('utf8')
|
|
23
|
+
.replace(/\r\n(\s)/g, '$1')
|
|
24
|
+
.split('\r\n')
|
|
25
|
+
.filter(line => line.trim() !== '')
|
|
26
|
+
.map(line => line.split(': ', 2));
|
|
27
|
+
this.headerSet = new SimpleMap();
|
|
28
|
+
this.parsedHeader.forEach(([k, v]) => this.headerSet.add(k, v));
|
|
29
|
+
delete this.fmo.headers;
|
|
30
|
+
}
|
|
31
|
+
get flagged() { return this.fmo.flags.has('\\Flagged'); }
|
|
32
|
+
get answered() { return this.fmo.flags.has('\\Answered'); }
|
|
33
|
+
get deleted() { return this.fmo.flags.has('\\Deleted'); }
|
|
34
|
+
get seen() { return this.fmo.flags.has('\\Seen'); }
|
|
35
|
+
get unread() { return !this.seen; }
|
|
36
|
+
get draft() { return this.fmo.flags.has('\\Draft'); }
|
|
37
|
+
get hasFlag() { return this.fmo.flags.size > 0; }
|
|
38
|
+
get to() { return this.envelope.to; }
|
|
39
|
+
get cc() { return this.envelope.cc; }
|
|
40
|
+
get bcc() { return this.envelope.bcc; }
|
|
41
|
+
get from() { return this.envelope.from; }
|
|
42
|
+
get replyTo() { return this.envelope.replyTo; }
|
|
43
|
+
get sender() { return this.envelope.sender; }
|
|
44
|
+
get date() { return this.envelope.date; }
|
|
45
|
+
get ymd() { return this.date?.toLocaleDateString('se'); }
|
|
46
|
+
get tcsb() { return this; } // tcsb = to, cc, bcc
|
|
47
|
+
get seq() { return this.fmo.seq; }
|
|
48
|
+
get uid() { return this.fmo.uid; }
|
|
49
|
+
get modseq() { return this.fmo.modseq; }
|
|
50
|
+
get envelope() { return this.fmo.envelope; }
|
|
51
|
+
get flags() { return this.fmo.flags; }
|
|
52
|
+
get internalDate() { return this.fmo.internalDate; }
|
|
53
|
+
get size() { return this.fmo.size; }
|
|
54
|
+
get bodyStructure() { return this.fmo.bodyStructure; }
|
|
55
|
+
get source() { return this._source; }
|
|
56
|
+
get messageId() { return this.envelope.messageId; }
|
|
57
|
+
get listUnsubscribe() { return this.headerSet.get('List-Unsubscribe')?.[0]; }
|
|
58
|
+
get xpriority() { return this.headerSet.get('X-Priority')?.[0]; }
|
|
59
|
+
get importance() { return this.headerSet.get('Importance')?.[0]; }
|
|
60
|
+
get deliveredTo() { return this.headerSet.get('Delivered-To')?.[0]; }
|
|
61
|
+
get subject() { return this.envelope.subject; }
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"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"}
|
package/imaplib/types.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import imapflow from 'imapflow';
|
|
2
|
+
|
|
3
|
+
// Simple map implementation to replace maplca dependency
|
|
4
|
+
class SimpleMap<T> {
|
|
5
|
+
private items: Record<string, T[]> = {};
|
|
6
|
+
|
|
7
|
+
add(key: string, value: T) {
|
|
8
|
+
const k = key.toLowerCase();
|
|
9
|
+
if (!this.items[k]) this.items[k] = [];
|
|
10
|
+
this.items[k].push(value);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
get(key: string): T[] | undefined {
|
|
14
|
+
return this.items[key.toLowerCase()];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type MessageEnvelope = imapflow.MessageEnvelopeObject;
|
|
19
|
+
|
|
20
|
+
export interface ImapClientConfig {
|
|
21
|
+
server: string;
|
|
22
|
+
port: number;
|
|
23
|
+
username: string;
|
|
24
|
+
password: string;
|
|
25
|
+
verbose?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// export interface ImapClientConfig {
|
|
29
|
+
// server: string;
|
|
30
|
+
// port: number;
|
|
31
|
+
// username: string;
|
|
32
|
+
// password?: string; // Make optional for OAuth
|
|
33
|
+
// oauth?: {
|
|
34
|
+
// accessToken: string;
|
|
35
|
+
// refreshToken: string;
|
|
36
|
+
// clientId: string;
|
|
37
|
+
// clientSecret: string;
|
|
38
|
+
// };
|
|
39
|
+
// verbose?: boolean;
|
|
40
|
+
// }
|
|
41
|
+
|
|
42
|
+
export interface FetchQuery extends imapflow.FetchQueryObject {
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type FetchPart = keyof imapflow.FetchQueryObject;
|
|
46
|
+
|
|
47
|
+
export class FetchedMessage {
|
|
48
|
+
private fmo: imapflow.FetchMessageObject;
|
|
49
|
+
parsedHeader: string[][];
|
|
50
|
+
headerSet: SimpleMap<string>;
|
|
51
|
+
_source: string;
|
|
52
|
+
|
|
53
|
+
constructor(init: imapflow.FetchMessageObject) {
|
|
54
|
+
this.fmo = init;
|
|
55
|
+
this._source = init.source?.toString('utf8');
|
|
56
|
+
this.parsedHeader = this.fmo.headers.toString('utf8')
|
|
57
|
+
.replace(/\r\n(\s)/g, '$1')
|
|
58
|
+
.split('\r\n')
|
|
59
|
+
.filter(line => line.trim() !== '')
|
|
60
|
+
.map(line => line.split(': ', 2));
|
|
61
|
+
this.headerSet = new SimpleMap<string>();
|
|
62
|
+
this.parsedHeader.forEach(([k, v]) => this.headerSet.add(k, v));
|
|
63
|
+
delete this.fmo.headers;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get flagged() { return this.fmo.flags.has('\\Flagged'); }
|
|
67
|
+
get answered() { return this.fmo.flags.has('\\Answered'); }
|
|
68
|
+
get deleted() { return this.fmo.flags.has('\\Deleted'); }
|
|
69
|
+
get seen() { return this.fmo.flags.has('\\Seen'); }
|
|
70
|
+
get unread() { return !this.seen; }
|
|
71
|
+
get draft() { return this.fmo.flags.has('\\Draft'); }
|
|
72
|
+
get hasFlag() { return this.fmo.flags.size > 0; }
|
|
73
|
+
|
|
74
|
+
get to() { return this.envelope.to; }
|
|
75
|
+
get cc() { return this.envelope.cc; }
|
|
76
|
+
get bcc() { return this.envelope.bcc; }
|
|
77
|
+
get from() { return this.envelope.from; }
|
|
78
|
+
get replyTo() { return this.envelope.replyTo; }
|
|
79
|
+
get sender() { return this.envelope.sender; }
|
|
80
|
+
get date() { return this.envelope.date; }
|
|
81
|
+
get ymd() { return this.date?.toLocaleDateString('se'); }
|
|
82
|
+
get tcsb() { return (this as any as Record<string, string[]>); } // tcsb = to, cc, bcc
|
|
83
|
+
|
|
84
|
+
get seq() { return this.fmo.seq; }
|
|
85
|
+
get uid() { return this.fmo.uid; }
|
|
86
|
+
get modseq() { return this.fmo.modseq; }
|
|
87
|
+
private get envelope() { return this.fmo.envelope; }
|
|
88
|
+
private get flags() { return this.fmo.flags; }
|
|
89
|
+
get internalDate() { return this.fmo.internalDate; }
|
|
90
|
+
get size() { return this.fmo.size; }
|
|
91
|
+
get bodyStructure() { return this.fmo.bodyStructure; }
|
|
92
|
+
get source() { return this._source; }
|
|
93
|
+
|
|
94
|
+
get messageId() { return this.envelope.messageId; }
|
|
95
|
+
get listUnsubscribe() { return this.headerSet.get('List-Unsubscribe')?.[0]; }
|
|
96
|
+
get xpriority() { return this.headerSet.get('X-Priority')?.[0]; }
|
|
97
|
+
get importance() { return this.headerSet.get('Importance')?.[0]; }
|
|
98
|
+
|
|
99
|
+
get deliveredTo() { return this.headerSet.get('Delivered-To')?.[0]; }
|
|
100
|
+
get subject() { return this.envelope.subject; }
|
|
101
|
+
|
|
102
|
+
// if you still need raw headers or bodyParts you can add:
|
|
103
|
+
// get rawHeaders() { return this.fmo.headers; }
|
|
104
|
+
// get bodyParts() { return this.fmo.bodyParts; }
|
|
105
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bobfrankston/iflow",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "IMAP client wrapper library",
|
|
5
|
+
"main": "imaplib/ImapClient.js",
|
|
6
|
+
"types": "imaplib/ImapClient.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"watch": "tsc -watch"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"imap",
|
|
14
|
+
"email",
|
|
15
|
+
"client"
|
|
16
|
+
],
|
|
17
|
+
"author": "",
|
|
18
|
+
"license": "ISC",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@types/imapflow": "^1.0.22",
|
|
21
|
+
"imapflow": "^1.0.187"
|
|
22
|
+
},
|
|
23
|
+
"exports": {
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./imaplib/types.ts",
|
|
26
|
+
"default": "./imaplib/types.js"
|
|
27
|
+
},
|
|
28
|
+
"./client": {
|
|
29
|
+
"types": "./imaplib/ImapClient.ts",
|
|
30
|
+
"default": "./imaplib/ImapClient.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^24.1.0"
|
|
35
|
+
},
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "git+https://github.com/BobFrankston/iflow.git"
|
|
39
|
+
},
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/BobFrankston/iflow/issues"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://github.com/BobFrankston/iflow#readme"
|
|
44
|
+
}
|