export = urlMetadata // Definitions are loose by default so arbitrary scraped meta tags // never fight the compiler: default results are `Result` (≈ any). // Opt into `KnownFields` / `KnownFieldsStrict` for structure; // `fields` or `omitEmpty: true` options auto-narrow to Partial. // Types only — nothing here changes runtime behavior. // Definition overloads resolve top-to-bottom. // A sparse selection (`fields`, or `omitEmpty: true` options) drops // the default "always present" guarantee, so the Result is typed: // `Partial` declare function urlMetadata( url: string | null, options: urlMetadata.Options & { fields: string[] }, ): Promise> declare function urlMetadata( url: string | null, options: urlMetadata.Options & { omitEmpty: true }, ): Promise> // The default (fallback — must stay last): all returned fields are present, // whether empty or not. `Result` is intentionally loose, by design. See below. declare function urlMetadata( url: string | null, options?: urlMetadata.Options, ): Promise declare namespace urlMetadata { // Helper Functions --- // Result-shaping helpers exposed on the main entry (index.js & browser.js). // So consumers can filter an already-parsed result without re-fetching. // Validate a `fields` selection eagerly. Returns the tokens, or null when // `fields` is undefined (signal: no filtering). Throws on a non-array, empty // array, non-string token, `meta:` with no name, or unknown bare token. function resolveFields(fields: string[] | undefined): string[] | null; // Project a full result down to a validated `fields` selection. Only keys // present in `metadata` are returned; groups expand against its own keys. function selectFields(metadata: Record, fields: string[]): Partial; // The `omitEmpty` predicate: true for undefined, null, '', [], {} (shallow). function isEmpty(value: unknown): boolean; // end Helper Functions --- interface Options { requestHeaders?: Record; proxyUrl?: string; // proxy/unblocking service endpoint (ex: https://api.scraperapi.com/); presence of this triggers proxy mode proxyParams?: ProxyParams; parseResponseObject?: globalThis.Response | import('node-fetch').Response; fields?: string[]; // sparse fieldset: atomic keys, group names ('network'|'og'|'twitter'|'meta'), and/or 'meta:' for page-specific tags; undefined returns full result omitEmpty?: boolean; // drop top-level empty fields (undefined, null, '', [], {}) for token efficiency; when true, result is typed Partial requestFilteringAgentOptions?: import('request-filtering-agent').RequestFilteringAgentOptions; agent?: any; // Suggest: Node.js http.Agent | https.Agent maxRedirects?: number; timeout?: number; // default 10000ms; auto-bumped to 60000ms in proxy mode unless explicitly set size?: number; compress?: boolean; ensureSecureImageRequest?: boolean; decode?: string; cache?: string; mode?: string; descriptionLength?: number; includeResponseBody?: boolean; } /** * Confirmed generic across ≥2 vendors (ScraperAPI, ScrapingAnt) — passes * through verbatim as query params on `proxyUrl` (no allowlist), so this * stays accurate for whatever params a vendor supports without us * maintaining a list. Optional — some vendors (header-auth ones) will * need `proxyUrl` with no params at all. */ interface ProxyParams { [param: string]: string | boolean | number; } /** * Intentionally loose (collapses to `any`) for maximum compatibility. * For a documented shape, opt in with: * const metadata = await urlMetadata(url) as urlMetadata.KnownFields; */ type Result = Record; /** * Opt-in documented result shape: * const metadata = await urlMetadata(url) as urlMetadata.KnownFields; * Known fields are fully typed; any additional meta tags found on the * page are appended as new fields, as strings — except tags starting * with `citation_`, which are string[] (per Google Scholar spec, see README). * * With `fields` or `omitEmpty: true`, presence guarantees no longer hold; * the function returns `Partial` automatically (no cast needed). */ interface KnownFields extends KnownFieldsStrict { [metaTagName: string]: any; } /** * The complete catalog of fields always present on a successful result * (see lib/metadata-fields.js). Closed: no index signature, so `keyof` * yields the literal field-name union. Most users want `KnownFields` * instead, which allows arbitrary meta tags to be included in definition. */ interface KnownFieldsStrict { requestUrl?: string | null | undefined; // echoes the url param verbatim; falsy ('' | null | undefined) in parseResponseObject mode redirects: { count: number; chain: RedirectHop[]; }; url: string; // final destination url in request chain responseStatusCode: number; responseHeaders: Record; // whitelisted set, see lib/extract-headers.js performance: { redirectTimeMs: number | undefined; // only set when redirects occurred ttfbMs: number | undefined; // cumulative: first request start -> final hop's headers arriving responseTimeMs: number | undefined; // cumulative: first request start -> body read complete }; canonical: string; // first found, empty string if none canonicalUrls: string[]; // raw href of every canonical tag, in document order lang: string; hreflang: HreflangTag[]; charset: string; viewport: string; title: string; 'application-name': string; favicons: FaviconTag[]; image: string; description: string; keywords: string; referrer: string; author: string; publisher: string; creator: string; source: string; robots: string; googlebot: string; generator: string; 'color-scheme': string; 'theme-color': string; price: string; priceCurrency: string; availability: string; jsonld: Record[]; // arbitrary schema.org shapes, shapes vary by page (schema.org types, often malformed) // http://ogp.me/ 'og:url': string; 'og:locale': string; 'og:locale:alternate': string; 'og:title': string; 'og:type': string; 'og:description': string; 'og:determiner': string; 'og:site_name': string; 'og:image': string; 'og:image:alt': string; 'og:image:secure_url': string; 'og:image:type': string; 'og:image:width': string; 'og:image:height': string; 'product:brand': string; 'product:availability': string; 'product:condition': string; 'product:price:amount': string; 'product:price:currency': string; 'product:retailer_item_id': string; 'product:item_group_id': string; // https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup 'twitter:title': string; 'twitter:description': string; 'twitter:image': string; 'twitter:image:alt': string; 'twitter:card': string; 'twitter:site': string; 'twitter:site:id': string; 'twitter:url': string; 'twitter:account_id': string; 'twitter:creator': string; 'twitter:creator:id': string; 'twitter:player': string; 'twitter:player:width': string; 'twitter:player:height': string; 'twitter:player:stream': string; 'twitter:app:name:iphone': string; 'twitter:app:id:iphone': string; 'twitter:app:url:iphone': string; 'twitter:app:name:ipad': string; 'twitter:app:id:ipad': string; 'twitter:app:url:ipad': string; 'twitter:app:name:googleplay': string; 'twitter:app:id:googleplay': string; 'twitter:app:url:googleplay': string; headings: Heading[]; imgTags: ImgTag[]; responseBody: string; // empty string unless options.includeResponseBody is true } interface HreflangTag { hreflang: string; href?: string; } interface FaviconTag { rel: string; type?: string; href?: string; sizes?: string; color?: string; // only set when rel is 'mask-icon' } interface Heading { level: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'; text: string; } interface ImgTag { src?: string; alt?: string; title?: string; width?: string; height?: string; } interface RedirectHop { order: number; // 1-based position in the chain (first hop is order: 1) url: string; // the url requested at this hop statusCode: number; // the 3xx status returned by this hop location: string; // resolved absolute url this hop redirects to (its Location header) ttfbMs: number; // time-to-headers for this hop (ms) } /** * Design decision (do not "fix"): error fields are attached only when * they carry information & are absent otherwise (see lib/http-error.js). * Keeps serialized errors sparse & log-friendly. */ interface UrlMetadataError extends Error { requestUrl?: string; // the url the user passed in; absent if falsy (parseResponseObject mode) redirects?: { // included in all errors where applicable (some do not reach this point tho) count: number; chain: RedirectHop[]; }; url?: string; // final destination url in request chain unfollowedRedirect?: boolean; // true when error is a redirect left unfollowed (maxRedirects reached), not a transport error; next-hop url is `url` / last redirects.chain[].location statusCode?: number; responseHeaders?: Record; // whitelisted set (see lib/extract-headers.js); present whenever a response was received, absent on transport errors & 'too many redirects'. In proxy mode these are the proxy vendor's response headers paymentRequired?: boolean; x402?: Record; // x402 payment requirements - https://www.x402.org/ // errors that *may* fall thru from `node-fetch` dependency in Node.js: type?: string; errno?: string; code?: string; } }