import { spawn } from "child_process"; import { join } from "path"; import { homedir } from "os"; import { DEFAULTS_TIMEOUT_MS } from "./constants"; export type Result = | { ok: true; value: T } | { ok: false; error: E }; export function formatTimestamp(date: Date): string { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, "0"); const day = String(date.getDate()).padStart(2, "0"); const hours = String(date.getHours()).padStart(2, "0"); const minutes = String(date.getMinutes()).padStart(2, "0"); const seconds = String(date.getSeconds()).padStart(2, "0"); return `${year}-${month}-${day} at ${hours}.${minutes}.${seconds}`; } export type FilenameValidationError = | "empty" | "too_long" | "path_traversal" | "invalid_chars"; export function validateFilename( filename: string ): Result { if (!filename || filename.trim() === "") { return { ok: false, error: "empty" }; } if (filename.length > 255) { return { ok: false, error: "too_long" }; } // Check for path traversal attempts if ( filename.includes("..") || filename.includes("/") || filename.includes("\\") ) { return { ok: false, error: "path_traversal" }; } // Check for null bytes and control characters if (/[\x00-\x1f\x7f-\x9f]/.test(filename)) { return { ok: false, error: "invalid_chars" }; } return { ok: true, value: filename.trim() }; } export async function getCaptureDirectory(): Promise { return new Promise((resolve) => { const proc = spawn("defaults", ["read", "com.apple.screencapture", "location"]); let output = ""; let timeoutId: Timer | null = null; proc.stdout.on("data", (data) => { output += data.toString(); }); proc.on("close", (code) => { if (timeoutId) clearTimeout(timeoutId); if (code === 0 && output.trim()) { resolve(output.trim()); } else { resolve(join(homedir(), "Desktop")); } }); proc.on("error", () => { if (timeoutId) clearTimeout(timeoutId); resolve(join(homedir(), "Desktop")); }); // Add timeout for defaults command timeoutId = setTimeout(() => { proc.kill("SIGTERM"); resolve(join(homedir(), "Desktop")); }, DEFAULTS_TIMEOUT_MS); }); }