From 27ac805141c0d170a40c2a7796a589a5ef29544f Mon Sep 17 00:00:00 2001 From: Miguel Ribeiro Date: Sun, 14 Sep 2025 16:46:42 +0200 Subject: [PATCH] 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) --- endpoints/ai/delete_recommendation.php | 52 ++++++++++++++++++++++++ endpoints/cronjobs/sendnotifications.php | 4 +- includes/i18n/cs.php | 2 + includes/i18n/da.php | 2 + includes/i18n/de.php | 2 + includes/i18n/el.php | 2 + includes/i18n/en.php | 2 + includes/i18n/es.php | 2 + includes/i18n/fr.php | 2 + includes/i18n/id.php | 2 + includes/i18n/it.php | 2 + includes/i18n/jp.php | 2 + includes/i18n/ko.php | 2 + includes/i18n/nl.php | 2 + includes/i18n/pl.php | 2 + includes/i18n/pt.php | 2 + includes/i18n/pt_br.php | 2 + includes/i18n/ru.php | 2 + includes/i18n/sl.php | 2 + includes/i18n/sr.php | 2 + includes/i18n/sr_lat.php | 2 + includes/i18n/tr.php | 2 + includes/i18n/uk.php | 2 + includes/i18n/vi.php | 2 + includes/i18n/zh_tw.php | 2 + includes/version.php | 2 +- index.php | 12 ++++-- migrations/000040.php | 14 +++++++ scripts/dashboard.js | 27 +++++++++++- settings.php | 28 ++++++------- styles/styles.css | 13 ++++++ 31 files changed, 177 insertions(+), 21 deletions(-) create mode 100644 endpoints/ai/delete_recommendation.php create mode 100644 migrations/000040.php diff --git a/endpoints/ai/delete_recommendation.php b/endpoints/ai/delete_recommendation.php new file mode 100644 index 0000000..ef88be2 --- /dev/null +++ b/endpoints/ai/delete_recommendation.php @@ -0,0 +1,52 @@ + 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); +} \ No newline at end of file diff --git a/endpoints/cronjobs/sendnotifications.php b/endpoints/cronjobs/sendnotifications.php index 7d15805..d69338c 100644 --- a/endpoints/cronjobs/sendnotifications.php +++ b/endpoints/cronjobs/sendnotifications.php @@ -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', diff --git a/includes/i18n/cs.php b/includes/i18n/cs.php index 8588847..d2a75f8 100644 --- a/includes/i18n/cs.php +++ b/includes/i18n/cs.php @@ -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", diff --git a/includes/i18n/da.php b/includes/i18n/da.php index 1c2b8fd..9bceb0d 100644 --- a/includes/i18n/da.php +++ b/includes/i18n/da.php @@ -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", diff --git a/includes/i18n/de.php b/includes/i18n/de.php index b054f52..2e8312d 100644 --- a/includes/i18n/de.php +++ b/includes/i18n/de.php @@ -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", diff --git a/includes/i18n/el.php b/includes/i18n/el.php index b712148..5ac49c5 100644 --- a/includes/i18n/el.php +++ b/includes/i18n/el.php @@ -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" => "Μέθοδος αίτησης", diff --git a/includes/i18n/en.php b/includes/i18n/en.php index 718dc9d..b21b8e0 100644 --- a/includes/i18n/en.php +++ b/includes/i18n/en.php @@ -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", diff --git a/includes/i18n/es.php b/includes/i18n/es.php index 77991f3..86b919b 100644 --- a/includes/i18n/es.php +++ b/includes/i18n/es.php @@ -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", diff --git a/includes/i18n/fr.php b/includes/i18n/fr.php index f529641..a51275d 100644 --- a/includes/i18n/fr.php +++ b/includes/i18n/fr.php @@ -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", diff --git a/includes/i18n/id.php b/includes/i18n/id.php index 5d9b8aa..db93f15 100644 --- a/includes/i18n/id.php +++ b/includes/i18n/id.php @@ -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", diff --git a/includes/i18n/it.php b/includes/i18n/it.php index 4dc3e3c..4113904 100644 --- a/includes/i18n/it.php +++ b/includes/i18n/it.php @@ -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", diff --git a/includes/i18n/jp.php b/includes/i18n/jp.php index 31a260c..c555ed8 100644 --- a/includes/i18n/jp.php +++ b/includes/i18n/jp.php @@ -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" => "リクエストメソッド", diff --git a/includes/i18n/ko.php b/includes/i18n/ko.php index 65a2815..6c858b6 100644 --- a/includes/i18n/ko.php +++ b/includes/i18n/ko.php @@ -193,6 +193,8 @@ $i18n = [ "telegram" => "텔레그램", "telegram_bot_token" => "텔레그램 봇 토큰", "telegram_chat_id" => "텔레그램 채팅 ID", + "pushplus" => "Pushplus", + "pushplus_token" => "Pushplus 토큰", "webhook" => "웹훅", "webhook_url" => "웹훅 URL", "request_method" => "요청 메서드", diff --git a/includes/i18n/nl.php b/includes/i18n/nl.php index 4b1c4a2..f789b57 100644 --- a/includes/i18n/nl.php +++ b/includes/i18n/nl.php @@ -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", diff --git a/includes/i18n/pl.php b/includes/i18n/pl.php index f885db8..fecbc12 100644 --- a/includes/i18n/pl.php +++ b/includes/i18n/pl.php @@ -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", diff --git a/includes/i18n/pt.php b/includes/i18n/pt.php index 837e6a9..fc6e473 100644 --- a/includes/i18n/pt.php +++ b/includes/i18n/pt.php @@ -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", diff --git a/includes/i18n/pt_br.php b/includes/i18n/pt_br.php index 4caec9e..e1388fd 100644 --- a/includes/i18n/pt_br.php +++ b/includes/i18n/pt_br.php @@ -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", diff --git a/includes/i18n/ru.php b/includes/i18n/ru.php index d8afffc..d795a90 100644 --- a/includes/i18n/ru.php +++ b/includes/i18n/ru.php @@ -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" => "Метод запроса", diff --git a/includes/i18n/sl.php b/includes/i18n/sl.php index 49ad9f5..48f2a94 100644 --- a/includes/i18n/sl.php +++ b/includes/i18n/sl.php @@ -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", diff --git a/includes/i18n/sr.php b/includes/i18n/sr.php index a101c79..5909f87 100644 --- a/includes/i18n/sr.php +++ b/includes/i18n/sr.php @@ -193,6 +193,8 @@ $i18n = [ "telegram" => "Телеграм", "telegram_bot_token" => "Телеграм бот токен", "telegram_chat_id" => "Телеграм чет ID", + "pushplus" => "Pushplus", + "pushplus_token" => "Pushplus токен", "webhook" => "Вебхук", "webhook_url" => "Вебхук URL", "request_method" => "Метод захтева", diff --git a/includes/i18n/sr_lat.php b/includes/i18n/sr_lat.php index f93125c..60857cb 100644 --- a/includes/i18n/sr_lat.php +++ b/includes/i18n/sr_lat.php @@ -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", diff --git a/includes/i18n/tr.php b/includes/i18n/tr.php index 4a54a89..4429032 100644 --- a/includes/i18n/tr.php +++ b/includes/i18n/tr.php @@ -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", diff --git a/includes/i18n/uk.php b/includes/i18n/uk.php index d0e2187..6eb63cf 100644 --- a/includes/i18n/uk.php +++ b/includes/i18n/uk.php @@ -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" => "Метод запиту", diff --git a/includes/i18n/vi.php b/includes/i18n/vi.php index e9d2176..d8705dd 100644 --- a/includes/i18n/vi.php +++ b/includes/i18n/vi.php @@ -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", diff --git a/includes/i18n/zh_tw.php b/includes/i18n/zh_tw.php index 8985a89..e3639f2 100644 --- a/includes/i18n/zh_tw.php +++ b/includes/i18n/zh_tw.php @@ -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" => "請求方法", diff --git a/includes/version.php b/includes/version.php index 6736c50..c290563 100644 --- a/includes/version.php +++ b/includes/version.php @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/index.php b/index.php index 7085127..1eb2d82 100644 --- a/index.php +++ b/index.php @@ -197,7 +197,7 @@ while ($row = $result->fetchArray(SQLITE3_ASSOC)) { $recommendation) { ?> -
  • +
  • @@ -205,9 +205,15 @@ while ($row = $result->fetchArray(SQLITE3_ASSOC)) {

    -

    -

    +

    + + + + + + +

  • diff --git a/migrations/000040.php b/migrations/000040.php new file mode 100644 index 0000000..3395ade --- /dev/null +++ b/migrations/000040.php @@ -0,0 +1,14 @@ +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 + ); + "); +} \ No newline at end of file diff --git a/scripts/dashboard.js b/scripts/dashboard.js index 0bd7b67..3ea5b7b 100644 --- a/scripts/dashboard.js +++ b/scripts/dashboard.js @@ -4,4 +4,29 @@ document.addEventListener("DOMContentLoaded", function () { item.classList.toggle("expanded"); }); }); -}); \ No newline at end of file + + 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'))); + }); + }); +}); + diff --git a/settings.php b/settings.php index c59ac9f..ccb812e 100644 --- a/settings.php +++ b/settings.php @@ -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"; diff --git a/styles/styles.css b/styles/styles.css index 84d8cc2..fb75d5c 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -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; }