@xuda.io/ai_module 1.1.4681 → 1.1.4682
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/index.mjs +790 -0
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -10359,3 +10359,793 @@ PROVIDE DETAILED ANALYSIS WITH CONFIDENCE LEVELS.`,
|
|
|
10359
10359
|
return getDefaultEmailAnalysis(name, email, email_context, error.message);
|
|
10360
10360
|
}
|
|
10361
10361
|
};
|
|
10362
|
+
|
|
10363
|
+
export const detect_email_type = async function (uid, name, email, account_profile_info, email_body = '', email_subject = '') {
|
|
10364
|
+
// ========== HELPER FUNCTIONS ==========
|
|
10365
|
+
|
|
10366
|
+
async function hashString(str) {
|
|
10367
|
+
const encoder = new TextEncoder();
|
|
10368
|
+
const data = encoder.encode(str);
|
|
10369
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
10370
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
10371
|
+
return hashArray
|
|
10372
|
+
.map((b) => b.toString(16).padStart(2, '0'))
|
|
10373
|
+
.join('')
|
|
10374
|
+
.substring(0, 16);
|
|
10375
|
+
}
|
|
10376
|
+
|
|
10377
|
+
function enhanceEmailDetection(data, name, email, emailBody, emailSubject) {
|
|
10378
|
+
// Run additional local analysis
|
|
10379
|
+
const localAnalysis = analyzeEmailLocally(emailBody, emailSubject, email, name);
|
|
10380
|
+
|
|
10381
|
+
// Combine AI analysis with local analysis
|
|
10382
|
+
const combinedData = {
|
|
10383
|
+
...data,
|
|
10384
|
+
// Override with local analysis if confidence is high
|
|
10385
|
+
spam_confidence: Math.max(data.spam_confidence, localAnalysis.spamScore),
|
|
10386
|
+
phishing_risk: localAnalysis.phishingScore > 70 ? 'high' : data.phishing_risk,
|
|
10387
|
+
|
|
10388
|
+
// Add local analysis results
|
|
10389
|
+
local_analysis: localAnalysis,
|
|
10390
|
+
|
|
10391
|
+
// Enhance sender analysis
|
|
10392
|
+
sender_analysis: analyzeSender(email, name, emailBody),
|
|
10393
|
+
|
|
10394
|
+
// Content statistics
|
|
10395
|
+
content_stats: {
|
|
10396
|
+
word_count: (emailBody.match(/\S+/g) || []).length,
|
|
10397
|
+
sentence_count: (emailBody.match(/[.!?]+/g) || []).length,
|
|
10398
|
+
link_count: (emailBody.match(/https?:\/\/[^\s]+/g) || []).length,
|
|
10399
|
+
uppercase_ratio: calculateUppercaseRatio(emailBody),
|
|
10400
|
+
exclamation_count: (emailBody.match(/!/g) || []).length,
|
|
10401
|
+
dollar_sign_count: (emailBody.match(/\$/g) || []).length,
|
|
10402
|
+
phone_patterns: (emailBody.match(/\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g) || []).length,
|
|
10403
|
+
},
|
|
10404
|
+
|
|
10405
|
+
// Behavioral patterns
|
|
10406
|
+
behavioral_patterns: detectBehavioralPatterns(emailBody, emailSubject),
|
|
10407
|
+
|
|
10408
|
+
// Domain reputation
|
|
10409
|
+
domain_reputation: assessDomainReputation(email),
|
|
10410
|
+
|
|
10411
|
+
// Add comprehensive risk assessment
|
|
10412
|
+
comprehensive_risk_score: calculateComprehensiveRisk(data, localAnalysis),
|
|
10413
|
+
|
|
10414
|
+
// Filter rules that matched
|
|
10415
|
+
matched_filters: detectMatchedFilters(emailBody, emailSubject, email, name),
|
|
10416
|
+
|
|
10417
|
+
// Timeline analysis
|
|
10418
|
+
temporal_analysis: {
|
|
10419
|
+
is_time_sensitive: detectTimeSensitivity(emailBody),
|
|
10420
|
+
has_expiry: detectExpiryDate(emailBody),
|
|
10421
|
+
is_follow_up: detectFollowUpPattern(emailBody, emailSubject),
|
|
10422
|
+
},
|
|
10423
|
+
|
|
10424
|
+
// Relationship context
|
|
10425
|
+
relationship_context: analyzeRelationshipContext(emailBody, emailSubject, name),
|
|
10426
|
+
|
|
10427
|
+
// Update timestamp
|
|
10428
|
+
analysis_timestamp: new Date().toISOString(),
|
|
10429
|
+
content_length: emailBody.length,
|
|
10430
|
+
};
|
|
10431
|
+
|
|
10432
|
+
// Calculate final classification confidence
|
|
10433
|
+
combinedData.final_confidence = calculateFinalConfidence(combinedData);
|
|
10434
|
+
|
|
10435
|
+
// Generate detailed explanation
|
|
10436
|
+
combinedData.explanation = generateExplanation(combinedData);
|
|
10437
|
+
|
|
10438
|
+
// Add compliance flags
|
|
10439
|
+
combinedData.compliance_flags = checkCompliance(emailBody, emailSubject);
|
|
10440
|
+
|
|
10441
|
+
return combinedData;
|
|
10442
|
+
}
|
|
10443
|
+
|
|
10444
|
+
function analyzeEmailLocally(body, subject, email, name) {
|
|
10445
|
+
const text = (subject + ' ' + body).toLowerCase();
|
|
10446
|
+
let spamScore = 0;
|
|
10447
|
+
let phishingScore = 0;
|
|
10448
|
+
let promotionalScore = 0;
|
|
10449
|
+
let transactionalScore = 0;
|
|
10450
|
+
|
|
10451
|
+
// SPAM indicators
|
|
10452
|
+
const spamKeywords = [
|
|
10453
|
+
'congratulations',
|
|
10454
|
+
'winner',
|
|
10455
|
+
'prize',
|
|
10456
|
+
'lottery',
|
|
10457
|
+
'free',
|
|
10458
|
+
'guaranteed',
|
|
10459
|
+
'risk-free',
|
|
10460
|
+
'act now',
|
|
10461
|
+
'limited time',
|
|
10462
|
+
'urgent',
|
|
10463
|
+
'important',
|
|
10464
|
+
'attention',
|
|
10465
|
+
'alert',
|
|
10466
|
+
'click here',
|
|
10467
|
+
'buy now',
|
|
10468
|
+
'order now',
|
|
10469
|
+
'discount',
|
|
10470
|
+
'save big',
|
|
10471
|
+
'cheap',
|
|
10472
|
+
'viagra',
|
|
10473
|
+
'cialis',
|
|
10474
|
+
'pharmacy',
|
|
10475
|
+
'prescription',
|
|
10476
|
+
'enlarge',
|
|
10477
|
+
'weight loss',
|
|
10478
|
+
'nigerian',
|
|
10479
|
+
'prince',
|
|
10480
|
+
'inheritance',
|
|
10481
|
+
'unclaimed',
|
|
10482
|
+
'bank account',
|
|
10483
|
+
'password',
|
|
10484
|
+
'account suspended',
|
|
10485
|
+
'verify',
|
|
10486
|
+
'security alert',
|
|
10487
|
+
'dear friend',
|
|
10488
|
+
'dear customer',
|
|
10489
|
+
'dear account holder',
|
|
10490
|
+
];
|
|
10491
|
+
|
|
10492
|
+
spamKeywords.forEach((keyword) => {
|
|
10493
|
+
if (text.includes(keyword)) spamScore += 2;
|
|
10494
|
+
});
|
|
10495
|
+
|
|
10496
|
+
// Phishing indicators
|
|
10497
|
+
const phishingPatterns = [/login\s*(?:here|now)/i, /verify\s*(?:your|my)\s*account/i, /password\s*(?:reset|expired|change)/i, /suspended\s*account/i, /unauthorized\s*activity/i, /security\s*breach/i, /update\s*(?:your|my)\s*information/i, /confirm\s*(?:your|my)\s*identity/i];
|
|
10498
|
+
|
|
10499
|
+
phishingPatterns.forEach((pattern) => {
|
|
10500
|
+
if (pattern.test(text)) phishingScore += 15;
|
|
10501
|
+
});
|
|
10502
|
+
|
|
10503
|
+
// Promotional indicators
|
|
10504
|
+
const promotionalKeywords = ['newsletter', 'subscribe', 'unsubscribe', 'marketing', 'promotion', 'sale', 'offer', 'deal', 'coupon', 'voucher', 'discount code', 'new product', 'announcement', 'launch', 'event', 'webinar', 'limited offer', 'exclusive', 'special offer', 'members only'];
|
|
10505
|
+
|
|
10506
|
+
promotionalKeywords.forEach((keyword) => {
|
|
10507
|
+
if (text.includes(keyword)) promotionalScore += 3;
|
|
10508
|
+
});
|
|
10509
|
+
|
|
10510
|
+
// Transactional indicators
|
|
10511
|
+
const transactionalKeywords = ['order', 'invoice', 'receipt', 'payment', 'booking', 'reservation', 'confirmation', 'shipping', 'delivery', 'tracking', 'shipment', 'appointment', 'meeting', 'reminder', 'bill', 'statement', 'account', 'statement', 'balance', 'transaction'];
|
|
10512
|
+
|
|
10513
|
+
transactionalKeywords.forEach((keyword) => {
|
|
10514
|
+
if (text.includes(keyword)) transactionalScore += 3;
|
|
10515
|
+
});
|
|
10516
|
+
|
|
10517
|
+
// Link analysis
|
|
10518
|
+
const links = body.match(/https?:\/\/[^\s]+/g) || [];
|
|
10519
|
+
const suspiciousDomains = ['bit.ly', 'tinyurl', 'shorte.st', 'adf.ly', 'goo.gl'];
|
|
10520
|
+
|
|
10521
|
+
links.forEach((link) => {
|
|
10522
|
+
if (suspiciousDomains.some((domain) => link.includes(domain))) {
|
|
10523
|
+
spamScore += 10;
|
|
10524
|
+
phishingScore += 10;
|
|
10525
|
+
}
|
|
10526
|
+
});
|
|
10527
|
+
|
|
10528
|
+
// Grammar/spelling analysis (simple)
|
|
10529
|
+
const misspellings = body.match(/\b(?:recieve|seperate|definately|occured|tomm?orrow)\b/gi) || [];
|
|
10530
|
+
spamScore += misspellings.length * 2;
|
|
10531
|
+
|
|
10532
|
+
// Urgency analysis
|
|
10533
|
+
const urgencyWords = ['urgent', 'immediately', 'asap', 'right now', 'today only'];
|
|
10534
|
+
let urgencyCount = 0;
|
|
10535
|
+
urgencyWords.forEach((word) => {
|
|
10536
|
+
if (text.includes(word)) urgencyCount++;
|
|
10537
|
+
});
|
|
10538
|
+
spamScore += urgencyCount * 5;
|
|
10539
|
+
|
|
10540
|
+
// Personalization check
|
|
10541
|
+
const hasName = name && body.toLowerCase().includes(name.toLowerCase().split(' ')[0]);
|
|
10542
|
+
const personalPronouns = ['you', 'your', 'yours'];
|
|
10543
|
+
let personalCount = 0;
|
|
10544
|
+
personalPronouns.forEach((pronoun) => {
|
|
10545
|
+
const regex = new RegExp(`\\b${pronoun}\\b`, 'gi');
|
|
10546
|
+
personalCount += (body.match(regex) || []).length;
|
|
10547
|
+
});
|
|
10548
|
+
|
|
10549
|
+
const personalizationScore = hasName ? 30 : Math.min(personalCount * 2, 20);
|
|
10550
|
+
|
|
10551
|
+
return {
|
|
10552
|
+
spamScore: Math.min(spamScore, 100),
|
|
10553
|
+
phishingScore: Math.min(phishingScore, 100),
|
|
10554
|
+
promotionalScore: Math.min(promotionalScore, 100),
|
|
10555
|
+
transactionalScore: Math.min(transactionalScore, 100),
|
|
10556
|
+
personalizationScore,
|
|
10557
|
+
linkCount: links.length,
|
|
10558
|
+
hasUnsubscribe: text.includes('unsubscribe') || text.includes('opt-out'),
|
|
10559
|
+
hasPhone: /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/.test(body),
|
|
10560
|
+
hasAddress: /\b\d+\s+[\w\s]+(?:street|st|avenue|ave|road|rd|boulevard|blvd|way)\b/i.test(body),
|
|
10561
|
+
urgencyCount,
|
|
10562
|
+
misspellingCount: misspellings.length,
|
|
10563
|
+
};
|
|
10564
|
+
}
|
|
10565
|
+
|
|
10566
|
+
function analyzeSender(email, name, body) {
|
|
10567
|
+
const domain = email.split('@')[1] || '';
|
|
10568
|
+
const localPart = email.split('@')[0] || '';
|
|
10569
|
+
|
|
10570
|
+
return {
|
|
10571
|
+
email: email,
|
|
10572
|
+
domain: domain,
|
|
10573
|
+
local_part: localPart,
|
|
10574
|
+
is_public_domain: isPublicEmailDomain(domain),
|
|
10575
|
+
is_generic_sender: isGenericSender(localPart, name),
|
|
10576
|
+
domain_age_indicator: assessDomainAge(domain),
|
|
10577
|
+
sender_consistency: checkSenderConsistency(name, email, body),
|
|
10578
|
+
};
|
|
10579
|
+
}
|
|
10580
|
+
|
|
10581
|
+
function isPublicEmailDomain(domain) {
|
|
10582
|
+
const publicDomains = ['gmail.com', 'yahoo.com', 'outlook.com', 'hotmail.com', 'aol.com', 'icloud.com', 'protonmail.com', 'zoho.com', 'yandex.com'];
|
|
10583
|
+
return publicDomains.includes(domain.toLowerCase());
|
|
10584
|
+
}
|
|
10585
|
+
|
|
10586
|
+
function isGenericSender(localPart, name) {
|
|
10587
|
+
const genericSenders = ['info', 'support', 'sales', 'contact', 'hello', 'noreply', 'admin', 'newsletter', 'marketing', 'team', 'notifications'];
|
|
10588
|
+
|
|
10589
|
+
const localLower = localPart.toLowerCase();
|
|
10590
|
+
const nameLower = name.toLowerCase();
|
|
10591
|
+
|
|
10592
|
+
return genericSenders.some((sender) => localLower.includes(sender) || nameLower.includes(sender));
|
|
10593
|
+
}
|
|
10594
|
+
|
|
10595
|
+
function assessDomainAge(domain) {
|
|
10596
|
+
// This is a simplified version - in production, you'd use a domain age API
|
|
10597
|
+
const newDomains = ['xyz', 'online', 'site', 'top', 'club', 'shop'];
|
|
10598
|
+
const tld = domain.split('.').pop() || '';
|
|
10599
|
+
|
|
10600
|
+
if (newDomains.includes(tld)) return 'likely_new';
|
|
10601
|
+
if (['com', 'org', 'net', 'edu', 'gov'].includes(tld)) return 'likely_established';
|
|
10602
|
+
return 'unknown';
|
|
10603
|
+
}
|
|
10604
|
+
|
|
10605
|
+
function checkSenderConsistency(name, email, body) {
|
|
10606
|
+
const emailName = email.split('@')[0].toLowerCase();
|
|
10607
|
+
const bodyName = name.toLowerCase();
|
|
10608
|
+
|
|
10609
|
+
// Check if name appears in body
|
|
10610
|
+
const nameInBody = body.toLowerCase().includes(bodyName.split(' ')[0]);
|
|
10611
|
+
|
|
10612
|
+
// Check email-name consistency patterns
|
|
10613
|
+
const nameParts = bodyName.split(' ');
|
|
10614
|
+
const firstName = nameParts[0] || '';
|
|
10615
|
+
const lastName = nameParts.slice(-1)[0] || '';
|
|
10616
|
+
|
|
10617
|
+
const patterns = [`${firstName}.${lastName}`, `${firstName}${lastName}`, `${firstName.charAt(0)}${lastName}`, `${firstName}`];
|
|
10618
|
+
|
|
10619
|
+
const consistent = patterns.some((pattern) => emailName.includes(pattern.toLowerCase()));
|
|
10620
|
+
|
|
10621
|
+
return {
|
|
10622
|
+
name_in_body: nameInBody,
|
|
10623
|
+
email_name_consistent: consistent,
|
|
10624
|
+
consistency_score: (nameInBody ? 40 : 0) + (consistent ? 60 : 0),
|
|
10625
|
+
};
|
|
10626
|
+
}
|
|
10627
|
+
|
|
10628
|
+
function calculateUppercaseRatio(text) {
|
|
10629
|
+
const letters = text.replace(/[^a-zA-Z]/g, '');
|
|
10630
|
+
if (letters.length === 0) return 0;
|
|
10631
|
+
|
|
10632
|
+
const uppercase = letters.replace(/[^A-Z]/g, '');
|
|
10633
|
+
return (uppercase.length / letters.length) * 100;
|
|
10634
|
+
}
|
|
10635
|
+
|
|
10636
|
+
function detectBehavioralPatterns(body, subject) {
|
|
10637
|
+
const text = (subject + ' ' + body).toLowerCase();
|
|
10638
|
+
const patterns = [];
|
|
10639
|
+
|
|
10640
|
+
if (/(?:click|tap)\s+(?:here|this|link|button)/i.test(text)) {
|
|
10641
|
+
patterns.push('call_to_action_link');
|
|
10642
|
+
}
|
|
10643
|
+
|
|
10644
|
+
if (/limited\s+time|offer\s+expires|only\s+\d+\s+left/i.test(text)) {
|
|
10645
|
+
patterns.push('scarcity_tactic');
|
|
10646
|
+
}
|
|
10647
|
+
|
|
10648
|
+
if (/\$\d+|\d+\s*%|\d+\s*off|discount|save|sale/i.test(text)) {
|
|
10649
|
+
patterns.push('monetary_offer');
|
|
10650
|
+
}
|
|
10651
|
+
|
|
10652
|
+
if (/urgent|important|alert|attention|warning/i.test(text)) {
|
|
10653
|
+
patterns.push('urgency_tactic');
|
|
10654
|
+
}
|
|
10655
|
+
|
|
10656
|
+
if (/(?:please|kindly)\s+(?:help|assist|reply)/i.test(text)) {
|
|
10657
|
+
patterns.push('polite_request');
|
|
10658
|
+
}
|
|
10659
|
+
|
|
10660
|
+
if (/unsubscribe|opt.?out|preference|manage subscription/i.test(text)) {
|
|
10661
|
+
patterns.push('subscription_management');
|
|
10662
|
+
}
|
|
10663
|
+
|
|
10664
|
+
return patterns;
|
|
10665
|
+
}
|
|
10666
|
+
|
|
10667
|
+
function assessDomainReputation(email) {
|
|
10668
|
+
const domain = email.split('@')[1] || '';
|
|
10669
|
+
|
|
10670
|
+
// Known spam domains (simplified list)
|
|
10671
|
+
const spamDomains = ['spam4.me', 'trashmail.com', 'mailinator.com', 'guerrillamail.com', 'tempmail.com', 'yopmail.com', 'dispostable.com'];
|
|
10672
|
+
|
|
10673
|
+
if (spamDomains.includes(domain.toLowerCase())) {
|
|
10674
|
+
return 'known_spam_domain';
|
|
10675
|
+
}
|
|
10676
|
+
|
|
10677
|
+
// Professional domains
|
|
10678
|
+
const professionalTLDs = ['com', 'org', 'net', 'edu', 'gov', 'io', 'ai', 'tech'];
|
|
10679
|
+
const tld = domain.split('.').pop() || '';
|
|
10680
|
+
|
|
10681
|
+
if (professionalTLDs.includes(tld.toLowerCase())) {
|
|
10682
|
+
return 'professional_domain';
|
|
10683
|
+
}
|
|
10684
|
+
|
|
10685
|
+
// New/suspicious TLDs
|
|
10686
|
+
const suspiciousTLDs = ['xyz', 'top', 'win', 'bid', 'download', 'stream'];
|
|
10687
|
+
if (suspiciousTLDs.includes(tld.toLowerCase())) {
|
|
10688
|
+
return 'suspicious_tld';
|
|
10689
|
+
}
|
|
10690
|
+
|
|
10691
|
+
return 'neutral';
|
|
10692
|
+
}
|
|
10693
|
+
|
|
10694
|
+
function calculateComprehensiveRisk(aiData, localAnalysis) {
|
|
10695
|
+
let risk = 0;
|
|
10696
|
+
|
|
10697
|
+
// Base on AI classification
|
|
10698
|
+
switch (aiData.email_type) {
|
|
10699
|
+
case 'spam':
|
|
10700
|
+
risk += 80;
|
|
10701
|
+
break;
|
|
10702
|
+
case 'promotional':
|
|
10703
|
+
risk += 30;
|
|
10704
|
+
break;
|
|
10705
|
+
case 'transactional':
|
|
10706
|
+
risk += 10;
|
|
10707
|
+
break;
|
|
10708
|
+
case 'personal':
|
|
10709
|
+
risk += 5;
|
|
10710
|
+
break;
|
|
10711
|
+
case 'business':
|
|
10712
|
+
risk += 15;
|
|
10713
|
+
break;
|
|
10714
|
+
}
|
|
10715
|
+
|
|
10716
|
+
// Add local analysis scores
|
|
10717
|
+
risk += localAnalysis.spamScore * 0.2;
|
|
10718
|
+
risk += localAnalysis.phishingScore * 0.3;
|
|
10719
|
+
|
|
10720
|
+
// Adjust for phishing risk
|
|
10721
|
+
switch (aiData.phishing_risk) {
|
|
10722
|
+
case 'critical':
|
|
10723
|
+
risk += 40;
|
|
10724
|
+
break;
|
|
10725
|
+
case 'high':
|
|
10726
|
+
risk += 30;
|
|
10727
|
+
break;
|
|
10728
|
+
case 'medium':
|
|
10729
|
+
risk += 15;
|
|
10730
|
+
break;
|
|
10731
|
+
case 'low':
|
|
10732
|
+
risk += 5;
|
|
10733
|
+
break;
|
|
10734
|
+
}
|
|
10735
|
+
|
|
10736
|
+
// Adjust for unsolicited
|
|
10737
|
+
if (aiData.is_unsolicited) risk += 20;
|
|
10738
|
+
|
|
10739
|
+
// Adjust for urgency
|
|
10740
|
+
if (aiData.is_urgent) risk += 10;
|
|
10741
|
+
|
|
10742
|
+
return Math.min(risk, 100);
|
|
10743
|
+
}
|
|
10744
|
+
|
|
10745
|
+
function detectMatchedFilters(body, subject, email, name) {
|
|
10746
|
+
const filters = [];
|
|
10747
|
+
const text = (subject + ' ' + body).toLowerCase();
|
|
10748
|
+
|
|
10749
|
+
// Spam filters
|
|
10750
|
+
if (/(?:viagra|cialis|penis|enlarge)/i.test(text)) filters.push('adult_content_filter');
|
|
10751
|
+
if (/(?:lottery|winner|prize|jackpot)/i.test(text)) filters.push('lottery_scam_filter');
|
|
10752
|
+
if (/(?:nigerian|prince|inheritance|unclaimed)/i.test(text)) filters.push('inheritance_scam_filter');
|
|
10753
|
+
if (/password\s+reset|verify\s+account/i.test(text)) filters.push('account_verification_filter');
|
|
10754
|
+
|
|
10755
|
+
// Promotional filters
|
|
10756
|
+
if (/newsletter|subscribe|unsubscribe/i.test(text)) filters.push('newsletter_filter');
|
|
10757
|
+
if (/sale|discount|offer|coupon/i.test(text)) filters.push('promotional_offer_filter');
|
|
10758
|
+
if (/webinar|event|conference/i.test(text)) filters.push('event_filter');
|
|
10759
|
+
|
|
10760
|
+
// Transactional filters
|
|
10761
|
+
if (/order|invoice|receipt|payment/i.test(text)) filters.push('transaction_filter');
|
|
10762
|
+
if (/shipping|delivery|tracking/i.test(text)) filters.push('shipping_filter');
|
|
10763
|
+
if (/appointment|meeting|reminder/i.test(text)) filters.push('calendar_filter');
|
|
10764
|
+
|
|
10765
|
+
return filters;
|
|
10766
|
+
}
|
|
10767
|
+
|
|
10768
|
+
function detectTimeSensitivity(body) {
|
|
10769
|
+
const text = body.toLowerCase();
|
|
10770
|
+
const patterns = [/today|tomorrow|this\s+week|immediately|asap/i, /deadline|due\s+by|expires|limited\s+time/i, /urgent|important|attention|alert/i];
|
|
10771
|
+
|
|
10772
|
+
return patterns.some((pattern) => pattern.test(text));
|
|
10773
|
+
}
|
|
10774
|
+
|
|
10775
|
+
function detectExpiryDate(body) {
|
|
10776
|
+
const datePatterns = [/\b\d{1,2}\/\d{1,2}\/\d{2,4}\b/g, /\b\d{1,2}\s+(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\s+\d{2,4}\b/gi, /\bexpires?\s+(?:on|by)?\s*[:]?\s*\d{1,2}\/\d{1,2}\/\d{2,4}\b/gi];
|
|
10777
|
+
|
|
10778
|
+
return datePatterns.some((pattern) => pattern.test(body.toLowerCase()));
|
|
10779
|
+
}
|
|
10780
|
+
|
|
10781
|
+
function detectFollowUpPattern(body, subject) {
|
|
10782
|
+
const text = (subject + ' ' + body).toLowerCase();
|
|
10783
|
+
return /follow.?up|following.?up|checking.?in|touch.?base|re:.?re:/i.test(text);
|
|
10784
|
+
}
|
|
10785
|
+
|
|
10786
|
+
function analyzeRelationshipContext(body, subject, name) {
|
|
10787
|
+
const text = (subject + ' ' + body).toLowerCase();
|
|
10788
|
+
const nameParts = name.toLowerCase().split(' ');
|
|
10789
|
+
const firstName = nameParts[0] || '';
|
|
10790
|
+
|
|
10791
|
+
const indicators = {
|
|
10792
|
+
uses_name: firstName && text.includes(firstName),
|
|
10793
|
+
uses_personal_pronouns: /\b(?:i|me|my|mine|you|your|yours)\b/gi.test(text),
|
|
10794
|
+
has_greeting: /^(?:hi|hello|hey|dear|greetings)\b/im.test(body),
|
|
10795
|
+
has_signature: /(?:best|regards|sincerely|thanks|thank you)\s*[,]?\s*\n/im.test(body),
|
|
10796
|
+
has_questions: /\?/.test(text) && /(?:can|could|would|will|do|are|is)\s+you/i.test(text),
|
|
10797
|
+
};
|
|
10798
|
+
|
|
10799
|
+
const relationshipScore = Object.values(indicators).filter(Boolean).length * 20;
|
|
10800
|
+
|
|
10801
|
+
return {
|
|
10802
|
+
...indicators,
|
|
10803
|
+
relationship_score: Math.min(relationshipScore, 100),
|
|
10804
|
+
likely_known_sender: relationshipScore >= 40,
|
|
10805
|
+
};
|
|
10806
|
+
}
|
|
10807
|
+
|
|
10808
|
+
function calculateFinalConfidence(data) {
|
|
10809
|
+
let confidence = data.email_type_confidence;
|
|
10810
|
+
|
|
10811
|
+
// Adjust based on local analysis
|
|
10812
|
+
if (data.local_analysis.spamScore > 70 && data.email_type !== 'spam') {
|
|
10813
|
+
confidence -= 20;
|
|
10814
|
+
}
|
|
10815
|
+
|
|
10816
|
+
// Adjust based on phishing risk
|
|
10817
|
+
if (data.phishing_risk === 'critical' || data.phishing_risk === 'high') {
|
|
10818
|
+
confidence += 15;
|
|
10819
|
+
}
|
|
10820
|
+
|
|
10821
|
+
// Adjust based on personalization
|
|
10822
|
+
if (data.is_personalized) {
|
|
10823
|
+
confidence += 10;
|
|
10824
|
+
}
|
|
10825
|
+
|
|
10826
|
+
// Adjust based on content stats
|
|
10827
|
+
if (data.content_stats.link_count > 5) {
|
|
10828
|
+
confidence += 5;
|
|
10829
|
+
}
|
|
10830
|
+
|
|
10831
|
+
return Math.max(0, Math.min(100, confidence));
|
|
10832
|
+
}
|
|
10833
|
+
|
|
10834
|
+
function generateExplanation(data) {
|
|
10835
|
+
const explanations = [];
|
|
10836
|
+
|
|
10837
|
+
if (data.email_type === 'spam') {
|
|
10838
|
+
explanations.push('Classified as spam due to:');
|
|
10839
|
+
if (data.spam_confidence > 70) explanations.push(`- High spam confidence (${data.spam_confidence}%)`);
|
|
10840
|
+
if (data.phishing_risk !== 'none') explanations.push(`- ${data.phishing_risk} phishing risk`);
|
|
10841
|
+
if (data.local_analysis.spamScore > 60) explanations.push('- Contains spam indicators');
|
|
10842
|
+
}
|
|
10843
|
+
|
|
10844
|
+
if (data.email_type === 'promotional') {
|
|
10845
|
+
explanations.push('Classified as promotional due to:');
|
|
10846
|
+
if (data.is_commercial) explanations.push('- Commercial intent detected');
|
|
10847
|
+
if (data.local_analysis.hasUnsubscribe) explanations.push('- Contains unsubscribe option');
|
|
10848
|
+
if (data.local_analysis.promotionalScore > 30) explanations.push('- Promotional content detected');
|
|
10849
|
+
}
|
|
10850
|
+
|
|
10851
|
+
if (data.key_indicators && data.key_indicators.length > 0) {
|
|
10852
|
+
explanations.push('Key indicators:');
|
|
10853
|
+
data.key_indicators.forEach((indicator) => {
|
|
10854
|
+
explanations.push(`- ${indicator}`);
|
|
10855
|
+
});
|
|
10856
|
+
}
|
|
10857
|
+
|
|
10858
|
+
return explanations.join('\n');
|
|
10859
|
+
}
|
|
10860
|
+
|
|
10861
|
+
function checkCompliance(body, subject) {
|
|
10862
|
+
const text = (subject + ' ' + body).toLowerCase();
|
|
10863
|
+
const flags = [];
|
|
10864
|
+
|
|
10865
|
+
// CAN-SPAM Act compliance (US)
|
|
10866
|
+
if (text.includes('unsubscribe') || text.includes('opt-out')) {
|
|
10867
|
+
flags.push('has_unsubscribe_option');
|
|
10868
|
+
}
|
|
10869
|
+
|
|
10870
|
+
if (/\b\d{10}\b/.test(body.replace(/\D/g, ''))) {
|
|
10871
|
+
flags.push('contains_phone_number');
|
|
10872
|
+
}
|
|
10873
|
+
|
|
10874
|
+
if (/address\s*[:]?\s*\d+\s+[\w\s]+/i.test(body)) {
|
|
10875
|
+
flags.push('contains_physical_address');
|
|
10876
|
+
}
|
|
10877
|
+
|
|
10878
|
+
// GDPR indicators (EU)
|
|
10879
|
+
if (/privacy\s+policy|gdpr|data\s+protection/i.test(text)) {
|
|
10880
|
+
flags.push('gdpr_mentions');
|
|
10881
|
+
}
|
|
10882
|
+
|
|
10883
|
+
if (/consent|opt.?in|permission/i.test(text)) {
|
|
10884
|
+
flags.push('consent_mentions');
|
|
10885
|
+
}
|
|
10886
|
+
|
|
10887
|
+
return flags;
|
|
10888
|
+
}
|
|
10889
|
+
|
|
10890
|
+
function getDefaultEmailDetection(name, email, emailBody, emailSubject, error = null) {
|
|
10891
|
+
const localAnalysis = analyzeEmailLocally(emailBody, emailSubject, email, name);
|
|
10892
|
+
|
|
10893
|
+
// Determine type based on local analysis
|
|
10894
|
+
let emailType = 'ambiguous';
|
|
10895
|
+
if (localAnalysis.spamScore > 70) emailType = 'spam';
|
|
10896
|
+
else if (localAnalysis.promotionalScore > localAnalysis.transactionalScore && localAnalysis.promotionalScore > 30) emailType = 'promotional';
|
|
10897
|
+
else if (localAnalysis.transactionalScore > 40) emailType = 'transactional';
|
|
10898
|
+
|
|
10899
|
+
const spamConfidence = localAnalysis.spamScore;
|
|
10900
|
+
const phishingRisk = localAnalysis.phishingScore > 50 ? 'medium' : 'low';
|
|
10901
|
+
|
|
10902
|
+
return {
|
|
10903
|
+
success: false,
|
|
10904
|
+
email_type: emailType,
|
|
10905
|
+
email_type_confidence: Math.max(50, spamConfidence),
|
|
10906
|
+
email_subtype: 'personal_message',
|
|
10907
|
+
|
|
10908
|
+
spam_confidence: spamConfidence,
|
|
10909
|
+
phishing_risk: phishingRisk,
|
|
10910
|
+
malware_risk: 'none',
|
|
10911
|
+
|
|
10912
|
+
is_unsolicited: true,
|
|
10913
|
+
is_commercial: localAnalysis.promotionalScore > 30,
|
|
10914
|
+
is_urgent: localAnalysis.urgencyCount > 0,
|
|
10915
|
+
is_personalized: localAnalysis.personalizationScore > 20,
|
|
10916
|
+
|
|
10917
|
+
sender_legitimacy: 'unknown',
|
|
10918
|
+
sender_intent: 'unknown',
|
|
10919
|
+
|
|
10920
|
+
contains_links: localAnalysis.linkCount > 0,
|
|
10921
|
+
contains_attachments: false,
|
|
10922
|
+
contains_unsubscribe: localAnalysis.hasUnsubscribe,
|
|
10923
|
+
contains_phone_number: localAnalysis.hasPhone,
|
|
10924
|
+
contains_address: localAnalysis.hasAddress,
|
|
10925
|
+
|
|
10926
|
+
language_quality: localAnalysis.misspellingCount > 3 ? 'poor' : 'average',
|
|
10927
|
+
urgency_level: localAnalysis.urgencyCount > 2 ? 'high' : 'low',
|
|
10928
|
+
personalization_score: localAnalysis.personalizationScore,
|
|
10929
|
+
|
|
10930
|
+
suggested_action: emailType === 'spam' ? 'mark_as_spam' : 'keep_in_inbox',
|
|
10931
|
+
auto_filter_suggestion: emailType === 'spam' ? 'spam_filter' : 'no_filter',
|
|
10932
|
+
inbox_priority: emailType === 'spam' ? 'ignore' : 'normal',
|
|
10933
|
+
|
|
10934
|
+
key_indicators: ['Local analysis only - API failed'],
|
|
10935
|
+
risk_factors: [],
|
|
10936
|
+
legitimacy_signals: [],
|
|
10937
|
+
|
|
10938
|
+
analysis_timestamp: new Date().toISOString(),
|
|
10939
|
+
content_length: emailBody.length,
|
|
10940
|
+
analysis_complexity: 'simple',
|
|
10941
|
+
|
|
10942
|
+
// Enhanced fields
|
|
10943
|
+
local_analysis: localAnalysis,
|
|
10944
|
+
sender_analysis: analyzeSender(email, name, emailBody),
|
|
10945
|
+
content_stats: {
|
|
10946
|
+
word_count: (emailBody.match(/\S+/g) || []).length,
|
|
10947
|
+
sentence_count: (emailBody.match(/[.!?]+/g) || []).length,
|
|
10948
|
+
link_count: localAnalysis.linkCount,
|
|
10949
|
+
uppercase_ratio: calculateUppercaseRatio(emailBody),
|
|
10950
|
+
exclamation_count: (emailBody.match(/!/g) || []).length,
|
|
10951
|
+
dollar_sign_count: (emailBody.match(/\$/g) || []).length,
|
|
10952
|
+
phone_patterns: (emailBody.match(/\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/g) || []).length,
|
|
10953
|
+
},
|
|
10954
|
+
|
|
10955
|
+
comprehensive_risk_score: calculateComprehensiveRisk({ email_type: emailType, phishing_risk: phishingRisk, is_unsolicited: true, is_urgent: localAnalysis.urgencyCount > 0 }, localAnalysis),
|
|
10956
|
+
|
|
10957
|
+
final_confidence: Math.max(50, spamConfidence),
|
|
10958
|
+
explanation: 'Default analysis due to API failure',
|
|
10959
|
+
error: error || 'API call failed',
|
|
10960
|
+
};
|
|
10961
|
+
}
|
|
10962
|
+
|
|
10963
|
+
const detection_ret = await submit_chat_gpt_prompt({
|
|
10964
|
+
uid,
|
|
10965
|
+
prompt: `Analyze this email and determine if it's SPAM, PROMOTIONAL, TRANSACTIONAL, or PERSONAL/BUSINESS communication.
|
|
10966
|
+
|
|
10967
|
+
FROM: "${name}" <${email}>
|
|
10968
|
+
SUBJECT: "${email_subject}"
|
|
10969
|
+
BODY: "${email_body.substring(0, 800)}${email_body.length > 800 ? '...' : ''}"
|
|
10970
|
+
|
|
10971
|
+
ANALYSIS CRITERIA:
|
|
10972
|
+
|
|
10973
|
+
SPAM INDICATORS:
|
|
10974
|
+
- Unsolicited commercial content
|
|
10975
|
+
- Phishing attempts or suspicious links
|
|
10976
|
+
- Poor grammar/spelling
|
|
10977
|
+
- Urgent/panic-inducing language
|
|
10978
|
+
- Requests for personal information
|
|
10979
|
+
- Too good to be true offers
|
|
10980
|
+
- Hidden/unclear sender identity
|
|
10981
|
+
- Multiple recipient addresses visible
|
|
10982
|
+
- Stock/"spammy" subject lines
|
|
10983
|
+
|
|
10984
|
+
PROMOTIONAL INDICATORS:
|
|
10985
|
+
- Marketing/sales content
|
|
10986
|
+
- Product announcements
|
|
10987
|
+
- Newsletter content
|
|
10988
|
+
- Discount/coupon offers
|
|
10989
|
+
- Event invitations (commercial)
|
|
10990
|
+
- Company updates (marketing focused)
|
|
10991
|
+
- Clear opt-out/unsubscribe option
|
|
10992
|
+
- Professional branding/templates
|
|
10993
|
+
- Call-to-action buttons
|
|
10994
|
+
|
|
10995
|
+
TRANSACTIONAL INDICATORS:
|
|
10996
|
+
- Order confirmations
|
|
10997
|
+
- Shipping notifications
|
|
10998
|
+
- Invoice/billing
|
|
10999
|
+
- Account notifications
|
|
11000
|
+
- Password resets
|
|
11001
|
+
- Booking confirmations
|
|
11002
|
+
- Appointment reminders
|
|
11003
|
+
- Payment receipts
|
|
11004
|
+
- Service updates
|
|
11005
|
+
|
|
11006
|
+
PERSONAL/BUSINESS INDICATORS:
|
|
11007
|
+
- Direct communication to you
|
|
11008
|
+
- Known sender relationship
|
|
11009
|
+
- Work/project related
|
|
11010
|
+
- Personal conversations
|
|
11011
|
+
- One-on-one correspondence
|
|
11012
|
+
- Contains your name specifically
|
|
11013
|
+
- Contextually relevant to you
|
|
11014
|
+
|
|
11015
|
+
ADDITIONAL FACTORS TO CONSIDER:
|
|
11016
|
+
- Sender reputation (company vs personal)
|
|
11017
|
+
- Your relationship with sender
|
|
11018
|
+
- Email formatting quality
|
|
11019
|
+
- Personalization level
|
|
11020
|
+
- Expected vs unexpected content
|
|
11021
|
+
- Action requested (if any)
|
|
11022
|
+
|
|
11023
|
+
PROVIDE DETAILED ANALYSIS WITH CONFIDENCE SCORES.`,
|
|
11024
|
+
model: 'gpt-5-nano',
|
|
11025
|
+
response_format: z.object({
|
|
11026
|
+
// Primary Classification
|
|
11027
|
+
email_type: z.enum(['spam', 'promotional', 'transactional', 'personal', 'business', 'ambiguous']),
|
|
11028
|
+
email_type_confidence: z.number().min(0).max(100),
|
|
11029
|
+
|
|
11030
|
+
// Sub-classification
|
|
11031
|
+
email_subtype: z.enum([
|
|
11032
|
+
// Spam types
|
|
11033
|
+
'phishing_attempt',
|
|
11034
|
+
'scam_offer',
|
|
11035
|
+
'malware_risk',
|
|
11036
|
+
'adult_content',
|
|
11037
|
+
'financial_scam',
|
|
11038
|
+
'lottery_scam',
|
|
11039
|
+
'inheritance_scam',
|
|
11040
|
+
'romance_scam',
|
|
11041
|
+
'tech_support_scam',
|
|
11042
|
+
|
|
11043
|
+
// Promotional types
|
|
11044
|
+
'marketing_newsletter',
|
|
11045
|
+
'product_announcement',
|
|
11046
|
+
'discount_offer',
|
|
11047
|
+
'event_invitation',
|
|
11048
|
+
'company_update',
|
|
11049
|
+
'blog_newsletter',
|
|
11050
|
+
'educational_content',
|
|
11051
|
+
'lead_magnet',
|
|
11052
|
+
|
|
11053
|
+
// Transactional types
|
|
11054
|
+
'order_confirmation',
|
|
11055
|
+
'shipping_notification',
|
|
11056
|
+
'invoice_billing',
|
|
11057
|
+
'payment_receipt',
|
|
11058
|
+
'account_verification',
|
|
11059
|
+
'password_reset',
|
|
11060
|
+
'appointment_reminder',
|
|
11061
|
+
'service_notification',
|
|
11062
|
+
|
|
11063
|
+
// Personal/Business types
|
|
11064
|
+
'personal_message',
|
|
11065
|
+
'work_collaboration',
|
|
11066
|
+
'client_communication',
|
|
11067
|
+
'team_announcement',
|
|
11068
|
+
'meeting_invitation',
|
|
11069
|
+
'project_update',
|
|
11070
|
+
'direct_inquiry',
|
|
11071
|
+
'networking_request',
|
|
11072
|
+
]),
|
|
11073
|
+
|
|
11074
|
+
// Risk Assessment
|
|
11075
|
+
spam_confidence: z.number().min(0).max(100).describe('Confidence this is spam'),
|
|
11076
|
+
phishing_risk: z.enum(['none', 'low', 'medium', 'high', 'critical']),
|
|
11077
|
+
malware_risk: z.enum(['none', 'low', 'medium', 'high']),
|
|
11078
|
+
|
|
11079
|
+
// Content Analysis
|
|
11080
|
+
is_unsolicited: z.boolean().describe('Email was not requested/expected'),
|
|
11081
|
+
is_commercial: z.boolean().describe('Contains commercial intent'),
|
|
11082
|
+
is_urgent: z.boolean().describe('Uses urgent/time-sensitive language'),
|
|
11083
|
+
is_personalized: z.boolean().describe('Content is personalized to recipient'),
|
|
11084
|
+
|
|
11085
|
+
// Sender Analysis
|
|
11086
|
+
sender_legitimacy: z.enum(['verified', 'likely_legitimate', 'suspicious', 'likely_fake', 'unknown']),
|
|
11087
|
+
sender_intent: z.enum(['inform', 'sell', 'scam', 'build_relationship', 'request_action', 'unknown']),
|
|
11088
|
+
|
|
11089
|
+
// Content Characteristics
|
|
11090
|
+
contains_links: z.boolean(),
|
|
11091
|
+
contains_attachments: z.boolean(),
|
|
11092
|
+
contains_unsubscribe: z.boolean(),
|
|
11093
|
+
contains_phone_number: z.boolean(),
|
|
11094
|
+
contains_address: z.boolean(),
|
|
11095
|
+
|
|
11096
|
+
// Language Analysis
|
|
11097
|
+
language_quality: z.enum(['excellent', 'good', 'average', 'poor', 'spammy']),
|
|
11098
|
+
urgency_level: z.enum(['none', 'low', 'medium', 'high', 'extreme']),
|
|
11099
|
+
personalization_score: z.number().min(0).max(100).describe('How personalized the content is'),
|
|
11100
|
+
|
|
11101
|
+
// Action Recommendations
|
|
11102
|
+
suggested_action: z.enum(['delete_immediately', 'mark_as_spam', 'move_to_promotions', 'respond_politely', 'follow_up', 'archive', 'keep_in_inbox', 'add_to_contacts', 'report_phishing']),
|
|
11103
|
+
|
|
11104
|
+
// Filter Recommendations
|
|
11105
|
+
auto_filter_suggestion: z.enum(['spam_filter', 'promotions_filter', 'social_filter', 'updates_filter', 'primary_inbox', 'no_filter']),
|
|
11106
|
+
|
|
11107
|
+
// Priority
|
|
11108
|
+
inbox_priority: z.enum(['critical', 'high', 'normal', 'low', 'ignore']),
|
|
11109
|
+
|
|
11110
|
+
// Detailed Analysis
|
|
11111
|
+
key_indicators: z.array(z.string()).describe('Key factors influencing classification'),
|
|
11112
|
+
risk_factors: z.array(z.string()).describe('Specific risks identified'),
|
|
11113
|
+
legitimacy_signals: z.array(z.string()).describe('Signals of legitimate communication'),
|
|
11114
|
+
|
|
11115
|
+
// Metadata
|
|
11116
|
+
analysis_timestamp: z.string(),
|
|
11117
|
+
content_length: z.number().describe('Length of email body analyzed'),
|
|
11118
|
+
analysis_complexity: z.enum(['simple', 'moderate', 'complex', 'ambiguous']),
|
|
11119
|
+
}),
|
|
11120
|
+
metadata: {
|
|
11121
|
+
func: 'detect_email_type',
|
|
11122
|
+
sender_name: name,
|
|
11123
|
+
sender_email: email,
|
|
11124
|
+
subject_length: email_subject?.length || 0,
|
|
11125
|
+
body_length: email_body?.length || 0,
|
|
11126
|
+
body_preview_hash: await hashString(email_body.substring(0, 200)),
|
|
11127
|
+
},
|
|
11128
|
+
account_profile_info,
|
|
11129
|
+
tools: [{ type: 'web_search_preview' }],
|
|
11130
|
+
});
|
|
11131
|
+
|
|
11132
|
+
try {
|
|
11133
|
+
if (detection_ret.code > -1) {
|
|
11134
|
+
const data = typeof detection_ret.data === 'string' ? JSON.parse(detection_ret.data) : detection_ret.data;
|
|
11135
|
+
|
|
11136
|
+
// Enhance with additional analysis
|
|
11137
|
+
const enhancedData = enhanceEmailDetection(data, name, email, email_body, email_subject);
|
|
11138
|
+
|
|
11139
|
+
return {
|
|
11140
|
+
success: true,
|
|
11141
|
+
...enhancedData,
|
|
11142
|
+
};
|
|
11143
|
+
} else {
|
|
11144
|
+
console.error('Email detection API call failed:', detection_ret);
|
|
11145
|
+
return getDefaultEmailDetection(name, email, email_body, email_subject);
|
|
11146
|
+
}
|
|
11147
|
+
} catch (error) {
|
|
11148
|
+
console.error('Error in detect_email_type:', error);
|
|
11149
|
+
return getDefaultEmailDetection(name, email, email_body, email_subject, error.message);
|
|
11150
|
+
}
|
|
11151
|
+
};
|