feat: extract individual Facebook marketplace items
This commit is contained in:
103
src/facebook.ts
103
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[] {
|
function parseFacebookAds(ads: FacebookAdNode[]): ListingDetails[] {
|
||||||
const results: ListingDetails[] = [];
|
const results: ListingDetails[] = [];
|
||||||
|
|||||||
Reference in New Issue
Block a user