/** * Queuing an AutoBuyer run. * * Lifted out of POST /api/autobuyer/automations so the master dashboard's * control link can queue the same run over its WebSocket without going back * through HTTP — and, more to the point, without a second copy of the checks * that decide how many times a purchase fires. * * Queuing is always allowed. Nothing arms the AutoBuyer any more, so the only * thing standing between a queued run and a moving mouse is whether * clicker/runner.py is up to claim it. */ import { createRun } from './db'; import { findAutomation, resolveSteps } from './automations'; export class QueueRunError extends Error { constructor(message: string, readonly status: number) { super(message); this.name = 'QueueRunError'; } } export interface QueuedRun { runId: number; totalSteps: number; inputs: Record; } export function queueRun(automationId: string, supplied: Record = {}): QueuedRun { const found = findAutomation(automationId); if (!found) { throw new QueueRunError(`No automation "${automationId}"`, 404); } if (found.automation.steps.length === 0) { throw new QueueRunError(`"${found.automation.label}" has no steps yet`, 400); } // Clamp every input to what the automation declared. The count decides how // many times a purchase runs, so it is not taken on trust from the client. const inputs: Record = {}; for (const declared of found.automation.inputs ?? []) { const raw = Number(supplied[declared.id]); const value = Number.isFinite(raw) ? Math.floor(raw) : declared.default; inputs[declared.id] = Math.min(declared.max, Math.max(declared.min, value)); } let resolved; try { resolved = resolveSteps(found.firm, found.automation, inputs); } catch (err: any) { throw new QueueRunError(err?.message ?? 'Could not expand the steps', 400); } const run = createRun(automationId, resolved.length, inputs); return { runId: run.id, totalSteps: run.total_steps, inputs }; }