All checks were successful
Deploy / deploy (push) Successful in 49s
- German (default) and English i18n support - Categories and tags - Blog posts with hero images - Dark/light theme switcher - View Transitions removed to fix reload ghost images - Webmentions integration - RSS feeds per locale Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
74 lines
1.9 KiB
JavaScript
74 lines
1.9 KiB
JavaScript
/**
|
|
* Fetches favicons for all linked domains and saves them locally to public/favicons/.
|
|
* Run before astro build so favicons are served statically instead of via Google S2.
|
|
* Idempotent: skips domains that already have a cached favicon.
|
|
*/
|
|
|
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
const root = join(__dirname, "..");
|
|
const linksPath = join(root, "src/content/links/links.json");
|
|
const outDir = join(root, "public/favicons");
|
|
|
|
function getDomain(url) {
|
|
try {
|
|
return new URL(url).hostname.replace(/^www\./, "");
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function faviconPath(domain) {
|
|
return join(outDir, `${domain}.png`);
|
|
}
|
|
|
|
async function fetchFavicon(domain) {
|
|
const url = `https://www.google.com/s2/favicons?domain=${domain}&sz=32`;
|
|
const res = await fetch(url);
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
const buf = await res.arrayBuffer();
|
|
return Buffer.from(buf);
|
|
}
|
|
|
|
async function main() {
|
|
const links = JSON.parse(readFileSync(linksPath, "utf-8"));
|
|
|
|
const domains = [
|
|
...new Set(links.map((l) => getDomain(l.url)).filter(Boolean)),
|
|
];
|
|
|
|
if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
|
|
|
|
let fetched = 0;
|
|
let skipped = 0;
|
|
|
|
await Promise.all(
|
|
domains.map(async (domain) => {
|
|
const dest = faviconPath(domain);
|
|
if (existsSync(dest)) {
|
|
skipped++;
|
|
return;
|
|
}
|
|
try {
|
|
const data = await fetchFavicon(domain);
|
|
writeFileSync(dest, data);
|
|
fetched++;
|
|
console.log(` ✓ ${domain}`);
|
|
} catch (err) {
|
|
console.warn(` ✗ ${domain}: ${err.message}`);
|
|
}
|
|
}),
|
|
);
|
|
|
|
console.log(
|
|
`Favicons: ${fetched} fetched, ${skipped} cached, ${domains.length} total`,
|
|
);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|