diff --git a/src/facebook.ts b/src/facebook.ts index 46a64c2..ffe1e28 100644 --- a/src/facebook.ts +++ b/src/facebook.ts @@ -1038,3 +1038,144 @@ export default async function fetchFacebookItems( console.log(`\nParsed ${pricedItems.length} Facebook marketplace listings.`); return pricedItems.slice(0, MAX_ITEMS); // Limit results } + +/** + * Fetch individual Facebook marketplace item details with enhanced error handling + */ +export async function fetchFacebookItem( + itemId: string, + cookiesSource?: string, + cookiePath?: string, +): Promise { + // Load Facebook cookies - required for Facebook Marketplace access + let cookies: Cookie[]; + if (cookiesSource) { + // Use provided cookie source (backward compatibility) + cookies = await loadFacebookCookies(cookiesSource); + } else { + // Auto-load from file or parse from env var + cookies = await ensureFacebookCookies(cookiePath); + } + + if (cookies.length === 0) { + throw new Error( + "Facebook cookies are required for marketplace access. " + + "Please provide cookies via 'cookies' parameter or create ./cookies/facebook.json file with valid Facebook session cookies.", + ); + } + + // Format cookies for HTTP header + const domain = "www.facebook.com"; + const cookiesHeader = formatCookiesForHeader(cookies, domain); + if (!cookiesHeader) { + throw new Error( + "No valid Facebook cookies found. Please check that cookies are not expired and apply to facebook.com domain.", + ); + } + + const itemUrl = `https://www.facebook.com/marketplace/item/${itemId}/`; + + console.log(`Fetching Facebook marketplace item: ${itemUrl}`); + + let itemHtml: string; + try { + itemHtml = await fetchHtml(itemUrl, 1000, { + onRateInfo: (remaining, reset) => { + if (remaining && reset) { + console.log( + "\n" + + `Facebook - Rate limit remaining: ${remaining}, reset in: ${reset}s`, + ); + } + }, + cookies: cookiesHeader, + }); + } catch (err) { + if (err instanceof HttpError) { + console.warn( + `\nFacebook marketplace item access failed (${err.status}): ${err.message}`, + ); + + // Enhanced error handling based on status codes + switch (err.status) { + case 400: + case 401: + case 403: + console.warn( + "Authentication error: Invalid or expired cookies. Please update ./cookies/facebook.json with fresh session cookies.", + ); + console.warn("Try logging out and back into Facebook, then export fresh cookies."); + break; + case 404: + console.warn( + "Listing not found: The marketplace item may have been removed, sold, or the URL is invalid.", + ); + break; + case 429: + console.warn( + "Rate limited: Too many requests. Facebook is blocking access temporarily.", + ); + break; + case 500: + case 502: + case 503: + console.warn( + "Facebook server error: Marketplace may be temporarily unavailable.", + ); + break; + default: + console.warn(`Unexpected error status: ${err.status}`); + } + return null; + } + throw err; + } + + const itemData = extractFacebookItemData(itemHtml); + if (!itemData) { + logExtractionMetrics(false, itemId); + // Enhanced checking for specific failure scenarios + if (itemHtml.includes("This listing is no longer available") || + itemHtml.includes("listing has been removed") || + itemHtml.includes("This item has been sold")) { + console.warn(`Item ${itemId} appears to be sold or removed from marketplace.`); + return null; + } + + if (itemHtml.includes("log in to Facebook") || + itemHtml.includes("You must log in") || + itemHtml.includes("authentication required")) { + console.warn(`Authentication failed for item ${itemId}. Cookies may be expired.`); + return null; + } + + console.warn(`No item data found in Facebook marketplace page for item ${itemId}. This may indicate:`); + console.warn(" - The listing was removed or sold"); + console.warn(" - Authentication issues"); + console.warn(" - Facebook changed their API structure"); + console.warn(" - Network or parsing issues"); + return null; + } + + logExtractionMetrics(true, itemId); + console.log(`Successfully extracted data for item ${itemId}`); + + const parsedItem = parseFacebookItem(itemData); + if (!parsedItem) { + console.warn(`Failed to parse item ${itemId}: Invalid data structure`); + return null; + } + + // Check for sold/removed status in the parsed data with proper precedence + if (itemData.is_sold) { + console.warn(`Item ${itemId} is marked as sold in the marketplace.`); + // Still return the data but mark it as sold + parsedItem.listingStatus = "SOLD"; + } else if (!itemData.is_live) { + console.warn(`Item ${itemId} is not live/active in the marketplace.`); + parsedItem.listingStatus = itemData.is_hidden ? "HIDDEN" : + itemData.is_pending ? "PENDING" : "INACTIVE"; + } + + return parsedItem; +}