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();
$user = $result->fetchArray(SQLITE3_ASSOC);
// 构建消息内容
// Build Message Content
$messageContent = "";
if ($user['name']) {
$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";
}
// 准备 PushPlus 数据
// Prepare PushPlus Data
$data = array(
'token' => $pushplus['token'],
'title' => '订阅续期提醒 - Wallos',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -197,7 +197,7 @@ while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
<?php
foreach ($aiRecommendations as $key => $recommendation) { ?>
<li class="ai-recommendation-item">
<li class="ai-recommendation-item" data-id="<?= $recommendation['id'] ?>">
<div class="ai-recommendation-header">
<h3>
<span><?= ($key + 1) . ". " ?></span>
@@ -205,9 +205,15 @@ while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
</h3>
<span class="item-arrow-down fa fa-caret-down"></span>
</div>
<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>
<?php } ?>
</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");
});
});
});
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
$sql = "SELECT * FROM pushplus_notifications WHERE user_id = :userId LIMIT 1";
$stmt = $db->prepare($sql);
$stmt->bindValue(':userId', $userId, SQLITE3_INTEGER);
$result = $stmt->execute();
$sql = "SELECT * FROM pushplus_notifications WHERE user_id = :userId LIMIT 1";
$stmt = $db->prepare($sql);
$stmt->bindValue(':userId', $userId, SQLITE3_INTEGER);
$result = $stmt->execute();
$rowCount = 0;
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
$notificationsPushPlus['enabled'] = $row['enabled'];
$notificationsPushPlus['token'] = $row['token'];
$rowCount++;
}
$rowCount = 0;
while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
$notificationsPushPlus['enabled'] = $row['enabled'];
$notificationsPushPlus['token'] = $row['token'];
$rowCount++;
}
if ($rowCount == 0) {
$notificationsPushPlus['enabled'] = 0;
$notificationsPushPlus['token'] = "";
}
if ($rowCount == 0) {
$notificationsPushPlus['enabled'] = 0;
$notificationsPushPlus['token'] = "";
}
// Ntfy notifications
$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);
}
.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 {
display: flex;
}