Files
blog/scripts/check_links.js
T
2026-01-21 13:13:54 +08:00

157 lines
5.9 KiB
JavaScript

const fs = require('fs');
const path = require('path');
// Configuration
const LINKS_FILE = path.join(__dirname, '../themes/Ying/static/json/link_lite.json');
const CONCURRENCY = 10; // Check 10 links at a time
const TIMEOUT = 10000; // 10 seconds timeout
async function checkLink(link) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT);
try {
// Some blogs block headless requests, so we fake a User-Agent
const response = await fetch(link.url, {
method: 'HEAD', // Try HEAD first to save bandwidth
signal: controller.signal,
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
}
});
clearTimeout(timeoutId);
if (response.ok) {
return { link, status: 'OK', code: response.status };
} else if (response.status === 405 || response.status === 403 || response.status === 503) {
// Some servers reject HEAD or block bots, try GET
const controllerGet = new AbortController();
const timeoutIdGet = setTimeout(() => controllerGet.abort(), TIMEOUT);
try {
const responseGet = await fetch(link.url, {
method: 'GET',
signal: controllerGet.signal,
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
}
});
clearTimeout(timeoutIdGet);
if (responseGet.ok) {
return { link, status: 'OK', code: responseGet.status };
} else {
return { link, status: 'FAIL', code: responseGet.status };
}
} catch (err) {
clearTimeout(timeoutIdGet);
return { link, status: 'ERROR', error: err.message };
}
} else {
return { link, status: 'FAIL', code: response.status };
}
} catch (error) {
clearTimeout(timeoutId);
// Retry with GET if HEAD failed with network error (sometimes happens)
if (error.name !== 'AbortError') {
const controllerGet = new AbortController();
const timeoutIdGet = setTimeout(() => controllerGet.abort(), TIMEOUT);
try {
const responseGet = await fetch(link.url, {
method: 'GET',
signal: controllerGet.signal,
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
}
});
clearTimeout(timeoutIdGet);
if (responseGet.ok) {
return { link, status: 'OK', code: responseGet.status };
} else {
return { link, status: 'ERROR', error: error.message }; // Return original error if GET also fails or is not tried properly
}
} catch (err) {
clearTimeout(timeoutIdGet);
return { link, status: 'ERROR', error: error.message };
}
}
return { link, status: 'ERROR', error: error.message };
}
}
async function main() {
console.log('Starting friend link check...');
console.log(`Reading links from: ${LINKS_FILE}`);
if (!fs.existsSync(LINKS_FILE)) {
console.error('Error: Link file not found!');
process.exit(1);
}
let linksData;
let links;
try {
const fileContent = fs.readFileSync(LINKS_FILE, 'utf8');
linksData = JSON.parse(fileContent);
// Adapt to both array (old) and object with friends key (new)
if (Array.isArray(linksData)) {
links = linksData;
} else if (linksData.friends && Array.isArray(linksData.friends)) {
// The new format has [name, url, avatar] arrays inside friends array
// We need to convert them to objects {name, url} for the checker
links = linksData.friends.map(item => {
if (Array.isArray(item)) {
return { name: item[0], url: item[1] };
}
return item;
});
} else {
console.error('Error: Unknown JSON format');
process.exit(1);
}
} catch (e) {
console.error('Error parsing JSON:', e.message);
process.exit(1);
}
console.log(`Found ${links.length} links. Checking...`);
const results = [];
const failedLinks = [];
// Process in chunks
for (let i = 0; i < links.length; i += CONCURRENCY) {
const chunk = links.slice(i, i + CONCURRENCY);
const chunkResults = await Promise.all(chunk.map(checkLink));
chunkResults.forEach(res => {
if (res.status === 'OK') {
process.stdout.write('.'); // Progress indicator
} else {
process.stdout.write('F');
failedLinks.push(res);
}
results.push(res);
});
}
console.log('\n\nCheck completed!');
console.log('---------------------------------------------------');
if (failedLinks.length === 0) {
console.log('✅ All links are accessible!');
} else {
console.log(`⚠️ Found ${failedLinks.length} inaccessible links:\n`);
failedLinks.forEach((item, index) => {
console.log(`${index + 1}. ${item.link.name} (${item.link.url})`);
if (item.code) {
console.log(` Status Code: ${item.code}`);
} else {
console.log(` Error: ${item.error}`);
}
console.log('');
});
}
}
main();