feat: activar encolado de reacciones en web al completar tareas

Co-authored-by: aider (openrouter/openai/gpt-5) <aider@aider.chat>
main
brobert 1 week ago
parent 5efcbbc98b
commit 7a5f933b8c

@ -44,3 +44,10 @@ export const icsRateLimitPerMin = Math.max(0, Math.floor(ICS_RATE_LIMIT_PER_MIN)
const UNCOMPLETE_WINDOW_MIN_RAW = Number(env.UNCOMPLETE_WINDOW_MIN || 1440); const UNCOMPLETE_WINDOW_MIN_RAW = Number(env.UNCOMPLETE_WINDOW_MIN || 1440);
export const UNCOMPLETE_WINDOW_MIN = Math.max(1, Math.floor(UNCOMPLETE_WINDOW_MIN_RAW)); export const UNCOMPLETE_WINDOW_MIN = Math.max(1, Math.floor(UNCOMPLETE_WINDOW_MIN_RAW));
export const uncompleteWindowMs = UNCOMPLETE_WINDOW_MIN * 60 * 1000; export const uncompleteWindowMs = UNCOMPLETE_WINDOW_MIN * 60 * 1000;
// Reacciones (flags de característica para la web)
const REACTIONS_TTL_DAYS_RAW = Number(env.REACTIONS_TTL_DAYS || 14);
export const REACTIONS_TTL_DAYS = Math.max(1, Math.floor(REACTIONS_TTL_DAYS_RAW));
export const REACTIONS_ENABLED = toBool(env.REACTIONS_ENABLED || '');
export const REACTIONS_SCOPE = ((env.REACTIONS_SCOPE || 'groups').trim().toLowerCase() === 'all' ? 'all' : 'groups');
export const GROUP_GATING_MODE = (env.GROUP_GATING_MODE || 'off').trim().toLowerCase();

@ -1,5 +1,6 @@
import type { RequestHandler } from './$types'; import type { RequestHandler } from './$types';
import { getDb } from '$lib/server/db'; import { getDb } from '$lib/server/db';
import { REACTIONS_ENABLED, REACTIONS_TTL_DAYS, REACTIONS_SCOPE, GROUP_GATING_MODE } from '$lib/server/env';
export const POST: RequestHandler = async (event) => { export const POST: RequestHandler = async (event) => {
const userId = event.locals.userId ?? null; const userId = event.locals.userId ?? null;
@ -101,6 +102,75 @@ export const POST: RequestHandler = async (event) => {
const statusStr = Number(updated.completed || 0) === 1 ? 'updated' : 'already'; const statusStr = Number(updated.completed || 0) === 1 ? 'updated' : 'already';
// Encolar reacción ✅ desde la web si procede (idéntico formato al bot)
try {
if (statusStr === 'updated' && REACTIONS_ENABLED) {
// Buscar origen con columnas opcionales (participant/from_me) si existen
let origin: any = null;
try {
origin = db.prepare(`
SELECT chat_id, message_id, created_at, participant, from_me
FROM task_origins
WHERE task_id = ?
`).get(taskId) as any;
} catch {
origin = db.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);
// Scope: por defecto solo reaccionar en grupos
if (REACTIONS_SCOPE === 'all' || chatId.endsWith('@g.us')) {
// TTL (por defecto 14 días)
const ttlMs = REACTIONS_TTL_DAYS * 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 <= ttlMs) : false;
// Gating 'enforce' (solo aplica a grupos)
let allowed = true;
if (GROUP_GATING_MODE === 'enforce' && chatId.endsWith('@g.us')) {
const row = db.prepare(`SELECT 1 FROM allowed_groups WHERE group_id = ? AND status = 'allowed' LIMIT 1`).get(chatId) as any;
allowed = !!row;
}
if (withinTtl && allowed) {
// Idempotencia 24h por metadata canónica exacta
const nowIso = new Date().toISOString().replace('T', ' ').replace('Z', '');
const cutoff = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString().replace('T', ' ').replace('Z', '');
const meta: any = { kind: 'reaction', emoji: '✅', chatId, messageId: String(origin.message_id) };
if (origin && (origin.from_me === 1 || origin.from_me === true)) meta.fromMe = true;
if (origin && origin.participant) meta.participant = String(origin.participant);
const metadata = JSON.stringify(meta);
const exists = db.prepare(`
SELECT 1
FROM response_queue
WHERE metadata = ?
AND status IN ('queued','processing','sent')
AND (updated_at > ? OR created_at > ?)
LIMIT 1
`).get(metadata, cutoff, cutoff) as any;
if (!exists) {
db.prepare(`
INSERT INTO response_queue (recipient, message, metadata, next_attempt_at)
VALUES (?, ?, ?, ?)
`).run(chatId, '', metadata, nowIso);
}
}
}
}
}
} catch {}
const body = { const body = {
status: statusStr, status: statusStr,
task: { task: {

Loading…
Cancel
Save