@botmesh/core exporta
constantes de trigger para el primer contacto de un usuario, para los
tipos de mensaje (imágenes, notas de voz, ubicaciones…) y para las
llamadas entrantes. Funcionan en cualquier sitio que acepte un trigger:
bot.when(), flow() (incluido .scope()) y
bot.conversation(name, fn, { trigger }).
WELCOME
WELCOME se dispara en la primera interacción de un usuario con el bot:
- Ámbito: por chat en un DM y por participante en un grupo (cada
miembro recibe su bienvenida), sin importar
conversationScope. Un DM y un grupo cuentan como primeros contactos distintos. Para limitarlo a DMs:flow(WELCOME).scope('dm')o{ ...WELCOME, scope: 'dm' }. - Qué cuenta como primer contacto: el primer mensaje que llega a la
etapa de matchers. Un comando (
/help), una conversación activa, la reanudación de un checkpoint, un hookbeforeque aborta o el filtrogroupModeno gastan la bienvenida. - Precedencia:
WELCOMEva antes que cualquier otro matcher, también antes que los triggers de texto. Si el primer mensaje es “menu”, lo atiendeWELCOME; llama actx.pass()para quebot.when('menu')también lo reciba. - Persistencia: se guarda en el clave/valor de cursores del store (lo
soportan todos los stores incluidos), así que sobrevive a reinicios con un
store persistente (
MemoryStoreconfile, SQLite, Redis, Postgres, MySQL o Mongo). Las claves son hashes (welcome:<sha256>), sin ids ni teléfonos en claro. UnIStorepropio sin cursores usa una caché en memoria que no sobrevive a reinicios. - Concurrencia: se decide dentro de la cola FIFO por chat, así que dos primeros mensajes simultáneos nunca reciben dos bienvenidas. Entre varios procesos que comparten store es check-then-set: enruta cada chat a un solo proceso, como ya exigen las conversaciones.
- Coste: sin handler
WELCOME, cero lecturas extra al store. Con handler, como máximo una lectura (dos en grupos) y una escritura por usuario y proceso; después responde una caché en memoria. - Si falla la lectura del store, no se saluda (falla cerrado), el error va a
bot.on('error')y el mensaje sigue su camino normal.
Rearmar la bienvenida
slashCommandsPlugin llama a resetWelcome en /reset (el DM, o el
participante que lo envía). /cancel solo cancela la conversación; la
bienvenida sigue gastada. bot.deleteSubject(userId) también rearma la
bienvenida del usuario borrado. exportSubject no incluye este dato.
Triggers por tipo de mensaje
Precedencia
Los matchers corren por especificidad (mayor primero; con empate gana el registrado primero):WELCOME (100) > igualdad / string (40) > startsWith (30) > endsWith
(25) > RegExp (20) > contains (10) > función predicado (5) > un tipo
(IMAGE…, bot.onType) (2) > multi-tipo (MEDIA,
typeTrigger(a, b)) (1) > catch-all (bot.onMessage).
Un trigger de texto nunca queda tapado por uno de tipo: una imagen con el
caption “hola” la atiende bot.when('hola'). bot.onType('image') y
bot.when(IMAGE) están en el mismo nivel.
Un trigger undefined o null (una variable de entorno sin definir, un
import que falta), solo, dentro de un array o como { pattern }, lanza un
TypeError al registrarse. Lo mismo pasa con un patrón de texto vacío o en
blanco ('', ' ', ['hola', ''], { pattern: '' }): coincidiría con
todo mensaje sin texto (una foto sin caption), o con todo mensaje con
contains/startsWith/endsWith. Usa IMAGE/MEDIA para media y
bot.onMessage() para un catch-all. [] se sigue aceptando: nunca coincide y
es el trigger habitual de un flujo al que solo se entra con goto().
bot.lint() lo reporta (como info si el flujo solo es destino de un goto) y
la regla de ESLint reporta cualquier trigger vacío antes de ejecutar el código.
CALL
CALL reacciona a una llamada entrante de voz o video:
- Se dispara una vez por id de llamada (WhatsApp emite offer y luego
ringing, reject, timeout o terminate). Para ver cada actualización,
escucha
bot.on('call', (ev) => ...). - El contexto tiene
type === 'call',body === ''yctx.call(elCallEvent:id,from,statusy, opcionales,chatId,isGroup,isVideoyoffline).ctx.reply()le responde a quien llama.react/quote/markRead/deleteno tienen sentido aquí. - Una llamada 1:1 se trata como el DM de quien llama. Una llamada de
grupo se proyecta en el grupo: las respuestas van al grupo,
ctx.senderes quien llama, la conversación es por participante y se aplicanscopeygroupMode. - Corre después de los hooks
before/after('call')y del evento'call'. Unbefore('call', (ev) => ev.abort())lo suprime (junto con los hooksafter('call')y el evento'call'). La blacklist, la whitelist y el gate de handoff se aplican antes de los hooks: una llamada bloqueada tampoco corre hooks'call'ni emite el evento'call'. - Las llamadas nunca llegan a comandos, triggers de texto o de función,
onMessageni al evento'message', y no se guardan como mensajes. Con el bot detenido se ignoran. - Un trigger
CALLque inicia una conversación nunca reemplaza una en curso: si quien llama ya tiene una conversación activa o un checkpoint dormido, corre el siguiente handlerCALL. Los handlers simplesbot.when(CALL, fn)sí corren a mitad de un flujo. bot.onType('call', handler)equivale abot.when(CALL, handler).- Testing:
provider.call({ id: 'c1', from: 'user-1', status: 'offer' })enMockProvider(@botmesh/core/testing).
flow(WELCOME) o con un trigger de tipo reciben un
id anónimo, así que .persistent() necesita un .id('...') explícito.
Siguientes pasos
- Conceptos básicos: dónde corre cada trigger
- Handoff y listas de acceso
- Datos persistentes por usuario
- Iniciar un flujo desde código:
bot.trigger()sin mensaje entrante