import { Env } from './types'; import { getConversationContext, getLatestSummary } from './summary-service'; import { getConversationHistory, searchConversations, getConversationStats, extractKeywords } from './services/conversation-storage'; export async function handleCommand( env: Env, userId: number, chatId: string, command: string, _args: string ): Promise { switch (command) { case '/start': return `πŸ‘‹ AnvilHosting κ³ κ°μ„Όν„°μž…λ‹ˆλ‹€! 제곡 μ„œλΉ„μŠ€: β€’ 🌐 도메인 등둝/관리 β€’ πŸ–₯️ ν΄λΌμš°λ“œ μ„œλ²„ (μ„œμšΈ/도쿄/μ˜€μ‚¬μΉ΄/싱가폴) β€’ πŸ›‘οΈ DDoS λ°©μ–΄ β€’ πŸ” PhantomX VPN (Xray 기반 μ°¨μ„ΈλŒ€ λ³΄μ•ˆ) λͺ…λ Ήμ–΄: /help - 도움말 /deposit - 예치금 μž”μ•‘ /domain - λ‚΄ 도메인 λͺ©λ‘ /server - λ‚΄ μ„œλ²„ λͺ©λ‘ /security - DDoS λ°©μ–΄ ν˜„ν™© /phantomx - PhantomX VPN /history - λŒ€ν™” 기둝 /search - λŒ€ν™” 검색 무엇을 λ„μ™€λ“œλ¦΄κΉŒμš”?`; case '/help': return `πŸ“– 도움말 λͺ…λ Ήμ–΄: /deposit - 예치금 μž”μ•‘ /domain - λ‚΄ 도메인 λͺ©λ‘ /server - λ‚΄ μ„œλ²„ λͺ©λ‘ /security - DDoS λ°©μ–΄ μ„œλΉ„μŠ€ /phantomx - PhantomX VPN μ„œλΉ„μŠ€ /history [N] - λŒ€ν™” 기둝 (κΈ°λ³Έ 20개) /search ν‚€μ›Œλ“œ - λŒ€ν™” 검색 μžμ—°μ–΄λ‘œ μš”μ²­: β€’ "도메인 등둝" - 도메인 검색/등둝 κΆκΈˆν•œ 점은 νŽΈν•˜κ²Œ λ¬Όμ–΄λ³΄μ„Έμš”!`; case '/deposit': { const deposit = await env.DB .prepare('SELECT balance FROM user_deposits WHERE user_id = ?') .bind(userId) .first<{ balance: number }>(); const balance = deposit?.balance ?? 0; return `πŸ’° 예치금 μž”μ•‘ ν˜„μž¬ μž”μ•‘: ${balance.toLocaleString()}원 μž…κΈˆ κ³„μ’Œ: ν•˜λ‚˜μ€ν–‰ 427-910018-27104 예금주: (μ£Ό)μ•„μ΄μ–Έν΄λž˜λ“œ μž…κΈˆ ν›„ "홍길동 10000원 μž…κΈˆ" ν˜•μ‹μœΌλ‘œ μ•Œλ €μ£Όμ„Έμš”.`; } case '/context': { const ctx = await getConversationContext(env.DB, userId, chatId); return `πŸ“Š ν˜„μž¬ μ»¨ν…μŠ€νŠΈ 총 λ©”μ‹œμ§€: ${ctx.totalMessages}개 버퍼: ${ctx.recentMessages.length}개`; } case '/profile': case '/summary': { const summary = await getLatestSummary(env.DB, userId, chatId); if (!summary) { return 'πŸ“­ 아직 ν”„λ‘œν•„μ΄ μ—†μŠ΅λ‹ˆλ‹€.\nλŒ€ν™”λ₯Ό 더 λ‚˜λˆ λ³΄μ„Έμš”!'; } const createdAt = new Date(summary.created_at).toLocaleString('ko-KR', { timeZone: 'Asia/Seoul', }); return `πŸ‘€ λ‚΄ ν”„λ‘œν•„ (v${summary.generation}) ${summary.summary} λΆ„μ„λœ λ©”μ‹œμ§€: ${summary.message_count}개 μ—…λ°μ΄νŠΈ: ${createdAt}`; } case '/history': { const limit = _args ? parseInt(_args, 10) : 20; const validLimit = Math.min(Math.max(limit, 1), 100); // 1-100 μ œν•œ const messages = await getConversationHistory(env.DB, chatId, validLimit); if (messages.length === 0) { return 'πŸ“œ μ €μž₯된 λŒ€ν™”κ°€ μ—†μŠ΅λ‹ˆλ‹€.'; } const formatted = messages.map(m => { const time = m.created_at ? new Date(m.created_at).toLocaleString('ko-KR', { timeZone: 'Asia/Seoul', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }) : ''; const role = m.role === 'user' ? 'λ‚˜' : '봇'; const content = m.content.length > 50 ? m.content.substring(0, 50) + '...' : m.content; return `[${time}] ${role}: ${content}`; }).join('\n'); return `πŸ“œ 졜근 λŒ€ν™” (${messages.length}개)\n\n${formatted}\n\n/history 50 으둜 더 보기`; } case '/search': { if (!_args || _args.trim().length < 2) { return 'πŸ” 검색어λ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”.\n예: /search 도메인'; } const keywords = extractKeywords(_args); if (keywords.length === 0) { return 'πŸ” μœ νš¨ν•œ 검색어가 μ—†μŠ΅λ‹ˆλ‹€.'; } const results = await searchConversations(env.DB, chatId, keywords, 15); if (results.length === 0) { return `πŸ” "${_args}" 검색 κ²°κ³Όκ°€ μ—†μŠ΅λ‹ˆλ‹€.`; } const formatted = results.map(m => { const date = m.created_at ? new Date(m.created_at).toLocaleDateString('ko-KR') : ''; const content = m.content.length > 60 ? m.content.substring(0, 60) + '...' : m.content; return `[${date}] ${content}`; }).join('\n'); return `πŸ” "${_args}" 검색 κ²°κ³Ό (${results.length}건)\n\n${formatted}`; } case '/stats': { const stats = await getConversationStats(env.DB, chatId); if (!stats) { return 'πŸ“ˆ 아직 λŒ€ν™” 기둝이 μ—†μŠ΅λ‹ˆλ‹€.'; } const firstDate = stats.first_message_at ? new Date(stats.first_message_at).toLocaleDateString('ko-KR') : 'μ—†μŒ'; const lastDate = stats.last_message_at ? new Date(stats.last_message_at).toLocaleDateString('ko-KR') : 'μ—†μŒ'; return `πŸ“ˆ λŒ€ν™” 톡계 총 λ©”μ‹œμ§€: ${stats.message_count.toLocaleString()}개 첫 λŒ€ν™”: ${firstDate} 졜근 λŒ€ν™”: ${lastDate} μ•„μΉ΄μ΄λΈŒλœ μš”μ•½: ${stats.archived_summaries}개 /history - λŒ€ν™” 기둝 보기 /search ν‚€μ›Œλ“œ - λŒ€ν™” 검색`; } case '/domain': { const domains = await env.DB .prepare(` SELECT domain, created_at FROM user_domains WHERE user_id = ? AND verified = 1 ORDER BY created_at DESC `) .bind(userId) .all<{ domain: string; created_at: string }>(); if (!domains.results || domains.results.length === 0) { return `🌐 λ‚΄ 도메인 λ“±λ‘λœ 도메인이 μ—†μŠ΅λ‹ˆλ‹€. "도메인 등둝" λ˜λŠ” "example.com 등둝"으둜 μ‹œμž‘ν•˜μ„Έμš”!`; } const domainList = domains.results.map((d, i) => { const date = new Date(d.created_at).toLocaleDateString('ko-KR'); return `${i + 1}. ${d.domain} (${date})`; }).join('\n'); return `🌐 λ‚΄ 도메인 (${domains.results.length}개) ${domainList} 도메인 관리: "도메인λͺ… λ„€μž„μ„œλ²„ λ³€κ²½"`; } case '/server': { // Cloud Orchestrator APIλ₯Ό 톡해 μŠ€νŽ™ 정보 ν¬ν•¨λœ μ„œλ²„ λͺ©λ‘ 쑰회 const telegramUserId = chatId; // chatIdκ°€ μ‹€μ œλ‘œλŠ” telegram_user_id interface ServerWithSpecs { id: number; label: string | null; status: string; region: string; vcpu?: number; memory_gb?: number; bandwidth_tb?: number; spec_name?: string; } let servers: ServerWithSpecs[] = []; if (env.CLOUD_ORCHESTRATOR) { try { const response = await env.CLOUD_ORCHESTRATOR.fetch(`https://internal/api/provision/orders?user_id=${telegramUserId}`, { method: 'GET', headers: { 'Content-Type': 'application/json', }, }); if (response.ok) { const data = await response.json() as { orders?: ServerWithSpecs[] }; servers = data.orders || []; } } catch { // API μ‹€νŒ¨ μ‹œ 둜컬 DB 폴백 } } // API μ‹€νŒ¨ μ‹œ 둜컬 DBμ—μ„œ 쑰회 (μŠ€νŽ™ 정보 없이) if (servers.length === 0) { const localServers = await env.DB .prepare(` SELECT id, label, status, region FROM server_orders WHERE telegram_user_id = ? AND status IN ('active', 'stopped', 'provisioning') ORDER BY created_at DESC `) .bind(telegramUserId) .all<{ id: number; label: string; status: string; region: string }>(); servers = localServers.results || []; } if (servers.length === 0) { return `πŸ–₯️ λ‚΄ μ„œλ²„ λ³΄μœ ν•œ μ„œλ²„κ°€ μ—†μŠ΅λ‹ˆλ‹€. πŸ›’ μ‹ μ²­ κ°€λŠ₯ν•œ μ„œλ²„λ³΄κΈ°`; } const statusIcon: Record = { active: '🟒', stopped: 'πŸ”΄', provisioning: '🟑', }; const serverList = servers .filter(s => ['active', 'stopped', 'provisioning'].includes(s.status)) .map((s) => { const icon = statusIcon[s.status] || 'βšͺ'; const label = s.label || '(μ΄λ¦„μ—†μŒ)'; // μŠ€νŽ™ 정보가 있으면 ν‘œμ‹œ let specInfo = ''; if (s.vcpu && s.memory_gb) { specInfo = `\n ${s.vcpu}vCPU / ${s.memory_gb}GB RAM`; if (s.bandwidth_tb) { specInfo += ` / ${s.bandwidth_tb}TB`; } } return `#${s.id} ${icon} ${label} (${s.region})${specInfo}`; }).join('\n\n'); return `πŸ–₯️ λ‚΄ μ„œλ²„ (${servers.filter(s => ['active', 'stopped', 'provisioning'].includes(s.status)).length}개) ${serverList} μ„œλ²„ 관리: "N번 μ‹œμž‘/쀑지" λ˜λŠ” "#N μž¬μ‹œμž‘" πŸ›’ μ‹ μ²­ κ°€λŠ₯ν•œ μ„œλ²„λ³΄κΈ°`; } case '/security': { return `πŸ›‘οΈ DDoS λ°©μ–΄ μ„œλΉ„μŠ€ AnvilShield - μ—”ν„°ν”„λΌμ΄μ¦ˆκΈ‰ DDoS λ°©μ–΄ β€’ 🌐 L3/L4 λ„€νŠΈμ›Œν¬ 곡격 λ°©μ–΄ β€’ πŸ”’ L7 μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 곡격 λ°©μ–΄ β€’ πŸ“Š μ‹€μ‹œκ°„ νŠΈλž˜ν”½ λͺ¨λ‹ˆν„°λ§ β€’ πŸ€– μžλ™ μœ„ν˜‘ 탐지 및 차단 μš”κΈˆμ œ: β€’ πŸ’š Basic: 10Gbps λ°©μ–΄ - β‚©99,000/μ›” β€’ πŸ’™ Pro: 100Gbps λ°©μ–΄ - β‚©299,000/μ›” β€’ πŸ’œ Enterprise: λ¬΄μ œν•œ - 별도 문의 πŸ”œ μ„œλΉ„μŠ€ μ€€λΉ„ μ€‘μž…λ‹ˆλ‹€. 문의: @AnvilSupport`; } case '/phantomx': { return `πŸ” PhantomX VPN Xray 기반 μ°¨μ„ΈλŒ€ λ³΄μ•ˆ VPN β€’ πŸš€ μ΄ˆκ³ μ† μ—°κ²° (Xray-core μ—”μ§„) β€’ πŸ‘» νŠΈλž˜ν”½ μœ„μž₯ (탐지 우회) β€’ 🌍 κΈ€λ‘œλ²Œ μ„œλ²„ (ν•œκ΅­/일본/λ―Έκ΅­/유럽) β€’ πŸ“± λ©€ν‹° λ””λ°”μ΄μŠ€ 지원 β€’ πŸ”’ 제둜 둜그 μ •μ±… μš”κΈˆμ œ: β€’ μ›”κ°„: β‚©9,900/μ›” β€’ μ—°κ°„: β‚©79,000/λ…„ (33% 할인) πŸ”œ μ„œλΉ„μŠ€ μ€€λΉ„ μ€‘μž…λ‹ˆλ‹€. 문의: @AnvilSupport`; } case '/debug': { // Admin only - exposes internal debug info const adminId = env.DEPOSIT_ADMIN_ID ? parseInt(env.DEPOSIT_ADMIN_ID, 10) : null; if (!adminId || userId !== adminId) { return 'πŸ”’ 이 λͺ…λ Ήμ–΄λŠ” κ΄€λ¦¬μžλ§Œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.'; } // λ””λ²„κ·Έμš© λͺ…λ Ήμ–΄ (개발 μ‹œ 유용) const ctx = await getConversationContext(env.DB, userId, chatId); const recentMsgs = ctx.recentMessages.slice(-5).map((m, i) => `${i + 1}. [${m.role}] ${m.message.substring(0, 30)}...` ).join('\n'); return `πŸ”§ 디버그 정보 User ID: ${userId} Chat ID: ${chatId} Buffer Count: ${ctx.recentMessages.length} Summary Gen: ${ctx.previousSummary?.generation ?? 0} 졜근 버퍼 (5개): ${recentMsgs || '(λΉ„μ–΄μžˆμŒ)'}`; } default: return '❓ μ•Œ 수 μ—†λŠ” λͺ…λ Ήμ–΄μž…λ‹ˆλ‹€.\n/helpλ₯Ό μž…λ ₯ν•΄λ³΄μ„Έμš”.'; } }