Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a99152d765 | ||
|
|
a49f403a48 |
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "telegram-codex-bot",
|
||||
"version": "0.1.12",
|
||||
"version": "0.1.14",
|
||||
"description": "Telegram bot powered by the local Codex CLI — zero-dependency, pm2-managed, with a built-in PM2 web viewer",
|
||||
"license": "MIT",
|
||||
"type": "commonjs",
|
||||
|
||||
+50
-4
@@ -210,13 +210,45 @@ function startBot(config) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildPrompt(message, content) {
|
||||
// 使用者引用/回覆的文字(圈選引用優先,其次是被回覆的整則訊息)
|
||||
function quotedTextOf(message) {
|
||||
return message.quote?.text || message.reply_to_message?.text || message.reply_to_message?.caption || '';
|
||||
}
|
||||
|
||||
// Telegram 限制:bot 永遠看不到其他 bot 的訊息——使用者「回覆另一隻 bot + tag 我」時,
|
||||
// reply_to_message 會被整個剝掉。繞法:每隻 bot 把自己在各聊天室的最後一則回覆
|
||||
// 寫進共用資料夾,收到「只有 tag 沒內容」時就拿同聊天室最近一則兄弟 bot 的發言當輸入。
|
||||
const CHATLAST_DIR = path.join(os.homedir(), '.tgcodex', 'chatlast');
|
||||
const SIBLING_MAX_AGE_MS = 6 * 60 * 60 * 1000; // 超過 6 小時的舊訊息就不當引用
|
||||
|
||||
function recordChatLast(chatId, text) {
|
||||
try {
|
||||
fs.mkdirSync(CHATLAST_DIR, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(CHATLAST_DIR, `${chatId}.json`),
|
||||
JSON.stringify({ botUsername, text, ts: Date.now() }) + '\n'
|
||||
);
|
||||
} catch { /* 純輔助功能,失敗不影響回覆 */ }
|
||||
}
|
||||
|
||||
function siblingLastOf(chatId) {
|
||||
try {
|
||||
const j = JSON.parse(fs.readFileSync(path.join(CHATLAST_DIR, `${chatId}.json`), 'utf-8'));
|
||||
if (!j.text || Date.now() - j.ts > SIBLING_MAX_AGE_MS) return null;
|
||||
return j;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function buildPrompt(message, content, { includeOwnReply = false } = {}) {
|
||||
const from = message.from || {};
|
||||
const name = [from.first_name, from.last_name].filter(Boolean).join(' ') || '未知使用者';
|
||||
const sender = from.username ? `${name}(@${from.username})` : name;
|
||||
const parts = [OUTBOX_RULE];
|
||||
const replied = message.reply_to_message;
|
||||
if (replied && replied.from?.id !== botId && (replied.text || replied.caption)) {
|
||||
// 回覆我自己的訊息通常已在會話記憶裡,不重複貼;但使用者沒打字只回覆時例外(那就是輸入本身)
|
||||
if (replied && (includeOwnReply || replied.from?.id !== botId) && (replied.text || replied.caption)) {
|
||||
parts.push(`【被回覆的訊息】\n${replied.text || replied.caption}`);
|
||||
}
|
||||
if (message.quote?.text) {
|
||||
@@ -291,7 +323,12 @@ function startBot(config) {
|
||||
await t.sendMessage(chatId, statusText(chatId), { replyTo: message.message_id });
|
||||
return;
|
||||
}
|
||||
if (!content && !photo) {
|
||||
// 只有「引用/回覆 + tag 我」沒打字:直接把引用內容當作這次的輸入。
|
||||
// 引用的是其他 bot 的訊息時 Telegram 不給內容,改拿共用記錄裡同聊天室最近一則 bot 發言。
|
||||
const quoted = quotedTextOf(message);
|
||||
const sibling = (!content && !photo && !quoted) ? siblingLastOf(chatId) : null;
|
||||
const quotedOnly = !content && !photo && !!(quoted || sibling);
|
||||
if (!content && !photo && !quotedOnly) {
|
||||
await t.sendMessage(chatId, HELP_TEXT, { replyTo: message.message_id });
|
||||
return;
|
||||
}
|
||||
@@ -324,7 +361,15 @@ function startBot(config) {
|
||||
images.push(dest);
|
||||
}
|
||||
|
||||
const prompt = buildPrompt(message, content || '(使用者只傳了圖片,請描述並依上下文處理)');
|
||||
let fallback = '(使用者只傳了圖片,請描述並依上下文處理)';
|
||||
if (quotedOnly && (quoted || !sibling)) {
|
||||
fallback = '(使用者沒有輸入文字,只引用了上面的內容並 tag 我:請直接把引用內容當作這次的輸入來處理)';
|
||||
} else if (quotedOnly && sibling) {
|
||||
fallback =
|
||||
`【引用的 bot 訊息(@${sibling.botUsername} 在本聊天室的最近發言)】\n${sibling.text}\n\n` +
|
||||
'(使用者沒有輸入文字,只引用了訊息並 tag 我:請直接把上面引用的內容當作這次的輸入來處理)';
|
||||
}
|
||||
const prompt = buildPrompt(message, content || fallback, { includeOwnReply: quotedOnly });
|
||||
const existing = sessionKey ? sessions.get(sessionKey) : null;
|
||||
const turnStart = Date.now();
|
||||
|
||||
@@ -389,6 +434,7 @@ function startBot(config) {
|
||||
const footer = buildFooter(config, result.usage, modelMeta, codexDefaults);
|
||||
const html = t.mdToTgHtml(sessionResetNote + (result.text || '(Codex 沒有回覆文字)') + ctxNote + footer);
|
||||
await t.editOrSplit(chatId, statusMsg.message_id, html, { html: true });
|
||||
recordChatLast(chatId, result.text || '');
|
||||
await flushOutputs(chatId, message.message_id, result.threadId, turnStart);
|
||||
await t.react(chatId, message.message_id, '👍');
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user