import { html as H, type ConnectorMeta } from '@rareindex/connectors'; import { CertLookupConnector, type PageStatus, type ParsedCert } from '../_g2-grading-lib/cert-base.js'; import { clean, money, parseCoinGrade, toInt, yearOf } from '../_g2-grading-lib/text.js'; /** * PCGS certificate verification — https://www.pcgs.com/cert/ (public; coins and banknotes). * Svelte-rendered page behind a Cloudflare managed challenge → Firecrawl render (Scrapfly fallback). * The page carries the coin's PCGS# (deterministic id shared with connectors/firecrawl/pcgs-priceguide), * grade, price-guide value and the PCGS population at that grade + higher. */ const PARSER_VERSION = '1.0.0'; const PATTERNS = [/^https?:\/\/(?:www\.)?pcgs\.com\/cert\/(\d{6,10})\/?(?:[?#].*)?$/i, /^https?:\/\/(?:www\.)?pcgs\.com\/cert\/verify\?(?:.*&)?certno=(\d{6,10})/i]; export function certUrl(cert: string): string { return `https://www.pcgs.com/cert/${clean(cert).replace(/\D/g, '')}`; } export function certFromUrl(url: string): string | null { for (const re of PATTERNS) { const m = url.match(re); if (m) return m[1]!; } return null; } export interface PcgsPage { title: string; cert: string | null; rows: Record; links: Record; images: string[]; } /** Pure parser over the trimmed fragment. Exported for tests. */ export function parsePcgsPage(snapshot: string): PcgsPage | null { const $ = H.load(snapshot); const title = clean($('.text-display5').first().text()); const cert = clean($('.text-display5').first().parent().text()).match(/#(\d{6,10})/)?.[1] ?? clean($.root().text()).match(/#(\d{6,10})/)?.[1] ?? null; const rows: Record = {}; const links: Record = {}; $('table tr').each((_, tr) => { const tds = $(tr).find('td'); if (tds.length < 2) return; const label = clean($(tds[0]).text()); if (!label) return; rows[label] = clean($(tds[1]).text()); const href = $(tds[1]).find('a[href]').first().attr('href'); if (href) links[label] = href; }); if (!title || !rows['Grade']) return null; const images = [...new Set($('img[alt*="enlarge" i], img[src*="/pcgs/cert/"]').toArray().map((e) => $(e).attr('src') ?? '').filter((u) => /^https?:\/\//.test(u)))]; return { title, cert, rows, links, images }; } export class PcgsCertConnector extends CertLookupConnector { readonly version = '1.0.0'; readonly parserVersion = PARSER_VERSION; readonly grader = 'pcgs'; readonly idKey = 'pcgs_cert'; readonly format = 'html' as const; override readonly urlPatterns = PATTERNS; certUrl(cert: string): string { return certUrl(cert); } certFromUrl(url: string): string | null { return certFromUrl(url); } classify(doc: string): PageStatus { const $ = H.load(doc); const hasGradeRow = $('table tr td') .toArray() .some((td) => /^grade$/i.test(clean($(td).text()))); if ($('.text-display5').length && hasGradeRow) return 'found'; const text = clean($('body').text() || $.root().text()); if (/not found|no record|invalid cert|does not exist|could not be found|unable to (find|locate)|no results|not in our|we were unable|isn.t in the pcgs/i.test(text)) return 'not_found'; return 'unknown'; } trim(doc: string): string { const $ = H.load(doc); $('script, style, noscript, iframe, svg, header, footer, nav, form').remove(); let node = $('.text-display5').first(); if (!node.length) { const body = $('body'); return `
${clean(body.text()).slice(0, 4000)}
`; } for (let i = 0; i < 10 && node.length; i++) { if (node.find('table').length) break; node = node.parent(); } return $.html(node.length ? node : $('body')); } parse(snapshot: string, info: { cert: string; url: string }): ParsedCert | null { const page = parsePcgsPage(snapshot); if (!page) return null; const r = page.rows; const cert = page.cert ?? info.cert; const g = parseCoinGrade(r['Grade']); const pcgsNo = clean(r['PCGS #'] ?? '').replace(/\D/g, '') || null; const region = r['Region'] ?? null; const isNote = /note|banknote|currency/i.test(`${page.title} ${r['Denomination'] ?? ''} ${r['Holder Type'] ?? ''}`) && !/coin/i.test(r['Holder Type'] ?? ''); const identifiers: Record = {}; if (pcgsNo) identifiers.pcgs_number = pcgsNo; const population = toInt(r['Population']); return { title: `${page.title} PCGS ${r['Grade']}`.trim(), attributes: { categorySlug: isNote ? 'banknotes' : 'coins', name: page.title, year: yearOf(r['Date, Mintmark'] ?? page.title), country: /united states/i.test(region ?? '') ? 'US' : null, region, variant: r['Variety'] ?? r['Designation'] ?? null, productionQuantity: toInt(r['Mintage']), identifiers, metadata: { pcgs_cert: cert, date_mintmark: r['Date, Mintmark'] ?? null, denomination: r['Denomination'] ?? null, price_guide_value_usd: money(r['Price Guide Value']), holder_type: r['Holder Type'] ?? null, security: r['Security'] ?? null, coinfacts_url: page.links['PCGS #'] ?? null, population_url: page.links['Population'] ?? null, fields: r, }, }, grade: g.grade, qualifier: g.qualifier, gradeLabel: g.label || null, images: page.images, population: population !== null ? { gradeKey: g.grade ?? g.label, atGrade: population, higher: toInt(r['Pop Higher']), url: page.links['Population'] ?? null } : null, confidence: pcgsNo ? 0.95 : 0.85, }; } } export default (meta: ConnectorMeta) => new PcgsCertConnector(meta);