export interface ParsedBookingData { locationSlug: string; // Event Details eventDate: string; eventType?: string; guestCount?: string; eventLocation?: string; contactPerson?: string; contactPhone?: string; // Model & Options model: string; printOption?: string; accessories?: string; taskGame?: string; audioGuestbook?: string; delivery?: string; // Price calculatedPrice?: number; // Customer Data salutation?: string; companyName?: string; firstName?: string; lastName?: string; email: string; phone: string; // Address street?: string; zip?: string; city?: string; // Message message?: string; // Raw Email Data rawSubject: string; rawBody: string; } export class NinjaFormsEmailParser { private fieldMappings = { // Date fields date: ['Datum:', 'Datum'], // Event fields eventType: ['Art der Veranstaltung:', 'Art der Veranstaltung'], guestCount: ['Gästeanzahl:', 'Gästeanzahl'], eventLocation: ['Location:', 'Location'], contactPerson: ['Ansprechpartner:', 'Ansprechpartner'], contactPhone: ['Durchwahl:', 'Durchwahl'], // Model fields model: ['Modell:', 'Modell'], printOption: ['Vor Ort Druck?:', 'Vor Ort Druck'], accessories: ['Inklusive Accessiores?:', 'Inklusive Accessiores'], taskGame: ['Fotobox - Aufgabenspiel:', 'Aufgabenspiel'], audioGuestbook: ['Audio-Gästebuch:', 'Audio'], delivery: ['Lieferung (inkl. Auf-/Abbau):', 'Lieferung'], // Price price: ['Preis:', 'Preis'], // Customer fields salutation: ['Anrede:', 'Anrede'], company: ['Firma:', 'Firma'], firstName: ['Vorname:', 'Vorname'], lastName: ['Nachname:', 'Nachname'], email: ['E-Mail:', 'E-Mail', 'Email:'], phone: ['Telefon:', 'Telefon', 'Phone:'], // Address street: ['Straße:', 'Strasse:', 'Straße'], zip: ['PLZ:', 'PLZ'], city: ['Ort:', 'Ort'], // Message message: ['Nachricht:', 'Nachricht'], }; parseEmailBody(body: string, subject: string): ParsedBookingData | null { try { const lines = body.split('\n').map(line => line.trim()).filter(Boolean); const data: any = { rawSubject: subject, rawBody: body, }; // Detect location from subject data.locationSlug = this.detectLocation(subject, body); // Parse each line (check current and next line for key-value pairs) for (let i = 0; i < lines.length; i++) { const line = lines[i]; const nextLine = lines[i + 1]; this.parseLine(line, nextLine, data); } // Extract email and phone if not found if (!data.email) { data.email = this.extractEmail(body); } if (!data.phone) { data.phone = this.extractPhone(body); } // Map model names data.model = this.mapModelName(data.model); console.log('Parsed data before validation:', { email: data.email, phone: data.phone, eventDate: data.eventDate, date: data.date, firstName: data.firstName, lastName: data.lastName, }); // Validate required fields if (!data.email || (!data.eventDate && !data.date)) { console.log('Validation failed: missing email or date'); return null; } // Normalize date field if (data.date && !data.eventDate) { data.eventDate = data.date; } return data as ParsedBookingData; } catch (error) { console.error('Email parsing error:', error); return null; } } private parseLine(line: string, nextLine: string | undefined, data: any) { for (const [key, patterns] of Object.entries(this.fieldMappings)) { for (const pattern of patterns) { if (line.includes(pattern)) { // Try to get value from same line first let value = line.split(pattern)[1]?.trim(); // If no value on same line, check next line if (!value && nextLine) { value = nextLine.trim(); } if (value) { // Special handling for street - keep full address if (key === 'street' && value) { // Don't split on numbers, keep the full street name data[key] = value; } else if (key === 'price') { data.calculatedPrice = this.extractPrice(value); } else { data[key] = value; } return; } } } } } private detectLocation(subject: string, body: string): string { const text = (subject + ' ' + body).toLowerCase(); if (text.includes('lübeck') || text.includes('luebeck')) return 'luebeck'; if (text.includes('hamburg')) return 'hamburg'; if (text.includes('kiel')) return 'kiel'; if (text.includes('berlin') || text.includes('potsdam')) return 'berlin'; if (text.includes('rostock')) return 'rostock'; return 'luebeck'; // Default } private mapModelName(modelString?: string): string { if (!modelString) return 'VINTAGE_SMILE'; const model = modelString.toLowerCase(); if (model.includes('smile')) return 'VINTAGE_SMILE'; if (model.includes('foto') || model.includes('photo')) return 'VINTAGE_PHOTOS'; if (model.includes('nostalgie')) return 'NOSTALGIE'; if (model.includes('magic') || model.includes('spiegel') || model.includes('mirror')) return 'MAGIC_MIRROR'; return 'VINTAGE_SMILE'; } private extractPrice(value: string): number { const match = value.match(/(\d+)/); return match ? parseInt(match[1]) : 0; } private extractEmail(text: string): string | null { const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/; const match = text.match(emailRegex); return match ? match[0] : null; } private extractPhone(text: string): string | null { const phoneRegex = /(\+?49|0)[\s\-]?(\d{2,5})[\s\-]?(\d{3,})[\s\-]?(\d{3,})/; const match = text.match(phoneRegex); return match ? match[0].replace(/\s+/g, '') : null; } parseFromNinjaFormsEmail(emailText: string, subject: string): ParsedBookingData | null { // Clean HTML if present let cleanText = emailText; // Convert HTML table rows to newlines for better parsing cleanText = cleanText.replace(/<\/tr>/gi, '\n'); cleanText = cleanText.replace(/<\/td>/gi, ' '); cleanText = cleanText.replace(//gi, '\n'); cleanText = cleanText.replace(/<\/p>/gi, '\n'); cleanText = cleanText.replace(/<\/div>/gi, '\n'); // Remove all other HTML tags cleanText = cleanText.replace(/<[^>]*>/g, ''); // Decode HTML entities cleanText = cleanText .replace(/ /g, ' ') .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/&#(\d+);/g, (match, dec) => String.fromCharCode(dec)); // Clean up whitespace cleanText = cleanText .split('\n') .map(line => line.trim()) .filter(line => line.length > 0) .join('\n'); console.log('=== CLEANED TEXT FOR PARSING ==='); console.log(cleanText.substring(0, 800)); console.log('=== END CLEANED TEXT ==='); const result = this.parseEmailBody(cleanText, subject); console.log('Parse result from parseEmailBody:', result ? 'SUCCESS' : 'FAILED'); return result; } } export const emailParser = new NinjaFormsEmailParser();