// post_feed.js — LinkedIn Feed Smart Comment Generator // Operates ONLY on linkedin.com/feed pages — fully independent from content.js (function () { 'use strict'; const BUTTON_CLASS = 'lja-comment-btn'; const BOX_CLASS = 'lja-comment-box'; // ─── Utility: get stored settings ──────────────────────────────────────── function getSettings() { return new Promise(resolve => { if (!chrome || !chrome.storage) { resolve({}); return; } chrome.storage.sync.get(['apiKey', 'language'], (syncData) => { if (syncData && syncData.apiKey) { resolve(syncData); } else { chrome.storage.local.get(['apiKey', 'language'], (localData) => { resolve(localData || {}); }); } }); }); } // ─── Extract post text from a feed item ────────────────────────────────── function extractPostText(postEl) { const selectors = [ '[data-testid="expandable-text-box"]', // New LinkedIn UI '.feed-shared-update-v2__description .break-words span[dir]', '.feed-shared-text-view span[dir]', '.update-components-text span[dir]', '.feed-shared-update-v2__description', '.feed-shared-text', ]; for (const sel of selectors) { const el = postEl.querySelector(sel); if (el) { // Strip out the "... more" text if it exists const text = el.textContent.replace(/…\s*more/gi, '').trim(); if (text.length > 20) return text; } } return null; } // ─── Find the action bar in a post ─────────────────────────────────────── function findActionBar(postEl) { const selectors = [ '.feed-shared-social-action-bar', '.social-actions-bar', '[data-urn] .feed-shared-social-action-bar', ]; for (const sel of selectors) { const el = postEl.querySelector(sel); if (el) return el; } // New UI fallback: find the comment button SVG and get its parent container const commentSvg = postEl.querySelector('svg#comment-small, svg[data-test-icon="comment-small"]'); if (commentSvg) { const btn = commentSvg.closest('button'); if (btn && btn.parentNode) { return btn.parentNode; // This is the action bar container } } return null; } // ─── Find comment input field ───────────────────────────────────────────── function findCommentInput(postEl) { const selectors = [ '.comments-comment-box__form-container .ql-editor', '.comments-comment-texteditor .ql-editor', '.ql-editor[contenteditable="true"]', '[contenteditable="true"][role="textbox"]', 'div[contenteditable="true"]' ]; for (const sel of selectors) { const el = postEl.querySelector(sel); if (el) return el; } return null; } // ─── Fill comment input (handles LinkedIn's Quill editor) ──────────────── function fillCommentInput(inputEl, text) { inputEl.focus(); inputEl.innerHTML = ''; document.execCommand('insertText', false, text); inputEl.dispatchEvent(new InputEvent('input', { bubbles: true, data: text })); inputEl.dispatchEvent(new Event('change', { bubbles: true })); } // ─── Create the comment suggestion box ─────────────────────────────────── function createCommentBox(postEl, commentText, arabicSummary) { const existing = postEl.querySelector('.' + BOX_CLASS); if (existing) existing.remove(); const box = document.createElement('div'); box.className = BOX_CLASS; box.innerHTML = `
🤖 Smart Comment & Analysis
📝 ملخص المنشور:
${arabicSummary}
`; box.querySelector('.lja-cb-close').addEventListener('click', () => box.remove()); box.querySelector('.lja-cb-copy').addEventListener('click', function () { const text = box.querySelector('.lja-cb-text').value; navigator.clipboard.writeText(text).then(() => { this.textContent = '✅ Copied!'; setTimeout(() => { this.textContent = '📋 Copy'; }, 1500); }); }); box.querySelector('.lja-cb-paste').addEventListener('click', function () { const text = box.querySelector('.lja-cb-text').value; const commentTrigger = postEl.querySelector( '.comment-button, [aria-label*="comment" i], [data-control-name="comment"], svg#comment-small' ); let btnToClick = commentTrigger; if (btnToClick && btnToClick.tagName.toLowerCase() === 'svg') { btnToClick = btnToClick.closest('button') || btnToClick; } if (btnToClick) btnToClick.click(); setTimeout(() => { const inputEl = findCommentInput(postEl); if (inputEl) { fillCommentInput(inputEl, text); this.textContent = '✅ Done!'; setTimeout(() => { this.textContent = '✅ Paste into Comment'; }, 1500); } else { navigator.clipboard.writeText(text); this.textContent = '📋 Copied! Paste manually'; } }, 500); }); box.querySelector('.lja-cb-regen').addEventListener('click', async function () { box.remove(); await generateComment(postEl); }); const actionBar = findActionBar(postEl); if (actionBar) { actionBar.parentNode.insertBefore(box, actionBar.nextSibling); } else { postEl.appendChild(box); } } // ─── Core: call server and generate comment ─────────────────────────────── async function generateComment(postEl) { const settings = await getSettings(); if (!settings || !settings.apiKey) { alert('Please set your Gemini API key in the extension popup first.\n(Debug: Key not found in storage)'); return; } const postText = extractPostText(postEl); if (!postText) { alert('Could not read post text. The post might be an image or video.'); return; } const btn = postEl.querySelector('.' + BUTTON_CLASS); if (btn) { btn.disabled = true; btn.innerHTML = ' Thinking...'; } try { const response = await chrome.runtime.sendMessage({ type: 'GEMINI_REQUEST', payload: { apiKey: settings.apiKey, action: 'generateComment', postText: postText } }); if (!response.success) { throw new Error(response.error || 'Unknown error'); } // Parse the JSON returned by Gemini let resultData; let rawText = response.data.comment || response.data; // Clean up markdown blocks if the AI accidentally adds them rawText = rawText.replace(/```json/gi, '').replace(/```/g, '').trim(); try { resultData = JSON.parse(rawText); } catch (parseError) { throw new Error('Failed to parse AI response. Raw output: ' + rawText); } const arabicSummary = resultData.arabic_summary || 'لم يتم توليد ملخص.'; // Wrap the comment in a code block automatically before placing it in the text area const commentText = `\`\`\`\n${resultData.comment}\n\`\`\``; createCommentBox(postEl, commentText, arabicSummary); } catch (e) { console.error('[LJA Feed]', e); alert('Comment generation failed: ' + e.message); } finally { if (btn) { btn.disabled = false; btn.innerHTML = '💬 Smart Comment'; } } } // ─── Core: call server and repurpose post ─────────────────────────────── async function repurposePost(postEl) { const settings = await getSettings(); if (!settings || !settings.apiKey) { alert('Please set your Gemini API key in the extension popup first.\n(Debug: Key not found in storage)'); return; } const postText = extractPostText(postEl); if (!postText) { alert('Could not read post text. The post might be an image or video.'); return; } const btn = postEl.querySelector('.lja-rewrite-btn'); if (btn) { btn.disabled = true; btn.innerHTML = ' Rewriting...'; } try { const response = await chrome.runtime.sendMessage({ type: 'GEMINI_REQUEST', payload: { apiKey: settings.apiKey, action: 'repurposePost', postText: postText } }); if (!response.success) { throw new Error(response.error || 'Unknown error'); } const resultText = response.data.result || response.data; // We reuse the comment box UI but style it differently, or just use the same box but change the title and actions. createRepurposeBox(postEl, resultText); } catch (e) { console.error('[LJA Feed]', e); alert('Post rewriting failed: ' + e.message); } finally { if (btn) { btn.disabled = false; btn.innerHTML = '📝 Rewrite'; } } } // ─── Create the repurpose result box ───────────────────────────────────── function createRepurposeBox(postEl, resultText) { const existing = postEl.querySelector('.' + BOX_CLASS); if (existing) existing.remove(); const isRTL = /[\u0600-\u06FF]/.test(resultText); // Split text into Post and Image Prompt if the separator exists let postContent = resultText; let imagePrompt = ''; const parts = resultText.split('--- IMAGE PROMPT ---'); if (parts.length > 1) { postContent = parts[0].trim(); imagePrompt = parts[1].trim(); } const box = document.createElement('div'); box.className = BOX_CLASS; box.innerHTML = `
✍️ Rewritten Post
Post Content:
${imagePrompt ? `
Image Generation Prompt (Midjourney / DALL-E):
` : ''}
${imagePrompt ? '' : ''}
`; box.querySelector('.lja-cb-close').addEventListener('click', () => box.remove()); box.querySelector('.lja-cb-copy').addEventListener('click', function () { const text = box.querySelector('.lja-cb-text').value; navigator.clipboard.writeText(text).then(() => { this.textContent = '✅ Post Copied!'; setTimeout(() => { this.textContent = '📋 Copy Post'; }, 1500); }); }); const copyImgBtn = box.querySelector('.lja-cb-copy-img'); if (copyImgBtn) { copyImgBtn.addEventListener('click', function () { const text = box.querySelector('.lja-cb-image-prompt').value; navigator.clipboard.writeText(text).then(() => { this.textContent = '✅ Prompt Copied!'; setTimeout(() => { this.textContent = '🎨 Copy Image Prompt'; }, 1500); }); }); } const actionBar = findActionBar(postEl); if (actionBar) { actionBar.parentNode.insertBefore(box, actionBar.nextSibling); } else { postEl.appendChild(box); } } // ─── Inject button into a single post ──────────────────────────────────── function injectButton(postEl) { if (postEl.querySelector('.' + BUTTON_CLASS)) return; // Already injected const actionBar = findActionBar(postEl); if (!actionBar) return; if (!extractPostText(postEl)) return; // Skip non-text posts silently const btn = document.createElement('button'); btn.className = BUTTON_CLASS; btn.innerHTML = '💬 Smart Comment'; btn.title = 'Generate an AI-powered comment for this post'; btn.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); generateComment(postEl); }); const rewriteBtn = document.createElement('button'); rewriteBtn.className = 'lja-rewrite-btn'; rewriteBtn.innerHTML = '📝 Rewrite'; rewriteBtn.title = 'Rewrite this post in your own style and generate an image prompt'; // Add some inline styles for the rewrite button to differentiate it rewriteBtn.style.background = 'linear-gradient(135deg, #FF8787 0%, #E03131 100%)'; rewriteBtn.style.color = 'white'; rewriteBtn.style.border = 'none'; rewriteBtn.style.borderRadius = '16px'; rewriteBtn.style.padding = '4px 12px'; rewriteBtn.style.fontSize = '12px'; rewriteBtn.style.fontWeight = '600'; rewriteBtn.style.cursor = 'pointer'; rewriteBtn.style.marginLeft = '8px'; rewriteBtn.style.display = 'flex'; rewriteBtn.style.alignItems = 'center'; rewriteBtn.style.gap = '4px'; rewriteBtn.addEventListener('click', (e) => { e.stopPropagation(); e.preventDefault(); repurposePost(postEl); }); actionBar.appendChild(btn); actionBar.appendChild(rewriteBtn); } // ─── Process all posts on the page ─────────────────────────────────────── function processAllPosts() { // Check both classic LinkedIn UI and the new React/hash-class UI const posts = document.querySelectorAll( '.feed-shared-update-v2, .occludable-update, [data-urn*="activity"], [role="listitem"]' ); posts.forEach(injectButton); } // ─── MutationObserver: watch for new posts (infinite scroll) ───────────── let observerTimer; const observer = new MutationObserver(() => { // Throttle the DOM scanning to prevent heavy performance hits on React updates if (observerTimer) return; observerTimer = setTimeout(() => { processAllPosts(); observerTimer = null; }, 500); }); // ─── Initialize ────────────────────────────────────────────────────────── function init() { processAllPosts(); observer.observe(document.body, { childList: true, subtree: true }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); setTimeout(init, 2000); // Fallback for delayed SPA renders } })();