Skip to content

Commit bf98913

Browse files
TheEdgeOfClawTheEdgeOfClaw
andauthored
Quote messsage sent to opencode in telegram (#4)
* Quote messsage sent to opencode in telegram * Fix CI --------- Co-authored-by: TheEdgeOfClaw <claw@theedgeofrage.com>
1 parent f394552 commit bf98913

File tree

2 files changed

+40
-8
lines changed

2 files changed

+40
-8
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ jobs:
1010
- uses: actions/checkout@v6
1111
- uses: oven-sh/setup-bun@v2
1212
- run: bun install
13-
- run: make check
13+
- run: bun lint
14+
- run: bun typecheck

src/telegram.ts

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { spawnSync } from "node:child_process";
22
import { Bot, InlineKeyboard, type Context } from "grammy";
3+
import type { Chat } from "grammy/types";
34
import type { Part } from "@opencode-ai/sdk";
45
import * as log from "./log.js";
56
import type { PermissionEvent } from "./events.js";
@@ -32,8 +33,22 @@ export const BOT_COMMANDS = [
3233
{ command: "stop_llama", description: "Stop llama service" },
3334
];
3435

36+
function isChannel(chat: Chat): boolean {
37+
return chat.type === "channel" || chat.type === "supergroup";
38+
}
39+
40+
function formatAsQuote(text: string): string {
41+
const escaped = escapeMarkdownV2(text);
42+
const lines = escaped.split("\n");
43+
const quoted = lines.map((line) => `> ${line}`).join("\n");
44+
return quoted;
45+
}
46+
3547
const THROTTLE_MS = 2000;
3648

49+
// Track channel membership for quoting
50+
let channelChatId: number | null = null;
51+
3752
// Telegram callback data is limited to 64 bytes. Permission IDs are too long,
3853
// so we store them in a map keyed by a short incrementing counter.
3954
let permCounter = 0;
@@ -69,6 +84,14 @@ function formatPermissionMessage(perm: PermissionEvent): string {
6984
return lines.join("\n");
7085
}
7186

87+
function trackChannel(ctx: Context): void {
88+
const chat = ctx.chat;
89+
if (!chat) return;
90+
if (isChannel(chat)) {
91+
channelChatId = chat.id;
92+
}
93+
}
94+
7295
export function createBot(token: string, allowedUsers: number[]): Bot {
7396
const bot = new Bot(token);
7497

@@ -81,6 +104,7 @@ export function createBot(token: string, allowedUsers: number[]): Bot {
81104
await ctx.reply("Not authorized.");
82105
return;
83106
}
107+
trackChannel(ctx);
84108
await next();
85109
});
86110

@@ -325,12 +349,15 @@ export function createBot(token: string, allowedUsers: number[]): Bot {
325349
.then(async (parts) => {
326350
const chunks = splitMessage(formatTextParts(parts));
327351
for (const chunk of chunks) {
328-
await ctx.api.sendMessage(chatId, chunk, { parse_mode: "MarkdownV2" });
352+
const chunkToSend = channelChatId !== null ? formatAsQuote(chunk) : chunk;
353+
await ctx.api.sendMessage(chatId, chunkToSend, { parse_mode: "MarkdownV2" });
329354
}
330355
})
331356
.catch(async (err) => {
332357
log.error(`[remember] error:`, err);
333-
await ctx.reply(escapeMarkdownV2(`Error: ${String(err)}`), { parse_mode: "MarkdownV2" });
358+
const errText = escapeMarkdownV2(`Error: ${String(err)}`);
359+
const errTextToSend = channelChatId !== null ? formatAsQuote(errText) : errText;
360+
await ctx.reply(errTextToSend, { parse_mode: "MarkdownV2" });
334361
});
335362
} catch (err) {
336363
log.error(`[cmd] /remember error:`, err);
@@ -373,7 +400,8 @@ export function createBot(token: string, allowedUsers: number[]): Bot {
373400

374401
const flushEdit = () => {
375402
if (latestPreview && responseMsgId !== null) {
376-
void editMessage(ctx, responseMsgId, latestPreview);
403+
const textToSend = channelChatId !== null ? formatAsQuote(latestPreview) : latestPreview;
404+
void editMessage(ctx, responseMsgId, textToSend);
377405
lastEditTime = Date.now();
378406
}
379407
};
@@ -387,7 +415,8 @@ export function createBot(token: string, allowedUsers: number[]): Bot {
387415
if (responseMsgId === null) {
388416
if (sendingFirst) return;
389417
sendingFirst = true;
390-
void ctx.api.sendMessage(chatId, latestPreview, { parse_mode: "MarkdownV2" })
418+
const textToSend = channelChatId !== null ? formatAsQuote(latestPreview) : latestPreview;
419+
void ctx.api.sendMessage(chatId, textToSend, { parse_mode: "MarkdownV2" })
391420
.then((msg) => { responseMsgId = msg.message_id; lastEditTime = Date.now(); })
392421
.catch((err) => log.error(`[prompt] failed to send first message:`, err));
393422
return;
@@ -430,7 +459,8 @@ export function createBot(token: string, allowedUsers: number[]): Bot {
430459
const chunks = splitMessage(textContent);
431460
log.info(`[prompt] done session=${sessionId} chunks=${chunks.length}`);
432461
for (const chunk of chunks) {
433-
await ctx.api.sendMessage(chatId, chunk, { parse_mode: "MarkdownV2" });
462+
const textToSend = channelChatId !== null ? formatAsQuote(chunk) : chunk;
463+
await ctx.api.sendMessage(chatId, textToSend, { parse_mode: "MarkdownV2" });
434464
}
435465
}
436466
// Save exchange to disk for qmd indexing
@@ -444,10 +474,11 @@ export function createBot(token: string, allowedUsers: number[]): Bot {
444474
cleanup();
445475
log.error(`[prompt] error session=${sessionId}:`, err);
446476
const errText = escapeMarkdownV2(`Error: ${String(err)}`);
477+
const errTextQuoted = channelChatId !== null ? formatAsQuote(errText) : errText;
447478
if (responseMsgId !== null) {
448-
await editMessage(ctx, responseMsgId, errText);
479+
await editMessage(ctx, responseMsgId, errTextQuoted);
449480
} else {
450-
await ctx.api.sendMessage(chatId, errText, { parse_mode: "MarkdownV2" });
481+
await ctx.api.sendMessage(chatId, errTextQuoted, { parse_mode: "MarkdownV2" });
451482
}
452483
});
453484
} catch (err) {

0 commit comments

Comments
 (0)