added notification and cancellation mail
This commit is contained in:
parent
8c3c3982ac
commit
26fb165c98
2
.env
2
.env
@ -1,5 +1,5 @@
|
||||
APP_ENV=production
|
||||
APP_DEBUG=false
|
||||
APP_DEBUG=true
|
||||
APP_KEY=base64:aj3bR0zA9I8nZ1Rm5alncE4QFTPNoHVkd8YSRJEImwY=
|
||||
APP_URL=https://yogibook.yogasoul.it
|
||||
|
||||
|
||||
139
public/cancella-prenotazione.php
Normal file
139
public/cancella-prenotazione.php
Normal file
@ -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();
|
||||
|
||||
26
public/fetch_available_classes.php
Normal file
26
public/fetch_available_classes.php
Normal file
@ -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);
|
||||
35
public/fetch_available_dates.php
Normal file
35
public/fetch_available_dates.php
Normal file
@ -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]);
|
||||
162
public/promemoria-cron.php
Normal file
162
public/promemoria-cron.php
Normal file
@ -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();
|
||||
4
public/promemoria_cron_log.txt
Normal file
4
public/promemoria_cron_log.txt
Normal file
@ -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)
|
||||
57
public/reprogram_class.php
Normal file
57
public/reprogram_class.php
Normal file
@ -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);
|
||||
File diff suppressed because it is too large
Load Diff
BIN
public/user/document/1758189938_CONSENSO SANTEX.pdf
Normal file
BIN
public/user/document/1758189938_CONSENSO SANTEX.pdf
Normal file
Binary file not shown.
BIN
public/user/document/1758189961_CONSENSO SANTEX.pdf
Normal file
BIN
public/user/document/1758189961_CONSENSO SANTEX.pdf
Normal file
Binary file not shown.
BIN
public/user/document/1758189974_CONSENSO SANTEX.pdf
Normal file
BIN
public/user/document/1758189974_CONSENSO SANTEX.pdf
Normal file
Binary file not shown.
BIN
public/user/document/1758190073_CONSENSO SANTEX.pdf
Normal file
BIN
public/user/document/1758190073_CONSENSO SANTEX.pdf
Normal file
Binary file not shown.
BIN
public/user/document/1758190086_CONSENSO SANTEX.pdf
Normal file
BIN
public/user/document/1758190086_CONSENSO SANTEX.pdf
Normal file
Binary file not shown.
BIN
public/user/document/1758448340_2.png
Normal file
BIN
public/user/document/1758448340_2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 569 KiB |
36
public/yogasoulscript/README-ISTRUZIONI.txt
Normal file
36
public/yogasoulscript/README-ISTRUZIONI.txt
Normal file
@ -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.
|
||||
11
public/yogasoulscript/config.json
Normal file
11
public/yogasoulscript/config.json
Normal file
@ -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()
|
||||
|
||||
17
public/yogasoulscript/prompt_template.txt
Normal file
17
public/yogasoulscript/prompt_template.txt
Normal file
@ -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).
|
||||
Loading…
x
Reference in New Issue
Block a user