feat: WHOIS 조회를 자체 Vercel API로 변경

- Vercel Serverless로 WHOIS API 서버 구축
- 모든 TLD 지원 (TCP 포트 43 직접 연결)
- RDAP 대신 raw WHOIS 사용으로 안정성 향상
- AI가 raw 응답 파싱

WHOIS API: whois-api-kappa-inoutercoms-projects.vercel.app

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kappa
2026-01-16 13:26:59 +09:00
parent 42c57747a7
commit 6071412949

View File

@@ -265,77 +265,27 @@ async function callNamecheapApi(funcName: string, funcArgs: Record<string, any>,
}).then(r => r.json()); }).then(r => r.json());
} }
case 'whois_lookup': { case 'whois_lookup': {
// 공개 RDAP API 사용 (누구나 조회 가능) // 자체 WHOIS API 서버 사용 (모든 TLD 지원)
const domain = funcArgs.domain; const domain = funcArgs.domain;
try { try {
// TLD별 RDAP 서버 (먼저 직접 시도) const whoisRes = await fetch(`https://whois-api-kappa-inoutercoms-projects.vercel.app/api/whois/${domain}`);
const tld = domain.split('.').pop()?.toLowerCase(); if (!whoisRes.ok) {
const rdapServers: Record<string, string> = { return { error: `WHOIS 조회 실패: HTTP ${whoisRes.status}` };
'com': 'https://rdap.verisign.com/com/v1',
'net': 'https://rdap.verisign.com/net/v1',
'org': 'https://rdap.publicinterestregistry.org/rdap',
'io': 'https://rdap.nic.io',
'me': 'https://rdap.nic.me',
'info': 'https://rdap.afilias.net/rdap/info',
'biz': 'https://rdap.nic.biz',
};
let rdapRes: Response | null = null;
// 1. TLD별 직접 서버 시도
if (tld && rdapServers[tld]) {
try {
rdapRes = await fetch(`${rdapServers[tld]}/domain/${domain}`);
} catch {
rdapRes = null;
} }
const whois = await whoisRes.json() as any;
if (whois.error) {
return { error: `WHOIS 조회 오류: ${whois.error}` };
} }
// 2. 실패하면 rdap.org 프록시 시도 (리다이렉트 따라가기) // raw WHOIS 응답을 그대로 반환 (AI가 파싱)
if (!rdapRes || !rdapRes.ok) {
const proxyRes = await fetch(`https://rdap.org/domain/${domain}`, { redirect: 'manual' });
if (proxyRes.status === 302 || proxyRes.status === 301) {
const location = proxyRes.headers.get('location');
if (location) {
rdapRes = await fetch(location);
}
} else if (proxyRes.ok) {
rdapRes = proxyRes;
}
}
if (!rdapRes || !rdapRes.ok) {
return { return {
error: `WHOIS 조회 실패: .${tld} TLD는 RDAP를 지원하지 않습니다.`, domain: whois.domain,
suggestion: '지원 TLD: com, net, org, io, me, info, biz' available: whois.available,
whois_server: whois.whois_server,
raw: whois.raw,
query_time_ms: whois.query_time_ms,
}; };
}
const rdap = await rdapRes.json() as any;
// RDAP 응답 파싱
const result: Record<string, any> = {
domain: rdap.ldhName || domain,
status: rdap.status || [],
};
// 등록일/만료일
for (const event of rdap.events || []) {
if (event.eventAction === 'registration') result.created = event.eventDate;
if (event.eventAction === 'expiration') result.expires = event.eventDate;
if (event.eventAction === 'last changed') result.updated = event.eventDate;
}
// 네임서버
result.nameservers = (rdap.nameservers || []).map((ns: any) => ns.ldhName).filter(Boolean);
// 등록기관
for (const entity of rdap.entities || []) {
if (entity.roles?.includes('registrar')) {
result.registrar = entity.vcardArray?.[1]?.find((v: any) => v[0] === 'fn')?.[3] || entity.handle;
}
}
return result;
} catch (error) { } catch (error) {
return { error: `WHOIS 조회 오류: ${String(error)}` }; return { error: `WHOIS 조회 오류: ${String(error)}` };
} }