Files
blog/scripts/check_links.js
T

109 lines
3.4 KiB
JavaScript
Raw Normal View History

2026-01-18 23:35:56 +08:00
const fs = require('fs');
2026-01-30 23:07:58 +08:00
const yaml = require('js-yaml');
const axios = require('axios');
2026-01-18 23:35:56 +08:00
const path = require('path');
2026-01-31 15:14:57 +08:00
const https = require('https');
const http = require('http');
2026-01-18 23:35:56 +08:00
2026-01-30 23:07:58 +08:00
const linksPath = path.join(__dirname, '../themes/Ying/data/links.yaml');
const outputPath = path.join(__dirname, '../static/link_status.json');
2026-01-18 23:35:56 +08:00
2026-01-30 23:07:58 +08:00
// Ensure static dir exists
if (!fs.existsSync(path.dirname(outputPath))) {
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
}
2026-01-31 15:14:57 +08:00
// Config
const CONCURRENCY = 10;
const TIMEOUT = 10000;
// Optimized Axios Instance with Keep-Alive
const axiosInstance = axios.create({
timeout: TIMEOUT,
maxRedirects: 5,
httpAgent: new http.Agent({ keepAlive: true }),
httpsAgent: new https.Agent({ keepAlive: true }),
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
},
validateStatus: (status) => status >= 200 && status < 400
});
async function checkUrl(url) {
try {
await axiosInstance.head(url);
return true;
} catch (error) {
try {
await axiosInstance.get(url);
return true;
} catch (err2) {
return false;
}
}
}
2026-01-30 23:07:58 +08:00
async function checkLinks() {
console.log('Starting link check...');
2026-01-31 15:14:57 +08:00
const start = Date.now();
2026-01-18 23:35:56 +08:00
try {
2026-01-30 23:07:58 +08:00
const fileContents = fs.readFileSync(linksPath, 'utf8');
const data = yaml.load(fileContents);
2026-01-18 23:35:56 +08:00
2026-01-30 23:07:58 +08:00
if (!data || !data.blogs) {
console.log('No blogs found in links.yaml');
return;
}
2026-01-31 15:14:57 +08:00
const blogs = data.blogs.filter(b => b.url);
2026-01-30 23:07:58 +08:00
const results = {};
2026-01-31 15:14:57 +08:00
const failedLinks = [];
let completed = 0;
const total = blogs.length;
// Concurrency Pool
async function worker() {
while (blogs.length > 0) {
const blog = blogs.shift();
const status = await checkUrl(blog.url);
results[blog.url] = status;
completed++;
const icon = status ? '🟢' : '🔴';
console.log(`[${completed}/${total}] ${icon} ${blog.name}`);
if (!status) {
failedLinks.push(`- ${blog.name}: ${blog.url}`);
2026-01-18 23:35:56 +08:00
}
2026-01-31 15:14:57 +08:00
}
2026-01-18 23:35:56 +08:00
}
2026-01-31 15:14:57 +08:00
// Start workers
const workers = Array(Math.min(blogs.length, CONCURRENCY))
.fill(null)
.map(() => worker());
await Promise.all(workers);
2026-01-30 23:07:58 +08:00
fs.writeFileSync(outputPath, JSON.stringify(results, null, 2));
2026-01-31 15:14:57 +08:00
const duration = ((Date.now() - start) / 1000).toFixed(2);
console.log(`✨ Check finished in ${duration}s. Status saved to ${outputPath}`);
// Write failed links to summary file for email notification
const summaryPath = path.join(__dirname, '../failed_links_summary.txt');
if (failedLinks.length > 0) {
const summaryContent = `\n🚫 监测到 ${failedLinks.length} 个异常友链:\n${failedLinks.join('\n')}\n`;
fs.writeFileSync(summaryPath, summaryContent);
console.log(`Failed links summary saved to ${summaryPath}`);
} else {
fs.writeFileSync(summaryPath, '\n✅ 所有友链状态正常\n');
}
2026-01-18 23:35:56 +08:00
} catch (e) {
2026-01-30 23:07:58 +08:00
console.error(e);
2026-01-18 23:35:56 +08:00
process.exit(1);
}
}
2026-01-30 23:07:58 +08:00
checkLinks();