Initial commit - SaveTheMoment Atlas Basis-Setup
This commit is contained in:
253
lib/email-parser.ts
Normal file
253
lib/email-parser.ts
Normal file
@@ -0,0 +1,253 @@
|
||||
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(/<br\s*\/?>/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();
|
||||
Reference in New Issue
Block a user