@gonzih/cc-tg 0.2.2 → 0.2.3

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/bot.d.ts CHANGED
@@ -17,6 +17,7 @@ export declare class CcTgBot {
17
17
  private isAllowed;
18
18
  private handleTelegram;
19
19
  private handleVoice;
20
+ private handlePhoto;
20
21
  private getOrCreateSession;
21
22
  private handleClaudeMessage;
22
23
  private startTyping;
package/dist/bot.js CHANGED
@@ -5,6 +5,8 @@
5
5
  import TelegramBot from "node-telegram-bot-api";
6
6
  import { existsSync } from "fs";
7
7
  import { resolve, basename } from "path";
8
+ import https from "https";
9
+ import http from "http";
8
10
  import { ClaudeProcess, extractText } from "./claude.js";
9
11
  import { transcribeVoice, isVoiceAvailable } from "./voice.js";
10
12
  import { CronManager } from "./cron.js";
@@ -53,6 +55,11 @@ export class CcTgBot {
53
55
  await this.handleVoice(chatId, msg);
54
56
  return;
55
57
  }
58
+ // Photo — send as base64 image content block to Claude
59
+ if (msg.photo?.length) {
60
+ await this.handlePhoto(chatId, msg);
61
+ return;
62
+ }
56
63
  const text = msg.text?.trim();
57
64
  if (!text)
58
65
  return;
@@ -120,6 +127,26 @@ export class CcTgBot {
120
127
  await this.bot.sendMessage(chatId, `Voice transcription failed: ${err.message}`);
121
128
  }
122
129
  }
130
+ async handlePhoto(chatId, msg) {
131
+ // Pick highest resolution photo
132
+ const photos = msg.photo;
133
+ const best = photos[photos.length - 1];
134
+ const caption = msg.caption?.trim();
135
+ console.log(`[photo:${chatId}] received image file_id=${best.file_id}`);
136
+ this.bot.sendChatAction(chatId, "typing").catch(() => { });
137
+ try {
138
+ const fileLink = await this.bot.getFileLink(best.file_id);
139
+ const imageData = await fetchAsBase64(fileLink);
140
+ // Telegram photos are always JPEG
141
+ const session = this.getOrCreateSession(chatId);
142
+ session.claude.sendImage(imageData, "image/jpeg", caption);
143
+ this.startTyping(chatId, session);
144
+ }
145
+ catch (err) {
146
+ console.error(`[photo:${chatId}] error:`, err.message);
147
+ await this.bot.sendMessage(chatId, `Failed to process image: ${err.message}`);
148
+ }
149
+ }
123
150
  getOrCreateSession(chatId) {
124
151
  const existing = this.sessions.get(chatId);
125
152
  if (existing && !existing.claude.exited)
@@ -356,6 +383,18 @@ export class CcTgBot {
356
383
  }
357
384
  }
358
385
  }
386
+ /** Download a URL and return its contents as a base64 string */
387
+ function fetchAsBase64(url) {
388
+ return new Promise((resolve, reject) => {
389
+ const client = url.startsWith("https") ? https : http;
390
+ client.get(url, (res) => {
391
+ const chunks = [];
392
+ res.on("data", (chunk) => chunks.push(chunk));
393
+ res.on("end", () => resolve(Buffer.concat(chunks).toString("base64")));
394
+ res.on("error", reject);
395
+ }).on("error", reject);
396
+ });
397
+ }
359
398
  function splitMessage(text, maxLen = 4096) {
360
399
  if (text.length <= maxLen)
361
400
  return [text];
package/dist/claude.d.ts CHANGED
@@ -30,6 +30,11 @@ export declare class ClaudeProcess extends EventEmitter {
30
30
  private _exited;
31
31
  constructor(opts?: ClaudeOptions);
32
32
  sendPrompt(text: string): void;
33
+ /**
34
+ * Send an image (with optional text caption) to Claude via stream-json content blocks.
35
+ * mediaType: image/jpeg | image/png | image/gif | image/webp
36
+ */
37
+ sendImage(base64Data: string, mediaType: string, caption?: string): void;
33
38
  kill(): void;
34
39
  get exited(): boolean;
35
40
  private drainBuffer;
package/dist/claude.js CHANGED
@@ -69,6 +69,31 @@ export class ClaudeProcess extends EventEmitter {
69
69
  });
70
70
  this.proc.stdin.write(payload + "\n");
71
71
  }
72
+ /**
73
+ * Send an image (with optional text caption) to Claude via stream-json content blocks.
74
+ * mediaType: image/jpeg | image/png | image/gif | image/webp
75
+ */
76
+ sendImage(base64Data, mediaType, caption) {
77
+ if (this._exited)
78
+ throw new Error("Claude process has exited");
79
+ const content = [];
80
+ if (caption) {
81
+ content.push({ type: "text", text: caption });
82
+ }
83
+ content.push({
84
+ type: "image",
85
+ source: {
86
+ type: "base64",
87
+ media_type: mediaType,
88
+ data: base64Data,
89
+ },
90
+ });
91
+ const payload = JSON.stringify({
92
+ type: "user",
93
+ message: { role: "user", content },
94
+ });
95
+ this.proc.stdin.write(payload + "\n");
96
+ }
72
97
  kill() {
73
98
  this.proc.kill();
74
99
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gonzih/cc-tg",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Claude Code Telegram bot — chat with Claude Code via Telegram",
5
5
  "type": "module",
6
6
  "bin": {