← back to CelebritySignatures
scripts/fetch-declaration.mjs
117 lines
#!/usr/bin/env node
// Declaration of Independence signers — the safest possible signature set:
// all 56 signers, deceased since the 1700s-1800s (no publicity rights), every
// signature public domain. Built entirely on MediaWiki/Commons/Wikidata-entity
// APIs (NOT the SPARQL endpoint), so it works even during a WDQS outage.
import { writeFile, mkdir } from 'node:fs/promises';
const UA = 'CelebritySignatures-research/1.0 (steve@designerwallcoverings.com)';
const EN = 'https://en.wikipedia.org/w/api.php';
const WD = 'https://www.wikidata.org/w/api.php';
const COMMONS = 'https://commons.wikimedia.org/w/api.php';
const CAT = 'Category:Signatories of the United States Declaration of Independence';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function api(base, params) {
const url = base + '?' + new URLSearchParams({ format: 'json', formatversion: '2', ...params });
const res = await fetch(url, { headers: { 'User-Agent': UA } });
if (!res.ok) throw new Error(`${base} ${res.status}`);
return res.json();
}
const chunk = (a, n) => Array.from({ length: Math.ceil(a.length / n) }, (_, i) => a.slice(i * n, i * n + n));
async function main() {
await mkdir('data', { recursive: true });
// 1. all 56 signer article titles
const cm = await api(EN, { action: 'query', list: 'categorymembers', cmtitle: CAT, cmlimit: '100', cmtype: 'page' });
const titles = cm.query.categorymembers.map((m) => m.title);
console.log(`signers in category: ${titles.length}`);
// 2. enwiki title -> Wikidata QID
const titleToQid = {};
for (const grp of chunk(titles, 50)) {
const r = await api(EN, { action: 'query', prop: 'pageprops', titles: grp.join('|'), ppprop: 'wikibase_item' });
for (const p of r.query.pages) if (p.pageprops?.wikibase_item) titleToQid[p.title] = p.pageprops.wikibase_item;
await sleep(200);
}
const qids = Object.values(titleToQid);
console.log(`resolved QIDs: ${qids.length}`);
// 3. Wikidata entities: P109 signature file, P570 death, sitelink count, label
const ent = {};
for (const grp of chunk(qids, 50)) {
const r = await api(WD, { action: 'wbgetentities', ids: grp.join('|'), props: 'claims|sitelinks|labels', languages: 'en' });
Object.assign(ent, r.entities);
await sleep(200);
}
// collect signature filenames
const rows = [];
const fileTitles = new Set();
for (const [title, qid] of Object.entries(titleToQid)) {
const e = ent[qid] || {};
const sigClaim = e.claims?.P109?.[0]?.mainsnak?.datavalue?.value || null;
const dod = e.claims?.P570?.[0]?.mainsnak?.datavalue?.value?.time || null;
const links = e.sitelinks ? Object.keys(e.sitelinks).length : 0;
const label = e.labels?.en?.value || title;
const fileTitle = sigClaim ? 'File:' + sigClaim : null;
if (fileTitle) fileTitles.add(fileTitle);
rows.push({ title, qid, label, sigClaim, fileTitle, dod, links });
}
// 4. Commons license + direct URL per signature file
const lic = {};
for (const grp of chunk([...fileTitles], 50)) {
const r = await api(COMMONS, { action: 'query', prop: 'imageinfo', iiprop: 'extmetadata|url', titles: grp.join('|') });
const norm = {}; (r.query?.normalized || []).forEach((n) => (norm[n.to] = n.from));
for (const p of r.query?.pages || []) {
const ii = p.imageinfo?.[0]; const ext = ii?.extmetadata || {};
lic[norm[p.title] || p.title] = {
license: ext.LicenseShortName?.value || 'Public domain (signature, pre-1830)',
directUrl: ii?.url || null, filePage: ii?.descriptionurl || null,
};
}
await sleep(200);
}
rows.sort((a, b) => b.links - a.links);
const out = rows.map((r, i) => {
const meta = r.fileTitle ? lic[r.fileTitle] : null;
const hasFile = !!meta?.directUrl;
const deathYear = r.dod ? r.dod.slice(1, 5) : '';
return {
category: 'Declaration of Independence',
rank: i + 1,
full_name: r.label,
wikidata: `https://www.wikidata.org/wiki/${r.qid}`,
reason_for_ranking: `Signer of the US Declaration of Independence (1776); cross-wiki notability: ${r.links} language Wikipedias`,
ranking_source_urls: 'https://www.archives.gov/founding-docs/declaration | https://founders.archives.gov/',
signature_image_url: hasFile ? meta.directUrl
: 'https://www.archives.gov/founding-docs/downloads',
signature_source_type: hasFile
? 'Wikimedia Commons (Wikidata P109 signature file)'
: 'US National Archives — engrossed Declaration of Independence (PD)',
image_license: hasFile ? meta.license : 'Public domain (US founding document, 1776)',
image_author: hasFile ? '' : 'Engrossed by Timothy Matlack; signed 1776',
deceased: 'yes',
death_date: deathYear,
risk_level: 'low',
usable_in_commercial_collage: 'yes',
notes: hasFile
? 'Public-domain signature; signer deceased since the founding era — no copyright or publicity-rights exposure'
: 'No standalone Commons signature file; signature appears on the engrossed Declaration (NARA, public domain)',
backup_source: hasFile ? meta.filePage : 'https://catalog.archives.gov/id/1419123',
};
});
await writeFile('data/declaration.json', JSON.stringify(out, null, 2));
const withFile = out.filter((r) => r.signature_source_type.startsWith('Wikimedia')).length;
console.log(`\n${out.length} signers written -> data/declaration.json`);
console.log(` ${withFile} have a standalone Commons signature file; ${out.length - withFile} sourced from the engrossed Declaration (NARA)`);
console.log(` ALL ${out.length}: risk_level=low, usable_in_commercial_collage=yes`);
console.log(` Top 5 by notability: ${out.slice(0, 5).map((r) => r.full_name).join(', ')}`);
}
main().catch((e) => { console.error(e); process.exit(1); });