|
|
|
@ -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: {
|
|
|
|
|