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_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
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({
sender: normalizedSenderId,
groupId: data.key.remoteJid,
message: messageText,
mentions,
messageId: messageId || undefined
messageId: messageId || undefined,
participant: participantForKey || undefined,
fromMe: !!data?.key?.fromMe
});
const responses = outcome.responses;
@ -516,7 +521,10 @@ export class WebhookServer {
}
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) {
// No romper el flujo por errores de reacción
if (process.env.NODE_ENV !== 'test') {

@ -18,6 +18,8 @@ type CommandContext = {
message: string; // raw message text
mentions: string[]; // array of raw JIDs mentioned
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 = {
@ -1216,10 +1218,19 @@ export class CommandService {
// Registrar origen del comando para esta tarea (Fase 1)
try {
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)
VALUES (?, ?, ?)
`).run(taskId, groupIdToUse, context.messageId);
}
}
} catch {}

@ -108,12 +108,14 @@ export const ResponseQueue = {
},
// 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 {
if (!chatId || !messageId || !emoji) return;
// Construir JSON canónico
const metaObj = { kind: 'reaction', emoji, chatId, messageId };
// Construir JSON canónico (incluir participant/fromMe si están disponibles)
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 emojiLabel = emoji === '✅' ? 'check' : (emoji === '🤖' ? 'robot' : (emoji === '⚠️' ? 'warn' : 'other'));
@ -173,8 +175,13 @@ export const ResponseQueue = {
if (!chatId || !messageId || !emoji) {
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 = {
key: { remoteJid: chatId, fromMe: false, id: messageId },
key,
reaction: emoji
};
try {

@ -285,11 +285,20 @@ export class TaskService {
const rxEnabled = String(process.env.REACTIONS_ENABLED || 'false').toLowerCase();
const enabled = ['true','1','yes','on'].includes(rxEnabled);
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
FROM task_origins
WHERE task_id = ?
`).get(taskId) as any;
}
if (origin && origin.chat_id && origin.message_id) {
const chatId = String(origin.chat_id);
@ -317,7 +326,9 @@ export class TaskService {
}
if (allowed) {
// 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(() => {});
}
}

Loading…
Cancel
Save