@procedures/mail 0.0.0-stage → 0.2.3
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/LICENSE +21 -0
- package/README.md +34 -2
- package/dist/.tsbuildinfo +1 -0
- package/dist/imap.d.ts +72 -0
- package/dist/imap.d.ts.map +1 -0
- package/dist/imap.js +73 -0
- package/dist/imap.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/mime.d.ts +21 -0
- package/dist/mime.d.ts.map +1 -0
- package/dist/mime.js +139 -0
- package/dist/mime.js.map +1 -0
- package/dist/module.d.ts +12 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +15 -0
- package/dist/module.js.map +1 -0
- package/dist/procedures.d.ts +23 -0
- package/dist/procedures.d.ts.map +1 -0
- package/dist/procedures.js +109 -0
- package/dist/procedures.js.map +1 -0
- package/dist/register.d.ts +6 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +8 -0
- package/dist/register.js.map +1 -0
- package/package.json +77 -3
- package/src/imap.ts +126 -0
- package/src/index.ts +5 -0
- package/src/mime.ts +160 -0
- package/src/module.ts +18 -0
- package/src/procedures.ts +139 -0
- package/src/register.ts +10 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC"}
|
package/dist/mime.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The headers and the readable text of a raw RFC 822 message. It reads multipart bodies,
|
|
3
|
+
* quoted-printable and base64, and HTML-only messages. It is not a mail client: it ignores
|
|
4
|
+
* attachments, inline images and the headers of the parts.
|
|
5
|
+
*/
|
|
6
|
+
/** This function gives the text of an HTML body as lines. A cell, a row, a paragraph or a break ends a line. */
|
|
7
|
+
export declare const htmlText: (html: string) => string;
|
|
8
|
+
/** This function gives the top-level headers of a raw message, unfolded, with lowercase names. */
|
|
9
|
+
export declare const messageHeaders: (raw: Uint8Array | string) => ReadonlyMap<string, string>;
|
|
10
|
+
/** This function decodes the RFC 2047 encoded words (`=?UTF-8?Q?...?=`) of a header value. */
|
|
11
|
+
export declare const decodeWords: (value: string) => string;
|
|
12
|
+
/** This function gives the text of a message: its plain parts, or its HTML as lines when it has no plain part. */
|
|
13
|
+
export declare const messageText: (raw: Uint8Array | string) => string;
|
|
14
|
+
/** An address of a header: `Name <user@host>`, `<user@host>` or `user@host`. */
|
|
15
|
+
export interface MailAddress {
|
|
16
|
+
readonly name?: string | undefined;
|
|
17
|
+
readonly address: string;
|
|
18
|
+
}
|
|
19
|
+
/** This function reads the addresses of an address header (From, To, Cc). */
|
|
20
|
+
export declare const parseAddresses: (value: string) => MailAddress[];
|
|
21
|
+
//# sourceMappingURL=mime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mime.d.ts","sourceRoot":"","sources":["../src/mime.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAgEH,gHAAgH;AAChH,eAAO,MAAM,QAAQ,SAAU,MAAM,KAAG,MAW7B,CAAC;AAkCZ,kGAAkG;AAClG,eAAO,MAAM,cAAc,QAAS,UAAU,GAAG,MAAM,KAAG,WAAW,CAAC,MAAM,EAAE,MAAM,CAA+B,CAAC;AAEpH,8FAA8F;AAC9F,eAAO,MAAM,WAAW,UAAW,MAAM,KAAG,MAKxC,CAAC;AAEL,kHAAkH;AAClH,eAAO,MAAM,WAAW,QAAS,UAAU,GAAG,MAAM,KAAG,MAYtD,CAAC;AAEF,gFAAgF;AAChF,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,6EAA6E;AAC7E,eAAO,MAAM,cAAc,UAAW,MAAM,KAAG,WAAW,EAYzD,CAAC"}
|
package/dist/mime.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The headers and the readable text of a raw RFC 822 message. It reads multipart bodies,
|
|
3
|
+
* quoted-printable and base64, and HTML-only messages. It is not a mail client: it ignores
|
|
4
|
+
* attachments, inline images and the headers of the parts.
|
|
5
|
+
*/
|
|
6
|
+
const split = (text) => {
|
|
7
|
+
const at = text.search(/\r?\n\r?\n/);
|
|
8
|
+
const head = at === -1 ? text : text.slice(0, at);
|
|
9
|
+
const body = at === -1 ? "" : text.slice(at).replace(/^\r?\n\r?\n/, "");
|
|
10
|
+
const headers = new Map();
|
|
11
|
+
// A header continues on each line that starts with white space (RFC 5322 folding)
|
|
12
|
+
for (const line of head.replace(/\r?\n[ \t]+/g, " ").split(/\r?\n/)) {
|
|
13
|
+
const colon = line.indexOf(":");
|
|
14
|
+
if (colon <= 0)
|
|
15
|
+
continue;
|
|
16
|
+
headers.set(line.slice(0, colon).trim().toLowerCase(), line.slice(colon + 1).trim());
|
|
17
|
+
}
|
|
18
|
+
return { headers, body };
|
|
19
|
+
};
|
|
20
|
+
const param = (value, name) => {
|
|
21
|
+
const match = new RegExp(`(?:^|;)\\s*${name}\\s*=\\s*(?:"([^"]*)"|([^;\\s]+))`, "i").exec(value);
|
|
22
|
+
return match?.[1] ?? match?.[2];
|
|
23
|
+
};
|
|
24
|
+
const quotedPrintable = (text) => text.replace(/=\r?\n/g, "").replace(/=([0-9A-F]{2})/gi, (_, hex) => String.fromCharCode(Number.parseInt(hex, 16)));
|
|
25
|
+
const decode = (body, encoding, charset) => {
|
|
26
|
+
const transfer = (encoding ?? "7bit").toLowerCase();
|
|
27
|
+
const bytes = transfer === "base64"
|
|
28
|
+
? Buffer.from(body.replace(/\s+/g, ""), "base64")
|
|
29
|
+
: transfer === "quoted-printable"
|
|
30
|
+
? Buffer.from(quotedPrintable(body), "latin1")
|
|
31
|
+
: Buffer.from(body, "latin1");
|
|
32
|
+
const set = (charset ?? "utf-8").toLowerCase();
|
|
33
|
+
return set === "utf-8" || set === "utf8" || set === "us-ascii" ? bytes.toString("utf8") : bytes.toString("latin1");
|
|
34
|
+
};
|
|
35
|
+
const ENTITIES = {
|
|
36
|
+
nbsp: " ",
|
|
37
|
+
lt: "<",
|
|
38
|
+
gt: ">",
|
|
39
|
+
quot: '"',
|
|
40
|
+
apos: "'",
|
|
41
|
+
ndash: "-",
|
|
42
|
+
mdash: "-",
|
|
43
|
+
rsquo: "'",
|
|
44
|
+
lsquo: "'",
|
|
45
|
+
ldquo: '"',
|
|
46
|
+
rdquo: '"',
|
|
47
|
+
};
|
|
48
|
+
/** `&` is the last replacement, so `&lt;` stays the text "<". */
|
|
49
|
+
const entities = (text) => text
|
|
50
|
+
.replace(/&#(\d+);/g, (_, code) => String.fromCodePoint(Number(code)))
|
|
51
|
+
.replace(/&#x([0-9a-f]+);/gi, (_, hex) => String.fromCodePoint(Number.parseInt(hex, 16)))
|
|
52
|
+
.replace(/&([a-z]+);/gi, (whole, name) => ENTITIES[name.toLowerCase()] ?? whole)
|
|
53
|
+
.replace(/&/gi, "&");
|
|
54
|
+
/** This function gives the text of an HTML body as lines. A cell, a row, a paragraph or a break ends a line. */
|
|
55
|
+
export const htmlText = (html) => entities(html
|
|
56
|
+
.replace(/<(script|style|head)\b[\s\S]*?<\/\1>/gi, " ")
|
|
57
|
+
.replace(/<br\s*\/?>/gi, "\n")
|
|
58
|
+
.replace(/<\/(?:p|div|tr|td|th|li|h[1-6]|table|tbody|section)>/gi, "\n")
|
|
59
|
+
.replace(/<[^>]+>/g, " "))
|
|
60
|
+
.replace(/[ \t ]+/g, " ")
|
|
61
|
+
.replace(/ *\n */g, "\n")
|
|
62
|
+
.replace(/\n{2,}/g, "\n")
|
|
63
|
+
.trim();
|
|
64
|
+
const walk = (text, plain, html, depth) => {
|
|
65
|
+
if (depth > 8)
|
|
66
|
+
return;
|
|
67
|
+
const { headers, body } = split(text);
|
|
68
|
+
const type = headers.get("content-type") ?? "text/plain";
|
|
69
|
+
const mime = (type.split(";")[0] ?? "").trim().toLowerCase();
|
|
70
|
+
if (mime.startsWith("multipart/")) {
|
|
71
|
+
const boundary = param(type, "boundary");
|
|
72
|
+
if (boundary === undefined)
|
|
73
|
+
return;
|
|
74
|
+
const delimiter = `--${boundary}`;
|
|
75
|
+
let current;
|
|
76
|
+
for (const line of body.split(/\r?\n/)) {
|
|
77
|
+
if (line.startsWith(delimiter)) {
|
|
78
|
+
if (current !== undefined)
|
|
79
|
+
walk(current.join("\r\n"), plain, html, depth + 1);
|
|
80
|
+
// After the closing delimiter comes the epilogue, which is not content
|
|
81
|
+
if (line.startsWith(`${delimiter}--`))
|
|
82
|
+
return;
|
|
83
|
+
current = [];
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
current?.push(line);
|
|
87
|
+
}
|
|
88
|
+
if (current !== undefined)
|
|
89
|
+
walk(current.join("\r\n"), plain, html, depth + 1);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (!mime.startsWith("text/"))
|
|
93
|
+
return;
|
|
94
|
+
if (/^attachment/i.test(headers.get("content-disposition") ?? ""))
|
|
95
|
+
return;
|
|
96
|
+
const decoded = decode(body, headers.get("content-transfer-encoding"), param(type, "charset"));
|
|
97
|
+
if (mime === "text/html")
|
|
98
|
+
html.push(decoded);
|
|
99
|
+
else if (mime === "text/plain")
|
|
100
|
+
plain.push(decoded);
|
|
101
|
+
};
|
|
102
|
+
const asText = (raw) => (typeof raw === "string" ? raw : Buffer.from(raw).toString("latin1"));
|
|
103
|
+
/** This function gives the top-level headers of a raw message, unfolded, with lowercase names. */
|
|
104
|
+
export const messageHeaders = (raw) => split(asText(raw)).headers;
|
|
105
|
+
/** This function decodes the RFC 2047 encoded words (`=?UTF-8?Q?...?=`) of a header value. */
|
|
106
|
+
export const decodeWords = (value) => value.replace(/\?=\s+=\?/g, "?==?").replace(/=\?([^?]+)\?([BQ])\?([^?]*)\?=/gi, (_, charset, encoding, text) => {
|
|
107
|
+
const bytes = encoding.toUpperCase() === "B" ? Buffer.from(text, "base64") : Buffer.from(quotedPrintable(text.replace(/_/g, " ")), "latin1");
|
|
108
|
+
return /^utf-?8$/i.test(charset) ? bytes.toString("utf8") : bytes.toString("latin1");
|
|
109
|
+
});
|
|
110
|
+
/** This function gives the text of a message: its plain parts, or its HTML as lines when it has no plain part. */
|
|
111
|
+
export const messageText = (raw) => {
|
|
112
|
+
const plain = [];
|
|
113
|
+
const html = [];
|
|
114
|
+
walk(asText(raw), plain, html, 0);
|
|
115
|
+
if (plain.some((part) => part.trim().length > 0)) {
|
|
116
|
+
return plain
|
|
117
|
+
.join("\n")
|
|
118
|
+
.replace(/\r\n/g, "\n")
|
|
119
|
+
.replace(/[ \t ]+/g, " ")
|
|
120
|
+
.trim();
|
|
121
|
+
}
|
|
122
|
+
return html.map(htmlText).join("\n").trim();
|
|
123
|
+
};
|
|
124
|
+
/** This function reads the addresses of an address header (From, To, Cc). */
|
|
125
|
+
export const parseAddresses = (value) => {
|
|
126
|
+
const out = [];
|
|
127
|
+
// Commas inside quotes do not separate addresses
|
|
128
|
+
for (const part of value.match(/(?:"[^"]*"|[^,])+/g) ?? []) {
|
|
129
|
+
const text = decodeWords(part.trim());
|
|
130
|
+
const angle = /^(.*?)<([^<>]+)>\s*$/.exec(text);
|
|
131
|
+
const address = (angle ? angle[2] : text)?.trim() ?? "";
|
|
132
|
+
if (!address.includes("@"))
|
|
133
|
+
continue;
|
|
134
|
+
const name = angle?.[1]?.trim().replace(/^"|"$/g, "").trim();
|
|
135
|
+
out.push(name ? { name, address } : { address });
|
|
136
|
+
}
|
|
137
|
+
return out;
|
|
138
|
+
};
|
|
139
|
+
//# sourceMappingURL=mime.js.map
|
package/dist/mime.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mime.js","sourceRoot":"","sources":["../src/mime.ts"],"names":[],"mappings":"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"}
|
package/dist/module.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The procedures of @procedures/mail as data. Importing this file registers nothing.
|
|
3
|
+
*/
|
|
4
|
+
import { type ProcedureModule, type ProcedureRegistry } from "@procedures/client";
|
|
5
|
+
export declare const mailModule: ProcedureModule;
|
|
6
|
+
export default mailModule;
|
|
7
|
+
/**
|
|
8
|
+
* This function registers the procedures of @procedures/mail in a registry. The default is the
|
|
9
|
+
* global registry.
|
|
10
|
+
*/
|
|
11
|
+
export declare function registerMailProcedures(registry?: ProcedureRegistry): void;
|
|
12
|
+
//# sourceMappingURL=module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAqD,KAAK,eAAe,EAAE,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAGrI,eAAO,MAAM,UAAU,EAAE,eAAkE,CAAC;eAE7E,UAAU;AAEzB;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,GAAE,iBAAsC,GAAG,IAAI,CAE7F"}
|
package/dist/module.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The procedures of @procedures/mail as data. Importing this file registers nothing.
|
|
3
|
+
*/
|
|
4
|
+
import { defineModule, registerModules, PROCEDURE_REGISTRY } from "@procedures/client";
|
|
5
|
+
import { mailProcedures } from "./procedures.js";
|
|
6
|
+
export const mailModule = defineModule("@procedures/mail", mailProcedures);
|
|
7
|
+
export default mailModule;
|
|
8
|
+
/**
|
|
9
|
+
* This function registers the procedures of @procedures/mail in a registry. The default is the
|
|
10
|
+
* global registry.
|
|
11
|
+
*/
|
|
12
|
+
export function registerMailProcedures(registry = PROCEDURE_REGISTRY) {
|
|
13
|
+
registerModules(registry, [mailModule]);
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"module.js","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,kBAAkB,EAAgD,MAAM,oBAAoB,CAAC;AACrI,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,MAAM,CAAC,MAAM,UAAU,GAAoB,YAAY,CAAC,kBAAkB,EAAE,cAAc,CAAC,CAAC;AAE5F,eAAe,UAAU,CAAC;AAE1B;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAAQ,GAAsB,kBAAkB;IACrF,eAAe,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;AAC1C,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The procedures of `@procedures/mail`: `mail.parse` and `mail.headers`.
|
|
3
|
+
*/
|
|
4
|
+
import { type AnyProcedure } from "@procedures/client";
|
|
5
|
+
import { type ImapClientFactory } from "./imap.js";
|
|
6
|
+
import { type MailAddress } from "./mime.js";
|
|
7
|
+
interface ParseOutput {
|
|
8
|
+
headers: Record<string, string>;
|
|
9
|
+
subject?: string | undefined;
|
|
10
|
+
from: MailAddress[];
|
|
11
|
+
to: MailAddress[];
|
|
12
|
+
date?: string | undefined;
|
|
13
|
+
text: string;
|
|
14
|
+
}
|
|
15
|
+
/** This function gives the headers, the addresses and the text of a raw message. */
|
|
16
|
+
export declare function parseMessage(raw: string): ParseOutput;
|
|
17
|
+
export declare const mailParseProcedure: AnyProcedure;
|
|
18
|
+
/** This function replaces the IMAP client of `mail.headers` (for tests). Undefined restores `imapflow`. */
|
|
19
|
+
export declare function setImapClientFactory(factory: ImapClientFactory | undefined): void;
|
|
20
|
+
export declare const mailHeadersProcedure: AnyProcedure;
|
|
21
|
+
export declare const mailProcedures: AnyProcedure[];
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=procedures.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"procedures.d.ts","sourceRoot":"","sources":["../src/procedures.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAA+B,KAAK,YAAY,EAAyB,MAAM,oBAAoB,CAAC;AAE3G,OAAO,EAAe,KAAK,iBAAiB,EAAmB,MAAM,WAAW,CAAC;AACjF,OAAO,EAA4D,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAavG,UAAU,WAAW;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,IAAI,EAAE,WAAW,EAAE,CAAC;IACpB,EAAE,EAAE,WAAW,EAAE,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;CACd;AAUD,oFAAoF;AACpF,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAarD;AAED,eAAO,MAAM,kBAAkB,EAAE,YAUP,CAAC;AA0C3B,2GAA2G;AAC3G,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,iBAAiB,GAAG,SAAS,GAAG,IAAI,CAEjF;AAED,eAAO,MAAM,oBAAoB,EAAE,YA0BT,CAAC;AAE3B,eAAO,MAAM,cAAc,EAAE,YAAY,EAA+C,CAAC"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The procedures of `@procedures/mail`: `mail.parse` and `mail.headers`.
|
|
3
|
+
*/
|
|
4
|
+
import { createProcedure, zodAdapter } from "@procedures/client";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { readHeaders } from "./imap.js";
|
|
7
|
+
import { decodeWords, messageHeaders, messageText, parseAddresses } from "./mime.js";
|
|
8
|
+
const addressSchema = z.object({ name: z.string().optional(), address: z.string() });
|
|
9
|
+
// =============================================================================
|
|
10
|
+
// mail.parse
|
|
11
|
+
// =============================================================================
|
|
12
|
+
const ParseInputSchema = z.object({
|
|
13
|
+
raw: z.string().describe("The raw message (RFC 822): the headers, an empty line, the body"),
|
|
14
|
+
});
|
|
15
|
+
const ParseOutputSchema = z.object({
|
|
16
|
+
headers: z.record(z.string(), z.string()),
|
|
17
|
+
subject: z.string().optional(),
|
|
18
|
+
from: z.array(addressSchema),
|
|
19
|
+
to: z.array(addressSchema),
|
|
20
|
+
date: z.string().optional(),
|
|
21
|
+
text: z.string(),
|
|
22
|
+
});
|
|
23
|
+
/** This function gives the headers, the addresses and the text of a raw message. */
|
|
24
|
+
export function parseMessage(raw) {
|
|
25
|
+
const headers = messageHeaders(raw);
|
|
26
|
+
const subject = headers.get("subject");
|
|
27
|
+
const dateHeader = headers.get("date");
|
|
28
|
+
const date = dateHeader !== undefined && !Number.isNaN(Date.parse(dateHeader)) ? new Date(dateHeader).toISOString() : undefined;
|
|
29
|
+
return {
|
|
30
|
+
headers: Object.fromEntries(headers),
|
|
31
|
+
...(subject !== undefined ? { subject: decodeWords(subject) } : {}),
|
|
32
|
+
from: parseAddresses(headers.get("from") ?? ""),
|
|
33
|
+
to: parseAddresses(headers.get("to") ?? ""),
|
|
34
|
+
...(date !== undefined ? { date } : {}),
|
|
35
|
+
text: messageText(raw),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export const mailParseProcedure = createProcedure()
|
|
39
|
+
.path(["mail", "parse"])
|
|
40
|
+
.input(zodAdapter(ParseInputSchema))
|
|
41
|
+
.output(zodAdapter(ParseOutputSchema))
|
|
42
|
+
.meta({
|
|
43
|
+
description: "Read the headers, the addresses and the text of a raw email message",
|
|
44
|
+
output: "json",
|
|
45
|
+
effects: { reads: [], writes: [] },
|
|
46
|
+
})
|
|
47
|
+
.handler(async (input) => parseMessage(input.raw))
|
|
48
|
+
.build();
|
|
49
|
+
// =============================================================================
|
|
50
|
+
// mail.headers
|
|
51
|
+
// =============================================================================
|
|
52
|
+
const HeadersInputSchema = z.object({
|
|
53
|
+
host: z.string().min(1).describe("The IMAP server, for example imap.gmail.com"),
|
|
54
|
+
port: z.number().int().min(1).max(65_535).default(993).describe("The port (default: 993)"),
|
|
55
|
+
secure: z.boolean().default(true).describe("Use TLS from the start (default: true)"),
|
|
56
|
+
user: z.string().min(1).describe("The user name of the mailbox"),
|
|
57
|
+
passwordEnv: z
|
|
58
|
+
.string()
|
|
59
|
+
.regex(/^[A-Za-z_][A-Za-z0-9_]*$/)
|
|
60
|
+
.default("IMAP_PASSWORD")
|
|
61
|
+
.describe("The name of the environment variable that holds the password. The password is never an input, so no log records it."),
|
|
62
|
+
mailbox: z.string().min(1).default("INBOX").describe("The mailbox (default: INBOX)"),
|
|
63
|
+
sinceDays: z.number().int().min(1).max(365).default(1).describe("The messages of this number of days (default: 1)"),
|
|
64
|
+
limit: z.number().int().min(1).max(5_000).default(200).describe("The maximum number of messages, the newest first (default: 200)"),
|
|
65
|
+
timeoutMs: z.number().int().positive().max(600_000).default(60_000).describe("The time limit, in milliseconds (default: 60000)"),
|
|
66
|
+
});
|
|
67
|
+
const HeadersOutputSchema = z.object({
|
|
68
|
+
messages: z.array(z.object({
|
|
69
|
+
uid: z.number(),
|
|
70
|
+
subject: z.string(),
|
|
71
|
+
from: z.array(addressSchema),
|
|
72
|
+
to: z.array(addressSchema),
|
|
73
|
+
date: z.string().optional(),
|
|
74
|
+
seen: z.boolean(),
|
|
75
|
+
})),
|
|
76
|
+
});
|
|
77
|
+
/** Tests replace the IMAP client. */
|
|
78
|
+
let clientFactory;
|
|
79
|
+
/** This function replaces the IMAP client of `mail.headers` (for tests). Undefined restores `imapflow`. */
|
|
80
|
+
export function setImapClientFactory(factory) {
|
|
81
|
+
clientFactory = factory;
|
|
82
|
+
}
|
|
83
|
+
export const mailHeadersProcedure = createProcedure()
|
|
84
|
+
.path(["mail", "headers"])
|
|
85
|
+
.input(zodAdapter(HeadersInputSchema))
|
|
86
|
+
.output(zodAdapter(HeadersOutputSchema))
|
|
87
|
+
.meta({
|
|
88
|
+
description: "Give the headers of the recent messages of an IMAP mailbox, read only (no message becomes read)",
|
|
89
|
+
args: ["host", "user"],
|
|
90
|
+
output: "json",
|
|
91
|
+
effects: { reads: ["network"], writes: [] },
|
|
92
|
+
})
|
|
93
|
+
.handler(async (input, ctx) => {
|
|
94
|
+
const password = process.env[input.passwordEnv];
|
|
95
|
+
if (!password)
|
|
96
|
+
throw new Error(`Set the environment variable ${input.passwordEnv} to the password of the mailbox`);
|
|
97
|
+
const messages = await readHeaders({ host: input.host, port: input.port, secure: input.secure, user: input.user, password }, {
|
|
98
|
+
mailbox: input.mailbox,
|
|
99
|
+
since: new Date(Date.now() - input.sinceDays * 86_400_000),
|
|
100
|
+
limit: input.limit,
|
|
101
|
+
timeoutMs: input.timeoutMs,
|
|
102
|
+
signal: ctx?.signal,
|
|
103
|
+
clientFactory,
|
|
104
|
+
});
|
|
105
|
+
return { messages };
|
|
106
|
+
})
|
|
107
|
+
.build();
|
|
108
|
+
export const mailProcedures = [mailParseProcedure, mailHeadersProcedure];
|
|
109
|
+
//# sourceMappingURL=procedures.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"procedures.js","sourceRoot":"","sources":["../src/procedures.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,cAAc,aAAa,CAAC"}
|
package/dist/register.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Importing this file registers the procedures of @procedures/mail in the global registry:
|
|
3
|
+
* `import "@procedures/mail/register"`. The package root registers nothing.
|
|
4
|
+
*/
|
|
5
|
+
import { registerMailProcedures } from "./module.js";
|
|
6
|
+
export * from "./module.js";
|
|
7
|
+
registerMailProcedures();
|
|
8
|
+
//# sourceMappingURL=register.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.js","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAErD,cAAc,aAAa,CAAC;AAE5B,sBAAsB,EAAE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,80 @@
|
|
|
1
1
|
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/package",
|
|
2
3
|
"name": "@procedures/mail",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
4
|
+
"version": "0.2.3",
|
|
5
|
+
"description": "Email procedures: the headers, addresses and text of a raw MIME message, and the headers of recent IMAP messages, read only.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"email",
|
|
8
|
+
"mime",
|
|
9
|
+
"imap",
|
|
10
|
+
"rfc822",
|
|
11
|
+
"procedures"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://mark1russell7.github.io/client/",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/mark1russell7/client/issues"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/mark1russell7/client.git",
|
|
20
|
+
"directory": "packages/mail"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": "Mark Russell (https://github.com/mark1russell7)",
|
|
24
|
+
"type": "module",
|
|
25
|
+
"main": "./dist/index.js",
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"import": "./dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./module": {
|
|
33
|
+
"types": "./dist/module.d.ts",
|
|
34
|
+
"import": "./dist/module.js"
|
|
35
|
+
},
|
|
36
|
+
"./register": {
|
|
37
|
+
"types": "./dist/register.d.ts",
|
|
38
|
+
"import": "./dist/register.js"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"src",
|
|
44
|
+
"!src/**/*.test.ts",
|
|
45
|
+
"!src/**/*.test.tsx",
|
|
46
|
+
"!src/**/__tests__",
|
|
47
|
+
"CHANGELOG.md"
|
|
48
|
+
],
|
|
49
|
+
"sideEffects": [
|
|
50
|
+
"./dist/register.js"
|
|
51
|
+
],
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=22"
|
|
54
|
+
},
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public",
|
|
57
|
+
"provenance": true
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"@procedures/client": "0.2.3",
|
|
61
|
+
"zod": "^4.6.5",
|
|
62
|
+
"imapflow": "~2.2.1"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"typescript": "^7.0.2",
|
|
66
|
+
"@types/node": "^22.0.0",
|
|
67
|
+
"@mark1russell7/cue": "^1.0.0",
|
|
68
|
+
"vitest": "^5.0.3"
|
|
69
|
+
},
|
|
70
|
+
"client": {
|
|
71
|
+
"procedures": "./dist/register.js"
|
|
72
|
+
},
|
|
73
|
+
"scripts": {
|
|
74
|
+
"build": "tsc -b",
|
|
75
|
+
"typecheck": "tsc --noEmit",
|
|
76
|
+
"clean": "rm -rf dist .tsbuildinfo",
|
|
77
|
+
"test": "vitest run",
|
|
78
|
+
"test:watch": "vitest"
|
|
79
|
+
}
|
|
6
80
|
}
|
package/src/imap.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The headers of the recent messages of an IMAP mailbox. It opens the mailbox read only (EXAMINE)
|
|
3
|
+
* and fetches the envelope and the flags of each message, so it changes no flag: a message stays
|
|
4
|
+
* unread.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { MailAddress } from "./mime.js";
|
|
8
|
+
|
|
9
|
+
/** The part of an IMAP client that this module uses (the shape of `imapflow`). */
|
|
10
|
+
export interface ImapClientLike {
|
|
11
|
+
connect(): Promise<void>;
|
|
12
|
+
logout(): Promise<void>;
|
|
13
|
+
close(): void;
|
|
14
|
+
getMailboxLock(mailbox: string, options?: { readOnly?: boolean }): Promise<{ release(): void }>;
|
|
15
|
+
fetch(
|
|
16
|
+
range: { since: Date },
|
|
17
|
+
query: { envelope: true; flags: true; uid: true }
|
|
18
|
+
): AsyncIterable<{
|
|
19
|
+
uid: number;
|
|
20
|
+
flags?: Set<string> | undefined;
|
|
21
|
+
envelope?:
|
|
22
|
+
| {
|
|
23
|
+
subject?: string | undefined;
|
|
24
|
+
date?: Date | undefined;
|
|
25
|
+
from?: Array<{ name?: string | undefined; address?: string | undefined }> | undefined;
|
|
26
|
+
to?: Array<{ name?: string | undefined; address?: string | undefined }> | undefined;
|
|
27
|
+
}
|
|
28
|
+
| undefined;
|
|
29
|
+
}>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The connection of an IMAP client. */
|
|
33
|
+
export interface ImapConnection {
|
|
34
|
+
readonly host: string;
|
|
35
|
+
readonly port: number;
|
|
36
|
+
readonly secure: boolean;
|
|
37
|
+
readonly user: string;
|
|
38
|
+
readonly password: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The headers of one message. */
|
|
42
|
+
export interface MailHeader {
|
|
43
|
+
readonly uid: number;
|
|
44
|
+
readonly subject: string;
|
|
45
|
+
readonly from: MailAddress[];
|
|
46
|
+
readonly to: MailAddress[];
|
|
47
|
+
/** The date of the message (ISO 8601), when it has one */
|
|
48
|
+
readonly date?: string | undefined;
|
|
49
|
+
readonly seen: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type ImapClientFactory = (connection: ImapConnection) => Promise<ImapClientLike>;
|
|
53
|
+
|
|
54
|
+
const imapflowClient: ImapClientFactory = async (connection) => {
|
|
55
|
+
const { ImapFlow } = await import("imapflow");
|
|
56
|
+
return new ImapFlow({
|
|
57
|
+
host: connection.host,
|
|
58
|
+
port: connection.port,
|
|
59
|
+
secure: connection.secure,
|
|
60
|
+
auth: { user: connection.user, pass: connection.password },
|
|
61
|
+
logger: false,
|
|
62
|
+
}) as unknown as ImapClientLike;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const addresses = (list: Array<{ name?: string | undefined; address?: string | undefined }> | undefined): MailAddress[] =>
|
|
66
|
+
(list ?? [])
|
|
67
|
+
.filter((entry): entry is { name?: string; address: string } => typeof entry.address === "string")
|
|
68
|
+
.map((entry) => (entry.name ? { name: entry.name, address: entry.address } : { address: entry.address }));
|
|
69
|
+
|
|
70
|
+
export interface ReadHeadersOptions {
|
|
71
|
+
readonly mailbox: string;
|
|
72
|
+
readonly since: Date;
|
|
73
|
+
readonly limit: number;
|
|
74
|
+
readonly timeoutMs: number;
|
|
75
|
+
readonly signal?: AbortSignal | undefined;
|
|
76
|
+
readonly clientFactory?: ImapClientFactory | undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* This function gives the headers of the messages of a mailbox since a date, the newest first, at
|
|
81
|
+
* most `limit`. A time limit or an abort closes the connection.
|
|
82
|
+
*/
|
|
83
|
+
export async function readHeaders(connection: ImapConnection, options: ReadHeadersOptions): Promise<MailHeader[]> {
|
|
84
|
+
const client = await (options.clientFactory ?? imapflowClient)(connection);
|
|
85
|
+
let stopped: string | undefined;
|
|
86
|
+
let loggedOut = false;
|
|
87
|
+
const stop = (reason: string): void => {
|
|
88
|
+
stopped ??= reason;
|
|
89
|
+
client.close();
|
|
90
|
+
};
|
|
91
|
+
const timer = setTimeout(() => stop(`no answer in ${options.timeoutMs} ms`), options.timeoutMs);
|
|
92
|
+
const onAbort = (): void => stop("the call was aborted");
|
|
93
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
94
|
+
try {
|
|
95
|
+
if (options.signal?.aborted) throw new Error("the call was aborted");
|
|
96
|
+
await client.connect();
|
|
97
|
+
const lock = await client.getMailboxLock(options.mailbox, { readOnly: true });
|
|
98
|
+
const headers: MailHeader[] = [];
|
|
99
|
+
try {
|
|
100
|
+
for await (const message of client.fetch({ since: options.since }, { envelope: true, flags: true, uid: true })) {
|
|
101
|
+
const envelope = message.envelope;
|
|
102
|
+
headers.push({
|
|
103
|
+
uid: message.uid,
|
|
104
|
+
subject: envelope?.subject ?? "",
|
|
105
|
+
from: addresses(envelope?.from),
|
|
106
|
+
to: addresses(envelope?.to),
|
|
107
|
+
...(envelope?.date ? { date: envelope.date.toISOString() } : {}),
|
|
108
|
+
seen: message.flags?.has("\\Seen") ?? false,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
} finally {
|
|
112
|
+
lock.release();
|
|
113
|
+
}
|
|
114
|
+
await client.logout();
|
|
115
|
+
loggedOut = true;
|
|
116
|
+
return headers.sort((a, b) => b.uid - a.uid).slice(0, options.limit);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
if (stopped !== undefined) throw new Error(`The IMAP server did not finish: ${stopped}`);
|
|
119
|
+
throw error;
|
|
120
|
+
} finally {
|
|
121
|
+
clearTimeout(timer);
|
|
122
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
123
|
+
// An error leaves the connection open: close it
|
|
124
|
+
if (!loggedOut && stopped === undefined) client.close();
|
|
125
|
+
}
|
|
126
|
+
}
|