From d8542eb8f7122028ebec4dd56afd78bce82d8d1a Mon Sep 17 00:00:00 2001 From: Dmytro Stanchiev Date: Thu, 22 Jan 2026 19:54:44 -0500 Subject: [PATCH] feat: parse Facebook marketplace item details and test exports --- src/facebook.ts | 104 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/src/facebook.ts b/src/facebook.ts index c1a460d..c41b172 100644 --- a/src/facebook.ts +++ b/src/facebook.ts @@ -830,6 +830,110 @@ function parseFacebookAds(ads: FacebookAdNode[]): ListingDetails[] { return results; } +/** + Parse Facebook marketplace item details into ListingDetails format + Updated for 2026 GroupCommerceProductItem structure +*/ +function parseFacebookItem(item: FacebookMarketplaceItem): ListingDetails | null { + try { + const title = item.marketplace_listing_title || item.custom_title; + if (!title) return null; + + const url = `https://www.facebook.com/marketplace/item/${item.id}`; + + // Extract price information + let cents = 0; + let currency = "CAD"; // Default + let amountFormatted = item.formatted_price?.text || "FREE"; + + if (item.listing_price) { + currency = item.listing_price.currency || "CAD"; + if (item.listing_price.amount && item.listing_price.amount !== "0.00") { + const amount = parseFloat(item.listing_price.amount); + if (!isNaN(amount)) { + cents = Math.round(amount * 100); + amountFormatted = item.formatted_price?.text || formatCentsToCurrency(cents); + } + } + } + + // Extract description + const description = item.redacted_description?.text; + + // Extract location + const address = item.location_text?.text || null; + + // Extract seller information + const seller = item.marketplace_listing_seller ? { + name: item.marketplace_listing_seller.name, + id: item.marketplace_listing_seller.id + } : undefined; + + // Determine listing status + let listingStatus: string | undefined; + if (item.is_sold) { + listingStatus = "SOLD"; + } else if (item.is_pending) { + listingStatus = "PENDING"; + } else if (item.is_live) { + listingStatus = "ACTIVE"; + } else if (item.is_hidden) { + listingStatus = "HIDDEN"; + } + + // Format creation date + const creationDate = item.creation_time + ? new Date(item.creation_time * 1000).toISOString() + : undefined; + + // Determine listing type based on category or vehicle data + let listingType = "item"; + if (item.vehicle_make_display_name || item.vehicle_odometer_data) { + listingType = "vehicle"; + } else if (item.marketplace_listing_category_id) { + // Could map category IDs to types, but keeping simple for now + listingType = "item"; + } + + const listingDetails: ListingDetails = { + url, + title, + description, + listingPrice: { + amountFormatted, + cents, + currency, + }, + address, + creationDate, + listingType, + listingStatus, + categoryId: item.marketplace_listing_category_id, + seller, + deliveryTypes: item.delivery_types, + }; + + return listingDetails; + } catch (error) { + console.warn(`Failed to parse Facebook item ${item.id}:`, error); + return null; + } +} + +// ----------------------------- Exports for Testing ----------------------------- +// Export internal functions for comprehensive testing +export { + extractFacebookItemData, + extractFacebookMarketplaceData, + parseFacebookItem, + parseFacebookAds, + formatCentsToCurrency, + loadFacebookCookies, + formatCookiesForHeader, + parseFacebookCookieString, + ensureFacebookCookies, +}; + // ----------------------------- Main ----------------------------- export default async function fetchFacebookItems(