Aktueller Stand
This commit is contained in:
@@ -21,6 +21,7 @@ const trackerElementsByPost = new WeakMap();
|
|||||||
const postAdditionalNotes = new WeakMap();
|
const postAdditionalNotes = new WeakMap();
|
||||||
|
|
||||||
const REELS_PATH_PREFIX = '/reel/';
|
const REELS_PATH_PREFIX = '/reel/';
|
||||||
|
const POST_TEXT_LOG_TAG = '[FB PostText]';
|
||||||
|
|
||||||
function isOnReelsPage() {
|
function isOnReelsPage() {
|
||||||
try {
|
try {
|
||||||
@@ -2376,21 +2377,51 @@ async function createTrackerUI(postElement, buttonBar, postNum = '?', options =
|
|||||||
// Insert UI - try multiple strategies to find stable insertion point
|
// Insert UI - try multiple strategies to find stable insertion point
|
||||||
let inserted = false;
|
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)
|
// 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;
|
const grandParent = buttonBar.parentElement.parentElement;
|
||||||
grandParent.insertBefore(container, buttonBar.parentElement.nextSibling);
|
grandParent.insertBefore(container, buttonBar.parentElement.nextSibling);
|
||||||
console.log('[FB Tracker] Post #' + postNum + ' - UI inserted after button bar parent. ID: #' + container.id);
|
console.log('[FB Tracker] Post #' + postNum + ' - UI inserted after button bar parent. ID: #' + container.id);
|
||||||
inserted = true;
|
inserted = true;
|
||||||
}
|
}
|
||||||
// Strategy 2: After button bar directly
|
// Strategy 2: After button bar directly
|
||||||
else if (buttonBar && buttonBar.parentElement) {
|
if (!inserted && buttonBar && buttonBar.parentElement) {
|
||||||
buttonBar.parentElement.insertBefore(container, buttonBar.nextSibling);
|
buttonBar.parentElement.insertBefore(container, buttonBar.nextSibling);
|
||||||
console.log('[FB Tracker] Post #' + postNum + ' - UI inserted after button bar. ID: #' + container.id);
|
console.log('[FB Tracker] Post #' + postNum + ' - UI inserted after button bar. ID: #' + container.id);
|
||||||
inserted = true;
|
inserted = true;
|
||||||
}
|
}
|
||||||
// Strategy 3: Append to post element
|
// Strategy 3: Append to post element
|
||||||
else {
|
if (!inserted) {
|
||||||
postElement.appendChild(container);
|
postElement.appendChild(container);
|
||||||
console.log('[FB Tracker] Post #' + postNum + ' - UI inserted into article (fallback). ID: #' + container.id);
|
console.log('[FB Tracker] Post #' + postNum + ' - UI inserted into article (fallback). ID: #' + container.id);
|
||||||
inserted = true;
|
inserted = true;
|
||||||
@@ -3027,13 +3058,64 @@ function extractPostText(postElement) {
|
|||||||
return '';
|
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 = [
|
const contentSelectors = [
|
||||||
'[data-ad-preview="message"]',
|
'[data-ad-preview="message"]',
|
||||||
'[data-ad-comet-preview="message"]',
|
'[data-ad-comet-preview="message"]',
|
||||||
'div[dir="auto"][style*="text-align"]',
|
|
||||||
'div[data-ad-comet-preview] > div > div > span',
|
'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;
|
const uiTextPattern = /(Gefällt mir|Kommentieren|Teilen|Like|Comment|Share)/gi;
|
||||||
@@ -3053,11 +3135,13 @@ function extractPostText(postElement) {
|
|||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
if (!cleaned) {
|
if (!cleaned) {
|
||||||
|
logPostText('Discard empty candidate after cleaning');
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore very short snippets that are likely button labels
|
// Ignore very short snippets that are likely button labels
|
||||||
if (cleaned.length < 5 && cleaned.split(/\s+/).length < 2) {
|
if (cleaned.length < 5 && cleaned.split(/\s+/).length < 2) {
|
||||||
|
logPostText('Discard very short candidate', makeSnippet(text));
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3065,34 +3149,86 @@ function extractPostText(postElement) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const candidates = [];
|
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) {
|
for (const selector of contentSelectors) {
|
||||||
const elements = postElement.querySelectorAll(selector);
|
const elements = postElement.querySelectorAll(selector);
|
||||||
for (const element of elements) {
|
for (const element of elements) {
|
||||||
const candidate = cleanCandidate(element.innerText || element.textContent || '');
|
if (isInsideSkippedRegion(element)) {
|
||||||
if (candidate) {
|
logPostText('Selector result skipped (inside disallowed region)', selector, makeSnippet(element.innerText || element.textContent || ''));
|
||||||
candidates.push(candidate);
|
continue;
|
||||||
}
|
}
|
||||||
}
|
tryAddCandidate(element.innerText || element.textContent || '', element, { selector });
|
||||||
if (candidates.length) {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let textContent = '';
|
let textContent = '';
|
||||||
|
|
||||||
if (candidates.length) {
|
if (candidates.length) {
|
||||||
textContent = candidates.reduce((longest, current) => (
|
const best = candidates.reduce((top, current) => (
|
||||||
current.length > longest.length ? current : longest
|
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) {
|
if (!textContent) {
|
||||||
const allText = postElement.innerText || postElement.textContent || '';
|
let fallbackText = '';
|
||||||
textContent = cleanCandidate(allText);
|
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
|
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