Note 03/11/2026 17:07:47

// ==UserScript== // @name XF Find Next Comment // @namespace http://tampermonkey.net/ // @version 1.4 // @description Thêm nút tìm bình luận tiếp theo + comment có ảnh/đính kèm + auto browse (XenForo: xamvn, rphang) // @author Alex // @include https://xamvn.tld/threads/* // @include https://rphang.tld/t/* // @grant none // ==/UserScript== (function () { 'use strict'; console.log('[XF-Find-Next] Script đang chạy...'); // CSS cho nút action bar const style = document.createElement('style'); style.textContent = ` .actionBar-action--findNext, .actionBar-action--findAttach, .actionBar-action--autoBrowse { font-weight: 600; color: inherit; } .actionBar-action--findNext:hover, .actionBar-action--findAttach:hover, .actionBar-action--autoBrowse:hover { text-decoration: underline !important; } .actionBar-action--findNext.searching, .actionBar-action--findAttach.searching { color: #d97706 !important; cursor: wait; } .actionBar-action--autoBrowse.active { color: #16a34a !important; animation: autoPulse 1.5s ease-in-out infinite; } @keyframes autoPulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } /* Floating auto-browse panel */ #xf-auto-browse-panel { position: fixed; bottom: 20px; right: 20px; background: rgba(15, 23, 42, 0.95); color: #e2e8f0; padding: 12px 18px; border-radius: 12px; font-family: system-ui, sans-serif; font-size: 14px; z-index: 999999; box-shadow: 0 8px 32px rgba(0,0,0,0.4); border: 1px solid rgba(255,255,255,0.1); display: flex; align-items: center; gap: 12px; transition: opacity 0.3s, transform 0.3s; } #xf-auto-browse-panel .auto-status { display: flex; flex-direction: column; gap: 2px; } #xf-auto-browse-panel .auto-status-main { font-weight: 600; } #xf-auto-browse-panel .auto-status-sub { font-size: 12px; color: #94a3b8; } #xf-auto-browse-panel .auto-countdown { font-size: 20px; font-weight: 700; color: #60a5fa; min-width: 28px; text-align: center; } #xf-auto-browse-panel .auto-stop-btn { background: #dc2626; color: #fff; border: none; padding: 6px 14px; border-radius: 8px; cursor: pointer; font-weight: 600; font-size: 13px; transition: background 0.2s; } #xf-auto-browse-panel .auto-stop-btn:hover { background: #b91c1c; } #xf-auto-browse-panel .auto-progress { position: absolute; bottom: 0; left: 0; height: 3px; background: #60a5fa; border-radius: 0 0 12px 12px; transition: width 1s linear; } #xf-auto-browse-panel .auto-manual-label { display: flex; align-items: center; gap: 4px; font-size: 12px; color: #94a3b8; cursor: pointer; user-select: none; white-space: nowrap; } #xf-auto-browse-panel .auto-manual-label input { cursor: pointer; accent-color: #60a5fa; } `; document.head.appendChild(style); // ================================================================ // AUTO-BROWSE STATE // ================================================================ let autoBrowseActive = false; let autoBrowseTimer = null; let autoScrollTimer = null; let autoPanel = null; let autoCurrentPost = null; // Post đang xem hiện tại (để hỗ trợ ← →) let autoNavigating = false; // Đang trong quá trình điều hướng (tránh spam phím) let isManualMode = localStorage.getItem('xf-auto-manual') === 'true'; // Chế độ manual (chỉ dùng mũi tên) const AUTO_DELAY_SEC = 5; const AUTO_SCROLL_SPEED = 60; function createAutoPanel() { if (autoPanel) return autoPanel; autoPanel = document.createElement('div'); autoPanel.id = 'xf-auto-browse-panel'; autoPanel.innerHTML = ` <div class="auto-status"> <span class="auto-status-main">📎 Auto Browse</span> <span class="auto-status-sub">Đang chờ...</span> </div> <span class="auto-countdown">${AUTO_DELAY_SEC}</span> <label class="auto-manual-label"> <input type="checkbox" id="xf-auto-manual-cb" ${isManualMode ? 'checked' : ''}> Manual </label> <button class="auto-stop-btn">Dừng (Esc)</button> <div class="auto-progress" style="width:0%"></div> <div style="font-size:11px;color:#64748b;margin-left:4px;">← → Esc</div> `; autoPanel.querySelector('.auto-stop-btn').onclick = () => stopAutoBrowse(); autoPanel.querySelector('#xf-auto-manual-cb').onchange = (e) => { isManualMode = e.target.checked; localStorage.setItem('xf-auto-manual', isManualMode); if (isManualMode) { // Đang countdown thì hủy, chuyển sang chờ mũi tên cancelCurrentAutoAction(); updateAutoPanel('📎 Manual', 'Nhấn ← → để chuyển', '⏸'); updateAutoProgress(0); } else if (autoCurrentPost) { // Bỏ manual → bắt đầu countdown lại autoCountdown(AUTO_DELAY_SEC, () => { if (!autoBrowseActive) return; autoFindNextFrom(autoCurrentPost); }); } }; document.body.appendChild(autoPanel); return autoPanel; } function updateAutoPanel(statusText, subText, countdown) { if (!autoPanel) return; autoPanel.querySelector('.auto-status-main').textContent = statusText; autoPanel.querySelector('.auto-status-sub').textContent = subText || ''; if (countdown !== undefined) { autoPanel.querySelector('.auto-countdown').textContent = countdown; } } function updateAutoProgress(percent) { if (!autoPanel) return; autoPanel.querySelector('.auto-progress').style.width = percent + '%'; } function removeAutoPanel() { if (autoPanel) { autoPanel.style.opacity = '0'; autoPanel.style.transform = 'translateY(20px)'; setTimeout(() => { autoPanel.remove(); autoPanel = null; }, 300); } } function stopAutoBrowse() { autoBrowseActive = false; autoCurrentPost = null; autoNavigating = false; if (autoBrowseTimer) { clearTimeout(autoBrowseTimer); autoBrowseTimer = null; } if (autoScrollTimer) { cancelAnimationFrame(autoScrollTimer); autoScrollTimer = null; } removeAutoPanel(); document.querySelectorAll('.actionBar-action--autoBrowse.active').forEach(b => { b.classList.remove('active'); b.innerHTML = 'Auto 📎 ▶'; }); console.log('[XF-Find-Next] Auto browse đã dừng.'); } // Hủy countdown/scroll hiện tại (dùng khi nhấn mũi tên) function cancelCurrentAutoAction() { if (autoBrowseTimer) { clearTimeout(autoBrowseTimer); autoBrowseTimer = null; } if (autoScrollTimer) { cancelAnimationFrame(autoScrollTimer); autoScrollTimer = null; } } // Cuộn mượt xuống cuối post, trả về Promise resolve khi xong function smoothScrollToBottom(postElement) { return new Promise(resolve => { const postRect = postElement.getBoundingClientRect(); const postBottom = postRect.bottom + window.scrollY; const viewportH = window.innerHeight; // Nếu post nằm gọn trong viewport → không cần cuộn thêm if (postBottom <= window.scrollY + viewportH + 50) { resolve(); return; } updateAutoPanel('📎 Đang cuộn...', 'Comment dài, cuộn xuống cuối', '⬇'); function scrollStep() { if (!autoBrowseActive) { resolve(); return; } const currentBottom = window.scrollY + viewportH; if (currentBottom >= postBottom - 30) { resolve(); return; } window.scrollBy(0, AUTO_SCROLL_SPEED); autoScrollTimer = requestAnimationFrame(scrollStep); } autoScrollTimer = requestAnimationFrame(scrollStep); }); } // Countdown rồi gọi callback function autoCountdown(seconds, callback) { let remaining = seconds; updateAutoPanel('📎 Auto Browse', `Chuyển tiếp sau...`, remaining); updateAutoProgress(0); function tick() { if (!autoBrowseActive) return; remaining--; const progress = ((seconds - remaining) / seconds) * 100; updateAutoProgress(progress); updateAutoPanel('📎 Auto Browse', `Chuyển tiếp sau...`, remaining); if (remaining <= 0) { callback(); } else { autoBrowseTimer = setTimeout(tick, 1000); } } autoBrowseTimer = setTimeout(tick, 1000); } // Điều hướng + cuộn + countdown cho một post trên trang hiện tại async function navigateToAttachPost(postElement) { if (!autoBrowseActive) return; autoCurrentPost = postElement; // Lưu vị trí hiện tại autoNavigating = true; const targetId = postElement.getAttribute('id'); if (targetId) { postElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); await new Promise(r => setTimeout(r, 500)); } if (!autoBrowseActive) { autoNavigating = false; return; } highlightElement(postElement); // Cuộn mượt xuống cuối post nếu dài await smoothScrollToBottom(postElement); autoNavigating = false; if (!autoBrowseActive) return; if (isManualMode) { // Chế độ manual: chờ mũi tên updateAutoPanel('📎 Manual', 'Nhấn ← → để chuyển', '⏸'); updateAutoProgress(0); } else { // Chế độ auto: countdown rồi tìm tiếp autoCountdown(AUTO_DELAY_SEC, () => { if (!autoBrowseActive) return; autoFindNextFrom(postElement); }); } } // Tìm attachment tiếp theo từ một post (dùng cho auto-browse) async function autoFindNextFrom(startPost) { if (!autoBrowseActive) return; // Tìm trên trang hiện tại const allPosts = Array.from(document.querySelectorAll('.message')); const startIndex = allPosts.indexOf(startPost); for (let i = startIndex + 1; i < allPosts.length; i++) { if (hasAttachment(allPosts[i])) { await navigateToAttachPost(allPosts[i]); return; } } // Không còn trên trang hiện tại → chuyển trang let nextButton = document.querySelector('a.pageNav-jump--next'); let nextUrl = nextButton ? nextButton.href : null; if (!nextUrl) { updateAutoPanel('📎 Hoàn tất', 'Đã đến trang cuối, không còn ảnh/đính kèm', '✅'); setTimeout(() => stopAutoBrowse(), 2000); return; } updateAutoPanel('📎 Đang quét...', 'Chuyển sang trang tiếp theo', '🔍'); let pagesScanned = 0; const MAX_PAGES = 50; while (nextUrl && autoBrowseActive && pagesScanned < MAX_PAGES) { pagesScanned++; updateAutoPanel('📎 Đang quét...', `Trang tiếp #${pagesScanned}`, '🔍'); try { const response = await fetch(nextUrl); const html = await response.text(); const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const posts = doc.querySelectorAll('.message'); for (let i = 0; i < posts.length; i++) { if (hasAttachment(posts[i])) { const postId = posts[i].getAttribute('id'); if (!postId) continue; // Chuyển trang (sẽ reload, auto-browse sẽ dừng) // Lưu trạng thái auto vào sessionStorage để tiếp tục sau reload sessionStorage.setItem('xf-auto-browse', JSON.stringify({ active: true, targetId: postId })); window.location.href = nextUrl + '#' + postId; return; } } const nextBtnDoc = doc.querySelector('a.pageNav-jump--next'); nextUrl = nextBtnDoc ? nextBtnDoc.href : null; await new Promise(r => setTimeout(r, 300)); } catch (err) { console.error('[XF-Find-Next] Auto browse lỗi:', err); updateAutoPanel('❌ Lỗi', err.message, '!'); setTimeout(() => stopAutoBrowse(), 3000); return; } } updateAutoPanel('📎 Hoàn tất', 'Không còn comment có ảnh/đính kèm', '✅'); setTimeout(() => stopAutoBrowse(), 2000); } // Tìm attachment TRƯỚC ĐÓ từ một post (dùng cho mũi tên trái) async function autoFindPrevFrom(startPost) { if (!autoBrowseActive) return; const allPosts = Array.from(document.querySelectorAll('.message')); const startIndex = allPosts.indexOf(startPost); // Tìm ngược trên trang hiện tại for (let i = startIndex - 1; i >= 0; i--) { if (hasAttachment(allPosts[i])) { cancelCurrentAutoAction(); await navigateToAttachPost(allPosts[i]); return; } } // Không còn trên trang hiện tại → thông báo updateAutoPanel('📎 Auto Browse', 'Đã ở đầu trang, không có ảnh trước đó', '⬆'); // Reset countdown tại post hiện tại setTimeout(() => { if (!autoBrowseActive || autoCurrentPost !== startPost) return; autoCountdown(AUTO_DELAY_SEC, () => { if (!autoBrowseActive) return; autoFindNextFrom(startPost); }); }, 1500); } // Bắt đầu auto-browse từ post hiện tại function startAutoBrowse(startPost) { if (autoBrowseActive) { stopAutoBrowse(); return; } autoBrowseActive = true; createAutoPanel(); updateAutoPanel('📎 Auto Browse', 'Đang tìm ảnh/đính kèm đầu tiên...', '🔍'); console.log('[XF-Find-Next] Auto browse bắt đầu.'); // Mark nút active document.querySelectorAll('.actionBar-action--autoBrowse').forEach(b => { b.classList.add('active'); b.innerHTML = 'Auto 📎 ⏸'; }); autoFindNextFrom(startPost); } // Phím tắt: Escape = dừng, ArrowRight = tiến, ArrowLeft = lùi document.addEventListener('keydown', (e) => { if (!autoBrowseActive) return; if (e.key === 'Escape') { stopAutoBrowse(); return; } // Chỉ xử lý mũi tên khi không đang trong input/textarea const tag = document.activeElement?.tagName; if (tag === 'INPUT' || tag === 'TEXTAREA' || document.activeElement?.isContentEditable) return; if (e.key === 'ArrowRight' && autoCurrentPost) { e.preventDefault(); if (autoNavigating) return; // Đang điều hướng, bỏ qua cancelCurrentAutoAction(); updateAutoPanel('📎 Tiến ▶', 'Nhấn → để next nhanh', '▶'); autoFindNextFrom(autoCurrentPost); } if (e.key === 'ArrowLeft' && autoCurrentPost) { e.preventDefault(); if (autoNavigating) return; cancelCurrentAutoAction(); updateAutoPanel('📎 Lùi ◀', 'Nhấn ← để quay lại', '◀'); autoFindPrevFrom(autoCurrentPost); } }); // Khôi phục auto-browse sau khi chuyển trang function resumeAutoBrowse() { const saved = sessionStorage.getItem('xf-auto-browse'); if (!saved) return; sessionStorage.removeItem('xf-auto-browse'); const data = JSON.parse(saved); if (!data.active) return; // Chờ DOM load xong rồi tìm post target setTimeout(() => { const targetPost = data.targetId ? document.getElementById(data.targetId) : null; if (targetPost && hasAttachment(targetPost)) { autoBrowseActive = true; createAutoPanel(); document.querySelectorAll('.actionBar-action--autoBrowse').forEach(b => { b.classList.add('active'); b.innerHTML = 'Auto 📎 ⏸'; }); navigateToAttachPost(targetPost); } else { // Target không tìm thấy, thử tìm post đầu tiên có attachment autoBrowseActive = true; createAutoPanel(); document.querySelectorAll('.actionBar-action--autoBrowse').forEach(b => { b.classList.add('active'); b.innerHTML = 'Auto 📎 ⏸'; }); const firstPost = document.querySelector('.message'); if (firstPost) { // Tìm từ đầu trang, dùng post giả trước firstPost const allPosts = Array.from(document.querySelectorAll('.message')); for (const p of allPosts) { if (hasAttachment(p)) { navigateToAttachPost(p); return; } } // Không có attachment nào, tìm trang tiếp autoFindNextFrom(allPosts[allPosts.length - 1]); } } }, 1500); } // Hàm chèn nút vào mỗi post function addButtons() { const posts = document.querySelectorAll('.message'); let addedCount = 0; posts.forEach(post => { // Tránh chèn trùng lặp if (post.dataset.hasFindNext) return; const username = post.getAttribute('data-author'); if (!username) return; post.dataset.hasFindNext = 'true'; let added = false; // Chèn nút vào Action Bar (góc dưới cùng bên phải của bài viết) let targetActionBar = post.querySelector('.message-actionBar .actionBar-set--external'); if (!targetActionBar) targetActionBar = post.querySelector('.message-actionBar .actionBar-set'); if (!targetActionBar) targetActionBar = post.querySelector('.message-actionBar'); if (targetActionBar) { // Nút tìm bình luận tiếp theo của cùng user const btnAction = document.createElement('a'); btnAction.href = '#'; btnAction.className = 'actionBar-action actionBar-action--findNext'; btnAction.innerHTML = 'Next Comment ⬇️'; btnAction.title = 'Tìm bình luận tiếp theo của ' + username; btnAction.onclick = (e) => { e.preventDefault(); findNextComment(post, username, btnAction); }; targetActionBar.appendChild(btnAction); // Nút tìm comment có ảnh/đính kèm (không phải emoji) const btnAttach = document.createElement('a'); btnAttach.href = '#'; btnAttach.className = 'actionBar-action actionBar-action--findAttach'; btnAttach.innerHTML = 'Next Attach 📎'; btnAttach.title = 'Tìm comment tiếp theo có ảnh hoặc đính kèm'; btnAttach.onclick = (e) => { e.preventDefault(); findNextAttachment(post, btnAttach); }; targetActionBar.appendChild(btnAttach); // Nút auto-browse (tự động next ảnh/đính kèm) const btnAuto = document.createElement('a'); btnAuto.href = '#'; btnAuto.className = 'actionBar-action actionBar-action--autoBrowse'; btnAuto.innerHTML = 'Auto 📎 ▶'; btnAuto.title = 'Tự động duyệt ảnh/đính kèm (5s mỗi comment)'; btnAuto.onclick = (e) => { e.preventDefault(); startAutoBrowse(post); }; targetActionBar.appendChild(btnAuto); added = true; } if (added) addedCount++; }); if (addedCount > 0) { console.log('[XF-Find-Next] Đã chèn thêm nút Next Comment vào ' + addedCount + ' bài viết.'); } } // Hàm xử lý tìm kiếm async function findNextComment(startPost, username, btn) { if (btn.classList.contains('searching')) return; btn.classList.add('searching'); btn.innerHTML = 'Đang tìm... ⏳'; try { // 1. Quét tìm trên trang HIỆN TẠI const allPostsOnPage = Array.from(document.querySelectorAll('.message')); const startIndex = allPostsOnPage.indexOf(startPost); for (let i = startIndex + 1; i < allPostsOnPage.length; i++) { if (allPostsOnPage[i].getAttribute('data-author') === username) { const targetId = allPostsOnPage[i].getAttribute('id'); if (targetId) { window.location.hash = targetId; highlightElement(allPostsOnPage[i]); } resetBtn(btn); return; } } // 2. Không có ở trang hiện tại, quét ngầm các TRANG TIẾP THEO let nextButton = document.querySelector('a.pageNav-jump--next'); let nextUrl = nextButton ? nextButton.href : null; if (!nextUrl) { alert(`Không tìm thấy bình luận nào khác của ${username} ở bên dưới, và đây đã là trang cuối.`); resetBtn(btn); return; } let foundPostId = null; let foundUrl = null; let pagesScanned = 0; const MAX_PAGES_TO_SCAN = 50; while (nextUrl && !foundUrl && pagesScanned < MAX_PAGES_TO_SCAN) { pagesScanned++; btn.innerHTML = `Đang quét trang tiếp (${pagesScanned})... ⏳`; const response = await fetch(nextUrl); const html = await response.text(); const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const posts = doc.querySelectorAll('.message'); for (let i = 0; i < posts.length; i++) { if (posts[i].getAttribute('data-author') === username) { foundPostId = posts[i].getAttribute('id'); if (!foundPostId) continue; foundUrl = nextUrl + '#' + foundPostId; break; } } if (foundUrl) { btn.innerHTML = 'Đã thấy! Đang chuyển... ✅'; window.location.href = foundUrl; return; } const nextBtnDoc = doc.querySelector('a.pageNav-jump--next'); nextUrl = nextBtnDoc ? nextBtnDoc.href : null; // Nghỉ xíu tránh spam request server await new Promise(r => setTimeout(r, 300)); } if (!foundUrl) { if (pagesScanned >= MAX_PAGES_TO_SCAN) { alert(`Đã quét ${MAX_PAGES_TO_SCAN} trang nhưng không tìm thấy. Có thể bình luận tiếp theo ở quá xa.`); } else { alert(`Đã quét tới trang cuối cùng nhưng không tìm thấy thêm bình luận nào của ${username}.`); } } } catch (err) { console.error(err); alert('Lỗi trong quá trình quét: ' + err.message); } resetBtn(btn); } // === Kiểm tra post có ảnh/đính kèm thực sự (không phải emoji) === // Bỏ qua nội dung nằm trong blockquote (trích dẫn bài người khác) function hasAttachment(postElement) { const body = postElement.querySelector('.message-body, .message-content'); if (!body) return false; // Clone body và xóa hết blockquote/quote block để không tính ảnh/video trong quote const cleanBody = body.cloneNode(true); cleanBody.querySelectorAll('blockquote, .bbCodeBlock--quote, .quoteContainer, .Quote').forEach(bq => bq.remove()); // 1. Ảnh thực sự (loại trừ emoji/smilie) const imgs = cleanBody.querySelectorAll('img'); for (const img of imgs) { // Bỏ qua emoji/smilie if (img.classList.contains('smilie') || img.closest('.smilie') || img.dataset.shortname || (img.src && /smilies|emoji/i.test(img.src)) || (img.alt && /^:[a-zA-Z0-9_]+:$/.test(img.alt.trim()))) { continue; } // Ảnh thực sự return true; } // 2. Video embed if (cleanBody.querySelector('video, .bbMediaWrapper video')) return true; // 3. Section đính kèm có link file const attachSection = postElement.querySelector('section.message-attachments'); if (attachSection) { const links = attachSection.querySelectorAll('a[href]'); for (const link of links) { const href = link.href.toLowerCase(); if (/\.(jpg|jpeg|png|gif|webp|bmp|svg|mp4|mov|webm|avi|mkv|m4v|zip|rar|pdf|doc|docx|xls|xlsx)/.test(href)) { return true; } } // Nếu có attachment list thì cũng tính if (attachSection.querySelector('.attachmentList')) return true; } // 4. Attachment list ngoài section const attachList = postElement.querySelector('.attachmentList'); if (attachList && attachList.querySelectorAll('a[href]').length > 0) return true; return false; } // === Tìm comment tiếp theo có ảnh/đính kèm === async function findNextAttachment(startPost, btn) { if (btn.classList.contains('searching')) return; btn.classList.add('searching'); btn.innerHTML = 'Đang tìm... ⏳'; try { // 1. Quét trang hiện tại const allPosts = Array.from(document.querySelectorAll('.message')); const startIndex = allPosts.indexOf(startPost); for (let i = startIndex + 1; i < allPosts.length; i++) { if (hasAttachment(allPosts[i])) { const targetId = allPosts[i].getAttribute('id'); if (targetId) { window.location.hash = targetId; highlightElement(allPosts[i]); } resetAttachBtn(btn); return; } } // 2. Quét các trang tiếp theo let nextButton = document.querySelector('a.pageNav-jump--next'); let nextUrl = nextButton ? nextButton.href : null; if (!nextUrl) { alert('Không tìm thấy comment có ảnh/đính kèm phía dưới, và đây đã là trang cuối.'); resetAttachBtn(btn); return; } let foundUrl = null; let pagesScanned = 0; const MAX_PAGES = 50; while (nextUrl && !foundUrl && pagesScanned < MAX_PAGES) { pagesScanned++; btn.innerHTML = `Đang quét trang (${pagesScanned})... ⏳`; const response = await fetch(nextUrl); const html = await response.text(); const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const posts = doc.querySelectorAll('.message'); for (let i = 0; i < posts.length; i++) { if (hasAttachment(posts[i])) { const postId = posts[i].getAttribute('id'); if (!postId) continue; foundUrl = nextUrl + '#' + postId; break; } } if (foundUrl) { btn.innerHTML = 'Đã thấy! Đang chuyển... ✅'; window.location.href = foundUrl; return; } const nextBtnDoc = doc.querySelector('a.pageNav-jump--next'); nextUrl = nextBtnDoc ? nextBtnDoc.href : null; await new Promise(r => setTimeout(r, 300)); } if (!foundUrl) { if (pagesScanned >= MAX_PAGES) { alert(`Đã quét ${MAX_PAGES} trang nhưng không tìm thấy comment có ảnh/đính kèm.`); } else { alert('Đã quét tới trang cuối nhưng không tìm thấy comment có ảnh/đính kèm.'); } } } catch (err) { console.error(err); alert('Lỗi trong quá trình quét: ' + err.message); } resetAttachBtn(btn); } function highlightElement(element) { element.style.transition = 'background-color 0.5s ease'; const origBg = element.style.backgroundColor; element.style.backgroundColor = 'rgba(59, 130, 246, 0.2)'; setTimeout(() => { element.style.backgroundColor = origBg; }, 1500); } function resetBtn(btn) { btn.classList.remove('searching'); btn.innerHTML = 'Next Comment ⬇️'; } function resetAttachBtn(btn) { btn.classList.remove('searching'); btn.innerHTML = 'Next Attach 📎'; } addButtons(); // Khôi phục auto-browse nếu đang chuyển trang resumeAutoBrowse(); // MutationObserver quan sát những comment load ajax / load chậm const observer = new MutationObserver((mutations) => { let shouldCheck = false; for (const m of mutations) { if (m.addedNodes.length > 0) { shouldCheck = true; break; } } if (shouldCheck) { if (window.addBtnTimer) clearTimeout(window.addBtnTimer); window.addBtnTimer = setTimeout(addButtons, 1000); } }); observer.observe(document.body, { childList: true, subtree: true }); // Đảm bảo nút được thêm kể cả khi trang load chậm setTimeout(addButtons, 1500); setTimeout(addButtons, 3000); })();

Public Last updated: 2026-03-11 05:07:47 PM