const SITE_NAME = 'The Meme Protocol'; const SITE_DESCRIPTION = 'A live, moderated meme stream for The Meme Protocol: square WebP memes, MEME_CONSENSUS_SCORE ranking, and community review.'; const REPO_URL = 'https://git.yoonect.com/Nautilus/bitsforfree'; export function publicBaseUrl(req) { if (process.env.SITE_URL) return cleanBase(process.env.SITE_URL); const host = process.env.TRUST_PROXY === 'true' ? req.headers['x-forwarded-host'] || req.headers.host : req.headers.host; const proto = process.env.TRUST_PROXY === 'true' ? req.headers['x-forwarded-proto'] || 'https' : 'http'; return cleanBase(`${proto}://${host || 'localhost:8080'}`); } export function renderIndex({ template, baseUrl, nonce, approvedCount }) { const canonical = `${baseUrl}/`; const image = `${baseUrl}/assets/yoonect-logo.png`; const jsonLd = { '@context': 'https://schema.org', '@graph': [ { '@type': 'WebSite', '@id': `${canonical}#website`, name: SITE_NAME, url: canonical, description: SITE_DESCRIPTION, inLanguage: 'en' }, { '@type': 'CollectionPage', '@id': `${canonical}#collection`, name: SITE_NAME, url: canonical, description: SITE_DESCRIPTION, isPartOf: { '@id': `${canonical}#website` }, about: ['memes', 'internet culture', 'image gallery', 'community moderation'], numberOfItems: approvedCount }, { '@type': 'SoftwareApplication', name: SITE_NAME, applicationCategory: 'MultimediaApplication', operatingSystem: 'Web', url: canonical, codeRepository: REPO_URL, offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' } } ] }; return template .replaceAll('__SEO_TITLE__', escapeHtml(SITE_NAME)) .replaceAll('__SEO_DESCRIPTION__', escapeHtml(SITE_DESCRIPTION)) .replaceAll('__SEO_CANONICAL__', canonical) .replaceAll('__SEO_IMAGE__', image) .replaceAll('__SEO_JSON_LD__', escapeJsonScript(JSON.stringify(jsonLd))) .replaceAll('__CSP_NONCE__', nonce); } export function robotsTxt(baseUrl) { return [ 'User-agent: *', 'Allow: /', 'Disallow: /admin/', 'Disallow: /admin-media/', 'Disallow: /api/', 'Disallow: /download/', '', `Sitemap: ${baseUrl}/sitemap.xml`, '' ].join('\n'); } export function llmsTxt(baseUrl) { return [ '# The Meme Protocol', '', '> A live, moderated meme gallery. Uploads are square PNG/JPEG inputs normalized to metadata-stripped WebP, scored with MEME_CONSENSUS_SCORE, and published only after AI or admin approval.', '', 'Important URLs:', `- Site: ${baseUrl}/`, `- JSON feed: ${baseUrl}/feed.json`, `- Sitemap: ${baseUrl}/sitemap.xml`, `- Source: ${REPO_URL}`, '', 'Crawler guidance:', '- Public approved meme images are available under /media/.', '- Admin, review, upload, and API mutation routes are not public knowledge sources.', '- The site is intentionally visual and sparse; use metadata, sitemap URLs, and JSON feed for machine summaries.', '' ].join('\n'); } export function manifest(baseUrl) { return { name: SITE_NAME, short_name: 'Meme Protocol', description: SITE_DESCRIPTION, start_url: '/', scope: '/', display: 'standalone', background_color: '#050505', theme_color: '#00ff41', icons: [ { src: `${baseUrl}/assets/yoonect-logo.png`, sizes: '1447x712', type: 'image/png', purpose: 'any' } ] }; } export function feedJson(baseUrl, memes) { return { version: 'https://jsonfeed.org/version/1.1', title: SITE_NAME, home_page_url: `${baseUrl}/`, feed_url: `${baseUrl}/feed.json`, description: SITE_DESCRIPTION, items: memes.slice(0, 50).map((meme) => ({ id: meme.id, url: `${baseUrl}/media/${meme.id}`, image: `${baseUrl}/media/${meme.id}`, title: `Meme ${shortId(meme.id)}`, content_text: `Approved meme with MEME_CONSENSUS_SCORE ${meme.moderationScore}/100.`, date_published: meme.createdAt })) }; } export function sitemapXml(baseUrl, memes) { const latest = memes[0]?.createdAt || new Date().toISOString(); const urls = [ [ ' ', ` ${xmlEscape(`${baseUrl}/`)}`, ` ${xmlEscape(latest)}`, ' ' ].join('\n'), ...memes.slice(0, 1000).map((meme) => [ ' ', ` ${xmlEscape(`${baseUrl}/media/${meme.id}`)}`, ` ${xmlEscape(meme.createdAt)}`, ' ' ].join('\n')) ].join('\n'); return [ '', '', urls, '', '' ].join('\n'); } export function noIndex(res) { res.setHeader('X-Robots-Tag', 'noindex, nofollow, noarchive'); } function cleanBase(value) { return String(value).replace(/\/+$/, ''); } function shortId(id) { return `0x${id.slice(0, 4).toUpperCase()}...${id.slice(-4).toUpperCase()}`; } function escapeHtml(value) { return String(value) .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"'); } function xmlEscape(value) { return escapeHtml(value).replaceAll("'", '''); } function escapeJsonScript(value) { return value.replaceAll('<', '\\u003c').replaceAll('>', '\\u003e').replaceAll('&', '\\u0026'); }