From 0a114cf3239a136c4fa8943adb7a5d16cfa60928 Mon Sep 17 00:00:00 2001 From: Dmytro Stanchiev Date: Thu, 22 Jan 2026 19:54:14 -0500 Subject: [PATCH] feat: extract individual Facebook marketplace items --- src/facebook.ts | 103 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/src/facebook.ts b/src/facebook.ts index 00db280..c1a460d 100644 --- a/src/facebook.ts +++ b/src/facebook.ts @@ -611,7 +611,108 @@ function formatCentsToCurrency( } /** - Parse Facebook marketplace search results into ListingDetails[] + Extract marketplace item details from Facebook item page HTML + Updated for 2026 Facebook Marketplace API structure with multiple extraction paths +*/ +function extractFacebookItemData(htmlString: HTMLString): FacebookMarketplaceItem | null { + const { document } = parseHTML(htmlString); + const scripts = document.querySelectorAll("script"); + + for (const script of scripts) { + const scriptText = script.textContent; + if (!scriptText) continue; + + try { + const parsed = JSON.parse(scriptText); + + // Check for the 2026 require structure with marketplace product details + if (parsed.require && Array.isArray(parsed.require)) { + // Try multiple extraction paths discovered from reverse engineering + const extractionPaths = [ + // Path 1: Primary path from current API structure + () => parsed.require[0][3].__bbox.result.data.viewer.marketplace_product_details_page.target, + // Path 2: Alternative path with nested require + () => parsed.require[0][3][0].__bbox.require[3][3][1].__bbox.result.data.viewer.marketplace_product_details_page.target, + // Path 3: Variation without the [0] index + () => parsed.require[0][3].__bbox.require[3][3][1].__bbox.result.data.viewer.marketplace_product_details_page.target, + // Path 4-5: Additional fallback paths for edge cases + () => parsed.require[0][3][1]?.__bbox?.result?.data?.viewer?.marketplace_product_details_page?.target, + () => parsed.require[0][3][2]?.__bbox?.result?.data?.viewer?.marketplace_product_details_page?.target, + ]; + + let pathIndex = 0; + for (const getPath of extractionPaths) { + try { + const targetData = getPath(); + if (targetData && typeof targetData === 'object' && + targetData.id && targetData.marketplace_listing_title && + targetData.__typename === 'GroupCommerceProductItem') { + console.log(`Successfully extracted Facebook item data using extraction path ${pathIndex + 1}`); + return targetData as FacebookMarketplaceItem; + } + } catch { + // Path not found or invalid, try next path + } + pathIndex++; + } + + // Fallback: Search recursively for marketplace data in the parsed structure + const findMarketplaceData = (obj: unknown, depth = 0, maxDepth = 10): FacebookMarketplaceItem | null => { + if (depth > maxDepth) return null; // Prevent infinite recursion + if (isRecord(obj)) { + // Check if this object matches the expected marketplace item structure + if (obj.marketplace_listing_title && obj.id && + obj.__typename === 'GroupCommerceProductItem' && + obj.redacted_description) { + return obj as FacebookMarketplaceItem; + } + // Recursively search nested objects and arrays + for (const key in obj) { + const value = obj[key]; + if (isRecord(value) || Array.isArray(value)) { + const result = findMarketplaceData(value, depth + 1, maxDepth); + if (result) return result; + } + } + } else if (Array.isArray(obj)) { + // Search through arrays + for (const item of obj) { + const result = findMarketplaceData(item, depth + 1, maxDepth); + if (result) return result; + } + } + return null; + }; + + // Search through the entire require structure + const recursiveResult = findMarketplaceData(parsed.require); + if (recursiveResult) { + console.log('Successfully extracted Facebook item data using recursive search'); + return recursiveResult; + } + + // Additional search in other potential locations + if (parsed.__bbox?.result?.data?.viewer?.marketplace_product_details_page?.target) { + const bboxData = parsed.__bbox.result.data.viewer.marketplace_product_details_page.target; + if (bboxData && typeof bboxData === 'object' && + bboxData.id && bboxData.marketplace_listing_title && + bboxData.__typename === 'GroupCommerceProductItem') { + console.log('Successfully extracted Facebook item data from __bbox structure'); + return bboxData as FacebookMarketplaceItem; + } + } + } + } catch (error) { + // Log parsing errors for debugging but continue to next script + console.debug(`Failed to parse script for Facebook item data: ${error}`); + } + } + + return null; +} + +/** + Parse Facebook marketplace search results into ListingDetails[] */ function parseFacebookAds(ads: FacebookAdNode[]): ListingDetails[] { const results: ListingDetails[] = [];