// backend/services/ScreenshotService.js // Screenshot Service for GOD CRM // Auto-generates screenshots from CRM pages for Telegram posts // Uses Playwright with Chromium for headless browser rendering import { chromium } from 'playwright'; import { apiLogger } from '../utils/logger.js'; import path from 'path'; import fs from 'fs'; const logger = apiLogger; // Screenshot cache directory const SCREENSHOT_DIR = path.join(process.cwd(), 'uploads', 'screenshots'); // Ensure screenshot directory exists if (!fs.existsSync(SCREENSHOT_DIR)) { fs.mkdirSync(SCREENSHOT_DIR, { recursive: true }); } /** * Take a screenshot of any URL * @param {string} url - URL to screenshot * @param {object} opts - Options * @param {number} opts.width - Viewport width (default: 1200) * @param {number} opts.height - Viewport height (default: 630) * @param {boolean} opts.fullPage - Capture full page (default: false) * @param {string} opts.selector - CSS selector to capture specific element * @param {number} opts.delay - Wait ms after load (default: 1000) * @returns {Buffer} PNG buffer */ export async function takeScreenshot(url, opts = {}) { const { width = 1200, height = 630, fullPage = false, selector = null, delay = 1000 } = opts; let browser; try { logger.info(`[ScreenshotService] Taking screenshot of: ${url}`); browser = await chromium.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'] }); const page = await browser.newPage({ viewport: { width, height }, deviceScaleFactor: 2 // Retina quality }); await page.goto(url, { waitUntil: 'networkidle', timeout: 30000 }); if (delay > 0) { await page.waitForTimeout(delay); } let buffer; if (selector) { const element = await page.$(selector); if (element) { buffer = await element.screenshot({ type: 'png' }); } else { logger.warn(`[ScreenshotService] Selector "${selector}" not found, taking full page`); buffer = await page.screenshot({ type: 'png', fullPage }); } } else { buffer = await page.screenshot({ type: 'png', fullPage }); } logger.info(`[ScreenshotService] Screenshot taken: ${buffer.length} bytes`); return buffer; } catch (error) { logger.error(`[ScreenshotService] Error: ${error.message}`); throw error; } finally { if (browser) await browser.close(); } } /** * Take a screenshot of a CRM widget/dashboard * @param {number} widgetId - Widget ID * @param {object} opts - Screenshot options * @returns {Buffer} PNG buffer */ export async function screenshotWidget(widgetId, opts = {}) { const baseUrl = process.env.CRM_BASE_URL || 'https://devcrm.hltrn.cc'; const url = `${baseUrl}/widgets/${widgetId}`; return takeScreenshot(url, { ...opts, delay: 2000 }); } /** * Take a screenshot of a CRM table view * @param {number} tableId - Table ID * @param {object} opts - Screenshot options * @returns {Buffer} PNG buffer */ export async function screenshotTable(tableId, opts = {}) { const baseUrl = process.env.CRM_BASE_URL || 'https://devcrm.hltrn.cc'; const url = `${baseUrl}/tables/${tableId}`; return takeScreenshot(url, { ...opts, delay: 2000 }); } /** * Generate a styled post card image from HTML template * @param {object} data - Post data * @param {string} data.title - Post title * @param {string} data.summary - Post summary * @param {string} data.source - Source name (Reddit, HN, etc.) * @param {string} data.sourceUrl - Link to original * @param {string} data.category - Category tag * @returns {Buffer} PNG buffer */ export async function generatePostCard(data) { const { title, summary, source = '', sourceUrl = '', category = 'AI News' } = data; const html = `