-
Notifications
You must be signed in to change notification settings - Fork 117
Expand file tree
/
Copy pathlink-check.js
More file actions
160 lines (133 loc) · 4.24 KB
/
Copy pathlink-check.js
File metadata and controls
160 lines (133 loc) · 4.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import TOML from '@iarna/toml';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const PARTICIPANTS_DIR = path.join(__dirname, '_src/_data/participants');
const SAMPLE_SIZE = Infinity;
const DELAY_MS = 100;
const UPDATE_FILES = true;
const BATCH_SIZE = 100;
function getTomlFiles(dir, limit = Infinity) {
return fs.readdirSync(dir)
.filter(f => f.endsWith('.toml'))
.slice(0, limit)
.map(f => path.join(dir, f));
}
function getMaxYear(years) {
return years.length > 0 ? Math.max(...years) : new Date().getFullYear();
}
async function checkUrl(url) {
if (isArchiveUrl(url)) {
return true;
}
try {
const res = await fetch(url, { method: 'HEAD', redirect: 'manual' });
return res.status >= 200 && res.status < 400;
} catch {
return false;
}
}
function isArchiveUrl(url) {
return url.includes('web.archive.org/web/');
}
function normalizeUrl(url) {
if (!url.startsWith('http://') && !url.startsWith('https://')) {
return 'https://' + url;
}
return url;
}
async function getArchiveUrl(originalUrl, targetYear) {
try {
const normUrl = normalizeUrl(originalUrl);
const apiUrl = `https://archive.org/wayback/available?url=${encodeURIComponent(normUrl)}`;
const res = await fetch(apiUrl);
const data = await res.json();
if (!data.archived_snapshots || !data.archived_snapshots.closest) {
return null;
}
const snapshot = data.archived_snapshots.closest;
const snapshotYear = parseInt(snapshot.timestamp.slice(0, 4), 10);
if (snapshotYear <= targetYear) {
return snapshot.url;
}
for (let year = targetYear - 1; year >= 2005; year--) {
const altUrl = `https://web.archive.org/web/${year}/${normUrl}`;
const checkRes = await fetch(altUrl, { method: 'HEAD' });
if (checkRes.status === 200) {
return altUrl;
}
}
return snapshot.url;
} catch {
return null;
}
}
async function processFile(filePath) {
let content = fs.readFileSync(filePath, 'utf-8');
const data = TOML.parse(content);
const results = [];
let fileModified = false;
if (!data.websites || data.websites.length === 0) {
return results;
}
for (const site of data.websites) {
const originalUrl = site.url;
const years = site.years || [];
const maxYear = getMaxYear(years);
const isAlive = await checkUrl(originalUrl);
results.push({
file: path.basename(filePath),
originalUrl,
maxYear,
isAlive,
newUrl: null
});
if (!isAlive) {
await new Promise(r => setTimeout(r, DELAY_MS));
const archiveUrl = await getArchiveUrl(originalUrl, maxYear);
const normUrl = normalizeUrl(originalUrl);
const newUrl = archiveUrl || `https://web.archive.org/web/${normUrl}`;
results[results.length - 1].newUrl = newUrl;
if (UPDATE_FILES) {
const escapedUrl = originalUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
content = content.replace(
new RegExp(`(url\\s*=\\s*)"${escapedUrl}"`),
`$1"${newUrl}"`
);
fileModified = true;
}
}
}
if (UPDATE_FILES && fileModified) {
fs.writeFileSync(filePath, content);
}
return results;
}
async function main() {
const files = getTomlFiles(PARTICIPANTS_DIR, SAMPLE_SIZE);
let allResults = [];
for (let i = 0; i < files.length; i += BATCH_SIZE) {
const batch = files.slice(i, i + BATCH_SIZE);
for (let j = 0; j < batch.length; j++) {
const file = batch[j];
try {
console.error(`Processing ${i + j + 1}/${files.length}: ${path.basename(file)}`);
const fileResults = await processFile(file);
allResults.push(...fileResults);
} catch (err) {
console.error(`Error processing ${path.basename(file)}: ${err.message}`);
}
}
const csv = [
'file,original_url,max_year,is_alive,new_url',
...allResults.map(r =>
`"${r.file}","${r.originalUrl}","${r.maxYear}","${r.isAlive}","${r.newUrl || ''}"`
)
].join('\n');
fs.writeFileSync(path.join(__dirname, 'link-check-results.csv'), csv);
}
console.error(`\nDone: ${allResults.length} sites processed`);
}
main().catch(console.error);