XML Style
This commit is contained in:
@@ -39,7 +39,7 @@ data/
|
||||
The app serves crawler and answer-engine metadata without adding visible page copy:
|
||||
|
||||
- `/robots.txt`
|
||||
- `/sitemap.xml` with approved meme image entries
|
||||
- `/sitemap.xml` with the home page and approved meme URLs
|
||||
- `/feed.json`
|
||||
- `/llms.txt`
|
||||
- `/site.webmanifest`
|
||||
|
||||
@@ -19,6 +19,13 @@ const PAGE_SIZE_MAX = 48;
|
||||
const UPLOAD_MAX_BYTES = 5 * 1024 * 1024;
|
||||
const REQUEST_MAX_BYTES = 6 * 1024 * 1024;
|
||||
const events = new Set();
|
||||
const DISCOVERY_ROUTES = new Set([
|
||||
'/robots.txt',
|
||||
'/llms.txt',
|
||||
'/site.webmanifest',
|
||||
'/feed.json',
|
||||
'/sitemap.xml'
|
||||
]);
|
||||
const ADMIN_TOKEN = process.env.ADMIN_TOKEN || crypto.randomBytes(24).toString('hex');
|
||||
const indexTemplate = await fs.readFile('./public/index.html', 'utf8');
|
||||
|
||||
@@ -32,10 +39,9 @@ if (!process.env.ADMIN_TOKEN) {
|
||||
}
|
||||
|
||||
const server = http.createServer(async (req, res) => {
|
||||
withSecurityHeaders(res);
|
||||
|
||||
try {
|
||||
const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
|
||||
withSecurityHeaders(res, { contentSecurityPolicy: !isDiscoveryRoute(req, url) });
|
||||
const baseUrl = publicBaseUrl(req);
|
||||
|
||||
if (req.method === 'GET' && url.pathname === '/') {
|
||||
@@ -253,6 +259,11 @@ function positiveInt(value, fallback) {
|
||||
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
||||
}
|
||||
|
||||
function isDiscoveryRoute(req, url) {
|
||||
if (req.method !== 'GET') return false;
|
||||
return DISCOVERY_ROUTES.has(url.pathname);
|
||||
}
|
||||
|
||||
function downloadName(meme) {
|
||||
return `meme-protocol-${meme.id.slice(0, 12)}.${meme.ext}`;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ export function withSecurityHeaders(res, options = {}) {
|
||||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||
res.setHeader('X-Frame-Options', 'DENY');
|
||||
res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
|
||||
if (options.contentSecurityPolicy === false) return;
|
||||
|
||||
const scriptSrc = options.scriptNonce ? `script-src 'self' 'nonce-${options.scriptNonce}'; ` : '';
|
||||
res.setHeader(
|
||||
'Content-Security-Policy',
|
||||
|
||||
+18
-14
@@ -87,7 +87,7 @@ export function llmsTxt(baseUrl) {
|
||||
'Crawler guidance:',
|
||||
'- Public approved meme images are available under /media/<sha256>.',
|
||||
'- Admin, review, upload, and API mutation routes are not public knowledge sources.',
|
||||
'- The site is intentionally visual and sparse; use metadata, sitemap image entries, and JSON feed for machine summaries.',
|
||||
'- The site is intentionally visual and sparse; use metadata, sitemap URLs, and JSON feed for machine summaries.',
|
||||
''
|
||||
].join('\n');
|
||||
}
|
||||
@@ -132,22 +132,26 @@ export function feedJson(baseUrl, memes) {
|
||||
}
|
||||
|
||||
export function sitemapXml(baseUrl, memes) {
|
||||
const images = memes.slice(0, 1000).map((meme) => [
|
||||
' <image:image>',
|
||||
` <image:loc>${xmlEscape(`${baseUrl}/media/${meme.id}`)}</image:loc>`,
|
||||
` <image:caption>${xmlEscape(`Meme ${shortId(meme.id)} with MEME_CONSENSUS_SCORE ${meme.moderationScore}/100`)}</image:caption>`,
|
||||
' </image:image>'
|
||||
].join('\n')).join('\n');
|
||||
const latest = memes[0]?.createdAt || new Date().toISOString();
|
||||
const urls = [
|
||||
[
|
||||
' <url>',
|
||||
` <loc>${xmlEscape(`${baseUrl}/`)}</loc>`,
|
||||
` <lastmod>${xmlEscape(latest)}</lastmod>`,
|
||||
' </url>'
|
||||
].join('\n'),
|
||||
...memes.slice(0, 1000).map((meme) => [
|
||||
' <url>',
|
||||
` <loc>${xmlEscape(`${baseUrl}/media/${meme.id}`)}</loc>`,
|
||||
` <lastmod>${xmlEscape(meme.createdAt)}</lastmod>`,
|
||||
' </url>'
|
||||
].join('\n'))
|
||||
].join('\n');
|
||||
|
||||
return [
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">',
|
||||
' <url>',
|
||||
` <loc>${xmlEscape(`${baseUrl}/`)}</loc>`,
|
||||
' <changefreq>hourly</changefreq>',
|
||||
' <priority>1.0</priority>',
|
||||
images,
|
||||
' </url>',
|
||||
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
|
||||
urls,
|
||||
'</urlset>',
|
||||
''
|
||||
].join('\n');
|
||||
|
||||
Reference in New Issue
Block a user