@realtimex/email-automator 2.8.5 → 2.9.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/api/src/config/index.ts +4 -2
- package/api/src/services/processor.ts +7 -1
- package/api/src/utils/filename.ts +55 -0
- package/dist/api/src/config/index.js +3 -1
- package/dist/api/src/services/processor.js +7 -1
- package/dist/api/src/utils/filename.js +38 -0
- package/dist/assets/{index-CFg67qjH.js → index-fpSQFV7a.js} +20 -20
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/supabase/migrations/20260119000003_add_intelligent_rename.sql +5 -0
package/api/src/config/index.ts
CHANGED
|
@@ -39,7 +39,7 @@ function loadEnvironment() {
|
|
|
39
39
|
|
|
40
40
|
loadEnvironment();
|
|
41
41
|
|
|
42
|
-
function parseArgs(args: string[]): { port: number | null, noUi: boolean } {
|
|
42
|
+
function parseArgs(args: string[]): { port: number | null, noUi: boolean, rename: boolean } {
|
|
43
43
|
const portIndex = args.indexOf('--port');
|
|
44
44
|
let port = null;
|
|
45
45
|
if (portIndex !== -1 && args[portIndex + 1]) {
|
|
@@ -50,8 +50,9 @@ function parseArgs(args: string[]): { port: number | null, noUi: boolean } {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
const noUi = args.includes('--no-ui');
|
|
53
|
+
const rename = args.includes('--rename');
|
|
53
54
|
|
|
54
|
-
return { port, noUi };
|
|
55
|
+
return { port, noUi, rename };
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
const cliArgs = parseArgs(process.argv.slice(2));
|
|
@@ -62,6 +63,7 @@ export const config = {
|
|
|
62
63
|
// Default port 3004 (RealTimeX Desktop uses 3001/3002)
|
|
63
64
|
port: cliArgs.port || (process.env.PORT ? parseInt(process.env.PORT, 10) : 3004),
|
|
64
65
|
noUi: cliArgs.noUi,
|
|
66
|
+
intelligentRename: cliArgs.rename || process.env.INTELLIGENT_RENAME === 'true',
|
|
65
67
|
nodeEnv: process.env.NODE_ENV || 'development',
|
|
66
68
|
isProduction: process.env.NODE_ENV === 'production',
|
|
67
69
|
|
|
@@ -6,6 +6,7 @@ import { getGmailService, GmailMessage } from './gmail.js';
|
|
|
6
6
|
import { getMicrosoftService, OutlookMessage } from './microsoft.js';
|
|
7
7
|
import { getIntelligenceService, EmailAnalysis, ContextAwareAnalysis, RuleContext } from './intelligence.js';
|
|
8
8
|
import { getStorageService } from './storage.js';
|
|
9
|
+
import { generateEmailFilename } from '../utils/filename.js';
|
|
9
10
|
import { EmailAccount, Email, Rule, ProcessingLog } from './supabase.js';
|
|
10
11
|
import { EventLogger } from './eventLogger.js';
|
|
11
12
|
|
|
@@ -434,7 +435,12 @@ export class EmailProcessorService {
|
|
|
434
435
|
// 2. Save raw content to local storage (.eml format)
|
|
435
436
|
let filePath = '';
|
|
436
437
|
try {
|
|
437
|
-
const filename =
|
|
438
|
+
const filename = generateEmailFilename({
|
|
439
|
+
subject,
|
|
440
|
+
date: parsed.date || new Date(),
|
|
441
|
+
externalId: message.id,
|
|
442
|
+
intelligentRename: settings?.intelligent_rename || config.intelligentRename
|
|
443
|
+
});
|
|
438
444
|
filePath = await this.storageService.saveEmail(rawMime, filename, settings?.storage_path);
|
|
439
445
|
} catch (storageError) {
|
|
440
446
|
logger.error('Failed to save raw email content', storageError);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility for generating standardized and human-readable filenames for archived emails.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
interface FilenameOptions {
|
|
6
|
+
subject: string;
|
|
7
|
+
date: Date;
|
|
8
|
+
externalId: string;
|
|
9
|
+
intelligentRename?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function generateEmailFilename({
|
|
13
|
+
subject,
|
|
14
|
+
date,
|
|
15
|
+
externalId,
|
|
16
|
+
intelligentRename = false
|
|
17
|
+
}: FilenameOptions): string {
|
|
18
|
+
// 1. Format Timestamp (YYYYMMDD_HHMM or YYYYMMDD-HHMM)
|
|
19
|
+
const pad = (n: number) => n.toString().padStart(2, '0');
|
|
20
|
+
const yyyy = date.getFullYear();
|
|
21
|
+
const mm = pad(date.getMonth() + 1);
|
|
22
|
+
const dd = pad(date.getDate());
|
|
23
|
+
const hh = pad(date.getHours());
|
|
24
|
+
const min = pad(date.getMinutes());
|
|
25
|
+
|
|
26
|
+
const timestamp = intelligentRename
|
|
27
|
+
? `${yyyy}${mm}${dd}-${hh}${min}`
|
|
28
|
+
: `${yyyy}${mm}${dd}_${hh}${min}`;
|
|
29
|
+
|
|
30
|
+
// 2. Get Internal ID (Last 8 characters of provider's message ID)
|
|
31
|
+
const internalId = externalId.slice(-8);
|
|
32
|
+
|
|
33
|
+
if (intelligentRename) {
|
|
34
|
+
// Intelligent Rename (Slugified)
|
|
35
|
+
// subject converted to lowercase, non-alphanumeric to hyphens
|
|
36
|
+
const slug = subject
|
|
37
|
+
.toLowerCase()
|
|
38
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
39
|
+
.replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens
|
|
40
|
+
.substring(0, 100);
|
|
41
|
+
|
|
42
|
+
return `${timestamp}-${slug || 'no-subject'}-${internalId}.eml`;
|
|
43
|
+
} else {
|
|
44
|
+
// Default Naming Convention
|
|
45
|
+
// Illegal characters replaced with underscores, non-printable removed
|
|
46
|
+
const sanitized = subject
|
|
47
|
+
.replace(/[/\\*?:\"<>|]/g, '_') // Illegal chars
|
|
48
|
+
.replace(/[\x00-\x1F\x7F]/g, '') // Non-printable
|
|
49
|
+
.replace(/_+/g, '_') // Collapse multiple underscores
|
|
50
|
+
.replace(/^_|_$/g, '') // Remove leading/trailing underscores
|
|
51
|
+
.substring(0, 100);
|
|
52
|
+
|
|
53
|
+
return `${timestamp}_${sanitized || 'No_Subject'}_${internalId}.eml`;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -44,7 +44,8 @@ function parseArgs(args) {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
const noUi = args.includes('--no-ui');
|
|
47
|
-
|
|
47
|
+
const rename = args.includes('--rename');
|
|
48
|
+
return { port, noUi, rename };
|
|
48
49
|
}
|
|
49
50
|
const cliArgs = parseArgs(process.argv.slice(2));
|
|
50
51
|
export const config = {
|
|
@@ -53,6 +54,7 @@ export const config = {
|
|
|
53
54
|
// Default port 3004 (RealTimeX Desktop uses 3001/3002)
|
|
54
55
|
port: cliArgs.port || (process.env.PORT ? parseInt(process.env.PORT, 10) : 3004),
|
|
55
56
|
noUi: cliArgs.noUi,
|
|
57
|
+
intelligentRename: cliArgs.rename || process.env.INTELLIGENT_RENAME === 'true',
|
|
56
58
|
nodeEnv: process.env.NODE_ENV || 'development',
|
|
57
59
|
isProduction: process.env.NODE_ENV === 'production',
|
|
58
60
|
// Paths - Robust resolution for both TS source and compiled JS in dist/
|
|
@@ -5,6 +5,7 @@ import { getGmailService } from './gmail.js';
|
|
|
5
5
|
import { getMicrosoftService } from './microsoft.js';
|
|
6
6
|
import { getIntelligenceService } from './intelligence.js';
|
|
7
7
|
import { getStorageService } from './storage.js';
|
|
8
|
+
import { generateEmailFilename } from '../utils/filename.js';
|
|
8
9
|
import { EventLogger } from './eventLogger.js';
|
|
9
10
|
const logger = createLogger('Processor');
|
|
10
11
|
export class EmailProcessorService {
|
|
@@ -352,7 +353,12 @@ export class EmailProcessorService {
|
|
|
352
353
|
// 2. Save raw content to local storage (.eml format)
|
|
353
354
|
let filePath = '';
|
|
354
355
|
try {
|
|
355
|
-
const filename =
|
|
356
|
+
const filename = generateEmailFilename({
|
|
357
|
+
subject,
|
|
358
|
+
date: parsed.date || new Date(),
|
|
359
|
+
externalId: message.id,
|
|
360
|
+
intelligentRename: settings?.intelligent_rename || config.intelligentRename
|
|
361
|
+
});
|
|
356
362
|
filePath = await this.storageService.saveEmail(rawMime, filename, settings?.storage_path);
|
|
357
363
|
}
|
|
358
364
|
catch (storageError) {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility for generating standardized and human-readable filenames for archived emails.
|
|
3
|
+
*/
|
|
4
|
+
export function generateEmailFilename({ subject, date, externalId, intelligentRename = false }) {
|
|
5
|
+
// 1. Format Timestamp (YYYYMMDD_HHMM or YYYYMMDD-HHMM)
|
|
6
|
+
const pad = (n) => n.toString().padStart(2, '0');
|
|
7
|
+
const yyyy = date.getFullYear();
|
|
8
|
+
const mm = pad(date.getMonth() + 1);
|
|
9
|
+
const dd = pad(date.getDate());
|
|
10
|
+
const hh = pad(date.getHours());
|
|
11
|
+
const min = pad(date.getMinutes());
|
|
12
|
+
const timestamp = intelligentRename
|
|
13
|
+
? `${yyyy}${mm}${dd}-${hh}${min}`
|
|
14
|
+
: `${yyyy}${mm}${dd}_${hh}${min}`;
|
|
15
|
+
// 2. Get Internal ID (Last 8 characters of provider's message ID)
|
|
16
|
+
const internalId = externalId.slice(-8);
|
|
17
|
+
if (intelligentRename) {
|
|
18
|
+
// Intelligent Rename (Slugified)
|
|
19
|
+
// subject converted to lowercase, non-alphanumeric to hyphens
|
|
20
|
+
const slug = subject
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
23
|
+
.replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens
|
|
24
|
+
.substring(0, 100);
|
|
25
|
+
return `${timestamp}-${slug || 'no-subject'}-${internalId}.eml`;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
// Default Naming Convention
|
|
29
|
+
// Illegal characters replaced with underscores, non-printable removed
|
|
30
|
+
const sanitized = subject
|
|
31
|
+
.replace(/[/\\*?:\"<>|]/g, '_') // Illegal chars
|
|
32
|
+
.replace(/[\x00-\x1F\x7F]/g, '') // Non-printable
|
|
33
|
+
.replace(/_+/g, '_') // Collapse multiple underscores
|
|
34
|
+
.replace(/^_|_$/g, '') // Remove leading/trailing underscores
|
|
35
|
+
.substring(0, 100);
|
|
36
|
+
return `${timestamp}_${sanitized || 'No_Subject'}_${internalId}.eml`;
|
|
37
|
+
}
|
|
38
|
+
}
|