diff --git a/chrom-ext/background.js b/chrom-ext/background.js new file mode 100644 index 0000000..d01afe7 --- /dev/null +++ b/chrom-ext/background.js @@ -0,0 +1,63 @@ +// ===== 1. TẠO CONTEXT MENU ===== +chrome.runtime.onInstalled.addListener(() => { + chrome.contextMenus.create({ + id: 'writeComment', + title: '✍️ Viết comment', + contexts: ['all'], + documentUrlPatterns: ['https://x.com/*', 'https://twitter.com/*'] + }); +}); + +// ===== 2. KHI CLICK VÀO MENU ===== +chrome.contextMenus.onClicked.addListener((info, tab) => { + if (info.menuItemId === 'writeComment') { + chrome.tabs.sendMessage(tab.id, { action: 'OPEN_FORM' }).catch(() => { + // Nếu content script chưa sẵn sàng (hiếm) thì bỏ qua + }); + } +}); + +// ===== 3. NHẬN YÊU CẦU TỪ CONTENT SCRIPT VÀ GỌI API ===== +chrome.runtime.onMessage.addListener((request, sender) => { + if (request.action === 'GENERATE_COMMENT') { + callYourAPI(request.data, sender.tab.id); + } +}); + +async function callYourAPI({ + text, + tone, + angle, + language + }, tabId) { + try { + // ⚠️ THAY BẰNG ENDPOINT CỦA BẠN + const API_URL = 'https://gamerdota0042-himalayas.nord:3000'; + const commentApi='/content-writer/comment' + const API_KEY = 'YOUR_API_KEY_HERE'; // Nên chuyển sang chrome.storage nếu publish + + const res = await fetch(`${API_URL}${commentApi}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${API_KEY}` + }, + body: JSON.stringify({ + originalPost: text, + tone: tone, + angle: angle, + language, + }) + }); + + if (!res.ok) throw new Error(`HTTP ${res.status}`); + + const data = await res.json(); + // Giả định API trả về: { comment: "..." } + const comment = data.comment || data.text || JSON.stringify(data); + + chrome.tabs.sendMessage(tabId, { action: 'SHOW_RESULT', comment }); + } catch (err) { + chrome.tabs.sendMessage(tabId, { action: 'SHOW_ERROR', error: err.message }); + } +} \ No newline at end of file diff --git a/chrom-ext/content.js b/chrom-ext/content.js new file mode 100644 index 0000000..fadd2c5 --- /dev/null +++ b/chrom-ext/content.js @@ -0,0 +1,210 @@ +(() => { + 'use strict'; + + let selectedTweetText = ''; + + // ===== A. LẮNG NGHE CHUỘT PHẢI ĐỂ LẤY TWEET TEXT ===== + document.addEventListener('contextmenu', (e) => { + const article = e.target.closest('article[data-testid="tweet"]'); + if (!article) { + selectedTweetText = ''; + return; + } + const textContainer = article.querySelector('[data-testid="tweetText"]'); + selectedTweetText = textContainer ? textContainer.innerText.trim() : ''; + }); + + // ===== B. LẮNG NGHE MESSAGE TỪ BACKGROUND ===== + chrome.runtime.onMessage.addListener((req, sender, sendResponse) => { + if (req.action === 'OPEN_FORM') { + if (!selectedTweetText) { + alert('Không tìm thấy nội dung tweet. Hãy chuột phải vào vùng văn bản của tweet.'); + return; + } + openModal(selectedTweetText); + } else if (req.action === 'SHOW_RESULT') { + showResult(req.comment); + } else if (req.action === 'SHOW_ERROR') { + showError(req.error); + } + }); + + // ===== C. MỞ MODAL (DÙNG SHADOW DOM ĐỂ TRÁNH CSS XUNG ĐỘT) ===== + function openModal(tweetText) { + removeModal(); // Xóa cũ nếu có + + const host = document.createElement('div'); + host.id = 'x-comment-ai-host'; + Object.assign(host.style, { + position: 'fixed', + top: '0', + left: '0', + width: '100%', + height: '100%', + zIndex: '999999', + pointerEvents: 'none' + }); + document.body.appendChild(host); + + const shadow = host.attachShadow({ mode: 'open' }); + + const style = document.createElement('style'); + style.textContent = ` + .overlay { + position: fixed; inset: 0; + background: rgba(0,0,0,0.55); + display: flex; align-items: center; justify-content: center; + pointer-events: auto; + } + .modal { + background: #fff; color: #0f1419; + width: 420px; max-width: 92vw; + border-radius: 16px; padding: 24px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + box-shadow: 0 25px 50px rgba(0,0,0,0.25); + display: flex; flex-direction: column; gap: 14px; + animation: popIn 0.2s ease-out; + } + @keyframes popIn { from {opacity:0; transform:scale(0.96)} to {opacity:1; transform:scale(1)} } + h3 { margin: 0; font-size: 20px; } + .tweet-box { + background: #f7f9f9; border: 1px solid #cfd9de; border-radius: 10px; + padding: 12px; font-size: 14px; line-height: 1.4; + max-height: 120px; overflow-y: auto; color: #333; + } + label { font-size: 13px; font-weight: 700; color: #536471; } + select, button { + width: 100%; padding: 10px 12px; border-radius: 8px; + border: 1px solid #cfd9de; font-size: 14px; outline: none; + } + button.primary { + background: #1d9bf0; color: #fff; border: none; + font-weight: 700; cursor: pointer; margin-top: 4px; + } + button.primary:hover { background: #1a8cd8; } + button.primary:disabled { background: #8ecdf7; cursor: default; } + button.ghost { + background: transparent; color: #536471; border: 1px solid #cfd9de; margin-top: 6px; + } + .status { + display: none; padding: 12px; border-radius: 10px; font-size: 14px; line-height: 1.5; white-space: pre-wrap; + } + .status.ok { background: #e8f6fe; border: 1px solid #b4dffc; color: #0f1419; } + .status.err { background: #ffeaea; border: 1px solid #ffc5c5; color: #b00; } + .copy-hint { font-size: 12px; color: #536471; margin-top: 6px; text-align: center; } + `; + + const overlay = document.createElement('div'); + overlay.className = 'overlay'; + overlay.addEventListener('click', (e) => { + if (e.target === overlay) removeModal(); + }); + + const modal = document.createElement('div'); + modal.className = 'modal'; + modal.innerHTML = ` +