@postalsys/email-text-tools 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.ncurc.js ADDED
@@ -0,0 +1,8 @@
1
+ module.exports = {
2
+ upgrade: true,
3
+ reject: [
4
+ // Block package upgrades that moved to ESM
5
+ 'node-fetch',
6
+ 'nanoid'
7
+ ]
8
+ };
@@ -0,0 +1,23 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs').promises;
4
+ const extractReply = require('../lib/extract-reply');
5
+ const simpleParser = require('mailparser').simpleParser;
6
+
7
+ async function main() {
8
+ const eml = await fs.readFile(process.argv[2]);
9
+
10
+ const parsed = await simpleParser(eml);
11
+
12
+ const replyText = await extractReply(
13
+ {
14
+ html: parsed.html,
15
+ text: parsed.text
16
+ },
17
+ process.env.OPENAI_API_KEY
18
+ );
19
+
20
+ console.log(replyText);
21
+ }
22
+
23
+ main();
@@ -0,0 +1,89 @@
1
+ 'use strict';
2
+
3
+ const packageData = require('../package.json');
4
+ const htmlToText = require('../lib/html-to-text');
5
+ const nodeFetch = require('node-fetch');
6
+ const fetchCmd = global.fetch || nodeFetch;
7
+
8
+ const MAX_TEXT_LENGTH = 6 * 1024;
9
+ const OPENAI_API_URL = 'https://api.openai.com/v1/chat/completions';
10
+
11
+ async function extractReply(message, apiToken) {
12
+ let text = message.text;
13
+ if (!text && message.html) {
14
+ text = htmlToText(message.text);
15
+ }
16
+
17
+ text = text.trim();
18
+ if (text.length > MAX_TEXT_LENGTH) {
19
+ text = text.substring(0, MAX_TEXT_LENGTH);
20
+ }
21
+
22
+ let headers = {
23
+ 'User-Agent': `${packageData.name}/${packageData.version}`,
24
+ Authorization: `Bearer ${apiToken}`,
25
+ 'Content-Type': 'application/json'
26
+ };
27
+
28
+ let payload = {
29
+ model: 'gpt-3.5-turbo',
30
+ messages: [
31
+ {
32
+ role: 'user',
33
+ content: `Extract the reply text without the final courtesy closing from the following email message:
34
+
35
+ ${text}`
36
+ }
37
+ ]
38
+ };
39
+
40
+ let res = await fetchCmd(OPENAI_API_URL, {
41
+ method: 'post',
42
+ headers,
43
+ body: JSON.stringify(payload)
44
+ });
45
+
46
+ let data = await res.json();
47
+
48
+ if (!res.ok) {
49
+ if (data && data.error) {
50
+ let error = new Error(data.error.message || data.error);
51
+ if (data.error.code) {
52
+ error.code = data.error.code;
53
+ }
54
+
55
+ error.statusCode = res.status;
56
+ throw error;
57
+ }
58
+
59
+ let error = new Error('Failed to run API request');
60
+ error.statusCode = res.status;
61
+ throw error;
62
+ }
63
+
64
+ if (!data) {
65
+ throw new Error(`Failed to POST API request`);
66
+ }
67
+
68
+ let output =
69
+ data &&
70
+ data.choices &&
71
+ data.choices
72
+ .filter(msg => msg && msg.message && msg.message.role === 'assistant' && msg.message.content)
73
+ .sort((a, b) => ((a && a.index) || 0) - ((b && b.index) || 0))
74
+ .map(msg => msg.message.content)
75
+ .join('\n')
76
+ .trim()
77
+ .replace(/^\s*"|"\s*$/g, '')
78
+ .trim();
79
+
80
+ const response = {
81
+ id: data && data.id,
82
+ tokens: data && data.usage && data.usage.total_tokens,
83
+ replyText: output
84
+ };
85
+
86
+ return response;
87
+ }
88
+
89
+ module.exports = extractReply;
package/lib/mime-html.js CHANGED
@@ -7,6 +7,30 @@ const he = require('he');
7
7
 
8
8
  const textToHtml = require('./text-to-html');
9
9
 
