diff --git a/packages/core/src/scrapers/ebay.ts b/packages/core/src/scrapers/ebay.ts index d579d6c..bbd45b0 100644 --- a/packages/core/src/scrapers/ebay.ts +++ b/packages/core/src/scrapers/ebay.ts @@ -38,7 +38,7 @@ export interface EbayListingDetails { address?: string | null; } -const EBAY_PRICE_TEXT_RE = /^(?:\s*(?:CA|C)\s*\$|\s*[$£€¥])/u; +const EBAY_PRICE_TEXT_RE = /^(?:\s*(?:CA|C|US)\s*\$|\s*[$£€¥])/u; function canonicalizeEbayItemUrl(url: string): string { try { diff --git a/packages/core/src/scrapers/kijiji.ts b/packages/core/src/scrapers/kijiji.ts index 9c342ae..425c663 100644 --- a/packages/core/src/scrapers/kijiji.ts +++ b/packages/core/src/scrapers/kijiji.ts @@ -218,6 +218,10 @@ function normalizeLookupKey(value: string): string { return value.toLowerCase().replace(/[\s-]+/g, "-"); } +function centsToKijijiPriceParam(cents: number): number { + return Math.floor(cents / 100); +} + /** * Resolve location ID from name or return numeric ID */ @@ -293,9 +297,13 @@ export function buildSearchUrl( : "relevancyDesc"; const sortOrder = options.sortOrder === "asc" ? "ASC" : "DESC"; const priceMinParam = - typeof options.priceMin === "number" ? `&priceMin=${options.priceMin}` : ""; + typeof options.priceMin === "number" + ? `&priceMin=${centsToKijijiPriceParam(options.priceMin)}` + : ""; const priceMaxParam = - typeof options.priceMax === "number" ? `&priceMax=${options.priceMax}` : ""; + typeof options.priceMax === "number" + ? `&priceMax=${centsToKijijiPriceParam(options.priceMax)}` + : ""; const pageParam = options.page && options.page > 1 ? `&page=${options.page}` : ""; diff --git a/packages/core/test/ebay-core.test.ts b/packages/core/test/ebay-core.test.ts index b8b59d8..02dd14f 100644 --- a/packages/core/test/ebay-core.test.ts +++ b/packages/core/test/ebay-core.test.ts @@ -360,6 +360,39 @@ describe("eBay Scraper Cookie Handling", () => { ]); }); + test("prefers discounted US dollar prices over original prices", async () => { + global.fetch = mock(() => + Promise.resolve({ + ok: true, + text: () => + Promise.resolve(` + +
  • + +

    Stable Laptop Bundle

    + + US $150.00 + US $100.00 + +
  • + + `), + }), + ) as typeof fetch; + + const results = await fetchEbayItems("laptop", 1000); + + expect(results).toEqual([ + expect.objectContaining({ + listingPrice: expect.objectContaining({ + amountFormatted: "US $100.00", + cents: 10000, + currency: "USD", + }), + }), + ]); + }); + test("accepts higher fallback prices without price classes", async () => { global.fetch = mock(() => Promise.resolve({ diff --git a/packages/core/test/kijiji-core.test.ts b/packages/core/test/kijiji-core.test.ts index 7f08158..e0cde78 100644 --- a/packages/core/test/kijiji-core.test.ts +++ b/packages/core/test/kijiji-core.test.ts @@ -163,8 +163,8 @@ describe("URL Construction", () => { priceMax: 10000, }); - expect(url).toContain("priceMin=8000"); - expect(url).toContain("priceMax=10000"); + expect(url).toContain("priceMin=80"); + expect(url).toContain("priceMax=100"); }); test("should handle string location/category inputs", () => { @@ -570,7 +570,7 @@ describe("fetchKijijiItems", () => { global.fetch = mock((input: string | URL | Request) => { const url = typeof input === "string" ? input : input.toString(); - if (url.includes("/k0c0l1700272") && url.includes("priceMin=8000")) { + if (url.includes("/k0c0l1700272") && url.includes("priceMin=80")) { return Promise.resolve({ ok: true, text: () => Promise.resolve(searchHtml), @@ -672,7 +672,7 @@ describe("fetchKijijiItems", () => { global.fetch = mock((input: string | URL | Request) => { const url = typeof input === "string" ? input : input.toString(); - if (url.includes("/k0c0l1700272") && url.includes("priceMin=8000") && url.includes("priceMax=15000")) { + if (url.includes("/k0c0l1700272") && url.includes("priceMin=80") && url.includes("priceMax=150")) { return Promise.resolve({ ok: true, text: () => Promise.resolve(searchHtml),