@iletai/nzb 1.9.0 → 1.9.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/cron/task-runner.js
CHANGED
|
@@ -262,7 +262,8 @@ async function generateAndSendVocabAudio(word) {
|
|
|
262
262
|
}
|
|
263
263
|
catch { /* ignore */ }
|
|
264
264
|
// Generate TTS with macOS say
|
|
265
|
-
|
|
265
|
+
const safeWord = word.replace(/["`$\\]/g, "");
|
|
266
|
+
execSync(`say -v Samantha -o "${aiffPath}" "${safeWord}"`, {
|
|
266
267
|
timeout: 10_000,
|
|
267
268
|
});
|
|
268
269
|
if (!existsSync(aiffPath)) {
|
|
@@ -275,16 +276,21 @@ async function generateAndSendVocabAudio(word) {
|
|
|
275
276
|
if (!existsSync(m4aPath)) {
|
|
276
277
|
throw new Error("Audio conversion failed: m4a file not created");
|
|
277
278
|
}
|
|
279
|
+
// Resolve symlink path (macOS /tmp → /private/tmp) for grammy InputFile
|
|
280
|
+
const { realpathSync: realpath } = await import("fs");
|
|
281
|
+
const resolvedPath = realpath(m4aPath);
|
|
282
|
+
console.log(`[nzb] Vocab TTS: sending voice for "${word}" (${resolvedPath}, ${statSync(resolvedPath).size} bytes)`);
|
|
278
283
|
// Send voice via Telegram
|
|
279
284
|
const { sendVoice } = await import("../telegram/bot.js");
|
|
280
|
-
await sendVoice(
|
|
285
|
+
await sendVoice(resolvedPath, `🔊 ${word}`);
|
|
286
|
+
console.log(`[nzb] Vocab TTS: voice sent successfully for "${word}"`);
|
|
281
287
|
// Clean up temp files
|
|
282
288
|
try {
|
|
283
289
|
unlinkSync(aiffPath);
|
|
284
290
|
}
|
|
285
291
|
catch { /* ignore */ }
|
|
286
292
|
try {
|
|
287
|
-
unlinkSync(
|
|
293
|
+
unlinkSync(resolvedPath);
|
|
288
294
|
}
|
|
289
295
|
catch { /* ignore */ }
|
|
290
296
|
}
|
|
@@ -13,6 +13,13 @@ const SCHEDULE_PRESETS = [
|
|
|
13
13
|
{ label: "Every 6 hours", cron: "0 */6 * * *" },
|
|
14
14
|
{ label: "Every day at 8AM", cron: "0 8 * * *" },
|
|
15
15
|
];
|
|
16
|
+
// ── Model presets ────────────────────────────────────────────────────
|
|
17
|
+
const MODEL_PRESETS = [
|
|
18
|
+
{ label: "⚡ Haiku (fast & cheap)", id: "claude-haiku-4.5", short: "haiku" },
|
|
19
|
+
{ label: "⚖️ Sonnet (balanced)", id: "claude-sonnet-4", short: "sonnet" },
|
|
20
|
+
{ label: "🧠 Opus (most capable)", id: "claude-opus-4.6", short: "opus" },
|
|
21
|
+
{ label: "⚡ GPT-4.1 (fast alt)", id: "gpt-4.1", short: "gpt-4.1" },
|
|
22
|
+
];
|
|
16
23
|
// In-memory state for users awaiting custom cron expression input
|
|
17
24
|
const pendingCustomSchedule = new Map();
|
|
18
25
|
/** Check if a user is awaiting custom cron input and return the job ID. */
|
|
@@ -39,6 +46,7 @@ function buildJobListKeyboard(jobs, page, totalPages) {
|
|
|
39
46
|
const toggleLabel = job.enabled ? "⏸" : "▶️";
|
|
40
47
|
kb.text(`${toggleLabel} ${job.name}`, `cron:toggle:${job.id}`)
|
|
41
48
|
.text("⏱", `cron:schedule:${job.id}`)
|
|
49
|
+
.text("🤖", `cron:model:${job.id}`)
|
|
42
50
|
.text("▶ Run", `cron:trigger:${job.id}`)
|
|
43
51
|
.text("📊", `cron:history:${job.id}`)
|
|
44
52
|
.text("🗑", `cron:delete:${job.id}`)
|
|
@@ -64,6 +72,18 @@ function buildScheduleKeyboard(jobId) {
|
|
|
64
72
|
kb.text("🔙 Back to list", "cron:list");
|
|
65
73
|
return kb;
|
|
66
74
|
}
|
|
75
|
+
function buildModelKeyboard(jobId, currentModel) {
|
|
76
|
+
const kb = new InlineKeyboard();
|
|
77
|
+
for (let i = 0; i < MODEL_PRESETS.length; i++) {
|
|
78
|
+
const preset = MODEL_PRESETS[i];
|
|
79
|
+
const current = currentModel === preset.id ? " ✓" : "";
|
|
80
|
+
kb.text(`${preset.label}${current}`, `cron:setmodel:${jobId}:${i}`).row();
|
|
81
|
+
}
|
|
82
|
+
const noOverride = !currentModel ? " ✓" : "";
|
|
83
|
+
kb.text(`🚫 No model override (use default)${noOverride}`, `cron:setmodel:${jobId}:none`).row();
|
|
84
|
+
kb.text("🔙 Back to list", "cron:list");
|
|
85
|
+
return kb;
|
|
86
|
+
}
|
|
67
87
|
/** Apply a schedule change: update DB and reschedule. */
|
|
68
88
|
function applyScheduleChange(jobId, cronExpression) {
|
|
69
89
|
const updated = updateCronJob(jobId, { cronExpression });
|
|
@@ -88,8 +108,11 @@ function formatJobLine(job, index) {
|
|
|
88
108
|
minute: "2-digit",
|
|
89
109
|
})
|
|
90
110
|
: "—";
|
|
111
|
+
const modelTag = job.model
|
|
112
|
+
? ` | 🤖 ${MODEL_PRESETS.find((p) => p.id === job.model)?.short ?? job.model}`
|
|
113
|
+
: "";
|
|
91
114
|
return (`${index}. ${status} ${job.name}\n` +
|
|
92
|
-
` ⏰ ${job.cronExpression} | 🏷 ${job.taskType}\n` +
|
|
115
|
+
` ⏰ ${job.cronExpression} | 🏷 ${job.taskType}${modelTag}\n` +
|
|
93
116
|
` 📅 Next: ${nextRun}`);
|
|
94
117
|
}
|
|
95
118
|
function buildJobListText(jobs, page, totalPages) {
|
|
@@ -437,6 +460,74 @@ export function registerCronHandlers(bot) {
|
|
|
437
460
|
}
|
|
438
461
|
}
|
|
439
462
|
});
|
|
463
|
+
// Model selection — show model presets for a job
|
|
464
|
+
bot.callbackQuery(/^cron:model:(.+)$/, async (ctx) => {
|
|
465
|
+
try {
|
|
466
|
+
const jobId = ctx.match[1];
|
|
467
|
+
const job = getCronJob(jobId);
|
|
468
|
+
if (!job) {
|
|
469
|
+
await ctx.answerCallbackQuery({ text: "Job not found", show_alert: true });
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const currentLabel = job.model
|
|
473
|
+
? MODEL_PRESETS.find((p) => p.id === job.model)?.label ?? job.model
|
|
474
|
+
: "Default (no override)";
|
|
475
|
+
const text = `🤖 Change Model: ${job.name}\n\n` +
|
|
476
|
+
`Current: ${currentLabel}\n\n` +
|
|
477
|
+
"Select a model for this job:";
|
|
478
|
+
await ctx.answerCallbackQuery();
|
|
479
|
+
await ctx.editMessageText(text, {
|
|
480
|
+
reply_markup: buildModelKeyboard(job.id, job.model),
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
catch (err) {
|
|
484
|
+
if (!isMessageNotModifiedError(err)) {
|
|
485
|
+
console.error("[nzb] Cron model menu error:", err instanceof Error ? err.message : err);
|
|
486
|
+
await ctx
|
|
487
|
+
.answerCallbackQuery({ text: "Error loading model options", show_alert: true })
|
|
488
|
+
.catch(() => { });
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
// Set model from preset or clear override
|
|
493
|
+
bot.callbackQuery(/^cron:setmodel:(.+):(none|\d+)$/, async (ctx) => {
|
|
494
|
+
try {
|
|
495
|
+
const jobId = ctx.match[1];
|
|
496
|
+
const selection = ctx.match[2];
|
|
497
|
+
const job = getCronJob(jobId);
|
|
498
|
+
if (!job) {
|
|
499
|
+
await ctx.answerCallbackQuery({ text: "Job not found", show_alert: true });
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
let newModel;
|
|
503
|
+
let label;
|
|
504
|
+
if (selection === "none") {
|
|
505
|
+
newModel = null;
|
|
506
|
+
label = "default";
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
const presetIndex = parseInt(selection, 10);
|
|
510
|
+
const preset = MODEL_PRESETS[presetIndex];
|
|
511
|
+
if (!preset) {
|
|
512
|
+
await ctx.answerCallbackQuery({ text: "Invalid model", show_alert: true });
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
newModel = preset.id;
|
|
516
|
+
label = preset.short;
|
|
517
|
+
}
|
|
518
|
+
updateCronJob(jobId, { model: newModel });
|
|
519
|
+
await ctx.answerCallbackQuery(`Model → ${label}`);
|
|
520
|
+
await showCronList(ctx, 0, true);
|
|
521
|
+
}
|
|
522
|
+
catch (err) {
|
|
523
|
+
if (!isMessageNotModifiedError(err)) {
|
|
524
|
+
console.error("[nzb] Cron set model error:", err instanceof Error ? err.message : err);
|
|
525
|
+
await ctx
|
|
526
|
+
.answerCallbackQuery({ text: "Error updating model", show_alert: true })
|
|
527
|
+
.catch(() => { });
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
});
|
|
440
531
|
// Custom schedule — prompt user to type cron expression
|
|
441
532
|
bot.callbackQuery(/^cron:customsched:(.+)$/, async (ctx) => {
|
|
442
533
|
try {
|