diff --git a/packages/core/src/scrapers/ebay.ts b/packages/core/src/scrapers/ebay.ts
index 771144c..1b9dec5 100644
--- a/packages/core/src/scrapers/ebay.ts
+++ b/packages/core/src/scrapers/ebay.ts
@@ -43,7 +43,7 @@ const EBAY_PRICE_TEXT_RE = /^(?:\s*(?:CA|C)\s*\$|\s*[$£€¥])/u;
function canonicalizeEbayItemUrl(url: string): string {
try {
const parsed = new URL(url, "https://www.ebay.ca");
- const match = parsed.pathname.match(/\/itm\/[^/?#]+/);
+ const match = parsed.pathname.match(/\/itm\/(?:[^/?#]+\/)?\d+/);
return match ? `${parsed.origin}${match[0]}` : `${parsed.origin}${parsed.pathname}`;
} catch {
return url;
@@ -267,8 +267,7 @@ function parseEbayListings(
if (
text &&
EBAY_PRICE_TEXT_RE.test(text) &&
- text.length < 50 &&
- !/\d{4}/.test(text)
+ text.length < 50
) {
actualPrices.push(el);
}
@@ -512,7 +511,7 @@ export default async function fetchEbayItems(
// Filter by price range (additional safety check)
const filteredListings = listings.filter((listing) => {
const cents = listing.listingPrice?.cents;
- return cents && cents >= minPrice && cents <= maxPrice;
+ return typeof cents === "number" && cents >= minPrice && cents <= maxPrice;
});
console.log(`Parsed ${filteredListings.length} eBay listings.`);
diff --git a/packages/core/test/ebay-core.test.ts b/packages/core/test/ebay-core.test.ts
index f7bd951..47b7235 100644
--- a/packages/core/test/ebay-core.test.ts
+++ b/packages/core/test/ebay-core.test.ts
@@ -131,6 +131,48 @@ describe("eBay Scraper Cookie Handling", () => {
);
});
+ test("deduplicates tracking variants of SEO-style item URLs", async () => {
+ global.fetch = mock(() =>
+ Promise.resolve({
+ ok: true,
+ text: () =>
+ Promise.resolve(`
+
+
+
+ Stable Laptop Bundle
+ CA $100.00
+
+
+
+ Stable Laptop Bundle
+ CA $100.00
+
+
+
+ Another Laptop Bundle
+ CA $110.00
+
+
+ `),
+ }),
+ ) as typeof fetch;
+
+ const results = await fetchEbayItems("laptop", 1000);
+
+ expect(results).toHaveLength(2);
+ expect(results[0]).toEqual(
+ expect.objectContaining({
+ url: "https://www.ebay.ca/itm/title-slug/1234567890?_trkparms=foo",
+ }),
+ );
+ expect(results[1]).toEqual(
+ expect.objectContaining({
+ url: "https://www.ebay.ca/itm/title-slug/9999999999?hash=item999",
+ }),
+ );
+ });
+
test("treats bare dollar prices as CAD on ebay.ca", async () => {
global.fetch = mock(() =>
Promise.resolve({
@@ -189,6 +231,68 @@ describe("eBay Scraper Cookie Handling", () => {
]);
});
+ test("prefers discounted Canadian prices that contain four consecutive digits", async () => {
+ global.fetch = mock(() =>
+ Promise.resolve({
+ ok: true,
+ text: () =>
+ Promise.resolve(`
+
+
+
+ Stable Laptop Bundle
+
+ CA $1500.00
+ CA $1000.00
+
+
+
+ `),
+ }),
+ ) as typeof fetch;
+
+ const results = await fetchEbayItems("laptop", 1000);
+
+ expect(results).toEqual([
+ expect.objectContaining({
+ listingPrice: expect.objectContaining({
+ amountFormatted: "CA $1000.00",
+ cents: 100000,
+ }),
+ }),
+ ]);
+ });
+
+ test("retains free items when the requested price range includes zero", async () => {
+ global.fetch = mock(() =>
+ Promise.resolve({
+ ok: true,
+ text: () =>
+ Promise.resolve(`
+
+
+
+ Free Laptop Bundle
+ $0.00
+
+
+ `),
+ }),
+ ) as typeof fetch;
+
+ const results = await fetchEbayItems("laptop", 1000, {
+ minPrice: 0,
+ maxPrice: 0,
+ });
+
+ expect(results).toEqual([
+ expect.objectContaining({
+ title: "Free Laptop Bundle",
+ listingPrice: expect.objectContaining({ cents: 0 }),
+ }),
+ ]);
+ });
+
test("returns results and unstableResults when unstable mode is enabled", async () => {
global.fetch = mock(() =>
Promise.resolve({