@zhin.js/plugin-nettools 0.1.2 → 0.1.3
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/CHANGELOG.md +11 -0
- package/commands/dns/[name]/[[type]]/index.js +5 -5
- package/commands/dns/[name]/[[type]]/index.ts +5 -5
- package/commands/domain-price/[suffix]/[[order]]/index.js +5 -5
- package/commands/domain-price/[suffix]/[[order]]/index.ts +5 -5
- package/commands/ping/[host]/index.js +4 -4
- package/commands/ping/[host]/index.ts +4 -4
- package/commands/whois/[domain]/index.js +4 -4
- package/commands/whois/[domain]/index.ts +4 -4
- package/lib/address-policy.d.ts +2 -0
- package/lib/address-policy.js +55 -54
- package/lib/address-policy.js.map +1 -1
- package/lib/cooldown.js +2 -1
- package/lib/cooldown.js.map +1 -1
- package/lib/dns.d.ts +2 -1
- package/lib/dns.js +14 -12
- package/lib/dns.js.map +1 -1
- package/lib/domain-price.d.ts +2 -1
- package/lib/domain-price.js +16 -19
- package/lib/domain-price.js.map +1 -1
- package/lib/errors.d.ts +5 -2
- package/lib/errors.js +8 -4
- package/lib/errors.js.map +1 -1
- package/lib/http.js +12 -11
- package/lib/http.js.map +1 -1
- package/lib/messages.d.ts +329 -0
- package/lib/messages.js +335 -0
- package/lib/messages.js.map +1 -0
- package/lib/ping.d.ts +2 -1
- package/lib/ping.js +13 -11
- package/lib/ping.js.map +1 -1
- package/lib/presentation.d.ts +1 -0
- package/lib/presentation.js +36 -0
- package/lib/presentation.js.map +1 -0
- package/lib/service.d.ts +6 -5
- package/lib/service.js +16 -15
- package/lib/service.js.map +1 -1
- package/lib/target-guard.js +6 -4
- package/lib/target-guard.js.map +1 -1
- package/lib/whois.d.ts +2 -1
- package/lib/whois.js +18 -16
- package/lib/whois.js.map +1 -1
- package/package.json +7 -7
- package/skills/nettools/tools/dns_lookup/index.js +1 -1
- package/skills/nettools/tools/domain_price/index.js +1 -1
- package/skills/nettools/tools/net_ping/index.js +1 -1
- package/skills/nettools/tools/whois_lookup/index.js +1 -1
package/lib/service.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { message } from './messages.js';
|
|
1
2
|
/**
|
|
2
3
|
* NettoolsService: the single entry point used by chat commands and Agent
|
|
3
4
|
* tools. Each method validates input, spends the caller's cooldown, performs
|
|
@@ -33,19 +34,19 @@ export class NettoolsService {
|
|
|
33
34
|
this.#cooldown = new UserCooldown(this.config.cooldownMs, dependencies.now);
|
|
34
35
|
}
|
|
35
36
|
/** Run one operation and always resolve to a user-facing string. */
|
|
36
|
-
async run(operation) {
|
|
37
|
+
async run(operation, locale = 'zh-CN') {
|
|
37
38
|
try {
|
|
38
39
|
return await operation();
|
|
39
40
|
}
|
|
40
41
|
catch (error) {
|
|
41
|
-
return describeFailure(error);
|
|
42
|
+
return describeFailure(error, locale);
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
|
-
async ping(hostInput, callerKey, signal) {
|
|
45
|
+
async ping(hostInput, callerKey, signal, locale = 'zh-CN') {
|
|
45
46
|
const host = parseHostInput(hostInput);
|
|
46
47
|
const verdict = classifyParsedHost(host);
|
|
47
48
|
if (verdict.blocked) {
|
|
48
|
-
throw new NettoolsError('blocked_target',
|
|
49
|
+
throw new NettoolsError('blocked_target', message('probeBlocked', { host: displayHost(host), reason: verdict.detail }));
|
|
49
50
|
}
|
|
50
51
|
this.#cooldown.take(callerKey);
|
|
51
52
|
const target = await assertPublicTarget(hostInput, this.#resolver, signal);
|
|
@@ -55,42 +56,42 @@ export class NettoolsService {
|
|
|
55
56
|
maxPolls: this.config.pingMaxPolls,
|
|
56
57
|
...(signal ? { signal } : {}),
|
|
57
58
|
});
|
|
58
|
-
return formatPingReport(report);
|
|
59
|
+
return formatPingReport(report, locale);
|
|
59
60
|
}
|
|
60
|
-
async whois(domainInput, callerKey, signal) {
|
|
61
|
+
async whois(domainInput, callerKey, signal, locale = 'zh-CN') {
|
|
61
62
|
const host = parseHostInput(domainInput);
|
|
62
63
|
if (host.kind !== 'domain') {
|
|
63
|
-
throw new NettoolsError('invalid_input', '
|
|
64
|
+
throw new NettoolsError('invalid_input', message('whoisDomainOnly'));
|
|
64
65
|
}
|
|
65
66
|
const verdict = classifyParsedHost(host);
|
|
66
67
|
if (verdict.blocked) {
|
|
67
|
-
throw new NettoolsError('blocked_target',
|
|
68
|
+
throw new NettoolsError('blocked_target', message('queryBlocked', { host: host.name, reason: verdict.detail }));
|
|
68
69
|
}
|
|
69
70
|
this.#cooldown.take(callerKey);
|
|
70
71
|
const record = await this.#rdap.lookup(host.name, signal);
|
|
71
|
-
return formatWhoisRecord(record);
|
|
72
|
+
return formatWhoisRecord(record, locale);
|
|
72
73
|
}
|
|
73
|
-
async dns(nameInput, typeInput, callerKey, signal) {
|
|
74
|
+
async dns(nameInput, typeInput, callerKey, signal, locale = 'zh-CN') {
|
|
74
75
|
const type = parseDnsType(typeInput);
|
|
75
76
|
const host = parseHostInput(nameInput, { allowUnderscore: true });
|
|
76
77
|
const verdict = classifyParsedHost(host);
|
|
77
78
|
if (verdict.blocked) {
|
|
78
|
-
throw new NettoolsError('blocked_target',
|
|
79
|
+
throw new NettoolsError('blocked_target', message('queryBlocked', { host: displayHost(host), reason: verdict.detail }));
|
|
79
80
|
}
|
|
80
81
|
if (host.kind !== 'domain' && type !== 'PTR') {
|
|
81
|
-
throw new NettoolsError('invalid_input', '
|
|
82
|
+
throw new NettoolsError('invalid_input', message('reverseOnly'));
|
|
82
83
|
}
|
|
83
84
|
const queryName = host.kind === 'domain' ? host.name : reverseLookupName(host);
|
|
84
85
|
this.#cooldown.take(callerKey);
|
|
85
86
|
const answer = await this.#resolver.resolve(queryName, type, signal);
|
|
86
|
-
return formatDnsAnswer(answer);
|
|
87
|
+
return formatDnsAnswer(answer, undefined, locale);
|
|
87
88
|
}
|
|
88
|
-
async domainPrice(suffixInput, kindInput, callerKey, signal) {
|
|
89
|
+
async domainPrice(suffixInput, kindInput, callerKey, signal, locale = 'zh-CN') {
|
|
89
90
|
const kind = parsePriceKind(kindInput);
|
|
90
91
|
const suffix = parseDomainSuffix(suffixInput);
|
|
91
92
|
this.#cooldown.take(callerKey);
|
|
92
93
|
const comparison = await comparePrices(this.#http, suffix, kind, signal);
|
|
93
|
-
return formatPriceComparison(comparison, this.config.priceListLimit);
|
|
94
|
+
return formatPriceComparison(comparison, this.config.priceListLimit, locale);
|
|
94
95
|
}
|
|
95
96
|
}
|
|
96
97
|
//# sourceMappingURL=service.js.map
|
package/lib/service.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Locale } from 'zhin.js/i18n';\nimport { message } from './messages.js';\n/**\n * NettoolsService: the single entry point used by chat commands and Agent\n * tools. Each method validates input, spends the caller's cooldown, performs\n * the lookup and returns a ready-to-send text reply. Failures are thrown as\n * NettoolsError; {@link NettoolsService.run} turns them into friendly text.\n */\nimport {\n classifyParsedHost,\n displayHost,\n parseHostInput,\n reverseLookupName,\n} from './address-policy.js';\nimport { resolveNettoolsConfig, type NettoolsConfig, type ResolvedNettoolsConfig } from './config.js';\nimport { UserCooldown } from './cooldown.js';\nimport { DohResolver, formatDnsAnswer, parseDnsType } from './dns.js';\nimport { comparePrices, formatPriceComparison, parseDomainSuffix, parsePriceKind } from './domain-price.js';\nimport { describeFailure, NettoolsError } from './errors.js';\nimport { DEFAULT_RETRY_POLICY, defaultHttpDependencies, HttpClient, type HttpDependencies } from './http.js';\nimport { formatPingReport, runMultiRegionPing } from './ping.js';\nimport { assertPublicTarget } from './target-guard.js';\nimport { formatWhoisRecord, RdapClient } from './whois.js';\n\nexport interface NettoolsServiceOptions {\n readonly config?: NettoolsConfig;\n /** Injected for tests: fetch, sleep, clock and randomness. */\n readonly dependencies?: HttpDependencies;\n}\n\nexport class NettoolsService {\n readonly config: ResolvedNettoolsConfig;\n readonly #http: HttpClient;\n readonly #resolver: DohResolver;\n readonly #rdap: RdapClient;\n readonly #cooldown: UserCooldown;\n\n constructor(options: NettoolsServiceOptions = {}) {\n this.config = resolveNettoolsConfig(options.config);\n const dependencies = options.dependencies ?? defaultHttpDependencies();\n this.#http = new HttpClient(\n {\n ...DEFAULT_RETRY_POLICY,\n timeoutMs: this.config.requestTimeoutMs,\n maxRetries: this.config.maxRetries,\n },\n dependencies,\n );\n this.#resolver = new DohResolver(this.#http, this.config.dohProviders);\n this.#rdap = new RdapClient(this.#http);\n this.#cooldown = new UserCooldown(this.config.cooldownMs, dependencies.now);\n }\n\n /** Run one operation and always resolve to a user-facing string. */\n async run(operation: () => Promise<string>, locale: Locale = 'zh-CN'): Promise<string> {\n try {\n return await operation();\n } catch (error) {\n return describeFailure(error, locale);\n }\n }\n\n async ping(hostInput: string, callerKey: string, signal?: AbortSignal, locale: Locale = 'zh-CN'): Promise<string> {\n const host = parseHostInput(hostInput);\n const verdict = classifyParsedHost(host);\n if (verdict.blocked) {\n throw new NettoolsError('blocked_target', message('probeBlocked', { host: displayHost(host), reason: verdict.detail }));\n }\n this.#cooldown.take(callerKey);\n const target = await assertPublicTarget(hostInput, this.#resolver, signal);\n const report = await runMultiRegionPing(this.#http, target.display, {\n maxNodes: this.config.pingMaxNodes,\n pollIntervalMs: this.config.pingPollIntervalMs,\n maxPolls: this.config.pingMaxPolls,\n ...(signal ? { signal } : {}),\n });\n return formatPingReport(report, locale);\n }\n\n async whois(domainInput: string, callerKey: string, signal?: AbortSignal, locale: Locale = 'zh-CN'): Promise<string> {\n const host = parseHostInput(domainInput);\n if (host.kind !== 'domain') {\n throw new NettoolsError('invalid_input', message('whoisDomainOnly'));\n }\n const verdict = classifyParsedHost(host);\n if (verdict.blocked) {\n throw new NettoolsError('blocked_target', message('queryBlocked', { host: host.name, reason: verdict.detail }));\n }\n this.#cooldown.take(callerKey);\n const record = await this.#rdap.lookup(host.name, signal);\n return formatWhoisRecord(record, locale);\n }\n\n async dns(nameInput: string, typeInput: string | undefined, callerKey: string, signal?: AbortSignal, locale: Locale = 'zh-CN'): Promise<string> {\n const type = parseDnsType(typeInput);\n const host = parseHostInput(nameInput, { allowUnderscore: true });\n const verdict = classifyParsedHost(host);\n if (verdict.blocked) {\n throw new NettoolsError('blocked_target', message('queryBlocked', { host: displayHost(host), reason: verdict.detail }));\n }\n if (host.kind !== 'domain' && type !== 'PTR') {\n throw new NettoolsError('invalid_input', message('reverseOnly'));\n }\n const queryName = host.kind === 'domain' ? host.name : reverseLookupName(host);\n this.#cooldown.take(callerKey);\n const answer = await this.#resolver.resolve(queryName, type, signal);\n return formatDnsAnswer(answer, undefined, locale);\n }\n\n async domainPrice(suffixInput: string, kindInput: string | undefined, callerKey: string, signal?: AbortSignal, locale: Locale = 'zh-CN'): Promise<string> {\n const kind = parsePriceKind(kindInput);\n const suffix = parseDomainSuffix(suffixInput);\n this.#cooldown.take(callerKey);\n const comparison = await comparePrices(this.#http, suffix, kind, signal);\n return formatPriceComparison(comparison, this.config.priceListLimit, locale);\n }\n}\n"]}
|
package/lib/target-guard.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { message, formatMessage } from './messages.js';
|
|
1
2
|
/**
|
|
2
3
|
* Resolve-and-check guard for hosts that will be actively probed (ping).
|
|
3
4
|
*
|
|
@@ -19,7 +20,7 @@ export async function assertPublicTarget(input, resolver, signal) {
|
|
|
19
20
|
const display = displayHost(host);
|
|
20
21
|
const verdict = classifyParsedHost(host);
|
|
21
22
|
if (verdict.blocked) {
|
|
22
|
-
throw new NettoolsError('blocked_target',
|
|
23
|
+
throw new NettoolsError('blocked_target', message('probeBlocked', { host: display, reason: verdict.detail }));
|
|
23
24
|
}
|
|
24
25
|
if (host.kind !== 'domain') {
|
|
25
26
|
return Object.freeze({ host, display, resolvedAddresses: Object.freeze([]) });
|
|
@@ -37,12 +38,12 @@ export async function assertPublicTarget(input, resolver, signal) {
|
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
if (addresses.length === 0) {
|
|
40
|
-
throw new NettoolsError('not_found',
|
|
41
|
+
throw new NettoolsError('not_found', message('noAddresses', { host: display }));
|
|
41
42
|
}
|
|
42
43
|
for (const address of addresses) {
|
|
43
44
|
const addressVerdict = safeClassify(address);
|
|
44
45
|
if (addressVerdict.blocked) {
|
|
45
|
-
throw new NettoolsError('blocked_target',
|
|
46
|
+
throw new NettoolsError('blocked_target', message('resolvedBlocked', { host: display, address, reason: addressVerdict.detail }));
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
49
|
return Object.freeze({ host, display, resolvedAddresses: Object.freeze(addresses) });
|
|
@@ -53,7 +54,8 @@ function safeClassify(address) {
|
|
|
53
54
|
return classifyAddress(address);
|
|
54
55
|
}
|
|
55
56
|
catch {
|
|
56
|
-
|
|
57
|
+
const detail = message('unknownAddress');
|
|
58
|
+
return { blocked: true, reason: formatMessage(detail), detail };
|
|
57
59
|
}
|
|
58
60
|
}
|
|
59
61
|
//# sourceMappingURL=target-guard.js.map
|
package/lib/target-guard.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"target-guard.js","sourceRoot":"","sources":["../src/target-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,WAAW,EACX,cAAc,GAEf,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAU5C,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,KAAa,EACb,QAAqB,EACrB,MAAoB;IAEpB,MAAM,IAAI,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,OAAO,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACzC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,aAAa,CAAC,gBAAgB,EAAE,
|
|
1
|
+
{"version":3,"file":"target-guard.js","sourceRoot":"","sources":["../src/target-guard.ts"],"names":[],"mappings":"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","sourcesContent":["import { message, formatMessage } from './messages.js';\n/**\n * Resolve-and-check guard for hosts that will be actively probed (ping).\n *\n * Literal IPs are classified directly. Hostnames are checked by name, then\n * resolved for both A and AAAA through DoH; the target is refused when ANY\n * returned address is blocked (an attacker controlling a zone could otherwise\n * mix one public and one private record). Resolution failure fails closed.\n *\n * Note on DNS rebinding: the probe itself is executed by check-host.net, which\n * resolves the name again on its own network. Our process never connects to\n * the target, so a rebinding answer can only make check-host.net probe its own\n * neighbourhood, never ours; the pre-check stops casual abuse and obvious\n * internal names.\n */\nimport {\n classifyAddress,\n classifyParsedHost,\n displayHost,\n parseHostInput,\n type ParsedHost,\n} from './address-policy.js';\nimport type { DohResolver } from './dns.js';\nimport { NettoolsError } from './errors.js';\n\nexport interface CheckedTarget {\n readonly host: ParsedHost;\n /** Canonical host string to send to the probing service. */\n readonly display: string;\n /** Addresses the name resolved to (empty for IP literals). */\n readonly resolvedAddresses: readonly string[];\n}\n\nexport async function assertPublicTarget(\n input: string,\n resolver: DohResolver,\n signal?: AbortSignal,\n): Promise<CheckedTarget> {\n const host = parseHostInput(input);\n const display = displayHost(host);\n const verdict = classifyParsedHost(host);\n if (verdict.blocked) {\n throw new NettoolsError('blocked_target', message('probeBlocked', { host: display, reason: verdict.detail }));\n }\n if (host.kind !== 'domain') {\n return Object.freeze({ host, display, resolvedAddresses: Object.freeze([]) });\n }\n\n const [ipv4, ipv6] = await Promise.all([\n resolver.resolve(host.name, 'A', signal),\n resolver.resolve(host.name, 'AAAA', signal),\n ]);\n const addresses: string[] = [];\n for (const answer of [ipv4, ipv6]) {\n for (const record of answer.records) {\n if (record.type === 'A' || record.type === 'AAAA') {\n addresses.push(record.data);\n }\n }\n }\n if (addresses.length === 0) {\n throw new NettoolsError('not_found', message('noAddresses', { host: display }));\n }\n for (const address of addresses) {\n const addressVerdict = safeClassify(address);\n if (addressVerdict.blocked) {\n throw new NettoolsError(\n 'blocked_target',\n message('resolvedBlocked', { host: display, address, reason: addressVerdict.detail }),\n );\n }\n }\n return Object.freeze({ host, display, resolvedAddresses: Object.freeze(addresses) });\n}\n\n/** A record value that is not a parsable IP is treated as blocked (fail closed). */\nfunction safeClassify(address: string): import('./address-policy.js').AddressVerdict {\n try {\n return classifyAddress(address);\n } catch {\n const detail = message('unknownAddress');\n return { blocked: true, reason: formatMessage(detail), detail };\n }\n}\n"]}
|
package/lib/whois.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Locale } from 'zhin.js/i18n';
|
|
1
2
|
import type { HttpClient } from './http.js';
|
|
2
3
|
export declare const IANA_RDAP_BOOTSTRAP_URL = "https://data.iana.org/rdap/dns.json";
|
|
3
4
|
export declare const RDAP_FALLBACK_BASE_URL = "https://rdap.org/";
|
|
@@ -28,4 +29,4 @@ export declare function parseRdapDomain(body: unknown, domain: string, source: s
|
|
|
28
29
|
* registrant name / organisation / e-mail are deliberately dropped.
|
|
29
30
|
*/
|
|
30
31
|
export declare function parseWhoisFallback(body: unknown, domain: string, source: string): WhoisRecord;
|
|
31
|
-
export declare function formatWhoisRecord(record: WhoisRecord): string;
|
|
32
|
+
export declare function formatWhoisRecord(record: WhoisRecord, locale?: Locale): string;
|
package/lib/whois.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { messages } from './presentation.js';
|
|
2
|
+
import { message } from './messages.js';
|
|
1
3
|
/**
|
|
2
4
|
* Domain registration lookup over RDAP (RFC 9082/9083), the JSON successor
|
|
3
5
|
* of WHOIS.
|
|
@@ -80,7 +82,7 @@ export class RdapClient {
|
|
|
80
82
|
if (lastError instanceof NettoolsError) {
|
|
81
83
|
throw lastError;
|
|
82
84
|
}
|
|
83
|
-
throw new NettoolsError('network', '
|
|
85
|
+
throw new NettoolsError('network', message('rdapFailed'), { cause: lastError });
|
|
84
86
|
}
|
|
85
87
|
async #queryWhoisFallback(domain, signal) {
|
|
86
88
|
const url = new URL(WHOIS_FALLBACK_URL);
|
|
@@ -148,7 +150,7 @@ export class RdapClient {
|
|
|
148
150
|
export function parseBootstrap(body) {
|
|
149
151
|
const servers = new Map();
|
|
150
152
|
if (!isRecord(body) || !Array.isArray(body.services)) {
|
|
151
|
-
throw new NettoolsError('malformed', '
|
|
153
|
+
throw new NettoolsError('malformed', message('ianaMalformed'));
|
|
152
154
|
}
|
|
153
155
|
for (const service of body.services) {
|
|
154
156
|
if (!Array.isArray(service) || !Array.isArray(service[0]) || !Array.isArray(service[1])) {
|
|
@@ -168,10 +170,10 @@ export function parseBootstrap(body) {
|
|
|
168
170
|
}
|
|
169
171
|
export function parseRdapDomain(body, domain, source) {
|
|
170
172
|
if (!isRecord(body)) {
|
|
171
|
-
throw new NettoolsError('malformed', '
|
|
173
|
+
throw new NettoolsError('malformed', message('rdapMalformed'));
|
|
172
174
|
}
|
|
173
175
|
if (body.objectClassName !== undefined && body.objectClassName !== 'domain') {
|
|
174
|
-
throw new NettoolsError('malformed', '
|
|
176
|
+
throw new NettoolsError('malformed', message('rdapNotDomain'));
|
|
175
177
|
}
|
|
176
178
|
const name = typeof body.ldhName === 'string' ? body.ldhName.toLowerCase() : domain;
|
|
177
179
|
const events = Array.isArray(body.events) ? body.events : [];
|
|
@@ -215,10 +217,10 @@ export function parseRdapDomain(body, domain, source) {
|
|
|
215
217
|
*/
|
|
216
218
|
export function parseWhoisFallback(body, domain, source) {
|
|
217
219
|
if (!isRecord(body) || !isRecord(body.data)) {
|
|
218
|
-
throw new NettoolsError('malformed', '
|
|
220
|
+
throw new NettoolsError('malformed', message('whoisMalformed'));
|
|
219
221
|
}
|
|
220
222
|
if (typeof body.code === 'number' && body.code !== 200 && body.code !== 0) {
|
|
221
|
-
throw new NettoolsError('http',
|
|
223
|
+
throw new NettoolsError('http', message('whoisError', { code: String(body.code) }));
|
|
222
224
|
}
|
|
223
225
|
const data = body.data;
|
|
224
226
|
const text = (value) => typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
@@ -306,31 +308,31 @@ function unregistered(domain, source) {
|
|
|
306
308
|
source,
|
|
307
309
|
});
|
|
308
310
|
}
|
|
309
|
-
export function formatWhoisRecord(record) {
|
|
311
|
+
export function formatWhoisRecord(record, locale = 'zh-CN') {
|
|
310
312
|
if (!record.registered) {
|
|
311
|
-
return
|
|
313
|
+
return messages(locale, 'noWhois', { domain: record.domain });
|
|
312
314
|
}
|
|
313
|
-
const lines = [
|
|
315
|
+
const lines = [messages(locale, 'whoisHeader', { domain: record.domain, source: record.source })];
|
|
314
316
|
if (record.registrar) {
|
|
315
|
-
lines.push(
|
|
317
|
+
lines.push(messages(locale, 'registrar', { value: record.registrar }));
|
|
316
318
|
}
|
|
317
319
|
if (record.createdAt) {
|
|
318
|
-
lines.push(
|
|
320
|
+
lines.push(messages(locale, 'createdAt', { value: formatDate(record.createdAt) }));
|
|
319
321
|
}
|
|
320
322
|
if (record.expiresAt) {
|
|
321
|
-
lines.push(
|
|
323
|
+
lines.push(messages(locale, 'expiresAt', { value: formatDate(record.expiresAt) }));
|
|
322
324
|
}
|
|
323
325
|
if (record.updatedAt) {
|
|
324
|
-
lines.push(
|
|
326
|
+
lines.push(messages(locale, 'updatedAt', { value: formatDate(record.updatedAt) }));
|
|
325
327
|
}
|
|
326
328
|
if (record.statuses.length > 0) {
|
|
327
|
-
lines.push(
|
|
329
|
+
lines.push(messages(locale, 'status', { value: record.statuses.join(', ') }));
|
|
328
330
|
}
|
|
329
331
|
if (record.nameservers.length > 0) {
|
|
330
|
-
lines.push(
|
|
332
|
+
lines.push(messages(locale, 'nameservers', { value: record.nameservers.join(', ') }));
|
|
331
333
|
}
|
|
332
334
|
if (record.dnssec !== undefined) {
|
|
333
|
-
lines.push(
|
|
335
|
+
lines.push(messages(locale, 'dnssec', { value: messages(locale, record.dnssec ? 'enabled' : 'disabled') }));
|
|
334
336
|
}
|
|
335
337
|
return lines.join('\n');
|
|
336
338
|
}
|
package/lib/whois.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"whois.js","sourceRoot":"","sources":["../src/whois.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Domain registration lookup over RDAP (RFC 9082/9083), the JSON successor\n * of WHOIS.\n *\n * 1. The IANA bootstrap registry (`data.iana.org/rdap/dns.json`) maps each TLD\n * to its registry's RDAP base URL. It is cached in memory (default 24 h).\n * Only `https://` bases are used.\n * 2. If the bootstrap is unavailable, lacks the TLD or the registry server\n * fails, we fall back to the public redirector `rdap.org`.\n * 3. Last resort: the 60s API WHOIS endpoint (`60s.viki.moe/v2/whois`), which\n * also covers TLDs without RDAP such as `.cn`. Registrant personal data in\n * that answer is ignored on purpose.\n * 4. HTTP 404 from the TLD's own registry is authoritative (\"not\n * registered\"). A 404 from rdap.org is not (the TLD may simply have no\n * RDAP), so the WHOIS fallback still runs.\n * 5. For names with more than two labels (e.g. `www.example.com`) that are\n * not registered we retry once with the leftmost label removed.\n */\nimport { NettoolsError } from './errors.js';\nimport type { HttpClient } from './http.js';\n\nexport const IANA_RDAP_BOOTSTRAP_URL = 'https://data.iana.org/rdap/dns.json';\nexport const RDAP_FALLBACK_BASE_URL = 'https://rdap.org/';\nexport const WHOIS_FALLBACK_URL = 'https://60s.viki.moe/v2/whois';\nexport const DEFAULT_BOOTSTRAP_TTL_MS = 24 * 60 * 60 * 1000;\nconst RDAP_ACCEPT = 'application/rdap+json, application/json;q=0.9';\n\nexport interface WhoisRecord {\n readonly domain: string;\n readonly registered: boolean;\n readonly registrar: string | undefined;\n readonly statuses: readonly string[];\n readonly createdAt: string | undefined;\n readonly expiresAt: string | undefined;\n readonly updatedAt: string | undefined;\n readonly nameservers: readonly string[];\n readonly dnssec: boolean | undefined;\n readonly source: string;\n}\n\ninterface BootstrapCache {\n readonly fetchedAt: number;\n readonly servers: ReadonlyMap<string, readonly string[]>;\n}\n\nexport class RdapClient {\n readonly #http: HttpClient;\n readonly #bootstrapTtlMs: number;\n #cache: BootstrapCache | undefined;\n\n constructor(http: HttpClient, bootstrapTtlMs = DEFAULT_BOOTSTRAP_TTL_MS) {\n this.#http = http;\n this.#bootstrapTtlMs = bootstrapTtlMs;\n }\n\n async lookup(domain: string, signal?: AbortSignal): Promise<WhoisRecord> {\n const first = await this.#lookupExact(domain, signal);\n if (first.registered) {\n return first;\n }\n const labels = domain.split('.');\n if (labels.length <= 2) {\n return first;\n }\n const parent = labels.slice(1).join('.');\n const second = await this.#lookupExact(parent, signal);\n return second.registered ? second : first;\n }\n\n async #lookupExact(domain: string, signal?: AbortSignal): Promise<WhoisRecord> {\n const failures: unknown[] = [];\n const registryBases = await this.#registryBases(domain, signal);\n for (const base of registryBases) {\n try {\n return await this.#query(base, domain, signal);\n } catch (error) {\n rethrowIfAborted(error);\n failures.push(error);\n }\n }\n\n let rdapOrgAnswer: WhoisRecord | undefined;\n try {\n rdapOrgAnswer = await this.#query(RDAP_FALLBACK_BASE_URL, domain, signal);\n if (rdapOrgAnswer.registered) {\n return rdapOrgAnswer;\n }\n } catch (error) {\n rethrowIfAborted(error);\n failures.push(error);\n }\n\n try {\n return await this.#queryWhoisFallback(domain, signal);\n } catch (error) {\n rethrowIfAborted(error);\n failures.push(error);\n }\n\n if (rdapOrgAnswer) {\n return rdapOrgAnswer;\n }\n const lastError = failures[failures.length - 1];\n if (lastError instanceof NettoolsError) {\n throw lastError;\n }\n throw new NettoolsError('network', 'RDAP / WHOIS 查询失败', { cause: lastError });\n }\n\n async #queryWhoisFallback(domain: string, signal?: AbortSignal): Promise<WhoisRecord> {\n const url = new URL(WHOIS_FALLBACK_URL);\n url.searchParams.set('domain', domain);\n const response = await this.#http.fetchJson(url, {\n provider: 'WHOIS (60s.viki.moe)',\n ...(signal ? { signal } : {}),\n });\n return parseWhoisFallback(response.body, domain, url.hostname);\n }\n\n async #query(base: string, domain: string, signal?: AbortSignal): Promise<WhoisRecord> {\n const baseUrl = new URL(base.endsWith('/') ? base : `${base}/`);\n const url = new URL(`domain/${encodeURIComponent(domain)}`, baseUrl);\n const response = await this.#http.fetchJson(url, {\n provider: `RDAP (${baseUrl.hostname})`,\n accept: RDAP_ACCEPT,\n passStatuses: [404],\n ...(signal ? { signal } : {}),\n });\n if (response.status === 404) {\n return unregistered(domain, baseUrl.hostname);\n }\n return parseRdapDomain(response.body, domain, baseUrl.hostname);\n }\n\n /** RDAP base URLs for the domain's TLD; empty when the bootstrap is unavailable. */\n async #registryBases(domain: string, signal?: AbortSignal): Promise<readonly string[]> {\n const servers = await this.#bootstrap(signal);\n if (!servers) {\n return [];\n }\n const labels = domain.split('.');\n for (let index = 0; index < labels.length; index += 1) {\n const suffix = labels.slice(index).join('.');\n const match = servers.get(suffix);\n if (match) {\n return match;\n }\n }\n return [];\n }\n\n async #bootstrap(signal?: AbortSignal): Promise<ReadonlyMap<string, readonly string[]> | undefined> {\n const now = this.#http.dependencies.now();\n if (this.#cache && now - this.#cache.fetchedAt < this.#bootstrapTtlMs) {\n return this.#cache.servers;\n }\n try {\n const response = await this.#http.fetchJson(new URL(IANA_RDAP_BOOTSTRAP_URL), {\n provider: 'IANA RDAP bootstrap',\n ...(signal ? { signal } : {}),\n });\n const servers = parseBootstrap(response.body);\n this.#cache = { fetchedAt: now, servers };\n return servers;\n } catch (error) {\n if (error instanceof NettoolsError && error.kind === 'aborted') {\n throw error;\n }\n // Stale data beats no data; otherwise fall back to rdap.org.\n return this.#cache?.servers;\n }\n }\n}\n\n/** Parse the IANA bootstrap file into suffix → https base URLs. */\nexport function parseBootstrap(body: unknown): ReadonlyMap<string, readonly string[]> {\n const servers = new Map<string, readonly string[]>();\n if (!isRecord(body) || !Array.isArray(body.services)) {\n throw new NettoolsError('malformed', 'IANA RDAP 引导数据格式异常');\n }\n for (const service of body.services) {\n if (!Array.isArray(service) || !Array.isArray(service[0]) || !Array.isArray(service[1])) {\n continue;\n }\n const urls = service[1].filter(\n (value: unknown): value is string => typeof value === 'string' && value.startsWith('https://'),\n );\n if (urls.length === 0) {\n continue;\n }\n for (const suffix of service[0]) {\n if (typeof suffix === 'string' && suffix.length > 0) {\n servers.set(suffix.toLowerCase(), Object.freeze(urls));\n }\n }\n }\n return servers;\n}\n\nexport function parseRdapDomain(body: unknown, domain: string, source: string): WhoisRecord {\n if (!isRecord(body)) {\n throw new NettoolsError('malformed', 'RDAP 返回了无法识别的数据');\n }\n if (body.objectClassName !== undefined && body.objectClassName !== 'domain') {\n throw new NettoolsError('malformed', 'RDAP 返回的不是域名对象');\n }\n const name = typeof body.ldhName === 'string' ? body.ldhName.toLowerCase() : domain;\n const events = Array.isArray(body.events) ? body.events : [];\n const eventDate = (action: string): string | undefined => {\n for (const event of events) {\n if (isRecord(event) && event.eventAction === action && typeof event.eventDate === 'string') {\n return event.eventDate;\n }\n }\n return undefined;\n };\n const statuses = Array.isArray(body.status)\n ? body.status.filter((value: unknown): value is string => typeof value === 'string')\n : [];\n const nameservers: string[] = [];\n if (Array.isArray(body.nameservers)) {\n for (const nameserver of body.nameservers) {\n if (isRecord(nameserver) && typeof nameserver.ldhName === 'string') {\n nameservers.push(nameserver.ldhName.toLowerCase());\n }\n }\n }\n const secureDns = isRecord(body.secureDNS) ? body.secureDNS : undefined;\n const dnssec = typeof secureDns?.delegationSigned === 'boolean' ? secureDns.delegationSigned : undefined;\n return Object.freeze({\n domain: name,\n registered: true,\n registrar: findRegistrarName(body.entities),\n statuses: Object.freeze(statuses),\n createdAt: eventDate('registration'),\n expiresAt: eventDate('expiration'),\n updatedAt: eventDate('last changed'),\n nameservers: Object.freeze(nameservers),\n dnssec,\n source,\n });\n}\n\n/**\n * Parse the 60s API WHOIS answer. Only registry-level facts are kept;\n * registrant name / organisation / e-mail are deliberately dropped.\n */\nexport function parseWhoisFallback(body: unknown, domain: string, source: string): WhoisRecord {\n if (!isRecord(body) || !isRecord(body.data)) {\n throw new NettoolsError('malformed', 'WHOIS 服务返回了无法识别的数据');\n }\n if (typeof body.code === 'number' && body.code !== 200 && body.code !== 0) {\n throw new NettoolsError('http', `WHOIS 服务返回错误 ${body.code}`);\n }\n const data = body.data;\n const text = (value: unknown): string | undefined =>\n typeof value === 'string' && value.trim() ? value.trim() : undefined;\n const statuses = Array.isArray(data.status)\n ? data.status.filter((value: unknown): value is string => typeof value === 'string')\n : [];\n const nameservers = Array.isArray(data.nameservers)\n ? data.nameservers.filter((value: unknown): value is string => typeof value === 'string').map((value) => value.toLowerCase())\n : [];\n const registrar = text(data.registrar);\n const createdAt = epochToIso(data.created_at) ?? text(data.created);\n const expiresAt = epochToIso(data.expires_at) ?? text(data.expires);\n const updatedAt = epochToIso(data.updated_at) ?? text(data.updated);\n const registered = Boolean(registrar || createdAt || expiresAt || statuses.length > 0 || nameservers.length > 0);\n if (!registered) {\n return unregistered(domain, source);\n }\n const dnssecText = text(data.dnssec)?.toLowerCase();\n const dnssec = dnssecText === undefined ? undefined : dnssecText !== 'unsigned' && dnssecText !== 'no';\n return Object.freeze({\n domain: (text(data.domain) ?? domain).toLowerCase(),\n registered: true,\n registrar,\n statuses: Object.freeze(statuses),\n createdAt,\n expiresAt,\n updatedAt,\n nameservers: Object.freeze(nameservers),\n dnssec,\n source,\n });\n}\n\nfunction epochToIso(value: unknown): string | undefined {\n if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {\n return undefined;\n }\n return new Date(value).toISOString();\n}\n\nfunction rethrowIfAborted(error: unknown): void {\n if (error instanceof NettoolsError && error.kind === 'aborted') {\n throw error;\n }\n}\n\nfunction findRegistrarName(entities: unknown): string | undefined {\n if (!Array.isArray(entities)) {\n return undefined;\n }\n for (const entity of entities) {\n if (!isRecord(entity) || !Array.isArray(entity.roles) || !entity.roles.includes('registrar')) {\n continue;\n }\n const fullName = readVcardFullName(entity.vcardArray);\n if (fullName) {\n return fullName;\n }\n if (typeof entity.handle === 'string') {\n return entity.handle;\n }\n }\n return undefined;\n}\n\n/** jCard: [\"vcard\", [[\"fn\", {}, \"text\", \"Name\"], …]] */\nfunction readVcardFullName(vcard: unknown): string | undefined {\n if (!Array.isArray(vcard) || !Array.isArray(vcard[1])) {\n return undefined;\n }\n for (const property of vcard[1]) {\n if (Array.isArray(property) && property[0] === 'fn' && typeof property[3] === 'string' && property[3].trim()) {\n return property[3].trim();\n }\n }\n return undefined;\n}\n\nfunction unregistered(domain: string, source: string): WhoisRecord {\n return Object.freeze({\n domain,\n registered: false,\n registrar: undefined,\n statuses: Object.freeze([]),\n createdAt: undefined,\n expiresAt: undefined,\n updatedAt: undefined,\n nameservers: Object.freeze([]),\n dnssec: undefined,\n source,\n });\n}\n\nexport function formatWhoisRecord(record: WhoisRecord): string {\n if (!record.registered) {\n return `📇 ${record.domain}\\n未查询到注册信息(可能未注册,或该后缀未提供 RDAP)`;\n }\n const lines = [`📇 ${record.domain} 注册信息(来源:${record.source})`];\n if (record.registrar) {\n lines.push(`注册商:${record.registrar}`);\n }\n if (record.createdAt) {\n lines.push(`注册时间:${formatDate(record.createdAt)}`);\n }\n if (record.expiresAt) {\n lines.push(`到期时间:${formatDate(record.expiresAt)}`);\n }\n if (record.updatedAt) {\n lines.push(`最近更新:${formatDate(record.updatedAt)}`);\n }\n if (record.statuses.length > 0) {\n lines.push(`状态:${record.statuses.join(', ')}`);\n }\n if (record.nameservers.length > 0) {\n lines.push(`DNS 服务器:${record.nameservers.join(', ')}`);\n }\n if (record.dnssec !== undefined) {\n lines.push(`DNSSEC:${record.dnssec ? '已启用' : '未启用'}`);\n }\n return lines.join('\\n');\n}\n\n/** Keep the date part of an ISO timestamp (UTC); leave anything else untouched. */\nfunction formatDate(value: string): string {\n const match = /^(\\d{4}-\\d{2}-\\d{2})T/u.exec(value);\n return match ? `${match[1]} (UTC)` : value;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"whois.js","sourceRoot":"","sources":["../src/whois.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Locale } from 'zhin.js/i18n';\nimport { messages } from './presentation.js';\nimport { message } from './messages.js';\n/**\n * Domain registration lookup over RDAP (RFC 9082/9083), the JSON successor\n * of WHOIS.\n *\n * 1. The IANA bootstrap registry (`data.iana.org/rdap/dns.json`) maps each TLD\n * to its registry's RDAP base URL. It is cached in memory (default 24 h).\n * Only `https://` bases are used.\n * 2. If the bootstrap is unavailable, lacks the TLD or the registry server\n * fails, we fall back to the public redirector `rdap.org`.\n * 3. Last resort: the 60s API WHOIS endpoint (`60s.viki.moe/v2/whois`), which\n * also covers TLDs without RDAP such as `.cn`. Registrant personal data in\n * that answer is ignored on purpose.\n * 4. HTTP 404 from the TLD's own registry is authoritative (\"not\n * registered\"). A 404 from rdap.org is not (the TLD may simply have no\n * RDAP), so the WHOIS fallback still runs.\n * 5. For names with more than two labels (e.g. `www.example.com`) that are\n * not registered we retry once with the leftmost label removed.\n */\nimport { NettoolsError } from './errors.js';\nimport type { HttpClient } from './http.js';\n\nexport const IANA_RDAP_BOOTSTRAP_URL = 'https://data.iana.org/rdap/dns.json';\nexport const RDAP_FALLBACK_BASE_URL = 'https://rdap.org/';\nexport const WHOIS_FALLBACK_URL = 'https://60s.viki.moe/v2/whois';\nexport const DEFAULT_BOOTSTRAP_TTL_MS = 24 * 60 * 60 * 1000;\nconst RDAP_ACCEPT = 'application/rdap+json, application/json;q=0.9';\n\nexport interface WhoisRecord {\n readonly domain: string;\n readonly registered: boolean;\n readonly registrar: string | undefined;\n readonly statuses: readonly string[];\n readonly createdAt: string | undefined;\n readonly expiresAt: string | undefined;\n readonly updatedAt: string | undefined;\n readonly nameservers: readonly string[];\n readonly dnssec: boolean | undefined;\n readonly source: string;\n}\n\ninterface BootstrapCache {\n readonly fetchedAt: number;\n readonly servers: ReadonlyMap<string, readonly string[]>;\n}\n\nexport class RdapClient {\n readonly #http: HttpClient;\n readonly #bootstrapTtlMs: number;\n #cache: BootstrapCache | undefined;\n\n constructor(http: HttpClient, bootstrapTtlMs = DEFAULT_BOOTSTRAP_TTL_MS) {\n this.#http = http;\n this.#bootstrapTtlMs = bootstrapTtlMs;\n }\n\n async lookup(domain: string, signal?: AbortSignal): Promise<WhoisRecord> {\n const first = await this.#lookupExact(domain, signal);\n if (first.registered) {\n return first;\n }\n const labels = domain.split('.');\n if (labels.length <= 2) {\n return first;\n }\n const parent = labels.slice(1).join('.');\n const second = await this.#lookupExact(parent, signal);\n return second.registered ? second : first;\n }\n\n async #lookupExact(domain: string, signal?: AbortSignal): Promise<WhoisRecord> {\n const failures: unknown[] = [];\n const registryBases = await this.#registryBases(domain, signal);\n for (const base of registryBases) {\n try {\n return await this.#query(base, domain, signal);\n } catch (error) {\n rethrowIfAborted(error);\n failures.push(error);\n }\n }\n\n let rdapOrgAnswer: WhoisRecord | undefined;\n try {\n rdapOrgAnswer = await this.#query(RDAP_FALLBACK_BASE_URL, domain, signal);\n if (rdapOrgAnswer.registered) {\n return rdapOrgAnswer;\n }\n } catch (error) {\n rethrowIfAborted(error);\n failures.push(error);\n }\n\n try {\n return await this.#queryWhoisFallback(domain, signal);\n } catch (error) {\n rethrowIfAborted(error);\n failures.push(error);\n }\n\n if (rdapOrgAnswer) {\n return rdapOrgAnswer;\n }\n const lastError = failures[failures.length - 1];\n if (lastError instanceof NettoolsError) {\n throw lastError;\n }\n throw new NettoolsError('network', message('rdapFailed'), { cause: lastError });\n }\n\n async #queryWhoisFallback(domain: string, signal?: AbortSignal): Promise<WhoisRecord> {\n const url = new URL(WHOIS_FALLBACK_URL);\n url.searchParams.set('domain', domain);\n const response = await this.#http.fetchJson(url, {\n provider: 'WHOIS (60s.viki.moe)',\n ...(signal ? { signal } : {}),\n });\n return parseWhoisFallback(response.body, domain, url.hostname);\n }\n\n async #query(base: string, domain: string, signal?: AbortSignal): Promise<WhoisRecord> {\n const baseUrl = new URL(base.endsWith('/') ? base : `${base}/`);\n const url = new URL(`domain/${encodeURIComponent(domain)}`, baseUrl);\n const response = await this.#http.fetchJson(url, {\n provider: `RDAP (${baseUrl.hostname})`,\n accept: RDAP_ACCEPT,\n passStatuses: [404],\n ...(signal ? { signal } : {}),\n });\n if (response.status === 404) {\n return unregistered(domain, baseUrl.hostname);\n }\n return parseRdapDomain(response.body, domain, baseUrl.hostname);\n }\n\n /** RDAP base URLs for the domain's TLD; empty when the bootstrap is unavailable. */\n async #registryBases(domain: string, signal?: AbortSignal): Promise<readonly string[]> {\n const servers = await this.#bootstrap(signal);\n if (!servers) {\n return [];\n }\n const labels = domain.split('.');\n for (let index = 0; index < labels.length; index += 1) {\n const suffix = labels.slice(index).join('.');\n const match = servers.get(suffix);\n if (match) {\n return match;\n }\n }\n return [];\n }\n\n async #bootstrap(signal?: AbortSignal): Promise<ReadonlyMap<string, readonly string[]> | undefined> {\n const now = this.#http.dependencies.now();\n if (this.#cache && now - this.#cache.fetchedAt < this.#bootstrapTtlMs) {\n return this.#cache.servers;\n }\n try {\n const response = await this.#http.fetchJson(new URL(IANA_RDAP_BOOTSTRAP_URL), {\n provider: 'IANA RDAP bootstrap',\n ...(signal ? { signal } : {}),\n });\n const servers = parseBootstrap(response.body);\n this.#cache = { fetchedAt: now, servers };\n return servers;\n } catch (error) {\n if (error instanceof NettoolsError && error.kind === 'aborted') {\n throw error;\n }\n // Stale data beats no data; otherwise fall back to rdap.org.\n return this.#cache?.servers;\n }\n }\n}\n\n/** Parse the IANA bootstrap file into suffix → https base URLs. */\nexport function parseBootstrap(body: unknown): ReadonlyMap<string, readonly string[]> {\n const servers = new Map<string, readonly string[]>();\n if (!isRecord(body) || !Array.isArray(body.services)) {\n throw new NettoolsError('malformed', message('ianaMalformed'));\n }\n for (const service of body.services) {\n if (!Array.isArray(service) || !Array.isArray(service[0]) || !Array.isArray(service[1])) {\n continue;\n }\n const urls = service[1].filter(\n (value: unknown): value is string => typeof value === 'string' && value.startsWith('https://'),\n );\n if (urls.length === 0) {\n continue;\n }\n for (const suffix of service[0]) {\n if (typeof suffix === 'string' && suffix.length > 0) {\n servers.set(suffix.toLowerCase(), Object.freeze(urls));\n }\n }\n }\n return servers;\n}\n\nexport function parseRdapDomain(body: unknown, domain: string, source: string): WhoisRecord {\n if (!isRecord(body)) {\n throw new NettoolsError('malformed', message('rdapMalformed'));\n }\n if (body.objectClassName !== undefined && body.objectClassName !== 'domain') {\n throw new NettoolsError('malformed', message('rdapNotDomain'));\n }\n const name = typeof body.ldhName === 'string' ? body.ldhName.toLowerCase() : domain;\n const events = Array.isArray(body.events) ? body.events : [];\n const eventDate = (action: string): string | undefined => {\n for (const event of events) {\n if (isRecord(event) && event.eventAction === action && typeof event.eventDate === 'string') {\n return event.eventDate;\n }\n }\n return undefined;\n };\n const statuses = Array.isArray(body.status)\n ? body.status.filter((value: unknown): value is string => typeof value === 'string')\n : [];\n const nameservers: string[] = [];\n if (Array.isArray(body.nameservers)) {\n for (const nameserver of body.nameservers) {\n if (isRecord(nameserver) && typeof nameserver.ldhName === 'string') {\n nameservers.push(nameserver.ldhName.toLowerCase());\n }\n }\n }\n const secureDns = isRecord(body.secureDNS) ? body.secureDNS : undefined;\n const dnssec = typeof secureDns?.delegationSigned === 'boolean' ? secureDns.delegationSigned : undefined;\n return Object.freeze({\n domain: name,\n registered: true,\n registrar: findRegistrarName(body.entities),\n statuses: Object.freeze(statuses),\n createdAt: eventDate('registration'),\n expiresAt: eventDate('expiration'),\n updatedAt: eventDate('last changed'),\n nameservers: Object.freeze(nameservers),\n dnssec,\n source,\n });\n}\n\n/**\n * Parse the 60s API WHOIS answer. Only registry-level facts are kept;\n * registrant name / organisation / e-mail are deliberately dropped.\n */\nexport function parseWhoisFallback(body: unknown, domain: string, source: string): WhoisRecord {\n if (!isRecord(body) || !isRecord(body.data)) {\n throw new NettoolsError('malformed', message('whoisMalformed'));\n }\n if (typeof body.code === 'number' && body.code !== 200 && body.code !== 0) {\n throw new NettoolsError('http', message('whoisError', { code: String(body.code) }));\n }\n const data = body.data;\n const text = (value: unknown): string | undefined =>\n typeof value === 'string' && value.trim() ? value.trim() : undefined;\n const statuses = Array.isArray(data.status)\n ? data.status.filter((value: unknown): value is string => typeof value === 'string')\n : [];\n const nameservers = Array.isArray(data.nameservers)\n ? data.nameservers.filter((value: unknown): value is string => typeof value === 'string').map((value) => value.toLowerCase())\n : [];\n const registrar = text(data.registrar);\n const createdAt = epochToIso(data.created_at) ?? text(data.created);\n const expiresAt = epochToIso(data.expires_at) ?? text(data.expires);\n const updatedAt = epochToIso(data.updated_at) ?? text(data.updated);\n const registered = Boolean(registrar || createdAt || expiresAt || statuses.length > 0 || nameservers.length > 0);\n if (!registered) {\n return unregistered(domain, source);\n }\n const dnssecText = text(data.dnssec)?.toLowerCase();\n const dnssec = dnssecText === undefined ? undefined : dnssecText !== 'unsigned' && dnssecText !== 'no';\n return Object.freeze({\n domain: (text(data.domain) ?? domain).toLowerCase(),\n registered: true,\n registrar,\n statuses: Object.freeze(statuses),\n createdAt,\n expiresAt,\n updatedAt,\n nameservers: Object.freeze(nameservers),\n dnssec,\n source,\n });\n}\n\nfunction epochToIso(value: unknown): string | undefined {\n if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {\n return undefined;\n }\n return new Date(value).toISOString();\n}\n\nfunction rethrowIfAborted(error: unknown): void {\n if (error instanceof NettoolsError && error.kind === 'aborted') {\n throw error;\n }\n}\n\nfunction findRegistrarName(entities: unknown): string | undefined {\n if (!Array.isArray(entities)) {\n return undefined;\n }\n for (const entity of entities) {\n if (!isRecord(entity) || !Array.isArray(entity.roles) || !entity.roles.includes('registrar')) {\n continue;\n }\n const fullName = readVcardFullName(entity.vcardArray);\n if (fullName) {\n return fullName;\n }\n if (typeof entity.handle === 'string') {\n return entity.handle;\n }\n }\n return undefined;\n}\n\n/** jCard: [\"vcard\", [[\"fn\", {}, \"text\", \"Name\"], …]] */\nfunction readVcardFullName(vcard: unknown): string | undefined {\n if (!Array.isArray(vcard) || !Array.isArray(vcard[1])) {\n return undefined;\n }\n for (const property of vcard[1]) {\n if (Array.isArray(property) && property[0] === 'fn' && typeof property[3] === 'string' && property[3].trim()) {\n return property[3].trim();\n }\n }\n return undefined;\n}\n\nfunction unregistered(domain: string, source: string): WhoisRecord {\n return Object.freeze({\n domain,\n registered: false,\n registrar: undefined,\n statuses: Object.freeze([]),\n createdAt: undefined,\n expiresAt: undefined,\n updatedAt: undefined,\n nameservers: Object.freeze([]),\n dnssec: undefined,\n source,\n });\n}\n\nexport function formatWhoisRecord(record: WhoisRecord, locale: Locale = 'zh-CN'): string {\n if (!record.registered) {\n return messages(locale, 'noWhois', { domain: record.domain });\n }\n const lines = [messages(locale, 'whoisHeader', { domain: record.domain, source: record.source })];\n if (record.registrar) {\n lines.push(messages(locale, 'registrar', { value: record.registrar }));\n }\n if (record.createdAt) {\n lines.push(messages(locale, 'createdAt', { value: formatDate(record.createdAt) }));\n }\n if (record.expiresAt) {\n lines.push(messages(locale, 'expiresAt', { value: formatDate(record.expiresAt) }));\n }\n if (record.updatedAt) {\n lines.push(messages(locale, 'updatedAt', { value: formatDate(record.updatedAt) }));\n }\n if (record.statuses.length > 0) {\n lines.push(messages(locale, 'status', { value: record.statuses.join(', ') }));\n }\n if (record.nameservers.length > 0) {\n lines.push(messages(locale, 'nameservers', { value: record.nameservers.join(', ') }));\n }\n if (record.dnssec !== undefined) {\n lines.push(messages(locale, 'dnssec', { value: messages(locale, record.dnssec ? 'enabled' : 'disabled') }));\n }\n return lines.join('\\n');\n}\n\n/** Keep the date part of an ISO timestamp (UTC); leave anything else untouched. */\nfunction formatDate(value: string): string {\n const match = /^(\\d{4}-\\d{2}-\\d{2})T/u.exec(value);\n return match ? `${match[1]} (UTC)` : value;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/plugin-nettools",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Network tools plugin for Zhin.js — multi-region ping, RDAP whois, DNS-over-HTTPS lookup and domain price comparison (Plugin Runtime)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -42,20 +42,20 @@
|
|
|
42
42
|
},
|
|
43
43
|
"license": "MIT",
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@zhin.js/skill": "1.1.
|
|
46
|
-
"@zhin.js/tool": "1.1.
|
|
45
|
+
"@zhin.js/skill": "1.1.10",
|
|
46
|
+
"@zhin.js/tool": "1.1.10"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"typescript": "^6.0.3",
|
|
50
50
|
"vitest": "^4.1.10",
|
|
51
51
|
"zod": "^4.4.3",
|
|
52
|
-
"@zhin.js/command": "1.1.
|
|
53
|
-
"zhin.js": "1.1.
|
|
52
|
+
"@zhin.js/command": "1.1.11",
|
|
53
|
+
"zhin.js": "1.1.16"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"zod": "^4.0.0",
|
|
57
|
-
"@zhin.js/command": "^1.1.
|
|
58
|
-
"zhin.js": "^1.1.
|
|
57
|
+
"@zhin.js/command": "^1.1.11",
|
|
58
|
+
"zhin.js": "^1.1.16"
|
|
59
59
|
},
|
|
60
60
|
"peerDependenciesMeta": {
|
|
61
61
|
"@zhin.js/command": {
|
|
@@ -15,6 +15,6 @@ export default defineAgentTool({
|
|
|
15
15
|
async execute({ name, type }, context) {
|
|
16
16
|
const service = context.use(nettoolsServiceToken);
|
|
17
17
|
const callerKey = toolCallerKey(context);
|
|
18
|
-
return service.run(() => service.dns(name, type, callerKey, context.signal));
|
|
18
|
+
return service.run(() => service.dns(name, type, callerKey, context.signal, context.locale), context.locale);
|
|
19
19
|
},
|
|
20
20
|
});
|
|
@@ -14,6 +14,6 @@ export default defineAgentTool({
|
|
|
14
14
|
async execute({ suffix, order }, context) {
|
|
15
15
|
const service = context.use(nettoolsServiceToken);
|
|
16
16
|
const callerKey = toolCallerKey(context);
|
|
17
|
-
return service.run(() => service.domainPrice(suffix, order, callerKey, context.signal));
|
|
17
|
+
return service.run(() => service.domainPrice(suffix, order, callerKey, context.signal, context.locale), context.locale);
|
|
18
18
|
},
|
|
19
19
|
});
|
|
@@ -11,6 +11,6 @@ export default defineAgentTool({
|
|
|
11
11
|
async execute({ host }, context) {
|
|
12
12
|
const service = context.use(nettoolsServiceToken);
|
|
13
13
|
const callerKey = toolCallerKey(context);
|
|
14
|
-
return service.run(() => service.ping(host, callerKey, context.signal));
|
|
14
|
+
return service.run(() => service.ping(host, callerKey, context.signal, context.locale), context.locale);
|
|
15
15
|
},
|
|
16
16
|
});
|
|
@@ -11,6 +11,6 @@ export default defineAgentTool({
|
|
|
11
11
|
async execute({ domain }, context) {
|
|
12
12
|
const service = context.use(nettoolsServiceToken);
|
|
13
13
|
const callerKey = toolCallerKey(context);
|
|
14
|
-
return service.run(() => service.whois(domain, callerKey, context.signal));
|
|
14
|
+
return service.run(() => service.whois(domain, callerKey, context.signal, context.locale), context.locale);
|
|
15
15
|
},
|
|
16
16
|
});
|