import { execSync } from 'child_process' import { readFileSync, writeFileSync } from 'fs' import { randomUUID } from 'crypto' const BASE = "/Users/nate.emmert/Documents/github/Siteforge/Verse by Verse with Nate Complete Series" const CHATBOT_FILE = "/Users/nate.emmert/Documents/github/Siteforge/data/chatbot-content.json" const FILES = [ { file: `${BASE}/Done/Verse_by_Verse_with_Nate_Episode02.docx`, ep: 2 }, { file: `${BASE}/Done/Verse_by_Verse_with_Nate_Episode03.docx`, ep: 3 }, { file: `${BASE}/Done/Verse_by_Verse_with_Nate_Episode04_Updated.docx`, ep: 4 }, { file: `${BASE}/Verse_by_Verse_with_Nate_Episode05.docx`, ep: 5 }, { file: `${BASE}/Verse_by_Verse_with_Nate_Episode06_expanded.docx`, ep: 6 }, { file: `${BASE}/Verse_by_Verse_with_Nate_Episode07.docx`, ep: 7 }, { file: `${BASE}/Verse_by_Verse_with_Nate_Episode08.docx`, ep: 8 }, { file: `${BASE}/Verse_by_Verse_with_Nate_Episode09.docx`, ep: 9 }, { file: `${BASE}/Verse_by_Verse_with_Nate_Episode10.docx`, ep: 10 }, { file: `${BASE}/Verse_by_Verse_with_Nate_Episode11.docx`, ep: 11 }, { file: `${BASE}/Verse_by_Verse_with_Nate_Episode12.docx`, ep: 12 }, { file: `${BASE}/Verse_by_Verse_with_Nate_Episode13.docx`, ep: 13 }, { file: `${BASE}/Verse_by_Verse_with_Nate_Episode14.docx`, ep: 14 }, ] function extractText(filePath) { const xml = execSync(`unzip -p "${filePath}" word/document.xml 2>/dev/null`, { encoding: 'utf8' }) return xml .replace(/<[^>]+>/g, ' ') .replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'") .replace(/\s+/g, ' ') .trim() } function parseEpisode(raw, epNum) { // Extract episode subtitle and passage reference from header const headerMatch = raw.match(/EPISODE\s+\d+\s*[โ€”\-\u2013\u2014]+\s*(.+?)\s+(Titus\s+[\d:]+(?:\s*[\-\u2013\u2014]+\s*[\d:]+)?)\s*ยท/i) const subtitle = headerMatch ? headerMatch[1].trim().replace(/\s+/g, ' ') : '' const passageRef = headerMatch ? headerMatch[2].trim() : 'Titus' const episodeTitle = `Episode ${epNum} โ€” ${subtitle || 'Verse by Verse with Nate'}` // Find where the actual teaching content starts let contentStart = raw.indexOf('SEGMENT 1') if (contentStart === -1) contentStart = raw.indexOf('WHO WAS PAUL') if (contentStart === -1) contentStart = raw.indexOf('COLD OPEN') if (contentStart === -1) contentStart = 400 const rawContent = raw.slice(contentStart, contentStart + 4000) const content = rawContent .replace(/\[[^\]]{0,100}\]/g, '') // remove [stage directions] .replace(/[โœ๐ŸŽ™๏ธ๐Ÿ“–๐Ÿ’ฌ๐Ÿง ๐Ÿ’ก๐Ÿ”‘โœ…โ—€โ–ถ]/gu, '') // remove emoji .replace(/SEGMENT\s+\d+\s*[โ€”\-\u2013]+\s*/g, '\n\n') // turn SEGMENT headers into breaks .replace(/\s{2,}/g, ' ') .trim() // Build keyword list const verseRefs = [...new Set((raw.match(/Titus\s+\d+:\d+/g) || []))].slice(0, 5).map(k => k.toLowerCase()) const titleWords = subtitle.toLowerCase().replace(/[^a-z0-9\s]/g, ' ').split(/\s+/).filter(w => w.length > 3) const keywords = [...new Set([ 'titus', `episode ${epNum}`, passageRef.toLowerCase(), ...verseRefs, ...titleWords ])].slice(0, 20) return { id: randomUUID(), type: 'episode', title: `${episodeTitle} (${passageRef})`, content: content.slice(0, 3900), keywords, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), } } // Load existing entries (keep the 8 hand-written ones) const existing = JSON.parse(readFileSync(CHATBOT_FILE, 'utf8')) // Remove any previously generated episode entries to avoid duplication const baseEntries = existing.filter(e => e.type !== 'episode') const newEntries = [] for (const { file, ep } of FILES) { try { const raw = extractText(file) const entry = parseEpisode(raw, ep) newEntries.push(entry) console.log(`โœ“ Ep ${ep}: ${entry.title.slice(0, 80)}`) } catch (err) { console.error(`โœ— Ep ${ep}: ${err.message}`) } } const combined = [...baseEntries, ...newEntries] writeFileSync(CHATBOT_FILE, JSON.stringify(combined, null, 2), 'utf8') console.log(`\nDone. ${newEntries.length} episode entries added. Total: ${combined.length}`)