feat: incluir participant y fromMe en key de reacciones para grupos

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

@ -472,5 +472,23 @@ export const migrations: Migration[] = [
db.exec(`CREATE INDEX IF NOT EXISTS idx_task_origins_task ON task_origins (task_id);`); db.exec(`CREATE INDEX IF NOT EXISTS idx_task_origins_task ON task_origins (task_id);`);
db.exec(`CREATE INDEX IF NOT EXISTS idx_task_origins_chat_msg ON task_origins (chat_id, message_id);`); db.exec(`CREATE INDEX IF NOT EXISTS idx_task_origins_chat_msg ON task_origins (chat_id, message_id);`);
} }
},
{
version: 18,
name: 'task-origins-participant-fromme',
checksum: 'v18-task-origins-participant-fromme-2025-10-21',
up: (db: Database) => {
try {
const cols = db.query(`PRAGMA table_info(task_origins)`).all() as any[];
const hasParticipant = Array.isArray(cols) && cols.some((c: any) => String(c.name) === 'participant');
if (!hasParticipant) {
db.exec(`ALTER TABLE task_origins ADD COLUMN participant TEXT NULL;`);
}
const hasFromMe = Array.isArray(cols) && cols.some((c: any) => String(c.name) === 'from_me');
if (!hasFromMe) {
db.exec(`ALTER TABLE task_origins ADD COLUMN from_me INTEGER NULL;`);
}
} catch {}
}
} }
]; ];

