← 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);
}