Files
telegram-ai-support/src/telegram.ts
kappa 1d6b64c9e4 Initial implementation of Telegram AI customer support bot
Cloudflare Workers + Hono + D1 + KV + R2 stack with 4 specialized AI agents
(onboarding, troubleshoot, asset, billing), OpenAI function calling with
7 tool definitions, human escalation, pending action approval workflow,
feedback collection, audit logging, i18n (ko/en), and Workers AI fallback.

43 source files, 45 tests passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 13:21:38 +09:00

236 lines
6.0 KiB
TypeScript

// ============================================
// Telegram Bot API Helpers
// ============================================
export class TelegramError extends Error {
constructor(
message: string,
public readonly code?: number,
public readonly description?: string
) {
super(message);
this.name = 'TelegramError';
}
}
export interface InlineKeyboardButton {
text: string;
url?: string;
callback_data?: string;
web_app?: { url: string };
}
async function callTelegramAPI(
token: string,
method: string,
body: Record<string, unknown>
): Promise<Response> {
const response = await fetch(
`https://api.telegram.org/bot${token}/${method}`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
}
);
if (!response.ok) {
let description = '';
try {
const errorData = await response.json() as { description?: string };
description = errorData.description || '';
} catch {
// JSON parse failure ignored
}
throw new TelegramError(
`Telegram API ${method} failed: ${response.status}`,
response.status,
description
);
}
return response;
}
function wrapTelegramCall(method: string, fn: () => Promise<Response>): Promise<void> {
return fn().then(() => undefined).catch((error: unknown) => {
if (error instanceof TelegramError) throw error;
throw new TelegramError(
`Network error in ${method}`,
undefined,
error instanceof Error ? error.message : String(error)
);
});
}
export async function sendMessage(
token: string,
chatId: number,
text: string,
options?: {
parse_mode?: 'HTML' | 'Markdown' | 'MarkdownV2';
reply_to_message_id?: number;
disable_notification?: boolean;
}
): Promise<void> {
return wrapTelegramCall('sendMessage', () =>
callTelegramAPI(token, 'sendMessage', {
chat_id: chatId,
text,
parse_mode: options?.parse_mode || 'HTML',
reply_to_message_id: options?.reply_to_message_id,
disable_notification: options?.disable_notification,
})
);
}
export async function sendMessageWithKeyboard(
token: string,
chatId: number,
text: string,
keyboard: InlineKeyboardButton[][],
options?: {
parse_mode?: 'HTML' | 'Markdown' | 'MarkdownV2';
}
): Promise<void> {
return wrapTelegramCall('sendMessageWithKeyboard', () =>
callTelegramAPI(token, 'sendMessage', {
chat_id: chatId,
text,
parse_mode: options?.parse_mode || 'HTML',
reply_markup: { inline_keyboard: keyboard },
})
);
}
export async function sendChatAction(
token: string,
chatId: number,
action: 'typing' | 'upload_photo' | 'upload_document' = 'typing'
): Promise<void> {
return wrapTelegramCall('sendChatAction', () =>
callTelegramAPI(token, 'sendChatAction', {
chat_id: chatId,
action,
})
);
}
export async function answerCallbackQuery(
token: string,
callbackQueryId: string,
options?: {
text?: string;
show_alert?: boolean;
}
): Promise<void> {
return wrapTelegramCall('answerCallbackQuery', () =>
callTelegramAPI(token, 'answerCallbackQuery', {
callback_query_id: callbackQueryId,
text: options?.text,
show_alert: options?.show_alert,
})
);
}
export async function editMessageText(
token: string,
chatId: number,
messageId: number,
text: string,
options?: {
parse_mode?: 'HTML' | 'Markdown' | 'MarkdownV2';
reply_markup?: { inline_keyboard: InlineKeyboardButton[][] };
}
): Promise<void> {
return wrapTelegramCall('editMessageText', () =>
callTelegramAPI(token, 'editMessageText', {
chat_id: chatId,
message_id: messageId,
text,
parse_mode: options?.parse_mode || 'HTML',
reply_markup: options?.reply_markup,
})
);
}
export async function sendPhoto(
token: string,
chatId: number,
photo: ArrayBuffer,
options?: {
caption?: string;
parse_mode?: 'HTML' | 'Markdown' | 'MarkdownV2';
reply_to_message_id?: number;
}
): Promise<void> {
const formData = new FormData();
formData.append('chat_id', String(chatId));
formData.append('photo', new Blob([photo], { type: 'image/png' }), 'diagram.png');
if (options?.caption) {
formData.append('caption', options.caption);
formData.append('parse_mode', options.parse_mode || 'HTML');
}
if (options?.reply_to_message_id) {
formData.append('reply_to_message_id', String(options.reply_to_message_id));
}
try {
const response = await fetch(
`https://api.telegram.org/bot${token}/sendPhoto`,
{ method: 'POST', body: formData }
);
if (!response.ok) {
let description = '';
try {
const errorData = await response.json() as { description?: string };
description = errorData.description || '';
} catch {
// JSON parse failure ignored
}
throw new TelegramError(
`Telegram API sendPhoto failed: ${response.status}`,
response.status,
description
);
}
} catch (error) {
if (error instanceof TelegramError) throw error;
throw new TelegramError(
'Network error in sendPhoto',
undefined,
error instanceof Error ? error.message : String(error)
);
}
}
export async function setWebhook(
token: string,
webhookUrl: string,
secretToken: string
): Promise<{ ok: boolean; description?: string }> {
const response = await fetch(
`https://api.telegram.org/bot${token}/setWebhook`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
url: webhookUrl,
secret_token: secretToken,
allowed_updates: ['message', 'callback_query'],
drop_pending_updates: true,
}),
}
);
return response.json() as Promise<{ ok: boolean; description?: string }>;
}
export async function getWebhookInfo(token: string): Promise<unknown> {
const response = await fetch(
`https://api.telegram.org/bot${token}/getWebhookInfo`
);
return response.json();
}