@ -478,12 +478,17 @@ export class WebhookServer {
// Delegar el manejo del comando // Delegar el manejo del comando
const messageId = typeof data?.key?.id === 'string' ? data.key.id : null; const messageId = typeof data?.key?.id === 'string' ? data.key.id : null;
const participantForKey = typeof data?.key?.participantAlt === 'string'
? data.key.participantAlt
: (typeof data?.key?.participant === 'string' ? data.key.participant : null);
const outcome = await CommandService.handleWithOutcome({ const outcome = await CommandService.handleWithOutcome({
sender: normalizedSenderId, sender: normalizedSenderId,
groupId: data.key.remoteJid, groupId: data.key.remoteJid,
message: messageText, message: messageText,
mentions, mentions,
messageId: messageId || undefined messageId: messageId || undefined,
participant: participantForKey || undefined,
fromMe: !!data?.key?.fromMe
}); });
const responses = outcome.responses; const responses = outcome.responses;
@ -516,7 +521,10 @@ export class WebhookServer {
} }
const emoji = outcome.ok ? '🤖' : '⚠️'; const emoji = outcome.ok ? '🤖' : '⚠️';
await ResponseQueue.enqueueReaction(data.key.remoteJid, messageId, emoji); const participant = typeof data?.key?.participantAlt === 'string'
? data.key.participantAlt
: (typeof data?.key?.participant === 'string' ? data.key.participant : undefined);
await ResponseQueue.enqueueReaction(data.key.remoteJid, messageId, emoji, { participant, fromMe: !!data?.key?.fromMe });
} catch (e) { } catch (e) {
// No romper el flujo por errores de reacción // No romper el flujo por errores de reacción
if (process.env.NODE_ENV !== 'test') { if (process.env.NODE_ENV !== 'test') {

@ -18,6 +18,8 @@ type CommandContext = {
message: string; // raw message text message: string; // raw message text
mentions: string[]; // array of raw JIDs mentioned mentions: string[]; // array of raw JIDs mentioned
messageId?: string; // id del mensaje origen (para task_origins y reacciones) messageId?: string; // id del mensaje origen (para task_origins y reacciones)
participant?: string; // JID del autor del mensaje origen (en grupos)
fromMe?: boolean; // si el mensaje origen fue enviado por la instancia
}; };
export type CommandResponse = { export type CommandResponse = {
@ -1216,10 +1218,19 @@ export class CommandService {
// Registrar origen del comando para esta tarea (Fase 1) // Registrar origen del comando para esta tarea (Fase 1)
try { try {
if (groupIdToUse && isGroupId(groupIdToUse) && context.messageId) { if (groupIdToUse && isGroupId(groupIdToUse) && context.messageId) {
this.dbInstance.prepare(` const participant = typeof context.participant === 'string' ? context.participant : null;
const fromMe = typeof context.fromMe === 'boolean' ? (context.fromMe ? 1 : 0) : null;
try {
this.dbInstance.prepare(`
INSERT OR IGNORE INTO task_origins (task_id, chat_id, message_id, participant, from_me)
VALUES (?, ?, ?, ?, ?)
`).run(taskId, groupIdToUse, context.messageId, participant, fromMe);
} catch {
this.dbInstance.prepare(`
INSERT OR IGNORE INTO task_origins (task_id, chat_id, message_id) INSERT OR IGNORE INTO task_origins (task_id, chat_id, message_id)
VALUES (?, ?, ?) VALUES (?, ?, ?)
`).run(taskId, groupIdToUse, context.messageId); `).run(taskId, groupIdToUse, context.messageId);
}
} }
} catch {} } catch {}

@ -108,12 +108,14 @@ export const ResponseQueue = {
}, },
// Encolar una reacción con idempotencia (24h) usando metadata canónica // Encolar una reacción con idempotencia (24h) usando metadata canónica
async enqueueReaction(chatId: string, messageId: string, emoji: string): Promise<void> { async enqueueReaction(chatId: string, messageId: string, emoji: string, opts?: { participant?: string; fromMe?: boolean }): Promise<void> {
try { try {
if (!chatId || !messageId || !emoji) return; if (!chatId || !messageId || !emoji) return;
// Construir JSON canónico // Construir JSON canónico (incluir participant/fromMe si están disponibles)
const metaObj = { kind: 'reaction', emoji, chatId, messageId }; const metaObj: any = { kind: 'reaction', emoji, chatId, messageId };
if (typeof opts?.fromMe === 'boolean') metaObj.fromMe = !!opts.fromMe;
if (opts?.participant) metaObj.participant = opts.participant;
const metadata = JSON.stringify(metaObj); const metadata = JSON.stringify(metaObj);
const emojiLabel = emoji === '✅' ? 'check' : (emoji === '🤖' ? 'robot' : (emoji === '⚠️' ? 'warn' : 'other')); const emojiLabel = emoji === '✅' ? 'check' : (emoji === '🤖' ? 'robot' : (emoji === '⚠️' ? 'warn' : 'other'));
@ -173,8 +175,13 @@ export const ResponseQueue = {
if (!chatId || !messageId || !emoji) { if (!chatId || !messageId || !emoji) {
return { ok: false, error: 'invalid_reaction_metadata' }; return { ok: false, error: 'invalid_reaction_metadata' };
} }
const fromMe = !!meta.fromMe;
const key: any = { remoteJid: chatId, fromMe, id: messageId };
if (meta.participant) {
key.participant = String(meta.participant);
}
const payload = { const payload = {
key: { remoteJid: chatId, fromMe: false, id: messageId }, key,
reaction: emoji reaction: emoji
}; };
try { try {

@ -285,11 +285,20 @@ export class TaskService {
const rxEnabled = String(process.env.REACTIONS_ENABLED || 'false').toLowerCase(); const rxEnabled = String(process.env.REACTIONS_ENABLED || 'false').toLowerCase();
const enabled = ['true','1','yes','on'].includes(rxEnabled); const enabled = ['true','1','yes','on'].includes(rxEnabled);
if (enabled) { if (enabled) {
const origin = this.dbInstance.prepare(` let origin: any = null;
try {
origin = this.dbInstance.prepare(`
SELECT chat_id, message_id, created_at, participant, from_me
FROM task_origins
WHERE task_id = ?
`).get(taskId) as any;
} catch {
origin = this.dbInstance.prepare(`
SELECT chat_id, message_id, created_at SELECT chat_id, message_id, created_at
FROM task_origins FROM task_origins
WHERE task_id = ? WHERE task_id = ?
`).get(taskId) as any; `).get(taskId) as any;
}
if (origin && origin.chat_id && origin.message_id) { if (origin && origin.chat_id && origin.message_id) {
const chatId = String(origin.chat_id); const chatId = String(origin.chat_id);
@ -317,7 +326,9 @@ export class TaskService {
} }
if (allowed) { if (allowed) {
// Encolar reacción ✅ con idempotencia; no bloquear si falla // Encolar reacción ✅ con idempotencia; no bloquear si falla
ResponseQueue.enqueueReaction(chatId, String(origin.message_id), '✅') const participant = origin && origin.participant ? String(origin.participant) : undefined;
const fromMe = (origin && (origin.from_me === 1 || origin.from_me === true)) ? true : undefined;
ResponseQueue.enqueueReaction(chatId, String(origin.message_id), '✅', { participant, fromMe })
.catch(() => {}); .catch(() => {});
} }
} }

Loading…
Cancel
Save