lite-email-parser 1.0.4 → 2.0.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/binding.gyp +33 -0
- package/dist/cjs/index.d.ts +7 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +61 -5
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -2
- package/package.json +36 -8
- package/src/addon.cpp +141 -0
- package/README.md +0 -61
- package/dist/cjs/parser.d.ts +0 -4
- package/dist/cjs/parser.d.ts.map +0 -1
- package/dist/cjs/parser.js +0 -180
- package/dist/index.cjs +0 -190
- package/dist/parser.d.ts +0 -4
- package/dist/parser.d.ts.map +0 -1
- package/dist/parser.js +0 -143
- package/dist/src/index.d.ts +0 -2
package/binding.gyp
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"targets": [
|
|
3
|
+
{
|
|
4
|
+
"target_name": "parser",
|
|
5
|
+
"sources": [
|
|
6
|
+
"src/addon.cpp",
|
|
7
|
+
"../core/src/html_parser.cpp",
|
|
8
|
+
"../core/src/html_extractor.cpp"
|
|
9
|
+
],
|
|
10
|
+
"include_dirs": [
|
|
11
|
+
"<!@(node -p \"require('node-addon-api').include\")",
|
|
12
|
+
"../core/include"
|
|
13
|
+
],
|
|
14
|
+
"dependencies": [
|
|
15
|
+
"<!(node -p \"require('node-addon-api').targets\"):node_addon_api"
|
|
16
|
+
],
|
|
17
|
+
"libraries": [
|
|
18
|
+
"-lgumbo"
|
|
19
|
+
],
|
|
20
|
+
"defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ],
|
|
21
|
+
"cflags!": [ "-fno-exceptions" ],
|
|
22
|
+
"cflags_cc!": [ "-fno-exceptions" ],
|
|
23
|
+
"xcode_settings": {
|
|
24
|
+
"GCC_ENABLE_CPP_EXCEPTIONS": "YES",
|
|
25
|
+
"CLANG_CXX_LIBRARY": "libc++",
|
|
26
|
+
"MACOSX_DEPLOYMENT_TARGET": "10.7"
|
|
27
|
+
},
|
|
28
|
+
"msvs_settings": {
|
|
29
|
+
"VCCLCompilerTool": { "ExceptionHandling": 1 }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import { IFile, ParseEmailResult } from './types.js';
|
|
2
|
+
export declare function parseEmail(buffer: Buffer): Promise<ParseEmailResult>;
|
|
3
|
+
export declare function cleanHtml(html: string): string;
|
|
4
|
+
export declare function removeSignature(html: string): string;
|
|
5
|
+
export declare function removeReplies(html: string): string;
|
|
6
|
+
export declare function removeDividers(html: string): string;
|
|
7
|
+
export declare function replaceSrc(html: string, files: IFile[]): string;
|
|
2
8
|
export type { IFile, ParseEmailResult } from './types.js';
|
|
3
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/cjs/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAUrD,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAE1E;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,MAAM,CAE/D;AAGD,YAAY,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,7 +1,63 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
36
|
+
exports.parseEmail = parseEmail;
|
|
37
|
+
exports.cleanHtml = cleanHtml;
|
|
38
|
+
exports.removeSignature = removeSignature;
|
|
39
|
+
exports.removeReplies = removeReplies;
|
|
40
|
+
exports.removeDividers = removeDividers;
|
|
41
|
+
exports.replaceSrc = replaceSrc;
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const addonPath = path.resolve(__dirname, '..', 'build', 'Release', 'parser.node');
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
45
|
+
const addon = require(addonPath);
|
|
46
|
+
async function parseEmail(buffer) {
|
|
47
|
+
return addon.parseEmail(buffer);
|
|
48
|
+
}
|
|
49
|
+
function cleanHtml(html) {
|
|
50
|
+
return addon.cleanHtml(html);
|
|
51
|
+
}
|
|
52
|
+
function removeSignature(html) {
|
|
53
|
+
return addon.removeSignature(html);
|
|
54
|
+
}
|
|
55
|
+
function removeReplies(html) {
|
|
56
|
+
return addon.removeReplies(html);
|
|
57
|
+
}
|
|
58
|
+
function removeDividers(html) {
|
|
59
|
+
return addon.removeDividers(html);
|
|
60
|
+
}
|
|
61
|
+
function replaceSrc(html, files) {
|
|
62
|
+
return addon.replaceSrc(html, files);
|
|
63
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
import { IFile, ParseEmailResult } from './types.js';
|
|
2
|
+
export declare function parseEmail(buffer: Buffer): Promise<ParseEmailResult>;
|
|
3
|
+
export declare function cleanHtml(html: string): string;
|
|
4
|
+
export declare function removeSignature(html: string): string;
|
|
5
|
+
export declare function removeReplies(html: string): string;
|
|
6
|
+
export declare function removeDividers(html: string): string;
|
|
7
|
+
export declare function replaceSrc(html: string, files: IFile[]): string;
|
|
2
8
|
export type { IFile, ParseEmailResult } from './types.js';
|
|
3
9
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAUrD,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAE1E;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,MAAM,CAE/D;AAGD,YAAY,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
const addonPath = path.resolve(__dirname, '..', 'build', 'Release', 'parser.node');
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
4
|
+
const addon = require(addonPath);
|
|
5
|
+
export async function parseEmail(buffer) {
|
|
6
|
+
return addon.parseEmail(buffer);
|
|
7
|
+
}
|
|
8
|
+
export function cleanHtml(html) {
|
|
9
|
+
return addon.cleanHtml(html);
|
|
10
|
+
}
|
|
11
|
+
export function removeSignature(html) {
|
|
12
|
+
return addon.removeSignature(html);
|
|
13
|
+
}
|
|
14
|
+
export function removeReplies(html) {
|
|
15
|
+
return addon.removeReplies(html);
|
|
16
|
+
}
|
|
17
|
+
export function removeDividers(html) {
|
|
18
|
+
return addon.removeDividers(html);
|
|
19
|
+
}
|
|
20
|
+
export function replaceSrc(html, files) {
|
|
21
|
+
return addon.replaceSrc(html, files);
|
|
22
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lite-email-parser",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Parse email to get signature, replies, attachments and inline images",
|
|
5
5
|
"main": "./dist/cjs/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -18,13 +18,40 @@
|
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"files": [
|
|
21
|
-
"dist/"
|
|
21
|
+
"dist/",
|
|
22
|
+
"binding.gyp",
|
|
23
|
+
"src/addon.cpp"
|
|
22
24
|
],
|
|
25
|
+
"binary": {
|
|
26
|
+
"module_name": "parser",
|
|
27
|
+
"module_path": "./build/Release/",
|
|
28
|
+
"host": "https://github.com/philipedc/lite-email-parser/releases/download/",
|
|
29
|
+
"remote_path": "{version}",
|
|
30
|
+
"package_name": "{module_name}-v{version}-napi-v9-{platform}-{arch}.tar.gz",
|
|
31
|
+
"napi_versions": [9]
|
|
32
|
+
},
|
|
23
33
|
"scripts": {
|
|
24
|
-
"
|
|
25
|
-
"
|
|
34
|
+
"install": "prebuild-install || node-gyp rebuild",
|
|
35
|
+
"build:native": "node-gyp rebuild",
|
|
36
|
+
"build:ts": "npx tsc && npx tsc --module commonjs --outDir dist/cjs --declaration && echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json",
|
|
37
|
+
"build": "npm run build:native && npm run build:ts",
|
|
38
|
+
"prebuild": "node-gyp rebuild && tar -czf parser-v2.0.0-napi-v9-linux-x64.tar.gz -C build/Release parser.node",
|
|
39
|
+
"test": "npx -y tsx samples/sample.ts"
|
|
26
40
|
},
|
|
27
|
-
"
|
|
41
|
+
"contributors": [
|
|
42
|
+
{
|
|
43
|
+
"name": "Henrique Teixeira Silva",
|
|
44
|
+
"email": "henriqueteixeirasilva16@gmail.com",
|
|
45
|
+
"url": "https://github.com/enriqTS"
|
|
46
|
+
},
|
|
47
|
+
"Henrique Teixeira Silva <henriqueteixeirasilva16@gmail.com> (https://github.com/enriqTS)",
|
|
48
|
+
{
|
|
49
|
+
"name": "Philipe Dutra Cunha",
|
|
50
|
+
"email": "philipedtc@gmail.com",
|
|
51
|
+
"url": "https://github.com/philipedc"
|
|
52
|
+
},
|
|
53
|
+
"Philipe Dutra Cunha <philipedtc@gmail.com> (https://github.com/philipedc)"
|
|
54
|
+
],
|
|
28
55
|
"type": "module",
|
|
29
56
|
"repository": {
|
|
30
57
|
"type": "git",
|
|
@@ -47,11 +74,12 @@
|
|
|
47
74
|
"signature-remover"
|
|
48
75
|
],
|
|
49
76
|
"dependencies": {
|
|
50
|
-
"
|
|
51
|
-
"
|
|
77
|
+
"node-addon-api": "^8.0.0",
|
|
78
|
+
"prebuild-install": "^7.1.2"
|
|
52
79
|
},
|
|
53
80
|
"devDependencies": {
|
|
54
|
-
"@types/
|
|
81
|
+
"@types/node": "^20.11.0",
|
|
82
|
+
"node-gyp": "^13.0.0",
|
|
55
83
|
"typescript": "^7.0.2"
|
|
56
84
|
}
|
|
57
85
|
}
|
package/src/addon.cpp
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#include <napi.h>
|
|
2
|
+
#include "html_parser.h"
|
|
3
|
+
#include "html_extractor.h"
|
|
4
|
+
|
|
5
|
+
Napi::String CleanHtmlWrapped(const Napi::CallbackInfo& info) {
|
|
6
|
+
Napi::Env env = info.Env();
|
|
7
|
+
if (info.Length() < 1 || !info[0].IsString()) {
|
|
8
|
+
Napi::TypeError::New(env, "String expected").ThrowAsJavaScriptException();
|
|
9
|
+
return Napi::String::New(env, "");
|
|
10
|
+
}
|
|
11
|
+
std::string html = info[0].As<Napi::String>().Utf8Value();
|
|
12
|
+
std::string result = liteEmailParser::cleanHtml(html);
|
|
13
|
+
return Napi::String::New(env, result);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
Napi::String RemoveSignatureWrapped(const Napi::CallbackInfo& info) {
|
|
17
|
+
Napi::Env env = info.Env();
|
|
18
|
+
if (info.Length() < 1 || !info[0].IsString()) {
|
|
19
|
+
Napi::TypeError::New(env, "String expected").ThrowAsJavaScriptException();
|
|
20
|
+
return Napi::String::New(env, "");
|
|
21
|
+
}
|
|
22
|
+
std::string html = info[0].As<Napi::String>().Utf8Value();
|
|
23
|
+
std::string result = liteEmailParser::removeSignature(html);
|
|
24
|
+
return Napi::String::New(env, result);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
Napi::String RemoveRepliesWrapped(const Napi::CallbackInfo& info) {
|
|
28
|
+
Napi::Env env = info.Env();
|
|
29
|
+
if (info.Length() < 1 || !info[0].IsString()) {
|
|
30
|
+
Napi::TypeError::New(env, "String expected").ThrowAsJavaScriptException();
|
|
31
|
+
return Napi::String::New(env, "");
|
|
32
|
+
}
|
|
33
|
+
std::string html = info[0].As<Napi::String>().Utf8Value();
|
|
34
|
+
std::string result = liteEmailParser::removeReplies(html);
|
|
35
|
+
return Napi::String::New(env, result);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
Napi::String RemoveDividersWrapped(const Napi::CallbackInfo& info) {
|
|
39
|
+
Napi::Env env = info.Env();
|
|
40
|
+
if (info.Length() < 1 || !info[0].IsString()) {
|
|
41
|
+
Napi::TypeError::New(env, "String expected").ThrowAsJavaScriptException();
|
|
42
|
+
return Napi::String::New(env, "");
|
|
43
|
+
}
|
|
44
|
+
std::string html = info[0].As<Napi::String>().Utf8Value();
|
|
45
|
+
std::string result = liteEmailParser::removeDividers(html);
|
|
46
|
+
return Napi::String::New(env, result);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
Napi::Value ParseEmailWrapped(const Napi::CallbackInfo& info) {
|
|
50
|
+
Napi::Env env = info.Env();
|
|
51
|
+
if (info.Length() < 1 || !info[0].IsBuffer()) {
|
|
52
|
+
Napi::TypeError::New(env, "Buffer expected").ThrowAsJavaScriptException();
|
|
53
|
+
return env.Null();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
Napi::Buffer<char> buffer = info[0].As<Napi::Buffer<char>>();
|
|
57
|
+
std::string rawEmail(buffer.Data(), buffer.Length());
|
|
58
|
+
|
|
59
|
+
liteEmailParser::ParseEmailResult result = liteEmailParser::parseEmail(rawEmail);
|
|
60
|
+
std::string cleaned = liteEmailParser::cleanHtml(result.text);
|
|
61
|
+
|
|
62
|
+
Napi::Object obj = Napi::Object::New(env);
|
|
63
|
+
obj.Set("subject", Napi::String::New(env, result.subject));
|
|
64
|
+
obj.Set("from", Napi::String::New(env, result.from));
|
|
65
|
+
obj.Set("to", Napi::String::New(env, result.to));
|
|
66
|
+
obj.Set("lastMessage", Napi::String::New(env, cleaned));
|
|
67
|
+
|
|
68
|
+
Napi::Array attachments = Napi::Array::New(env, result.attachments.size());
|
|
69
|
+
for (size_t i = 0; i < result.attachments.size(); ++i) {
|
|
70
|
+
const auto& att = result.attachments[i];
|
|
71
|
+
Napi::Object attObj = Napi::Object::New(env);
|
|
72
|
+
attObj.Set("name", Napi::String::New(env, att.name));
|
|
73
|
+
attObj.Set("type", Napi::String::New(env, att.type));
|
|
74
|
+
attObj.Set("size", Napi::Number::New(env, att.size));
|
|
75
|
+
attObj.Set("buffer", Napi::Buffer<char>::Copy(env, att.buffer.data(), att.buffer.size()));
|
|
76
|
+
attachments.Set(i, attObj);
|
|
77
|
+
}
|
|
78
|
+
obj.Set("attachments", attachments);
|
|
79
|
+
|
|
80
|
+
Napi::Array inlineAttachments = Napi::Array::New(env, result.inlineAttachments.size());
|
|
81
|
+
for (size_t i = 0; i < result.inlineAttachments.size(); ++i) {
|
|
82
|
+
const auto& att = result.inlineAttachments[i];
|
|
83
|
+
Napi::Object attObj = Napi::Object::New(env);
|
|
84
|
+
attObj.Set("name", Napi::String::New(env, att.name));
|
|
85
|
+
attObj.Set("type", Napi::String::New(env, att.type));
|
|
86
|
+
attObj.Set("size", Napi::Number::New(env, att.size));
|
|
87
|
+
attObj.Set("buffer", Napi::Buffer<char>::Copy(env, att.buffer.data(), att.buffer.size()));
|
|
88
|
+
if (!att.originalSrc.empty()) {
|
|
89
|
+
attObj.Set("originalSrc", Napi::String::New(env, att.originalSrc));
|
|
90
|
+
}
|
|
91
|
+
if (!att.src.empty()) {
|
|
92
|
+
attObj.Set("src", Napi::String::New(env, att.src));
|
|
93
|
+
}
|
|
94
|
+
inlineAttachments.Set(i, attObj);
|
|
95
|
+
}
|
|
96
|
+
obj.Set("inlineAttachments", inlineAttachments);
|
|
97
|
+
|
|
98
|
+
return obj;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
Napi::String ReplaceSrcWrapped(const Napi::CallbackInfo& info) {
|
|
102
|
+
Napi::Env env = info.Env();
|
|
103
|
+
if (info.Length() < 2 || !info[0].IsString() || !info[1].IsArray()) {
|
|
104
|
+
Napi::TypeError::New(env, "Expected (string, Array) arguments").ThrowAsJavaScriptException();
|
|
105
|
+
return Napi::String::New(env, "");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
std::string html = info[0].As<Napi::String>().Utf8Value();
|
|
109
|
+
Napi::Array filesArray = info[1].As<Napi::Array>();
|
|
110
|
+
|
|
111
|
+
std::vector<liteEmailParser::Attachment> files;
|
|
112
|
+
for (uint32_t i = 0; i < filesArray.Length(); ++i) {
|
|
113
|
+
Napi::Value val = filesArray.Get(i);
|
|
114
|
+
if (!val.IsObject()) continue;
|
|
115
|
+
Napi::Object fileObj = val.As<Napi::Object>();
|
|
116
|
+
|
|
117
|
+
liteEmailParser::Attachment att;
|
|
118
|
+
if (fileObj.Has("originalSrc") && fileObj.Get("originalSrc").IsString()) {
|
|
119
|
+
att.originalSrc = fileObj.Get("originalSrc").As<Napi::String>().Utf8Value();
|
|
120
|
+
}
|
|
121
|
+
if (fileObj.Has("src") && fileObj.Get("src").IsString()) {
|
|
122
|
+
att.src = fileObj.Get("src").As<Napi::String>().Utf8Value();
|
|
123
|
+
}
|
|
124
|
+
files.push_back(att);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
std::string result = liteEmailParser::replaceSrc(html, files);
|
|
128
|
+
return Napi::String::New(env, result);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
132
|
+
exports.Set(Napi::String::New(env, "cleanHtml"), Napi::Function::New(env, CleanHtmlWrapped));
|
|
133
|
+
exports.Set(Napi::String::New(env, "removeSignature"), Napi::Function::New(env, RemoveSignatureWrapped));
|
|
134
|
+
exports.Set(Napi::String::New(env, "removeReplies"), Napi::Function::New(env, RemoveRepliesWrapped));
|
|
135
|
+
exports.Set(Napi::String::New(env, "removeDividers"), Napi::Function::New(env, RemoveDividersWrapped));
|
|
136
|
+
exports.Set(Napi::String::New(env, "parseEmail"), Napi::Function::New(env, ParseEmailWrapped));
|
|
137
|
+
exports.Set(Napi::String::New(env, "replaceSrc"), Napi::Function::New(env, ReplaceSrcWrapped));
|
|
138
|
+
return exports;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
NODE_API_MODULE(parser, Init)
|
package/README.md
DELETED
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
# Lite email parser
|
|
2
|
-
|
|
3
|
-
Simple library to remove signature and replies and extract attachments and inline attachments from an email.
|
|
4
|
-
|
|
5
|
-
At [Deer Designer](https://deerdesigner.com) we were having trouble to receive emails and filter out unnecessary content. After a careful analysis, I noticed machine learning didn't deal well with HTML mixed with attachments and embedded files. Our use case depends on enriched HTML and there weren't any good libs out there that solve this problem. So I decided to create this library.
|
|
6
|
-
|
|
7
|
-
If you stumble upon an email that is not parsed correctly, please create an issue and attach the raw .eml file.
|
|
8
|
-
|
|
9
|
-
Example of a parsed email: [sample](https://github.com/philipedc/lite-email-parser/blob/main/sample/sample.ts)
|
|
10
|
-
|
|
11
|
-
## Import
|
|
12
|
-
|
|
13
|
-
`const { parseEmail } = require('lite-email-parser');`
|
|
14
|
-
|
|
15
|
-
`import { parseEmail } from 'lite-email-parser';`
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
## Usage
|
|
19
|
-
|
|
20
|
-
### No file upload
|
|
21
|
-
|
|
22
|
-
```
|
|
23
|
-
// Example: Simple usage without uploading attachments
|
|
24
|
-
const { parseEmail } = require('lite-email-parser');
|
|
25
|
-
const fs = require('fs');
|
|
26
|
-
|
|
27
|
-
// Load your email (string or Buffer accepted here)
|
|
28
|
-
const email = fs.readFileSync('email.eml');
|
|
29
|
-
|
|
30
|
-
const result = await parseEmail(email);
|
|
31
|
-
|
|
32
|
-
// lastMessage: Pure HTML content with replies/signatures stripped
|
|
33
|
-
console.log(result.lastMessage);
|
|
34
|
-
|
|
35
|
-
// attachments: Array of IFile objects
|
|
36
|
-
console.log(result.attachments);
|
|
37
|
-
|
|
38
|
-
// inlineAttachments: Array of IFile objects embedded in the email
|
|
39
|
-
console.log(result.inlineAttachments);
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
### File upload
|
|
43
|
-
|
|
44
|
-
```
|
|
45
|
-
import { parseEmail, replaceSrc } from 'lite-email-parser';
|
|
46
|
-
import * as fs from 'fs';
|
|
47
|
-
|
|
48
|
-
// Load your email (string or Buffer accepted here)
|
|
49
|
-
const email = fs.readFileSync('email.eml');
|
|
50
|
-
|
|
51
|
-
const result = await parseEmail(email);
|
|
52
|
-
|
|
53
|
-
// lastMessage: Pure HTML content with replies/signatures stripped
|
|
54
|
-
console.log(result.lastMessage);
|
|
55
|
-
|
|
56
|
-
// attachments: Array of IFile objects
|
|
57
|
-
console.log(result.attachments);
|
|
58
|
-
|
|
59
|
-
// inlineAttachments: Array of IFile objects embedded in the email
|
|
60
|
-
console.log(result.inlineAttachments);
|
|
61
|
-
```
|
package/dist/cjs/parser.d.ts
DELETED
package/dist/cjs/parser.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/parser.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAErD,wBAAsB,UAAU,CAC9B,MAAM,EAAE,MAAM,GAAG,MAAM,GACtB,OAAO,CAAC,gBAAgB,CAAC,CAsC3B;AAKD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,MAAM,CAW/D"}
|
package/dist/cjs/parser.js
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.parseEmail = parseEmail;
|
|
37
|
-
exports.replaceSrc = replaceSrc;
|
|
38
|
-
const mailparser_1 = require("mailparser");
|
|
39
|
-
const cheerio = __importStar(require("cheerio"));
|
|
40
|
-
async function parseEmail(buffer) {
|
|
41
|
-
const parsedEmail = await (0, mailparser_1.simpleParser)(buffer);
|
|
42
|
-
let content = parsedEmail.html;
|
|
43
|
-
if (!content) {
|
|
44
|
-
content = parsedEmail.text || '';
|
|
45
|
-
}
|
|
46
|
-
const cleanedContent = extractFirstMessage(content);
|
|
47
|
-
const attachments = [];
|
|
48
|
-
const inlineAttachments = [];
|
|
49
|
-
for (const att of parsedEmail.attachments || []) {
|
|
50
|
-
const filename = att.filename || new Date().toDateString();
|
|
51
|
-
const contentType = att.contentType || 'application/octet-stream';
|
|
52
|
-
const file = {
|
|
53
|
-
name: filename,
|
|
54
|
-
type: contentType,
|
|
55
|
-
size: att.size,
|
|
56
|
-
buffer: att.content,
|
|
57
|
-
};
|
|
58
|
-
if (att.related) {
|
|
59
|
-
const base64Data = att.content.toString('base64');
|
|
60
|
-
file.originalSrc = `data:${contentType};base64,${base64Data}`;
|
|
61
|
-
inlineAttachments.push(file);
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
attachments.push(file);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return {
|
|
68
|
-
lastMessage: cleanedContent,
|
|
69
|
-
attachments,
|
|
70
|
-
inlineAttachments,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
// Replace src attributes with the uploaded file URLs
|
|
74
|
-
function replaceSrc(html, files) {
|
|
75
|
-
const $ = cheerio.load(html);
|
|
76
|
-
for (const file of files) {
|
|
77
|
-
if (file.originalSrc && file.src) {
|
|
78
|
-
$(`img[src="${file.originalSrc}"]`).attr('src', file.src);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return $.html();
|
|
82
|
-
}
|
|
83
|
-
function extractFirstMessage(html) {
|
|
84
|
-
const $ = cheerio.load(html);
|
|
85
|
-
// Remove text dividers (e.g. ----- Original Message -----) and everything after them
|
|
86
|
-
let dividerNode = null;
|
|
87
|
-
let isTextNode = false;
|
|
88
|
-
function findDividerNode(parent) {
|
|
89
|
-
const contents = $(parent).contents();
|
|
90
|
-
for (let i = 0; i < contents.length; i++) {
|
|
91
|
-
const node = contents[i];
|
|
92
|
-
if (node.type === 'text') {
|
|
93
|
-
const text = $(node).text();
|
|
94
|
-
const match = text.match(/-{5,}.*?-{5,}/);
|
|
95
|
-
if (match) {
|
|
96
|
-
const index = text.indexOf(match[0]);
|
|
97
|
-
node.data = text.substring(0, index);
|
|
98
|
-
dividerNode = node;
|
|
99
|
-
isTextNode = true;
|
|
100
|
-
return true;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
else if (node.type === 'tag') {
|
|
104
|
-
if (node.name !== 'style' && node.name !== 'script') {
|
|
105
|
-
const clone = $(node).clone();
|
|
106
|
-
clone.find('style, script').remove();
|
|
107
|
-
if (/-{5,}.*?-{5,}/.test(clone.text())) {
|
|
108
|
-
const foundInChild = findDividerNode(node);
|
|
109
|
-
if (!foundInChild) {
|
|
110
|
-
dividerNode = node;
|
|
111
|
-
isTextNode = false;
|
|
112
|
-
}
|
|
113
|
-
return true;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
findDividerNode($('body'));
|
|
121
|
-
if (dividerNode) {
|
|
122
|
-
let current = $(dividerNode);
|
|
123
|
-
let isFirst = true;
|
|
124
|
-
while (current.length > 0 && !current.is('body')) {
|
|
125
|
-
current.nextAll().remove();
|
|
126
|
-
const parent = current.parent();
|
|
127
|
-
if (isFirst) {
|
|
128
|
-
if (!isTextNode) {
|
|
129
|
-
current.remove();
|
|
130
|
-
}
|
|
131
|
-
isFirst = false;
|
|
132
|
-
}
|
|
133
|
-
current = parent;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
// Gmail signature
|
|
137
|
-
$('span.gmail_signature_prefix').remove();
|
|
138
|
-
$('div.gmail_signature').remove();
|
|
139
|
-
// Gmail quoted replies
|
|
140
|
-
$('.gmail_chip').remove();
|
|
141
|
-
$('.gmail_quote').nextAll().remove();
|
|
142
|
-
$('.gmail_quote').remove();
|
|
143
|
-
// Outlook signature / forwarded messages
|
|
144
|
-
$('#divRplyFwdMsg').nextAll().remove();
|
|
145
|
-
$('#divRplyFwdMsg').remove();
|
|
146
|
-
// Outlook signature marker: <div id="Signature">
|
|
147
|
-
$('div#Signature').remove();
|
|
148
|
-
// Apple Mail signature
|
|
149
|
-
// Apple Mail wraps signatures in <div dir="ltr" class="AppleMailSignature">
|
|
150
|
-
$('div.AppleMailSignature').remove();
|
|
151
|
-
// Apple Mail quoted replies
|
|
152
|
-
// Apple Mail wraps quoted content in <blockquote type="cite">
|
|
153
|
-
$('blockquote[type="cite"]').nextAll().remove();
|
|
154
|
-
$('blockquote[type="cite"]').remove();
|
|
155
|
-
// Fallback for other email providers
|
|
156
|
-
// Matches any div whose id or class contains "signature" (case-insensitive)
|
|
157
|
-
$('div')
|
|
158
|
-
.filter((_, el) => {
|
|
159
|
-
const id = ($(el).attr('id') || '').toLowerCase();
|
|
160
|
-
const cls = ($(el).attr('class') || '').toLowerCase();
|
|
161
|
-
return id.includes('signature') || cls.includes('signature');
|
|
162
|
-
})
|
|
163
|
-
.remove();
|
|
164
|
-
// Fallback for quoted replies from other email providers
|
|
165
|
-
// Matches any div whose id or class contains "quote" or "reply" (case-insensitive)
|
|
166
|
-
$('div')
|
|
167
|
-
.filter((_, el) => {
|
|
168
|
-
const id = ($(el).attr('id') || '').toLowerCase();
|
|
169
|
-
const cls = ($(el).attr('class') || '').toLowerCase();
|
|
170
|
-
return (id.includes('quote') ||
|
|
171
|
-
id.includes('reply') ||
|
|
172
|
-
cls.includes('quote') ||
|
|
173
|
-
cls.includes('reply'));
|
|
174
|
-
})
|
|
175
|
-
.each((_, el) => {
|
|
176
|
-
$(el).nextAll().remove();
|
|
177
|
-
$(el).remove();
|
|
178
|
-
});
|
|
179
|
-
return $('body').html() || '';
|
|
180
|
-
}
|
package/dist/index.cjs
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
var __create = Object.create;
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
-
};
|
|
11
|
-
var __copyProps = (to, from, except, desc) => {
|
|
12
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
-
for (let key of __getOwnPropNames(from))
|
|
14
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
-
}
|
|
17
|
-
return to;
|
|
18
|
-
};
|
|
19
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
-
mod
|
|
26
|
-
));
|
|
27
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
-
|
|
29
|
-
// src/index.ts
|
|
30
|
-
var index_exports = {};
|
|
31
|
-
__export(index_exports, {
|
|
32
|
-
parseEmail: () => parseEmail
|
|
33
|
-
});
|
|
34
|
-
module.exports = __toCommonJS(index_exports);
|
|
35
|
-
|
|
36
|
-
// src/parser.ts
|
|
37
|
-
var import_mailparser = require("mailparser");
|
|
38
|
-
var cheerio = __toESM(require("cheerio"), 1);
|
|
39
|
-
async function parseEmail(buffer, uploader, uploadInlineFilesWithSrc) {
|
|
40
|
-
const parsedEmail = await (0, import_mailparser.simpleParser)(buffer);
|
|
41
|
-
let content = parsedEmail.html;
|
|
42
|
-
if (!content) {
|
|
43
|
-
content = parsedEmail.text || "";
|
|
44
|
-
}
|
|
45
|
-
let cleanedContent = extractFirstMessage(content);
|
|
46
|
-
let attachments = [];
|
|
47
|
-
let inlineAttachments = [];
|
|
48
|
-
const pendingAttachments = await attachmentsToFiles(parsedEmail.attachments);
|
|
49
|
-
const pendingInline = inlineAttachmentsToFiles(cleanedContent);
|
|
50
|
-
if (uploader) {
|
|
51
|
-
attachments = await uploadFiles(pendingAttachments, uploader);
|
|
52
|
-
if (uploadInlineFilesWithSrc) {
|
|
53
|
-
inlineAttachments = await uploadFiles(pendingInline, uploader);
|
|
54
|
-
cleanedContent = replaceSrc(inlineAttachments, cleanedContent);
|
|
55
|
-
} else {
|
|
56
|
-
inlineAttachments = pendingInline;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return {
|
|
60
|
-
attachments,
|
|
61
|
-
inlineAttachments,
|
|
62
|
-
lastMessage: cleanedContent
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
async function attachmentsToFiles(attach) {
|
|
66
|
-
const files = [];
|
|
67
|
-
for (const att of attach) {
|
|
68
|
-
const filename = att.filename || (/* @__PURE__ */ new Date()).toDateString();
|
|
69
|
-
const contentType = att.contentType || "application/octet-stream";
|
|
70
|
-
const attachment = {
|
|
71
|
-
name: filename,
|
|
72
|
-
type: contentType,
|
|
73
|
-
size: att.size,
|
|
74
|
-
buffer: att.content
|
|
75
|
-
};
|
|
76
|
-
files.push(attachment);
|
|
77
|
-
}
|
|
78
|
-
return files;
|
|
79
|
-
}
|
|
80
|
-
function inlineAttachmentsToFiles(content) {
|
|
81
|
-
const files = [];
|
|
82
|
-
const $ = cheerio.load(content);
|
|
83
|
-
const base64Images = $('img[src^="data:"]').toArray();
|
|
84
|
-
for (const el of base64Images) {
|
|
85
|
-
const src = $(el).attr("src");
|
|
86
|
-
if (!src) continue;
|
|
87
|
-
const matches = src.match(/^data:(image\/(\w+));base64,(.*)$/);
|
|
88
|
-
if (matches) {
|
|
89
|
-
const contentType = matches[1];
|
|
90
|
-
const extension = matches[2];
|
|
91
|
-
const base64Data = matches[3];
|
|
92
|
-
const buffer = Buffer.from(base64Data, "base64");
|
|
93
|
-
files.push({
|
|
94
|
-
name: `inline-${(/* @__PURE__ */ new Date()).toISOString()}.${extension}`,
|
|
95
|
-
src,
|
|
96
|
-
// keep the data URI as the src
|
|
97
|
-
type: contentType,
|
|
98
|
-
size: buffer.byteLength,
|
|
99
|
-
buffer,
|
|
100
|
-
originalSrc: src
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return files;
|
|
105
|
-
}
|
|
106
|
-
async function uploadFiles(files, uploader) {
|
|
107
|
-
const failedFiles = [];
|
|
108
|
-
const uploadedFiles = [];
|
|
109
|
-
for (const file of files) {
|
|
110
|
-
try {
|
|
111
|
-
const uploadedFile = await uploader({
|
|
112
|
-
buffer: file.buffer || file.src || "",
|
|
113
|
-
contentType: file.type,
|
|
114
|
-
filename: file.name || "unnamed"
|
|
115
|
-
});
|
|
116
|
-
const { buffer, ...uploadedFileObj } = file;
|
|
117
|
-
uploadedFiles.push({
|
|
118
|
-
...uploadedFileObj,
|
|
119
|
-
src: uploadedFile
|
|
120
|
-
});
|
|
121
|
-
} catch (err) {
|
|
122
|
-
console.error(`Failed to upload file: ${file.name}`, err);
|
|
123
|
-
failedFiles.push(file);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
if (failedFiles.length > 0) {
|
|
127
|
-
throw new Error(`Failed to upload: ${failedFiles.map((f) => f.name).join(", ")}`);
|
|
128
|
-
}
|
|
129
|
-
return uploadedFiles;
|
|
130
|
-
}
|
|
131
|
-
function replaceSrc(inlineAttachments, html) {
|
|
132
|
-
const $ = cheerio.load(html);
|
|
133
|
-
for (const file of inlineAttachments) {
|
|
134
|
-
if (file.originalSrc) {
|
|
135
|
-
$(`img[src="${file.originalSrc}"]`).attr("src", file.src);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
return $.html();
|
|
139
|
-
}
|
|
140
|
-
function extractFirstMessage(html) {
|
|
141
|
-
const $ = cheerio.load(html);
|
|
142
|
-
$("span.gmail_signature_prefix").remove();
|
|
143
|
-
$("div.gmail_signature").remove();
|
|
144
|
-
$(".gmail_chip").remove();
|
|
145
|
-
$(".gmail_quote").nextAll().remove();
|
|
146
|
-
$(".gmail_quote").remove();
|
|
147
|
-
$("#divRplyFwdMsg").nextAll().remove();
|
|
148
|
-
$("#divRplyFwdMsg").remove();
|
|
149
|
-
$("div#Signature").remove();
|
|
150
|
-
$("div.AppleMailSignature").remove();
|
|
151
|
-
$('blockquote[type="cite"]').nextAll().remove();
|
|
152
|
-
$('blockquote[type="cite"]').remove();
|
|
153
|
-
$("div").filter((_, el) => {
|
|
154
|
-
const id = ($(el).attr("id") || "").toLowerCase();
|
|
155
|
-
const cls = ($(el).attr("class") || "").toLowerCase();
|
|
156
|
-
return id.includes("signature") || cls.includes("signature");
|
|
157
|
-
}).remove();
|
|
158
|
-
$("div").filter((_, el) => {
|
|
159
|
-
const id = ($(el).attr("id") || "").toLowerCase();
|
|
160
|
-
const cls = ($(el).attr("class") || "").toLowerCase();
|
|
161
|
-
return id.includes("quote") || id.includes("reply") || cls.includes("quote") || cls.includes("reply");
|
|
162
|
-
}).each((_, el) => {
|
|
163
|
-
$(el).nextAll().remove();
|
|
164
|
-
$(el).remove();
|
|
165
|
-
});
|
|
166
|
-
$("body").find("div, p, font, span, td").filter(function() {
|
|
167
|
-
if ($(this).children().not("br").length > 0) {
|
|
168
|
-
return false;
|
|
169
|
-
}
|
|
170
|
-
const elementText = $(this).text().toLowerCase();
|
|
171
|
-
return [
|
|
172
|
-
"this e-mail and any attachments may contain confidential",
|
|
173
|
-
"sent from"
|
|
174
|
-
].some((text) => elementText.includes(text));
|
|
175
|
-
}).remove();
|
|
176
|
-
$("div").filter((_, el) => {
|
|
177
|
-
if ($(el).closest("li").length > 0) {
|
|
178
|
-
return false;
|
|
179
|
-
}
|
|
180
|
-
if ($(el).find("img").length > 0) {
|
|
181
|
-
return false;
|
|
182
|
-
}
|
|
183
|
-
return $(el).text().trim().length === 0 && $(el).children().not("br").length === 0;
|
|
184
|
-
}).remove();
|
|
185
|
-
return $("body").html() || "";
|
|
186
|
-
}
|
|
187
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
188
|
-
0 && (module.exports = {
|
|
189
|
-
parseEmail
|
|
190
|
-
});
|
package/dist/parser.d.ts
DELETED
package/dist/parser.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAErD,wBAAsB,UAAU,CAC9B,MAAM,EAAE,MAAM,GAAG,MAAM,GACtB,OAAO,CAAC,gBAAgB,CAAC,CAsC3B;AAKD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,MAAM,CAW/D"}
|
package/dist/parser.js
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { simpleParser } from 'mailparser';
|
|
2
|
-
import * as cheerio from 'cheerio';
|
|
3
|
-
export async function parseEmail(buffer) {
|
|
4
|
-
const parsedEmail = await simpleParser(buffer);
|
|
5
|
-
let content = parsedEmail.html;
|
|
6
|
-
if (!content) {
|
|
7
|
-
content = parsedEmail.text || '';
|
|
8
|
-
}
|
|
9
|
-
const cleanedContent = extractFirstMessage(content);
|
|
10
|
-
const attachments = [];
|
|
11
|
-
const inlineAttachments = [];
|
|
12
|
-
for (const att of parsedEmail.attachments || []) {
|
|
13
|
-
const filename = att.filename || new Date().toDateString();
|
|
14
|
-
const contentType = att.contentType || 'application/octet-stream';
|
|
15
|
-
const file = {
|
|
16
|
-
name: filename,
|
|
17
|
-
type: contentType,
|
|
18
|
-
size: att.size,
|
|
19
|
-
buffer: att.content,
|
|
20
|
-
};
|
|
21
|
-
if (att.related) {
|
|
22
|
-
const base64Data = att.content.toString('base64');
|
|
23
|
-
file.originalSrc = `data:${contentType};base64,${base64Data}`;
|
|
24
|
-
inlineAttachments.push(file);
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
attachments.push(file);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
return {
|
|
31
|
-
lastMessage: cleanedContent,
|
|
32
|
-
attachments,
|
|
33
|
-
inlineAttachments,
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
// Replace src attributes with the uploaded file URLs
|
|
37
|
-
export function replaceSrc(html, files) {
|
|
38
|
-
const $ = cheerio.load(html);
|
|
39
|
-
for (const file of files) {
|
|
40
|
-
if (file.originalSrc && file.src) {
|
|
41
|
-
$(`img[src="${file.originalSrc}"]`).attr('src', file.src);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return $.html();
|
|
45
|
-
}
|
|
46
|
-
function extractFirstMessage(html) {
|
|
47
|
-
const $ = cheerio.load(html);
|
|
48
|
-
// Remove text dividers (e.g. ----- Original Message -----) and everything after them
|
|
49
|
-
let dividerNode = null;
|
|
50
|
-
let isTextNode = false;
|
|
51
|
-
function findDividerNode(parent) {
|
|
52
|
-
const contents = $(parent).contents();
|
|
53
|
-
for (let i = 0; i < contents.length; i++) {
|
|
54
|
-
const node = contents[i];
|
|
55
|
-
if (node.type === 'text') {
|
|
56
|
-
const text = $(node).text();
|
|
57
|
-
const match = text.match(/-{5,}.*?-{5,}/);
|
|
58
|
-
if (match) {
|
|
59
|
-
const index = text.indexOf(match[0]);
|
|
60
|
-
node.data = text.substring(0, index);
|
|
61
|
-
dividerNode = node;
|
|
62
|
-
isTextNode = true;
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
else if (node.type === 'tag') {
|
|
67
|
-
if (node.name !== 'style' && node.name !== 'script') {
|
|
68
|
-
const clone = $(node).clone();
|
|
69
|
-
clone.find('style, script').remove();
|
|
70
|
-
if (/-{5,}.*?-{5,}/.test(clone.text())) {
|
|
71
|
-
const foundInChild = findDividerNode(node);
|
|
72
|
-
if (!foundInChild) {
|
|
73
|
-
dividerNode = node;
|
|
74
|
-
isTextNode = false;
|
|
75
|
-
}
|
|
76
|
-
return true;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
findDividerNode($('body'));
|
|
84
|
-
if (dividerNode) {
|
|
85
|
-
let current = $(dividerNode);
|
|
86
|
-
let isFirst = true;
|
|
87
|
-
while (current.length > 0 && !current.is('body')) {
|
|
88
|
-
current.nextAll().remove();
|
|
89
|
-
const parent = current.parent();
|
|
90
|
-
if (isFirst) {
|
|
91
|
-
if (!isTextNode) {
|
|
92
|
-
current.remove();
|
|
93
|
-
}
|
|
94
|
-
isFirst = false;
|
|
95
|
-
}
|
|
96
|
-
current = parent;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
// Gmail signature
|
|
100
|
-
$('span.gmail_signature_prefix').remove();
|
|
101
|
-
$('div.gmail_signature').remove();
|
|
102
|
-
// Gmail quoted replies
|
|
103
|
-
$('.gmail_chip').remove();
|
|
104
|
-
$('.gmail_quote').nextAll().remove();
|
|
105
|
-
$('.gmail_quote').remove();
|
|
106
|
-
// Outlook signature / forwarded messages
|
|
107
|
-
$('#divRplyFwdMsg').nextAll().remove();
|
|
108
|
-
$('#divRplyFwdMsg').remove();
|
|
109
|
-
// Outlook signature marker: <div id="Signature">
|
|
110
|
-
$('div#Signature').remove();
|
|
111
|
-
// Apple Mail signature
|
|
112
|
-
// Apple Mail wraps signatures in <div dir="ltr" class="AppleMailSignature">
|
|
113
|
-
$('div.AppleMailSignature').remove();
|
|
114
|
-
// Apple Mail quoted replies
|
|
115
|
-
// Apple Mail wraps quoted content in <blockquote type="cite">
|
|
116
|
-
$('blockquote[type="cite"]').nextAll().remove();
|
|
117
|
-
$('blockquote[type="cite"]').remove();
|
|
118
|
-
// Fallback for other email providers
|
|
119
|
-
// Matches any div whose id or class contains "signature" (case-insensitive)
|
|
120
|
-
$('div')
|
|
121
|
-
.filter((_, el) => {
|
|
122
|
-
const id = ($(el).attr('id') || '').toLowerCase();
|
|
123
|
-
const cls = ($(el).attr('class') || '').toLowerCase();
|
|
124
|
-
return id.includes('signature') || cls.includes('signature');
|
|
125
|
-
})
|
|
126
|
-
.remove();
|
|
127
|
-
// Fallback for quoted replies from other email providers
|
|
128
|
-
// Matches any div whose id or class contains "quote" or "reply" (case-insensitive)
|
|
129
|
-
$('div')
|
|
130
|
-
.filter((_, el) => {
|
|
131
|
-
const id = ($(el).attr('id') || '').toLowerCase();
|
|
132
|
-
const cls = ($(el).attr('class') || '').toLowerCase();
|
|
133
|
-
return (id.includes('quote') ||
|
|
134
|
-
id.includes('reply') ||
|
|
135
|
-
cls.includes('quote') ||
|
|
136
|
-
cls.includes('reply'));
|
|
137
|
-
})
|
|
138
|
-
.each((_, el) => {
|
|
139
|
-
$(el).nextAll().remove();
|
|
140
|
-
$(el).remove();
|
|
141
|
-
});
|
|
142
|
-
return $('body').html() || '';
|
|
143
|
-
}
|
package/dist/src/index.d.ts
DELETED