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 links; try { const fileContent = fs.readFileSync(LINKS_FILE, 'utf8'); links = JSON.parse(fileContent); } 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();