← back to Apartmentwallpaper
afternic-unlock.mjs
128 lines
#!/usr/bin/env node
// Raw GoDaddy API attempt: unlock + repoint nameservers off Afternic.
// Run staged: node afternic-unlock.mjs test -> tries ONE domain, prints raw response
// node afternic-unlock.mjs all -> fans out to all 38 (only after test looks good)
//
// NOTE: This does NOT remove the Afternic for-sale LISTING (no API exists for that).
// It only attempts the registrar-level unlock + nameserver replace. If GoDaddy has
// deprecated these write endpoints for this account you'll see 403 / 422 and we pivot
// to the browser path.
//
// Pure logic (DOMAINS, findCreds, buildRequests, parseMode) is exported for unit tests;
// network/registrar writes only run when this file is executed directly as main.
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
export const DOMAINS = [
'wallpapercontractor.com','wallpaperdistributors.com','wallpaperestimator.com','allnewsdaily.com',
'bestactorawards.com','bestdocumentaryfilms.com','cannabisofthemonthclub.com','cleaninggrants.com',
'cleaninggrants.org','commercialdesignreps.com','designerrepresentatives.com','designerssalesreps.com',
'designtradelive.com','dsgntv.com','ephedrafreeherbs.com','fauxnewslive.com','hempofthemonthclub.com',
'herbsmadeinchina.com','homesonspec.com','hospitalitydesignreps.com','hospitalityfabrics.net',
'hospitalitysalesrepresentatives.com','hospitalitywalls.com','marijuanabuyersexchange.com',
'marijuanaofthemonthclub.com','mohairvelvet.com','mymemosamples.com','nationalvotingday.org',
'nodailyworries.com','notvrequired.com','petitionyour.com','petitionyour.org','potofthemonthclub.com',
'protectyourspec.com','urthfriendly.com','wall-papers.org','wallpapernashville.com','weedofthemonthclub.com'
];
// --- pure: locate GoDaddy key/secret (env first, then domain-suite MCP env block) ---
export function findCreds(env = process.env, readClaudeJson = defaultReadClaudeJson) {
let key = env.GODADDY_API_KEY || env.GODADDY_KEY;
let secret = env.GODADDY_API_SECRET || env.GODADDY_SECRET;
if (key && secret) return { key, secret, src: 'process.env' };
const cfg = readClaudeJson();
if (cfg) {
const hits = [];
(function walk(o) {
for (const k in o) {
if (k === 'mcpServers' && o[k]) {
for (const s in o[k]) if (/domain|godaddy/i.test(s)) hits.push(o[k][s].env || {});
} else if (o[k] && typeof o[k] === 'object') walk(o[k]);
}
})(cfg);
for (const e of hits) {
const kEntry = Object.entries(e).find(([n]) => /godaddy.*key|gd.*key/i.test(n));
const sEntry = Object.entries(e).find(([n]) => /godaddy.*secret|gd.*secret/i.test(n));
if (kEntry && sEntry) return { key: kEntry[1], secret: sEntry[1], src: '.claude.json' };
}
}
return null;
}
function defaultReadClaudeJson() {
try { return JSON.parse(fs.readFileSync(path.join(os.homedir(), '.claude.json'), 'utf8')); }
catch { return null; }
}
// --- pure: the exact sequence of API requests we'd make for one domain ---
export function buildRequests(domain) {
return [
{ method: 'GET', path: `/v1/domains/${domain}`, body: null },
{ method: 'PUT', path: `/v1/domains/${domain}`, body: { locked: false } },
{ method: 'PUT', path: `/v1/domains/${domain}`, body: { nameServers: ['ns01.domaincontrol.com','ns02.domaincontrol.com'] } },
];
}
// --- pure: CLI mode -> target list ---
export function parseMode(arg, domains = DOMAINS) {
const mode = arg === 'all' ? 'all' : 'test';
return { mode, targets: mode === 'all' ? domains : [domains[0]] };
}
// ---------------------------------------------------------------------------
// main (only runs when executed directly; never on import)
// ---------------------------------------------------------------------------
async function main() {
const creds = findCreds();
if (!creds) {
console.error('Could not find GoDaddy key/secret. Set GODADDY_API_KEY and GODADDY_API_SECRET and re-run.');
process.exit(1);
}
const AUTH = process.env.GODADDY_PAT
? `Bearer ${process.env.GODADDY_PAT}`
: `sso-key ${creds.key}:${creds.secret}`;
const BASE = process.env.GODADDY_API_BASE || 'https://api.godaddy.com';
console.error(`Using creds from ${creds.src}; base=${BASE}; key=${String(creds.key).slice(0,4)}…`);
async function gd(method, urlPath, body) {
const res = await fetch(BASE + urlPath, {
method,
headers: { Authorization: AUTH, 'Content-Type': 'application/json' },
body: body ? JSON.stringify(body) : undefined,
});
return { status: res.status, body: await res.text() };
}
async function processDomain(d) {
const out = { domain: d };
const [getReq, unlockReq, nsReq] = buildRequests(d);
const cur = await gd(getReq.method, getReq.path);
out.before = cur.status === 200
? (() => { try { const j = JSON.parse(cur.body); return { locked: j.locked, ns: j.nameServers }; } catch { return cur.body.slice(0,120); } })()
: `GET ${cur.status}: ${cur.body.slice(0,120)}`;
const unlock = await gd(unlockReq.method, unlockReq.path, unlockReq.body);
out.unlock = `${unlock.status}${unlock.body ? ' ' + unlock.body.slice(0,160) : ''}`;
const ns = await gd(nsReq.method, nsReq.path, nsReq.body);
out.nameservers = `${ns.status}${ns.body ? ' ' + ns.body.slice(0,160) : ''}`;
return out;
}
const { mode, targets } = parseMode(process.argv[2]);
console.error(`Mode: ${mode} — ${targets.length} domain(s)\n`);
const results = [];
for (const d of targets) {
const r = await processDomain(d);
results.push(r);
console.log(JSON.stringify(r));
}
const ok = results.filter(r => /^20[04]/.test(r.unlock)).length;
console.error(`\nDone. unlock 2xx: ${ok}/${results.length}. (Listing removal still requires the Afternic dashboard.)`);
}
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
main();
}