This commit is contained in:
ImBenji
2025-12-31 07:07:52 +00:00
commit ade522e2ff
18 changed files with 2841 additions and 0 deletions

167
api.js Normal file
View File

@@ -0,0 +1,167 @@
const express = require('express');
const nodeHtmlToImage = require('node-html-to-image');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const app = express();
const PORT = 3000;
const CACHE_DIR = path.join(__dirname, 'cache');
// Create cache directory if it doesn't exist
if (!fs.existsSync(CACHE_DIR)) {
fs.mkdirSync(CACHE_DIR);
}
app.use(express.json());
function hashConfig(config) {
const configString = JSON.stringify(config);
return crypto.createHash('sha256').update(configString).digest('hex');
}
function getCachePath(hash) {
return path.join(CACHE_DIR, `${hash}.png`);
}
function getCachedImage(config) {
const hash = hashConfig(config);
const cachePath = getCachePath(hash);
if (fs.existsSync(cachePath)) {
return fs.readFileSync(cachePath);
}
return null;
}
function cacheImage(config, imageBuffer) {
const hash = hashConfig(config);
const cachePath = getCachePath(hash);
fs.writeFileSync(cachePath, imageBuffer);
}
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}`;
}
async function generateQuoteBuffer(config) {
const htmlTemplate = fs.readFileSync(path.join(__dirname, 'quote.html'), 'utf8');
const configScript = `
<script>
window.tweetConfig = ${JSON.stringify(config)};
</script>
`;
const modifiedHtml = htmlTemplate.replace('</head>', `${configScript}</head>`);
const image = await nodeHtmlToImage({
html: modifiedHtml,
puppeteerArgs: {
args: ['--no-sandbox'],
defaultViewport: {
width: 3240,
height: 3240
}
},
beforeScreenshot: async (page) => {
await new Promise(resolve => setTimeout(resolve, 500));
}
});
return image;
}
// GET endpoint - use query parameters
app.get('/generate', async (req, res) => {
try {
const timestamp = req.query.timestamp ? formatTimestamp(parseInt(req.query.timestamp)) : formatTimestamp(Date.now() / 1000);
const config = {
displayName: req.query.displayName || "Anonymous",
username: req.query.username || "@anonymous",
avatarUrl: req.query.avatarUrl || "",
text: req.query.text || "No text provided",
imageUrl: req.query.imageUrl || null,
timestamp: timestamp,
viewsCount: req.query.viewsCount || "0"
};
// Check cache first
let image = getCachedImage(config);
let fromCache = true;
if (!image) {
// Generate new image
image = await generateQuoteBuffer(config);
cacheImage(config, image);
fromCache = false;
}
res.setHeader('Content-Type', 'image/png');
res.setHeader('X-Cache', fromCache ? 'HIT' : 'MISS');
res.send(image);
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Failed to generate image' });
}
});
// POST endpoint - use request body
app.post('/generate', async (req, res) => {
try {
const timestamp = req.body.timestamp ? formatTimestamp(parseInt(req.body.timestamp)) : formatTimestamp(Date.now() / 1000);
const config = {
displayName: req.body.displayName || "Anonymous",
username: req.body.username || "@anonymous",
avatarUrl: req.body.avatarUrl || "",
text: req.body.text || "No text provided",
imageUrl: req.body.imageUrl || null,
timestamp: timestamp,
viewsCount: req.body.viewsCount || "0"
};
// Check cache first
let image = getCachedImage(config);
let fromCache = true;
if (!image) {
// Generate new image
image = await generateQuoteBuffer(config);
cacheImage(config, image);
fromCache = false;
}
res.setHeader('Content-Type', 'image/png');
res.setHeader('X-Cache', fromCache ? 'HIT' : 'MISS');
res.send(image);
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Failed to generate image' });
}
});
// Health check endpoint
app.get('/health', (req, res) => {
res.status(200).json({ status: 'ok' });
});
app.listen(PORT, () => {
console.log(`Quote generator API running on http://localhost:${PORT}`);
console.log(`GET: http://localhost:${PORT}/generate?text=Hello&displayName=Test&username=@test&timestamp=1735574400`);
console.log(`POST: http://localhost:${PORT}/generate`);
});