import { z } from 'zod'; import { BaseConnector, type ConnectorMeta, type CrawlContext, type RawRecordInput, type RawRecordLike } from '@rareindex/connectors'; import { NormalizedAuctionLotSchema, NormalizedSaleSchema, type NormalizedRecord, type CurrencyCode, SUPPORTED_CURRENCIES } from '@rareindex/shared'; import { parseGradeFromTitle } from '@rareindex/taxonomy'; import { brandFromSlug, hintFromLabel, isBundleTitle, legoSetNumber, safeYear, slugFromTitle, watchReference } from '../_auction-lib/categories.js'; /** * Bonhams — realized prices + upcoming lots from the public, server-rendered Next.js pages. * Engine: plain HTTPS (no credits). See meta.json accessNotes. */ const SITE = 'https://www.bonhams.com'; export const AuctionSummarySchema = z.object({ id: z.string(), title: z.string(), slug: z.string(), status: z.string().nullable(), type: z.string().nullable(), departments: z.array(z.string()), categories: z.array(z.string()), currency: z.string().nullable(), country: z.string().nullable(), venue: z.string().nullable(), start: z.string().nullable(), end: z.string().nullable(), isEnded: z.boolean(), numberOfLots: z.number().nullable(), }); export type AuctionSummary = z.infer; export const LotSchema = z.object({ lotId: z.string(), lotUniqueId: z.string().nullable(), lotNo: z.string(), title: z.string(), heading: z.string().nullable(), slug: z.string().nullable(), imageUrl: z.string().nullable(), estimateLow: z.number().nullable(), estimateHigh: z.number().nullable(), hammerPrice: z.number().nullable(), hammerPremium: z.number().nullable(), startingBid: z.number().nullable(), currency: z.string().nullable(), status: z.string().nullable(), hammerTime: z.string().nullable(), endDate: z.string().nullable(), department: z.string().nullable(), categories: z.array(z.string()), isEnded: z.boolean(), isWithoutReserve: z.boolean().nullable(), }); export type Lot = z.infer; export const PagePayloadSchema = z.object({ kind: z.literal('auction_lots'), auction: AuctionSummarySchema, page: z.number().int(), nbHits: z.number().nullable(), lots: z.array(LotSchema), }); export type PagePayload = z.infer; const ConfigSchema = z.object({ departments: z.array(z.string()).default([]), maxResultsPages: z.number().int().min(1).default(20), maxAuctionsPerRun: z.number().int().min(1).default(40), upcomingAuctionsPerRun: z.number().int().min(0).default(12), lotsPerPage: z.number().int().default(48), }); type NextData = { props?: { pageProps?: Record } }; export function extractNextData(html: string): NextData | null { const m = html.match(/