added notification and cancellation mail
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
APP_ENV=production
|
||||
APP_DEBUG=false
|
||||
APP_DEBUG=true
|
||||
APP_KEY=base64:aj3bR0zA9I8nZ1Rm5alncE4QFTPNoHVkd8YSRJEImwY=
|
||||
APP_URL=https://yogibook.yogasoul.it
|
||||
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
// Abilita visualizzazione errori PHP
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use PHPMailer\PHPMailer\Exception;
|
||||
|
||||
// Connessione al database
|
||||
include('include/headscript.php');
|
||||
|
||||
$conn = new mysqli($servername, $username, $password, $dbname);
|
||||
if ($conn->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 = "<p style='font-size: 14px; line-height: 190%;'><span style='font-size: 18px; line-height: 34.2px;'><strong><span style='line-height: 34.2px; font-size: 18px;'> Ciao $firstname , </span></strong></span></p>
|
||||
<p style='font-size: 14px; line-height: 190%;'><span style='font-size: 16px; line-height: 30.4px;'>La tua lezione è stata cancellata con successo! </span></p>
|
||||
<p style='font-size: 14px; line-height: 190%;'><span style='font-size: 16px; line-height: 30.4px;'>Dettaglio: $newtimeformat</span></p>
|
||||
<br>
|
||||
<p style='font-size: 14px; line-height: 190%;'><span style='font-size: 16px; line-height: 30.4px;'>Per vedere e gestire le tue lezioni clicca qui: https://yogibook.yogasoul.it </span></p>
|
||||
<br>
|
||||
<p style='font-size: 14px; line-height: 190%;'><span style='font-size: 16px; line-height: 30.4px;'>Ci vediamo sul tappetino!</span></p>
|
||||
<p style='font-size: 14px; line-height: 190%;'><span style='font-size: 16px; line-height: 30.4px;'>Il Team Yogasoul</span></p>";
|
||||
|
||||
// Definisci $messageedit per il template
|
||||
$messageedit = $messagecancel;
|
||||
|
||||
// Definisci $buttonedit (pulsante generico)
|
||||
$buttonedit = "<a href='https://yogibook.yogasoul.it/' target='_blank' class='v-button v-font-size' style='box-sizing: border-box;display: inline-block;text-decoration: none;-webkit-text-size-adjust: none;text-align: center;color: #FFFFFF; background-color: #3AAEE0; border-radius: 4px;-webkit-border-radius: 4px; -moz-border-radius: 4px; width:auto; max-width:100%; overflow-wrap: break-word; word-break: break-word; word-wrap:break-word; mso-border-alt: none;font-size: 14px;'>
|
||||
<span style='display:block;padding:10px 20px;line-height:120%;'><span style='line-height: 16.8px;'>YogiBook - YogaSoul</span></span>
|
||||
</a>";
|
||||
|
||||
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 "<h1>Cancellazione confermata</h1>";
|
||||
echo "<p>La lezione del $newtimeformat è stata cancellata con successo.</p>";
|
||||
echo "<a href='https://yogibook.yogasoul.it'>Torna al portale</a>";
|
||||
} 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();
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
require_once('include/headscript.php');
|
||||
|
||||
$conn = mysqli_connect($servername, $username, $password, $dbname);
|
||||
|
||||
if (!$conn) {
|
||||
die(json_encode(['error' => '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);
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
require_once('include/headscript.php');
|
||||
|
||||
$conn = mysqli_connect($servername, $username, $password, $dbname);
|
||||
|
||||
if (!$conn) {
|
||||
die(json_encode(['error' => '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]);
|
||||
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
// Abilita visualizzazione errori PHP
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use PHPMailer\PHPMailer\Exception;
|
||||
|
||||
include('include/headscript.php');
|
||||
|
||||
// Verifica connessione al database
|
||||
$conn = new mysqli($servername, $username, $password, $dbname);
|
||||
if ($conn->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 = "<p style='font-size: 14px; line-height: 190%;'><span style='font-size: 18px; line-height: 34.2px;'><strong>Ciao $firstname,</strong></span></p>
|
||||
<p style='font-size: 14px; line-height: 190%;'><span style='font-size: 16px; line-height: 30.4px;'>Promemoria: domani hai la lezione $servicename del $dataformat.</span></p>
|
||||
<p style='font-size: 14px; line-height: 190%;'><span style='font-size: 16px; line-height: 30.4px;'>Puoi cancellarla fino alle 12:00 cliccando qui:</span></p>
|
||||
<a href='$link' target='_blank'>Cancella prenotazione</a>
|
||||
<br>
|
||||
<p style='font-size: 14px; line-height: 190%;'><span style='font-size: 16px; line-height: 30.4px;'>Ci vediamo sul tappetino!</span></p>
|
||||
<p style='font-size: 14px; line-height: 190%;'><span style='font-size: 16px; line-height: 30.4px;'>Il Team Yogasoul</span></p>";
|
||||
|
||||
// Definisci $messageedit per il template
|
||||
$messageedit = $message;
|
||||
|
||||
// Definisci $buttonedit (vuoto o pulsante generico)
|
||||
$buttonedit = "<a href='https://yogibook.yogasoul.it/' target='_blank' class='v-button v-font-size' style='box-sizing: border-box;display: inline-block;text-decoration: none;-webkit-text-size-adjust: none;text-align: center;color: #FFFFFF; background-color: #3AAEE0; border-radius: 4px;-webkit-border-radius: 4px; -moz-border-radius: 4px; width:auto; max-width:100%; overflow-wrap: break-word; word-break: break-word; word-wrap:break-word; mso-border-alt: none;font-size: 14px;'>
|
||||
<span style='display:block;padding:10px 20px;line-height:120%;'><span style='line-height: 16.8px;'>YogiBook - YogaSoul</span></span>
|
||||
</a>";
|
||||
|
||||
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();
|
||||
@@ -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)
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
require_once('include/headscript.php');
|
||||
|
||||
$conn = mysqli_connect($servername, $username, $password, $dbname);
|
||||
|
||||
if (!$conn) {
|
||||
die(json_encode(['success' => 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);
|
||||
+619
-322
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 569 KiB |
@@ -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 <p>/<br>; 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.
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.<questa>
|
||||
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 = "<p>{}</p>".format(html_lib.escape(s).replace("\n", "<br>"))
|
||||
return "<!doctype html><html><body>{}</body></html>".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"<blockquote style='margin:0 0 0 1em; padding-left:1em; border-left:3px solid #ddd'>{escaped}</blockquote>"
|
||||
)
|
||||
|
||||
def _inject_before_body_end(html_src: str, addition: str) -> str:
|
||||
"""Inserisce 'addition' prima di </body> in modo case-insensitive; se manca </body>, appende."""
|
||||
m = re.search(r'</\s*body\s*>', 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 <a href='link'>Iscriviti qui</a> per prenotazioni,
|
||||
e il calendario <a href='https://yogasoul.it/wp-content/uploads/2025/08/Calendario-settembre-2025-2.jpg'>qui</a>.
|
||||
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 <a href='link'>Iscriviti qui</a> per prenotazioni,\n"
|
||||
"e il calendario <a href='https://yogasoul.it/wp-content/uploads/2025/08/Calendario-settembre-2025-2.jpg'>qui</a>.\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 <p>, <ul>/<li> se utile; niente CSS superfluo).\n"
|
||||
)
|
||||
|
||||
{policy}
|
||||
prompt = render_prompt(
|
||||
template,
|
||||
nickname=nickname,
|
||||
policy=policy,
|
||||
kb_json=kb_json,
|
||||
email_text=email_text,
|
||||
)
|
||||
|
||||
Knowledge Base:
|
||||
{kb_json}
|
||||
|
||||
Email ricevuta:
|
||||
{email_text}
|
||||
|
||||
Scrivi la risposta in HTML pulito (usa <p>, <ul>/<li> se utile; niente CSS superfluo).
|
||||
"""
|
||||
resp = client.chat.completions.create(
|
||||
model=OPENAI_MODEL,
|
||||
messages=[{"role": "user", "content": prompt}],
|
||||
@@ -283,7 +340,7 @@ def fetch_all_unseen(mail, limit=None):
|
||||
return results
|
||||
|
||||
if limit and limit > 0:
|
||||
ids = ids[-limit:] # ⬅️ prendi SOLO le ultime N non lette
|
||||
ids = ids[-limit:] # prendi SOLO le ultime N non lette
|
||||
|
||||
for seq_id in ids:
|
||||
typ, msg_data = mail.fetch(seq_id, "(RFC822)")
|
||||
@@ -335,9 +392,10 @@ def fetch_all_unseen(mail, limit=None):
|
||||
|
||||
def prepare_reply_mime(email_info, response_html):
|
||||
"""Costruisce il MIME HTML della risposta con quote e thread headers."""
|
||||
reply_body = (response_html.replace("</body>", _make_quoted_original(email_info.get("body_text","")) + "</body>")
|
||||
if "</body>" in response_html.lower()
|
||||
else response_html + _make_quoted_original(email_info.get("body_text","")))
|
||||
reply_body = _inject_before_body_end(
|
||||
response_html,
|
||||
_make_quoted_original(email_info.get("body_text",""))
|
||||
)
|
||||
|
||||
msg = MIMEText(reply_body, "html", "utf-8")
|
||||
subj = email_info.get("subject") or "[Risposta automatica] - YogaSoul"
|
||||
@@ -388,9 +446,23 @@ def append_draft(mail, folder, msg):
|
||||
# MAIN
|
||||
# ==========================
|
||||
def main():
|
||||
if not assert_config():
|
||||
# Controllo config di base
|
||||
problems = []
|
||||
if not isinstance(IMAP_SERVER, str) or not IMAP_SERVER.strip():
|
||||
problems.append("imap_server mancante in config.json")
|
||||
if not isinstance(EMAIL_ADDRESS, str) or not EMAIL_ADDRESS.strip():
|
||||
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 config.json:")
|
||||
for p in problems:
|
||||
print(" -", p)
|
||||
return
|
||||
|
||||
# Login IMAP
|
||||
try:
|
||||
mail = imaplib.IMAP4_SSL(IMAP_SERVER)
|
||||
mail.login(EMAIL_ADDRESS, EMAIL_PASSWORD)
|
||||
@@ -403,7 +475,7 @@ def main():
|
||||
drafts_folder, delim = ensure_drafts_folder(mail)
|
||||
print(f"[DEBUG] drafts_folder='{drafts_folder}' delim='{delim}'")
|
||||
|
||||
emails = fetch_all_unseen(mail, limit=MAX_TO_PROCESS) # ⬅️ usa il limite
|
||||
emails = fetch_all_unseen(mail, limit=MAX_TO_PROCESS)
|
||||
if not emails:
|
||||
print("Nessuna nuova email da processare.")
|
||||
mail.logout()
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
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 <a href='link'>Iscriviti qui</a> per prenotazioni,
|
||||
e il calendario <a href='https://yogasoul.it/wp-content/uploads/2025/08/Calendario-settembre-2025-2.jpg'>qui</a>.
|
||||
Se non sai, scrivi: "Contattami per dettagli! 🧘♀️".
|
||||
|
||||
{{policy}}
|
||||
|
||||
Knowledge Base:
|
||||
{{kb_json}}
|
||||
|
||||
Email ricevuta:
|
||||
{{email_text}}
|
||||
|
||||
Scrivi la risposta in HTML pulito (usa <p>, <ul>/<li> se utile; niente CSS superfluo).
|
||||
Reference in New Issue
Block a user