claude-telegram-bot 0.3.36 → 0.3.40
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/README.ko.md +3 -3
- package/README.md +3 -3
- package/bot.mjs +65 -6
- package/package.json +1 -1
package/README.ko.md
CHANGED
|
@@ -110,7 +110,7 @@ claude-telegram-bot ~/botconfigs/myproj/mybot.json
|
|
|
110
110
|
| `commands` | (선택) 쉘 스크립트를 실행하는 커스텀 `/명령어` — [커스텀 명령어](#커스텀-명령어) 참고 |
|
|
111
111
|
| `ollamaFallback` | (선택) `true`로 설정하면 Claude 레이트 리밋·크레딧 부족 시 로컬 Ollama로 자동 대체 |
|
|
112
112
|
| `ollamaModel` | (선택) 폴백에 쓸 Ollama 모델 (기본값: `"phi3:mini"`) |
|
|
113
|
-
| `autoCompactThreshold` | (선택) 캐시된 입력 토큰이 이 값을 초과하면 컨텍스트 자동 압축 (기본값: `100000`). `0`이면
|
|
113
|
+
| `autoCompactThreshold` | (선택) 캐시된 입력 토큰이 이 값을 초과하면 컨텍스트 자동 압축 (기본값: `100000`). `0`이면 비활성화. 런타임에 `/autocompact`로 전환 가능(state에 저장) |
|
|
114
114
|
|
|
115
115
|
`state`와 첨부 파일은 config 파일 옆 **`.claude-bot/` 숨김 폴더**에 저장됩니다(프로젝트 격리). 구버전에서 올리면 첫 시작 때 기존 `state.json`·`attachments/`를 `.claude-bot/`로 **자동 이동**합니다(무손실). 로그는 launchd plist가 가리키는 위치 그대로입니다.
|
|
116
116
|
|
|
@@ -122,7 +122,7 @@ claude-telegram-bot ~/botconfigs/myproj/mybot.json
|
|
|
122
122
|
- `테스트 돌려보고 통과하면 커밋하고 push 해줘`
|
|
123
123
|
- `api.ts 에 에러 핸들링 추가해줘`
|
|
124
124
|
|
|
125
|
-
명령어: `/new`(맥락 초기화) · `/compact`(컨텍스트 압축, 세션 유지) · `/plan <요청>`(계획만 세우고 승인/취소) · `/stop`(작업 중단; `--reset`으로 세션도 롤백) · `/ollama`(Ollama 채팅 모드 토글) · `/cron`(예약 작업 보기·추가·삭제) · `/reserve`(대기 큐 상태 확인 · `/reserve rm`으로 취소) · `/restart`(문법 검사 후 재시작) · `/status`(봇 상태·버전) · `/model`(모델 보기·전환) · `/id`(채팅 ID 확인) · `/help`(도움말)
|
|
125
|
+
명령어: `/new`(맥락 초기화) · `/compact`(컨텍스트 압축, 세션 유지) · `/plan <요청>`(계획만 세우고 승인/취소) · `/stop`(작업 중단; `--reset`으로 세션도 롤백) · `/ollama`(Ollama 채팅 모드 토글) · `/cron`(예약 작업 보기·추가·삭제) · `/reserve`(대기 큐 상태 확인 · `/reserve rm`으로 취소) · `/restart`(문법 검사 후 재시작) · `/status`(봇 상태·버전) · `/model`(모델 보기·전환) · `/autocompact`(자동 압축 임계값 보기·설정) · `/id`(채팅 ID 확인) · `/help`(도움말)
|
|
126
126
|
|
|
127
127
|
> **`/plan <요청>`** 은 봇에 설정된 `permissionMode`와 무관하게 강제로 읽기 전용 plan 모드(편집·쉘 없음)로 요청을 실행하고, 계획 내용과 함께 **✅ 진행 / ❌ 취소** 버튼을 보냅니다. **진행**을 누르면 같은 세션을 봇의 실제 `permissionMode`로 이어서 승인된 계획을 실행하고, **취소**를 누르면 세션은 그대로 유지됩니다. `bypassPermissions` 봇이 뭔가 건드리기 전에 검토 단계를 두고 싶을 때 유용합니다. `/new`로 새 세션을 시작하면 대기 중인 승인은 만료됩니다.
|
|
128
128
|
|
|
@@ -137,7 +137,7 @@ claude-telegram-bot ~/botconfigs/myproj/mybot.json
|
|
|
137
137
|
- **모델 권유** — 봇이 Claude에게 현재 모델을 알려줍니다. 질문 난이도가 현재 모델 수준을 넘는다고 판단되면 답변 끝에 전환 권유 한 줄이 붙습니다(예: 💡 `/model sonnet`). `/model <이름>`으로 전환(`haiku`, `sonnet`, `opus`, `fable`, 또는 전체 모델 ID) — `state.json`에 저장돼 재시작 후에도 유지됩니다.
|
|
138
138
|
- **한도 초과 큐** — Claude Max / API 레이트 리밋 에러에 리셋 시간이 포함되면, 해당 메시지를 자동으로 큐에 넣고 리셋 시각에 재시도합니다 — 작업 중 큐와 같은 방식입니다. 한도가 걸린 동안 추가로 보내는 메시지도 자동으로 큐에 쌓입니다. `/reserve`로 대기 현황과 리셋 시각 확인, `/reserve rm`으로 큐 전체 취소.
|
|
139
139
|
- **Ollama 폴백** — `"ollamaFallback": true`로 설정하고 `"ollamaModel"`에 로컬 [Ollama](https://ollama.ai) 모델을 지정하면(기본값: `"phi3:mini"`), Claude 레이트 리밋·크레딧 부족 시 자동으로 Ollama로 대체 응답합니다. `/ollama`로 Claude와 관계없이 Ollama를 기본 채팅 상대로 수동 전환할 수도 있습니다 (가벼운 질문에 유용).
|
|
140
|
-
- **자동 컴팩션** — 세션의 캐시된 입력 토큰이 `autoCompactThreshold`(기본값 100,000)를 초과하면 `/compact`를 자동 실행하고 알림을 보냅니다. config에서 임계값을
|
|
140
|
+
- **자동 컴팩션** — 세션의 캐시된 입력 토큰이 `autoCompactThreshold`(기본값 100,000)를 초과하면 `/compact`를 자동 실행하고 알림을 보냅니다. config에서 임계값을 조정하거나, 런타임에 `/autocompact <숫자>`로 설정(`/autocompact off`로 비활성화, `/autocompact default`로 초기화) — `state.json`에 저장돼 재시작 후에도 유지됩니다. 수동으로 `/compact`를 써도 됩니다.
|
|
141
141
|
- **간결한 답변** — 텔레그램에 맞게 짧게 답하도록 시스템 프롬프트가 기본으로 붙습니다. 바꾸려면 `appendSystemPrompt`에 직접 넣으세요 (빈 문자열이면 끔).
|
|
142
142
|
- **언어** — 봇 자체 문구(`/help`, 명령 메뉴, 상태 메시지)는 **기본 영어**, 텔레그램이 한국어인 사용자에겐 한국어로 나옵니다. `lang`(`"en"`/`"ko"`)으로 고정할 수 있습니다. Claude의 실제 답변은 **사용자가 쓴 언어**를 따라갑니다. `/` 명령 메뉴는 `setMyCommands`로 언어별 등록됩니다.
|
|
143
143
|
- **서식 변환** — 답변의 마크다운(굵게·코드·표 등)을 텔레그램 HTML로 바꿔 보냅니다. 변환이 깨지는 경우엔 평문으로 다시 보냅니다.
|
package/README.md
CHANGED
|
@@ -162,7 +162,7 @@ auth layer.)
|
|
|
162
162
|
- `run the solver tests and commit + push if they pass`
|
|
163
163
|
- `add an edge case to solve-2nd-floor-edges.ts`
|
|
164
164
|
|
|
165
|
-
Commands: `/new` (reset context / new session) · `/compact` (compress context, keep session) · `/plan <request>` (plan only, then approve/cancel) · `/stop` (stop current task; `--reset` to also roll back the session) · `/ollama` (toggle Ollama chat mode) · `/cron` (list / add / remove scheduled tasks) · `/reserve` (show queue status at usage-limit · `/reserve rm` to cancel) · `/restart` (syntax-check & restart the bot) · `/status` (bot status & version) · `/model` (view / switch the model) · `/id` (show chat ID) · `/help`.
|
|
165
|
+
Commands: `/new` (reset context / new session) · `/compact` (compress context, keep session) · `/plan <request>` (plan only, then approve/cancel) · `/stop` (stop current task; `--reset` to also roll back the session) · `/ollama` (toggle Ollama chat mode) · `/cron` (list / add / remove scheduled tasks) · `/reserve` (show queue status at usage-limit · `/reserve rm` to cancel) · `/restart` (syntax-check & restart the bot) · `/status` (bot status & version) · `/model` (view / switch the model) · `/autocompact` (view / set the auto-compact token threshold) · `/id` (show chat ID) · `/help`.
|
|
166
166
|
|
|
167
167
|
> **`/plan <request>`** runs the request in read-only plan mode (no edits, no shell) regardless of
|
|
168
168
|
> the bot's configured `permissionMode`, and replies with the plan plus **✅ Proceed / ❌ Cancel**
|
|
@@ -213,7 +213,7 @@ Edit `mybot.json`:
|
|
|
213
213
|
| `commands` | (optional) Custom `/commands` that run shell scripts — see [Custom commands](#custom-commands) |
|
|
214
214
|
| `ollamaFallback` | (optional) `true` to enable Ollama as a fallback when Claude is rate-limited or out of credits |
|
|
215
215
|
| `ollamaModel` | (optional) Ollama model to use for fallback (default: `"phi3:mini"`) |
|
|
216
|
-
| `autoCompactThreshold` | (optional) Auto-compact context when cached input tokens exceed this value (default: `100000`). Set to `0` to disable. |
|
|
216
|
+
| `autoCompactThreshold` | (optional) Auto-compact context when cached input tokens exceed this value (default: `100000`). Set to `0` to disable. Override at runtime with `/autocompact` (persists in state). |
|
|
217
217
|
|
|
218
218
|
State and downloaded attachments live in a hidden **`.claude-bot/`** folder next to the config
|
|
219
219
|
file, so projects stay isolated. Upgrading from an older version **auto-moves** an existing
|
|
@@ -238,7 +238,7 @@ your launchd plist points them.
|
|
|
238
238
|
- **Model hint**: the bot tells Claude which model it is running as. If Claude judges a question to be beyond its current tier, it appends a one-line suggestion at the end of the reply (e.g. 💡 `/model sonnet`). Switch with `/model <name>` — `haiku`, `sonnet`, `opus`, `fable`, or a full model id. The choice persists in `state.json` across restarts.
|
|
239
239
|
- **Usage-limit queue**: when a Claude Max / API rate-limit error includes a reset time, the triggering message is automatically queued and retried at that time — just like messages queued while Claude is busy. Any additional messages you send during the limit window are also added to the queue. Use `/reserve` to check queue status and reset time, `/reserve rm` to cancel and clear the queue.
|
|
240
240
|
- **Ollama fallback**: set `"ollamaFallback": true` and point `"ollamaModel"` at a locally-running [Ollama](https://ollama.ai) model (default: `"phi3:mini"`). When Claude is rate-limited or out of credits, the bot automatically falls back to Ollama and prepends a notice to the reply. Use `/ollama` to toggle Ollama as your primary chat partner at any time, even when Claude is working fine — useful for lightweight queries.
|
|
241
|
-
- **Auto-compact**: the bot tracks how many cached tokens are in the session context. When `cache_read_input_tokens` exceeds `autoCompactThreshold` (default 100 000), it automatically runs `/compact` and notifies you. Tune the threshold in config or
|
|
241
|
+
- **Auto-compact**: the bot tracks how many cached tokens are in the session context. When `cache_read_input_tokens` exceeds `autoCompactThreshold` (default 100 000), it automatically runs `/compact` and notifies you. Tune the threshold in config, or at runtime with `/autocompact <number>` (`/autocompact off` to disable, `/autocompact default` to reset) — the override persists in `state.json` across restarts. You can also run `/compact` manually at any time.
|
|
242
242
|
|
|
243
243
|
### Custom commands
|
|
244
244
|
|
package/bot.mjs
CHANGED
|
@@ -145,6 +145,7 @@ const STR = {
|
|
|
145
145
|
"• /restart — restart the bot (after a syntax check)\n" +
|
|
146
146
|
"• /status — bot status & version\n" +
|
|
147
147
|
"• /model — view / switch the model\n" +
|
|
148
|
+
"• /autocompact — view / set the auto-compact token threshold\n" +
|
|
148
149
|
"• /id — show this chat ID\n" +
|
|
149
150
|
`\nWorking dir: ${cfg.projectDir}\nPermission mode: ${cfg.permissionMode}`,
|
|
150
151
|
newSession: "🆕 Started a new conversation (previous context cleared).",
|
|
@@ -208,6 +209,13 @@ const STR = {
|
|
|
208
209
|
`/model default — clear the override`,
|
|
209
210
|
modelSet: (m) => `🧠 Model set to: ${m}`,
|
|
210
211
|
modelReset: (def) => `🧠 Model reset to default (${def}).`,
|
|
212
|
+
autoCompactStatus: (cur, def) =>
|
|
213
|
+
`🗜️ Auto-compact threshold: ${cur} tokens${cur === def ? " (default)" : ""}\n` +
|
|
214
|
+
"Set: `/autocompact <number>` · `/autocompact off` to disable · `/autocompact default` to reset",
|
|
215
|
+
autoCompactSet: (n) => `🗜️ Auto-compact threshold set to ${n} tokens.`,
|
|
216
|
+
autoCompactOff: "🗜️ Auto-compact disabled.",
|
|
217
|
+
autoCompactReset: (def) => `🗜️ Auto-compact threshold reset to default (${def}).`,
|
|
218
|
+
autoCompactUsage: "Usage: `/autocompact <number>` · `/autocompact off` · `/autocompact default`",
|
|
211
219
|
memoryEmpty: "No memory yet. Use `/remember <text>` to add.",
|
|
212
220
|
memoryShow: (m) => `💾 Memory:\n\`\`\`\n${m}\n\`\`\``,
|
|
213
221
|
memoryCleared: "🧹 Memory cleared.",
|
|
@@ -237,6 +245,7 @@ const STR = {
|
|
|
237
245
|
"• /restart — 봇 재시작 (문법 검사 후 안전하게)\n" +
|
|
238
246
|
"• /status — 봇 상태·버전 보기\n" +
|
|
239
247
|
"• /model — 모델 보기·전환\n" +
|
|
248
|
+
"• /autocompact — 자동 압축 임계값 보기·설정\n" +
|
|
240
249
|
"• /id — 이 채팅 ID 확인\n" +
|
|
241
250
|
`\n작업 폴더: ${cfg.projectDir}\n권한 모드: ${cfg.permissionMode}`,
|
|
242
251
|
newSession: "🆕 새 대화를 시작합니다 (이전 맥락 초기화).",
|
|
@@ -286,6 +295,13 @@ const STR = {
|
|
|
286
295
|
`/model default — 오버라이드 해제`,
|
|
287
296
|
modelSet: (m) => `🧠 모델을 ${m} (으)로 설정했습니다.`,
|
|
288
297
|
modelReset: (def) => `🧠 모델을 기본값(${def})으로 되돌렸습니다.`,
|
|
298
|
+
autoCompactStatus: (cur, def) =>
|
|
299
|
+
`🗜️ 자동 압축 임계값: ${cur} 토큰${cur === def ? " (기본값)" : ""}\n` +
|
|
300
|
+
"설정: `/autocompact <숫자>` · `/autocompact off` 로 끄기 · `/autocompact default` 로 초기화",
|
|
301
|
+
autoCompactSet: (n) => `🗜️ 자동 압축 임계값을 ${n} 토큰으로 설정했습니다.`,
|
|
302
|
+
autoCompactOff: "🗜️ 자동 압축을 껐습니다.",
|
|
303
|
+
autoCompactReset: (def) => `🗜️ 자동 압축 임계값을 기본값(${def})으로 되돌렸습니다.`,
|
|
304
|
+
autoCompactUsage: "사용법: `/autocompact <숫자>` · `/autocompact off` · `/autocompact default`",
|
|
289
305
|
memoryEmpty: "저장된 메모리가 없습니다. `/remember <내용>`으로 추가하세요.",
|
|
290
306
|
memoryShow: (m) => `💾 메모리:\n\`\`\`\n${m}\n\`\`\``,
|
|
291
307
|
memoryCleared: "🧹 메모리를 삭제했습니다.",
|
|
@@ -335,6 +351,7 @@ const COMMANDS = {
|
|
|
335
351
|
{ command: "restart", description: "Restart the bot (after syntax check)" },
|
|
336
352
|
{ command: "status", description: "Bot status / version" },
|
|
337
353
|
{ command: "model", description: "View / switch the model" },
|
|
354
|
+
{ command: "autocompact", description: "View / set the auto-compact token threshold" },
|
|
338
355
|
{ command: "reserve", description: "Schedule retry when usage limit resets · /reserve rm to cancel" },
|
|
339
356
|
{ command: "id", description: "Show this chat ID" },
|
|
340
357
|
{ command: "help", description: "Help" },
|
|
@@ -351,6 +368,7 @@ const COMMANDS = {
|
|
|
351
368
|
{ command: "restart", description: "봇 재시작 (문법 검사 후)" },
|
|
352
369
|
{ command: "status", description: "봇 상태·버전 보기" },
|
|
353
370
|
{ command: "model", description: "모델 보기·전환" },
|
|
371
|
+
{ command: "autocompact", description: "자동 압축 임계값 보기·설정" },
|
|
354
372
|
{ command: "reserve", description: "한도 리셋 시 재시도 예약 · /reserve rm 으로 취소" },
|
|
355
373
|
{ command: "id", description: "이 채팅 ID 확인" },
|
|
356
374
|
{ command: "help", description: "도움말" },
|
|
@@ -873,8 +891,8 @@ async function handleCron(chatId, rest, l) {
|
|
|
873
891
|
return;
|
|
874
892
|
}
|
|
875
893
|
busy = true;
|
|
876
|
-
await tg("sendChatAction", { chat_id: chatId, action: "typing" });
|
|
877
894
|
try {
|
|
895
|
+
await tg("sendChatAction", { chat_id: chatId, action: "typing" });
|
|
878
896
|
const r = await extractCron(input, l);
|
|
879
897
|
if (r.error) {
|
|
880
898
|
await send(chatId, `⚠️ ${r.error}`);
|
|
@@ -1020,7 +1038,7 @@ async function replyWithClaudeResult(chatId, l, prompt, msg, res, started) {
|
|
|
1020
1038
|
const footer = `\n\n— ${secs}s${res.cost ? ` · $${res.cost.toFixed(4)}` : ""}`;
|
|
1021
1039
|
if (!stopping) await send(chatId, res.text + footer);
|
|
1022
1040
|
// 자동 컴팩션: cache_read_input_tokens 가 임계값 초과 시 자동 /compact
|
|
1023
|
-
const compactThreshold = cfg.autoCompactThreshold ?? 100000;
|
|
1041
|
+
const compactThreshold = state.autoCompactThreshold ?? cfg.autoCompactThreshold ?? 100000;
|
|
1024
1042
|
if (compactThreshold > 0 && res.cacheTokens > compactThreshold && state.sessionId && !stopping) {
|
|
1025
1043
|
try {
|
|
1026
1044
|
const cr = await runClaude("/compact", state.sessionId);
|
|
@@ -1050,7 +1068,6 @@ async function runApprovedPlan(chatId, l) {
|
|
|
1050
1068
|
return;
|
|
1051
1069
|
}
|
|
1052
1070
|
busy = true;
|
|
1053
|
-
await tg("sendChatAction", { chat_id: chatId, action: "typing" });
|
|
1054
1071
|
const started = Date.now();
|
|
1055
1072
|
currentTyping = setInterval(
|
|
1056
1073
|
() => tg("sendChatAction", { chat_id: chatId, action: "typing" }).catch(() => {}),
|
|
@@ -1058,6 +1075,7 @@ async function runApprovedPlan(chatId, l) {
|
|
|
1058
1075
|
);
|
|
1059
1076
|
const syntheticMsg = { chat: { id: chatId }, text: PLAN_PROCEED_PROMPT };
|
|
1060
1077
|
try {
|
|
1078
|
+
await tg("sendChatAction", { chat_id: chatId, action: "typing" });
|
|
1061
1079
|
prevSessionId = state.sessionId;
|
|
1062
1080
|
const res = await runClaude(PLAN_PROCEED_PROMPT, pending.sessionId, { modelHint: true, trackChild: true, injectMemory: true });
|
|
1063
1081
|
if (res.sessionId) {
|
|
@@ -1136,7 +1154,7 @@ async function handle(msg) {
|
|
|
1136
1154
|
}
|
|
1137
1155
|
if (text === "/status") {
|
|
1138
1156
|
const latest = await fetchLatestVersion();
|
|
1139
|
-
const versionStr = !latest || latest
|
|
1157
|
+
const versionStr = !latest || !isNewer(latest, VERSION)
|
|
1140
1158
|
? VERSION
|
|
1141
1159
|
: `${VERSION} → ${latest} ✨`;
|
|
1142
1160
|
await send(
|
|
@@ -1171,6 +1189,36 @@ async function handle(msg) {
|
|
|
1171
1189
|
await send(chatId, t(l, "modelSet", arg));
|
|
1172
1190
|
return;
|
|
1173
1191
|
}
|
|
1192
|
+
if (text === "/autocompact" || text.startsWith("/autocompact ")) {
|
|
1193
|
+
const arg = text.slice(13).trim();
|
|
1194
|
+
const def = cfg.autoCompactThreshold ?? 100000;
|
|
1195
|
+
if (!arg) {
|
|
1196
|
+
const cur = state.autoCompactThreshold ?? def;
|
|
1197
|
+
await send(chatId, t(l, "autoCompactStatus", cur, def));
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
if (arg === "default" || arg === "reset") {
|
|
1201
|
+
state.autoCompactThreshold = undefined;
|
|
1202
|
+
saveState(state);
|
|
1203
|
+
await send(chatId, t(l, "autoCompactReset", def));
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
if (arg === "off") {
|
|
1207
|
+
state.autoCompactThreshold = 0;
|
|
1208
|
+
saveState(state);
|
|
1209
|
+
await send(chatId, t(l, "autoCompactOff"));
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
const n = Number(arg);
|
|
1213
|
+
if (!Number.isFinite(n) || n < 0) {
|
|
1214
|
+
await send(chatId, t(l, "autoCompactUsage"));
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
state.autoCompactThreshold = n;
|
|
1218
|
+
saveState(state);
|
|
1219
|
+
await send(chatId, t(l, "autoCompactSet", n));
|
|
1220
|
+
return;
|
|
1221
|
+
}
|
|
1174
1222
|
if (text === "/cron" || text.startsWith("/cron ")) {
|
|
1175
1223
|
await handleCron(chatId, text.slice(5).trim(), l);
|
|
1176
1224
|
return;
|
|
@@ -1310,7 +1358,6 @@ async function handle(msg) {
|
|
|
1310
1358
|
return;
|
|
1311
1359
|
}
|
|
1312
1360
|
busy = true;
|
|
1313
|
-
await tg("sendChatAction", { chat_id: chatId, action: "typing" });
|
|
1314
1361
|
const started = Date.now();
|
|
1315
1362
|
// 긴 작업 동안 타이핑 표시 유지
|
|
1316
1363
|
currentTyping = setInterval(
|
|
@@ -1322,6 +1369,7 @@ async function handle(msg) {
|
|
|
1322
1369
|
);
|
|
1323
1370
|
|
|
1324
1371
|
try {
|
|
1372
|
+
await tg("sendChatAction", { chat_id: chatId, action: "typing" });
|
|
1325
1373
|
// /plan <요청> — permission-mode를 강제로 plan으로 실행해 편집 없이 계획만 받고,
|
|
1326
1374
|
// 승인 버튼을 눌러야 실제 permissionMode로 이어서 실행 (runApprovedPlan).
|
|
1327
1375
|
if (text === "/plan" || text.startsWith("/plan ")) {
|
|
@@ -1410,7 +1458,18 @@ function drainQueue() {
|
|
|
1410
1458
|
return i === 0 ? `[1] ${text}` : `[${i + 1}, +${dt}s] ${text}`;
|
|
1411
1459
|
})
|
|
1412
1460
|
.join("\n");
|
|
1413
|
-
|
|
1461
|
+
// 마지막 메시지 필드만 남기면 앞서 온 메시지의 사진/첨부가 유실되므로, 전체 첨부를 순서대로 모아 둠
|
|
1462
|
+
const fileIds = group.flatMap((item) => {
|
|
1463
|
+
if (item.msg._mediaGroup?.length) return item.msg._mediaGroup;
|
|
1464
|
+
const att = pickAttachment(item.msg);
|
|
1465
|
+
return att ? [att.fileId] : [];
|
|
1466
|
+
});
|
|
1467
|
+
return {
|
|
1468
|
+
...group[group.length - 1].msg,
|
|
1469
|
+
text: merged,
|
|
1470
|
+
caption: undefined,
|
|
1471
|
+
_mediaGroup: fileIds.length ? fileIds : undefined,
|
|
1472
|
+
};
|
|
1414
1473
|
}
|
|
1415
1474
|
|
|
1416
1475
|
// 미디어 그룹(여러 장 동시 전송) — 1초 대기 후 일괄 처리
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-telegram-bot",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.40",
|
|
4
4
|
"description": "Drive Claude Code from Telegram — messages run headless `claude -p` in a project dir and replies come back to the chat. Zero dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|