#!/usr/bin/env node const Imap = require('imap'); const { simpleParser } = require('mailparser'); const fs = require('fs'); const path = require('path'); const { exec } = require('child_process'); const util = require('util'); const execPromise = util.promisify(exec); // Load credentials const creds = JSON.parse(fs.readFileSync('/root/.openclaw/credentials/prep101_credentials.json', 'utf8')); const PREP101_API = 'https://prep101-api.vercel.app/api/clawdbot/generate'; const PREP101_KEY = 'clawdbot_sk_live_5928349283749238'; const WATCH_INTERVAL = 60000; // Check every 60 seconds const PROCESSED_FILE = '/root/.openclaw/workspace/Projects/Casting/.processed_emails.json'; // Load processed emails function loadProcessed() { if (fs.existsSync(PROCESSED_FILE)) { return JSON.parse(fs.readFileSync(PROCESSED_FILE, 'utf8')); } return { emails: [] }; } function saveProcessed(processed) { fs.writeFileSync(PROCESSED_FILE, JSON.stringify(processed, null, 2)); } // Extract character name from email function extractCharacterName(subject, body) { // Try subject first const subjectMatch = subject.match(/\[([^\]]+)\]/); if (subjectMatch) return subjectMatch[1]; // Try "Role:" pattern in body const roleMatch = body.match(/Role:\s*([^\n]+)/i); if (roleMatch) { return roleMatch[1].split(',')[0].trim(); } return 'Character'; } // Extract metadata from email function extractMetadata(parsed) { const body = parsed.text || ''; const subject = parsed.subject || ''; const metadata = { characterName: extractCharacterName(subject, body), productionTitle: 'Unknown Production', productionType: 'Unknown', roleSize: 'Unknown', genre: 'Unknown', storyline: '', characterBreakdown: '' }; // Try to extract production title const titleMatch = body.match(/(?:Project|Production|Title):\s*([^\n]+)/i); if (titleMatch) metadata.productionTitle = titleMatch[1].trim(); // Try to extract production type const typeMatch = body.match(/(?:Type|Format):\s*([^\n]+)/i); if (typeMatch) metadata.productionType = typeMatch[1].trim(); // Try to extract role size const roleSizeMatch = body.match(/(?:Role|Size):\s*([^\n]+)/i); if (roleSizeMatch) metadata.roleSize = roleSizeMatch[1].trim(); // Try to extract genre const genreMatch = body.match(/Genre:\s*([^\n]+)/i); if (genreMatch) metadata.genre = genreMatch[1].trim(); // Try to extract storyline const storylineMatch = body.match(/Storyline:\s*([^\n]+)/i); if (storylineMatch) metadata.storyline = storylineMatch[1].trim(); // Try to extract character breakdown const breakdownMatch = body.match(/(?:Character|Breakdown):\s*([^\n]+)/i); if (breakdownMatch) metadata.characterBreakdown = breakdownMatch[1].trim(); return metadata; } // Parse PDF using pdfjs async function parsePDF(pdfPath) { try { const pdfjsLib = await import('pdfjs-dist/legacy/build/pdf.mjs'); const data = new Uint8Array(fs.readFileSync(pdfPath)); const loadingTask = pdfjsLib.getDocument({ data }); const pdf = await loadingTask.promise; let fullText = ''; for (let i = 1; i <= pdf.numPages; i++) { const page = await pdf.getPage(i); const textContent = await page.getTextContent(); const pageText = textContent.items.map(item => item.str).join(' '); fullText += pageText + '\n\n'; } return fullText; } catch (error) { console.error('PDF parsing error:', error.message); return ''; } } // Generate guide via Prep101 API async function generateGuide(metadata, sceneText, actorName, actorAge) { const payload = { characterName: metadata.characterName, actorName: actorName, actorAge: actorAge, productionTitle: metadata.productionTitle, productionType: metadata.productionType, roleSize: metadata.roleSize, genre: metadata.genre, storyline: metadata.storyline, characterBreakdown: metadata.characterBreakdown, sceneText: sceneText }; console.log('Calling Prep101 API...'); const response = await fetch(PREP101_API, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': PREP101_KEY }, body: JSON.stringify(payload) }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Prep101 API error: ${response.status} ${errorText}`); } return await response.json(); } // Send WhatsApp notification async function notifyWhatsApp(message) { try { // Using OpenClaw's message tool via CLI await execPromise(`openclaw message send --to "+15595723897" --channel whatsapp --message "${message.replace(/"/g, '\\"')}"`); } catch (error) { console.error('WhatsApp notification failed:', error.message); } } // Process a single email async function processEmail(parsed, messageId) { try { console.log(`\nšŸ“§ Processing email: ${parsed.subject}`); // Check for PDF attachment const pdfAttachment = parsed.attachments.find(a => a.filename && a.filename.endsWith('.pdf')); if (!pdfAttachment) { console.log('āš ļø No PDF attachment found, skipping'); return; } console.log(`šŸ“Ž Found PDF: ${pdfAttachment.filename}`); // Save PDF temporarily const pdfPath = `/tmp/sides_${Date.now()}.pdf`; fs.writeFileSync(pdfPath, pdfAttachment.content); // Parse PDF console.log('šŸ“„ Parsing PDF...'); const sceneText = await parsePDF(pdfPath); if (!sceneText || sceneText.length < 50) { console.log('āš ļø PDF parsing failed or no text extracted'); fs.unlinkSync(pdfPath); return; } console.log(`āœ… Extracted ${sceneText.length} characters from PDF`); // Extract metadata const metadata = extractMetadata(parsed); console.log('šŸ“‹ Metadata:', metadata); // Generate guide (using default actor for now) console.log('šŸ¤– Generating guide...'); const result = await generateGuide(metadata, sceneText, 'Actor', 15); if (!result.success) { throw new Error('Guide generation failed'); } // Save guide const guideName = `${metadata.characterName.replace(/[^a-zA-Z0-9]/g, '_')}_${Date.now()}`; const guidePath = `/root/.openclaw/workspace/Projects/Casting/Output/${guideName}.html`; fs.writeFileSync(guidePath, result.guideHtml); console.log(`āœ… Guide saved: ${guidePath}`); // Notify via WhatsApp await notifyWhatsApp(`āœ… New audition guide generated!\n\nšŸ“‹ Character: ${metadata.characterName}\nšŸŽ¬ Production: ${metadata.productionTitle}\nšŸ“ File: ${guideName}.html`); // Cleanup fs.unlinkSync(pdfPath); } catch (error) { console.error('āŒ Error processing email:', error.message); } } // Main email checker function checkEmails() { return new Promise((resolve, reject) => { const imap = new Imap({ user: creds.email.user, password: creds.email.pass, host: creds.email.host, port: creds.email.port, tls: creds.email.tls, tlsOptions: { rejectUnauthorized: false } }); const processed = loadProcessed(); imap.once('ready', () => { imap.openBox('INBOX', false, (err, box) => { if (err) { reject(err); return; } // Search for unread emails from today const searchCriteria = ['UNSEEN', ['SINCE', new Date()]]; imap.search(searchCriteria, (err, results) => { if (err) { reject(err); return; } if (!results || results.length === 0) { console.log('šŸ“­ No new emails'); imap.end(); resolve(); return; } console.log(`šŸ“¬ Found ${results.length} new email(s)`); const fetch = imap.fetch(results, { bodies: '' }); fetch.on('message', (msg, seqno) => { msg.on('body', (stream) => { simpleParser(stream, async (err, parsed) => { if (err) { console.error('Parse error:', err); return; } const messageId = parsed.messageId; // Skip if already processed if (processed.emails.includes(messageId)) { console.log('ā­ļø Already processed:', messageId); return; } // Process email await processEmail(parsed, messageId); // Mark as processed processed.emails.push(messageId); saveProcessed(processed); }); }); }); fetch.once('end', () => { console.log('āœ… Finished checking emails'); imap.end(); }); }); }); }); imap.once('error', (err) => { reject(err); }); imap.once('end', () => { resolve(); }); imap.connect(); }); } // Main loop async function main() { console.log('šŸ¤– Clawdbot Email Watcher Started'); console.log(`šŸ“§ Monitoring: ${creds.email.user}`); console.log(`ā° Check interval: ${WATCH_INTERVAL / 1000}s\n`); while (true) { try { await checkEmails(); } catch (error) { console.error('āŒ Email check error:', error.message); } // Wait before next check await new Promise(resolve => setTimeout(resolve, WATCH_INTERVAL)); } } // Start main().catch(console.error);