GOOGLE RANKING CHECKER
Wed May 07 2025 16:53:12 GMT+0000 (Coordinated Universal Time)
Saved by @thanhsonnguyen #html #javascript
<!DOCTYPE html> <html lang="vi"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Kiểm tra Thứ hạng Google</title> <style> body { font-family: Arial, sans-serif; line-height: 1.6; margin: 0; padding: 20px; color: #333; } .container { max-width: 1200px; margin: 0 auto; } h1 { text-align: center; margin-bottom: 20px; } .form-row { margin-bottom: 15px; display: flex; flex-wrap: wrap; gap: 10px; } label { display: block; margin-bottom: 5px; font-weight: bold; } input[type="text"], textarea, select { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; } button { background-color: #4CAF50; color: white; border: none; padding: 10px 15px; border-radius: 4px; cursor: pointer; font-size: 16px; } button:hover { background-color: #45a049; } .loading { display: none; margin: 20px 0; text-align: center; } .progress-container { width: 100%; background-color: #f1f1f1; border-radius: 4px; margin-top: 10px; } .progress-bar { width: 0%; height: 30px; background-color: #4CAF50; text-align: center; line-height: 30px; color: white; border-radius: 4px; } table { width: 100%; border-collapse: collapse; margin-top: 20px; } th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; } th { background-color: #f2f2f2; } tr:hover { background-color: #f5f5f5; } .error { color: red; margin-top: 10px; display: none; } .success { color: green; margin-top: 10px; display: none; } .results-actions { display: none; justify-content: space-between; margin-top: 20px; } .tooltip { position: relative; display: inline-block; cursor: pointer; } .tooltip .tooltiptext { visibility: hidden; width: 200px; background-color: #555; color: #fff; text-align: center; border-radius: 6px; padding: 5px; position: absolute; z-index: 1; bottom: 125%; left: 50%; margin-left: -100px; opacity: 0; transition: opacity 0.3s; } .tooltip:hover .tooltiptext { visibility: visible; opacity: 1; } .rank-good { background-color: #d4edda; color: #155724; } .rank-medium { background-color: #fff3cd; color: #856404; } .rank-bad { background-color: #f8d7da; color: #721c24; } .rank-none { background-color: #e2e3e5; color: #383d41; } .copy-btn { background-color: #007bff; } .copy-btn:hover { background-color: #0069d9; } .redirect-info { font-size: 12px; color: #666; margin-top: 3px; } .report-settings { background-color: #f9f9f9; padding: 15px; border-radius: 5px; margin-bottom: 15px; } /* Pháo hoa CSS */ .pyro { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 999; pointer-events: none; display: none; } .pyro > .before, .pyro > .after { position: absolute; width: 5px; height: 5px; border-radius: 50%; box-shadow: 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff; animation: 1s bang ease-out infinite backwards, 1s gravity ease-in infinite backwards, 5s position linear infinite backwards; } .pyro > .after { animation-delay: 1.25s, 1.25s, 1.25s; animation-duration: 1.25s, 1.25s, 6.25s; } @keyframes bang { to { box-shadow: -70px -115.67px #00ff73, -28px -99.67px #a6ff00, 58px -31.67px #0051ff, 13px -7.67px #00ffa2, -19px -33.67px #ff00d0, -37px -23.67px #ff8800, 19px -78.67px #ff002f, 56px -87.67px #00ffcc, -29px -45.67px #ff5e00, 1px -66.67px #ff1500, 42px -123.67px #91ff00, 21px -108.67px #b300ff, -23px -3.67px #ffd000, -65px -55.67px #ff4800, -63px -27.67px #00ff88, 46px 0.33px #0055ff, 75px -86.67px #8cff00, -11px -117.67px #00ff4d, 69px -125.67px #ff0033, 82px -36.67px #00ffbb, -39px -92.67px #00ff73, 49px -124.67px #ff0040, 94px -7.67px #ff6600, 82px 22.33px #ff001a, -14px -98.67px #00ffd5, 27px 7.33px #00ff33, -68px -18.67px #0080ff, 89px -42.67px #ff00fb, -88px -93.67px #ff0004, -62px -59.67px #00ff8c, 52px -21.67px #00ff33, 74px 6.33px #ff00bf, -42px -69.67px #00ff8c, -9px -92.67px #00ff8c, 26px -65.67px #ff0004, 57px -51.67px #a2ff00, 47px -89.67px #0099ff, 74px -123.67px #ff0037, -86px -108.67px #ff4000, 76px -25.67px #0400ff, 77px -57.67px #6aff00, -13px -91.67px #00ff95, 52px -66.67px #91ff00, -42px -103.67px #00ff73, -69px -115.67px #ff0037, 89px -38.67px #ff0088, 90px -113.67px #00ff6a, -63px -42.67px #0066ff, -71px -69.67px #0400ff, 0px -53.67px #002bff, 26px -70.67px #ff006a; } } @keyframes gravity { to { transform: translateY(200px); opacity: 0; } } @keyframes position { 0%, 19.9% { margin-top: 10%; margin-left: 40%; } 20%, 39.9% { margin-top: 40%; margin-left: 30%; } 40%, 59.9% { margin-top: 20%; margin-left: 70%; } 60%, 79.9% { margin-top: 30%; margin-left: 20%; } 80%, 99.9% { margin-top: 30%; margin-left: 80%; } } /* Thông báo chúc mừng */ .celebration { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: rgba(255, 255, 255, 0.9); border: 2px solid #4CAF50; border-radius: 10px; padding: 20px; text-align: center; box-shadow: 0 0 20px rgba(0, 0, 0, 0.2); z-index: 1000; display: none; } .celebration h2 { color: #4CAF50; margin-top: 0; } .celebration p { font-size: 18px; margin-bottom: 20px; } .celebration button { background-color: #4CAF50; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; } .redirect-chain { margin-top: 5px; font-size: 0.85em; color: #666; } .redirect-chain-item { display: block; margin-bottom: 3px; padding-left: 10px; border-left: 2px solid #d35400; } @media (max-width: 768px) { .form-row { flex-direction: column; } .form-row > div { width: 100%; margin-bottom: 10px; } } </style> </head> <body> <div class="container"> <h1>Kiểm tra Thứ hạng Google</h1> <div class="form-row"> <div style="flex: 1;"> <label for="api-key">API Key (Serper.dev):</label> <input type="text" id="api-key" placeholder="Nhập API key của bạn" value="85dd0b1bd9a79f29cd3a121a23cc404a759dc00a" hidden> </div> </div> <div class="form-row"> <div style="flex: 1;"> <label for="domains">Danh sách Domain:</label> <textarea id="domains" rows="5" placeholder="Mỗi domain một dòng, ví dụ: example.com example.org"></textarea> </div> <div style="flex: 1;"> <label for="keywords">Danh sách Từ khóa:</label> <textarea id="keywords" rows="5" placeholder="Mỗi từ khóa một dòng, ví dụ: từ khóa 1 từ khóa 2"></textarea> </div> </div> <div class="form-row"> <div> <label for="device">Thiết bị:</label> <select id="device"> <option value="desktop">Desktop</option> <option value="mobile">Mobile</option> </select> </div> <div> <label for="location">Vị trí:</label> <select id="location"> <option value="">Mặc định (Việt Nam)</option> <option value="1006094">Hà Nội</option> <option value="1006113">TP. Hồ Chí Minh</option> <option value="1006151">Đà Nẵng</option> </select> </div> </div> <div class="report-settings"> <label for="report-name">Tên báo cáo:</label> <input type="text" id="report-name" placeholder="PIC" value=""> </div> <div class="form-row"> <button onclick="checkRanks()">Kiểm tra Thứ hạng</button> </div> <div id="loading" class="loading"> <p>Đang kiểm tra thứ hạng... Vui lòng đợi.</p> <div class="progress-container"> <div id="progress" class="progress-bar">0%</div> </div> <p id="progress-text">0/0</p> </div> <div id="error" class="error"></div> <div id="success" class="success"></div> <div id="results-actions" class="results-actions"> <button class="copy-btn" onclick="copyAllRanks()">Sao chép dán vào daily ranking</button> <button class="copy-btn" onclick="copyFormattedReport()">Sao chép báo cáo Viptalk</button> </div> <div id="results"></div> </div> <!-- Hiệu ứng pháo hoa --> <div class="pyro"> <div class="before"></div> <div class="after"></div> </div> <!-- Thông báo chúc mừng --> <div id="celebration" class="celebration"> <h2>🎉 Chúc mừng! 🎉</h2> <p id="celebration-message"></p> <button onclick="closeCelebration()">Đóng</button> </div> <script> // Biến toàn cục để lưu kết quả let results = []; // Kích thước batch mặc định const DEFAULT_BATCH_SIZE = 5; // Đường dẫn API redirect server const DEFAULT_REDIRECT_SERVER = "https://red.nguonkienthuc.com"; // Cache kết quả tìm kiếm const searchCache = {}; // Hàm làm sạch domain function cleanDomain(domain) { let cleanedDomain = domain; // Loại bỏ http://, https://, www. và dấu / ở cuối cleanedDomain = cleanedDomain.replace(/^https?:\/\//, ''); cleanedDomain = cleanedDomain.replace(/^www\./, ''); cleanedDomain = cleanedDomain.replace(/\/$/, ''); return { cleanedDomain: cleanedDomain.toLowerCase(), originalDomain: domain }; } // Hàm chia mảng thành các mảng con có kích thước nhỏ hơn function chunkArray(array, size) { const chunks = []; for (let i = 0; i < array.length; i += size) { chunks.push(array.slice(i, i + size)); } return chunks; } // Hàm kiểm tra nhiều chuyển hướng cùng lúc (sử dụng API như phiên bản cũ) async function checkMultipleRedirects(domains) { try { // Đảm bảo mỗi domain có protocol const urls = domains.map(domain => { if (!domain.startsWith('http://') && !domain.startsWith('https://')) { return 'https://' + domain; } return domain; }); // Gọi API kiểm tra nhiều chuyển hướng const response = await fetch(`${DEFAULT_REDIRECT_SERVER}/check-redirects`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ urls, maxRedirects: 10 }) }); if (!response.ok) { throw new Error(`Lỗi API: ${response.status}`); } return await response.json(); } catch (error) { console.error('Lỗi kiểm tra nhiều chuyển hướng:', error); return {}; } } // Hàm tìm kiếm Google với cache async function searchGoogleBatch(queries) { const apiKey = document.getElementById('api-key').value.trim(); if (!apiKey) { throw new Error('API key không được để trống'); } const device = document.getElementById('device').value; const location = document.getElementById('location').value; // Gom nhóm các từ khóa giống nhau để tránh tìm kiếm trùng lặp const uniqueQueries = []; const queryMap = new Map(); // Ánh xạ từ từ khóa đến chỉ mục trong uniqueQueries queries.forEach(query => { const cacheKey = `${query.keyword}_${device}_${location || 'default'}`; // Nếu từ khóa đã có trong cache, bỏ qua if (searchCache[cacheKey]) { return; } // Nếu từ khóa chưa được thêm vào uniqueQueries, thêm vào if (!queryMap.has(query.keyword)) { queryMap.set(query.keyword, uniqueQueries.length); uniqueQueries.push({ keyword: query.keyword, cacheKey: cacheKey }); } }); // Nếu có từ khóa cần tìm kiếm if (uniqueQueries.length > 0) { const searchQueries = uniqueQueries.map(query => { const queryParams = { q: query.keyword, device: device, gl: "vn", hl: "vi", num: 100 }; if (location) { queryParams.location = location; } return queryParams; }); const myHeaders = new Headers(); myHeaders.append("X-API-KEY", apiKey); myHeaders.append("Content-Type", "application/json"); const requestOptions = { method: "POST", headers: myHeaders, body: JSON.stringify(searchQueries), redirect: "follow" }; try { const response = await fetch("https://google.serper.dev/search", requestOptions); if (!response.ok) { const errorText = await response.text(); throw new Error(`Lỗi API: ${response.status} - ${errorText}`); } const data = await response.json(); // Lưu kết quả vào cache uniqueQueries.forEach((query, index) => { searchCache[query.cacheKey] = data[index]; }); } catch (error) { console.error("Lỗi khi tìm kiếm:", error); throw error; } } // Trả về kết quả từ cache cho tất cả queries return queries.map(query => { const cacheKey = `${query.keyword}_${device}_${location || 'default'}`; return searchCache[cacheKey]; }); } // Hàm hiển thị pháo hoa function showFireworks() { const pyro = document.querySelector('.pyro'); pyro.style.display = 'block'; // Ẩn pháo hoa sau 3 giây setTimeout(() => { pyro.style.display = 'none'; }, 3000); } // Hàm hiển thị thông báo chúc mừng function showCelebration(message) { const celebration = document.getElementById('celebration'); const celebrationMessage = document.getElementById('celebration-message'); celebrationMessage.textContent = message; celebration.style.display = 'block'; // Hiển thị pháo hoa showFireworks(); } // Hàm đóng thông báo chúc mừng function closeCelebration() { document.getElementById('celebration').style.display = 'none'; } // Hàm kiểm tra thứ hạng tối ưu async function checkRanks() { const domainsText = document.getElementById('domains').value.trim(); const keywordsText = document.getElementById('keywords').value.trim(); if (!domainsText) { showError('Vui lòng nhập danh sách domain'); return; } if (!keywordsText) { showError('Vui lòng nhập danh sách từ khóa'); return; } // Parse danh sách domain và từ khóa const domains = domainsText.split('\n') .map(domain => domain.trim()) .filter(domain => domain.length > 0); const keywords = keywordsText.split('\n') .map(keyword => keyword.trim()) .filter(keyword => keyword.length > 0); if (domains.length === 0) { showError('Không có domain hợp lệ'); return; } if (keywords.length === 0) { showError('Không có từ khóa hợp lệ'); return; } // Hiển thị loading document.getElementById('loading').style.display = 'block'; document.getElementById('error').style.display = 'none'; document.getElementById('success').style.display = 'none'; document.getElementById('results').innerHTML = ''; document.getElementById('results-actions').style.display = 'none'; // Reset kết quả results = []; // Kiểm tra chuyển hướng cho tất cả domain một lần let redirectResults = {}; try { redirectResults = await checkMultipleRedirects(domains); } catch (error) { console.error('Lỗi khi kiểm tra chuyển hướng:', error); } // Tạo danh sách các domain đã làm sạch để kiểm tra const domainInfo = domains.map(domain => { const { cleanedDomain } = cleanDomain(domain); let domainToCheck = domain; if (!domainToCheck.startsWith('http://') && !domainToCheck.startsWith('https://')) { domainToCheck = 'https://' + domainToCheck; } // Thêm thông tin chuyển hướng const redirectInfo = redirectResults[domainToCheck] || {}; return { domain: domain, cleanedDomain: cleanedDomain, redirected: redirectInfo.hasRedirect || false, redirectChain: redirectInfo.redirectChain || null, finalUrl: redirectInfo.finalUrl || null, redirectCount: redirectInfo.redirectCount || 0 }; }); // PHƯƠNG PHÁP TỐI ƯU: Tạo danh sách các từ khóa duy nhất để tìm kiếm const uniqueKeywords = [...new Set(keywords)]; // Tạo ánh xạ từ từ khóa đến các domain cần kiểm tra const keywordToDomains = new Map(); // Nếu số lượng domain và từ khóa bằng nhau, giả định mỗi domain đi với một từ khóa if (domains.length === keywords.length) { for (let i = 0; i < domains.length; i++) { if (!keywordToDomains.has(keywords[i])) { keywordToDomains.set(keywords[i], []); } keywordToDomains.get(keywords[i]).push(domainInfo[i]); } } else { // Nếu số lượng không bằng nhau, kiểm tra mọi domain với mọi từ khóa uniqueKeywords.forEach(keyword => { keywordToDomains.set(keyword, domainInfo); }); } // Tạo danh sách các query để tìm kiếm const queries = uniqueKeywords.map(keyword => ({ keyword: keyword })); // Thực hiện tìm kiếm theo batch const batchSize = DEFAULT_BATCH_SIZE; const batches = chunkArray(queries, batchSize); let completed = 0; const totalKeywords = uniqueKeywords.length; try { for (const batch of batches) { // Cập nhật tiến trình document.getElementById('progress-text').textContent = `${completed}/${totalKeywords}`; const percent = Math.round((completed / totalKeywords) * 100); document.getElementById('progress').style.width = `${percent}%`; document.getElementById('progress').textContent = `${percent}%`; // Tìm kiếm batch const searchResults = await searchGoogleBatch(batch); // Xử lý kết quả tìm kiếm for (let i = 0; i < batch.length; i++) { const keyword = batch[i].keyword; const searchResult = searchResults[i]; // Lấy danh sách domain cần kiểm tra cho từ khóa này const domainsToCheck = keywordToDomains.get(keyword) || []; // Kiểm tra từng domain trong kết quả tìm kiếm for (const domainData of domainsToCheck) { let rank = null; let matchedUrl = null; // Tạo danh sách các domain cần kiểm tra (bao gồm cả domain chuyển hướng) let domainVariants = [domainData.cleanedDomain]; // Thêm domain từ chuỗi chuyển hướng if (domainData.redirectChain && domainData.redirectChain.length > 1) { for (let j = 1; j < domainData.redirectChain.length; j++) { try { const redirectUrl = new URL(domainData.redirectChain[j].url); const redirectDomain = redirectUrl.hostname.replace(/^www\./, ''); domainVariants.push(redirectDomain.toLowerCase()); } catch (e) { console.error("Lỗi xử lý URL chuyển hướng:", e); } } } // Tìm kiếm trong kết quả Google if (searchResult && searchResult.organic) { for (let j = 0; j < searchResult.organic.length; j++) { const result = searchResult.organic[j]; try { const resultUrl = new URL(result.link); const resultDomain = resultUrl.hostname.replace(/^www\./, '').toLowerCase(); // Kiểm tra xem domain kết quả có khớp với domain cần kiểm tra không if (domainVariants.some(domain => resultDomain.includes(domain))) { rank = j + 1; matchedUrl = result.link; break; } } catch (e) { console.error("Lỗi xử lý URL kết quả:", e); } } } // Thêm kết quả results.push({ domain: domainData.domain, cleanedDomain: domainData.cleanedDomain, keyword: keyword, rank: rank, matchedUrl: matchedUrl, redirected: domainData.redirected, redirectChain: domainData.redirectChain, finalUrl: domainData.finalUrl, redirectCount: domainData.redirectCount, date: new Date().toLocaleDateString('vi-VN'), location: document.getElementById('location').options[document.getElementById('location').selectedIndex].text }); } } // Hiển thị kết quả sau mỗi batch displayResults(); // Cập nhật số lượng hoàn thành completed += batch.length; } // Cập nhật tiến trình thành 100% document.getElementById('progress-text').textContent = `${totalKeywords}/${totalKeywords}`; document.getElementById('progress').style.width = '100%'; document.getElementById('progress').textContent = '100%'; // Đếm số từ khóa trong top 10 const top10Keywords = results.filter(result => result.rank !== null && result.rank <= 6); const top10Count = top10Keywords.length; // Hiển thị thông báo chúc mừng nếu có từ khóa trong top 10 if (top10Count > 0) { // Tạo thông báo dựa trên số lượng từ khóa trong top 10 const message = `Bạn có ${top10Count} từ khóa nằm trong top 6 Google!`; // Hiển thị thông báo chúc mừng showCelebration(message); // Bắn pháo hoa nhiều lần tương ứng với số từ khóa top 10 for (let i = 0; i < top10Count; i++) { setTimeout(() => { showFireworks(); }, i * 3000); // Mỗi hiệu ứng pháo hoa cách nhau 3 giây } } showSuccess(`Đã kiểm tra thành công ${results.length} kết quả`); } catch (error) { showError(`Lỗi: ${error.message}`); } finally { // Ẩn loading document.getElementById('loading').style.display = 'none'; // Hiển thị nút sao chép nếu có kết quả if (results.length > 0) { document.getElementById('results-actions').style.display = 'flex'; } } } // Hàm hiển thị kết quả function displayResults() { const resultsDiv = document.getElementById('results'); // Tạo bảng kết quả let tableHtml = ` <table> <thead> <tr> <th>STT</th> <th>Domain</th> <th>Từ khóa</th> <th>Thứ hạng</th> <th>URL khớp</th> <th>Ngày kiểm tra</th> <th>Vị trí</th> </tr> </thead> <tbody> `; // Thêm các dòng kết quả results.forEach((result, index) => { // Xác định class cho thứ hạng let rankClass = 'rank-none'; let rankDisplay = 'Không tìm thấy'; if (result.rank !== null) { rankDisplay = result.rank; if (result.rank <= 10) { rankClass = 'rank-good'; } else if (result.rank <= 20) { rankClass = 'rank-medium'; } else { rankClass = 'rank-bad'; } } // Tạo thông tin chuyển hướng let redirectInfo = ''; if (result.redirected) { // Tạo tooltip với thông tin chi tiết về chuỗi chuyển hướng let redirectChainHtml = ''; if (result.redirectChain && result.redirectChain.length > 0) { redirectChainHtml = '<div class="redirect-chain">'; result.redirectChain.forEach((redirect, idx) => { const statusColor = redirect.status === 301 ? '#e74c3c' : '#3498db'; redirectChainHtml += ` <span class="redirect-chain-item" style="color: ${statusColor}"> ${idx + 1}. ${redirect.url} (${redirect.status}) </span> `; }); redirectChainHtml += '</div>'; } redirectInfo = ` <div class="redirect-info"> <span class="tooltip"> Đã chuyển hướng (${result.redirectCount}) <span class="tooltiptext"> ${redirectChainHtml || 'Không có thông tin chi tiết'} </span> </span> </div> `; } // Thêm dòng vào bảng tableHtml += ` <tr> <td>${index + 1}</td> <td> ${result.domain} ${redirectInfo} </td> <td>${result.keyword}</td> <td class="${rankClass}">${rankDisplay}</td> <td>${result.matchedUrl ? `<a href="${result.matchedUrl}" target="_blank">${result.matchedUrl}</a>` : 'N/A'}</td> <td>${result.date}</td> <td>${result.location}</td> </tr> `; }); // Đóng bảng tableHtml += ` </tbody> </table> `; // Hiển thị bảng resultsDiv.innerHTML = tableHtml; } // Hàm sao chép tất cả thứ hạng thành một cột function copyAllRanks() { // Tạo một chuỗi chứa tất cả thứ hạng, mỗi thứ hạng trên một dòng const ranksList = results.map(result => result.rank ? result.rank.toString() : 'N/A').join('\n'); // Sao chép vào clipboard navigator.clipboard.writeText(ranksList) .then(() => { showSuccess('Đã sao chép tất cả thứ hạng thành công!'); }) .catch(err => { console.error('Lỗi khi sao chép: ', err); showError('Không thể sao chép. Vui lòng thử lại.'); }); } // Sao chép báo cáo theo định dạng // function copyFormattedReport() { // // Lấy ngày hiện tại // const today = new Date(); // const day = today.getDate(); // const month = today.getMonth() + 1; // // Lấy tên báo cáo từ input // const reportName = document.getElementById('report-name').value.trim(); // // Lấy thông tin khu vực // const locationElement = document.getElementById('location'); // const locationText = locationElement.options[locationElement.selectedIndex].text; // const locationInfo = locationElement.value ? ` - ${locationText}` : ''; // // Bắt đầu với tiêu đề báo cáo // let report = `Ngày ${day}/${month} - [${reportName}]\n============\n\n`; // // Nhóm kết quả theo domain (sau khi đã làm sạch) // const domainGroups = {}; // results.forEach(result => { // const domain = result.cleanedDomain; // if (!domainGroups[domain]) { // domainGroups[domain] = { // originalDomain: result.domain, // cleanedDomain: domain, // keywords: [], // ranks: [], // redirected: result.redirected || false, // finalUrl: result.finalUrl || null, // redirectChain: result.redirectChain || null, // has301: false // }; // } // // Kiểm tra xem có chuyển hướng 301 không // if (result.redirectChain && result.redirectChain.length > 0) { // for (let i = 0; i < result.redirectChain.length; i++) { // if (result.redirectChain[i].status === 301) { // domainGroups[domain].has301 = true; // break; // } // } // } // domainGroups[domain].keywords.push(result.keyword); // domainGroups[domain].ranks.push(result.rank); // }); // // Tạo báo cáo cho từng domain // for (const domain in domainGroups) { // const group = domainGroups[domain]; // // Thêm domain vào báo cáo (màu xanh) // // Nếu có chuyển hướng 301, thêm đánh dấu [301] // const redirectMark = group.has301 ? ' [301]' : ''; // report += `${domain}${redirectMark}\n`; // // Nếu có chuyển hướng, hiển thị URL cuối cùng // if (group.redirected && group.finalUrl) { // // Lấy hostname từ finalUrl // try { // const finalUrlObj = new URL(group.finalUrl); // const finalDomain = finalUrlObj.hostname; // report += `→ ${finalDomain}\n`; // } catch (e) { // // Nếu không parse được URL, hiển thị toàn bộ finalUrl // report += `→ ${group.finalUrl}\n`; // } // } // // Thêm từ khóa và thứ hạng // for (let i = 0; i < group.keywords.length; i++) { // const keyword = group.keywords[i]; // const rank = group.ranks[i] || 'N/A'; // report += `${keyword} - top ${rank}\n`; // } // report += '\n'; // } // // Sao chép vào clipboard // navigator.clipboard.writeText(report) // .then(() => { // showSuccess('Đã sao chép báo cáo định dạng thành công!'); // }) // .catch(err => { // console.error('Lỗi khi sao chép: ', err); // showError('Không thể sao chép. Vui lòng thử lại.'); // }); // } // Sao chép báo cáo theo định dạng function copyFormattedReport() { // Lấy ngày hiện tại const today = new Date(); const day = today.getDate(); const month = today.getMonth() + 1; // Lấy tên báo cáo từ input const reportName = document.getElementById('report-name').value.trim(); // Lấy thông tin khu vực const locationElement = document.getElementById('location'); const locationText = locationElement.options[locationElement.selectedIndex].text; const locationInfo = locationElement.value ? ` - ${locationText}` : ''; // Bắt đầu với tiêu đề báo cáo let report = `Ngày ${day}/${month} - [${reportName}]\n============\n\n`; // Lấy danh sách domain và từ khóa ban đầu để giữ nguyên thứ tự const domainsText = document.getElementById('domains').value.trim(); const keywordsText = document.getElementById('keywords').value.trim(); const originalDomains = domainsText.split('\n') .map(domain => domain.trim()) .filter(domain => domain.length > 0); const originalKeywords = keywordsText.split('\n') .map(keyword => keyword.trim()) .filter(keyword => keyword.length > 0); // Tạo Map để lưu trữ thông tin domain đã xử lý const processedDomains = new Map(); // Nếu số lượng domain và từ khóa bằng nhau, giả định mỗi domain đi với một từ khóa if (originalDomains.length === originalKeywords.length) { // Lặp qua danh sách domain theo thứ tự ban đầu for (let i = 0; i < originalDomains.length; i++) { const domain = originalDomains[i]; const keyword = originalKeywords[i]; // Tìm kết quả tương ứng const result = results.find(r => r.domain === domain && r.keyword === keyword); if (result) { const cleanedDomain = result.cleanedDomain; // Kiểm tra xem domain đã được xử lý chưa if (!processedDomains.has(cleanedDomain)) { processedDomains.set(cleanedDomain, { originalDomain: domain, keywords: [], ranks: [], redirected: result.redirected || false, finalUrl: result.finalUrl || null, redirectChain: result.redirectChain || null, has301: false }); // Kiểm tra xem có chuyển hướng 301 không if (result.redirectChain && result.redirectChain.length > 0) { for (let j = 0; j < result.redirectChain.length; j++) { if (result.redirectChain[j].status === 301) { processedDomains.get(cleanedDomain).has301 = true; break; } } } } // Thêm từ khóa và thứ hạng vào domain const domainData = processedDomains.get(cleanedDomain); domainData.keywords.push(keyword); domainData.ranks.push(result.rank); } } // Tạo báo cáo theo thứ tự domain ban đầu const addedDomains = new Set(); for (let i = 0; i < originalDomains.length; i++) { const domain = originalDomains[i]; const { cleanedDomain } = cleanDomain(domain); // Nếu domain này đã được thêm vào báo cáo, bỏ qua if (addedDomains.has(cleanedDomain)) { continue; } // Đánh dấu domain đã được thêm vào báo cáo addedDomains.add(cleanedDomain); // Lấy thông tin domain const domainData = processedDomains.get(cleanedDomain); if (domainData) { // Thêm domain vào báo cáo const redirectMark = domainData.has301 ? ' [301]' : ''; report += `${cleanedDomain}${redirectMark}\n`; // Nếu có chuyển hướng, hiển thị URL cuối cùng if (domainData.redirected && domainData.finalUrl) { try { const finalUrlObj = new URL(domainData.finalUrl); const finalDomain = finalUrlObj.hostname; report += `→ ${finalDomain}\n`; } catch (e) { report += `→ ${domainData.finalUrl}\n`; } } // Thêm từ khóa và thứ hạng theo thứ tự for (let j = 0; j < domainData.keywords.length; j++) { const keyword = domainData.keywords[j]; const rank = domainData.ranks[j] || 'N/A'; report += `${keyword} - top ${rank}\n`; } report += '\n'; } } } else { // Nếu số lượng domain và từ khóa không bằng nhau // Nhóm kết quả theo domain (giữ nguyên thứ tự xuất hiện đầu tiên) const domainOrder = []; const domainGroups = {}; results.forEach(result => { const domain = result.cleanedDomain; if (!domainGroups[domain]) { domainOrder.push(domain); domainGroups[domain] = { originalDomain: result.domain, cleanedDomain: domain, keywords: [], ranks: [], redirected: result.redirected || false, finalUrl: result.finalUrl || null, redirectChain: result.redirectChain || null, has301: false }; } // Kiểm tra xem có chuyển hướng 301 không if (result.redirectChain && result.redirectChain.length > 0) { for (let i = 0; i < result.redirectChain.length; i++) { if (result.redirectChain[i].status === 301) { domainGroups[domain].has301 = true; break; } } } domainGroups[domain].keywords.push(result.keyword); domainGroups[domain].ranks.push(result.rank); }); // Tạo báo cáo theo thứ tự domain đã lưu for (const domain of domainOrder) { const group = domainGroups[domain]; // Thêm domain vào báo cáo const redirectMark = group.has301 ? ' [301]' : ''; report += `${domain}${redirectMark}\n`; // Nếu có chuyển hướng, hiển thị URL cuối cùng if (group.redirected && group.finalUrl) { try { const finalUrlObj = new URL(group.finalUrl); const finalDomain = finalUrlObj.hostname; report += `→ ${finalDomain}\n`; } catch (e) { report += `→ ${group.finalUrl}\n`; } } // Thêm từ khóa và thứ hạng for (let i = 0; i < group.keywords.length; i++) { const keyword = group.keywords[i]; const rank = group.ranks[i] || 'N/A'; report += `${keyword} - top ${rank}\n`; } report += '\n'; } } // Sao chép vào clipboard navigator.clipboard.writeText(report) .then(() => { showSuccess('Đã sao chép báo cáo định dạng thành công!'); }) .catch(err => { console.error('Lỗi khi sao chép: ', err); showError('Không thể sao chép. Vui lòng thử lại.'); }); } // Hàm hiển thị lỗi function showError(message) { const errorDiv = document.getElementById('error'); errorDiv.textContent = message; errorDiv.style.display = 'block'; document.getElementById('success').style.display = 'none'; } // Hàm hiển thị thông báo thành công function showSuccess(message) { const successDiv = document.getElementById('success'); successDiv.textContent = message; successDiv.style.display = 'block'; document.getElementById('error').style.display = 'none'; } </script> </body> </html>
Comments