const assert = require('assert'); // 1. URL detection helper function isFacebookUrl(url) { try { const parsed = new URL(url); const host = parsed.hostname.toLowerCase(); const isFb = host.includes('facebook.com') || host.includes('fb.watch') || host.includes('fb.com'); if (!isFb) return false; return host.includes('fb.watch') || parsed.pathname.includes('/reel') || parsed.pathname.includes('/watch') || parsed.pathname.includes('/videos') || parsed.pathname.includes('/share/'); } catch (e) { return false; } } function cleanSocialCaption(raw) { if (!raw) return ''; let text = raw.replace(/(?:\s*#[^\s#]+)+\s*$/g, '').trim(); text = text.replace(/[\uFE00-\uFE0F\uD83C-\uDBFF\uDC00-\uDFFF\u2600-\u26FF\u2700-\u27BF]+$/, '').trim(); return text; } function cleanFacebookCaption(raw) { if (!raw) return ''; let text = raw.trim(); const quotedMatch = text.match(/(?:on Facebook:\s*["“]|:\s*["“])([\s\S]*?)["”]?$/i); if (quotedMatch && quotedMatch[1]) { text = quotedMatch[1]; } else { const colonQuote = text.indexOf(': "'); if (colonQuote !== -1) { text = text.slice(colonQuote + 3).replace(/["”]$/, ''); } } text = text.replace(/\s*\|\s*By\s+[^|]+\|\s*Facebook.*$/i, ''); text = text.replace(/\s*\|\s*Facebook.*$/i, ''); return cleanSocialCaption(text); } function isCaptionTruncated(text) { if (!text || typeof text !== 'string') return false; const trimmed = text.trim(); if (/(?:\.{3,}|…)\s*(?:See\s+more)?$/i.test(trimmed)) return true; if (/\bSee\s+more$/i.test(trimmed)) return true; if (trimmed.length >= 180 && trimmed.length <= 320) { const endsWithPunctuation = /[.!?\n\r"']$/.test(trimmed); if (!endsWithPunctuation) return true; } return false; } function extractFullFacebookMessageFromHtml(html) { if (!html || typeof html !== 'string') return ''; let longestText = ''; const isBoilerplate = (str) => /log in|watch popular videos|sign up for facebook|connect with friends/i.test(str); const messageRegex = /"(?:message|savable_description)"\s*:\s*\{\s*"text"\s*:\s*"((?:[^"\\]|\\.)*)"/g; let match; while ((match = messageRegex.exec(html)) !== null) { try { const decoded = JSON.parse(`"${match[1]}"`); if (decoded && decoded.length > longestText.length && !isBoilerplate(decoded)) { longestText = decoded; } } catch (e) {} } const storyRegex = /"creation_story"\s*:\s*\{[\s\S]*?"message"\s*:\s*\{\s*"text"\s*:\s*"((?:[^"\\]|\\.)*)"/g; while ((match = storyRegex.exec(html)) !== null) { try { const decoded = JSON.parse(`"${match[1]}"`); if (decoded && decoded.length > longestText.length && !isBoilerplate(decoded)) { longestText = decoded; } } catch (e) {} } const ldJsonRegex = /]*>([\s\S]*?)<\/script>/gi; while ((match = ldJsonRegex.exec(html)) !== null) { try { const data = JSON.parse(match[1]); const body = data.articleBody || data.description || ''; if (body && body.length > longestText.length && !isBoilerplate(body)) { longestText = body; } } catch (e) {} } return cleanFacebookCaption(longestText); } console.log('Testing isFacebookUrl...'); assert.strictEqual(isFacebookUrl('https://www.facebook.com/reel/123456789'), true); assert.strictEqual(isFacebookUrl('https://fb.watch/abcdef123/'), true); assert.strictEqual(isFacebookUrl('https://m.facebook.com/reel/987654321/'), true); assert.strictEqual(isFacebookUrl('https://www.facebook.com/watch/?v=11223344'), true); assert.strictEqual(isFacebookUrl('https://www.facebook.com/share/r/abc123xyz/'), true); assert.strictEqual(isFacebookUrl('https://www.instagram.com/reel/xyz123'), false); console.log('✔ isFacebookUrl tests passed!'); console.log('\nTesting isCaptionTruncated...'); assert.strictEqual(isCaptionTruncated('1 cup flour, 2 eggs, 1 tbsp butter...'), true); assert.strictEqual(isCaptionTruncated('1 cup flour, 2 eggs, 1 tbsp butter…'), true); assert.strictEqual(isCaptionTruncated('1 cup flour, 2 eggs, 1 tbsp butter... See more'), true); assert.strictEqual(isCaptionTruncated('1 cup flour, 2 eggs, 1 tbsp butter… See More'), true); assert.strictEqual(isCaptionTruncated('This is a truncated facebook description that hits around two hundred characters and stops abruptly in the middle of a sentence without any final punctuation mark like a period so it indicates cut off'), true); assert.strictEqual(isCaptionTruncated('1 cup flour, 2 eggs. Mix thoroughly and bake for 25 minutes at 350F.'), false); console.log('✔ isCaptionTruncated tests passed!'); console.log('\nTesting extractFullFacebookMessageFromHtml...'); const sampleHtml = ` `; const extracted = extractFullFacebookMessageFromHtml(sampleHtml); assert.ok(extracted.includes('4 cloves garlic, minced')); assert.ok(extracted.includes('Directions:')); assert.strictEqual(isCaptionTruncated(extracted), false); console.log('✔ extractFullFacebookMessageFromHtml successfully retrieved complete message!'); console.log('\nAll Facebook Reel scraper & truncation tests passed successfully!');