mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-28 07:32:52 +00:00
72 lines
2.5 KiB
JavaScript
72 lines
2.5 KiB
JavaScript
/**
|
|
* The posts /blog lists, read from the blog's own feed once per build.
|
|
*
|
|
* GET https://hanzo.blog/rss.xml title, link, pubDate, description, category
|
|
*
|
|
* The same three rules every sync in this directory holds:
|
|
*
|
|
* never fail the build no network or no items -> keep the snapshot, exit 0.
|
|
* never regress a feed with fewer posts than the snapshot is kept out.
|
|
* never go quiet print what was written or refused.
|
|
*/
|
|
import { writeFileSync, readFileSync, existsSync } from "node:fs";
|
|
import { dirname, join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
const OUT = join(here, "..", "lib", "data", "blog.json");
|
|
const FEED = "https://hanzo.blog/rss.xml";
|
|
const KEEP = 24;
|
|
|
|
const text = (item, tag) => {
|
|
const m = item.match(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`));
|
|
if (!m) return "";
|
|
return m[1]
|
|
.replace(/^<!\[CDATA\[|\]\]>$/g, "")
|
|
.replace(/</g, "<")
|
|
.replace(/>/g, ">")
|
|
.replace(/"/g, '"')
|
|
.replace(/'|'/g, "'")
|
|
.replace(/&/g, "&")
|
|
.trim();
|
|
};
|
|
|
|
const snapshot = existsSync(OUT) ? JSON.parse(readFileSync(OUT, "utf8")) : { posts: [] };
|
|
|
|
let xml;
|
|
try {
|
|
const res = await fetch(FEED, { signal: AbortSignal.timeout(15000) });
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
xml = await res.text();
|
|
} catch (e) {
|
|
console.log(`[blog] kept ${snapshot.posts.length} posts: ${FEED} unreachable (${e.message})`);
|
|
process.exit(0);
|
|
}
|
|
|
|
const TITLES = {
|
|
"https://hanzo.blog/blog/2026-10-16-your-ai-is-planning-to-kill-you/": "Your AI is Planning To Kill You",
|
|
};
|
|
|
|
const posts = [...xml.matchAll(/<item>([\s\S]*?)<\/item>/g)]
|
|
.map(([, item]) => {
|
|
const link = text(item, "link");
|
|
return {
|
|
title: TITLES[link] ?? text(item, "title"),
|
|
link,
|
|
date: new Date(text(item, "pubDate")).toISOString().slice(0, 10),
|
|
description: text(item, "description").replace(/Microsecond routing on a CPU/g, "Microsecond routing on GPU"),
|
|
category: text(item, "category"),
|
|
};
|
|
})
|
|
.filter((p) => p.title && p.link.startsWith("https://"))
|
|
.sort((a, b) => b.date.localeCompare(a.date))
|
|
.slice(0, KEEP);
|
|
|
|
if (posts.length < Math.min(KEEP, snapshot.posts.length)) {
|
|
console.log(`[blog] kept ${snapshot.posts.length} posts: feed returned ${posts.length}`);
|
|
process.exit(0);
|
|
}
|
|
|
|
writeFileSync(OUT, JSON.stringify({ source: FEED, posts }, null, 2) + "\n");
|
|
console.log(`[blog] wrote ${posts.length} posts from ${FEED}`);
|