diff --git a/.env b/.env index 1d4b7a7..c6c153a 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ APP_ENV=production -APP_DEBUG=false +APP_DEBUG=true APP_KEY=base64:aj3bR0zA9I8nZ1Rm5alncE4QFTPNoHVkd8YSRJEImwY= APP_URL=https://yogibook.yogasoul.it diff --git a/public/cancella-prenotazione.php b/public/cancella-prenotazione.php new file mode 100644 index 0000000..2980324 --- /dev/null +++ b/public/cancella-prenotazione.php @@ -0,0 +1,139 @@ +connect_error) { + die("Connessione al database fallita: " . $conn->connect_error); +} + +// Recupera parametri GET: idbookingclass e token +if (isset($_GET['idbookingclass']) && isset($_GET['token'])) { + $idbookingclass = $_GET['idbookingclass']; + $token = $_GET['token']; + + // Verifica validità: token corrisponde, lezione futura e prima delle 12:00 del giorno + $query = "SELECT * FROM bookingclass + WHERE idbookingclass = ? + AND cancellation_token = ? + AND status = 'booked' + AND bookingstart > NOW() + AND NOW() <= DATE_ADD(DATE(bookingstart), INTERVAL 12 HOUR)"; + $stmt = $conn->prepare($query); + + if ($stmt) { + $stmt->bind_param("is", $idbookingclass, $token); + $stmt->execute(); + $result = $stmt->get_result(); + + if ($result->num_rows > 0) { + $row = $result->fetch_assoc(); + $bookingstart = $row['bookingstart']; + $newtimeformat = date("d-m-Y H:i", strtotime($bookingstart)); + + // Aggiorna status a 'cancelled' e invalida token + $updateQuery = "UPDATE bookingclass + SET status = 'cancelled', cancellation_token = NULL + WHERE idbookingclass = ?"; + $updateStmt = $conn->prepare($updateQuery); + $updateStmt->bind_param("i", $idbookingclass); + $updateStmt->execute(); + + // Recupera dati utente e servizio + $dataQuery = "SELECT bookingclass.*, auth_users.*, service.* + FROM bookingclass + LEFT JOIN auth_users ON bookingclass.iduser = auth_users.id + LEFT JOIN service ON bookingclass.idservice = service.idservice + WHERE bookingclass.idbookingclass = ?"; + $dataStmt = $conn->prepare($dataQuery); + $dataStmt->bind_param("i", $idbookingclass); + $dataStmt->execute(); + $dataResult = $dataStmt->get_result(); + $dataRow = $dataResult->fetch_assoc(); + + $emailuser = $dataRow['email']; + $firstname = $dataRow['first_name'] ?? 'Utente'; + + // Prepara messaggio email + $messagecancel = "

Ciao $firstname ,

+

La tua lezione è stata cancellata con successo!

+

Dettaglio: $newtimeformat

+
+

Per vedere e gestire le tue lezioni clicca qui: https://yogibook.yogasoul.it

+
+

Ci vediamo sul tappetino!

+

Il Team Yogasoul

"; + + // Definisci $messageedit per il template + $messageedit = $messagecancel; + + // Definisci $buttonedit (pulsante generico) + $buttonedit = " + YogiBook - YogaSoul + "; + + require 'phpmailer/src/Exception.php'; + require 'phpmailer/src/PHPMailer.php'; + require 'phpmailer/src/SMTP.php'; + + $mail = new PHPMailer(true); + try { + $mail->isSMTP(); + $mail->Host = 'mail.yogasoul.it'; + $mail->SMTPAuth = true; + $mail->Username = 'info@yogasoul.it'; + $mail->Password = '!Testolina88'; + $mail->SMTPSecure = 'tls'; + $mail->Port = 587; + + // Verifica esistenza file template + if (!file_exists('mail/emailtemplate2.php')) { + throw new Exception("File emailtemplate2.php non trovato."); + } + include('mail/emailtemplate2.php'); + + // Verifica che $mailmessage1 esista + if (!isset($mailmessage1)) { + throw new Exception("Variabile \$mailmessage1 non definita in emailtemplate2.php."); + } + + // Sostituisci placeholder (per compatibilità) + $htmlContent = str_replace('{message}', $messagecancel, $mailmessage1); + + $mail->From = 'info@yogasoul.it'; + $mail->FromName = 'YogiBook [YogaSoul]'; + $mail->addAddress($emailuser); + $mail->Subject = "YogiBook - Lezione cancellata con successo!"; + $mail->Body = $htmlContent; + $mail->AltBody = 'This is the body in plain text for non-HTML mail clients'; + + $mail->send(); + + // Mostra landing di conferma + echo "

