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>
236 lines
6.0 KiB
TypeScript
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();
|
|
}
|