@xuda.io/ai_module 1.1.4681 → 1.1.4683

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