import { sendMessage, sendMessageWithKeyboard } from '../../telegram'; import { checkRateLimit } from '../../security'; import { handleCommand } from '../../commands'; import { UserService } from '../../services/user-service'; import { ConversationService } from '../../services/conversation-service'; import { ERROR_MESSAGES } from '../../constants/messages'; import type { Env, TelegramUpdate } from '../../types'; /** * 메시지 처리 핸들러 */ export 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(); // 1. Rate Limiting 체크 if (!(await checkRateLimit(env.RATE_LIMIT_KV, telegramUserId))) { await sendMessage( env.BOT_TOKEN, chatId, '⚠️ 너무 많은 요청입니다. 잠시 후 다시 시도해주세요.' ); return; } // 2. 서비스 인스턴스 초기화 const userService = new UserService(env.DB); const conversationService = new ConversationService(env); // 3. 사용자 조회/생성 let userId: number; try { userId = await userService.getOrCreateUser( telegramUserId, message.from.first_name, message.from.username ); } catch (dbError) { console.error('[handleMessage] 사용자 DB 오류:', dbError); await sendMessage( env.BOT_TOKEN, chatId, ERROR_MESSAGES.TEMPORARY_ERROR ); return; } try { // 4. 명령어 처리 if (text.startsWith('/')) { const [command, ...argParts] = text.split(' '); const args = argParts.join(' '); const responseText = await handleCommand(env, userId, chatIdStr, command, args); // /start 명령어는 미니앱 버튼과 함께 전송 if (command === '/start') { const hostingUrl = env.HOSTING_SITE_URL || 'https://hosting.anvil.it.com'; await sendMessageWithKeyboard(env.BOT_TOKEN, chatId, responseText, [ [{ text: '🌐 서비스 보기', web_app: { url: hostingUrl } }], [{ text: '💬 문의하기', url: 'https://t.me/AnvilForgeBot' }], ]); return; } await sendMessage(env.BOT_TOKEN, chatId, responseText); return; } // 5. 일반 대화 처리 (ConversationService 위임) const result = await conversationService.processUserMessage( userId, chatIdStr, text, telegramUserId ); let finalResponse = result.responseText; if (result.isProfileUpdated) { finalResponse += '\n\n👤 프로필이 업데이트되었습니다.'; } // 6. 응답 전송 (키보드 포함 여부 확인) if (result.keyboardData) { console.log('[Webhook] Keyboard data received:', result.keyboardData.type); if (result.keyboardData.type === 'domain_register') { const { domain, price } = result.keyboardData; const callbackData = `domain_reg:${domain}:${price}`; await sendMessageWithKeyboard(env.BOT_TOKEN, chatId, finalResponse, [ [ { text: '✅ 등록하기', callback_data: callbackData }, { text: '❌ 취소', callback_data: 'domain_cancel' } ] ]); } else { // TypeScript exhaustiveness check - should never reach here console.warn('[Webhook] Unknown keyboard type:', (result.keyboardData as { type: string }).type); await sendMessage(env.BOT_TOKEN, chatId, finalResponse); } } else { await sendMessage(env.BOT_TOKEN, chatId, finalResponse); } } catch (error) { console.error('[handleMessage] 처리 오류:', error); await sendMessage( env.BOT_TOKEN, chatId, '⚠️ 메시지 처리 중 오류가 발생했습니다. 잠시 후 다시 시도해주세요.' ); } }