feat: complete Telegram-compatible Bot API + bot dev guide
Bot API (routes/bot.rs — full rewrite): - getUpdates: persistent update_id counter, offset acknowledgement, limit (max 100), long-poll up to 30s with 1s intervals - sendMessage: parse_mode, reply_to_message_id, reply_markup (inline keyboards) - answerCallbackQuery: acknowledge button clicks - editMessageText: update sent messages - setWebhook / deleteWebhook / getWebhookInfo: webhook configuration - sendDocument: file reference with caption - Bot queue: raw messages migrated to bot_queue:<fp>:<update_id> for ordering Web client (routes/web.rs): - Bot messages rendered properly (was showing "[message could not be decrypted]") - Handles bot_message, bot_edit, bot_document as both Text and Binary WS frames - Inline keyboard buttons rendered as bracketed text - Missed call notifications handled in Text frame path Docs: - LLM_BOT_DEV.md: token-optimized bot dev reference for coding assistant LLM (Python + Node.js examples, all endpoints, TG compatibility table) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -527,6 +527,45 @@ function connectWebSocket() {
|
||||
};
|
||||
|
||||
ws.onmessage = async (event) => {
|
||||
if (typeof event.data === 'string') {
|
||||
// Text frame — could be a bot message or missed call notification
|
||||
try {
|
||||
const json = JSON.parse(event.data);
|
||||
if (json.type === 'missed_call') {
|
||||
addSys('Missed call from ' + (json.data?.caller_fp || 'unknown'));
|
||||
return;
|
||||
}
|
||||
if (json.type === 'bot_message') {
|
||||
const botName = json.from_name || json.from || 'bot';
|
||||
let msgText = json.text || '';
|
||||
if (json.reply_markup && json.reply_markup.inline_keyboard) {
|
||||
msgText += '\\n';
|
||||
for (const row of json.reply_markup.inline_keyboard) {
|
||||
for (const btn of row) {
|
||||
msgText += ' [' + btn.text + '] ';
|
||||
}
|
||||
msgText += '\\n';
|
||||
}
|
||||
}
|
||||
addMsg('@' + botName, msgText, false);
|
||||
lastDmPeer = json.from ? normFP(json.from) : '';
|
||||
return;
|
||||
}
|
||||
if (json.type === 'bot_edit') {
|
||||
addSys('[bot updated: ' + (json.text || '') + ']');
|
||||
return;
|
||||
}
|
||||
if (json.type === 'bot_document') {
|
||||
addMsg('@' + (json.from || 'bot'), '[Document: ' + json.document + ']', false);
|
||||
return;
|
||||
}
|
||||
} catch(e) {}
|
||||
// If not JSON or unrecognized, try treating as binary
|
||||
const bytes = new TextEncoder().encode(event.data);
|
||||
dbg('WS text frame treated as bytes,', bytes.length, 'bytes');
|
||||
await handleIncomingMessage(bytes);
|
||||
return;
|
||||
}
|
||||
const bytes = new Uint8Array(event.data);
|
||||
dbg('WS received', bytes.length, 'bytes');
|
||||
await handleIncomingMessage(bytes);
|
||||
@@ -628,12 +667,39 @@ async function handleIncomingMessage(bytes) {
|
||||
}
|
||||
}
|
||||
|
||||
// Last try: raw JSON file messages (from web file upload)
|
||||
// Last try: raw JSON file messages (from web file upload) or bot messages
|
||||
try {
|
||||
const str = new TextDecoder().decode(bytes);
|
||||
const json = JSON.parse(str);
|
||||
if (json.type === 'file_header') { handleFileHeader(json); return; }
|
||||
if (json.type === 'file_chunk') { handleFileChunk(json); return; }
|
||||
// Handle bot messages (plaintext JSON from bot API)
|
||||
if (json.type === 'bot_message') {
|
||||
const botName = json.from_name || json.from || 'bot';
|
||||
let msgText = json.text || '';
|
||||
// Handle inline keyboard if present
|
||||
if (json.reply_markup && json.reply_markup.inline_keyboard) {
|
||||
msgText += '\\n';
|
||||
for (const row of json.reply_markup.inline_keyboard) {
|
||||
for (const btn of row) {
|
||||
msgText += ' [' + btn.text + '] ';
|
||||
}
|
||||
msgText += '\\n';
|
||||
}
|
||||
}
|
||||
addMsg('@' + botName, msgText, false);
|
||||
lastDmPeer = json.from ? normFP(json.from) : '';
|
||||
return;
|
||||
}
|
||||
if (json.type === 'bot_edit') {
|
||||
addSys('[bot updated message: ' + (json.text || '') + ']');
|
||||
return;
|
||||
}
|
||||
if (json.type === 'bot_document') {
|
||||
const caption = json.caption ? ' \u2014 ' + json.caption : '';
|
||||
addMsg('@' + (json.from || 'bot'), '[Document: ' + json.document + caption + ']', false);
|
||||
return;
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
dbg('ALL decrypt attempts failed');
|
||||
|
||||
Reference in New Issue
Block a user