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-02-01 11:55:50 +08:00
|
|
|
const linksPath = path.join(__dirname, '../data/links.yaml');
|
2026-01-30 23:07:58 +08:00
|
|
|
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();
|