Cancellazione confermata

"; + echo "

La lezione del $newtimeformat è stata cancellata con successo.

"; + echo "Torna al portale"; + } catch (Exception $e) { + echo "Errore invio email: " . $mail->ErrorInfo; + } + } else { + echo "Link non valido o scaduto."; + } + $stmt->close(); + } else { + echo "Errore nella preparazione della query: " . $conn->error; + } +} else { + echo "Parametri mancanti."; +} + +$conn->close(); + diff --git a/public/fetch_available_classes.php b/public/fetch_available_classes.php new file mode 100644 index 0000000..e1ebbfb --- /dev/null +++ b/public/fetch_available_classes.php @@ -0,0 +1,26 @@ + 'Connessione al database fallita'])); +} + +// Query per ottenere tutte le classi disponibili +$sql = "SELECT id, servicename, day, time FROM classes ORDER BY servicename, day, time"; +$result = mysqli_query($conn, $sql); + +$classes = []; +while ($row = mysqli_fetch_assoc($result)) { + $classes[] = [ + 'id' => $row['id'], + 'servicename' => $row['servicename'], + 'day' => $row['day'], + 'time' => $row['time'] + ]; +} + +mysqli_close($conn); +header('Content-Type: application/json'); +echo json_encode($classes); diff --git a/public/fetch_available_dates.php b/public/fetch_available_dates.php new file mode 100644 index 0000000..20aee7c --- /dev/null +++ b/public/fetch_available_dates.php @@ -0,0 +1,35 @@ + 'Connessione al database fallita'])); +} + +$class_id = isset($_POST['class_id']) ? intval($_POST['class_id']) : 0; + +if ($class_id <= 0) { + die(json_encode(['error' => 'ID classe non valido'])); +} + +// Query per ottenere le date disponibili per la classe specificata +// Supponiamo che ci sia una tabella 'class_schedule' con le date disponibili +$sql = "SELECT DISTINCT DATE(bookingstart) as available_date + FROM class_schedule + WHERE class_id = ? + ORDER BY available_date"; +$stmt = mysqli_prepare($conn, $sql); +mysqli_stmt_bind_param($stmt, 'i', $class_id); +mysqli_stmt_execute($stmt); +$result = mysqli_stmt_get_result($stmt); + +$availableDates = []; +while ($row = mysqli_fetch_assoc($result)) { + $availableDates[] = $row['available_date']; +} + +mysqli_stmt_close($stmt); +mysqli_close($conn); +header('Content-Type: application/json'); +echo json_encode(['availableDates' => $availableDates]); diff --git a/public/promemoria-cron.php b/public/promemoria-cron.php new file mode 100644 index 0000000..c120d85 --- /dev/null +++ b/public/promemoria-cron.php @@ -0,0 +1,162 @@ +connect_error) { + die("Connessione al database fallita: " . $conn->connect_error); +} + +// Inizializza contatore e log +$emailCount = 0; +$errors = []; +$logFile = 'promemoria_cron_log.txt'; +$logMessage = "Esecuzione cron: " . date('Y-m-d H:i:s') . "\n"; + +// Seleziona prenotazioni per domani +$tomorrow = date('Y-m-d', strtotime('+1 day')); +$query = "SELECT bc.*, au.email, au.first_name, s.servicename + FROM bookingclass bc + LEFT JOIN auth_users au ON bc.iduser = au.id + LEFT JOIN service s ON bc.idservice = s.idservice + WHERE DATE(bc.bookingstart) = ? AND bc.status = 'booked'"; + +$stmt = $conn->prepare($query); +if (!$stmt) { + $errors[] = "Errore preparazione query: " . $conn->error; + file_put_contents($logFile, $logMessage . "Errore query: " . $conn->error . "\n", FILE_APPEND); + echo "Errore preparazione query: " . $conn->error; + exit; +} + +$stmt->bind_param("s", $tomorrow); +$stmt->execute(); +$result = $stmt->get_result(); + +if ($result->num_rows === 0) { + $logMessage .= "Nessuna prenotazione trovata per domani.\n"; + file_put_contents($logFile, $logMessage, FILE_APPEND); + echo "Nessuna prenotazione trovata per domani.\n"; + exit; +} + +while ($row = $result->fetch_assoc()) { + $idbookingclass = $row['idbookingclass']; + $token = $row['cancellation_token']; + + // Genera token se assente + if (empty($token)) { + $token = bin2hex(random_bytes(32)); + $updateQuery = "UPDATE bookingclass SET cancellation_token = ? WHERE idbookingclass = ?"; + $updateStmt = $conn->prepare($updateQuery); + if ($updateStmt) { + $updateStmt->bind_param("si", $token, $idbookingclass); + $updateStmt->execute(); + } else { + $errors[] = "Errore preparazione query token per ID $idbookingclass: " . $conn->error; + } + } + + $firstname = $row['first_name'] ?? 'Utente'; + $emailuser = $row['email']; + $servicename = $row['servicename'] ?? 'Sconosciuta'; + $bookingstart = $row['bookingstart']; + $dataformat = date("d-m-Y H:i", strtotime($bookingstart)); + + // Verifica email valida + if (!filter_var($emailuser, FILTER_VALIDATE_EMAIL)) { + $errors[] = "Email non valida per ID $idbookingclass: $emailuser"; + $logMessage .= "Email non valida per ID $idbookingclass: $emailuser\n"; + continue; + } + + // Link cancellazione + $link = "https://yogibook.yogasoul.it/cancella-prenotazione.php?idbookingclass=$idbookingclass&token=$token"; + + // Messaggio email + $message = "

