mirror of
https://gitlab.com/Zer0interrupt/recipe-manager.git
synced 2026-11-01 23:24:11 -05:00
135 lines
5.6 KiB
JavaScript
135 lines
5.6 KiB
JavaScript
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 = /<script\s+type="application\/ld\+json"[^>]*>([\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 = `
|
|
<html>
|
|
<head>
|
|
<meta property="og:description" content="Chef Gordon: Crispy Garlic Butter Steak Bites. 1 lb sirloin steak, 2 tbsp butter...">
|
|
</head>
|
|
<body>
|
|
<script type="application/json" data-sjs>
|
|
{"require":[["RelayPrefetchedStreamCache","next",[],[{"message":{"text":"Crispy Garlic Butter Steak Bites.\\n\\nIngredients:\\n- 1 lb sirloin steak\\n- 2 tbsp butter\\n- 4 cloves garlic, minced\\n\\nDirections:\\n1. Sear steak in hot pan for 3 mins.\\n2. Add butter and garlic.\\n3. Serve warm!"}}]]]}
|
|
</script>
|
|
</body>
|
|
</html>
|
|
`;
|
|
|
|
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!');
|