diff --git a/packages/core/src/scrapers/ebay.ts b/packages/core/src/scrapers/ebay.ts
index a7ef028..d579d6c 100644
--- a/packages/core/src/scrapers/ebay.ts
+++ b/packages/core/src/scrapers/ebay.ts
@@ -87,6 +87,12 @@ function parseEbayPrice(
cleaned.toUpperCase().includes("US $")
) {
currency = "USD";
+ } else if (cleaned.includes("£")) {
+ currency = "GBP";
+ } else if (cleaned.includes("€")) {
+ currency = "EUR";
+ } else if (cleaned.includes("¥")) {
+ currency = "JPY";
}
return { cents, currency };
diff --git a/packages/core/src/scrapers/facebook.ts b/packages/core/src/scrapers/facebook.ts
index e47f0c5..2178c68 100644
--- a/packages/core/src/scrapers/facebook.ts
+++ b/packages/core/src/scrapers/facebook.ts
@@ -1180,13 +1180,13 @@ export default async function fetchFacebookItems(
console.log(`\nFound ${ads.length} raw ads. Processing...`);
- const progressBar = new cliProgress.SingleBar(
- {},
- cliProgress.Presets.shades_classic,
- );
+ const isTTY = process.stdout?.isTTY ?? false;
+ const progressBar = isTTY
+ ? new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic)
+ : null;
const totalProgress = ads.length;
const currentProgress = 0;
- progressBar.start(totalProgress, currentProgress);
+ progressBar?.start(totalProgress, currentProgress);
const items = parseFacebookAds(ads);
@@ -1196,8 +1196,8 @@ export default async function fetchFacebookItems(
typeof item.listingPrice?.cents === "number" && item.listingPrice.cents >= 0,
);
- progressBar.update(totalProgress);
- progressBar.stop();
+ progressBar?.update(totalProgress);
+ progressBar?.stop();
console.log(`\nParsed ${pricedItems.length} Facebook marketplace listings.`);
return finalizeResults(pricedItems);
diff --git a/packages/core/test/ebay-core.test.ts b/packages/core/test/ebay-core.test.ts
index 27eae06..b8b59d8 100644
--- a/packages/core/test/ebay-core.test.ts
+++ b/packages/core/test/ebay-core.test.ts
@@ -234,6 +234,68 @@ describe("eBay Scraper Cookie Handling", () => {
]);
});
+ test("maps pound prices to GBP", async () => {
+ global.fetch = mock(() =>
+ Promise.resolve({
+ ok: true,
+ text: () =>
+ Promise.resolve(`
+
+
+
+ Stable Laptop Bundle
+ £123.45
+
+
+ `),
+ }),
+ ) as typeof fetch;
+
+ const results = await fetchEbayItems("laptop", 1000);
+
+ expect(results).toEqual([
+ expect.objectContaining({
+ listingPrice: expect.objectContaining({ currency: "GBP", cents: 12345 }),
+ }),
+ ]);
+ });
+
+ test("maps euro and yen prices to the matching currency labels", async () => {
+ global.fetch = mock(() =>
+ Promise.resolve({
+ ok: true,
+ text: () =>
+ Promise.resolve(`
+
+
+
+ Euro Bundle
+ €123.45
+
+
+
+ Yen Bundle
+ ¥123
+
+
+ `),
+ }),
+ ) as typeof fetch;
+
+ const results = await fetchEbayItems("bundle", 1000, {
+ keywords: ["bundle"],
+ });
+
+ expect(results).toEqual([
+ expect.objectContaining({
+ listingPrice: expect.objectContaining({ currency: "EUR", cents: 12345 }),
+ }),
+ expect.objectContaining({
+ listingPrice: expect.objectContaining({ currency: "JPY", cents: 12300 }),
+ }),
+ ]);
+ });
+
test("prefers the discounted Canadian-formatted price", 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 a9b126e..2a82e7b 100644
--- a/packages/core/test/facebook-core.test.ts
+++ b/packages/core/test/facebook-core.test.ts
@@ -1,4 +1,5 @@
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
+import cliProgress from "cli-progress";
import {
classifyFacebookResponse,
type FacebookListingDetails,
@@ -16,6 +17,8 @@ import type { UnstableListingBuckets } from "../src/types/common";
import { formatCookiesForHeader } from "../src/utils/cookies";
import { formatCentsToCurrency } from "../src/utils/format";
+const originalStdoutIsTTY = process.stdout.isTTY;
+
type Assert = T;
type IsExact =
(() => G extends T ? 1 : 2) extends () => G extends U ? 1 : 2
@@ -50,6 +53,7 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
afterEach(() => {
global.fetch = originalFetch;
+ process.stdout.isTTY = originalStdoutIsTTY;
});
describe("Cookie Parsing", () => {
@@ -621,6 +625,68 @@ describe("Facebook Marketplace Scraper Core Tests", () => {
]);
});
+ test("does not start a progress bar when stdout is not a TTY", async () => {
+ const mockSearchHtml = ``;
+
+ process.stdout.isTTY = false;
+ const startSpy = mock(() => {});
+ const updateSpy = mock(() => {});
+ const stopSpy = mock(() => {});
+ const originalStart = cliProgress.SingleBar.prototype.start;
+ const originalUpdate = cliProgress.SingleBar.prototype.update;
+ const originalStop = cliProgress.SingleBar.prototype.stop;
+ try {
+ cliProgress.SingleBar.prototype.start = startSpy;
+ cliProgress.SingleBar.prototype.update = updateSpy;
+ cliProgress.SingleBar.prototype.stop = stopSpy;
+
+ global.fetch = mock(() =>
+ Promise.resolve({
+ ok: true,
+ text: () => Promise.resolve(mockSearchHtml),
+ url: "https://www.facebook.com/marketplace/toronto/search?query=chair",
+ headers: {
+ get: () => null,
+ },
+ }),
+ );
+
+ const results = await fetchFacebookItems("chair", 1, "toronto", 25);
+
+ expect(results).toHaveLength(1);
+ expect(startSpy).not.toHaveBeenCalled();
+ expect(updateSpy).not.toHaveBeenCalled();
+ expect(stopSpy).not.toHaveBeenCalled();
+ } finally {
+ cliProgress.SingleBar.prototype.start = originalStart;
+ cliProgress.SingleBar.prototype.update = originalUpdate;
+ cliProgress.SingleBar.prototype.stop = originalStop;
+ }
+ });
+
test("returns results and unstableResults when unstable mode is enabled", async () => {
const mockSearchHtml = `