← back to Domain Sniper
register.js
181 lines
#!/usr/bin/env node
// register.js — atomic domain registration via direct registrar API.
//
// SAFETY: defaults to DRY-RUN. Pass --confirm to actually spend money.
// In YOLO autonomous mode, NEVER pass --confirm — registrations are not reversible.
//
// Backend auto-detection (cheapest/simplest first):
// - NAMESILO_API_KEY → NameSilo (~$9 .com, no IP whitelist)
// - NAMECHEAP_API_USER + NAMECHEAP_API_KEY → Namecheap (needs IP whitelist + $50 prefund)
// - GODADDY_KEY + GODADDY_SECRET → GoDaddy (needs payment on file)
//
// Skip-check pattern: this script DOES NOT call any availability API before
// registering. The registrar tells us success-or-taken in one round-trip. The
// "check" step IS the leak we're trying to avoid.
//
// Usage:
// node register.js <domain> # dry-run
// node register.js <domain> --confirm # actually buy
const https = require('https');
const fs = require('fs');
const path = require('path');
const { isOwned } = require('./owned-check');
const DATA_DIR = path.join(__dirname, 'data');
const LOG_FILE = path.join(DATA_DIR, 'registrations.jsonl');
function detectBackend() {
if (process.env.NAMESILO_API_KEY) return 'namesilo';
if (process.env.NAMECHEAP_API_USER && process.env.NAMECHEAP_API_KEY) return 'namecheap';
if (process.env.GODADDY_KEY && process.env.GODADDY_SECRET) return 'godaddy';
return null;
}
function httpsRequest(opts, body) {
return new Promise((resolve, reject) => {
const req = https.request(opts, (res) => {
let d = '';
res.on('data', (c) => (d += c));
res.on('end', () => resolve({ status: res.statusCode, body: d }));
});
req.on('error', reject);
if (body) req.write(body);
req.end();
});
}
async function registerNamesilo(domain) {
const key = process.env.NAMESILO_API_KEY;
const url = `/api/registerDomain?version=1&type=xml&key=${encodeURIComponent(key)}&domain=${encodeURIComponent(domain)}&years=1&private=1&auto_renew=0`;
const r = await httpsRequest({ hostname: 'www.namesilo.com', path: url, method: 'GET' });
const code = (r.body.match(/<code>(\d+)<\/code>/) || [])[1];
const detail = (r.body.match(/<detail>([^<]+)<\/detail>/) || [])[1];
// NameSilo 300 = success
return { success: code === '300', code, detail, http: r.status };
}
async function registerNamecheap(domain) {
const user = process.env.NAMECHEAP_API_USER;
const key = process.env.NAMECHEAP_API_KEY;
const clientIp = process.env.NAMECHEAP_CLIENT_IP || '127.0.0.1';
const username = process.env.NAMECHEAP_USERNAME || user;
const contact = process.env.NAMECHEAP_CONTACT_JSON
? JSON.parse(process.env.NAMECHEAP_CONTACT_JSON)
: null;
if (!contact) {
return {
success: false,
code: 'NO_CONTACT',
detail: 'NAMECHEAP_CONTACT_JSON env required (firstName/lastName/address1/city/stateProvince/postalCode/country/phone/emailAddress). Namecheap registration needs full registrant contact.',
};
}
const params = new URLSearchParams({
ApiUser: user, ApiKey: key, UserName: username, ClientIp: clientIp,
Command: 'namecheap.domains.create', DomainName: domain, Years: '1',
});
for (const role of ['Registrant', 'Tech', 'Admin', 'AuxBilling']) {
for (const [k, v] of Object.entries(contact)) {
params.set(role + k.charAt(0).toUpperCase() + k.slice(1), v);
}
}
const r = await httpsRequest(
{ hostname: 'api.namecheap.com', path: '/xml.response?' + params.toString(), method: 'GET' },
null,
);
// Two-layer success check: the API call must have Status="OK" AND the
// inner <DomainCreateResult Registered="true"...> must confirm the actual
// registration. Status="OK" alone only means "API request was valid" — it
// does NOT mean the domain was registered (the create result can still
// come back Registered="false" with the call still nominally OK).
const apiOk = /Status="OK"/.test(r.body);
const regOk = /<DomainCreateResult\b[^>]*\bRegistered="true"/i.test(r.body);
const success = apiOk && regOk;
const errMatch = r.body.match(/<Error[^>]*>([^<]+)<\/Error>/);
let detail;
if (success) detail = 'registered';
else if (!apiOk && errMatch) detail = errMatch[1];
else if (apiOk && !regOk) detail = 'api ok but DomainCreateResult.Registered != true';
else detail = 'unknown';
return { success, code: success ? 'OK' : 'ERROR', detail, http: r.status };
}
async function registerGodaddy(domain) {
// GoDaddy /v1/domains/purchase needs full contact JSON + signed consent.
// Implementing this safely requires Steve's stored contact + payment method.
// For now, refuse to call so we don't silently fail-open and bill him.
return {
success: false,
code: 'NOT_WIRED',
detail: 'GoDaddy direct-purchase needs full contact + consent payload + saved payment method. Wire via secrets-manager + GODADDY_PURCHASE_PAYLOAD env before use.',
};
}
async function main() {
const args = process.argv.slice(2);
const domain = args.find((a) => !a.startsWith('--'));
const confirm = args.includes('--confirm');
if (!domain) {
console.error('usage: node register.js <domain> [--confirm]');
console.error(' default = DRY-RUN. Pass --confirm to actually buy.');
process.exit(2);
}
if (!/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z]{2,})+$/i.test(domain)) {
console.error(`invalid domain syntax: ${domain}`);
process.exit(2);
}
if (isOwned(domain)) {
console.error(`\n \x1b[31mREFUSED:\x1b[0m ${domain} is in data/owned.json — already owned.`);
console.error(` Remove it from owned.json first if you really intend to re-register.\n`);
process.exit(5);
}
const backend = detectBackend();
console.log(`\n domain: ${domain}`);
console.log(` backend: ${backend || '(none — credentials missing)'}`);
console.log(` mode: ${confirm ? '\x1b[31mREAL PURCHASE\x1b[0m' : 'dry-run'}`);
if (!backend) {
console.error(`\n no registrar credentials found in env. add via the \`secrets\` skill:`);
console.error(` NAMESILO_API_KEY # cheapest, simplest`);
console.error(` NAMECHEAP_API_USER + NAMECHEAP_API_KEY # needs IP whitelist + prefund`);
console.error(` GODADDY_KEY + GODADDY_SECRET # needs payment-on-file`);
process.exit(3);
}
if (!confirm) {
console.log(`\n [DRY-RUN] would register ${domain} via ${backend}.`);
console.log(` pass --confirm to spend money. registration is NOT reversible.\n`);
return;
}
// Real call
let result;
const start = Date.now();
try {
if (backend === 'namesilo') result = await registerNamesilo(domain);
else if (backend === 'namecheap') result = await registerNamecheap(domain);
else if (backend === 'godaddy') result = await registerGodaddy(domain);
} catch (e) {
result = { success: false, code: 'EXCEPTION', detail: e.message };
}
const elapsed = Date.now() - start;
const rec = { ts: new Date().toISOString(), domain, backend, result, elapsedMs: elapsed };
fs.mkdirSync(DATA_DIR, { recursive: true });
fs.appendFileSync(LOG_FILE, JSON.stringify(rec) + '\n');
if (result.success) {
console.log(`\n \x1b[32m✓ REGISTERED ${domain}\x1b[0m (${elapsed}ms via ${backend})`);
console.log(` log: ${LOG_FILE}\n`);
} else {
console.log(`\n \x1b[31m✗ FAILED:\x1b[0m ${result.code || ''} — ${result.detail || ''}\n`);
process.exit(4);
}
}
main().catch((e) => { console.error('error:', e.message); process.exit(1); });