@ssbun/domain-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/bin/domain-cli.js +106 -0
- package/package.json +17 -0
package/README.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# domain-cli
|
|
2
|
+
|
|
3
|
+
使用 RDAP 检查域名是否已有注册记录。
|
|
4
|
+
|
|
5
|
+
npm 包名:`@ssbun/domain-cli`,命令名:`domain-cli`。
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm link
|
|
9
|
+
domain-cli check 101010.xyz example.com
|
|
10
|
+
domain-cli check --json 101010.xyz example.com
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
输出含义:
|
|
14
|
+
|
|
15
|
+
- `registered`:RDAP 有注册记录,普通新注册不可买。
|
|
16
|
+
- `available`:RDAP 返回未找到,通常可作为可注册候选,最终以注册商购物车为准。
|
|
17
|
+
- `unknown`:RDAP 服务缺失、网络错误或注册局返回异常。
|
|
18
|
+
|
|
19
|
+
价格和 premium 信息不在 RDAP 中,需要注册商 API。
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const BOOTSTRAP_URL = "https://data.iana.org/rdap/dns.json";
|
|
4
|
+
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
const [command, ...commandArgs] = args;
|
|
7
|
+
|
|
8
|
+
if (!command || command === "-h" || command === "--help") {
|
|
9
|
+
printUsage();
|
|
10
|
+
process.exit(command ? 0 : 2);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (command !== "check") {
|
|
14
|
+
console.error(`Unknown command: ${command}`);
|
|
15
|
+
printUsage();
|
|
16
|
+
process.exit(2);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const jsonOutput = commandArgs.includes("--json");
|
|
20
|
+
const domains = commandArgs.filter((arg) => arg !== "--json").map(normalizeDomain);
|
|
21
|
+
|
|
22
|
+
if (domains.length === 0) {
|
|
23
|
+
console.error("Usage: domain-cli check [--json] <domain> [domain...]");
|
|
24
|
+
process.exit(2);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
await runCheck(domains, jsonOutput);
|
|
28
|
+
|
|
29
|
+
async function runCheck(domains, jsonOutput) {
|
|
30
|
+
const bootstrap = await fetchJson(BOOTSTRAP_URL);
|
|
31
|
+
const results = await Promise.all(domains.map((domain) => checkDomain(domain, bootstrap)));
|
|
32
|
+
|
|
33
|
+
if (jsonOutput) {
|
|
34
|
+
console.log(JSON.stringify(results, null, 2));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (const result of results) {
|
|
39
|
+
const suffix = result.expiresAt ? ` expires=${result.expiresAt}` : "";
|
|
40
|
+
console.log(`${result.domain}\t${result.status}${suffix}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function checkDomain(domain, bootstrap) {
|
|
45
|
+
const tld = domain.split(".").at(-1);
|
|
46
|
+
const rdapBaseUrl = findRdapBaseUrl(tld, bootstrap);
|
|
47
|
+
|
|
48
|
+
if (!rdapBaseUrl) {
|
|
49
|
+
return { domain, status: "unknown", reason: `No RDAP server for .${tld}` };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const url = `${rdapBaseUrl.replace(/\/$/, "")}/domain/${encodeURIComponent(domain)}`;
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const response = await fetch(url, { headers: { accept: "application/rdap+json" } });
|
|
56
|
+
|
|
57
|
+
if (response.status === 404) {
|
|
58
|
+
return { domain, status: "available" };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const data = await response.json().catch(() => ({}));
|
|
62
|
+
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
return { domain, status: "unknown", reason: data.title ?? `HTTP ${response.status}` };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
domain,
|
|
69
|
+
status: "registered",
|
|
70
|
+
rdapStatus: data.status ?? [],
|
|
71
|
+
registeredAt: eventDate(data, "registration"),
|
|
72
|
+
expiresAt: eventDate(data, "expiration"),
|
|
73
|
+
};
|
|
74
|
+
} catch (error) {
|
|
75
|
+
return { domain, status: "unknown", reason: error.message };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function findRdapBaseUrl(tld, bootstrap) {
|
|
80
|
+
const service = bootstrap.services.find(([tlds]) => tlds.includes(tld));
|
|
81
|
+
return service?.[1]?.[0];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function fetchJson(url) {
|
|
85
|
+
const response = await fetch(url);
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
throw new Error(`Failed to fetch ${url}: HTTP ${response.status}`);
|
|
88
|
+
}
|
|
89
|
+
return response.json();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function normalizeDomain(domain) {
|
|
93
|
+
return domain.trim().replace(/^https?:\/\//, "").replace(/\/.*$/, "").toLowerCase();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function eventDate(data, action) {
|
|
97
|
+
return data.events?.find((event) => event.eventAction === action)?.eventDate;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function printUsage() {
|
|
101
|
+
console.error(`Usage:
|
|
102
|
+
domain-cli check [--json] <domain> [domain...]
|
|
103
|
+
|
|
104
|
+
Commands:
|
|
105
|
+
check Check whether domains have RDAP registration records`);
|
|
106
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ssbun/domain-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Check domain registration status via RDAP.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"domain-cli": "./bin/domain-cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"check": "node --check bin/domain-cli.js",
|
|
11
|
+
"demo": "node bin/domain-cli.js check 101010.xyz this-domain-should-not-exist-928374.example"
|
|
12
|
+
},
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT"
|
|
17
|
+
}
|