@iletai/nzb 1.2.5 → 1.3.0
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/telegram/bot.js +146 -0
- package/package.json +2 -1
package/dist/telegram/bot.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { autoRetry } from "@grammyjs/auto-retry";
|
|
1
2
|
import { Bot, InlineKeyboard } from "grammy";
|
|
2
3
|
import { Agent as HttpsAgent } from "https";
|
|
3
4
|
import { config, persistEnvVar, persistModel } from "../config.js";
|
|
@@ -42,6 +43,8 @@ export function createBot() {
|
|
|
42
43
|
});
|
|
43
44
|
console.log("[nzb] Telegram bot using direct HTTPS agent (proxy bypass)");
|
|
44
45
|
initLogChannel(bot);
|
|
46
|
+
// Auto-retry on rate limit (429) and server errors (500+)
|
|
47
|
+
bot.api.config.use(autoRetry({ maxRetryAttempts: 3, maxDelaySeconds: 10 }));
|
|
45
48
|
// Auth middleware — only allow the authorized user
|
|
46
49
|
bot.use(async (ctx, next) => {
|
|
47
50
|
if (config.authorizedUserId !== undefined && ctx.from?.id !== config.authorizedUserId) {
|
|
@@ -529,6 +532,149 @@ export function createBot() {
|
|
|
529
532
|
}
|
|
530
533
|
}, onToolEvent, onUsage);
|
|
531
534
|
});
|
|
535
|
+
// Handle photo messages — download and pass to AI as image description request
|
|
536
|
+
bot.on("message:photo", async (ctx) => {
|
|
537
|
+
const chatId = ctx.chat.id;
|
|
538
|
+
const userMessageId = ctx.message.message_id;
|
|
539
|
+
const caption = ctx.message.caption || "Describe this image and analyze what you see.";
|
|
540
|
+
void logInfo(`📸 Photo received: ${caption.slice(0, 80)}`);
|
|
541
|
+
try {
|
|
542
|
+
await ctx.react("👀");
|
|
543
|
+
}
|
|
544
|
+
catch { }
|
|
545
|
+
// Get the largest photo (last in array)
|
|
546
|
+
const photo = ctx.message.photo[ctx.message.photo.length - 1];
|
|
547
|
+
try {
|
|
548
|
+
const file = await ctx.api.getFile(photo.file_id);
|
|
549
|
+
const filePath = file.file_path;
|
|
550
|
+
if (!filePath) {
|
|
551
|
+
await ctx.reply("❌ Could not download photo.", { reply_parameters: { message_id: userMessageId } });
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
const url = `https://api.telegram.org/file/bot${config.telegramBotToken}/${filePath}`;
|
|
555
|
+
// Download to temp file
|
|
556
|
+
const { mkdtempSync, writeFileSync } = await import("fs");
|
|
557
|
+
const { join } = await import("path");
|
|
558
|
+
const { tmpdir } = await import("os");
|
|
559
|
+
const tmpDir = mkdtempSync(join(tmpdir(), "nzb-photo-"));
|
|
560
|
+
const ext = filePath.split(".").pop() || "jpg";
|
|
561
|
+
const localPath = join(tmpDir, `photo.${ext}`);
|
|
562
|
+
const response = await fetch(url);
|
|
563
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
564
|
+
writeFileSync(localPath, buffer);
|
|
565
|
+
// Send to orchestrator with image context
|
|
566
|
+
const prompt = `[User sent a photo saved at: ${localPath}]\n\nCaption: ${caption}\n\nPlease analyze this image. The file is at ${localPath} — you can use bash to view it with tools if needed.`;
|
|
567
|
+
sendToOrchestrator(prompt, { type: "telegram", chatId, messageId: userMessageId }, (text, done) => {
|
|
568
|
+
if (done) {
|
|
569
|
+
const formatted = toTelegramMarkdown(text);
|
|
570
|
+
const chunks = chunkMessage(formatted);
|
|
571
|
+
const fallbackChunks = chunkMessage(text);
|
|
572
|
+
void (async () => {
|
|
573
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
574
|
+
if (i > 0)
|
|
575
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
576
|
+
const pageTag = chunks.length > 1 ? `📄 ${i + 1}/${chunks.length}\n` : "";
|
|
577
|
+
try {
|
|
578
|
+
await ctx.api.sendMessage(chatId, pageTag + chunks[i], {
|
|
579
|
+
parse_mode: "MarkdownV2",
|
|
580
|
+
reply_parameters: i === 0 ? { message_id: userMessageId } : undefined,
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
catch {
|
|
584
|
+
try {
|
|
585
|
+
await ctx.api.sendMessage(chatId, pageTag + (fallbackChunks[i] ?? chunks[i]), {
|
|
586
|
+
reply_parameters: i === 0 ? { message_id: userMessageId } : undefined,
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
catch { }
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
try {
|
|
593
|
+
await ctx.api.setMessageReaction(chatId, userMessageId, [{ type: "emoji", emoji: "👍" }]);
|
|
594
|
+
}
|
|
595
|
+
catch { }
|
|
596
|
+
})();
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
catch (err) {
|
|
601
|
+
await ctx.reply(`❌ Error processing photo: ${err instanceof Error ? err.message : String(err)}`, {
|
|
602
|
+
reply_parameters: { message_id: userMessageId },
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
// Handle document/file messages — download and pass to AI
|
|
607
|
+
bot.on("message:document", async (ctx) => {
|
|
608
|
+
const chatId = ctx.chat.id;
|
|
609
|
+
const userMessageId = ctx.message.message_id;
|
|
610
|
+
const doc = ctx.message.document;
|
|
611
|
+
const caption = ctx.message.caption || `Analyze this file: ${doc.file_name || "unknown"}`;
|
|
612
|
+
void logInfo(`📄 Document received: ${doc.file_name || "unknown"} (${doc.file_size || 0} bytes)`);
|
|
613
|
+
try {
|
|
614
|
+
await ctx.react("👀");
|
|
615
|
+
}
|
|
616
|
+
catch { }
|
|
617
|
+
// Limit file size to 10MB
|
|
618
|
+
if (doc.file_size && doc.file_size > 10 * 1024 * 1024) {
|
|
619
|
+
await ctx.reply("❌ File too large (max 10MB).", { reply_parameters: { message_id: userMessageId } });
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
try {
|
|
623
|
+
const file = await ctx.api.getFile(doc.file_id);
|
|
624
|
+
const filePath = file.file_path;
|
|
625
|
+
if (!filePath) {
|
|
626
|
+
await ctx.reply("❌ Could not download file.", { reply_parameters: { message_id: userMessageId } });
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
const url = `https://api.telegram.org/file/bot${config.telegramBotToken}/${filePath}`;
|
|
630
|
+
const { mkdtempSync, writeFileSync } = await import("fs");
|
|
631
|
+
const { join } = await import("path");
|
|
632
|
+
const { tmpdir } = await import("os");
|
|
633
|
+
const tmpDir = mkdtempSync(join(tmpdir(), "nzb-doc-"));
|
|
634
|
+
const localPath = join(tmpDir, doc.file_name || "file");
|
|
635
|
+
const response = await fetch(url);
|
|
636
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
637
|
+
writeFileSync(localPath, buffer);
|
|
638
|
+
const prompt = `[User sent a file: ${doc.file_name || "unknown"} (${doc.file_size || 0} bytes), saved at: ${localPath}]\n\nCaption: ${caption}\n\nPlease analyze this file. You can read it with bash tools.`;
|
|
639
|
+
sendToOrchestrator(prompt, { type: "telegram", chatId, messageId: userMessageId }, (text, done) => {
|
|
640
|
+
if (done) {
|
|
641
|
+
const formatted = toTelegramMarkdown(text);
|
|
642
|
+
const chunks = chunkMessage(formatted);
|
|
643
|
+
const fallbackChunks = chunkMessage(text);
|
|
644
|
+
void (async () => {
|
|
645
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
646
|
+
if (i > 0)
|
|
647
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
648
|
+
const pageTag = chunks.length > 1 ? `📄 ${i + 1}/${chunks.length}\n` : "";
|
|
649
|
+
try {
|
|
650
|
+
await ctx.api.sendMessage(chatId, pageTag + chunks[i], {
|
|
651
|
+
parse_mode: "MarkdownV2",
|
|
652
|
+
reply_parameters: i === 0 ? { message_id: userMessageId } : undefined,
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
catch {
|
|
656
|
+
try {
|
|
657
|
+
await ctx.api.sendMessage(chatId, pageTag + (fallbackChunks[i] ?? chunks[i]), {
|
|
658
|
+
reply_parameters: i === 0 ? { message_id: userMessageId } : undefined,
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
catch { }
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
try {
|
|
665
|
+
await ctx.api.setMessageReaction(chatId, userMessageId, [{ type: "emoji", emoji: "👍" }]);
|
|
666
|
+
}
|
|
667
|
+
catch { }
|
|
668
|
+
})();
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
catch (err) {
|
|
673
|
+
await ctx.reply(`❌ Error processing file: ${err instanceof Error ? err.message : String(err)}`, {
|
|
674
|
+
reply_parameters: { message_id: userMessageId },
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
});
|
|
532
678
|
return bot;
|
|
533
679
|
}
|
|
534
680
|
export async function startBot() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iletai/nzb",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "NZB — a personal AI assistant for developers, built on the GitHub Copilot SDK",
|
|
5
5
|
"bin": {
|
|
6
6
|
"nzb": "dist/cli.js"
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"type": "module",
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@github/copilot-sdk": "^0.1.26",
|
|
50
|
+
"@grammyjs/auto-retry": "^2.0.2",
|
|
50
51
|
"better-sqlite3": "^12.6.2",
|
|
51
52
|
"dotenv": "^17.3.1",
|
|
52
53
|
"express": "^5.2.1",
|