(() => { 'use strict'; let lastTweetText = ''; let sidebarHost = null; let isTyping = false; // chặn bấm paste nhiều lần // ===== DATA ===== const LANGS = [ { value: 'vi', label: 'Việt' }, { value: 'en', label: 'English' }, { value: 'ja', label: 'Nhật' }, { value: 'ko', label: 'Han' }, { value: 'cn', label: 'TQ' } ]; const TONE_BASE = [ { value: 'PROFESSIONAL', text: 'chuyên nghiệp, rõ ràng, đáng tin cậy' }, { value: 'CASUAL', text: 'Giản dị, thân thiện' }, { value: 'HYPE', text: 'Hype — Hào hứng, tràn đầy năng lượng' }, { value: 'URGENT', text: 'urgent' }, { value: 'HUMOROUS', text: 'Dí dỏm, hài hước' }, { value: 'INFORMATIVE', text: 'Thông tin, chính xác' }, { value: 'EMPATHETIC', text: 'empathetic — Đồng cảm, thấu hiểu cảm xúc' }, { value: 'PROVOCATIVE', text: 'provocative — Gợi mở suy nghĩ, thách thức giả định' }, { value: 'AUTHORITATIVE', text: 'authoritative — Tự tin, uy quyền' }, { value: 'SPICY', text: 'spicy — Tự tin, hơi đối đầu, chỉ thẳng' } ]; const TONE_JA_ONLY = [ { value: 'AGGRESSIVE', text: 'aggressive — Cục súc, attack ideas mạnh' }, { value: 'PROFANE', text: 'profane — Nói tục thoải mái, raw' }, { value: 'INFLAMMATORY', text: 'inflammatory — Kích động cao' }, { value: 'SAVAGE', text: 'savage — Chửi tục OK, sass tối đa' } ]; const ANGLE_DEFAULT = [ { value: 'AGREE', text: 'Đồng ý' }, { value: 'CHALLENGE', text: 'Không đồng ý' }, { value: 'ADD_INFO', text: 'Thêm thông tin liên quan hữu ích' }, { value: 'FUNNY', text: 'Hóm hỉnh, hài hước nhẹ nhàng' }, { value: 'QUESTION', text: 'Đặt câu hỏi tiếp theo thông minh' }, { value: 'RELATE', text: 'Chia sẻ trải nghiệm cá nhân tương tự' }, { value: 'DEVIL_ADVOCATE', text: 'Phản biện công bằng, không thù địch' }, { value: 'EXPAND', text: 'Phân tích sâu 1 điểm' }, { value: 'VALIDATE', text: 'Khẳng định bằng chứng mạnh mẽ' }, { value: 'CTA', text: 'Kêu gọi hành động nhẹ nhàng' } ]; const ANGLE_EMPATHY = [ { value: 'WISH_RECOVERY', text: 'Chúc hồi phục' }, { value: 'TRIBUTE', text: 'Tưởng nhớ / RIP' }, { value: 'SOLIDARITY', text: 'Đồng lòng / Đứng cùng' }, { value: 'PERSONAL_SUPPORT', text: 'Hỗ trợ cá nhân' }, { value: 'SHARED_GRIEF', text: 'Cùng nỗi buồn' } ]; function getTones(lang) { if (lang === 'ja') return [...TONE_BASE, ...TONE_JA_ONLY]; return [...TONE_BASE]; } function getAngles(tone) { return tone === 'EMPATHETIC' ? [...ANGLE_DEFAULT, ...ANGLE_EMPATHY] : [...ANGLE_DEFAULT]; } // ===== A. CAPTURE TWEET ===== document.addEventListener('contextmenu', (e) => { const article = e.target.closest('article'); if (!article) { lastTweetText = ''; return; } const textEl = article.querySelector('[data-testid="tweetText"]') || article.querySelector('div[lang]') || article.querySelector('div[dir="auto"]'); lastTweetText = textEl ? textEl.innerText.trim() : article.innerText.trim().slice(0, 600); }); // ===== B. LISTENER — FIX LỖI CHANNEL ===== chrome.runtime.onMessage.addListener((req, sender, sendResponse) => { if (req.action === 'OPEN_FORM') { if (!lastTweetText) { sendResponse({ ok: false, reason: 'no_text' }); return false; } openSidebar(lastTweetText); sendResponse({ ok: true }); return false; } if (req.action === 'SHOW_RESULT') { showResult(req.comment, req.model); sendResponse({ ok: true }); return false; } if (req.action === 'SHOW_ERROR') { showError(req.error); sendResponse({ ok: true }); return false; } sendResponse({ ok: false, unknown: true }); return false; }); // ===== C. TYPING ENGINE ===== async function simulateHumanTyping(fullText) { let editor = document.querySelector('[data-testid="tweetTextarea_0"]') || document.querySelector('div[contenteditable="true"][role="textbox"]'); if (!editor) { alert('🔍 Không tìm thấy ô reply.\n\n👉 Hãy bấm nút "Reply" của tweet trước để ô nhập hiện ra, rồi thử lại!'); return false; } editor.focus(); editor.click(); editor.textContent = ''; const textNode = document.createTextNode(''); editor.appendChild(textNode); const sel = window.getSelection(); const range = document.createRange(); range.setStart(textNode, 0); range.collapse(true); sel.removeAllRanges(); sel.addRange(range); for (let i = 0; i < fullText.length; i++) { const char = fullText[i]; textNode.nodeValue += char; range.setEnd(textNode, textNode.nodeValue.length); range.collapse(false); sel.removeAllRanges(); sel.addRange(range); editor.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true, inputType: 'insertText', data: char })); let delay = 30 + Math.random() * 50; if (char === ' ') delay += 40 + Math.random() * 40; if ('.!?,'.includes(char)) delay += 120 + Math.random() * 200; await new Promise(r => setTimeout(r, delay)); } editor.dispatchEvent(new Event('change', { bubbles: true })); return true; } // ===== D. SIDEBAR UI ===== function openSidebar(tweetText) { removeSidebar(); const host = document.createElement('div'); host.id = 'x-ai-sidebar-host'; Object.assign(host.style, { position: 'fixed', top: '0', left: '0', width: '0', height: '0', zIndex: '2147483647', overflow: 'visible', pointerEvents: 'none' }); document.body.appendChild(host); sidebarHost = host; const shadow = host.attachShadow({ mode: 'open' }); const style = document.createElement('style'); style.textContent = ` .fab { position: fixed; right: 24px; bottom: 24px; width: 56px; height: 56px; border-radius: 50%; background: #1d9bf0; color: #fff; font-size: 24px; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 4px 16px rgba(0,0,0,0.25); border: none; pointer-events: auto; z-index: 1; transition: transform .15s ease; user-select: none; } .fab:hover { transform: scale(1.08); } .drawer { position: fixed; right: 0; top: 0; width: 420px; max-width: 100vw; height: 100vh; background: #fff; color: #0f1419; box-shadow: -5px 0 25px rgba(0,0,0,0.15); transform: translateX(100%); transition: transform .25s ease; display: flex; flex-direction: column; pointer-events: auto; z-index: 2; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } .drawer.open { transform: translateX(0); } .header { padding: 14px 18px; border-bottom: 1px solid #eff3f4; display: flex; justify-content: space-between; align-items: center; font-weight: 700; font-size: 16px; } .btn-x { background: none; border: none; font-size: 20px; cursor: pointer; color: #536471; padding: 4px; line-height: 1; } .tabs { display: flex; border-bottom: 1px solid #eff3f4; } .tab-btn { flex: 1; padding: 10px; border: none; background: none; cursor: pointer; font-size: 14px; color: #536471; border-bottom: 2px solid transparent; font-weight: 600; } .tab-btn.active { color: #1d9bf0; border-bottom-color: #1d9bf0; } .tab-content { padding: 16px 18px; flex: 1; overflow-y: auto; display: none; flex-direction: column; gap: 10px; } .tab-content.active { display: flex; } .tweet-box { background: #f7f9f9; border: 1px solid #cfd9de; border-radius: 10px; padding: 10px; font-size: 13px; line-height: 1.4; max-height: 150px; overflow-y: auto; color: #333; white-space: pre-wrap; word-break: break-word; } label { font-size: 12px; font-weight: 700; color: #536471; text-transform: uppercase; letter-spacing: .3px; margin-top: 4px; } select, input[type="text"], input[type="password"] { width: 100%; padding: 9px 10px; border-radius: 8px; border: 1px solid #cfd9de; font-size: 14px; background: #fff; box-sizing: border-box; } .hint-text { font-size: 12px; color: #888; line-height: 1.4; font-style: italic; } button.primary { width: 100%; padding: 10px; border-radius: 9999px; border: none; background: #1d9bf0; color: #fff; font-weight: 700; font-size: 15px; cursor: pointer; margin-top: 6px; } button.primary:hover { background: #1a8cd8; } button.primary:disabled { background: #8ecdf7; cursor: default; } button.green { background: #17bf63 !important; } button.green:hover { background: #15a857 !important; } button.gray { background: #536471 !important; } button.gray:hover { background: #3e4e56 !important; } .status { display: none; padding: 12px; border-radius: 10px; font-size: 14px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; } .status.ok { background: #e8f6fe; border: 1px solid #b4dffc; } .status.err { background: #ffeaea; border: 1px solid #ffc5c5; color: #b00; } .copy-hint { font-size: 12px; color: #536471; text-align: center; display: none; } .badge { display: inline-block; padding: 2px 8px; border-radius: 999px; background: #ffad1f; color: #fff; font-size: 11px; font-weight: 700; } .typing-opts { margin-top: 8px; padding-top: 10px; border-top: 1px solid #eff3f4; display: none; flex-direction: column; gap: 8px; } .typing-opts.visible { display: flex; } .check-row { display: flex; align-items: center; gap: 8px; font-size: 13px; color: #0f1419; cursor: pointer; } .check-row input { cursor: pointer; } .btn-row { display: flex; gap: 8px; } .btn-row .primary { flex: 1; margin-top: 0; } `; const fab = document.createElement('button'); fab.className = 'fab'; fab.textContent = '🤖'; fab.title = 'AI Comment'; const drawer = document.createElement('div'); drawer.className = 'drawer'; drawer.innerHTML = `