import { Env, TelegramUpdate } from './types'; import { validateWebhookRequest, checkRateLimit } from './security'; import { sendMessage, sendMessageWithKeyboard, setWebhook, getWebhookInfo, sendChatAction } from './telegram'; import { addToBuffer, processAndSummarize, generateAIResponse, } from './summary-service'; import { handleCommand } from './commands'; // 사용자 조회/생성 async function getOrCreateUser( db: D1Database, telegramId: string, firstName: string, username?: string ): Promise { const existing = await db .prepare('SELECT id FROM users WHERE telegram_id = ?') .bind(telegramId) .first<{ id: number }>(); if (existing) { // 마지막 활동 시간 업데이트 await db .prepare('UPDATE users SET updated_at = CURRENT_TIMESTAMP WHERE id = ?') .bind(existing.id) .run(); return existing.id; } // 새 사용자 생성 const result = await db .prepare('INSERT INTO users (telegram_id, first_name, username) VALUES (?, ?, ?)') .bind(telegramId, firstName, username || null) .run(); return result.meta.last_row_id as number; } // 메시지 처리 async function handleMessage( env: Env, update: TelegramUpdate ): Promise { if (!update.message?.text) return; const { message } = update; const chatId = message.chat.id; const chatIdStr = chatId.toString(); const text = message.text; const telegramUserId = message.from.id.toString(); // Rate Limiting 체크 if (!checkRateLimit(telegramUserId)) { await sendMessage( env.BOT_TOKEN, chatId, '⚠️ 너무 많은 요청입니다. 잠시 후 다시 시도해주세요.' ); return; } // 사용자 처리 const userId = await getOrCreateUser( env.DB, telegramUserId, message.from.first_name, message.from.username ); let responseText: string; // 명령어 처리 if (text.startsWith('/')) { const [command, ...argParts] = text.split(' '); const args = argParts.join(' '); responseText = await handleCommand(env, userId, chatIdStr, command, args); // /start 명령어는 미니앱 버튼과 함께 전송 if (command === '/start') { await sendMessageWithKeyboard(env.BOT_TOKEN, chatId, responseText, [ [{ text: '🌐 서비스 보기', web_app: { url: 'https://hosting.anvil.it.com' } }], [{ text: '💬 문의하기', url: 'https://t.me/AnvilForgeBot' }], ]); return; } } else { // 타이핑 표시 await sendChatAction(env.BOT_TOKEN, chatId, 'typing'); // 1. 사용자 메시지 버퍼에 추가 await addToBuffer(env.DB, userId, chatIdStr, 'user', text); try { // 2. AI 응답 생성 responseText = await generateAIResponse(env, userId, chatIdStr, text); // 3. 봇 응답 버퍼에 추가 await addToBuffer(env.DB, userId, chatIdStr, 'bot', responseText); // 4. 임계값 도달시 프로필 업데이트 const { summarized } = await processAndSummarize(env, userId, chatIdStr); if (summarized) { responseText += '\n\n👤 프로필이 업데이트되었습니다.'; } } catch (error) { console.error('AI Response error:', error); responseText = `⚠️ AI 응답 생성 중 오류가 발생했습니다.\n\n${String(error)}`; } } await sendMessage(env.BOT_TOKEN, chatId, responseText); } export default { // HTTP 요청 핸들러 async fetch(request: Request, env: Env): Promise { const url = new URL(request.url); // Webhook 설정 엔드포인트 if (url.pathname === '/setup-webhook') { if (!env.BOT_TOKEN) { return Response.json({ error: 'BOT_TOKEN not configured' }, { status: 500 }); } if (!env.WEBHOOK_SECRET) { return Response.json({ error: 'WEBHOOK_SECRET not configured' }, { status: 500 }); } const webhookUrl = `${url.origin}/webhook`; const result = await setWebhook(env.BOT_TOKEN, webhookUrl, env.WEBHOOK_SECRET); return Response.json(result); } // Webhook 정보 조회 if (url.pathname === '/webhook-info') { if (!env.BOT_TOKEN) { return Response.json({ error: 'BOT_TOKEN not configured' }, { status: 500 }); } const result = await getWebhookInfo(env.BOT_TOKEN); return Response.json(result); } // 헬스 체크 if (url.pathname === '/health') { try { const userCount = await env.DB .prepare('SELECT COUNT(*) as cnt FROM users') .first<{ cnt: number }>(); const summaryCount = await env.DB .prepare('SELECT COUNT(*) as cnt FROM summaries') .first<{ cnt: number }>(); return Response.json({ status: 'ok', timestamp: new Date().toISOString(), stats: { users: userCount?.cnt || 0, summaries: summaryCount?.cnt || 0, }, }); } catch (error) { return Response.json({ status: 'error', error: String(error), }, { status: 500 }); } } // Telegram Webhook 처리 if (url.pathname === '/webhook') { // 보안 검증 const validation = await validateWebhookRequest(request, env); if (!validation.valid) { console.error('Webhook validation failed:', validation.error); return new Response(validation.error, { status: 401 }); } try { await handleMessage(env, validation.update!); return new Response('OK'); } catch (error) { console.error('Message handling error:', error); return new Response('Error', { status: 500 }); } } // 루트 경로 if (url.pathname === '/') { return new Response(` Telegram Rolling Summary Bot Endpoints: GET /health - Health check GET /webhook-info - Webhook status GET /setup-webhook - Configure webhook POST /webhook - Telegram webhook (authenticated) Documentation: https://github.com/your-repo `.trim(), { headers: { 'Content-Type': 'text/plain' }, }); } return new Response('Not Found', { status: 404 }); }, };