|
|
|
|
@ -2,6 +2,8 @@ import type { Database } from 'bun:sqlite';
|
|
|
|
|
import { db, ensureUserExists } from '../db';
|
|
|
|
|
import { AllowedGroups } from '../services/allowed-groups';
|
|
|
|
|
import { isGroupId } from '../utils/whatsapp';
|
|
|
|
|
import { ResponseQueue } from '../services/response-queue';
|
|
|
|
|
import { Metrics } from '../services/metrics';
|
|
|
|
|
|
|
|
|
|
type CreateTaskInput = {
|
|
|
|
|
description: string;
|
|
|
|
|
@ -278,6 +280,53 @@ export class TaskService {
|
|
|
|
|
`)
|
|
|
|
|
.run(ensured, taskId);
|
|
|
|
|
|
|
|
|
|
// Fase 2: reacción ✅ al completar dentro del TTL y con gating
|
|
|
|
|
try {
|
|
|
|
|
const rxEnabled = String(process.env.REACTIONS_ENABLED || 'false').toLowerCase();
|
|
|
|
|
const enabled = ['true','1','yes','on'].includes(rxEnabled);
|
|
|
|
|
if (enabled) {
|
|
|
|
|
const origin = this.dbInstance.prepare(`
|
|
|
|
|
SELECT chat_id, message_id, created_at
|
|
|
|
|
FROM task_origins
|
|
|
|
|
WHERE task_id = ?
|
|
|
|
|
`).get(taskId) as any;
|
|
|
|
|
|
|
|
|
|
if (origin && origin.chat_id && origin.message_id) {
|
|
|
|
|
const chatId = String(origin.chat_id);
|
|
|
|
|
const scope = String(process.env.REACTIONS_SCOPE || 'groups').toLowerCase();
|
|
|
|
|
if (scope === 'all' || isGroupId(chatId)) {
|
|
|
|
|
// TTL desde REACTIONS_TTL_DAYS (usar tal cual; default 14 si inválido)
|
|
|
|
|
const ttlDaysEnv = Number(process.env.REACTIONS_TTL_DAYS);
|
|
|
|
|
const ttlDays = Number.isFinite(ttlDaysEnv) && ttlDaysEnv > 0 ? ttlDaysEnv : 14;
|
|
|
|
|
const maxAgeMs = ttlDays * 24 * 60 * 60 * 1000;
|
|
|
|
|
|
|
|
|
|
const createdRaw = String(origin.created_at || '');
|
|
|
|
|
const createdIso = createdRaw.includes('T') ? createdRaw : (createdRaw.replace(' ', 'T') + 'Z');
|
|
|
|
|
const createdMs = Date.parse(createdIso);
|
|
|
|
|
const withinTtl = Number.isFinite(createdMs) ? (Date.now() - createdMs <= maxAgeMs) : false;
|
|
|
|
|
|
|
|
|
|
if (withinTtl) {
|
|
|
|
|
// Gating 'enforce' para grupos
|
|
|
|
|
let allowed = true;
|
|
|
|
|
if (isGroupId(chatId)) {
|
|
|
|
|
try { (AllowedGroups as any).dbInstance = this.dbInstance; } catch {}
|
|
|
|
|
const mode = String(process.env.GROUP_GATING_MODE || 'off').toLowerCase();
|
|
|
|
|
if (mode === 'enforce') {
|
|
|
|
|
try { allowed = AllowedGroups.isAllowed(chatId); } catch { allowed = true; }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (allowed) {
|
|
|
|
|
// Encolar reacción ✅ con idempotencia; no bloquear si falla
|
|
|
|
|
ResponseQueue.enqueueReaction(chatId, String(origin.message_id), '✅')
|
|
|
|
|
.then(() => { try { Metrics.inc('reactions_enqueued_total', 1, { emoji: 'check' }); } catch {} })
|
|
|
|
|
.catch(() => {});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch {}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
status: 'updated',
|
|
|
|
|
task: {
|
|
|
|
|
|