domain-info-fetcher
Version:
A powerful TypeScript/JavaScript tool for comprehensive domain analysis, featuring detailed WHOIS data with registration dates, registrars, and domain status. Offers SSL certificate extraction (with PEM support), DNS records, and server details. Includes
549 lines (543 loc) • 24.8 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const index_1 = require("../index");
const chalk_1 = __importDefault(require("chalk"));
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const args = process.argv.slice(2);
const helpText = `
${chalk_1.default.bold("domain-info-fetcher CLI")}
A command-line tool to fetch information about domains (single or batch).
${chalk_1.default.bold("Usage:")}
domain-info-fetcher <domain> [options]
domain-info-fetcher --file domains.txt [options]
${chalk_1.default.bold("Options:")}
--timeout <ms> Set request timeout in milliseconds (default: 10000)
--format <fmt> Output format: json | csv | table (default: table)
--out <file> Write output to file (JSON/CSV/table text)
--file <path> Read domains from file (one per line)
--concurrency <n> Concurrent lookups when using --file (default: 5)
--include <parts> Only include sections (csv/json and printed sections). Comma-separated of: ssl,server,dns,http,whois
--exclude <parts> Exclude sections (takes precedence if both set)
--json Shortcut for --format json
--help Show this help message
${chalk_1.default.bold("Examples:")}
domain-info-fetcher example.com
domain-info-fetcher blog.example.com --timeout 5000 --format json
domain-info-fetcher --file domains.txt --concurrency 10 --format csv --out results.csv
domain-info-fetcher example.com --include ssl,whois
${chalk_1.default.bold("Notes:")}
- Subdomains are fully supported. For subdomains, A and CNAME records are fetched
for the subdomain, while MX, TXT, NS, and SOA are fetched from the root domain.
`;
function parseListFlag(name) {
const idx = args.indexOf(name);
if (idx !== -1 && args[idx + 1]) {
return new Set(args[idx + 1]
.split(",")
.map((s) => s.trim().toLowerCase())
.filter(Boolean));
}
return null;
}
function getArgValue(name) {
const idx = args.indexOf(name);
if (idx !== -1) {
return args[idx + 1];
}
return undefined;
}
function printHelpAndExit() {
console.log(helpText);
process.exit(0);
}
function pickSections(obj, include, exclude) {
const map = {
ssl: "sslData",
server: "serverData",
dns: "dnsData",
http: "httpStatus",
whois: "whoisData",
};
const result = {};
const keys = Object.keys(map);
for (const k of keys) {
const key = map[k];
const shouldInclude = (include === null || include.has(k)) && !(exclude && exclude.has(k));
if (shouldInclude && key in obj) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
result[key] = obj[key];
}
}
return result;
}
function sanitizeDomainList(domains) {
return domains
.map((d) => d.trim())
.filter((d) => d.length > 0 && !d.startsWith("#"));
}
async function readDomainsFromFile(filePath) {
const abs = path.resolve(process.cwd(), filePath);
const content = await fs.promises.readFile(abs, "utf8");
return sanitizeDomainList(content.split(/\r?\n/));
}
function toCsvValue(value) {
if (value === null || value === undefined)
return "";
const str = typeof value === "string" ? value : JSON.stringify(value);
if (str.includes(",") || str.includes("\n") || str.includes("\"")) {
return '"' + str.replace(/"/g, '""') + '"';
}
return str;
}
function buildCsv(rows, headers) {
const lines = [headers.join(",")];
for (const row of rows) {
const line = headers.map((h) => toCsvValue(row[h])).join(",");
lines.push(line);
}
return lines.join("\n");
}
function summarizeRow(domain, info) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
const sslValid = (_b = (_a = info === null || info === void 0 ? void 0 : info.sslData) === null || _a === void 0 ? void 0 : _a.valid) !== null && _b !== void 0 ? _b : null;
const sslValidTo = ((_c = info === null || info === void 0 ? void 0 : info.sslData) === null || _c === void 0 ? void 0 : _c.validTo)
? new Date(info.sslData.validTo).toISOString()
: null;
const http = (_d = info === null || info === void 0 ? void 0 : info.httpStatus) !== null && _d !== void 0 ? _d : null;
const server = (_e = info === null || info === void 0 ? void 0 : info.serverData) !== null && _e !== void 0 ? _e : null;
const aCount = Array.isArray((_f = info === null || info === void 0 ? void 0 : info.dnsData) === null || _f === void 0 ? void 0 : _f.A) ? info.dnsData.A.length : 0;
const cname = (_h = (_g = info === null || info === void 0 ? void 0 : info.dnsData) === null || _g === void 0 ? void 0 : _g.CNAME) !== null && _h !== void 0 ? _h : null;
const mxCount = Array.isArray((_j = info === null || info === void 0 ? void 0 : info.dnsData) === null || _j === void 0 ? void 0 : _j.MX) ? info.dnsData.MX.length : 0;
const nsCount = Array.isArray((_k = info === null || info === void 0 ? void 0 : info.dnsData) === null || _k === void 0 ? void 0 : _k.NS) ? info.dnsData.NS.length : 0;
const txtCount = Array.isArray((_l = info === null || info === void 0 ? void 0 : info.dnsData) === null || _l === void 0 ? void 0 : _l.TXT)
? info.dnsData.TXT.length
: 0;
const registrar = (_o = (_m = info === null || info === void 0 ? void 0 : info.whoisData) === null || _m === void 0 ? void 0 : _m.registrar) !== null && _o !== void 0 ? _o : null;
const creation = ((_p = info === null || info === void 0 ? void 0 : info.whoisData) === null || _p === void 0 ? void 0 : _p.creationDate)
? new Date(info.whoisData.creationDate).toISOString()
: null;
const expiration = ((_q = info === null || info === void 0 ? void 0 : info.whoisData) === null || _q === void 0 ? void 0 : _q.expirationDate)
? new Date(info.whoisData.expirationDate).toISOString()
: null;
let daysToExpiry = null;
if ((_r = info === null || info === void 0 ? void 0 : info.whoisData) === null || _r === void 0 ? void 0 : _r.expirationDate) {
const now = Date.now();
const exp = new Date(info.whoisData.expirationDate).getTime();
daysToExpiry = Math.floor((exp - now) / (1000 * 60 * 60 * 24));
}
return {
domain,
http_status: http,
server,
ssl_valid: sslValid,
ssl_valid_to: sslValidTo,
a_count: aCount,
cname,
mx_count: mxCount,
ns_count: nsCount,
txt_count: txtCount,
whois_registrar: registrar,
whois_creation: creation,
whois_expiration: expiration,
whois_days_to_expiry: daysToExpiry,
};
}
async function fetchOne(domain, options) {
try {
const info = await (0, index_1.fetchDomainInfo)(domain, options);
return { domain, info };
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
return { domain, error: message };
}
}
async function processWithConcurrency(domains, options, concurrency, onProgress) {
const total = domains.length;
let done = 0;
const results = [];
let index = 0;
async function worker() {
while (true) {
const current = index < domains.length ? domains[index++] : undefined;
if (!current)
break;
const res = await fetchOne(current, options);
results.push(res);
done++;
if (onProgress)
onProgress(done, total, res);
}
}
const workers = Array.from({ length: Math.min(concurrency, total) }, () => worker());
await Promise.all(workers);
return results;
}
function renderSingle(domain, info, include, exclude) {
var _a, _b, _c, _d, _e;
console.log(chalk_1.default.blue(`Fetching information for ${domain}...`));
const subdomain = (0, index_1.extractSubdomain)(domain);
if (subdomain) {
const rootDomain = (0, index_1.getRootDomain)(domain);
console.log(chalk_1.default.blue(`Detected subdomain: ${subdomain} of ${rootDomain}`));
console.log(chalk_1.default.blue(`For subdomain queries, A and CNAME records are specific to the subdomain,`));
console.log(chalk_1.default.blue(`while other DNS records are from the root domain ${rootDomain}`));
}
const allow = (k) => (include === null || include.has(k)) && !(exclude && exclude.has(k));
if (allow("ssl")) {
console.log("\n" + chalk_1.default.green.bold("🔒 SSL Certificate:"));
if ((_a = info.sslData) === null || _a === void 0 ? void 0 : _a.details) {
console.log(` - Issued to: ${info.sslData.details.subject}`);
console.log(` - Issued by: ${info.sslData.details.issuer}`);
console.log(` - Valid: ${info.sslData.valid ? chalk_1.default.green("✅ Yes") : chalk_1.default.red("❌ No")}`);
console.log(` - Valid from: ${new Date(info.sslData.details.validFrom).toLocaleDateString()}`);
console.log(` - Valid until: ${new Date(info.sslData.details.validTo).toLocaleDateString()}`);
console.log(` - Days until expiration: ${Math.floor((new Date(info.sslData.details.validTo).getTime() - Date.now()) / (1000 * 60 * 60 * 24))}`);
}
else {
console.log(` - Issued to: ${JSON.stringify((_b = info.sslData) === null || _b === void 0 ? void 0 : _b.subject)}`);
console.log(` - Issued by: ${JSON.stringify((_c = info.sslData) === null || _c === void 0 ? void 0 : _c.issuer)}`);
console.log(` - Valid: ${((_d = info.sslData) === null || _d === void 0 ? void 0 : _d.valid) ? chalk_1.default.green("✅ Yes") : chalk_1.default.red("❌ No")}`);
if (info.sslData) {
console.log(` - Valid from: ${new Date(info.sslData.validFrom).toLocaleDateString()}`);
console.log(` - Valid until: ${new Date(info.sslData.validTo).toLocaleDateString()}`);
}
}
if ((_e = info.sslData) === null || _e === void 0 ? void 0 : _e.certificate) {
console.log(` - ${chalk_1.default.green("✅")} PEM certificate available`);
}
}
if (allow("server") || allow("http")) {
console.log("\n" + chalk_1.default.cyan.bold("🖥️ Server:"));
if (allow("server")) {
console.log(` - Server software: ${info.serverData || chalk_1.default.gray("Not available")}`);
}
if (allow("http")) {
console.log(` - HTTP Status: ${info.httpStatus
? info.httpStatus >= 200 && info.httpStatus < 300
? chalk_1.default.green(info.httpStatus)
: chalk_1.default.yellow(info.httpStatus)
: chalk_1.default.gray("Not available")}`);
}
}
if (allow("dns")) {
if (info.dnsData) {
console.log("\n" + chalk_1.default.yellow.bold("🌐 DNS Records:"));
console.log(` - A Records: ${info.dnsData.A.join(", ")}`);
console.log(` - CNAME: ${info.dnsData.CNAME || chalk_1.default.gray("None")}`);
if (info.dnsData.MX.length) {
console.log(" - MX Records:");
info.dnsData.MX.forEach((mx) => {
console.log(` * ${mx.exchange} (priority: ${mx.priority})`);
});
}
if (info.dnsData.TXT.length) {
console.log(" - TXT Records:");
info.dnsData.TXT.forEach((txt) => {
console.log(` * ${txt}`);
});
}
if (info.dnsData.NS.length) {
console.log(" - NS Records:");
info.dnsData.NS.forEach((ns) => {
console.log(` * ${ns}`);
});
}
}
else {
console.log("\n" + chalk_1.default.red("🌐 DNS Records: Not available"));
}
}
if (allow("whois")) {
if (info.whoisData) {
console.log("\n" + chalk_1.default.magenta.bold("📋 WHOIS Information:"));
console.log(` - Registrar: ${info.whoisData.registrar || chalk_1.default.gray("Not available")}`);
if (info.whoisData.registrarUrl) {
console.log(` - Registrar URL: ${info.whoisData.registrarUrl}`);
}
if (info.whoisData.registrarIanaId) {
console.log(` - Registrar IANA ID: ${info.whoisData.registrarIanaId}`);
}
const datesAvailable = info.whoisData.creationDate || info.whoisData.updatedDate || info.whoisData.expirationDate;
if (datesAvailable) {
console.log("\n " + chalk_1.default.magenta.bold("⏰ Important Dates:"));
}
if (info.whoisData.creationDate) {
console.log(` - Created: ${new Date(info.whoisData.creationDate).toLocaleDateString()}`);
}
if (info.whoisData.updatedDate) {
console.log(` - Last Updated: ${new Date(info.whoisData.updatedDate).toLocaleDateString()}`);
}
if (info.whoisData.expirationDate) {
const now = new Date();
const daysUntilExpiration = Math.floor((new Date(info.whoisData.expirationDate).getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
const expirationColor = daysUntilExpiration < 30 ? chalk_1.default.red : daysUntilExpiration < 90 ? chalk_1.default.yellow : chalk_1.default.green;
console.log(` - Expires: ${new Date(info.whoisData.expirationDate).toLocaleDateString()} (${expirationColor(`${daysUntilExpiration} days`)})`);
}
if (info.whoisData.statusCodes && info.whoisData.statusCodes.length > 0) {
console.log("\n " + chalk_1.default.magenta.bold("🔒 Domain Status:"));
info.whoisData.statusCodes.forEach((status) => {
let statusDisplay = status;
if (status.includes("clientTransferProhibited") ||
status.includes("serverTransferProhibited")) {
statusDisplay = chalk_1.default.yellow(status) + " " + chalk_1.default.dim("(Transfer locked)");
}
else if (status.includes("clientDeleteProhibited") ||
status.includes("serverDeleteProhibited")) {
statusDisplay = chalk_1.default.yellow(status) + " " + chalk_1.default.dim("(Deletion protected)");
}
else if (status.includes("clientUpdateProhibited") ||
status.includes("serverUpdateProhibited")) {
statusDisplay = chalk_1.default.yellow(status) + " " + chalk_1.default.dim("(Updates restricted)");
}
else if (status.includes("clientHold") || status.includes("serverHold")) {
statusDisplay = chalk_1.default.red(status) + " " + chalk_1.default.dim("(Domain not in DNS)");
}
else if (status.includes("ok")) {
statusDisplay = chalk_1.default.green(status);
}
console.log(` - ${statusDisplay}`);
});
}
if (info.whoisData.nameServers && info.whoisData.nameServers.length > 0) {
console.log("\n " + chalk_1.default.magenta.bold("🌐 Name Servers (from WHOIS):"));
info.whoisData.nameServers.forEach((ns) => {
console.log(` - ${ns}`);
});
}
if (info.whoisData.rawText) {
console.log("\n " + chalk_1.default.magenta.bold("📝 Sample Raw WHOIS Data:"));
const rawTextSample = info.whoisData.rawText
.split("\n")
.filter((line) => line.trim() !== "")
.slice(0, 5)
.map((line) => ` ${line}`)
.join("\n");
console.log(chalk_1.default.gray(`${rawTextSample}`));
console.log(chalk_1.default.dim(" (Showing first 5 non-empty lines of raw WHOIS data)"));
console.log(chalk_1.default.cyan("\n 💡 Tip: To view full WHOIS data, use: whois " + domain));
}
}
else {
console.log("\n" + chalk_1.default.magenta("📋 WHOIS Information: ") + chalk_1.default.gray("Not available"));
}
}
}
function renderSummaryTable(rows) {
const headers = [
"domain",
"http_status",
"server",
"ssl_valid",
"ssl_valid_to",
"a_count",
"mx_count",
"ns_count",
"whois_expiration",
"whois_days_to_expiry",
];
const widths = headers.map((h) => Math.max(h.length, ...rows.map((r) => { var _a; return String((_a = r[h]) !== null && _a !== void 0 ? _a : "").length; })));
const pad = (s, w) => (s + " ".repeat(w)).slice(0, w);
const line = headers.map((h, i) => pad(h, widths[i])).join(" ");
console.log(chalk_1.default.bold(line));
for (const r of rows) {
const l = headers.map((h, i) => { var _a; return pad(String((_a = r[h]) !== null && _a !== void 0 ? _a : ""), widths[i]); }).join(" ");
console.log(l);
}
}
async function main() {
if (args.includes("--help") || args.length === 0) {
printHelpAndExit();
}
const jsonShortcut = args.includes("--json");
const formatArg = (getArgValue("--format") || (jsonShortcut ? "json" : "table"));
const format = ["json", "csv", "table"].includes(formatArg)
? formatArg
: "table";
const outFile = getArgValue("--out");
const timeoutStr = getArgValue("--timeout");
const fileInput = getArgValue("--file");
const concurrencyStr = getArgValue("--concurrency");
const include = parseListFlag("--include");
const exclude = parseListFlag("--exclude");
const options = {};
if (timeoutStr) {
const t = parseInt(timeoutStr, 10);
if (!isNaN(t))
options.timeout = t;
}
let domains = [];
const positionalDomain = !fileInput ? args[0] : undefined;
if (fileInput) {
domains = await readDomainsFromFile(fileInput);
}
if (positionalDomain) {
domains.push(positionalDomain);
}
domains = sanitizeDomainList(domains);
if (domains.length === 0) {
console.error(chalk_1.default.red("No domains specified."));
printHelpAndExit();
}
if (domains.length === 1) {
const domain = domains[0];
try {
const info = await (0, index_1.fetchDomainInfo)(domain, options);
if (!info) {
console.error(chalk_1.default.red("No domain information returned"));
console.error(chalk_1.default.yellow("Suggestion: Verify that the domain exists and is accessible."));
return;
}
if (format === "json") {
const output = include || exclude ? pickSections(info, include, exclude) : info;
const text = JSON.stringify(output, null, 2);
if (outFile) {
await fs.promises.writeFile(outFile, text, "utf8");
}
else {
console.log(text);
}
return;
}
if (format === "csv") {
const row = summarizeRow(domain, info);
const headers = Object.keys(row);
const csv = buildCsv([row], headers);
if (outFile) {
await fs.promises.writeFile(outFile, csv, "utf8");
}
else {
console.log(csv);
}
return;
}
// table
renderSingle(domain, info, include, exclude);
return;
}
catch (error) {
console.error(chalk_1.default.red("❌ Error fetching domain information:"));
if (error instanceof Error) {
console.error(chalk_1.default.red(` ${error.message}`));
}
else {
console.error(chalk_1.default.red(` ${String(error)}`));
}
process.exit(1);
}
}
// Batch mode
const concurrency = Math.max(1, Math.min(50, parseInt(concurrencyStr || "5", 10) || 5));
console.log(chalk_1.default.blue(`Processing ${domains.length} domains with concurrency ${concurrency}...`));
const results = await processWithConcurrency(domains, options, concurrency, (done, total, item) => {
const prefix = item.error ? chalk_1.default.red("✖") : chalk_1.default.green("✔");
console.log(`${prefix} ${item.domain} (${done}/${total})`);
});
const successes = results.filter((r) => r.info);
const failures = results.filter((r) => r.error);
if (format === "json") {
const payload = successes.map((r) => ({
domain: r.domain,
data: include || exclude ? pickSections(r.info, include, exclude) : r.info,
}));
const text = JSON.stringify({ results: payload, failed: failures }, null, 2);
if (outFile) {
await fs.promises.writeFile(outFile, text, "utf8");
}
else {
console.log(text);
}
}
else if (format === "csv") {
const rows = successes.map((r) => summarizeRow(r.domain, r.info));
const headers = rows.length > 0 ? Object.keys(rows[0]) : [
"domain",
"http_status",
"server",
"ssl_valid",
"ssl_valid_to",
"a_count",
"cname",
"mx_count",
"ns_count",
"txt_count",
"whois_registrar",
"whois_creation",
"whois_expiration",
"whois_days_to_expiry",
];
const csv = buildCsv(rows, headers);
if (outFile) {
await fs.promises.writeFile(outFile, csv, "utf8");
}
else {
console.log(csv);
}
}
else {
// table summary
const rows = successes.map((r) => summarizeRow(r.domain, r.info));
if (rows.length > 0) {
console.log("");
renderSummaryTable(rows);
}
}
// Diagnostics summary
console.log("");
console.log(chalk_1.default.bold("Summary:"));
console.log(chalk_1.default.green(` ✔ Succeeded: ${successes.length}`));
console.log(chalk_1.default.red(` ✖ Failed: ${failures.length}`));
if (failures.length > 0) {
const buckets = {};
for (const f of failures) {
const msg = (f.error || "Unknown error").split(".")[0];
buckets[msg] = (buckets[msg] || 0) + 1;
}
console.log(" Error categories:");
Object.entries(buckets)
.sort((a, b) => b[1] - a[1])
.forEach(([k, v]) => console.log(` - ${k}: ${v}`));
}
}
main().catch((error) => {
console.error(chalk_1.default.red("Unexpected error:"), error);
process.exit(1);
});