UNPKG

screenshot-monitor-pro

Version:

A Node.js package for automated screenshot monitoring with SQLite database storage and configurable capture intervals

469 lines (419 loc) 12.6 kB
const screenshot = require('screenshot-desktop'); const sqlite3 = require('sqlite3').verbose(); const sharp = require('sharp'); const path = require('path'); const fs = require('fs-extra'); const os = require('os'); const { v4: uuidv4 } = require('uuid'); class ScreenshotMonitor { constructor(config = {}) { this.config = { frequency: config.frequency || 30000, // Default: 30 seconds screenshotQuality: config.screenshotQuality || 90, thumbnailWidth: config.thumbnailWidth || 300, thumbnailHeight: config.thumbnailHeight || 200, userDataPath: config.userDataPath || path.join(os.homedir(), '.screenshot-monitor-pro'), ...config }; this.isRunning = false; this.intervalId = null; this.db = null; this.screenshotsPath = path.join(this.config.userDataPath, 'screenshots'); this.thumbnailsPath = path.join(this.config.userDataPath, 'thumbnails'); this.monitors = []; } /** * Initialize the screenshot monitor */ async init() { try { // Create directories await this.createDirectories(); // Detect monitors await this.detectMonitors(); // Initialize database await this.initDatabase(); console.log(`Screenshot Monitor initialized successfully with ${this.monitors.length} monitor(s)`); return true; } catch (error) { console.error('Failed to initialize Screenshot Monitor:', error); throw error; } } /** * Create necessary directories */ async createDirectories() { await fs.ensureDir(this.config.userDataPath); await fs.ensureDir(this.screenshotsPath); await fs.ensureDir(this.thumbnailsPath); } /** * Detect available monitors */ async detectMonitors() { try { // Get all screens info const screens = await screenshot.listDisplays(); this.monitors = screens.map((screen, index) => ({ id: screen.id || index, name: screen.name || `Monitor ${index + 1}`, width: screen.width, height: screen.height, x: screen.x, y: screen.y, index: index })); console.log(`Detected ${this.monitors.length} monitor(s):`); this.monitors.forEach(monitor => { console.log(` Monitor ${monitor.id}: ${monitor.width}x${monitor.height} at (${monitor.x},${monitor.y})`); }); } catch (error) { console.warn('Could not detect monitors, using fallback method:', error.message); // Fallback: assume single monitor this.monitors = [{ id: 0, name: 'Primary Monitor', width: 1920, height: 1080, x: 0, y: 0, index: 0 }]; } } /** * Initialize SQLite database */ async initDatabase() { return new Promise((resolve, reject) => { const dbPath = path.join(this.config.userDataPath, 'screenshots.db'); this.db = new sqlite3.Database(dbPath, (err) => { if (err) { reject(err); return; } // Create tables with monitor information this.db.run(` CREATE TABLE IF NOT EXISTS screenshots ( id INTEGER PRIMARY KEY AUTOINCREMENT, filename TEXT NOT NULL, thumbnail_filename TEXT NOT NULL, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, monitor_id INTEGER NOT NULL, monitor_name TEXT, screen_width INTEGER, screen_height INTEGER, file_size INTEGER, file_path TEXT NOT NULL, thumbnail_path TEXT NOT NULL, uuid TEXT NOT NULL ) `, (err) => { if (err) { reject(err); } else { resolve(); } }); }); }); } /** * Start monitoring screenshots */ async start() { if (this.isRunning) { console.log('Screenshot monitor is already running'); return; } this.isRunning = true; console.log(`Starting screenshot monitor with ${this.config.frequency}ms interval for ${this.monitors.length} monitor(s)`); // Take initial screenshots await this.captureAllScreenshots(); // Set up interval this.intervalId = setInterval(async () => { if (this.isRunning) { await this.captureAllScreenshots(); } }, this.config.frequency); } /** * Stop monitoring screenshots */ stop() { if (!this.isRunning) { console.log('Screenshot monitor is not running'); return; } this.isRunning = false; if (this.intervalId) { clearInterval(this.intervalId); this.intervalId = null; } console.log('Screenshot monitor stopped'); } /** * Capture screenshots from all monitors */ async captureAllScreenshots() { const results = []; for (const monitor of this.monitors) { try { const result = await this.captureScreenshot(monitor); results.push(result); } catch (error) { console.error(`Failed to capture screenshot from monitor ${monitor.id}:`, error.message); } } return results; } /** * Capture screenshot from a specific monitor */ async captureScreenshot(monitor = null) { try { const targetMonitor = monitor || this.monitors[0]; const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const uuid = uuidv4(); const filename = `${targetMonitor.id}-${timestamp}-${uuid}.png`; const thumbnailFilename = `thumb-${targetMonitor.id}-${timestamp}-${uuid}.png`; const filePath = path.join(this.screenshotsPath, filename); const thumbnailPath = path.join(this.thumbnailsPath, thumbnailFilename); // Capture screenshot for specific monitor const imgBuffer = await screenshot({ screen: targetMonitor.index }); // Save original screenshot await fs.writeFile(filePath, imgBuffer); // Get image metadata const image = sharp(imgBuffer); const metadata = await image.metadata(); const stats = await fs.stat(filePath); // Create thumbnail await image .resize(this.config.thumbnailWidth, this.config.thumbnailHeight, { fit: 'inside', withoutEnlargement: true }) .png({ quality: this.config.screenshotQuality }) .toFile(thumbnailPath); // Save to database await this.saveToDatabase({ filename, thumbnailFilename, monitorId: targetMonitor.id, monitorName: targetMonitor.name, screenWidth: metadata.width, screenHeight: metadata.height, fileSize: stats.size, filePath, thumbnailPath, uuid }); console.log(`Screenshot captured from Monitor ${targetMonitor.id}: ${filename}`); return { filename, thumbnailFilename, filePath, thumbnailPath, monitorId: targetMonitor.id, monitorName: targetMonitor.name, uuid }; } catch (error) { console.error('Failed to capture screenshot:', error); throw error; } } /** * Save screenshot entry to database */ async saveToDatabase(screenshotData) { return new Promise((resolve, reject) => { const query = ` INSERT INTO screenshots (filename, thumbnail_filename, monitor_id, monitor_name, screen_width, screen_height, file_size, file_path, thumbnail_path, uuid) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `; this.db.run(query, [ screenshotData.filename, screenshotData.thumbnailFilename, screenshotData.monitorId, screenshotData.monitorName, screenshotData.screenWidth, screenshotData.screenHeight, screenshotData.fileSize, screenshotData.filePath, screenshotData.thumbnailPath, screenshotData.uuid ], function(err) { if (err) { reject(err); } else { resolve(this.lastID); } }); }); } /** * Get all screenshot entries from database */ async getScreenshots(limit = 100, offset = 0, monitorId = null) { return new Promise((resolve, reject) => { let query = ` SELECT * FROM screenshots `; const params = []; if (monitorId !== null) { query += `WHERE monitor_id = ? `; params.push(monitorId); } query += `ORDER BY timestamp DESC LIMIT ? OFFSET ?`; params.push(limit, offset); this.db.all(query, params, (err, rows) => { if (err) { reject(err); } else { resolve(rows); } }); }); } /** * Get screenshot by ID */ async getScreenshotById(id) { return new Promise((resolve, reject) => { const query = 'SELECT * FROM screenshots WHERE id = ?'; this.db.get(query, [id], (err, row) => { if (err) { reject(err); } else { resolve(row); } }); }); } /** * Get screenshots by monitor ID */ async getScreenshotsByMonitor(monitorId, limit = 100, offset = 0) { return this.getScreenshots(limit, offset, monitorId); } /** * Delete screenshot by ID */ async deleteScreenshot(id) { return new Promise((resolve, reject) => { // First get the screenshot data this.getScreenshotById(id).then(screenshot => { if (!screenshot) { reject(new Error('Screenshot not found')); return; } // Delete files fs.remove(screenshot.file_path).catch(() => {}); fs.remove(screenshot.thumbnail_path).catch(() => {}); // Delete from database const query = 'DELETE FROM screenshots WHERE id = ?'; this.db.run(query, [id], function(err) { if (err) { reject(err); } else { resolve(this.changes); } }); }).catch(reject); }); } /** * Get statistics about stored screenshots */ async getStats(monitorId = null) { return new Promise((resolve, reject) => { let query = ` SELECT COUNT(*) as total_screenshots, SUM(file_size) as total_size, MIN(timestamp) as first_screenshot, MAX(timestamp) as last_screenshot FROM screenshots `; const params = []; if (monitorId !== null) { query += ` WHERE monitor_id = ?`; params.push(monitorId); } this.db.get(query, params, (err, row) => { if (err) { reject(err); } else { resolve(row); } }); }); } /** * Get monitor statistics */ async getMonitorStats() { return new Promise((resolve, reject) => { const query = ` SELECT monitor_id, monitor_name, COUNT(*) as screenshot_count, SUM(file_size) as total_size, MIN(timestamp) as first_screenshot, MAX(timestamp) as last_screenshot FROM screenshots GROUP BY monitor_id, monitor_name ORDER BY monitor_id `; this.db.all(query, (err, rows) => { if (err) { reject(err); } else { resolve(rows); } }); }); } /** * Get available monitors */ getMonitors() { return [...this.monitors]; } /** * Update configuration */ updateConfig(newConfig) { this.config = { ...this.config, ...newConfig }; console.log('Configuration updated:', this.config); } /** * Get current configuration */ getConfig() { return { ...this.config }; } /** * Close database connection */ async close() { return new Promise((resolve, reject) => { if (this.db) { this.db.close((err) => { if (err) { reject(err); } else { resolve(); } }); } else { resolve(); } }); } } module.exports = ScreenshotMonitor;