Ciao $firstname,

+

Promemoria: domani hai la lezione $servicename del $dataformat.

+

Puoi cancellarla fino alle 12:00 cliccando qui:

+Cancella prenotazione +
+

Ci vediamo sul tappetino!

+

Il Team Yogasoul

"; + + // Definisci $messageedit per il template + $messageedit = $message; + + // Definisci $buttonedit (vuoto o pulsante generico) + $buttonedit = " + YogiBook - YogaSoul + "; + + require 'phpmailer/src/Exception.php'; + require 'phpmailer/src/PHPMailer.php'; + require 'phpmailer/src/SMTP.php'; + + $mail = new PHPMailer(true); + try { + $mail->isSMTP(); + $mail->Host = 'mail.yogasoul.it'; + $mail->SMTPAuth = true; + $mail->Username = 'info@yogasoul.it'; + $mail->Password = '!Testolina88'; + $mail->SMTPSecure = 'tls'; + $mail->Port = 587; + + // Verifica esistenza file template + if (!file_exists('mail/emailtemplate2.php')) { + throw new Exception("File emailtemplate2.php non trovato."); + } + include('mail/emailtemplate2.php'); + + // Verifica che $mailmessage1 esista + if (!isset($mailmessage1)) { + throw new Exception("Variabile \$mailmessage1 non definita in emailtemplate2.php."); + } + + // Sostituisci placeholder (anche se non usato, per compatibilità) + $htmlContent = str_replace('{message}', $message, $mailmessage1); + + $mail->From = 'info@yogasoul.it'; + $mail->FromName = 'YogiBook [YogaSoul]'; + $mail->addAddress($emailuser); + $mail->Subject = "YogiBook - Promemoria lezione domani!"; + $mail->Body = $htmlContent; + $mail->AltBody = 'Promemoria lezione.'; + + $mail->send(); + $emailCount++; + $logMessage .= "Email inviata a $emailuser per lezione ID $idbookingclass ($dataformat)\n"; + } catch (Exception $e) { + $errors[] = "Errore invio email a $emailuser (ID $idbookingclass): " . $mail->ErrorInfo; + $logMessage .= "Errore invio a $emailuser (ID $idbookingclass): " . $mail->ErrorInfo . "\n"; + } + + sleep(2); // Delay 2 secondi +} + +// Scrivi log +file_put_contents($logFile, $logMessage, FILE_APPEND); + +// Output debug +echo "Esecuzione completata: $emailCount email inviate.\n"; +if (!empty($errors)) { + echo "Errori rilevati:\n"; + foreach ($errors as $error) { + echo "- $error\n"; + } +} else { + echo "Nessun errore.\n"; +} +echo "Dettagli nel file di log: $logFile\n"; + +$conn->close(); diff --git a/public/promemoria_cron_log.txt b/public/promemoria_cron_log.txt new file mode 100644 index 0000000..1e0540e --- /dev/null +++ b/public/promemoria_cron_log.txt @@ -0,0 +1,4 @@ +Esecuzione cron: 2025-10-08 13:39:42 +Esecuzione cron: 2025-10-08 13:40:48 +Esecuzione cron: 2025-10-08 13:54:46 +Email inviata a info@claudiosironi.com per lezione ID 8 (09-10-2025 18:15) diff --git a/public/reprogram_class.php b/public/reprogram_class.php new file mode 100644 index 0000000..08e2128 --- /dev/null +++ b/public/reprogram_class.php @@ -0,0 +1,57 @@ + false, 'message' => 'Connessione al database fallita'])); +} + +$id_booking_class = isset($_POST['id']) ? intval($_POST['id']) : 0; +$class_id = isset($_POST['class_id']) ? intval($_POST['class_id']) : 0; +$new_date = isset($_POST['new_date']) ? $_POST['new_date'] : ''; + +if ($id_booking_class <= 0 || $class_id <= 0 || empty($new_date)) { + die(json_encode(['success' => false, 'message' => 'Dati non validi'])); +} + +// Ottieni i dettagli della classe attuale +$sql = "SELECT bookingstart, servicename, day, time FROM bookingclass WHERE idbookingclass = ?"; +$stmt = mysqli_prepare($conn, $sql); +mysqli_stmt_bind_param($stmt, 'i', $id_booking_class); +mysqli_stmt_execute($stmt); +$result = mysqli_stmt_get_result($stmt); +$current_class = mysqli_fetch_assoc($result); + +if (!$current_class) { + die(json_encode(['success' => false, 'message' => 'Lezione non trovata'])); +} + +// Ottieni i dettagli della nuova classe +$sql = "SELECT servicename, day, time FROM classes WHERE id = ?"; +$stmt = mysqli_prepare($conn, $sql); +mysqli_stmt_bind_param($stmt, 'i', $class_id); +mysqli_stmt_execute($stmt); +$result = mysqli_stmt_get_result($stmt); +$new_class = mysqli_fetch_assoc($result); + +if (!$new_class) { + die(json_encode(['success' => false, 'message' => 'Classe non trovata'])); +} + +// Aggiorna la lezione +$sql = "UPDATE bookingclass + SET bookingstart = ?, prevbookingstart = ?, servicename = ?, day = ?, time = ? + WHERE idbookingclass = ?"; +$stmt = mysqli_prepare($conn, $sql); +$new_bookingstart = $new_date . ' ' . $new_class['time'] . ':00'; +mysqli_stmt_bind_param($stmt, 'sssssi', $new_bookingstart, $current_class['bookingstart'], $new_class['servicename'], $new_class['day'], $new_class['time'], $id_booking_class); + +if (mysqli_stmt_execute($stmt)) { + echo json_encode(['success' => true]); +} else { + echo json_encode(['success' => false, 'message' => 'Errore durante l\'aggiornamento della lezione']); +} + +mysqli_stmt_close($stmt); +mysqli_close($conn); diff --git a/public/situationusers.php b/public/situationusers.php index 0a9b585..0ffaeb2 100644 --- a/public/situationusers.php +++ b/public/situationusers.php @@ -17,7 +17,7 @@ SELECT auth_users.id AS id_utente, auth_users.first_name AS nome_utente, auth_users.last_name AS cognome_utente, - auth_users.email AS email_utente, -- Aggiungiamo l'email dell'utente + auth_users.email AS email_utente, COALESCE(lezioni_acquistate, 0) AS lezioni_acquistate, COUNT(CASE WHEN bookingclass.bookingstart <= CURDATE() AND bookingclass.lostlesson = 'N' THEN 1 END) AS lezioni_praticate, COUNT(CASE WHEN bookingclass.bookingstart > CURDATE() THEN 1 END) AS lezioni_programmate, @@ -70,7 +70,7 @@ if (!$result) { - + @@ -84,13 +84,15 @@ if (!$result) { - - - + + + @@ -456,210 +515,310 @@ if (!$result) { - - - - - - + + + \ No newline at end of file diff --git a/public/user/document/1758189938_CONSENSO SANTEX.pdf b/public/user/document/1758189938_CONSENSO SANTEX.pdf new file mode 100644 index 0000000..bf2bb04 Binary files /dev/null and b/public/user/document/1758189938_CONSENSO SANTEX.pdf differ diff --git a/public/user/document/1758189961_CONSENSO SANTEX.pdf b/public/user/document/1758189961_CONSENSO SANTEX.pdf new file mode 100644 index 0000000..bf2bb04 Binary files /dev/null and b/public/user/document/1758189961_CONSENSO SANTEX.pdf differ diff --git a/public/user/document/1758189974_CONSENSO SANTEX.pdf b/public/user/document/1758189974_CONSENSO SANTEX.pdf new file mode 100644 index 0000000..bf2bb04 Binary files /dev/null and b/public/user/document/1758189974_CONSENSO SANTEX.pdf differ diff --git a/public/user/document/1758190073_CONSENSO SANTEX.pdf b/public/user/document/1758190073_CONSENSO SANTEX.pdf new file mode 100644 index 0000000..bf2bb04 Binary files /dev/null and b/public/user/document/1758190073_CONSENSO SANTEX.pdf differ diff --git a/public/user/document/1758190086_CONSENSO SANTEX.pdf b/public/user/document/1758190086_CONSENSO SANTEX.pdf new file mode 100644 index 0000000..bf2bb04 Binary files /dev/null and b/public/user/document/1758190086_CONSENSO SANTEX.pdf differ diff --git a/public/user/document/1758448340_2.png b/public/user/document/1758448340_2.png new file mode 100644 index 0000000..f5b13ca Binary files /dev/null and b/public/user/document/1758448340_2.png differ diff --git a/public/yogasoulscript/README-ISTRUZIONI.txt b/public/yogasoulscript/README-ISTRUZIONI.txt new file mode 100644 index 0000000..7548d5a --- /dev/null +++ b/public/yogasoulscript/README-ISTRUZIONI.txt @@ -0,0 +1,36 @@ +YogaSoul Autoresponder — Istruzioni rapide + +COS'È +- Crea BOZZE di risposta (non invia) per le ultime X email NON LETTE in INBOX. +- Le bozze vengono salvate nella cartella Drafts/Bozze dell’account IMAP, in thread (In-Reply-To/References) con citazione del messaggio originale. + +FILE NELLA CARTELLA +- yogasoul_autoresponder.exe (oppure: email_autoresponder_yogasoul.py se lo usi con Python) +- config.json (credenziali e impostazioni) +- prompt_template.txt (testo/tono della risposta: modificabile liberamente) +- yogasoul_knowledge_base.json (dati corsi, link, ecc.) + +COME SI USA (Windows, senza Python) +1) Apri la cartella “YogaSoul Autoresponder”. +2) Modifica `config.json` con: + - imap_server, email_address, email_password + - openai_api_key + - max_to_process (es. 5), throttle_seconds (es. 1.5) +3) (Opz.) Modifica `prompt_template.txt` e/o `yogasoul_knowledge_base.json`. +4) Doppio click su `yogasoul_autoresponder.exe` (oppure su `run_autoresponder.bat` se fornito). +5) Controlla in Posta le BOZZE: dovresti vedere una bozza per ogni email non letta (fino a max_to_process). + +IMPOSTAZIONI UTILI (config.json) +- "max_to_process": quante email non lette processare (consigliato 5). +- "throttle_seconds": pausa tra le richieste (consigliato 1–2s). +- "mark_as_seen": true per segnare come lette dopo la bozza (false per lasciarle non lette). +- "preferred_draft_folder": cartella locale alternativa se il server non ha Drafts. + +TROUBLESHOOTING +- Nessuna bozza: verifica credenziali IMAP/API in config.json; verifica che ci siano email UNSEEN. +- Bozze “schiacciate”: il programma converte in HTML con

