From 874e1952e3fc1a7ee5f76b853be0b6f2f065c3b4 Mon Sep 17 00:00:00 2001 From: desu-bot Date: Tue, 25 Mar 2025 02:39:46 +0000 Subject: [PATCH] chore: update public repo --- utils/fetch.ts | 1 + utils/whoisxmlapi.ts | 109 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 utils/whoisxmlapi.ts diff --git a/utils/fetch.ts b/utils/fetch.ts index bd33dee..f892306 100644 --- a/utils/fetch.ts +++ b/utils/fetch.ts @@ -21,6 +21,7 @@ const cheerioAddon: FfetchAddon Promise }> export const ffetch = ffetchBase.extend({ addons: [ ffetchAddons.parser(ffetchZodAdapter()), + ffetchAddons.rateLimitHandler(), cheerioAddon, toughCookieAddon(), ], diff --git a/utils/whoisxmlapi.ts b/utils/whoisxmlapi.ts new file mode 100644 index 0000000..412ea2c --- /dev/null +++ b/utils/whoisxmlapi.ts @@ -0,0 +1,109 @@ +import { sleep } from '@fuman/utils' +import { z } from 'zod' +import { ffetch } from './fetch.ts' +import { getEnv } from './misc.ts' + +// https://whois.whoisxmlapi.com/documentation/output +// not all fields are present currently +const WhoisResultSchema = z.object({ + registrant: z.object({ + organization: z.string(), + name: z.string(), + email: z.string(), + }).partial().optional(), + administrativeContact: z.object({ + organization: z.string(), + name: z.string(), + email: z.string(), + }).partial().optional(), + technicalContact: z.object({ + organization: z.string(), + name: z.string(), + email: z.string(), + }).partial().optional(), + registryData: z.object({ + registrant: z.object({ + organization: z.string(), + name: z.string(), + email: z.string(), + }).partial().optional(), + registrarName: z.string(), + createdDate: z.string(), + updatedDate: z.string(), + expiresDate: z.string(), + }).partial().optional(), + registrarName: z.string().optional(), + createdDate: z.string().optional(), + updatedDate: z.string().optional(), + expiresDate: z.string().optional(), +}) +export type WhoisResult = z.infer + +const WhoisWrapSchema = z.object({ + domainName: z.string(), + domainStatus: z.enum(['I', 'N']), + whoisRecord: WhoisResultSchema.optional(), +}) + +export async function bulkWhois(domains: string[]) { + const res = await ffetch.post('https://www.whoisxmlapi.com/BulkWhoisLookup/bulkServices/bulkWhois', { + json: { + apiKey: getEnv('WHOISXMLAPI_TOKEN'), + domains, + outputFormat: 'JSON', + }, + }).parsedJson(z.object({ + requestId: z.string(), + })) + + while (true) { + const res2 = await ffetch.post('https://www.whoisxmlapi.com/BulkWhoisLookup/bulkServices/getRecords', { + json: { + apiKey: getEnv('WHOISXMLAPI_KEY'), + requestId: res.requestId, + outputFormat: 'JSON', + maxRecords: 1, + }, + }).parsedJson(z.object({ + recordsLeft: z.number(), + })) + + if (res2.recordsLeft !== 0) { + await sleep(1000) + continue + } + + break + } + + const result = new Map() + const finalRes = await ffetch.post('https://www.whoisxmlapi.com/BulkWhoisLookup/bulkServices/getRecords', { + json: { + apiKey: getEnv('WHOISXMLAPI_KEY'), + requestId: res.requestId, + outputFormat: 'JSON', + }, + }).parsedJson(z.object({ + whoisRecords: z.array(WhoisWrapSchema), + })) + + for (const record of finalRes.whoisRecords) { + result.set(record.domainName, record.domainStatus === 'I' ? record.whoisRecord ?? null : null) + } + + return result +} + +export async function whois(domain: string) { + const res = await ffetch.post('https://www.whoisxmlapi.com/whoisserver/WhoisService', { + json: { + domainName: domain, + outputFormat: 'JSON', + apiKey: getEnv('WHOISXMLAPI_TOKEN'), + }, + }).parsedJson(z.object({ + WhoisRecord: WhoisResultSchema.optional(), + })) + + return res.WhoisRecord ?? null +}