feat: extract individual Facebook marketplace items

This commit is contained in:
2026-01-22 19:54:14 -05:00
parent 5f7de1167e
commit 0a114cf323

View File

@@ -610,6 +610,107 @@ function formatCentsToCurrency(
return formatter.format(dollars);
}
/**
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[]
*/