/
; se serve, modifica il prompt. +- Rate limit/blocchi: alza "throttle_seconds" (es. 1.5–2.0) e tieni "max_to_process" basso (5). +- Cartella bozze: il programma cerca \Drafts; in fallback crea INBOX.BozzaRisposte. + +SICUREZZA +- `config.json` contiene password in chiaro: conserva la cartella su un PC/utente fidato. diff --git a/public/yogasoulscript/config.json b/public/yogasoulscript/config.json new file mode 100644 index 0000000..f78a85a --- /dev/null +++ b/public/yogasoulscript/config.json @@ -0,0 +1,11 @@ +{ + "imap_server": "mail.yogasoul.it", + "email_address": "info@yogasoul.it", + "email_password": "!Testolina88", + "openai_api_key": "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", + "openai_model": "gpt-3.5-turbo", + "preferred_draft_folder": "BozzaRisposte", + "mark_as_seen": true, + "throttle_seconds": 2, + "max_to_process": 5 +} diff --git a/public/yogasoulscript/email_autoresponder_yogasoul.py b/public/yogasoulscript/email_autoresponder_yogasoul.py index dfa561d..a2cfeb3 100644 --- a/public/yogasoulscript/email_autoresponder_yogasoul.py +++ b/public/yogasoulscript/email_autoresponder_yogasoul.py @@ -13,20 +13,43 @@ from email.utils import parseaddr from email.mime.text import MIMEText from email.header import decode_header, make_header from datetime import datetime +from pathlib import Path +import sys # ========================== -# CONFIG (compila qui) +# CONFIG da config.json (accanto allo script/EXE) # ========================== -IMAP_SERVER = "mail.yogasoul.it" -EMAIL_ADDRESS = "info@yogasoul.it" -EMAIL_PASSWORD = "!Testolina88" # <— METTI LA PASSWORD QUI -OPENAI_API_KEY = "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" # <— METTI LA OPENAI KEY QUI -KB_PATH = "yogasoul_knowledge_base.json" -OPENAI_MODEL = "gpt-3.5-turbo" -PREFERRED_DRAFT_FOLDER = "BozzaRisposte" # se non trova Drafts, crea INBOX. -MARK_AS_SEEN = True # False per lasciare le email non lette -THROTTLE_SECONDS = 0 # ritardo tra email (0 = nessuno) -MAX_TO_PROCESS = 5 # ⬅️ NUOVO: processa al massimo N email UNSEEN (ultime) +def app_dir() -> Path: + # se "frozen" (PyInstaller), usa la cartella dell'eseguibile + return Path(sys.executable).parent if getattr(sys, "frozen", False) else Path(__file__).resolve().parent + +CONFIG_PATH = app_dir() / "config.json" + +def load_config(): + try: + with open(CONFIG_PATH, "r", encoding="utf-8") as f: + cfg = json.load(f) + print(f"[CONFIG] Caricato: {CONFIG_PATH}") + return cfg + except Exception as e: + print(f"[ERRORE] Impossibile leggere {CONFIG_PATH}: {e}") + return {} + +CFG = load_config() + +# valori letti dal config (con default sensati) +IMAP_SERVER = CFG.get("imap_server", "mail.yogasoul.it") +EMAIL_ADDRESS = CFG.get("email_address", "") +EMAIL_PASSWORD = CFG.get("email_password", "") +OPENAI_API_KEY = CFG.get("openai_api_key", "") +OPENAI_MODEL = CFG.get("openai_model", "gpt-3.5-turbo") +PREFERRED_DRAFT_FOLDER = CFG.get("preferred_draft_folder", "BozzaRisposte") +MARK_AS_SEEN = bool(CFG.get("mark_as_seen", True)) +THROTTLE_SECONDS = float(CFG.get("throttle_seconds", 0) or 0) +MAX_TO_PROCESS = int(CFG.get("max_to_process", 5) or 5) +# Percorsi esterni (accanto all'eseguibile) +KB_PATH = str(app_dir() / CFG.get("kb_path", "yogasoul_knowledge_base.json")) +PROMPT_PATH = str(app_dir() / CFG.get("prompt_path", "prompt_template.txt")) # ========================== # UTILS @@ -72,7 +95,6 @@ def _ensure_html_blocks(s): body = "

