feat: make container shutdown instant & graceful

feat: make container shutdown instant & graceful  (#916)
feat: add pushplus notification service  (#911)
feat: option to delete ai recommendations
fix: parsing ai recommendations from gemini (#909)
This commit is contained in:
Miguel Ribeiro
2025-09-14 16:46:42 +02:00
committed by GitHub
parent c9696ff274
commit 27ac805141
31 changed files with 177 additions and 21 deletions

View File

@@ -0,0 +1,52 @@
<?php
require_once '../../includes/connect_endpoint.php';
if (isset($_SESSION['loggedin']) && $_SESSION['loggedin'] === true) {
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$input = file_get_contents('php://input');
$data = json_decode($input, true);
$recommendationId = isset($data['id']) ? (int) $data['id'] : 0;
if ($recommendationId <= 0) {
$response = [
"success" => false,
"message" => translate('error', $i18n)
];
echo json_encode($response);
exit;
}
// Delete the recommendation for the user
$stmt = $db->prepare("DELETE FROM ai_recommendations WHERE id = ? AND user_id = ?");
$stmt->bindValue(1, $recommendationId, SQLITE3_INTEGER);
$stmt->bindValue(2, $userId, SQLITE3_INTEGER);
$result = $stmt->execute();
if ($db->changes() > 0) {
$response = [
"success" => true,
"message" => translate('success', $i18n)
];
} else {
$response = [
"success" => false,
"message" => translate('error', $i18n)
];
}
echo json_encode($response);
} else {
http_response_code(405);
echo json_encode([
"success" => false,
"message" => translate('invalid_request_method', $i18n)
]);
}
} else {
$response = [
"success" => false,
"message" => translate('session_expired', $i18n)
];
echo json_encode($response);
}

View File

@@ -516,7 +516,7 @@ while ($userToNotify = $usersToNotify->fetchArray(SQLITE3_ASSOC)) {
$result = $stmt->execute(); $result = $stmt->execute();
$user = $result->fetchArray(SQLITE3_ASSOC); $user = $result->fetchArray(SQLITE3_ASSOC);
// 构建消息内容 // Build Message Content
$messageContent = ""; $messageContent = "";
if ($user['name']) { if ($user['name']) {
$messageContent = $user['name'] . ", the following subscriptions are up for renewal:\n"; $messageContent = $user['name'] . ", the following subscriptions are up for renewal:\n";
@@ -529,7 +529,7 @@ while ($userToNotify = $usersToNotify->fetchArray(SQLITE3_ASSOC)) {
$messageContent .= $subscription['name'] . " for " . $subscription['formatted_price'] . " (" . $dayText . ")\n"; $messageContent .= $subscription['name'] . " for " . $subscription['formatted_price'] . " (" . $dayText . ")\n";
} }
// 准备 PushPlus 数据 // Prepare PushPlus Data
$data = array( $data = array(
'token' => $pushplus['token'], 'token' => $pushplus['token'],
'title' => '订阅续期提醒 - Wallos', 'title' => '订阅续期提醒 - Wallos',

View File

@@ -194,6 +194,8 @@ $i18n = [
"telegram" => "Telegram", "telegram" => "Telegram",
"telegram_bot_token" => "Token služby Telegram Bot", "telegram_bot_token" => "Token služby Telegram Bot",
"telegram_chat_id" => "ID chatu v Telegramu", "telegram_chat_id" => "ID chatu v Telegramu",
"pushplus" => "Pushplus",
"pushplus_token" => "Pushplus Token",
"webhook" => "Webhook", "webhook" => "Webhook",
"webhook_url" => "Adresa URL webhooku", "webhook_url" => "Adresa URL webhooku",
"request_method" => "Metoda požadavku", "request_method" => "Metoda požadavku",

View File

@@ -194,6 +194,8 @@ $i18n = [
"telegram" => "Telegram", "telegram" => "Telegram",
"telegram_bot_token" => "Telegram Bot Token", "telegram_bot_token" => "Telegram Bot Token",
"telegram_chat_id" => "Telegram Chat ID", "telegram_chat_id" => "Telegram Chat ID",
"pushplus" => "Pushplus",
"pushplus_token" => "Pushplus Token",
"webhook" => "Webhook", "webhook" => "Webhook",
"webhook_url" => "Webhook URL", "webhook_url" => "Webhook URL",
"request_method" => "Request-metode", "request_method" => "Request-metode",

View File

@@ -193,6 +193,8 @@ $i18n = [
"telegram" => "Telegram", "telegram" => "Telegram",
"telegram_bot_token" => "Telegram Bot Token", "telegram_bot_token" => "Telegram Bot Token",
"telegram_chat_id" => "Telegram Chat ID", "telegram_chat_id" => "Telegram Chat ID",
"pushplus" => "Pushplus",
"pushplus_token" => "Pushplus Token",
"webhook" => "Webhook", "webhook" => "Webhook",
"webhook_url" => "Webhook URL", "webhook_url" => "Webhook URL",
"request_method" => "Request Methode", "request_method" => "Request Methode",

View File

@@ -193,6 +193,8 @@ $i18n = [
"telegram" => "Telegram", "telegram" => "Telegram",
"telegram_bot_token" => "Τηλεγραφήματα Bot Token", "telegram_bot_token" => "Τηλεγραφήματα Bot Token",
"telegram_chat_id" => "Τηλεγραφήματα Chat ID", "telegram_chat_id" => "Τηλεγραφήματα Chat ID",
"pushplus" => "Pushplus",
"pushplus_token" => "Pushplus Token",
"webhook" => "Webhook", "webhook" => "Webhook",
"webhook_url" => "Webhook URL", "webhook_url" => "Webhook URL",
"request_method" => "Μέθοδος αίτησης", "request_method" => "Μέθοδος αίτησης",

View File

@@ -194,6 +194,8 @@ $i18n = [
"telegram" => "Telegram", "telegram" => "Telegram",
"telegram_bot_token" => "Telegram Bot Token", "telegram_bot_token" => "Telegram Bot Token",
"telegram_chat_id" => "Telegram Chat ID", "telegram_chat_id" => "Telegram Chat ID",
"pushplus" => "Pushplus",
"pushplus_token" => "Pushplus Token",
"webhook" => "Webhook", "webhook" => "Webhook",
"webhook_url" => "Webhook URL", "webhook_url" => "Webhook URL",
"request_method" => "Request Method", "request_method" => "Request Method",

View File

@@ -193,6 +193,8 @@ $i18n = [
"telegram" => "Telegram", "telegram" => "Telegram",
"telegram_bot_token" => "Token del Bot de Telegram", "telegram_bot_token" => "Token del Bot de Telegram",
"telegram_chat_id" => "ID del Chat de Telegram", "telegram_chat_id" => "ID del Chat de Telegram",
"pushplus" => "Pushplus",
"pushplus_token" => "Token de Pushplus",
"webhook" => "Webhook", "webhook" => "Webhook",
"webhook_url" => "URL del Webhook", "webhook_url" => "URL del Webhook",
"request_method" => "Método de Solicitud", "request_method" => "Método de Solicitud",

View File

@@ -193,6 +193,8 @@ $i18n = [
"telegram" => "Telegram", "telegram" => "Telegram",
"telegram_bot_token" => "Jeton du bot Telegram", "telegram_bot_token" => "Jeton du bot Telegram",
"telegram_chat_id" => "ID de chat Telegram", "telegram_chat_id" => "ID de chat Telegram",
"pushplus" => "Pushplus",
"pushplus_token" => "Jeton Pushplus",
"webhook" => "Webhook", "webhook" => "Webhook",
"webhook_url" => "URL du webhook", "webhook_url" => "URL du webhook",
"request_method" => "Méthode de requête", "request_method" => "Méthode de requête",

View File

@@ -194,6 +194,8 @@ $i18n = [
"telegram" => "Telegram", "telegram" => "Telegram",
"telegram_bot_token" => "Token Bot Telegram", "telegram_bot_token" => "Token Bot Telegram",
"telegram_chat_id" => "ID Obrolan Telegram", "telegram_chat_id" => "ID Obrolan Telegram",
"pushplus" => "Pushplus",
"pushplus_token" => "Token Pushplus",
"webhook" => "Webhook", "webhook" => "Webhook",
"webhook_url" => "URL Webhook", "webhook_url" => "URL Webhook",
"request_method" => "Metode Permintaan", "request_method" => "Metode Permintaan",

View File

@@ -201,6 +201,8 @@ $i18n = [
"telegram" => "Telegram", "telegram" => "Telegram",
"telegram_bot_token" => "Telegram Bot Token", "telegram_bot_token" => "Telegram Bot Token",
"telegram_chat_id" => "Telegram Chat ID", "telegram_chat_id" => "Telegram Chat ID",
"pushplus" => "Pushplus",
"pushplus_token" => "Pushplus Token",
"webhook" => "Webhook", "webhook" => "Webhook",
"webhook_url" => "Webhook URL", "webhook_url" => "Webhook URL",
"request_method" => "Metodo di richiesta", "request_method" => "Metodo di richiesta",

View File

@@ -194,6 +194,8 @@ $i18n = [
"telegram" => "Telegram", "telegram" => "Telegram",
"telegram_bot_token" => "Telegramボットトークン", "telegram_bot_token" => "Telegramボットトークン",
"telegram_chat_id" => "TelegramチャットID", "telegram_chat_id" => "TelegramチャットID",
"pushplus" => "Pushplus",
"pushplus_token" => "Pushplusトークン",
"webhook" => "Webhook", "webhook" => "Webhook",
"webhook_url" => "Webhook URL", "webhook_url" => "Webhook URL",
"request_method" => "リクエストメソッド", "request_method" => "リクエストメソッド",

View File

@@ -193,6 +193,8 @@ $i18n = [
"telegram" => "텔레그램", "telegram" => "텔레그램",
"telegram_bot_token" => "텔레그램 봇 토큰", "telegram_bot_token" => "텔레그램 봇 토큰",
"telegram_chat_id" => "텔레그램 채팅 ID", "telegram_chat_id" => "텔레그램 채팅 ID",
"pushplus" => "Pushplus",
"pushplus_token" => "Pushplus 토큰",
"webhook" => "웹훅", "webhook" => "웹훅",
"webhook_url" => "웹훅 URL", "webhook_url" => "웹훅 URL",
"request_method" => "요청 메서드", "request_method" => "요청 메서드",

View File

@@ -194,6 +194,8 @@ $i18n = [
"telegram" => "Telegram", "telegram" => "Telegram",
"telegram_bot_token" => "Telegram Bot Token", "telegram_bot_token" => "Telegram Bot Token",
"telegram_chat_id" => "Telegram Chat ID", "telegram_chat_id" => "Telegram Chat ID",
"pushplus" => "Pushplus",
"pushplus_token" => "Pushplus Token",
"webhook" => "Webhook", "webhook" => "Webhook",
"webhook_url" => "Webhook URL", "webhook_url" => "Webhook URL",
"request_method" => "Request Methode", "request_method" => "Request Methode",

View File

@@ -193,6 +193,8 @@ $i18n = [
"telegram" => "Telegram", "telegram" => "Telegram",
"telegram_bot_token" => "Token bota", "telegram_bot_token" => "Token bota",
"telegram_chat_id" => "ID czatu", "telegram_chat_id" => "ID czatu",
"pushplus" => "Pushplus",
"pushplus_token" => "Token Pushplus",
"webhook" => "Webhook", "webhook" => "Webhook",
"webhook_url" => "URL webhooka", "webhook_url" => "URL webhooka",
"request_method" => "Metoda żądania", "request_method" => "Metoda żądania",

View File

@@ -193,6 +193,8 @@ $i18n = [
"telegram" => "Telegram", "telegram" => "Telegram",
"telegram_bot_token" => "Token do Bot Telegram", "telegram_bot_token" => "Token do Bot Telegram",
"telegram_chat_id" => "ID do Chat Telegram", "telegram_chat_id" => "ID do Chat Telegram",
"pushplus" => "Pushplus",
"pushplus_token" => "Token do Pushplus",
"webhook" => "Webhook", "webhook" => "Webhook",
"webhook_url" => "URL do Webhook", "webhook_url" => "URL do Webhook",
"request_method" => "Método de Pedido", "request_method" => "Método de Pedido",

View File

@@ -193,6 +193,8 @@ $i18n = [
"telegram" => "Telegram", "telegram" => "Telegram",
"telegram_bot_token" => "Token do Bot", "telegram_bot_token" => "Token do Bot",
"telegram_chat_id" => "Chat ID", "telegram_chat_id" => "Chat ID",
"pushplus" => "Pushplus",
"pushplus_token" => "Token do Pushplus",
"webhook" => "Webhook", "webhook" => "Webhook",
"webhook_url" => "URL do Webhook", "webhook_url" => "URL do Webhook",
"request_method" => "Método de requisição", "request_method" => "Método de requisição",

View File

@@ -193,6 +193,8 @@ $i18n = [
"telegram" => "Telegram", "telegram" => "Telegram",
"telegram_bot_token" => "Токен Telegram-бота", "telegram_bot_token" => "Токен Telegram-бота",
"telegram_chat_id" => "Telegram Chat ID", "telegram_chat_id" => "Telegram Chat ID",
"pushplus" => "Pushplus",
"pushplus_token" => "Токен Pushplus",
"webhook" => "Webhook", "webhook" => "Webhook",
"webhook_url" => "Webhook URL", "webhook_url" => "Webhook URL",
"request_method" => "Метод запроса", "request_method" => "Метод запроса",

View File

@@ -193,6 +193,8 @@ $i18n = [
"telegram" => "Telegram", "telegram" => "Telegram",
"telegram_bot_token" => "Telegram Bot žeton", "telegram_bot_token" => "Telegram Bot žeton",
"telegram_chat_id" => "ID klepeta Telegrama", "telegram_chat_id" => "ID klepeta Telegrama",
"pushplus" => "Pushplus",
"pushplus_token" => "Pushplus žeton",
"webhook" => "Webhook", "webhook" => "Webhook",
"webhook_url" => "Webhook URL", "webhook_url" => "Webhook URL",
"request_method" => "Metoda zahteve", "request_method" => "Metoda zahteve",

View File

@@ -193,6 +193,8 @@ $i18n = [
"telegram" => "Телеграм", "telegram" => "Телеграм",
"telegram_bot_token" => "Телеграм бот токен", "telegram_bot_token" => "Телеграм бот токен",
"telegram_chat_id" => "Телеграм чет ID", "telegram_chat_id" => "Телеграм чет ID",
"pushplus" => "Pushplus",
"pushplus_token" => "Pushplus токен",
"webhook" => "Вебхук", "webhook" => "Вебхук",
"webhook_url" => "Вебхук URL", "webhook_url" => "Вебхук URL",
"request_method" => "Метод захтева", "request_method" => "Метод захтева",

View File

@@ -193,6 +193,8 @@ $i18n = [
"telegram" => "Telegram", "telegram" => "Telegram",
"telegram_bot_token" => "Telegram bot token", "telegram_bot_token" => "Telegram bot token",
"telegram_chat_id" => "Telegram chat ID", "telegram_chat_id" => "Telegram chat ID",
"pushplus" => "Pushplus",
"pushplus_token" => "Pushplus token",
"webhook" => "Webhook", "webhook" => "Webhook",
"webhook_url" => "Webhook URL", "webhook_url" => "Webhook URL",
"request_method" => "Metod zahteva", "request_method" => "Metod zahteva",

View File

@@ -193,6 +193,8 @@ $i18n = [
"telegram" => "Telegram", "telegram" => "Telegram",
"telegram_bot_token" => "Telegram Bot Token", "telegram_bot_token" => "Telegram Bot Token",
"telegram_chat_id" => "Telegram Chat ID", "telegram_chat_id" => "Telegram Chat ID",
"pushplus" => "Pushplus",
"pushplus_token" => "Pushplus Token",
"webhook" => "Webhook", "webhook" => "Webhook",
"webhook_url" => "Webhook URL", "webhook_url" => "Webhook URL",
"request_method" => "İstek Metodu", "request_method" => "İstek Metodu",

View File

@@ -193,6 +193,8 @@ $i18n = [
"telegram" => "Telegram", "telegram" => "Telegram",
"telegram_bot_token" => "Токен Telegram-бота", "telegram_bot_token" => "Токен Telegram-бота",
"telegram_chat_id" => "Telegram Chat ID", "telegram_chat_id" => "Telegram Chat ID",
"pushplus" => "Pushplus",
"pushplus_token" => "Pushplus токен",
"webhook" => "Webhook", "webhook" => "Webhook",
"webhook_url" => "Webhook URL", "webhook_url" => "Webhook URL",
"request_method" => "Метод запиту", "request_method" => "Метод запиту",

View File

@@ -194,6 +194,8 @@ $i18n = [
"telegram" => "Telegram", "telegram" => "Telegram",
"telegram_bot_token" => "Mã thông báo Bot Telegram", "telegram_bot_token" => "Mã thông báo Bot Telegram",
"telegram_chat_id" => "ID cuộc trò chuyện Telegram", "telegram_chat_id" => "ID cuộc trò chuyện Telegram",
"pushplus" => "Pushplus",
"pushplus_token" => "Mã thông báo Pushplus",
"webhook" => "Webhook", "webhook" => "Webhook",
"webhook_url" => "URL Webhook", "webhook_url" => "URL Webhook",
"request_method" => "Phương thức yêu cầu", "request_method" => "Phương thức yêu cầu",

View File

@@ -194,6 +194,8 @@ $i18n = [
"telegram" => "Telegram", "telegram" => "Telegram",
"telegram_bot_token" => "Telegram 機器人令牌", "telegram_bot_token" => "Telegram 機器人令牌",
"telegram_chat_id" => "Telegram 聊天 ID", "telegram_chat_id" => "Telegram 聊天 ID",
"pushplus" => "PushPlus",
"pushplus_token" => "消息令牌或者是用户令牌",
"webhook" => "Webhook", "webhook" => "Webhook",
"webhook_url" => "Webhook 網址", "webhook_url" => "Webhook 網址",
"request_method" => "請求方法", "request_method" => "請求方法",

View File

@@ -1,3 +1,3 @@
<?php <?php
$version = "v4.1.1"; $version = "v4.2.0";
?> ?>

View File

@@ -197,7 +197,7 @@ while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
<?php <?php
foreach ($aiRecommendations as $key => $recommendation) { ?> foreach ($aiRecommendations as $key => $recommendation) { ?>
<li class="ai-recommendation-item"> <li class="ai-recommendation-item" data-id="<?= $recommendation['id'] ?>">
<div class="ai-recommendation-header"> <div class="ai-recommendation-header">
<h3> <h3>
<span><?= ($key + 1) . ". " ?></span> <span><?= ($key + 1) . ". " ?></span>
@@ -205,9 +205,15 @@ while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
</h3> </h3>
<span class="item-arrow-down fa fa-caret-down"></span> <span class="item-arrow-down fa fa-caret-down"></span>
</div> </div>
<p class="collapsible"><?= htmlspecialchars($recommendation['description']) ?></p> <p class="collapsible"><?= htmlspecialchars($recommendation['description']) ?></p>
<p><?= htmlspecialchars($recommendation['savings']) ?></p> <p class="ai-recommendation-savings">
<?= htmlspecialchars($recommendation['savings']) ?>
<span>
<a href="#" class="delete-ai-recommendation" title="<?= translate('delete', $i18n) ?>">
<i class="fa fa-trash"></i>
</a>
</span>
</p>
</li> </li>
<?php } ?> <?php } ?>
</ul> </ul>

14
migrations/000040.php Normal file
View File

@@ -0,0 +1,14 @@
<?php
// This migration adds a pushplus_notifications table to store PushPlus notification settings.
$tableQuery = $db->query("SELECT name FROM sqlite_master WHERE type='table' AND name='pushplus_notifications'");
$tableExists = $tableQuery->fetchArray(SQLITE3_ASSOC);
if ($tableExists === false) {
$db->exec("
CREATE TABLE pushplus_notifications (
enabled INTEGER NOT NULL DEFAULT 0,
token TEXT,
user_id INTEGER
);
");
}

View File

@@ -4,4 +4,29 @@ document.addEventListener("DOMContentLoaded", function () {
item.classList.toggle("expanded"); item.classList.toggle("expanded");
}); });
}); });
});
document.querySelectorAll(".delete-ai-recommendation").forEach(function (el) {
el.addEventListener("click", function (e) {
e.preventDefault();
e.stopPropagation();
const item = el.closest(".ai-recommendation-item");
const id = item.getAttribute("data-id");
fetch("endpoints/ai/delete_recommendation.php", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ id: id })
})
.then(res => res.json())
.then(data => {
if (data.success) {
item.remove();
showSuccessMessage(translate('success'));
} else {
showErrorMessage(data.message || "Delete failed.");
}
})
.catch(() => showErrorMessage(translate('unknown_error')));
});
});
});

View File

@@ -223,22 +223,22 @@ $userData['currency_symbol'] = $currencies[$main_currency]['symbol'];
// PushPlus notifications // PushPlus notifications
$sql = "SELECT * FROM pushplus_notifications WHERE user_id = :userId LIMIT 1"; $sql = "SELECT * FROM pushplus_notifications WHERE user_id = :userId LIMIT 1";
$stmt = $db->prepare($sql); $stmt = $db->prepare($sql);
$stmt->bindValue(':userId', $userId, SQLITE3_INTEGER); $stmt->bindValue(':userId', $userId, SQLITE3_INTEGER);
$result = $stmt->execute(); $result = $stmt->execute();
$rowCount = 0; $rowCount = 0;
while ($row = $result->fetchArray(SQLITE3_ASSOC)) { while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
$notificationsPushPlus['enabled'] = $row['enabled']; $notificationsPushPlus['enabled'] = $row['enabled'];
$notificationsPushPlus['token'] = $row['token']; $notificationsPushPlus['token'] = $row['token'];
$rowCount++; $rowCount++;
} }
if ($rowCount == 0) { if ($rowCount == 0) {
$notificationsPushPlus['enabled'] = 0; $notificationsPushPlus['enabled'] = 0;
$notificationsPushPlus['token'] = ""; $notificationsPushPlus['token'] = "";
} }
// Ntfy notifications // Ntfy notifications
$sql = "SELECT * FROM ntfy_notifications WHERE user_id = :userId LIMIT 1"; $sql = "SELECT * FROM ntfy_notifications WHERE user_id = :userId LIMIT 1";

View File

@@ -3042,6 +3042,19 @@ input[type="radio"]:checked+label::after {
color: var(--accent-color); color: var(--accent-color);
} }
.ai-recommendation-item p.ai-recommendation-savings {
justify-content: space-between;
}
.ai-recommendation-item p.ai-recommendation-savings a {
color: var(--main-color);
text-decoration: none;
}
.ai-recommendation-item.expanded p.ai-recommendation-savings {
display: flex;
}
.flex { .flex {
display: flex; display: flex;
} }