[🐛] Respuestas rapidas - addAction se ejecuta mas de una vez
¿Que versión estas usando?
v2
¿Sobre que afecta?
Otro
Describe tu problema
Hola, tengo un problema muy puntual, cuando un usuario escribe mientras el bot esta procesando una entrada, este aveces se cae causando un error llamado: "queue cleared", me imagino que debe suceder porque tengo una funcion addAction la cual espera una respuesta con {capture: true, idle: time_idle} y entonces al momento de recibir la respuesta empieza a procesarla, mientras la procesa, llega otro mensaje y tambien intenta procesarla y entonces tiene 2 procesos y crashea, el problema esta en eso, intente solucionarlo agregando un sistema que intenta procesar las entradas una por una, intente hacer que al momento de tomar el primer mensaje lo registre y siga la ejecución si otro mensaje llegase a pasar de alguna manera me gustaria que el bot lo ignore, intente hacerlo con endflow() intentando cortar ese "hilo" secundario pero el "hilo" principal se ve afectado, de que manera puedo arreglar esto?
Reproducir error
No response
Información Adicional
No response
Podrías intentar implementar esta solución a tu addAction (esta en ingles, pero aqui tienes en español, me parece que ingles tiene más información)
espero te ayude
Podrías intentar implementar esta solución a tu addAction (está en inglés, pero aquí tienes en español, me parece que inglés tiene más información)
Espero te ayude
Hola, gracias por la respuesta, lamentablemente eso no soluciona el problema, solamente agrupa los mensajes, despues tendremos el mismo escenario mientras el bot procese ese nuevo mensaje agrupado, aparte como mostre en la imagen, no necesito que los mensajes se agrupen, solamente necesito 1 mensaje el cual incluye la respuesta del usuario. De momento lo unico que hago es cuando detecta esto, directamente le notifica que no escriba tan rapido y finaliza, algo que no me gusta del todo pero es lo que hay.
Bueno... yo no he tenido mucho este problema por que cada entrada la verifico manualmente en lugar de usar subflujos (no se como los uses) si pudieras enviar el fragmento de tu codigo que se encarga de eso, quizas podriamos darle una solución, pero asi como lo veo, podrias intentar esto (que es lo que yo hago personalmente)
import { addKeyword, EVENTS } from "@builderbot/bot";
const flowOpcionUno = addKeyword(EVENTS.ACTION)
.addAnswer("Opcion 1")
const flowOpcionDos = addKeyword(EVENTS.ACTION)
.addAnswer("Opcion 2")
const flowOpcionTres = addKeyword(EVENTS.ACTION)
.addAnswer("Opcion 3")
const flowOpcionCuatro = addKeyword(EVENTS.ACTION)
.addAnswer("Opcion 4")
const flowOpcionMenu = addKeyword("opciones")
.addAnswer(
[
"Selecione una opcion",
"1. ",
"2. ",
"3. ",
"4. ",
"0. Regresar"
],
{ capture: true},
async (ctx, {gotoFlow, fallBack}) => {
//Aqui puedes usar función para filtrar el mensaje, como borrar espacios
//letras, o cosas asi
const message = ctx.body
//En lugar de ifs pueden ser switch
if (message == "1")
{
return gotoFlow(flowOpcionUno)
}
else if (message == "2")
{
return gotoFlow(flowOpcionDos)
}
else if (message == "3")
{
return gotoFlow(flowOpcionTres)
}
else if (message == "4")
{
return gotoFlow(flowOpcionCuatro)
}
else if (message == "0")
{
//Aqui puedes hacer que llame a otro, no es muy que se llame asi mismo
// y puede causar error
return gotoFlow(flowOpcionMenu)
}
else
{
return fallBack("Opción incorrecta")
}
}
)
De esta forma, el flujo solo se crea con la palabra clave y el gotoFlow solo se activa con el primer mensaje (que se asume es la opción) y no se crean múltiples, salvo que por alguna razón el usuario escriba "opciones", pero la solución a eso ya creo sale un poco del tema
yo me imagino todo como si fuera un simple programa consola y no un bot que recibe y envía mensajes
Si mira, tengo este codigo, la funcion verify_message lo que hace es manejar ella todo el flujo para poder evitar este problema de queue cleared, es una promesa la cual si detecta que hay otra peticion y el bot esta trabajando en una, espere a que termine, y recien da paso, la funcion de agregar flujo y eliminar flujo lo que hace es un map, el cual registra cada flow para que cuando evalue que es la primera vez que se entro a ese flujo, proceda, si no que finalice(es lo que explique que si detecta que van a volver a entrar a ese flujo directamente le diga que escriba mas lento) y poco mas, lo demas es para que veas como manejo la logica, se supone que cuando va a hacer un return, la promesa se resuelve y si hay otro esperando, se libera e inicia, y creo que por aqui van los problemas, y no se bien como solucionarlo, a eso voy, pasa que cuando tengo un flujo con capture true si en el momento el usuario envia muy rapido y el bot esta procesando todavia el primer mensaje y llega otro, entramos en esa carrera de cual tiene el control.
Comentas que no usas subflujos pero en mi caso es importante y no tener otro .addAction manejando el capture true porque necesito reiniciar el idle, si hago un fallback por ejemplo el idle se pierde.
const opciones = addKeyword(EVENTS.ACTION)
.addAction({ capture: true, idle: time_idle }, (ctx, tools) =>
verify_message(ctx, tools, async (ctx, { gotoFlow, flowDynamic, state}) =>
{
await agregar_flujo(ctx.from, 'opciones', gotoFlow, flowDynamic)
if (ctx?.idleFallBack)
{
return gotoFlow(flow_end)
}
if (!["1", "2", "3", "4", "5", "6", "0"].includes(ctx.body))
{
await flowDynamic(EXTRAS.callback_opciones)
//ELIMINAMOS EL FLUJO ACTUAL PARA QUE PUEDA VOLVER A ACCEDER
await eliminar_flujo(ctx.from, 'opciones', gotoFlow)
return gotoFlow(opciones)
}
switch (ctx.body)
{
case "1": // 1 mes
await state.update({options: "1"})
return gotoFlow(flow_orden)
case "2": // 2 meses
await state.update({options: "2"})
return gotoFlow(flow_orden)
case "3": // 3 meses
await state.update({options: "3"})
return gotoFlow(flow_orden)
case "4": // 4 meses
await state.update({options: "4"})
return gotoFlow(flow_orden)
case "5": // 5 meses
await state.update({options: "5"})
return gotoFlow(flow_orden)
case "6": // 6 meses
await state.update({options: "6"})
return gotoFlow(flow_orden)
case "0": //Regresar
//ELIMINAMOS EL FLUJO ACTUAL PARA QUE PUEDA VOLVER A ACCEDER
await eliminar_flujo(ctx.from, 'flow_metodo_pago', gotoFlow)
return gotoFlow(flow_metodo_pago)
default: //seguridad extra
await flowDynamic("Opcion no valida, por favor intentalo de nuevo.")
return gotoFlow(flow_end)
}
}))
Respecto del error "queue cleared" no me ha pasado (aún), uso pm2 para en caso muere por cualquier motivo, se reinicie automáticamente. ( mi bot maneja bastante carga, y no suele dar ese error)
De lo otro, podrías usar una variable externa o globalState para que, usando el número de teléfono, se cree una variable en cada parte del proceso, sabemos que solo queremos evaluar una cosa a la vez, y siendo el problema que el usuario escribe muy rápido y puede haber duplicados de un mismo proceso, podemos hacer lo siguiente:
- con la globalState (o algo asi, yo uso un diccionario externo para diferenciar) creamos un arreglo cuyo largo sea el total de las partes del proceso con booleanos
- cuando el usuario ejecute una opción, verifica si la posición a la que ese proceso pertenece esta siendo evaluada, en cuyo caso, mostramos un mensaje como "Procesando anterior solicitud" si es cierto, sino ejecutamos lo que sea que se requiera y cambiamos el estado de esa opción para ese usuario a verdadero.
- cuando termine la ejecución de ese proceso, se cambia a falso para indicar que ya termino de procesar ello y esta listo para obtener otro
De forma tal que, aunque escriba muy rápido, solo se evaluara la primera y bloqueara todo lo demás, algo a si seria usando mi ejemplo anterior
import { addKeyword, EVENTS } from "@builderbot/bot";
const todoUsuarios = {}
const flowOpcionUno = addKeyword(EVENTS.ACTION)
.addAnswer("Opcion 1", null, async (ctx, etc) => {
todoUsuarios[ctx.from]["lugares"][0] = false
})
const flowOpcionDos = addKeyword(EVENTS.ACTION)
.addAnswer("Opcion 2", null, async (ctx, etc) => {
todoUsuarios[ctx.from]["lugares"][0] = false
})
const flowOpcionTres = addKeyword(EVENTS.ACTION)
.addAnswer("Opcion 3", null, async (ctx, etc) => {
todoUsuarios[ctx.from]["lugares"][0] = false
})
const flowOpcionCuatro = addKeyword(EVENTS.ACTION)
.addAnswer("Opcion 4", null, async (ctx, etc) => {
todoUsuarios[ctx.from]["lugares"][0] = false
})
const flowOpcionMenu = addKeyword("opciones")
//Uso un addAction para registrar al usuario dentro de la lista si no lo tengo ya
.addAction(
async (ctx, etc) => {
if (!todoUsuarios.hasOwnProperty(ctx.from))
{
todoUsuarios[ctx.from] = {
"lugares":[false] //tiene que ser tan largo como flujos donde se pueda acceder
}
}
}
)
.addAnswer(
[
"Selecione una opcion",
"1. ",
"2. ",
"3. ",
"4. ",
"0. Regresar"
],
{ capture: true},
async (ctx, {gotoFlow, fallBack, endFlow}) => {
//Aqui puedes usar función para filtrar el mensaje, como borrar espacios
//letras, o cosas asi
const message = ctx.body
//en este caso, 0 representa este flujo como ID
// (aunque creo que puedes obtener el ID del flujo que les asigna la libreria)
if (todoUsuarios[ctx.from]["lugares"][0] == true)
{
return endFlow("Muy rapido vaquero")
}
todoUsuarios[ctx.from]["lugares"][0] = true
//En lugar de ifs pueden ser switch
if (message == "1")
{
return gotoFlow(flowOpcionUno)
}
else if (message == "2")
{
return gotoFlow(flowOpcionDos)
}
else if (message == "3")
{
return gotoFlow(flowOpcionTres)
}
else if (message == "4")
{
return gotoFlow(flowOpcionCuatro)
}
else if (message == "0")
{
//Aqui puedes hacer que llame a otro, no es muy que se llame asi mismo
// y puede causar error
todoUsuarios[ctx.from]["lugares"][0] = false
return gotoFlow(flowOpcionMenu)
}
else
{
todoUsuarios[ctx.from]["lugares"][0] = false
return fallBack("Opción incorrecta")
}
}
)
NOTA: no se por que tu idle se pierde con el fallback, quizás sea por como lo tienes montado, a mi no me pasa eso (literal el meme de "en mi local funciona")
haha intente algo similar ya, de hecho es algo que hace la funcion de agregar_flujo, pero el problema en si esta en como manejamos estos "hilos secundarios" por lo que veo, cuando escribe rapido, se crean hilos en ese addaction pero no se muy bien como funciona internamente esto, es algo que me tocara investigar bien para entenderlo, como dices, puedo hacer que cuando detecta estos hilos le diga, tu solicitud esta siendo procesada, pero dsp ese hilo que hacemos con el?, si hacemos un return o endflow, como comente antes, todo se ve afectado, el "hilo principal" ,no se si me explico la vrd, pero necesitamos en algun punto manejar estos hilos secundarios para que no me de este error de "queue cleared" y cuando los intento manejar, este error desaparece pero ya no tengo este control sobre mi hilo principal, ese es el punto, por cierto que libreria usas actualmente?, cuando usaba bot-whatsapp el problema de queue cleared no aparecia, directamente el bot no respondia, y tampoco me mostraba nada en consola.
sobre el idle, q raro, porque a mi, cuando ya valido el capture true o el idle, directamente el fallback solamente vuelve a ejecutar después de las opciones(como el capture true, idle) y entonces por eso el idle sera falso porque nunca vuelve a pasar por ahi, asi que redirijo al flujo desde el inicio, por eso tengo mi estructura un poco asi, de hecho leyendo comentarios en el discord, mucha gente le pasaba lo mismo, q raro q no te sucedan estas cosas, que suerte JAKSD, saludoss.
ahora estoy usando la librería de builderbot, no la de bot-whatsapp (me gustaba, pero era incomodo de escribir jajaja) no existe forma de usar la solución del primer comentario? creo seria lo más apropiado, tomar todas las respuestas del usuario en un corto periodo de tiempo para luego evaluarlas individualmente en búsqueda de alguna valida. O otra opción, es hacer que cuando detecte la primera interacción responda rápidamente algo como "Procesando", o algún mensaje que indique al usuario que no escriba más, si no podemos impedir desde código que escriba, podemos indicarle que no lo haga XD
En caso aún la detecte y escriba rápido, si puedes saber cuando el usuario hace una interacción muy rápida, también puedes saber donde lo hizo. según lo que dijiste, cuando tu bot detecta esa situación muestra un "estas escribiendo muy rápido" y termina la ejecución, pero si además en ese mensaje obtuviera el lugar donde ocurre la acción, podría llamarla para ejecutarla otra vez cambiando un valor indicando que ya esta en uso, para cuando vengan los otros hilos por el mismo camino, se vean ya terminado por que alguno de ellos ya esta usando esa "salida de emergencia" por así decirlo
otra es que cuando el usuario interactúe la primera vez, inicie un temporizador interno de X segundos para la siguiente interacción en esa parte, en lugar de cambiar el valor al momento
¿Alguna novedad sobre esta ISSUE?