Initial
This commit is contained in:
167
api.js
Normal file
167
api.js
Normal 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×tamp=1735574400`);
|
||||
console.log(`POST: http://localhost:${PORT}/generate`);
|
||||
});
|
||||
Reference in New Issue
Block a user