const SITE_NAME = 'The Meme Protocol'; const SITE_DESCRIPTION = 'A live, moderated meme stream for The Meme Protocol: 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, meme = null }) { const home = `${baseUrl}/`; const canonical = meme ? `${baseUrl}/meme/${meme.id}` : home; const image = meme ? `${baseUrl}/media/${meme.id}` : `${baseUrl}/assets/yoonect-logo.png`; const title = meme ? `Meme ${shortId(meme.id)} | ${SITE_NAME}` : SITE_NAME; const description = meme ? `View and share meme ${shortId(meme.id)} on ${SITE_NAME}.` : SITE_DESCRIPTION; const pageMetadata = meme ? { '@type': 'ImageObject', '@id': `${canonical}#meme`, name: `Meme ${shortId(meme.id)}`, contentUrl: image, url: canonical, uploadDate: meme.createdAt, width: meme.width, height: meme.height, isPartOf: { '@id': `${home}#website` } } : { '@type': 'CollectionPage', '@id': `${canonical}#collection`, name: SITE_NAME, url: canonical, description: SITE_DESCRIPTION, isPartOf: { '@id': `${home}#website` }, about: ['memes', 'internet culture', 'image gallery', 'community moderation'], numberOfItems: approvedCount }; const jsonLd = { '@context': 'https://schema.org', '@graph': [ { '@type': 'WebSite', '@id': `${home}#website`, name: SITE_NAME, url: home, description: SITE_DESCRIPTION, inLanguage: 'en' }, pageMetadata, { '@type': 'SoftwareApplication', name: SITE_NAME, applicationCategory: 'MultimediaApplication', operatingSystem: 'Web', url: home, codeRepository: REPO_URL, offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' } } ] }; return template .replaceAll('__SEO_TITLE__', escapeHtml(title)) .replaceAll('__SEO_DESCRIPTION__', escapeHtml(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 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}/`, `- OpenAPI: ${baseUrl}/openapi.json`, `- API skill: ${baseUrl}/meme-api.skill.md`, `- JSON feed: ${baseUrl}/feed.json`, `- Sitemap: ${baseUrl}/sitemap.xml`, `- Source: ${REPO_URL}`, '', 'Crawler guidance:', '- Public approved meme images are available under /media/.', '- Public API clients should use /openapi.json and /meme-api.skill.md before calling API routes.', '- 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}/meme/${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}/meme/${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'); }