{}

".format(html_lib.escape(s).replace("\n", "
")) return "{}".format(body) - def _make_quoted_original(body_text): """Crea il blocco citato del messaggio originale, safe-escaped.""" if not body_text: @@ -84,6 +106,14 @@ def _make_quoted_original(body_text): f"
{escaped}
" ) +def _inject_before_body_end(html_src: str, addition: str) -> str: + """Inserisce 'addition' prima di in modo case-insensitive; se manca , appende.""" + m = re.search(r'', html_src, flags=re.I) + if not m: + return html_src + addition + start = m.start() + return html_src[:start] + addition + html_src[start:] + def load_knowledge_base(path=KB_PATH): if not os.path.isfile(path): print(f"[ATTENZIONE] KB non trovata: {path}. Proseguo senza.") @@ -98,20 +128,39 @@ def load_knowledge_base(path=KB_PATH): def assert_config(): problems = [] if not isinstance(IMAP_SERVER, str) or not IMAP_SERVER.strip(): - problems.append("IMAP_SERVER mancante") + problems.append("imap_server mancante in config.json") if not isinstance(EMAIL_ADDRESS, str) or not EMAIL_ADDRESS.strip(): - problems.append("EMAIL_ADDRESS mancante") - if not isinstance(EMAIL_PASSWORD, str) or not EMAIL_PASSWORD.strip() or EMAIL_PASSWORD == "INSERISCI_LA_PASSWORD": - problems.append("EMAIL_PASSWORD non impostata") - if not isinstance(OPENAI_API_KEY, str) or not OPENAI_API_KEY.strip() or OPENAI_API_KEY == "INSERISCI_OPENAI_API_KEY": - problems.append("OPENAI_API_KEY non impostata") + problems.append("email_address mancante in config.json") + if not isinstance(EMAIL_PASSWORD, str) or not EMAIL_PASSWORD.strip(): + problems.append("email_password mancante in config.json") + if not isinstance(OPENAI_API_KEY, str) or not OPENAI_API_KEY.strip(): + problems.append("openai_api_key mancante in config.json") if problems: - print("[CONFIG] Correggi questi parametri prima di proseguire:") + print("[CONFIG] Correggi config.json:") for p in problems: print(" -", p) return False return True +# ========================== +# Prompt esterno +# ========================== +def load_prompt(path: str) -> str: + try: + with open(path, "r", encoding="utf-8") as f: + print(f"[PROMPT] Caricato: {path}") + return f.read() + except Exception as e: + print(f"[ATTENZIONE] Prompt non trovato o illeggibile: {path} ({e})") + return "" + +def render_prompt(template: str, **vars_) -> str: + # Sostituzione semplice stile {{nome}} + out = template + for k, v in vars_.items(): + out = out.replace(f"{{{{{k}}}}}", str(v)) + return out + # ========================== # IMAP helpers robusti # ========================== @@ -229,25 +278,33 @@ def generate_response(email_info, kb): f"Corpo: {email_info.get('body_text','')}" ) - prompt = f""" -Sei Aurora, fondatrice di YogaSoul (www.yogasoul.it), stile zen e informale, diretto. -Rispondi in italiano, amichevole e rilassato, con emoticon yoga (🌿, 🧘‍♀️, 😊) senza esagerare. -Saluta con "Ciao {nickname}, bello sentirti!" e firma con "Namaste, Aurora - YogaSoul". -Usa la knowledge base per corsi, orari, prezzi, benefici, insegnanti. -Includi sempre il link_prenotazione specifico come Iscriviti qui per prenotazioni, -e il calendario qui. -Se non sai, scrivi: "Contattami per dettagli! 🧘‍♀️". + # Prompt da file esterno (con fallback interno) + template = load_prompt(PROMPT_PATH) + if not template: + template = ( + "Sei Aurora, fondatrice di YogaSoul (www.yogasoul.it), stile zen e informale, diretto.\n" + "Rispondi in italiano, amichevole e rilassato, con emoticon yoga (🌿, 🧘‍♀️, 😊) senza esagerare.\n" + "Saluta con \"Ciao {{nickname}}, bello sentirti!\" e firma con \"Namaste, Aurora - YogaSoul\".\n" + "Usa la knowledge base per corsi, orari, prezzi, benefici, insegnanti.\n" + "Includi sempre il link_prenotazione specifico come Iscriviti qui per prenotazioni,\n" + "e il calendario qui.\n" + "Se non sai, scrivi: \"Contattami per dettagli! 🧘‍♀️\".\n\n" + "{{policy}}\n\n" + "Knowledge Base:\n" + "{{kb_json}}\n\n" + "Email ricevuta:\n" + "{{email_text}}\n\n" + "Scrivi la risposta in HTML pulito (usa

,