Aktueller Stand
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
10
fb_complementaryPost.txt
Normal file
10
fb_complementaryPost.txt
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user