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:
@@ -265,77 +265,27 @@ async function callNamecheapApi(funcName: string, funcArgs: Record<string, any>,
|
||||
}).then(r => r.json());
|
||||
}
|
||||
case 'whois_lookup': {
|
||||
// 공개 RDAP API 사용 (누구나 조회 가능)
|
||||
// 자체 WHOIS API 서버 사용 (모든 TLD 지원)
|
||||
const domain = funcArgs.domain;
|
||||
try {
|
||||
// TLD별 RDAP 서버 (먼저 직접 시도)
|
||||
const tld = domain.split('.').pop()?.toLowerCase();
|
||||
const rdapServers: Record<string, string> = {
|
||||
'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',
|
||||
const whoisRes = await fetch(`https://whois-api-kappa-inoutercoms-projects.vercel.app/api/whois/${domain}`);
|
||||
if (!whoisRes.ok) {
|
||||
return { error: `WHOIS 조회 실패: HTTP ${whoisRes.status}` };
|
||||
}
|
||||
const whois = await whoisRes.json() as any;
|
||||
|
||||
if (whois.error) {
|
||||
return { error: `WHOIS 조회 오류: ${whois.error}` };
|
||||
}
|
||||
|
||||
// raw WHOIS 응답을 그대로 반환 (AI가 파싱)
|
||||
return {
|
||||
domain: whois.domain,
|
||||
available: whois.available,
|
||||
whois_server: whois.whois_server,
|
||||
raw: whois.raw,
|
||||
query_time_ms: whois.query_time_ms,
|
||||
};
|
||||
|
||||
let rdapRes: Response | null = null;
|
||||
|
||||
// 1. TLD별 직접 서버 시도
|
||||
if (tld && rdapServers[tld]) {
|
||||
try {
|
||||
rdapRes = await fetch(`${rdapServers[tld]}/domain/${domain}`);
|
||||
} catch {
|
||||
rdapRes = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 실패하면 rdap.org 프록시 시도 (리다이렉트 따라가기)
|
||||
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 {
|
||||
error: `WHOIS 조회 실패: .${tld} TLD는 RDAP를 지원하지 않습니다.`,
|
||||
suggestion: '지원 TLD: com, net, org, io, me, info, biz'
|
||||
};
|
||||
}
|
||||
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) {
|
||||
return { error: `WHOIS 조회 오류: ${String(error)}` };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user