94 lines
2.7 KiB
TypeScript
94 lines
2.7 KiB
TypeScript
// Per-user conversation state for menu-driven flows.
|
|
// Currently tracks: "operator clicked Pair New, waiting for them to type the label".
|
|
// In-memory only — fine for a single-instance bot. If we ever scale horizontally,
|
|
// move this to Postgres.
|
|
|
|
const PENDING_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
|
|
const pendingPairLabel = new Map<number, number>(); // userId → expires_at_ms
|
|
|
|
export function setPendingPairLabel(userId: number): void {
|
|
pendingPairLabel.set(userId, Date.now() + PENDING_TTL_MS);
|
|
}
|
|
|
|
export function clearPendingPairLabel(userId: number): void {
|
|
pendingPairLabel.delete(userId);
|
|
}
|
|
|
|
export function consumePendingPairLabel(userId: number): boolean {
|
|
const expiresAt = pendingPairLabel.get(userId);
|
|
if (!expiresAt) return false;
|
|
pendingPairLabel.delete(userId);
|
|
return Date.now() < expiresAt;
|
|
}
|
|
|
|
// "Send a test message to this WhatsApp group" pending state.
|
|
type PendingSend = { groupId: string; expiresAt: number };
|
|
const pendingSendToGroup = new Map<number, PendingSend>();
|
|
|
|
export function setPendingSendToGroup(userId: number, groupId: string): void {
|
|
pendingSendToGroup.set(userId, { groupId, expiresAt: Date.now() + PENDING_TTL_MS });
|
|
}
|
|
|
|
export function clearPendingSendToGroup(userId: number): void {
|
|
pendingSendToGroup.delete(userId);
|
|
}
|
|
|
|
export function consumePendingSendToGroup(userId: number): string | null {
|
|
const pending = pendingSendToGroup.get(userId);
|
|
if (!pending) return null;
|
|
pendingSendToGroup.delete(userId);
|
|
if (Date.now() >= pending.expiresAt) return null;
|
|
return pending.groupId;
|
|
}
|
|
|
|
// Reminder creation wizard state.
|
|
export type WizardStep =
|
|
| "pick_account"
|
|
| "pick_group"
|
|
| "compose"
|
|
| "set_time"
|
|
| "confirm";
|
|
|
|
export type WizardState = {
|
|
step: WizardStep;
|
|
accountId?: string;
|
|
groupId?: string;
|
|
text?: string | null;
|
|
mediaId?: string | null;
|
|
caption?: string | null;
|
|
scheduledAt?: Date;
|
|
expiresAt: number;
|
|
};
|
|
|
|
const wizardState = new Map<number, WizardState>();
|
|
const WIZARD_TTL_MS = 30 * 60 * 1000;
|
|
|
|
export function getWizard(userId: number): WizardState | null {
|
|
const w = wizardState.get(userId);
|
|
if (!w) return null;
|
|
if (Date.now() >= w.expiresAt) {
|
|
wizardState.delete(userId);
|
|
return null;
|
|
}
|
|
return w;
|
|
}
|
|
|
|
export function startWizard(userId: number): WizardState {
|
|
const w: WizardState = { step: "pick_account", expiresAt: Date.now() + WIZARD_TTL_MS };
|
|
wizardState.set(userId, w);
|
|
return w;
|
|
}
|
|
|
|
export function updateWizard(userId: number, patch: Partial<WizardState>): WizardState | null {
|
|
const w = getWizard(userId);
|
|
if (!w) return null;
|
|
const next = { ...w, ...patch, expiresAt: Date.now() + WIZARD_TTL_MS };
|
|
wizardState.set(userId, next);
|
|
return next;
|
|
}
|
|
|
|
export function clearWizard(userId: number): void {
|
|
wizardState.delete(userId);
|
|
}
|