<!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>
Preview:
downloadDownload PNG
downloadDownload JPEG
downloadDownload SVG
Tip: You can change the style, width & colours of the snippet with the inspect tool before clicking Download!
Click to optimize width for Twitter