TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1import type { ConnectorMeta, CrawlContext } from '@rareindex/connectors';2import type { ExtractionResult } from '@rareindex/shared';3import { parseDrouotCatalogue, parseDrouotIndex } from '../_g8-auctions-eu-apac-lib/drouot-platform.js';4import { SaleResultsConnector, type HouseConfig, type ParsedSalePage, type SaleRef } from '../_g8-auctions-eu-apac-lib/sale-results.js';56/**7 * Osenat (Fontainebleau / Versailles / Paris — Napoleonic & military history, watches, wine, cars) — same Drouot8 * "site générique" platform as Ader. Index /resultats-ventes-passees?year=YYYY; lots /catalogue/<id>?offset=N&max=50.9 * Osenat pages say "Résultats sans frais" → hammer prices (read per page).10 */11const BASE = 'https://www.osenat.com';12const HOUSE = { base: BASE, currency: 'EUR' as const };13const PAGE = 50;1415export class OsenatConnector extends SaleResultsConnector {16 readonly version = '1.0.0';17 readonly house: HouseConfig = { houseName: 'Osenat', defaultCurrency: 'EUR', location: 'Fontainebleau, France', idKey: 'osenat_lot', premiumIncluded: null, fallbackSlug: 'antiques', minIntervalMs: 2500, maxPagesPerSale: 30 };18 protected override minIntervalMs = 2500;1920 async listSales(ctx: CrawlContext): Promise<SaleRef[]> {21 const years = (this.meta.config.years as number[] | undefined) ?? [new Date().getUTCFullYear()];22 const start = Number(this.meta.config.backfillFromYear ?? 2005);23 const now = new Date().getUTCFullYear();24 const list = ctx.options.mode === 'backfill' ? Array.from({ length: now - start + 1 }, (_, i) => start + i) : years;25 const all: SaleRef[] = [];26 for (const y of list) {27 const url = `${BASE}/resultats-ventes-passees?year=${y}`;28 await this.throttle(url);29 const res = await ctx.fetch(url, { engines: ['api'], responseType: 'text', minQuality: 0, timeoutMs: 60_000 });30 if (!res.success || !res.html) {31 ctx.anomaly('past_list_failed', `${url}: ${res.error ?? res.httpStatus}`);32 continue;33 }34 all.push(...parseDrouotIndex(res.html, HOUSE));35 }36 return all;37 }3839 salePageUrl(sale: SaleRef, page: number): string {40 return page > 1 ? `${sale.url.replace(/\/catalogue\/(\d+)[^?]*/, '/catalogue/$1')}?lang=fr&search=&offset=${(page - 1) * PAGE}&max=${PAGE}` : sale.url;41 }4243 parseSalePage(res: ExtractionResult, sale: SaleRef): ParsedSalePage | null {44 return res.html ? parseDrouotCatalogue(res.html, sale, HOUSE, PAGE) : null;45 }46}4748export default function createConnector(meta: ConnectorMeta) {49 return new OsenatConnector(meta);50}51