@iletai/nzb 1.8.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +34 -5
- package/dist/copilot/tools.js +50 -0
- package/dist/daemon.js +55 -11
- package/dist/telegram/bot.js +3 -0
- package/dist/telegram/handlers/commands.js +1 -0
- package/dist/telegram/handlers/update.js +211 -0
- package/dist/update.js +170 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -45,6 +45,8 @@ Commands:
|
|
|
45
45
|
tui Connect to the daemon via terminal UI
|
|
46
46
|
setup Interactive first-run configuration
|
|
47
47
|
update Check for updates and install the latest version
|
|
48
|
+
update check Check for updates without installing
|
|
49
|
+
update --force Force reinstall the latest version
|
|
48
50
|
cron Manage scheduled cron jobs
|
|
49
51
|
help Show this help message
|
|
50
52
|
|
|
@@ -77,21 +79,48 @@ switch (command) {
|
|
|
77
79
|
await import("./setup.js");
|
|
78
80
|
break;
|
|
79
81
|
case "update": {
|
|
80
|
-
const { checkForUpdate, performUpdate } = await import("./update.js");
|
|
82
|
+
const { checkForUpdate, performUpdate, performForceUpdate } = await import("./update.js");
|
|
83
|
+
const updateArgs = args.slice(1);
|
|
84
|
+
const subCmd = updateArgs[0];
|
|
85
|
+
const force = updateArgs.includes("--force");
|
|
86
|
+
// `nzb update check` — check only, don't install
|
|
87
|
+
if (subCmd === "check") {
|
|
88
|
+
const check = await checkForUpdate();
|
|
89
|
+
if (!check.checkSucceeded) {
|
|
90
|
+
console.error("Warning: Could not reach the npm registry. Check your network and try again.");
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
if (check.updateAvailable) {
|
|
94
|
+
console.log(`Update available: v${check.current} → v${check.latest}`);
|
|
95
|
+
if (check.publishedAt) {
|
|
96
|
+
console.log(`Published: ${new Date(check.publishedAt).toLocaleDateString()}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
console.log(`nzb v${check.current} is already the latest version.`);
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
// `nzb update` or `nzb update --force` — check and install
|
|
81
105
|
const check = await checkForUpdate();
|
|
82
106
|
if (!check.checkSucceeded) {
|
|
83
107
|
console.error("Warning: Could not reach the npm registry. Check your network and try again.");
|
|
84
108
|
process.exit(1);
|
|
85
109
|
}
|
|
86
|
-
if (!check.updateAvailable) {
|
|
110
|
+
if (!check.updateAvailable && !force) {
|
|
87
111
|
console.log(`nzb v${check.current} is already the latest version.`);
|
|
88
112
|
break;
|
|
89
113
|
}
|
|
90
|
-
|
|
114
|
+
if (check.updateAvailable) {
|
|
115
|
+
console.log(`Update available: v${check.current} → v${check.latest}`);
|
|
116
|
+
}
|
|
117
|
+
else if (force) {
|
|
118
|
+
console.log(`Force reinstalling nzb v${check.current}...`);
|
|
119
|
+
}
|
|
91
120
|
console.log("Installing...");
|
|
92
|
-
const result = await performUpdate();
|
|
121
|
+
const result = force ? await performForceUpdate() : await performUpdate();
|
|
93
122
|
if (result.ok) {
|
|
94
|
-
console.log(`Updated to v${check.latest}`);
|
|
123
|
+
console.log(check.updateAvailable ? `Updated to v${check.latest}` : `Reinstalled v${check.current}`);
|
|
95
124
|
}
|
|
96
125
|
else {
|
|
97
126
|
console.error(`Update failed: ${result.output}`);
|
package/dist/copilot/tools.js
CHANGED
|
@@ -855,6 +855,56 @@ export function createTools(deps) {
|
|
|
855
855
|
return `Restarting NZB${reason}. I'll be back in a few seconds.`;
|
|
856
856
|
},
|
|
857
857
|
}),
|
|
858
|
+
defineTool("check_update", {
|
|
859
|
+
description: "Check for NZB updates and optionally apply them. " +
|
|
860
|
+
"Use 'check' to see if updates are available. " +
|
|
861
|
+
"Use 'update' to install the latest version and restart.",
|
|
862
|
+
parameters: z.object({
|
|
863
|
+
action: z.enum(["check", "update"]).describe("'check' to check for updates, 'update' to install and restart"),
|
|
864
|
+
}),
|
|
865
|
+
handler: async (args) => {
|
|
866
|
+
try {
|
|
867
|
+
const { checkForUpdate, performUpdate, getChangelog } = await import("../update.js");
|
|
868
|
+
if (args.action === "check") {
|
|
869
|
+
const result = await checkForUpdate();
|
|
870
|
+
if (!result.checkSucceeded) {
|
|
871
|
+
return "Could not reach the npm registry. Network may be unavailable.";
|
|
872
|
+
}
|
|
873
|
+
if (!result.updateAvailable) {
|
|
874
|
+
return `NZB v${result.current} is already the latest version.`;
|
|
875
|
+
}
|
|
876
|
+
const changelog = await getChangelog(5);
|
|
877
|
+
const changelogText = changelog.length > 0
|
|
878
|
+
? "\n\nRecent versions:\n" + changelog.map((e) => `• v${e.version} (${e.date})`).join("\n")
|
|
879
|
+
: "";
|
|
880
|
+
return `Update available: v${result.current} → v${result.latest}${changelogText}`;
|
|
881
|
+
}
|
|
882
|
+
// action === "update"
|
|
883
|
+
const result = await checkForUpdate();
|
|
884
|
+
if (!result.checkSucceeded) {
|
|
885
|
+
return "Could not reach the npm registry. Network may be unavailable.";
|
|
886
|
+
}
|
|
887
|
+
if (!result.updateAvailable) {
|
|
888
|
+
return `NZB v${result.current} is already the latest version. No update needed.`;
|
|
889
|
+
}
|
|
890
|
+
const updateResult = await performUpdate();
|
|
891
|
+
if (!updateResult.ok) {
|
|
892
|
+
return `Update failed: ${updateResult.output}`;
|
|
893
|
+
}
|
|
894
|
+
// Schedule restart after returning the response
|
|
895
|
+
const { restartDaemon } = await import("../daemon.js");
|
|
896
|
+
setTimeout(() => {
|
|
897
|
+
restartDaemon().catch((err) => {
|
|
898
|
+
console.error("[nzb] Post-update restart failed:", err);
|
|
899
|
+
});
|
|
900
|
+
}, 1000);
|
|
901
|
+
return `Updated NZB to v${result.latest}. Restarting now...`;
|
|
902
|
+
}
|
|
903
|
+
catch (err) {
|
|
904
|
+
return `Update error: ${err instanceof Error ? err.message : String(err)}`;
|
|
905
|
+
}
|
|
906
|
+
},
|
|
907
|
+
}),
|
|
858
908
|
];
|
|
859
909
|
}
|
|
860
910
|
function formatAge(date) {
|
package/dist/daemon.js
CHANGED
|
@@ -8,7 +8,7 @@ import { PID_FILE_PATH } from "./paths.js";
|
|
|
8
8
|
import { closeDb, getDb } from "./store/db.js";
|
|
9
9
|
import { createBot, sendProactiveMessage, sendWorkerNotification, startBot, stopBot } from "./telegram/bot.js";
|
|
10
10
|
import { startCronScheduler, stopCronScheduler } from "./cron/scheduler.js";
|
|
11
|
-
import { checkForUpdate } from "./update.js";
|
|
11
|
+
import { checkForUpdate, getDismissedVersion, isAutoUpdateEnabled, scheduleUpdateCheck, shouldCheckUpdate, stopUpdateCheck } from "./update.js";
|
|
12
12
|
// Log the active CA bundle (injected by cli.ts via re-exec).
|
|
13
13
|
if (process.env.NODE_EXTRA_CA_CERTS) {
|
|
14
14
|
console.log(`[nzb] Using system CA bundle: ${process.env.NODE_EXTRA_CA_CERTS}`);
|
|
@@ -152,18 +152,60 @@ async function main() {
|
|
|
152
152
|
console.log("[nzb] Telegram user ID missing — skipping bot. Run 'nzb setup' and enter your Telegram user ID (get it from @userinfobot).");
|
|
153
153
|
}
|
|
154
154
|
console.log("[nzb] NZB is fully operational.");
|
|
155
|
-
// Non-blocking update check —
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
155
|
+
// Non-blocking update check — delayed 30s after startup, respects check interval
|
|
156
|
+
setTimeout(async () => {
|
|
157
|
+
try {
|
|
158
|
+
const autoEnabled = await isAutoUpdateEnabled();
|
|
159
|
+
if (!autoEnabled)
|
|
160
|
+
return;
|
|
161
|
+
const shouldCheck = await shouldCheckUpdate();
|
|
162
|
+
if (!shouldCheck)
|
|
163
|
+
return;
|
|
164
|
+
const result = await checkForUpdate();
|
|
165
|
+
if (result.updateAvailable && result.latest) {
|
|
166
|
+
const dismissed = await getDismissedVersion();
|
|
167
|
+
if (dismissed === result.latest)
|
|
168
|
+
return;
|
|
169
|
+
const msg = `⬆ Update available: v${result.current} → v${result.latest} — run \`nzb update\` to install`;
|
|
170
|
+
console.log(`[nzb] ${msg}`);
|
|
171
|
+
if (config.telegramEnabled) {
|
|
172
|
+
try {
|
|
173
|
+
const { sendUpdateNotification } = await import("./telegram/handlers/update.js");
|
|
174
|
+
const tgBot = (await import("./telegram/bot.js")).getBot();
|
|
175
|
+
if (tgBot && config.authorizedUserId !== undefined) {
|
|
176
|
+
await sendUpdateNotification(tgBot, config.authorizedUserId, result);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
// Fallback to plain text
|
|
181
|
+
sendProactiveMessage(msg).catch(() => { });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
broadcastToSSE(msg);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// Silent — network may be unavailable
|
|
189
|
+
}
|
|
190
|
+
}, 30_000);
|
|
191
|
+
// Schedule periodic update checks (every 6 hours)
|
|
192
|
+
scheduleUpdateCheck(async (result) => {
|
|
193
|
+
const msg = `⬆ Update available: v${result.current} → v${result.latest} — run \`nzb update\` to install`;
|
|
194
|
+
console.log(`[nzb] ${msg}`);
|
|
195
|
+
if (config.telegramEnabled) {
|
|
196
|
+
try {
|
|
197
|
+
const { sendUpdateNotification } = await import("./telegram/handlers/update.js");
|
|
198
|
+
const tgBot = (await import("./telegram/bot.js")).getBot();
|
|
199
|
+
if (tgBot && config.authorizedUserId !== undefined) {
|
|
200
|
+
await sendUpdateNotification(tgBot, config.authorizedUserId, result);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
162
204
|
sendProactiveMessage(msg).catch(() => { });
|
|
163
|
-
|
|
205
|
+
}
|
|
164
206
|
}
|
|
165
|
-
|
|
166
|
-
|
|
207
|
+
broadcastToSSE(msg);
|
|
208
|
+
});
|
|
167
209
|
// Notify user if this is a restart (not a fresh start)
|
|
168
210
|
if (config.telegramEnabled && process.env.NZB_RESTARTED === "1") {
|
|
169
211
|
await sendProactiveMessage("I'm back online.").catch(() => { });
|
|
@@ -198,6 +240,7 @@ async function shutdown() {
|
|
|
198
240
|
// Stop health check timer first
|
|
199
241
|
stopHealthCheck();
|
|
200
242
|
stopCronScheduler();
|
|
243
|
+
stopUpdateCheck();
|
|
201
244
|
if (config.telegramEnabled) {
|
|
202
245
|
try {
|
|
203
246
|
await stopBot();
|
|
@@ -225,6 +268,7 @@ export async function restartDaemon() {
|
|
|
225
268
|
console.log("[nzb] Restarting...");
|
|
226
269
|
stopHealthCheck();
|
|
227
270
|
stopCronScheduler();
|
|
271
|
+
stopUpdateCheck();
|
|
228
272
|
const activeWorkers = getWorkers();
|
|
229
273
|
const runningCount = Array.from(activeWorkers.values()).filter((w) => w.status === "running").length;
|
|
230
274
|
if (runningCount > 0) {
|
package/dist/telegram/bot.js
CHANGED
|
@@ -17,6 +17,7 @@ import { registerMediaHandlers } from "./handlers/media.js";
|
|
|
17
17
|
import { registerReactionHandlers } from "./handlers/reactions.js";
|
|
18
18
|
import { registerMessageHandler } from "./handlers/streaming.js";
|
|
19
19
|
import { registerSmartSuggestionHandlers } from "./handlers/suggestions.js";
|
|
20
|
+
import { registerUpdateHandlers } from "./handlers/update.js";
|
|
20
21
|
import { initLogChannel, logError, logInfo } from "./log-channel.js";
|
|
21
22
|
import { createMenus } from "./menus.js";
|
|
22
23
|
let bot;
|
|
@@ -106,6 +107,7 @@ export function createBot() {
|
|
|
106
107
|
// --- Handler registrations ---
|
|
107
108
|
registerCallbackHandlers(bot);
|
|
108
109
|
registerCronHandlers(bot);
|
|
110
|
+
registerUpdateHandlers(bot);
|
|
109
111
|
registerInlineQueryHandler(bot);
|
|
110
112
|
registerSmartSuggestionHandlers(bot);
|
|
111
113
|
registerReactionHandlers(bot);
|
|
@@ -155,6 +157,7 @@ export async function startBot() {
|
|
|
155
157
|
{ command: "skills", description: "List installed skills" },
|
|
156
158
|
{ command: "memory", description: "Show stored memories" },
|
|
157
159
|
{ command: "cron", description: "Manage cron jobs" },
|
|
160
|
+
{ command: "update", description: "Check for updates" },
|
|
158
161
|
{ command: "settings", description: "Bot settings" },
|
|
159
162
|
{ command: "restart", description: "Restart NZB" },
|
|
160
163
|
]);
|
|
@@ -32,6 +32,7 @@ export function registerCommandHandlers(bot, deps) {
|
|
|
32
32
|
"/skills — Installed skills\n" +
|
|
33
33
|
"/workers — Active worker sessions\n" +
|
|
34
34
|
"/cron — Manage cron jobs\n" +
|
|
35
|
+
"/update — Check for updates\n" +
|
|
35
36
|
"/restart — Restart NZB\n\n" +
|
|
36
37
|
"⚡ Breakthrough Features:\n" +
|
|
37
38
|
"• @bot query — Use me inline in any chat!\n" +
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { InlineKeyboard } from "grammy";
|
|
2
|
+
import { checkForUpdate, dismissVersion, getChangelog, getCurrentVersion, isAutoUpdateEnabled, performUpdate, toggleAutoUpdate, } from "../../update.js";
|
|
3
|
+
/** Build the update menu inline keyboard. */
|
|
4
|
+
async function buildUpdateKeyboard() {
|
|
5
|
+
const autoEnabled = await isAutoUpdateEnabled();
|
|
6
|
+
return new InlineKeyboard()
|
|
7
|
+
.text("🔍 Check Update", "update:check")
|
|
8
|
+
.text("⬆️ Update Now", "update:now")
|
|
9
|
+
.row()
|
|
10
|
+
.text("📋 Changelog", "update:changelog")
|
|
11
|
+
.text(`⚙️ Auto-Update: ${autoEnabled ? "ON" : "OFF"}`, "update:auto:toggle");
|
|
12
|
+
}
|
|
13
|
+
/** Build the notification keyboard shown when an update is available. */
|
|
14
|
+
export function buildUpdateNotificationKeyboard() {
|
|
15
|
+
return new InlineKeyboard()
|
|
16
|
+
.text("⬆️ Update Now", "update:now")
|
|
17
|
+
.text("📋 Changelog", "update:changelog")
|
|
18
|
+
.row()
|
|
19
|
+
.text("❌ Dismiss", "update:dismiss");
|
|
20
|
+
}
|
|
21
|
+
/** Format an update check result for display. */
|
|
22
|
+
function formatCheckResult(result) {
|
|
23
|
+
if (!result.checkSucceeded) {
|
|
24
|
+
return "❌ Could not reach npm registry. Check your network and try again.";
|
|
25
|
+
}
|
|
26
|
+
if (result.updateAvailable) {
|
|
27
|
+
const published = result.publishedAt
|
|
28
|
+
? `\nPublished: ${new Date(result.publishedAt).toLocaleDateString()}`
|
|
29
|
+
: "";
|
|
30
|
+
return `🆕 Update available!\n\nCurrent: v${result.current}\nLatest: v${result.latest}${published}`;
|
|
31
|
+
}
|
|
32
|
+
return `✅ NZB v${result.current} is up to date.`;
|
|
33
|
+
}
|
|
34
|
+
/** Register the /update command and callback handlers. */
|
|
35
|
+
export function registerUpdateHandlers(bot) {
|
|
36
|
+
// /update command — show update menu
|
|
37
|
+
bot.command("update", async (ctx) => {
|
|
38
|
+
const keyboard = await buildUpdateKeyboard();
|
|
39
|
+
const version = getCurrentVersion();
|
|
40
|
+
await ctx.reply(`📦 NZB Update Manager (v${version})`, { reply_markup: keyboard });
|
|
41
|
+
});
|
|
42
|
+
// Check for updates
|
|
43
|
+
bot.callbackQuery("update:check", async (ctx) => {
|
|
44
|
+
try {
|
|
45
|
+
await ctx.answerCallbackQuery({ text: "Checking for updates..." });
|
|
46
|
+
const result = await checkForUpdate();
|
|
47
|
+
const text = formatCheckResult(result);
|
|
48
|
+
if (result.updateAvailable) {
|
|
49
|
+
await ctx.editMessageText(text, { reply_markup: buildUpdateNotificationKeyboard() });
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
const keyboard = await buildUpdateKeyboard();
|
|
53
|
+
await ctx.editMessageText(text, { reply_markup: keyboard });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
console.error("[nzb] Update check callback error:", err instanceof Error ? err.message : err);
|
|
58
|
+
await ctx.answerCallbackQuery({
|
|
59
|
+
text: `Error: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
60
|
+
show_alert: true,
|
|
61
|
+
}).catch(() => { });
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
// Update now — show confirmation dialog
|
|
65
|
+
bot.callbackQuery("update:now", async (ctx) => {
|
|
66
|
+
try {
|
|
67
|
+
await ctx.answerCallbackQuery();
|
|
68
|
+
const result = await checkForUpdate();
|
|
69
|
+
if (!result.checkSucceeded) {
|
|
70
|
+
await ctx.editMessageText("❌ Cannot reach npm registry. Try again later.");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (!result.updateAvailable) {
|
|
74
|
+
const keyboard = await buildUpdateKeyboard();
|
|
75
|
+
await ctx.editMessageText(`✅ NZB v${result.current} is already the latest version.`, {
|
|
76
|
+
reply_markup: keyboard,
|
|
77
|
+
});
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const confirmKeyboard = new InlineKeyboard()
|
|
81
|
+
.text("✅ Yes, update now", "update:confirm")
|
|
82
|
+
.text("❌ Cancel", "update:cancel");
|
|
83
|
+
await ctx.editMessageText(`⚠️ Update NZB?\n\nv${result.current} → v${result.latest}\n\nThis will install the new version and restart the daemon.`, { reply_markup: confirmKeyboard });
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
console.error("[nzb] Update now callback error:", err instanceof Error ? err.message : err);
|
|
87
|
+
await ctx.answerCallbackQuery({
|
|
88
|
+
text: `Error: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
89
|
+
show_alert: true,
|
|
90
|
+
}).catch(() => { });
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
// Confirm update — actually perform the update
|
|
94
|
+
bot.callbackQuery("update:confirm", async (ctx) => {
|
|
95
|
+
try {
|
|
96
|
+
await ctx.answerCallbackQuery({ text: "Updating..." });
|
|
97
|
+
await ctx.editMessageText("⏳ Installing update... This may take a minute.");
|
|
98
|
+
const result = await performUpdate();
|
|
99
|
+
if (!result.ok) {
|
|
100
|
+
const keyboard = await buildUpdateKeyboard();
|
|
101
|
+
await ctx.editMessageText(`❌ Update failed:\n${result.output}`, { reply_markup: keyboard });
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
await ctx.editMessageText("✅ Update installed! Restarting NZB...");
|
|
105
|
+
// Restart daemon after a short delay
|
|
106
|
+
setTimeout(async () => {
|
|
107
|
+
try {
|
|
108
|
+
const { restartDaemon } = await import("../../daemon.js");
|
|
109
|
+
await restartDaemon();
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
console.error("[nzb] Post-update restart failed:", err);
|
|
113
|
+
}
|
|
114
|
+
}, 1000);
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
console.error("[nzb] Update confirm callback error:", err instanceof Error ? err.message : err);
|
|
118
|
+
await ctx.answerCallbackQuery({
|
|
119
|
+
text: `Error: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
120
|
+
show_alert: true,
|
|
121
|
+
}).catch(() => { });
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
// Cancel update
|
|
125
|
+
bot.callbackQuery("update:cancel", async (ctx) => {
|
|
126
|
+
try {
|
|
127
|
+
await ctx.answerCallbackQuery({ text: "Cancelled" });
|
|
128
|
+
const keyboard = await buildUpdateKeyboard();
|
|
129
|
+
const version = getCurrentVersion();
|
|
130
|
+
await ctx.editMessageText(`📦 NZB Update Manager (v${version})`, { reply_markup: keyboard });
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
console.error("[nzb] Update cancel callback error:", err instanceof Error ? err.message : err);
|
|
134
|
+
await ctx.answerCallbackQuery({
|
|
135
|
+
text: `Error: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
136
|
+
show_alert: true,
|
|
137
|
+
}).catch(() => { });
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
// Show changelog
|
|
141
|
+
bot.callbackQuery("update:changelog", async (ctx) => {
|
|
142
|
+
try {
|
|
143
|
+
await ctx.answerCallbackQuery({ text: "Fetching changelog..." });
|
|
144
|
+
const entries = await getChangelog(8);
|
|
145
|
+
if (entries.length === 0) {
|
|
146
|
+
await ctx.editMessageText("📋 Could not fetch version history.", {
|
|
147
|
+
reply_markup: await buildUpdateKeyboard(),
|
|
148
|
+
});
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const current = getCurrentVersion();
|
|
152
|
+
const lines = entries.map((e) => {
|
|
153
|
+
const marker = e.version === current ? " ← current" : "";
|
|
154
|
+
return `• v${e.version} (${e.date})${marker}`;
|
|
155
|
+
});
|
|
156
|
+
const keyboard = await buildUpdateKeyboard();
|
|
157
|
+
await ctx.editMessageText(`📋 Recent versions:\n\n${lines.join("\n")}`, { reply_markup: keyboard });
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
console.error("[nzb] Changelog callback error:", err instanceof Error ? err.message : err);
|
|
161
|
+
await ctx.answerCallbackQuery({
|
|
162
|
+
text: `Error: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
163
|
+
show_alert: true,
|
|
164
|
+
}).catch(() => { });
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
// Toggle auto-update
|
|
168
|
+
bot.callbackQuery("update:auto:toggle", async (ctx) => {
|
|
169
|
+
try {
|
|
170
|
+
const newState = await toggleAutoUpdate();
|
|
171
|
+
await ctx.answerCallbackQuery({ text: `Auto-Update ${newState ? "ON" : "OFF"}` });
|
|
172
|
+
const keyboard = await buildUpdateKeyboard();
|
|
173
|
+
const version = getCurrentVersion();
|
|
174
|
+
await ctx.editMessageText(`📦 NZB Update Manager (v${version})`, { reply_markup: keyboard });
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
console.error("[nzb] Auto-update toggle error:", err instanceof Error ? err.message : err);
|
|
178
|
+
await ctx.answerCallbackQuery({
|
|
179
|
+
text: `Error: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
180
|
+
show_alert: true,
|
|
181
|
+
}).catch(() => { });
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
// Dismiss update notification
|
|
185
|
+
bot.callbackQuery("update:dismiss", async (ctx) => {
|
|
186
|
+
try {
|
|
187
|
+
const result = await checkForUpdate();
|
|
188
|
+
if (result.latest) {
|
|
189
|
+
await dismissVersion(result.latest);
|
|
190
|
+
}
|
|
191
|
+
await ctx.answerCallbackQuery({ text: "Dismissed" });
|
|
192
|
+
await ctx.editMessageText(`📦 NZB v${result.current} — update notification dismissed.`);
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
console.error("[nzb] Dismiss callback error:", err instanceof Error ? err.message : err);
|
|
196
|
+
await ctx.answerCallbackQuery({
|
|
197
|
+
text: `Error: ${err instanceof Error ? err.message : "Unknown error"}`,
|
|
198
|
+
show_alert: true,
|
|
199
|
+
}).catch(() => { });
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
/** Send a proactive update notification to a chat. */
|
|
204
|
+
export async function sendUpdateNotification(bot, chatId, result) {
|
|
205
|
+
const keyboard = buildUpdateNotificationKeyboard();
|
|
206
|
+
const published = result.publishedAt
|
|
207
|
+
? ` (${new Date(result.publishedAt).toLocaleDateString()})`
|
|
208
|
+
: "";
|
|
209
|
+
await bot.api.sendMessage(chatId, `🆕 NZB v${result.latest} available!${published}\nCurrent: v${result.current}\n\nRun \`nzb update\` or use the buttons below.`, { reply_markup: keyboard, parse_mode: "Markdown" });
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=update.js.map
|
package/dist/update.js
CHANGED
|
@@ -4,6 +4,9 @@ import { dirname, join } from "path";
|
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
5
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
6
|
const PKG_NAME = "@iletai/nzb";
|
|
7
|
+
const NPM_REGISTRY_URL = `https://registry.npmjs.org/${PKG_NAME}`;
|
|
8
|
+
const CHECK_INTERVAL_MS = 6 * 60 * 60 * 1000; // 6 hours
|
|
9
|
+
let updateCheckTimer;
|
|
7
10
|
function getPackageJson() {
|
|
8
11
|
try {
|
|
9
12
|
const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
@@ -54,11 +57,37 @@ function isNewer(local, remote) {
|
|
|
54
57
|
export async function checkForUpdate() {
|
|
55
58
|
const current = getLocalVersion();
|
|
56
59
|
const latest = await getLatestVersion();
|
|
60
|
+
let publishedAt;
|
|
61
|
+
// Try to fetch publish date from registry
|
|
62
|
+
if (latest) {
|
|
63
|
+
try {
|
|
64
|
+
const res = await fetch(`${NPM_REGISTRY_URL}/latest`, { signal: AbortSignal.timeout(10_000) });
|
|
65
|
+
if (res.ok) {
|
|
66
|
+
const data = (await res.json());
|
|
67
|
+
const time = data.time;
|
|
68
|
+
if (time && latest in time) {
|
|
69
|
+
publishedAt = time[latest];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// Expected: registry may be unreachable
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Record the check timestamp
|
|
78
|
+
try {
|
|
79
|
+
const { setState } = await import("./store/db.js");
|
|
80
|
+
setState("last_update_check", new Date().toISOString());
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Expected: DB may not be initialized during CLI usage
|
|
84
|
+
}
|
|
57
85
|
return {
|
|
58
86
|
current,
|
|
59
87
|
latest,
|
|
60
88
|
updateAvailable: latest !== null && isNewer(current, latest),
|
|
61
89
|
checkSucceeded: latest !== null,
|
|
90
|
+
publishedAt,
|
|
62
91
|
};
|
|
63
92
|
}
|
|
64
93
|
/** Run `npm install -g <pkg>@latest` and return success/failure. */
|
|
@@ -67,7 +96,23 @@ export async function performUpdate() {
|
|
|
67
96
|
const { name } = getPackageJson();
|
|
68
97
|
const output = execSync(`npm install -g ${name}@latest`, {
|
|
69
98
|
encoding: "utf-8",
|
|
70
|
-
timeout:
|
|
99
|
+
timeout: 120_000,
|
|
100
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
101
|
+
});
|
|
102
|
+
return { ok: true, output: output.trim() };
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
const msg = err.stderr?.trim() || err.message || "Unknown error";
|
|
106
|
+
return { ok: false, output: msg };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/** Force update even if the same version is installed. */
|
|
110
|
+
export async function performForceUpdate() {
|
|
111
|
+
try {
|
|
112
|
+
const { name } = getPackageJson();
|
|
113
|
+
const output = execSync(`npm install -g ${name}@latest --force`, {
|
|
114
|
+
encoding: "utf-8",
|
|
115
|
+
timeout: 120_000,
|
|
71
116
|
stdio: ["ignore", "pipe", "pipe"],
|
|
72
117
|
});
|
|
73
118
|
return { ok: true, output: output.trim() };
|
|
@@ -77,4 +122,128 @@ export async function performUpdate() {
|
|
|
77
122
|
return { ok: false, output: msg };
|
|
78
123
|
}
|
|
79
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Check if enough time has passed since the last update check.
|
|
127
|
+
* Returns true if we should check now (>= 6 hours since last check).
|
|
128
|
+
*/
|
|
129
|
+
export async function shouldCheckUpdate() {
|
|
130
|
+
try {
|
|
131
|
+
const { getState } = await import("./store/db.js");
|
|
132
|
+
const last = getState("last_update_check");
|
|
133
|
+
if (!last)
|
|
134
|
+
return true;
|
|
135
|
+
const elapsed = Date.now() - new Date(last).getTime();
|
|
136
|
+
return elapsed >= CHECK_INTERVAL_MS;
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// DB not ready — check anyway
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Check if auto-update notifications are enabled.
|
|
145
|
+
* Defaults to true if not explicitly set.
|
|
146
|
+
*/
|
|
147
|
+
export async function isAutoUpdateEnabled() {
|
|
148
|
+
try {
|
|
149
|
+
const { getState } = await import("./store/db.js");
|
|
150
|
+
const val = getState("auto_update_enabled");
|
|
151
|
+
return val !== "false";
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** Toggle auto-update notifications on/off. Returns the new state. */
|
|
158
|
+
export async function toggleAutoUpdate() {
|
|
159
|
+
const { getState, setState } = await import("./store/db.js");
|
|
160
|
+
const current = getState("auto_update_enabled");
|
|
161
|
+
const newVal = current === "false" ? "true" : "false";
|
|
162
|
+
setState("auto_update_enabled", newVal);
|
|
163
|
+
return newVal === "true";
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Get the version that the user dismissed (won't be notified about again).
|
|
167
|
+
*/
|
|
168
|
+
export async function getDismissedVersion() {
|
|
169
|
+
try {
|
|
170
|
+
const { getState } = await import("./store/db.js");
|
|
171
|
+
return getState("dismissed_version");
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/** Dismiss update notifications for a specific version. */
|
|
178
|
+
export async function dismissVersion(version) {
|
|
179
|
+
const { setState } = await import("./store/db.js");
|
|
180
|
+
setState("dismissed_version", version);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Fetch recent version history from npm registry for changelog display.
|
|
184
|
+
* Returns the last N versions with their publish dates.
|
|
185
|
+
*/
|
|
186
|
+
export async function getChangelog(limit = 5) {
|
|
187
|
+
try {
|
|
188
|
+
const res = await fetch(NPM_REGISTRY_URL, { signal: AbortSignal.timeout(15_000) });
|
|
189
|
+
if (!res.ok)
|
|
190
|
+
return [];
|
|
191
|
+
const data = (await res.json());
|
|
192
|
+
if (!data.time || !data.versions)
|
|
193
|
+
return [];
|
|
194
|
+
const versions = Object.keys(data.versions)
|
|
195
|
+
.filter((v) => v in data.time && v !== "created" && v !== "modified")
|
|
196
|
+
.sort((a, b) => {
|
|
197
|
+
const dateA = new Date(data.time[a]).getTime();
|
|
198
|
+
const dateB = new Date(data.time[b]).getTime();
|
|
199
|
+
return dateB - dateA;
|
|
200
|
+
})
|
|
201
|
+
.slice(0, limit);
|
|
202
|
+
return versions.map((v) => ({
|
|
203
|
+
version: v,
|
|
204
|
+
date: new Date(data.time[v]).toISOString().split("T")[0],
|
|
205
|
+
}));
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/** Get the current local version. */
|
|
212
|
+
export function getCurrentVersion() {
|
|
213
|
+
return getLocalVersion();
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Schedule periodic update checks (every 6 hours).
|
|
217
|
+
* Calls the provided callback when an update is found.
|
|
218
|
+
*/
|
|
219
|
+
export function scheduleUpdateCheck(onUpdateFound) {
|
|
220
|
+
if (updateCheckTimer)
|
|
221
|
+
return; // already scheduled
|
|
222
|
+
updateCheckTimer = setInterval(async () => {
|
|
223
|
+
try {
|
|
224
|
+
const autoEnabled = await isAutoUpdateEnabled();
|
|
225
|
+
if (!autoEnabled)
|
|
226
|
+
return;
|
|
227
|
+
const result = await checkForUpdate();
|
|
228
|
+
if (result.updateAvailable && result.latest) {
|
|
229
|
+
const dismissed = await getDismissedVersion();
|
|
230
|
+
if (dismissed === result.latest)
|
|
231
|
+
return;
|
|
232
|
+
onUpdateFound(result);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
// Silent — network may be unavailable
|
|
237
|
+
}
|
|
238
|
+
}, CHECK_INTERVAL_MS);
|
|
239
|
+
updateCheckTimer.unref();
|
|
240
|
+
console.log("[nzb] Update check scheduled (every 6 hours)");
|
|
241
|
+
}
|
|
242
|
+
/** Stop the periodic update check timer. */
|
|
243
|
+
export function stopUpdateCheck() {
|
|
244
|
+
if (updateCheckTimer) {
|
|
245
|
+
clearInterval(updateCheckTimer);
|
|
246
|
+
updateCheckTimer = undefined;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
80
249
|
//# sourceMappingURL=update.js.map
|