- Gefällt mir
- Antworten
diff --git a/extension/content.js b/extension/content.js index 3fa90f1..f47fa97 100644 --- a/extension/content.js +++ b/extension/content.js @@ -21,6 +21,7 @@ const trackerElementsByPost = new WeakMap(); const postAdditionalNotes = new WeakMap(); const REELS_PATH_PREFIX = '/reel/'; +const POST_TEXT_LOG_TAG = '[FB PostText]'; function isOnReelsPage() { try { @@ -2376,21 +2377,51 @@ async function createTrackerUI(postElement, buttonBar, postNum = '?', options = // Insert UI - try multiple strategies to find stable insertion point let inserted = false; + const tryInsertBeforeReelsCommentComposer = () => { + const textboxCandidates = postElement ? postElement.querySelectorAll('div[role="textbox"]') : []; + const composerElement = Array.from(textboxCandidates).find((element) => { + const ariaLabel = (element.getAttribute('aria-label') || '').toLowerCase(); + const ariaPlaceholder = (element.getAttribute('aria-placeholder') || '').toLowerCase(); + const combined = `${ariaLabel} ${ariaPlaceholder}`; + return combined.includes('komment') || combined.includes('comment'); + }); + + if (!composerElement) { + return false; + } + + const anchorRoot = composerElement.closest('form[role="presentation"]') + || composerElement.closest('form') + || composerElement.parentElement; + + if (!anchorRoot || !anchorRoot.parentElement) { + return false; + } + + anchorRoot.parentElement.insertBefore(container, anchorRoot); + console.log('[FB Tracker] Post #' + postNum + ' - UI inserted before comment composer (Reels complementary). ID: #' + container.id); + return true; + }; + + if (!inserted && isOnReelsPage() && postElement && postElement.matches('div[role="complementary"]')) { + inserted = tryInsertBeforeReelsCommentComposer(); + } + // Strategy 1: After button bar's parent (more stable) - if (buttonBar && buttonBar.parentElement && buttonBar.parentElement.parentElement) { + if (!inserted && buttonBar && buttonBar.parentElement && buttonBar.parentElement.parentElement) { const grandParent = buttonBar.parentElement.parentElement; grandParent.insertBefore(container, buttonBar.parentElement.nextSibling); console.log('[FB Tracker] Post #' + postNum + ' - UI inserted after button bar parent. ID: #' + container.id); inserted = true; } // Strategy 2: After button bar directly - else if (buttonBar && buttonBar.parentElement) { + if (!inserted && buttonBar && buttonBar.parentElement) { buttonBar.parentElement.insertBefore(container, buttonBar.nextSibling); console.log('[FB Tracker] Post #' + postNum + ' - UI inserted after button bar. ID: #' + container.id); inserted = true; } // Strategy 3: Append to post element - else { + if (!inserted) { postElement.appendChild(container); console.log('[FB Tracker] Post #' + postNum + ' - UI inserted into article (fallback). ID: #' + container.id); inserted = true; @@ -3027,13 +3058,64 @@ function extractPostText(postElement) { return ''; } - // Try to find the post content div + const logPostText = (...args) => { + try { + console.log(POST_TEXT_LOG_TAG, ...args); + } catch (error) { + // ignore logging failure + } + }; + + const SKIP_TEXT_CONTAINERS_SELECTOR = [ + 'div[role="textbox"]', + '[contenteditable="true"]', + '[data-lexical-editor="true"]', + 'form[role="presentation"]', + 'form[method]', + '.fb-tracker-ui', + '.fb-tracker-ai-wrapper', + '[aria-label*="komment"]', + '[aria-label*="comment"]', + '[aria-roledescription*="komment"]', + '[aria-roledescription*="comment"]' + ].join(', '); + + const KEYWORD_HINTS = ['meta', 'facebook', 'instagram']; + + const isInsideSkippedRegion = (element) => { + if (!element || typeof element.closest !== 'function') { + return false; + } + return Boolean(element.closest(SKIP_TEXT_CONTAINERS_SELECTOR)); + }; + + const scoreCandidate = (text) => { + const base = text.length; + const lower = text.toLowerCase(); + let bonus = 0; + for (const keyword of KEYWORD_HINTS) { + if (lower.includes(keyword)) { + bonus += 200; + } + } + return base + bonus; + }; + + const makeSnippet = (text) => { + if (!text) { + return ''; + } + const trimmed = text.trim(); + return trimmed.length > 140 ? `${trimmed.substring(0, 137)}…` : trimmed; + }; + const contentSelectors = [ '[data-ad-preview="message"]', '[data-ad-comet-preview="message"]', - 'div[dir="auto"][style*="text-align"]', 'div[data-ad-comet-preview] > div > div > span', - '.x193iq5w.xeuugli' // Common Facebook text class + '.x193iq5w.xeuugli', // Common Facebook text class + 'span[dir="auto"]', + 'div[dir="auto"]' ]; const uiTextPattern = /(Gefällt mir|Kommentieren|Teilen|Like|Comment|Share)/gi; @@ -3053,11 +3135,13 @@ function extractPostText(postElement) { .trim(); if (!cleaned) { + logPostText('Discard empty candidate after cleaning'); return ''; } // Ignore very short snippets that are likely button labels if (cleaned.length < 5 && cleaned.split(/\s+/).length < 2) { + logPostText('Discard very short candidate', makeSnippet(text)); return ''; } @@ -3065,34 +3149,86 @@ function extractPostText(postElement) { }; const candidates = []; + const seen = new Set(); + + const tryAddCandidate = (rawText, element = null, context = {}) => { + const candidate = cleanCandidate(rawText); + if (!candidate) { + if (rawText) { + logPostText('Candidate rejected during cleaning', makeSnippet(rawText), context); + } + return; + } + if (seen.has(candidate)) { + logPostText('Candidate skipped as duplicate', makeSnippet(candidate), context); + return; + } + if (element && isInsideSkippedRegion(element)) { + logPostText('Candidate inside skipped region', makeSnippet(candidate), context); + return; + } + seen.add(candidate); + candidates.push({ + text: candidate, + score: scoreCandidate(candidate) + }); + logPostText('Candidate accepted', { + score: scoreCandidate(candidate), + snippet: makeSnippet(candidate), + context + }); + }; + + logPostText('Begin extraction'); for (const selector of contentSelectors) { const elements = postElement.querySelectorAll(selector); for (const element of elements) { - const candidate = cleanCandidate(element.innerText || element.textContent || ''); - if (candidate) { - candidates.push(candidate); + if (isInsideSkippedRegion(element)) { + logPostText('Selector result skipped (inside disallowed region)', selector, makeSnippet(element.innerText || element.textContent || '')); + continue; } - } - if (candidates.length) { - break; + tryAddCandidate(element.innerText || element.textContent || '', element, { selector }); } } let textContent = ''; if (candidates.length) { - textContent = candidates.reduce((longest, current) => ( - current.length > longest.length ? current : longest - ), ''); + const best = candidates.reduce((top, current) => ( + current.score > top.score ? current : top + ), candidates[0]); + textContent = best.text; + logPostText('Best candidate selected', { + score: best.score, + snippet: makeSnippet(best.text) + }); } - // Fallback: Get all text but filter out common UI elements if (!textContent) { - const allText = postElement.innerText || postElement.textContent || ''; - textContent = cleanCandidate(allText); + let fallbackText = ''; + try { + const clone = postElement.cloneNode(true); + const elementsToRemove = clone.querySelectorAll(SKIP_TEXT_CONTAINERS_SELECTOR); + elementsToRemove.forEach((node) => node.remove()); + const cloneText = clone.innerText || clone.textContent || ''; + fallbackText = cleanCandidate(cloneText); + logPostText('Fallback extracted from clone', makeSnippet(fallbackText || cloneText)); + } catch (error) { + const allText = postElement.innerText || postElement.textContent || ''; + fallbackText = cleanCandidate(allText); + logPostText('Fallback extracted from original element', makeSnippet(fallbackText || allText)); + } + textContent = fallbackText; } + if (!textContent) { + logPostText('No usable text found'); + return ''; + } + + logPostText('Final post text', makeSnippet(textContent)); + return textContent.substring(0, 2000); // Limit length } diff --git a/fb_complementaryPost.txt b/fb_complementaryPost.txt new file mode 100644 index 0000000..c534d5f --- /dev/null +++ b/fb_complementaryPost.txt @@ -0,0 +1,10 @@ +