import { prisma, SpamRule, SpamFeedback, User } from '@shieldsai/shared-db'; import { spamShieldEnv, SpamDecision, ConfidenceLevel } from './spamshield.config'; // Number reputation service (Hiya API integration) export class NumberReputationService { /** * Check number reputation using Hiya API */ async checkReputation(phoneNumber: string): Promise<{ isSpam: boolean; confidence: number; spamType?: string; reportCount: number; }> { try { // TODO: Integrate with Hiya API // const response = await fetch(`${spamShieldEnv.HIYA_API_URL}/lookup`, { // headers: { 'X-API-Key': spamShieldEnv.HIYA_API_KEY }, // method: 'POST', // body: JSON.stringify({ phone: phoneNumber }), // }); // Simulated response for now return { isSpam: false, confidence: 0.1, spamType: undefined, reportCount: 0, }; } catch (error) { console.error('Error checking number reputation:', error); return { isSpam: false, confidence: 0.0, reportCount: 0, }; } } /** * Check number against multiple reputation sources */ async checkMultiSource(phoneNumber: string): Promise<{ hiya: { isSpam: boolean; confidence: number }; truecaller: { isSpam: boolean; confidence: number } | null; combinedScore: number; }> { const hiyaResult = await this.checkReputation(phoneNumber); let truecallerResult: { isSpam: boolean; confidence: number } | null = null; if (spamShieldEnv.TRUECALLER_API_KEY) { // TODO: Integrate Truecaller truecallerResult = { isSpam: false, confidence: 0.0, }; } // Weighted average: Hiya 70%, Truecaller 30% const combinedScore = hiyaResult.confidence * 0.7 + (truecallerResult?.confidence ?? 0) * 0.3; return { hiya: { isSpam: hiyaResult.isSpam, confidence: hiyaResult.confidence }, truecaller: truecallerResult, combinedScore, }; } } // SMS content classifier (BERT-based) export class SMSClassifierService { private model: any = null; // BERT model placeholder /** * Initialize the BERT model */ async initialize(): Promise { // TODO: Load BERT model from path // this.model = await loadBERTModel(spamShieldEnv.BERT_MODEL_PATH); console.log('SMS classifier initialized'); } /** * Classify SMS text as spam or ham */ async classify(smsText: string): Promise<{ isSpam: boolean; confidence: number; spamFeatures: string[]; }> { if (!this.model) { await this.initialize(); } // Extract features const features = this.extractFeatures(smsText); // TODO: Run through BERT model // const prediction = await this.model.predict(smsText); // Simulated prediction const confidence = this.calculateConfidence(features); const isSpam = confidence >= spamShieldEnv.SPAM_THRESHOLD_AUTO_BLOCK; return { isSpam, confidence, spamFeatures: features, }; } private extractFeatures(text: string): string[] { const features: string[] = []; const lowerText = text.toLowerCase(); // URL presence if (/(http|www)\./i.test(text)) { features.push('url_present'); } // Emoji density const emojiCount = (text.match(/[\p{Emoji}]/gu) || []).length; if (emojiCount / text.length > 0.1) { features.push('high_emoji_density'); } // Urgency keywords const urgencyWords = ['now', 'urgent', 'limited', 'act fast', 'today']; if (urgencyWords.some(word => lowerText.includes(word))) { features.push('urgency_keyword'); } // Excessive capitalization if (/[A-Z]{3,}/.test(text)) { features.push('excessive_caps'); } return features; } private calculateConfidence(features: string[]): number { const baseConfidence = 0.5; const featureWeights: Record = { url_present: 0.1, high_emoji_density: 0.15, urgency_keyword: 0.2, excessive_caps: 0.15, }; return Math.min(1.0, baseConfidence + features.reduce((sum, f) => sum + (featureWeights[f] || 0), 0)); } } // Call analysis service export class CallAnalysisService { /** * Analyze incoming call for spam indicators */ async analyzeCall(callData: { phoneNumber: string; duration?: number; callTime: Date; isVoip?: boolean; }): Promise<{ decision: SpamDecision; confidence: number; reasons: string[]; }> { const reasons: string[] = []; let spamScore = 0.0; // Number reputation check const reputationService = new NumberReputationService(); const reputation = await reputationService.checkMultiSource(callData.phoneNumber); if (reputation.combinedScore > 0.7) { spamScore += reputation.combinedScore * 0.4; reasons.push('high_spam_reputation'); } // Behavioral analysis if (callData.duration && callData.duration < 10) { spamScore += 0.2; reasons.push('short_duration'); } if (callData.isVoip) { spamScore += 0.15; reasons.push('voip_number'); } // Time-of-day anomaly (simplified) const hour = callData.callTime.getHours(); if (hour < 6 || hour > 22) { spamScore += 0.1; reasons.push('unusual_hours'); } // Determine decision let decision: SpamDecision; if (spamScore >= spamShieldEnv.SPAM_THRESHOLD_AUTO_BLOCK) { decision = SpamDecision.BLOCK; } else if (spamScore >= spamShieldEnv.SPAM_THRESHOLD_FLAG) { decision = SpamDecision.FLAG; } else { decision = SpamDecision.ALLOW; } return { decision, confidence: spamScore, reasons, }; } } // User feedback service export class SpamFeedbackService { /** * Record user feedback on spam detection */ async recordFeedback( userId: string, phoneNumber: string, isSpam: boolean, confidence?: number, metadata?: Record ): Promise { const phoneNumberHash = this.hashPhoneNumber(phoneNumber); const feedback = await prisma.spamFeedback.create({ data: { userId, phoneNumber, phoneNumberHash, isSpam, confidence, feedbackType: 'user_confirmation', metadata, }, }); return feedback; } /** * Get spam history for a user */ async getSpamHistory( userId: string, options?: { limit?: number; isSpam?: boolean; startDate?: Date; } ): Promise { return prisma.spamFeedback.findMany({ where: { userId, ...(options?.isSpam !== undefined && { isSpam: options.isSpam }), ...(options?.startDate && { createdAt: { gte: options.startDate } }), }, orderBy: { createdAt: 'desc' }, take: options?.limit ?? 100, }); } /** * Get statistics for a user */ async getStatistics(userId: string): Promise<{ totalAnalyses: number; spamCount: number; hamCount: number; spamPercentage: number; }> { const [total, spam] = await Promise.all([ prisma.spamFeedback.count({ where: { userId } }), prisma.spamFeedback.count({ where: { userId, isSpam: true } }), ]); return { totalAnalyses: total, spamCount: spam, hamCount: total - spam, spamPercentage: total > 0 ? (spam / total) * 100 : 0, }; } private hashPhoneNumber(phoneNumber: string): string { // Simple hash for demonstration let hash = 0; for (let i = 0; i < phoneNumber.length; i++) { hash = ((hash << 5) - hash) + phoneNumber.charCodeAt(i); hash |= 0; } return `hash_${Math.abs(hash)}`; } } // Export instances export const numberReputationService = new NumberReputationService(); export const smsClassifierService = new SMSClassifierService(); export const callAnalysisService = new CallAnalysisService(); export const spamFeedbackService = new SpamFeedbackService();