Files
telegram-bot-workers/src/commands.ts
kappa ec2d4da517 feat: add server page link to /server command
- Show "신청 가능한 서버보기" link when no servers
- Show link at bottom of server list

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 18:59:21 +09:00

371 lines
11 KiB
TypeScript

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<string> {
switch (command) {
case '/start':
return `👋 <b>AnvilHosting 고객센터</b>입니다!
<b>제공 서비스:</b>
• 🌐 도메인 등록/관리
• 🖥️ 클라우드 서버 (서울/도쿄/오사카/싱가폴)
• 🛡️ DDoS 방어
• 🔐 PhantomX VPN (Xray 기반 차세대 보안)
<b>명령어:</b>
/help - 도움말
/deposit - 예치금 잔액
/domain - 내 도메인 목록
/server - 내 서버 목록
/security - DDoS 방어 현황
/phantomx - PhantomX VPN
/history - 대화 기록
/search - 대화 검색
무엇을 도와드릴까요?`;
case '/help':
return `📖 <b>도움말</b>
<b>명령어:</b>
/deposit - 예치금 잔액
/domain - 내 도메인 목록
/server - 내 서버 목록
/security - DDoS 방어 서비스
/phantomx - PhantomX VPN 서비스
/history [N] - 대화 기록 (기본 20개)
/search 키워드 - 대화 검색
<b>자연어로 요청:</b>
• "도메인 등록" - 도메인 검색/등록
궁금한 점은 편하게 물어보세요!`;
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 `💰 <b>예치금 잔액</b>
현재 잔액: <b>${balance.toLocaleString()}원</b>
<b>입금 계좌:</b>
하나은행 427-910018-27104
예금주: (주)아이언클래드
입금 후 "홍길동 10000원 입금" 형식으로 알려주세요.`;
}
case '/context': {
const ctx = await getConversationContext(env.DB, userId, chatId);
return `📊 <b>현재 컨텍스트</b>
총 메시지: ${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 `👤 <b>내 프로필</b> (v${summary.generation})
${summary.summary}
<i>분석된 메시지: ${summary.message_count}개</i>
<i>업데이트: ${createdAt}</i>`;
}
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 `📜 <b>최근 대화</b> (${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 `🔍 <b>"${_args}"</b> 검색 결과 (${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 `📈 <b>대화 통계</b>
총 메시지: ${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 `🌐 <b>내 도메인</b>
등록된 도메인이 없습니다.
"도메인 등록" 또는 "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 `🌐 <b>내 도메인</b> (${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 `🖥️ <b>내 서버</b>
보유한 서버가 없습니다.
<a href="https://hosting.anvil.it.com/servers">🛒 신청 가능한 서버보기</a>`;
}
const statusIcon: Record<string, string> = {
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} <b>${label}</b> (${s.region})${specInfo}`;
}).join('\n\n');
return `🖥️ <b>내 서버</b> (${servers.filter(s => ['active', 'stopped', 'provisioning'].includes(s.status)).length}개)
${serverList}
서버 관리: "N번 시작/중지" 또는 "#N 재시작"
<a href="https://hosting.anvil.it.com/servers">🛒 신청 가능한 서버보기</a>`;
}
case '/security': {
return `🛡️ <b>DDoS 방어 서비스</b>
<b>AnvilShield</b> - 엔터프라이즈급 DDoS 방어
• 🌐 L3/L4 네트워크 공격 방어
• 🔒 L7 애플리케이션 공격 방어
• 📊 실시간 트래픽 모니터링
• 🤖 자동 위협 탐지 및 차단
<b>요금제:</b>
• 💚 Basic: 10Gbps 방어 - ₩99,000/월
• 💙 Pro: 100Gbps 방어 - ₩299,000/월
• 💜 Enterprise: 무제한 - 별도 문의
🔜 <i>서비스 준비 중입니다. 문의: @AnvilSupport</i>`;
}
case '/phantomx': {
return `🔐 <b>PhantomX VPN</b>
<b>Xray 기반 차세대 보안 VPN</b>
• 🚀 초고속 연결 (Xray-core 엔진)
• 👻 트래픽 위장 (탐지 우회)
• 🌍 글로벌 서버 (한국/일본/미국/유럽)
• 📱 멀티 디바이스 지원
• 🔒 제로 로그 정책
<b>요금제:</b>
• 월간: ₩9,900/월
• 연간: ₩79,000/년 (33% 할인)
🔜 <i>서비스 준비 중입니다. 문의: @AnvilSupport</i>`;
}
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 `🔧 <b>디버그 정보</b>
User ID: ${userId}
Chat ID: ${chatId}
Buffer Count: ${ctx.recentMessages.length}
Summary Gen: ${ctx.previousSummary?.generation ?? 0}
<b>최근 버퍼 (5개):</b>
${recentMsgs || '(비어있음)'}`;
}
default:
return '❓ 알 수 없는 명령어입니다.\n/help를 입력해보세요.';
}
}