Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
31 lines
4.1 KiB
JavaScript
31 lines
4.1 KiB
JavaScript
'use strict';
|
|
// ============================================================================
|
|
// Porkbun 등록 업체 (v1.5.36, 요구 4) — 검색(등록 가능 여부·가격), 등록, 갱신, 네임서버
|
|
//
|
|
// 접속 정보: 보관소 종류 'porkbun'(DNS 와 같은 키): 키(secret) = Secret API Key, 설정 API_KEY=…. 가격은 USD. 등록자 정보는 Porkbun 계정의 기본 연락처를 쓰므로 따로 받지 않는다(needsContact=false).
|
|
// API(v3, JSON POST): /pricing/get(확장자별 가격) · /domain/checkDomain/<도메인>(등록 가능·가격) · /domain/create(등록) · /domain/renew(갱신) · /domain/listAll · /domain/updateNs.
|
|
// 등록·갱신 호출은 계정에 API 등록 권한이 켜져 있어야 하며, 응답 형식이 바뀌면 메시지를 그대로 안내한다. 시험용 대체 주소: PORKBUN_API_BASE.
|
|
// ============================================================================
|
|
const dnsp = require('../dns/porkbun');
|
|
const httpError = (m, s = 400) => Object.assign(new Error(m), { status: s });
|
|
function split(fqdn) { const d = String(fqdn || '').trim().toLowerCase().replace(/\.$/, ''); const m = d.match(/^([a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)\.((?:co|or|ne|go|ac|re|pe)\.kr|com\.[a-z]{2}|co\.[a-z]{2}|[a-z]{2,24})$/); if (!m) throw httpError('도메인 이름은 영문 소문자·숫자·하이픈으로 된 example.com 형식이어야 합니다'); return { name: m[1], extension: m[2] }; }
|
|
let priceCache = { at: 0, map: {} };
|
|
async function pricing() { if (Date.now() - priceCache.at < 3600000 && Object.keys(priceCache.map).length) return priceCache.map; const j = await dnsp.call('/pricing/get', {}); priceCache = { at: Date.now(), map: j.pricing || {} }; return priceCache.map; }
|
|
/** 등록 가능 여부와 가격 — /domain/checkDomain 이 안 되면 가격표로 대신(가능 여부는 알 수 없음 → status 'unknown') */
|
|
async function check(fqdns) {
|
|
const out = []; const prices = await pricing().catch(() => ({}));
|
|
for (const f of fqdns) {
|
|
const { extension } = split(f); const pr = prices[extension] || null;
|
|
try { const j = await dnsp.call(`/domain/checkDomain/${f}`); const avail = j.response && (j.response.avail === 'yes' || j.response.avail === true); const p = j.response && j.response.price ? Number(j.response.price) : (pr ? Number(pr.registration) : NaN); out.push({ domain: f, status: avail ? 'free' : 'active', premium: !!(j.response && j.response.premium === 'yes'), price: Number.isFinite(p) ? { currency: 'USD', amount: p } : null, reason: avail ? '' : (j.response && j.response.reason) || '' }); }
|
|
catch (e) { out.push({ domain: f, status: 'unknown', premium: false, price: pr ? { currency: 'USD', amount: Number(pr.registration) } : null, reason: `가능 여부 확인 실패: ${e.message}` }); }
|
|
}
|
|
return out;
|
|
}
|
|
async function price(fqdn, operation = 'create', period = 1) { const { extension } = split(fqdn); const pr = (await pricing())[extension]; if (!pr) throw httpError(`Porkbun 가격표에 .${extension} 이 없습니다`); const one = Number(operation === 'renew' ? pr.renewal : operation === 'transfer' ? pr.transfer : pr.registration); return { currency: 'USD', amount: Math.round(one * period * 100) / 100 }; }
|
|
async function createCustomer() { return null; }
|
|
async function createDomain(fqdn, { period = 1 } = {}) { const j = await dnsp.call('/domain/create', { domain: fqdn, years: String(period) }); const exp = new Date(); exp.setFullYear(exp.getFullYear() + period); return { id: fqdn, status: 'ACT', expiresAt: (j.expireDate || exp.toISOString().slice(0, 10)), raw: j }; }
|
|
async function renewDomain(ref, fqdn, period = 1) { await dnsp.call('/domain/renew', { domain: fqdn, years: String(period) }); return { status: 'ACT' }; }
|
|
async function getDomain(ref) { const z = (await dnsp.listZones()).find((x) => x.name === String(ref).toLowerCase()); return { expiresAt: z ? z.expires : null, status: z ? z.status : null }; }
|
|
async function setNameServers(fqdn, ref, ns) { return dnsp.setNameServers(fqdn, ns); }
|
|
module.exports = { id: 'porkbun', label: 'Porkbun', ready: dnsp.ready, split, check, price, createCustomer, createDomain, renewDomain, getDomain, setNameServers, needsContact: false };
|