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
JavaScript
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;