const express = require('express'); const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const router = express.Router(); const { createSession, getSession, updateSession, deleteSession } = require('./db'); const { renderHtml } = require('./browserPool'); const CACHE_DIR = path.join(__dirname, 'cache'); // caching functions function normalizeConfig(config) { const normalized = {}; for (const [key, value] of Object.entries(config)) { if (value !== null && value !== undefined && value !== '') { normalized[key] = value; } } return normalized; } function hashConfig(config) { const normalized = normalizeConfig(config); const configString = JSON.stringify(normalized); return crypto.createHash('sha256').update(configString).digest('hex'); } function getCachedImage(config) { const hash = hashConfig(config); const cachePath = path.join(CACHE_DIR, `${hash}.png`); if (fs.existsSync(cachePath)) { const now = new Date(); fs.utimesSync(cachePath, now, now); return fs.readFileSync(cachePath); } return null; } function cacheImage(config, imageBuffer) { const hash = hashConfig(config); const cachePath = path.join(CACHE_DIR, `${hash}.png`); fs.writeFileSync(cachePath, imageBuffer); } function deleteCachedImage(config) { const hash = hashConfig(config); const cachePath = path.join(CACHE_DIR, `${hash}.png`); if (fs.existsSync(cachePath)) { fs.unlinkSync(cachePath); } } // build config from session for cache operations function buildConfigFromSession(session) { const timestamp = session.timestamp ? formatTimestamp(session.timestamp) : formatTimestamp(Math.floor(Date.now() / 1000)); let engagement = null; if (session.engagement) { engagement = { likes: formatCount(session.engagement.likes), retweets: formatCount(session.engagement.retweets), replies: formatCount(session.engagement.replies), views: formatCount(session.engagement.views) }; } return { displayName: session.displayName, username: session.username, avatarUrl: session.avatarUrl, text: session.text, imageUrl: session.imageUrl, timestamp: timestamp, engagement: engagement, verified: session.verified }; } // helper functions (copied from api.js) function formatCount(num) { if (num === null || num === undefined) return '0'; num = parseInt(num); if (num >= 1000000000) { return (num / 1000000000).toFixed(1).replace(/\.0$/, '') + 'B'; } if (num >= 1000000) { return (num / 1000000).toFixed(1).replace(/\.0$/, '') + 'M'; } if (num >= 1000) { return (num / 1000).toFixed(1).replace(/\.0$/, '') + 'K'; } return num.toString(); } function formatTimestamp(epoch) { const date = new Date(epoch * 1000); const hours = date.getHours(); const minutes = date.getMinutes().toString().padStart(2, '0'); const ampm = hours >= 12 ? 'PM' : 'AM'; const hour12 = hours % 12 || 12; const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; const month = months[date.getMonth()]; const day = date.getDate(); const year = date.getFullYear(); return `${hour12}:${minutes} ${ampm} ยท ${month} ${day}, ${year}`; } function detectImageType(base64String) { const base64Data = base64String.includes(',') ? base64String.split(',')[1] : base64String; const buffer = Buffer.from(base64Data, 'base64'); if (buffer[0] === 0xFF && buffer[1] === 0xD8 && buffer[2] === 0xFF) { return 'image/jpeg'; } else if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4E && buffer[3] === 0x47) { return 'image/png'; } else if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46) { return 'image/gif'; } else if (buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46) { return 'image/webp'; } return 'image/png'; } function fixDataUri(dataUri) { if (!dataUri || !dataUri.startsWith('data:')) return dataUri; const parts = dataUri.split(','); if (parts.length !== 2) return dataUri; const base64Data = parts[1]; try { const correctType = detectImageType(base64Data); return `data:${correctType};base64,${base64Data}`; } catch (error) { console.error('Invalid base64 data:', error.message); return null; } } const TEMPLATE_PATH = path.join(__dirname, 'template.html'); const templateHtml = fs.readFileSync(TEMPLATE_PATH, 'utf8'); function buildEngagementHtml(engagement) { if (!engagement) return ''; return `
`; } async function generateQuoteBuffer(config) { const avatarHtml = config.avatarUrl ? `