From 224e83ac4cda4098b16b01df05d5e3dac04dffde Mon Sep 17 00:00:00 2001 From: Dmytro Stanchiev Date: Mon, 27 Apr 2026 02:04:48 -0400 Subject: [PATCH] fix: correct ebay title filtering and type contracts --- packages/core/src/scrapers/ebay.ts | 6 +++-- packages/core/src/scrapers/facebook.ts | 8 +++++-- packages/core/src/scrapers/kijiji.ts | 6 ++--- packages/core/test/ebay-core.test.ts | 27 +++++++++++++++++++++ packages/core/test/facebook-core.test.ts | 30 ++++++++++++++++++++++++ 5 files changed, 69 insertions(+), 8 deletions(-) diff --git a/packages/core/src/scrapers/ebay.ts b/packages/core/src/scrapers/ebay.ts index bdf26e4..5c12af6 100644 --- a/packages/core/src/scrapers/ebay.ts +++ b/packages/core/src/scrapers/ebay.ts @@ -205,16 +205,18 @@ function parseEbayListings( "opens in a new window or tab", ]; + let shortened = false; for (const uiString of uiStrings) { const uiIndex = title.indexOf(uiString); if (uiIndex !== -1) { title = title.substring(0, uiIndex).trim(); + shortened = true; break; // Only remove one UI string per title } } - // If the title became empty or too short after cleaning, skip this item - if (title.length < 10) { + // If the title was shortened by UI cleaning and became too short, skip this item + if (shortened && title.length < 10) { continue; } } diff --git a/packages/core/src/scrapers/facebook.ts b/packages/core/src/scrapers/facebook.ts index 1750622..f91b78d 100644 --- a/packages/core/src/scrapers/facebook.ts +++ b/packages/core/src/scrapers/facebook.ts @@ -899,6 +899,11 @@ export function parseFacebookAds( if (Number.isNaN(dollars)) continue; cents = Math.round(dollars * 100); + } else if ( + typeof priceObj.formatted_amount === "string" && + priceObj.formatted_amount.toUpperCase() === "FREE" + ) { + cents = 0; } else { continue; // No price available } @@ -1192,8 +1197,7 @@ export default async function fetchFacebookItems( ? new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic) : null; const totalProgress = ads.length; - const currentProgress = 0; - progressBar?.start(totalProgress, currentProgress); + progressBar?.start(totalProgress, 0); const items = parseFacebookAds(ads); diff --git a/packages/core/src/scrapers/kijiji.ts b/packages/core/src/scrapers/kijiji.ts index 8a7c44a..25cf46a 100644 --- a/packages/core/src/scrapers/kijiji.ts +++ b/packages/core/src/scrapers/kijiji.ts @@ -254,7 +254,7 @@ export function resolveCategoryId(category?: number | string): number { function matchesPriceFilters( listing: DetailedListing, - searchOptions: Required, + searchOptions: SearchOptions, ): boolean { const cents = listing.listingPrice?.cents; @@ -971,9 +971,7 @@ export default async function fetchKijijiItems( console.log( `\nParsed ${filteredListings.length} detailed listings.`, ); - return unstableMode.hideUnstableResults - ? finalizeResults(filteredListings) - : finalizeResults(filteredListings); + return finalizeResults(filteredListings); } // Re-export error classes for convenience diff --git a/packages/core/test/ebay-core.test.ts b/packages/core/test/ebay-core.test.ts index 2e90c5f..95708e1 100644 --- a/packages/core/test/ebay-core.test.ts +++ b/packages/core/test/ebay-core.test.ts @@ -419,6 +419,33 @@ describe("eBay Scraper Cookie Handling", () => { ]); }); + test("keeps short titles that were not shortened by UI cleaning", async () => { + global.fetch = mock(() => + Promise.resolve({ + ok: true, + text: () => + Promise.resolve(` + +
  • + +

    Free Bike

    + CA $0.00 +
  • + + `), + }), + ) as typeof fetch; + + const results = await fetchEbayItems("bike", 1000); + + expect(results).toEqual([ + expect.objectContaining({ + title: "Free Bike", + listingPrice: expect.objectContaining({ cents: 0, currency: "CAD" }), + }), + ]); + }); + test("accepts higher fallback prices without price classes", async () => { global.fetch = mock(() => Promise.resolve({ diff --git a/packages/core/test/facebook-core.test.ts b/packages/core/test/facebook-core.test.ts index 0e5bfd4..4ef4f1f 100644 --- a/packages/core/test/facebook-core.test.ts +++ b/packages/core/test/facebook-core.test.ts @@ -1804,6 +1804,36 @@ describe("Facebook Marketplace Scraper Core Tests", () => { }), ]); }); + + test("keeps free search listings when amount is missing but formatted_amount is FREE", () => { + const ads = [ + { + node: { + listing: { + id: "free-no-amount", + marketplace_listing_title: "Free Sofa", + listing_price: { + formatted_amount: "FREE", + currency: "CAD", + }, + is_live: true, + }, + }, + }, + ]; + + const results = parseFacebookAds(ads); + + expect(results).toEqual([ + expect.objectContaining({ + title: "Free Sofa", + listingPrice: expect.objectContaining({ + cents: 0, + amountFormatted: "FREE", + }), + }), + ]); + }); }); });