10
+ const FORBID_TAGS = [
11
+ 'title',
12
+ 'link',
13
+ 'meta',
14
+ 'base',
15
+ 'basefont',
16
+ 'frame',
17
+ 'iframe',
18
+ 'frameset',
19
+ 'template',
20
+ 'script',
21
+ 'noframes',
22
+ 'noscript',
23
+ 'object',
24
+ 'embed',
25
+ 'dialog',
26
+ 'canvas',
27
+ 'audio',
28
+ 'video',
29
+ 'applet'
30
+ ];
31
+ // keep the style at first
32
+ const FORBID_TAGS_ALL = ['style'].concat(FORBID_TAGS);
33
+
10
34
  // define styles for plaintext emails
11
35
  const INLINE_STYLE_BLOCK = `<style>
12
36
 
@@ -63,7 +87,7 @@ function mimeHtml(message) {
63
87
  const DOMPurify = createDOMPurify(window);
64
88
 
65
89
  // first pass, outputs a fixed HTML page
66
- html = DOMPurify.sanitize(html, { WHOLE_DOCUMENT: true, FORCE_BODY: false, FORBID_TAGS: ['title', 'link', 'meta'] });
90
+ html = DOMPurify.sanitize(html, { WHOLE_DOCUMENT: true, FORCE_BODY: false, FORBID_TAGS });
67
91
 
68
92
  try {
69
93
  //inline styles
@@ -74,7 +98,12 @@ function mimeHtml(message) {
74
98
  }
75
99
 
76
100
  // second pass, outputs a BODY DOM element
77
- const dom = DOMPurify.sanitize(html, { RETURN_DOM: true, WHOLE_DOCUMENT: false, FORCE_BODY: true, FORBID_TAGS: ['style', 'title', 'link', 'meta'] });
101
+ const dom = DOMPurify.sanitize(html, {
102
+ RETURN_DOM: true,
103
+ WHOLE_DOCUMENT: false,
104
+ FORCE_BODY: true,
105
+ FORBID_TAGS: FORBID_TAGS_ALL
106
+ });
78
107
 
79
108
  // mark all links to be opened in a new window
80
109
  for (let elm of dom.querySelectorAll('a')) {
@@ -134,14 +134,14 @@ function textLinesToHtml(lines) {
134
134
  }
135
135
  } else if (i < lines.length - 1 && lines[i + 1].trim()) {
136
136
  let escaped = linkifyAndHtmlEscape(line);
137
- if (/^[ \t]*[-_]+[ \t]*$/.test(escaped)) {
137
+ if (/^[ \t]*[-_]{2,}[ \t]*$/.test(escaped)) {
138
138
  textparts.push(`<hr />`);
139
139
  } else {
140
140
  textparts.push(`${escaped}<br />`);
141
141
  }
142
142
  } else {
143
143
  let escaped = linkifyAndHtmlEscape(line);
144
- if (/^[ \t]*[-_]+[ \t]*$/.test(escaped)) {
144
+ if (/^[ \t]*[-_]{2,}[ \t]*$/.test(escaped)) {
145
145
  escaped = `<hr />`;
146
146
  }
147
147
  textparts.push(escaped);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postalsys/email-text-tools",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Email text tools",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -19,19 +19,21 @@
19
19
  "homepage": "https://github.com/postalsys/email-text-tools#readme",
20
20
  "devDependencies": {
21
21
  "eslint-config-nodemailer": "1.2.0",
22
- "eslint-config-prettier": "8.5.0"
22
+ "eslint-config-prettier": "8.7.0",
23
+ "mailparser": "3.6.4"
23
24
  },
24
25
  "dependencies": {
25
26
  "dompurify": "3.0.1",
26
27
  "handlebars": "4.7.7",
27
28
  "he": "1.2.0",
28
- "html-to-text": "8.2.1",
29
+ "html-to-text": "9.0.4",
29
30
  "js-beautify": "1.14.7",
30
31
  "jsdom": "21.1.0",
31
32
  "juice": "8.1.0",
32
33
  "linkify-it": "4.0.1",
33
- "moment-timezone": "0.5.39",
34
- "node-html-parser": "6.1.4",
35
- "tlds": "1.233.0"
34
+ "moment-timezone": "0.5.41",
35
+ "node-fetch": "2.6.7",
36
+ "node-html-parser": "6.1.5",
37
+ "tlds": "1.236.0"
36
38
  }
37
39
  }