← back to Dear Bubbe Nextjs
lib/instagram-image-generator.js
291 lines
#!/usr/bin/env node
const { createCanvas, registerFont, loadImage } = require('canvas');
const fs = require('fs').promises;
const path = require('path');
const { anonymizeMessage } = require('./anonymous-user-poster');
// Register fonts if available
try {
// Try to use system fonts
registerFont('/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', { family: 'DejaVu Sans', weight: 'bold' });
registerFont('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', { family: 'DejaVu Sans' });
} catch (e) {
console.log('Using default fonts');
}
// Color schemes for variety
const COLOR_SCHEMES = [
{
background: ['#FF6B6B', '#4ECDC4'], // Coral to teal
text: '#FFFFFF',
accent: '#FFE66D'
},
{
background: ['#A8E6CF', '#7FCD91'], // Mint gradient
text: '#2D3436',
accent: '#FF6B6B'
},
{
background: ['#FFD93D', '#FCB045'], // Golden gradient
text: '#2D3436',
accent: '#6C63FF'
},
{
background: ['#6C63FF', '#4158D0'], // Purple gradient
text: '#FFFFFF',
accent: '#FFD93D'
},
{
background: ['#F093FB', '#F5576C'], // Pink gradient
text: '#FFFFFF',
accent: '#4A00E0'
}
];
// Create gradient background
function createGradient(ctx, width, height, colors) {
const gradient = ctx.createLinearGradient(0, 0, width, height);
gradient.addColorStop(0, colors[0]);
gradient.addColorStop(1, colors[1]);
return gradient;
}
// Word wrap text
function wrapText(ctx, text, maxWidth) {
const words = text.split(' ');
const lines = [];
let currentLine = '';
for (const word of words) {
const testLine = currentLine + (currentLine ? ' ' : '') + word;
const metrics = ctx.measureText(testLine);
if (metrics.width > maxWidth && currentLine) {
lines.push(currentLine);
currentLine = word;
} else {
currentLine = testLine;
}
}
if (currentLine) {
lines.push(currentLine);
}
return lines;
}
// Generate Instagram image
async function generateInstagramImage(userMessage, outputPath) {
// Canvas size (Instagram square)
const width = 1080;
const height = 1080;
// Create canvas
const canvas = createCanvas(width, height);
const ctx = canvas.getContext('2d');
// Choose random color scheme
const scheme = COLOR_SCHEMES[Math.floor(Math.random() * COLOR_SCHEMES.length)];
// Draw gradient background
const gradient = createGradient(ctx, width, height, scheme.background);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
// Add subtle pattern overlay
ctx.globalAlpha = 0.1;
for (let i = 0; i < height; i += 40) {
ctx.strokeStyle = scheme.text;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, i);
ctx.lineTo(width, i);
ctx.stroke();
}
ctx.globalAlpha = 1;
// Draw quote card background
const cardMargin = 80;
const cardX = cardMargin;
const cardY = 200;
const cardWidth = width - (cardMargin * 2);
const cardHeight = height - 400;
// Card shadow
ctx.shadowColor = 'rgba(0, 0, 0, 0.2)';
ctx.shadowBlur = 30;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 15;
// Draw card
ctx.fillStyle = 'rgba(255, 255, 255, 0.95)';
ctx.beginPath();
ctx.roundRect(cardX, cardY, cardWidth, cardHeight, 20);
ctx.fill();
// Reset shadow
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
// Draw quote marks
ctx.font = 'bold 120px DejaVu Sans, Arial';
ctx.fillStyle = scheme.accent;
ctx.globalAlpha = 0.3;
ctx.fillText('"', cardX + 40, cardY + 100);
ctx.fillText('"', cardX + cardWidth - 100, cardY + cardHeight - 40);
ctx.globalAlpha = 1;
// Anonymize message
const anonymized = anonymizeMessage(userMessage);
// Draw main text
ctx.font = 'bold 42px DejaVu Sans, Arial';
ctx.fillStyle = '#2D3436';
const textMaxWidth = cardWidth - 160;
const lines = wrapText(ctx, anonymized, textMaxWidth);
// Center text vertically
const lineHeight = 55;
const totalTextHeight = lines.length * lineHeight;
const textStartY = cardY + (cardHeight / 2) - (totalTextHeight / 2) + 20;
// Draw each line
lines.forEach((line, index) => {
const metrics = ctx.measureText(line);
const x = cardX + (cardWidth / 2) - (metrics.width / 2);
const y = textStartY + (index * lineHeight);
ctx.fillText(line, x, y);
});
// Draw "Someone asked Bubbe:" at top
ctx.font = '28px DejaVu Sans, Arial';
ctx.fillStyle = scheme.text;
const header = 'Someone asked Bubbe:';
const headerMetrics = ctx.measureText(header);
ctx.fillText(header, (width / 2) - (headerMetrics.width / 2), 120);
// Draw branding at bottom
ctx.font = 'bold 36px DejaVu Sans, Arial';
ctx.fillStyle = scheme.text;
const branding = 'bubbe.ai';
const brandingMetrics = ctx.measureText(branding);
ctx.fillText(branding, (width / 2) - (brandingMetrics.width / 2), height - 80);
// Draw tagline
ctx.font = '24px DejaVu Sans, Arial';
const tagline = 'Your AI Jewish Grandma';
const taglineMetrics = ctx.measureText(tagline);
ctx.fillText(tagline, (width / 2) - (taglineMetrics.width / 2), height - 45);
// Save image
const buffer = canvas.toBuffer('image/png');
await fs.writeFile(outputPath, buffer);
console.log(`✅ Image saved to ${outputPath}`);
return outputPath;
}
// Generate hashtags
function generateHashtags() {
const baseHashtags = [
'#BubbeAI',
'#JewishGrandma',
'#AIAdvice',
'#BubbeWisdom',
'#AskBubbe'
];
const additionalHashtags = [
'#JewishHumor',
'#GrandmaKnowsBest',
'#LifeAdvice',
'#WisdomWednesday',
'#ThrowbackThursday',
'#MotivationMonday',
'#RealTalk',
'#NoFilter',
'#TruthBomb',
'#FamilyWisdom',
'#GrandmaAdvice',
'#AIGrandma',
'#TechMeetsTraition',
'#ModernBubbe'
];
// Pick 5 random additional hashtags
const shuffled = additionalHashtags.sort(() => 0.5 - Math.random());
const selected = shuffled.slice(0, 5);
return [...baseHashtags, ...selected].join(' ');
}
// Generate Instagram caption
function generateCaption(userMessage) {
const anonymized = anonymizeMessage(userMessage);
const intros = [
"Someone asked Bubbe for advice today...",
"Real question from our community:",
"Can you relate to this dilemma?",
"What would YOU tell them?",
"Today's anonymous question:",
"Someone needs Bubbe's wisdom:"
];
const intro = intros[Math.floor(Math.random() * intros.length)];
const hashtags = generateHashtags();
// Truncate message if too long
const maxLength = 200;
const truncated = anonymized.length > maxLength ?
anonymized.substring(0, maxLength) + '...' :
anonymized;
return `${intro}\n\n"${truncated}"\n\n🥯 What would Bubbe say? Find out at bubbe.ai (link in bio)\n\n${hashtags}`;
}
// Test function
async function testImageGeneration() {
const testMessages = [
"My husband forgot our anniversary",
"Should I quit my job without another lined up?",
"My mother-in-law criticizes my cooking",
"I spent $500 on shoes and feel guilty",
"My boss is micromanaging me"
];
const message = testMessages[Math.floor(Math.random() * testMessages.length)];
const timestamp = Date.now();
const imagePath = `/root/Projects/dear-bubbe-admin/public/instagram-${timestamp}.png`;
console.log('🎨 Generating Instagram image...');
console.log(`📝 Message: "${message}"`);
await generateInstagramImage(message, imagePath);
const caption = generateCaption(message);
console.log('\n📱 Instagram Caption:');
console.log('-'.repeat(50));
console.log(caption);
console.log('-'.repeat(50));
console.log(`\n🖼️ View image at: http://45.61.58.125:7902/instagram-${timestamp}.png`);
return { imagePath, caption };
}
// Export functions
module.exports = {
generateInstagramImage,
generateCaption,
generateHashtags
};
// Run if called directly
if (require.main === module) {
testImageGeneration().catch(console.error);
}