@bobfrankston/iflow-direct 0.1.23 → 0.1.25
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/imap-compat.d.ts +1 -1
- package/imap-compat.js +55 -37
- package/imap-native.js +5 -5
- package/package.json +1 -1
- package/tasks.json +20 -0
package/imap-compat.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export declare class CompatImapClient {
|
|
|
36
36
|
logout(): Promise<void>;
|
|
37
37
|
/** Get folder list */
|
|
38
38
|
getFolderList(): Promise<NativeFolder[]>;
|
|
39
|
-
/**
|
|
39
|
+
/** Extract special folders from folder list */
|
|
40
40
|
getSpecialFolders(folders: NativeFolder[]): SpecialFolders;
|
|
41
41
|
/** Fetch messages since a UID in a mailbox */
|
|
42
42
|
fetchMessagesSinceUid(mailbox: string, sinceUid: number, options?: {
|
package/imap-compat.js
CHANGED
|
@@ -5,6 +5,16 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { NativeImapClient } from "./imap-native.js";
|
|
7
7
|
import { FetchedMessage } from "./fetched-message.js";
|
|
8
|
+
// We may need some special cases in the future
|
|
9
|
+
const defaultFolders = {
|
|
10
|
+
inbox: "INBOX", // Uppercase -- we'll need a reality check
|
|
11
|
+
sent: "Sent",
|
|
12
|
+
trash: "Trash",
|
|
13
|
+
drafts: "Drafts",
|
|
14
|
+
spam: "_Spam",
|
|
15
|
+
junk: "_Spam",
|
|
16
|
+
archive: "Archive"
|
|
17
|
+
};
|
|
8
18
|
/**
|
|
9
19
|
* Compatibility IMAP client — wraps NativeImapClient with the old ImapClient API.
|
|
10
20
|
* Each method creates its own select/operation/close cycle.
|
|
@@ -39,55 +49,63 @@ export class CompatImapClient {
|
|
|
39
49
|
await this.ensureConnected();
|
|
40
50
|
return this.native.listFolders();
|
|
41
51
|
}
|
|
42
|
-
/**
|
|
52
|
+
/** Extract special folders from folder list */
|
|
43
53
|
getSpecialFolders(folders) {
|
|
44
54
|
// Two-pass detection so RFC 6154 \Drafts / \Sent / \Trash / \Junk /
|
|
45
55
|
// \Archive flags always win over name-based guesses. Previous single-pass
|
|
46
56
|
// `else if (flag || name)` could tag a stray folder named "DRAFT" as the
|
|
47
57
|
// drafts folder on Gmail, beating out [Imap]/Drafts which has the real
|
|
48
58
|
// \Drafts flag — Gmail then rejects APPEND to "DRAFT" as NONEXISTENT.
|
|
49
|
-
|
|
59
|
+
let result = {};
|
|
50
60
|
const leafName = (f) => {
|
|
51
61
|
const delim = f.delimiter || ".";
|
|
52
|
-
return (f.path.split(delim).
|
|
62
|
+
return (f.path.split(delim).at(-1) ?? f.path).toLowerCase();
|
|
53
63
|
};
|
|
54
|
-
const
|
|
55
|
-
//
|
|
64
|
+
const flagSet = (f) => new Set(f.flags.map(fl => fl.toLowerCase()));
|
|
65
|
+
// const df = 'y:\\temp\\folders.csv';
|
|
66
|
+
// fs.writeFileSync(df, "path,flags\n" + folders.map(f => `${f.path},"${f.flags.join(";")}"`).join("\n"));
|
|
67
|
+
// console.log(`Wrote folder list to ${df}`);
|
|
68
|
+
// RFC 6154 flags only — authoritative if present
|
|
69
|
+
// Note that Dovecot does not have them but we check INBOX for the case
|
|
56
70
|
for (const f of folders) {
|
|
57
|
-
const flags =
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (
|
|
63
|
-
result.
|
|
64
|
-
if (
|
|
65
|
-
result.
|
|
66
|
-
if (
|
|
67
|
-
result.
|
|
68
|
-
if (
|
|
71
|
+
const flags = flagSet(f);
|
|
72
|
+
// Debubing
|
|
73
|
+
const dbgignore = ['\\haschildren', '\\hasnochildren', '\\noselect', '\\unmarked'];
|
|
74
|
+
const dbgfgall = Array.from(flags);
|
|
75
|
+
const dbgfg = dbgfgall.filter(fl => !dbgignore.includes(fl));
|
|
76
|
+
if (flags.has("\\inbox") || leafName(f) === "inbox")
|
|
77
|
+
result.inbox ??= f.path;
|
|
78
|
+
if (flags.has("\\sent"))
|
|
79
|
+
result.sent ??= f.path;
|
|
80
|
+
if (flags.has("\\trash"))
|
|
81
|
+
result.trash ??= f.path;
|
|
82
|
+
if (flags.has("\\drafts"))
|
|
83
|
+
result.drafts ??= f.path;
|
|
84
|
+
if (flags.has("\\archive"))
|
|
85
|
+
result.archive ??= f.path;
|
|
86
|
+
if (flags.has("\\junk"))
|
|
69
87
|
result.junk = f.path;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
// Pass 2: exact leaf-name fallback for servers that don't advertise flags
|
|
75
|
-
for (const f of folders) {
|
|
76
|
-
const name = leafName(f);
|
|
77
|
-
if (!result.sent && (name === "sent" || name === "sent items" || name === "sent mail"))
|
|
78
|
-
result.sent = f.path;
|
|
79
|
-
if (!result.trash && (name === "trash" || name === "deleted items" || name === "deleted"))
|
|
80
|
-
result.trash = f.path;
|
|
81
|
-
if (!result.drafts && (name === "drafts" || name === "draft"))
|
|
82
|
-
result.drafts = f.path;
|
|
83
|
-
if (!result.archive && (name === "archive" || name === "archives"))
|
|
84
|
-
result.archive = f.path;
|
|
85
|
-
if (!result.junk && (name === "spam" || name === "junk" || name === "junk email" || name === "junk e-mail")) {
|
|
86
|
-
result.junk = f.path;
|
|
87
|
-
if (!result.spam)
|
|
88
|
-
result.spam = f.path;
|
|
89
|
-
}
|
|
88
|
+
if (flags.has("\\spam"))
|
|
89
|
+
result.spam ??= f.path;
|
|
90
90
|
}
|
|
91
|
+
result = { ...defaultFolders, ...result }; // Fill in any missing defaults
|
|
92
|
+
// // Pass 2: exact leaf-name fallback for servers that don't advertise flags
|
|
93
|
+
// const sentNames = new Set(["sent", "sent items", "sent mail"]);
|
|
94
|
+
// const trashNames = new Set(["trash", "deleted items", "deleted"]);
|
|
95
|
+
// const draftsNames = new Set(["drafts", "draft"]);
|
|
96
|
+
// const archiveNames = new Set(["archive", "archives"]);
|
|
97
|
+
// const junkNames = new Set(["spam", "junk", "junk email", "junk e-mail"]);
|
|
98
|
+
// for (const f of folders) {
|
|
99
|
+
// const name = leafName(f);
|
|
100
|
+
// if (sentNames.has(name)) result.sent ??= f.path;
|
|
101
|
+
// if (trashNames.has(name)) result.trash ??= f.path;
|
|
102
|
+
// if (draftsNames.has(name)) result.drafts ??= f.path;
|
|
103
|
+
// if (archiveNames.has(name)) result.archive ??= f.path;
|
|
104
|
+
// if (junkNames.has(name)) {
|
|
105
|
+
// result.junk ??= f.path;
|
|
106
|
+
// result.spam ??= f.path;
|
|
107
|
+
// }
|
|
108
|
+
// }
|
|
91
109
|
return result;
|
|
92
110
|
}
|
|
93
111
|
/** Fetch messages since a UID in a mailbox */
|
package/imap-native.js
CHANGED
|
@@ -386,7 +386,7 @@ export class NativeImapClient {
|
|
|
386
386
|
const chunk = uids.slice(i, i + chunkSize);
|
|
387
387
|
const msgs = await this.fetchMessages(chunk.join(","), options);
|
|
388
388
|
allMessages.push(...msgs);
|
|
389
|
-
console.log(` [fetch] ${allMessages.length}/${uids.length} (chunk of ${chunk.length})`);
|
|
389
|
+
// console.log(` [fetch] ${allMessages.length}/${uids.length} (chunk of ${chunk.length})`);
|
|
390
390
|
if (onChunk)
|
|
391
391
|
onChunk(msgs);
|
|
392
392
|
if (chunkSize < this.fetchChunkSizeMax)
|
|
@@ -405,7 +405,7 @@ export class NativeImapClient {
|
|
|
405
405
|
return [];
|
|
406
406
|
// Reverse so newest messages (highest UIDs) come first
|
|
407
407
|
uids.reverse();
|
|
408
|
-
console.log(` [fetch] ${uids.length} UIDs to fetch (newest first)`);
|
|
408
|
+
// console.log(` [fetch] ${uids.length} UIDs to fetch (newest first)`);
|
|
409
409
|
const allMessages = [];
|
|
410
410
|
let chunkSize = this.fetchChunkSize;
|
|
411
411
|
for (let i = 0; i < uids.length; i += chunkSize) {
|
|
@@ -788,9 +788,9 @@ export class NativeImapClient {
|
|
|
788
788
|
const rest_bytes = encoded.subarray(neededBytes);
|
|
789
789
|
let literal = new TextDecoder().decode(literal_bytes);
|
|
790
790
|
this.buffer = new TextDecoder().decode(rest_bytes);
|
|
791
|
-
if (neededBytes !== literal.length) {
|
|
792
|
-
|
|
793
|
-
}
|
|
791
|
+
// if (neededBytes !== literal.length) {
|
|
792
|
+
// console.log(`iflow-direct: [imap] literal: ${neededBytes} bytes → ${literal.length} chars (multi-byte corrected)`);
|
|
793
|
+
// }
|
|
794
794
|
// For non-BODY literals (e.g. display names in ENVELOPE), wrap in quotes
|
|
795
795
|
// so tokenizeParenList treats them as a single token
|
|
796
796
|
if (!this.pendingCommand.currentLiteralKey) {
|
package/package.json
CHANGED
package/tasks.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "2.0.0",
|
|
3
|
+
"tasks": [
|
|
4
|
+
{
|
|
5
|
+
"label": "tsc: watch",
|
|
6
|
+
"type": "shell",
|
|
7
|
+
"command": "tsc",
|
|
8
|
+
"args": ["--watch"],
|
|
9
|
+
"runOptions": {
|
|
10
|
+
"runOn": "folderOpen"
|
|
11
|
+
},
|
|
12
|
+
"problemMatcher": "$tsc-watch",
|
|
13
|
+
"isBackground": true,
|
|
14
|
+
"group": {
|
|
15
|
+
"kind": "build",
|
|
16
|
+
"isDefault": true
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
}
|