Initial commit: Astro 6 static blog site
All checks were successful
Deploy / deploy (push) Successful in 49s
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>
This commit is contained in:
commit
5bb63bacf5
95 changed files with 12199 additions and 0 deletions
74
scripts/fetch-favicons.mjs
Normal file
74
scripts/fetch-favicons.mjs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
* 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);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue