email-origin-chain 1.0.0 → 1.0.1

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.
@@ -27,14 +27,22 @@ async function processInline(text, depth, baseHistory = [], customDetectors = []
27
27
  flags: ['level:root', 'trust:medium_inline']
28
28
  });
29
29
  }
30
- // Detection loop: This allows combining the library (CrispDetector)
31
- // with custom local detectors (OutlookFRDetector, etc.)
30
+ // Detection loop
32
31
  while (currentDepth < maxRecursiveDepth) {
33
32
  const result = registry.detect(currentText);
34
33
  if (!result.found || !result.email) {
35
34
  // No more forwards detected
36
35
  const lastIdx = history.length - 1;
37
36
  history[lastIdx].text = (0, utils_1.cleanText)(currentText);
37
+ // Enrich with inline attachments if not already present
38
+ const inlineAtts = (0, utils_1.extractInlineAttachments)(history[lastIdx].text);
39
+ if (inlineAtts.length > 0) {
40
+ const existing = history[lastIdx].attachments || [];
41
+ const toAdd = inlineAtts.filter(a => !existing.find(e => e.filename === a.filename));
42
+ if (toAdd.length > 0) {
43
+ history[lastIdx].attachments = [...existing, ...toAdd];
44
+ }
45
+ }
38
46
  break;
39
47
  }
40
48
  const email = result.email;
@@ -44,6 +52,15 @@ async function processInline(text, depth, baseHistory = [], customDetectors = []
44
52
  if (!history[previousIdx].text && !history[previousIdx].flags.includes('content:silent_forward')) {
45
53
  history[previousIdx].flags.push('content:silent_forward');
46
54
  }
55
+ // Enrich previous level with inline attachments
56
+ const prevInlineAtts = (0, utils_1.extractInlineAttachments)(history[previousIdx].text);
57
+ if (prevInlineAtts.length > 0) {
58
+ const existing = history[previousIdx].attachments || [];
59
+ const toAdd = prevInlineAtts.filter(a => !existing.find(e => e.filename === a.filename));
60
+ if (toAdd.length > 0) {
61
+ history[previousIdx].attachments = [...existing, ...toAdd];
62
+ }
63
+ }
47
64
  // Build flags
48
65
  const flags = [`method:${result.detector || 'unknown'}`, 'trust:medium_inline'];
49
66
  if (!email.body || email.body.trim() === '') {
@@ -55,20 +72,22 @@ async function processInline(text, depth, baseHistory = [], customDetectors = []
55
72
  warnings.push(`Could not normalize date: "${email.date}"`);
56
73
  flags.push('date:unparseable');
57
74
  }
58
- // Normalize from address (fix patterns like "email [email]")
75
+ // Normalize from address
59
76
  let fromNormalized = typeof email.from === 'object'
60
77
  ? { name: email.from.name, address: email.from.address }
61
78
  : (email.from ? { address: email.from } : null);
62
79
  fromNormalized = (0, utils_1.normalizeFrom)(fromNormalized);
63
80
  // Add this forward level to history
81
+ const cleanedBody = (0, utils_1.cleanText)(email.body || '');
64
82
  history.push({
65
83
  from: fromNormalized,
66
84
  subject: email.subject || null,
67
85
  date_raw: email.date || null,
68
86
  date_iso: dateIso,
69
- text: (0, utils_1.cleanText)(email.body || ''),
87
+ text: cleanedBody,
70
88
  depth: currentDepth + 1,
71
- flags: flags
89
+ flags: flags,
90
+ attachments: (0, utils_1.extractInlineAttachments)(cleanedBody)
72
91
  });
73
92
  // Continue with the body for next iteration
74
93
  currentText = (email.body || '').trim();
@@ -28,7 +28,12 @@ async function processMime(raw, options) {
28
28
  date_iso: parsed.date ? parsed.date.toISOString() : null,
29
29
  text: parsed.text || null, // Will be "exclusive" text once we know if there’s a forward inside
30
30
  depth,
31
- flags: ['trust:high_mime']
31
+ flags: ['trust:high_mime'],
32
+ attachments: parsed.attachments.map(att => ({
33
+ filename: att.filename,
34
+ contentType: att.contentType || 'application/octet-stream',
35
+ size: att.size || 0
36
+ }))
32
37
  });
33
38
  // Check for attached messages
34
39
  const rfcParts = parsed.attachments.filter(a => a.contentType === 'message/rfc822');
package/dist/types.d.ts CHANGED
@@ -24,6 +24,7 @@ export interface HistoryEntry {
24
24
  text: string | null;
25
25
  depth: number;
26
26
  flags: string[];
27
+ attachments?: Attachment[];
27
28
  }
28
29
  export interface ResultObject {
29
30
  from: EmailAddress | null;
package/dist/utils.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { ResultObject, EmailAddress } from './types';
2
2
  export declare function normalizeDateToISO(dateRaw: string | Date | null | undefined): string | null;
3
3
  export declare function cleanText(text: string | null | undefined): string | null;
4
+ export declare function extractInlineAttachments(text: string | null | undefined): import('./types').Attachment[];
4
5
  /**
5
6
  * Normalizes EmailAddress to fix edge cases like "email [email]" pattern
6
7
  *
package/dist/utils.js CHANGED
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.normalizeDateToISO = normalizeDateToISO;
37
37
  exports.cleanText = cleanText;
38
+ exports.extractInlineAttachments = extractInlineAttachments;
38
39
  exports.normalizeFrom = normalizeFrom;
39
40
  exports.normalizeParserResult = normalizeParserResult;
40
41
  const anyDateParser = __importStar(require("any-date-parser"));
@@ -109,6 +110,45 @@ function cleanText(text) {
109
110
  .replace(/[ \t]+$/gm, '') // trim end of lines
110
111
  .trim();
111
112
  }
113
+ function extractInlineAttachments(text) {
114
+ if (typeof text !== 'string')
115
+ return [];
116
+ const attachments = [];
117
+ const attachmentRegex = /<([-a-zA-Z0-9._ ]+\.([a-zA-Z0-9]+))>/g;
118
+ let match;
119
+ const extensionMap = {
120
+ 'pdf': 'application/pdf',
121
+ 'jpg': 'image/jpeg',
122
+ 'jpeg': 'image/jpeg',
123
+ 'png': 'image/png',
124
+ 'gif': 'image/gif',
125
+ 'zip': 'application/zip',
126
+ 'rar': 'application/x-rar-compressed',
127
+ '7z': 'application/x-7z-compressed',
128
+ 'doc': 'application/msword',
129
+ 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
130
+ 'xls': 'application/vnd.ms-excel',
131
+ 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
132
+ 'txt': 'text/plain',
133
+ 'csv': 'text/csv',
134
+ 'html': 'text/html',
135
+ 'xml': 'application/xml',
136
+ 'json': 'application/json'
137
+ };
138
+ while ((match = attachmentRegex.exec(text)) !== null) {
139
+ const filename = match[1];
140
+ const ext = match[2]?.toLowerCase();
141
+ // Prevent duplicate filenames in the same node
142
+ if (!attachments.find(a => a.filename === filename)) {
143
+ attachments.push({
144
+ filename: filename,
145
+ contentType: extensionMap[ext] || 'application/octet-stream',
146
+ size: 0
147
+ });
148
+ }
149
+ }
150
+ return attachments;
151
+ }
112
152
  /**
113
153
  * Normalizes EmailAddress to fix edge cases like "email [email]" pattern
114
154
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "email-origin-chain",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Uncover the full audit trail of your email threads. Recursively reconstructs the entire conversation history with instant access to the original sender and true source message.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -45,4 +45,4 @@
45
45
  "email-forward-parser": "^1.7.2",
46
46
  "mailparser": "^3.9.1"
47
47
  }
48
- }
48
+ }