@manybot/manybot 5.6.1 → 5.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -3
- package/dist/client/banner.js +10 -0
- package/dist/client/banner.test.js +31 -0
- package/dist/client/store.js +56 -5
- package/dist/client/store.test.js +170 -0
- package/dist/config.js +28 -44
- package/dist/config.test.js +26 -0
- package/dist/drivers/baileys/adapter.js +207 -8
- package/dist/drivers/baileys/api/index.js +300 -130
- package/dist/drivers/baileys/index.js +62 -30
- package/dist/drivers/baileys/loginPrompt.js +0 -2
- package/dist/drivers/baileys/messageHandler.js +158 -4
- package/dist/drivers/baileys/messageHandler.test.js +203 -0
- package/dist/drivers/baileysAdapter.test.js +281 -0
- package/dist/drivers/jid.test.js +40 -0
- package/dist/drivers/types.js +5 -5
- package/dist/i18n/index.js +15 -2
- package/dist/kernel/activeDriverSend.js +21 -0
- package/dist/kernel/activeDriverSend.test.js +89 -0
- package/dist/kernel/alerts.js +3 -9
- package/dist/kernel/chatSession.js +65 -0
- package/dist/kernel/chatSession.test.js +46 -0
- package/dist/kernel/commandAccess.js +66 -0
- package/dist/kernel/commandAccess.test.js +74 -0
- package/dist/kernel/commandDeprecation.js +168 -0
- package/dist/kernel/commandDeprecation.test.js +107 -0
- package/dist/kernel/commandMenu.js +268 -0
- package/dist/kernel/commandMenu.test.js +234 -0
- package/dist/kernel/commandPermissions.js +125 -0
- package/dist/kernel/commandPermissions.test.js +159 -0
- package/dist/kernel/commandRegistry.js +459 -0
- package/dist/kernel/commandRegistry.test.js +156 -0
- package/dist/kernel/commandsConfig.js +517 -0
- package/dist/kernel/commandsConfig.test.js +236 -0
- package/dist/kernel/contactAutoSave.test.js +87 -0
- package/dist/kernel/driverManager.js +10 -6
- package/dist/kernel/driverManager.test.js +90 -0
- package/dist/kernel/integrationMode.js +88 -0
- package/dist/kernel/integrationMode.test.js +95 -0
- package/dist/kernel/loadIntegrationPlugin.test.js +67 -0
- package/dist/kernel/pluginApi.test.js +583 -0
- package/dist/kernel/pluginGuard.js +15 -12
- package/dist/kernel/pluginGuard.test.js +39 -0
- package/dist/kernel/pluginLoader.js +96 -1
- package/dist/kernel/pluginLoader.test.js +80 -0
- package/dist/kernel/runCommand.js +245 -0
- package/dist/kernel/runCommand.test.js +235 -0
- package/dist/kernel/sendFallbackGuard.js +19 -48
- package/dist/kernel/sendFallbackGuard.test.js +80 -0
- package/dist/kernel/sendGuard.js +38 -42
- package/dist/kernel/sendGuard.test.js +102 -0
- package/dist/kernel/settingsDb.js +4 -3
- package/dist/kernel/statusServer.js +9 -2
- package/dist/kernel/statusServer.test.js +70 -0
- package/dist/kernel/testConfig.js +183 -0
- package/dist/kernel/testConfig.test.js +181 -0
- package/dist/kernel/updateCheck.js +33 -10
- package/dist/locales/en.json +64 -13
- package/dist/locales/es.json +64 -13
- package/dist/locales/pt.json +64 -13
- package/dist/logger/logger.js +23 -3
- package/dist/logger/logger.test.js +45 -0
- package/dist/main.js +5 -76
- package/dist/plugins/__manybot_integration__/index.js +167 -0
- package/dist/plugins/__manybot_integration__/index.test.js +184 -0
- package/package.json +75 -18
- package/dist/drivers/whatsmeow/client.js +0 -252
- package/dist/drivers/whatsmeow/index.js +0 -79
- package/dist/drivers/whatsmeow/installer.js +0 -86
- package/dist/drivers/whatsmeow/supervisor.js +0 -328
- package/dist/drivers/whatsmeow/whatsmeow.proto +0 -64
package/dist/locales/es.json
CHANGED
|
@@ -36,13 +36,75 @@
|
|
|
36
36
|
"cacheLoaded": "{{count}} chat(s) cargado(s) desde la caché.",
|
|
37
37
|
"cacheLoadedStale": "{{count}} chat(s) cargado(s) desde la caché (desactualizada).",
|
|
38
38
|
"sendFailedNoFallback": "manybot: no hay driver de respaldo disponible (jid={{jid}}, primario={{driver}})",
|
|
39
|
-
"sendFailedBothDrivers": "manybot: envío falló en ambos drivers (jid={{jid}}, se probó {{primary}} y luego {{secondary}})"
|
|
39
|
+
"sendFailedBothDrivers": "manybot: envío falló en ambos drivers (jid={{jid}}, se probó {{primary}} y luego {{secondary}})",
|
|
40
|
+
"commandsConfigMissingCmd": "commands.yaml: el comando \"{{id}}\" no tiene el campo obligatorio \"cmd\" — se omitió",
|
|
41
|
+
"commandRegistryInvalidPluginCommand": "commandRegistry: el plugin \"{{plugin}}\" comando \"{{function}}\" ({{id}}) tiene \"cmd\" inválido o ausente — se omitió",
|
|
42
|
+
"commandsConfigArgumentMissingName": "commands.yaml: la entrada de argumento bajo \"{{id}}\" no tiene el campo obligatorio \"name\" — se omitió",
|
|
43
|
+
"commandsConfigArgumentMissingType": "commands.yaml: el argumento \"{{name}}\" bajo \"{{id}}\" no tiene el campo obligatorio \"type\" — se omitió",
|
|
44
|
+
"commandsConfigUnknownArgType": "commands.yaml: el argumento \"{{name}}\" bajo \"{{id}}\" tiene un tipo desconocido \"{{type}}\" — se omitió",
|
|
45
|
+
"commandsConfigChoiceArgumentNoChoices": "commands.yaml: el argumento \"{{name}}\" bajo \"{{id}}\" es de tipo \"choice\" pero no declaró ninguna opción — usando placeholder genérico en la sugerencia de uso",
|
|
46
|
+
"commandsConfigArgumentsNotList": "commands.yaml: arguments bajo \"{{id}}\" debe ser una lista — se ignoró",
|
|
47
|
+
"commandsConfigSubcommandMissingCmd": "commands.yaml: el subcomando bajo \"{{id}}\" no tiene el campo obligatorio \"cmd\" — se omitió",
|
|
48
|
+
"commandsConfigSubcommandsNotList": "commands.yaml: subcommands bajo \"{{id}}\" debe ser una lista — se ignoró",
|
|
49
|
+
"commandsConfigDuplicateSubcommand": "commands.yaml: subcomando duplicado \"{{cmd}}\" bajo \"{{id}}\" — se conserva el primero",
|
|
50
|
+
"commandsConfigReadFailed": "commands.yaml: no se pudo leer ({{path}}): {{message}}",
|
|
51
|
+
"commandsConfigParseFailed": "commands.yaml: error de parseo YAML: {{message}}",
|
|
52
|
+
"commandsConfigInvalidRoot": "commands.yaml: el nivel raíz debe ser un mapa de entradas de comandos",
|
|
53
|
+
"commandsConfigInvalidEntry": "commands.yaml: la entrada \"{{id}}\" no es un objeto — se omitió",
|
|
54
|
+
"commandsConfigImportReadFailed": "commands.yaml: no se pudo leer el import \"{{path}}\": {{message}}",
|
|
55
|
+
"commandsConfigImportParseFailed": "commands.yaml: error de parseo YAML en el import \"{{path}}\": {{message}}",
|
|
56
|
+
"commandsConfigImportInvalidRoot": "commands.yaml: el import \"{{path}}\" debe tener un mapa de secciones en el nivel raíz — se omitió",
|
|
57
|
+
"commandsConfigImportNested": "commands.yaml: el import \"{{path}}\" declara su propio \"import\" — los imports anidados no son compatibles y se ignoraron",
|
|
58
|
+
"commandsConfigImportKeyConflict": "commands.yaml: el import \"{{path}}\" redefine \"{{key}}\", que ya pertenece a \"{{owner}}\" — se conserva el primero",
|
|
59
|
+
"commandRegistryOrphanEntry": "commandRegistry: la entrada \"{{id}}\" apunta a {{plugin}}.{{function}} pero ningún plugin activo la provee — se omitió",
|
|
60
|
+
"commandRegistryInvalidEntry": "commandRegistry: la entrada \"{{id}}\" no tiene plugin/function ni text — se omitió",
|
|
61
|
+
"commandRegistryInvocationCollision": "commandRegistry: la invocación \"{{text}}\" ya pertenece a \"{{winner}}\" — descartando {{loser}} ({{via}})",
|
|
62
|
+
"commandDeprecationRenamed": "commandRegistry: comando \"{{id}}\" renombrado de \"{{old}}\" a \"{{new}}\" — notificando a quienes llamen al antiguo durante {{days}} días",
|
|
63
|
+
"commandDeprecationRemoved": "commandRegistry: comando \"{{id}}\" (cmd \"{{old}}\") fue eliminado — notificando a quienes llamen al antiguo durante {{days}} días",
|
|
64
|
+
"commandDeprecationFallbackRenamed": "El comando \"{{old}}\" se renombró a \"{{new}}\". Actualiza tu mensaje.",
|
|
65
|
+
"commandDeprecationFallbackRemoved": "El comando \"{{old}}\" ya no está disponible.",
|
|
66
|
+
"commandDeprecationReservedInvocation": "commandRegistry: la invocación \"{{text}}\" está reservada por deprecation activa de \"{{id}}\" — registro rechazado",
|
|
67
|
+
"commandManualMissing": "No hay manual detallado disponible para el comando \"{{cmd}}\".",
|
|
68
|
+
"commandNotFound": "Comando o categoría \"{{cmd}}\" no encontrado. Usa {{prefix}}{{menuCmd}} para ver la lista de comandos.",
|
|
69
|
+
"commandRegistryMenuAliasCollision": "commandRegistry: alias de menú \"{{alias}}\" colisionó con comando real \"{{winner}}\" — alias de menú ignorado"
|
|
40
70
|
},
|
|
41
71
|
"errors": {
|
|
42
72
|
"stack": "Stack",
|
|
43
73
|
"invalid_config": "Archivo de configuración inválido {{file}}: {{error}}",
|
|
44
74
|
"fix_config": "Corrige la sintaxis en {{file}} antes de iniciar el bot."
|
|
45
75
|
},
|
|
76
|
+
"menu": {
|
|
77
|
+
"intro": "Usa {prefix}<comando> para ejecutarlo o {prefix}help <comando> para ver el manual.",
|
|
78
|
+
"other": "Otros",
|
|
79
|
+
"category": "Categoría",
|
|
80
|
+
"manual": "Manual",
|
|
81
|
+
"description": "Descripción"
|
|
82
|
+
},
|
|
83
|
+
"commandPermissions": {
|
|
84
|
+
"botNotAdmin": "Este comando requiere que el bot sea administrador del grupo.",
|
|
85
|
+
"senderNotAdmin": "Este comando está restringido a administradores del grupo.",
|
|
86
|
+
"ownerOnly": "Este comando solo puede ser ejecutado por el dueño del bot.",
|
|
87
|
+
"wrongScope": "Este comando no puede usarse en este contexto.",
|
|
88
|
+
"cooldown": "Espera {{seconds}}s antes de volver a usar este comando."
|
|
89
|
+
},
|
|
90
|
+
"commandRun": {
|
|
91
|
+
"missingRequiredArg": "Faltan argumentos obligatorios.",
|
|
92
|
+
"unknownSubcommand": "Subcomando \"{{sub}}\" desconocido para !{{cmd}}. Válidos: {{valid}}."
|
|
93
|
+
},
|
|
94
|
+
"alerts": {
|
|
95
|
+
"noFallbackTitle": "manybot: sin driver de fallback",
|
|
96
|
+
"bothDriversFailedTitle": "manybot: envío fallido en ambos drivers",
|
|
97
|
+
"updateAvailableTitle": "Nueva versión del manybot disponible",
|
|
98
|
+
"updateAvailableMessage": "Instalado: {{instalado}} → disponible: {{disponible}}. Vea {{url}} para instrucciones de actualización.",
|
|
99
|
+
"reconnectHaltedTitle": "manybot detuvo la reconexión",
|
|
100
|
+
"reconnectHaltedMessage": "Desistió después de {{intento}} intentos — posible restricción de la cuenta. Ejecute connect() manualmente para intentar nuevamente."
|
|
101
|
+
},
|
|
102
|
+
"driver": {
|
|
103
|
+
"groupParticipantNotFound": "\"{{id}}\" no coincide con ningún participante actual del grupo \"{{group}}\" (comprueba si la persona sigue en el grupo o usa una respuesta/mención en lugar de escribir el número).",
|
|
104
|
+
"groupParticipantsUpdateRejected": "groupParticipantsUpdate(\"{{action}}\") rechazado para: {{detail}}",
|
|
105
|
+
"groupParticipantsUpdateFailed": "groupParticipantsUpdate(\"{{action}}\") falló para el grupo \"{{group}}\" con participantes [{{users}}]: {{message}}",
|
|
106
|
+
"pollDecryptFailed": "No se pudo descifrar el voto de la encuesta: {{error}}"
|
|
107
|
+
},
|
|
46
108
|
"onboarding": {
|
|
47
109
|
"intro": "ManyBot — primer inicio de sesión",
|
|
48
110
|
"methodPrompt": "¿Cómo quieres conectar tu cuenta de WhatsApp?",
|
|
@@ -69,17 +131,6 @@
|
|
|
69
131
|
"retrying": "Reintentando ({{attempt}}/{{max}})...",
|
|
70
132
|
"sessionWiped": "Sesión descartada, emparejando de nuevo ({{round}}/{{max}})...",
|
|
71
133
|
"connectGaveUp": "No se pudo conectar tras varios intentos. Revisa tu conexión e intenta de nuevo."
|
|
72
|
-
},
|
|
73
|
-
"whatsmeow": {
|
|
74
|
-
"installPrompt": "¿Instalar el driver whatsmeow para soporte de fallback? (EXPERIMENTAL — solo sendText e historial funcionan; otros métodos lanzan error)",
|
|
75
|
-
"unsupportedArch": "Driver whatsmeow no disponible para {{os}}-{{arch}}. El bot usará solo Baileys.",
|
|
76
|
-
"fetchingTag": "Obteniendo última versión de whatsmeow...",
|
|
77
|
-
"fetchFailed": "No se pudo contactar a Codeberg. Instalación de whatsmeow omitida.",
|
|
78
|
-
"downloading": "Descargando {{url}}...",
|
|
79
|
-
"downloadFailed": "Descarga fallida: {{reason}}",
|
|
80
|
-
"installTitle": "Driver whatsmeow",
|
|
81
|
-
"installed": "Driver whatsmeow instalado en {{path}}",
|
|
82
|
-
"restartNotice": "Reinicia el bot para que el driver whatsmeow surta efecto.",
|
|
83
|
-
"experimentalNotice": "whatsmeow es EXPERIMENTAL: solo sendText y getHistory están implementados. sendImage, sendPoll, groupMetadata y otros métodos lanzan error."
|
|
84
134
|
}
|
|
85
135
|
}
|
|
136
|
+
|
package/dist/locales/pt.json
CHANGED
|
@@ -36,13 +36,75 @@
|
|
|
36
36
|
"cacheLoaded": "{{count}} conversa(s) carregada(s) do cache.",
|
|
37
37
|
"cacheLoadedStale": "{{count}} conversa(s) carregada(s) do cache (desatualizado).",
|
|
38
38
|
"sendFailedNoFallback": "manybot: nenhum driver de fallback disponível (jid={{jid}}, primário={{driver}})",
|
|
39
|
-
"sendFailedBothDrivers": "manybot: envio falhou nos dois drivers (jid={{jid}}, tentou {{primary}} depois {{secondary}})"
|
|
39
|
+
"sendFailedBothDrivers": "manybot: envio falhou nos dois drivers (jid={{jid}}, tentou {{primary}} depois {{secondary}})",
|
|
40
|
+
"commandsConfigMissingCmd": "commands.yaml: comando \"{{id}}\" sem o campo obrigatório \"cmd\" — ignorado",
|
|
41
|
+
"commandRegistryInvalidPluginCommand": "commandRegistry: plugin \"{{plugin}}\" comando \"{{function}}\" ({{id}}) tem \"cmd\" inválido ou ausente — ignorado",
|
|
42
|
+
"commandsConfigArgumentMissingName": "commands.yaml: entrada de argumento sob \"{{id}}\" sem o campo obrigatório \"name\" — ignorada",
|
|
43
|
+
"commandsConfigArgumentMissingType": "commands.yaml: argumento \"{{name}}\" sob \"{{id}}\" sem o campo obrigatório \"type\" — ignorado",
|
|
44
|
+
"commandsConfigUnknownArgType": "commands.yaml: argumento \"{{name}}\" sob \"{{id}}\" com tipo desconhecido \"{{type}}\" — ignorado",
|
|
45
|
+
"commandsConfigChoiceArgumentNoChoices": "commands.yaml: argumento \"{{name}}\" sob \"{{id}}\" é do tipo \"choice\" mas não declarou nenhuma opção — usando placeholder genérico na sugestão de uso",
|
|
46
|
+
"commandsConfigArgumentsNotList": "commands.yaml: arguments sob \"{{id}}\" precisa ser uma lista — ignorado",
|
|
47
|
+
"commandsConfigSubcommandMissingCmd": "commands.yaml: subcomando sob \"{{id}}\" sem o campo obrigatório \"cmd\" — ignorado",
|
|
48
|
+
"commandsConfigSubcommandsNotList": "commands.yaml: subcommands sob \"{{id}}\" precisa ser uma lista — ignorado",
|
|
49
|
+
"commandsConfigDuplicateSubcommand": "commands.yaml: subcomando duplicado \"{{cmd}}\" sob \"{{id}}\" — mantendo o primeiro",
|
|
50
|
+
"commandsConfigReadFailed": "commands.yaml: não foi possível ler ({{path}}): {{message}}",
|
|
51
|
+
"commandsConfigParseFailed": "commands.yaml: erro de parse YAML: {{message}}",
|
|
52
|
+
"commandsConfigInvalidRoot": "commands.yaml: o nível raiz deve ser um mapa de entradas de comando",
|
|
53
|
+
"commandsConfigInvalidEntry": "commands.yaml: entrada \"{{id}}\" não é um objeto — ignorada",
|
|
54
|
+
"commandsConfigImportReadFailed": "commands.yaml: não foi possível ler o import \"{{path}}\": {{message}}",
|
|
55
|
+
"commandsConfigImportParseFailed": "commands.yaml: erro de parse YAML no import \"{{path}}\": {{message}}",
|
|
56
|
+
"commandsConfigImportInvalidRoot": "commands.yaml: o import \"{{path}}\" precisa ter um mapa de seções no nível raiz — ignorado",
|
|
57
|
+
"commandsConfigImportNested": "commands.yaml: o import \"{{path}}\" declara seu próprio \"import\" — imports aninhados não são suportados e foram ignorados",
|
|
58
|
+
"commandsConfigImportKeyConflict": "commands.yaml: o import \"{{path}}\" redefine \"{{key}}\", que já pertence a \"{{owner}}\" — mantendo o primeiro",
|
|
59
|
+
"commandRegistryOrphanEntry": "commandRegistry: entrada \"{{id}}\" aponta para {{plugin}}.{{function}} mas nenhum plugin ativo fornece — ignorada",
|
|
60
|
+
"commandRegistryInvalidEntry": "commandRegistry: entrada \"{{id}}\" não tem plugin/function nem text — ignorada",
|
|
61
|
+
"commandRegistryInvocationCollision": "commandRegistry: invocação \"{{text}}\" já pertence a \"{{winner}}\" — descartando {{loser}} ({{via}})",
|
|
62
|
+
"commandDeprecationRenamed": "commandRegistry: comando \"{{id}}\" renomeado de \"{{old}}\" para \"{{new}}\" — avisando chamadores antigos por {{days}} dias",
|
|
63
|
+
"commandDeprecationRemoved": "commandRegistry: comando \"{{id}}\" (cmd \"{{old}}\") foi removido — avisando chamadores antigos por {{days}} dias",
|
|
64
|
+
"commandDeprecationFallbackRenamed": "O comando \"{{old}}\" foi renomeado para \"{{new}}\". Atualize sua mensagem.",
|
|
65
|
+
"commandDeprecationFallbackRemoved": "O comando \"{{old}}\" não está mais disponível.",
|
|
66
|
+
"commandDeprecationReservedInvocation": "commandRegistry: invocação \"{{text}}\" está reservada por deprecation ativa de \"{{id}}\" — registro recusado",
|
|
67
|
+
"commandManualMissing": "Nenhum manual detalhado disponível para o comando \"{{cmd}}\".",
|
|
68
|
+
"commandNotFound": "Comando ou categoria \"{{cmd}}\" não encontrado. Use {{prefix}}{{menuCmd}} para ver a lista de comandos.",
|
|
69
|
+
"commandRegistryMenuAliasCollision": "commandRegistry: alias de menu \"{{alias}}\" colidiu com comando real \"{{winner}}\" — alias de menu ignorado"
|
|
40
70
|
},
|
|
41
71
|
"errors": {
|
|
42
72
|
"stack": "Stack",
|
|
43
73
|
"invalid_config": "Arquivo de configuração inválido {{file}}: {{error}}",
|
|
44
74
|
"fix_config": "Corrija a sintaxe em {{file}} antes de iniciar o bot."
|
|
45
75
|
},
|
|
76
|
+
"menu": {
|
|
77
|
+
"intro": "Use {prefix}<comando> para executar ou {prefix}help <comando> para ver o manual.",
|
|
78
|
+
"other": "Outros",
|
|
79
|
+
"category": "Categoria",
|
|
80
|
+
"manual": "Manual",
|
|
81
|
+
"description": "Descrição"
|
|
82
|
+
},
|
|
83
|
+
"commandPermissions": {
|
|
84
|
+
"botNotAdmin": "Este comando exige que o bot seja administrador do grupo.",
|
|
85
|
+
"senderNotAdmin": "Este comando é restrito a administradores do grupo.",
|
|
86
|
+
"ownerOnly": "Este comando só pode ser executado pelo dono do bot.",
|
|
87
|
+
"wrongScope": "Este comando não pode ser usado neste contexto.",
|
|
88
|
+
"cooldown": "Aguarde {{seconds}}s antes de usar este comando novamente."
|
|
89
|
+
},
|
|
90
|
+
"commandRun": {
|
|
91
|
+
"missingRequiredArg": "Faltam argumentos obrigatórios.",
|
|
92
|
+
"unknownSubcommand": "Subcomando \"{{sub}}\" desconhecido para !{{cmd}}. Válidos: {{valid}}."
|
|
93
|
+
},
|
|
94
|
+
"alerts": {
|
|
95
|
+
"noFallbackTitle": "manybot: sem driver de fallback",
|
|
96
|
+
"bothDriversFailedTitle": "manybot: envio falhou nos dois drivers",
|
|
97
|
+
"updateAvailableTitle": "Nova versão do manybot disponível",
|
|
98
|
+
"updateAvailableMessage": "Instalado: {{installed}} → disponível: {{available}}. Veja {{url}} para instruções de atualização.",
|
|
99
|
+
"reconnectHaltedTitle": "manybot parou de reconectar",
|
|
100
|
+
"reconnectHaltedMessage": "Desistiu após {{attempts}} tentativas — possível restrição da conta. Execute connect() manualmente para tentar novamente."
|
|
101
|
+
},
|
|
102
|
+
"driver": {
|
|
103
|
+
"groupParticipantNotFound": "\"{{id}}\" não corresponde a nenhum participante atual do grupo \"{{group}}\" (confira se a pessoa ainda está no grupo, ou use reply/menção em vez de digitar o número).",
|
|
104
|
+
"groupParticipantsUpdateRejected": "groupParticipantsUpdate(\"{{action}}\") rejeitado para: {{detail}}",
|
|
105
|
+
"groupParticipantsUpdateFailed": "groupParticipantsUpdate(\"{{action}}\") falhou para o grupo \"{{group}}\" com participantes [{{users}}]: {{message}}",
|
|
106
|
+
"pollDecryptFailed": "Erro ao decriptar voto da enquete: {{error}}"
|
|
107
|
+
},
|
|
46
108
|
"onboarding": {
|
|
47
109
|
"intro": "ManyBot — primeiro login",
|
|
48
110
|
"methodPrompt": "Como você quer conectar sua conta do WhatsApp?",
|
|
@@ -69,17 +131,6 @@
|
|
|
69
131
|
"retrying": "Tentando novamente ({{attempt}}/{{max}})...",
|
|
70
132
|
"sessionWiped": "Sessão descartada, tentando parear novamente ({{round}}/{{max}})...",
|
|
71
133
|
"connectGaveUp": "Não foi possível conectar após várias tentativas. Verifique sua conexão e tente de novo."
|
|
72
|
-
},
|
|
73
|
-
"whatsmeow": {
|
|
74
|
-
"installPrompt": "Instalar driver whatsmeow para suporte a fallback? (EXPERIMENTAL — apenas sendText e histórico funcionam; outros métodos lançam erro)",
|
|
75
|
-
"unsupportedArch": "Driver whatsmeow não disponível para {{os}}-{{arch}}. O bot usará apenas Baileys.",
|
|
76
|
-
"fetchingTag": "Buscando última versão do whatsmeow...",
|
|
77
|
-
"fetchFailed": "Não foi possível acessar o Codeberg. Instalação do whatsmeow ignorada.",
|
|
78
|
-
"downloading": "Baixando {{url}}...",
|
|
79
|
-
"downloadFailed": "Download falhou: {{reason}}",
|
|
80
|
-
"installTitle": "Driver whatsmeow",
|
|
81
|
-
"installed": "Driver whatsmeow instalado em {{path}}",
|
|
82
|
-
"restartNotice": "Reinicie o bot para que o driver whatsmeow entre em efeito.",
|
|
83
|
-
"experimentalNotice": "whatsmeow é EXPERIMENTAL: apenas sendText e getHistory estão implementados. sendImage, sendPoll, groupMetadata e outros métodos lançam erro."
|
|
84
134
|
}
|
|
85
135
|
}
|
|
136
|
+
|
package/dist/logger/logger.js
CHANGED
|
@@ -3,14 +3,34 @@ const c = {
|
|
|
3
3
|
green: "\x1b[32m", yellow: "\x1b[33m", cyan: "\x1b[36m",
|
|
4
4
|
red: "\x1b[31m", blue: "\x1b[34m",
|
|
5
5
|
};
|
|
6
|
+
let debugEnabled = process.argv.includes("--debug");
|
|
7
|
+
let logLevel = "normal";
|
|
8
|
+
export function setLogLevel(level) { logLevel = level; }
|
|
9
|
+
export function getLogLevel() { return logLevel; }
|
|
6
10
|
/**
|
|
7
11
|
* ManyBot central logger.
|
|
8
12
|
* Each method only handles output — no business logic or external I/O.
|
|
13
|
+
*
|
|
14
|
+
* `debug` is silent by default to keep production logs clean. Pass
|
|
15
|
+
* `--debug` on the command line to enable it. The check is a single
|
|
16
|
+
* `Array.includes` on argv, cheap enough to do per call and avoids
|
|
17
|
+
* requiring logger consumers to know about a global toggle.
|
|
9
18
|
*/
|
|
10
19
|
export const logger = {
|
|
11
|
-
info: (...a) =>
|
|
12
|
-
|
|
20
|
+
info: (...a) => {
|
|
21
|
+
if (logLevel === "normal")
|
|
22
|
+
console.log(`${c.cyan}INFO ${c.reset}`, ...a);
|
|
23
|
+
},
|
|
24
|
+
success: (...a) => {
|
|
25
|
+
if (logLevel !== "minimal")
|
|
26
|
+
console.log(`${c.green}OK ${c.reset}`, ...a);
|
|
27
|
+
},
|
|
13
28
|
warn: (...a) => console.log(`${c.yellow}WARN ${c.reset}`, ...a),
|
|
14
29
|
error: (...a) => console.log(`${c.red}ERROR ${c.reset}`, ...a),
|
|
15
|
-
debug: (...a) =>
|
|
30
|
+
debug: (...a) => {
|
|
31
|
+
if (debugEnabled)
|
|
32
|
+
console.log(`${c.blue}DEBUG ${c.reset}`, ...a);
|
|
33
|
+
},
|
|
16
34
|
};
|
|
35
|
+
export function enableDebug() { debugEnabled = true; }
|
|
36
|
+
export function isDebugEnabled() { return debugEnabled; }
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import test, { describe, beforeEach, afterEach } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { logger, setLogLevel, getLogLevel } from "./logger.js";
|
|
4
|
+
describe("logger levels", () => {
|
|
5
|
+
const originalLog = console.log;
|
|
6
|
+
let lines;
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
lines = [];
|
|
9
|
+
console.log = (...a) => { lines.push(a.join(" ")); };
|
|
10
|
+
});
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
console.log = originalLog;
|
|
13
|
+
setLogLevel("normal");
|
|
14
|
+
});
|
|
15
|
+
test("defaults to normal", () => {
|
|
16
|
+
assert.equal(getLogLevel(), "normal");
|
|
17
|
+
});
|
|
18
|
+
test("normal shows info, success, warn, error", () => {
|
|
19
|
+
setLogLevel("normal");
|
|
20
|
+
logger.info("a");
|
|
21
|
+
logger.success("b");
|
|
22
|
+
logger.warn("c");
|
|
23
|
+
logger.error("d");
|
|
24
|
+
assert.equal(lines.length, 4);
|
|
25
|
+
});
|
|
26
|
+
test("clean hides info but keeps success, warn, error", () => {
|
|
27
|
+
setLogLevel("clean");
|
|
28
|
+
logger.info("a");
|
|
29
|
+
logger.success("b");
|
|
30
|
+
logger.warn("c");
|
|
31
|
+
logger.error("d");
|
|
32
|
+
assert.equal(lines.length, 3);
|
|
33
|
+
assert.ok(!lines.some(l => l.includes("a")));
|
|
34
|
+
});
|
|
35
|
+
test("minimal hides info and success, keeps warn and error", () => {
|
|
36
|
+
setLogLevel("minimal");
|
|
37
|
+
logger.info("a");
|
|
38
|
+
logger.success("b");
|
|
39
|
+
logger.warn("c");
|
|
40
|
+
logger.error("d");
|
|
41
|
+
assert.equal(lines.length, 2);
|
|
42
|
+
assert.ok(lines.some(l => l.includes("c")));
|
|
43
|
+
assert.ok(lines.some(l => l.includes("d")));
|
|
44
|
+
});
|
|
45
|
+
});
|
package/dist/main.js
CHANGED
|
@@ -10,49 +10,20 @@ import path from "path";
|
|
|
10
10
|
process.env.NODE_PATH = path.resolve(process.cwd(), "node_modules");
|
|
11
11
|
Module._initPaths();
|
|
12
12
|
import { baileysContract } from "#drivers/baileys/index.js";
|
|
13
|
-
import { whatsmeowContract, startWhatsmeowSupervisor, wrapWithSupervisor } from "#drivers/whatsmeow/index.js";
|
|
14
|
-
import { promptWhatsmeowInstall } from "#drivers/whatsmeow/installer.js";
|
|
15
13
|
import { cleanupPlugins } from "#kernel/pluginLoader.js";
|
|
16
14
|
import { stopAll as stopScheduler } from "#kernel/scheduler.js";
|
|
17
15
|
import { sendAlert } from "#kernel/alerts.js";
|
|
18
16
|
import { startStatusServer } from "#kernel/statusServer.js";
|
|
19
17
|
import { getDriverManager } from "#kernel/driverManager.js";
|
|
20
|
-
import {
|
|
21
|
-
import { logger } from "#logger";
|
|
18
|
+
import { STATUS_ENABLED, STATUS_PORT, LOG_LEVEL } from "#config";
|
|
19
|
+
import { logger, setLogLevel } from "#logger";
|
|
22
20
|
import { t } from "#i18n";
|
|
21
|
+
setLogLevel(LOG_LEVEL);
|
|
23
22
|
let shuttingDown = false;
|
|
24
|
-
// DriverManager registration: only
|
|
25
|
-
// the config. whatsmeow.enabled = false means no gRPC
|
|
26
|
-
// subprocess, no Go binary lookup, no extra work at boot — the manager
|
|
27
|
-
// simply doesn't know about it and sendFallbackGuard sees a missing
|
|
28
|
-
// secondary and fires send_failed_no_fallback if needed.
|
|
23
|
+
// DriverManager registration: only Baileys driver now
|
|
29
24
|
const driverManager = getDriverManager();
|
|
30
|
-
driverManager.register(baileysContract, { isPrimary:
|
|
31
|
-
// Whatsmeow supervisor: spawns the Go subprocess when enabled=true,
|
|
32
|
-
// owns the restart/backoff/circuit-breaker logic, and gates the
|
|
33
|
-
// driver's connect()/isReady() until HealthCheck{ready:true}. When
|
|
34
|
-
// enabled=false or the binary can't be located, `supervisor` is null
|
|
35
|
-
// and the whatsmeow driver is simply not registered — ManyBot keeps
|
|
36
|
-
// running on Baileys alone, no fallback.
|
|
37
|
-
let supervisor = null;
|
|
38
|
-
if (CONFIG.drivers.whatsmeow.enabled) {
|
|
39
|
-
logger.info("[driverManager] whatsmeow enabled — spawning supervisor");
|
|
40
|
-
supervisor = await startWhatsmeowSupervisor();
|
|
41
|
-
if (supervisor) {
|
|
42
|
-
const wrapped = wrapWithSupervisor(whatsmeowContract, supervisor);
|
|
43
|
-
driverManager.register(wrapped, { isPrimary: CONFIG.drivers.primary === "whatsmeow" });
|
|
44
|
-
logger.info(`[driverManager] whatsmeow registered (primary=${CONFIG.drivers.primary === "whatsmeow"})`);
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
logger.warn("[driverManager] whatsmeow supervisor failed to start — fallback disabled");
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
logger.info("[driverManager] whatsmeow disabled by config — no fallback");
|
|
52
|
-
}
|
|
25
|
+
driverManager.register(baileysContract, { isPrimary: true });
|
|
53
26
|
const activeDriver = driverManager.active();
|
|
54
|
-
const secondaryName = (activeDriver.name === "baileys" ? "whatsmeow" : "baileys");
|
|
55
|
-
const secondaryDriver = driverManager.get(secondaryName);
|
|
56
27
|
async function shutdown(reason, isError = false) {
|
|
57
28
|
if (shuttingDown)
|
|
58
29
|
return;
|
|
@@ -87,16 +58,6 @@ async function shutdown(reason, isError = false) {
|
|
|
87
58
|
catch (err) {
|
|
88
59
|
logger.error(`Error disconnecting driver: ${err.message}`);
|
|
89
60
|
}
|
|
90
|
-
// Belt-and-suspenders: driverManager.shutdown() should already have
|
|
91
|
-
// disconnected the wrapped contract, which in turn calls
|
|
92
|
-
// supervisor.shutdown(). This catches the case where the supervisor
|
|
93
|
-
// was started but the driver wasn't registered (binary missing).
|
|
94
|
-
if (supervisor) {
|
|
95
|
-
try {
|
|
96
|
-
await supervisor.shutdown();
|
|
97
|
-
}
|
|
98
|
-
catch { }
|
|
99
|
-
}
|
|
100
61
|
process.exit(isError ? 1 : 0);
|
|
101
62
|
}
|
|
102
63
|
// Global error listeners
|
|
@@ -116,11 +77,6 @@ process.on("SIGINT", () => shutdown("SIGINT"));
|
|
|
116
77
|
// the JID to the console, to paste into CHATS in manybot.toml.
|
|
117
78
|
// Does not enter the normal bot flow (plugins are not loaded).
|
|
118
79
|
if (process.argv.includes("--getid")) {
|
|
119
|
-
// getId? is a Baileys-only diagnostic method. Look it
|
|
120
|
-
// up on the registered Baileys driver regardless of which one is
|
|
121
|
-
// active — --getid always uses Baileys, even in a whatsmeow-primary
|
|
122
|
-
// configuration, because it needs the diagnostic session, not the
|
|
123
|
-
// bot's normal one.
|
|
124
80
|
const baileys = driverManager.get("baileys");
|
|
125
81
|
const getIdFn = baileys?.getId;
|
|
126
82
|
if (!getIdFn) {
|
|
@@ -134,16 +90,6 @@ if (process.argv.includes("--getid")) {
|
|
|
134
90
|
process.exit(1);
|
|
135
91
|
});
|
|
136
92
|
}
|
|
137
|
-
else if (process.argv.includes("--install-whatsmeow")) {
|
|
138
|
-
// Re-run the whatsmeow installer outside the normal setup flow.
|
|
139
|
-
// Useful when the initial install failed or the binary was moved.
|
|
140
|
-
promptWhatsmeowInstall()
|
|
141
|
-
.then(() => process.exit(0))
|
|
142
|
-
.catch((err) => {
|
|
143
|
-
logger.error(`--install-whatsmeow failed: ${err.message}`);
|
|
144
|
-
process.exit(1);
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
93
|
else {
|
|
148
94
|
// Start bot
|
|
149
95
|
logger.info(t("bot.initialized"));
|
|
@@ -153,23 +99,6 @@ else {
|
|
|
153
99
|
activeDriver.connect()
|
|
154
100
|
.then(() => {
|
|
155
101
|
logger.success(t("bot.ready"));
|
|
156
|
-
// The secondary driver is connected and paired in the
|
|
157
|
-
// background so sendFallbackGuard can reach for it without first
|
|
158
|
-
// having to wait through a connect() round-trip when the primary
|
|
159
|
-
// fails. The secondary does NOT register `messages.upsert` handlers
|
|
160
|
-
// (no kernel code subscribes on it — only the primary path does),
|
|
161
|
-
// so connecting it does not duplicate inbound processing. A failure
|
|
162
|
-
// here is non-fatal: the primary keeps running, fallback just stays
|
|
163
|
-
// unavailable (sendFallbackGuard's `isReady()` check covers that).
|
|
164
|
-
if (secondaryDriver) {
|
|
165
|
-
logger.info(`[driverManager] connecting secondary "${secondaryName}" in background…`);
|
|
166
|
-
secondaryDriver.connect()
|
|
167
|
-
.then(() => logger.info(`[driverManager] secondary "${secondaryName}" connected — fallback available`))
|
|
168
|
-
.catch((err) => logger.warn(`[driverManager] secondary "${secondaryName}" connect failed: ${err.message} — fallback unavailable`));
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
logger.info(`[driverManager] no secondary driver registered — running on ${activeDriver.name} only`);
|
|
172
|
-
}
|
|
173
102
|
})
|
|
174
103
|
.catch((err) => {
|
|
175
104
|
shutdown(`Failed to connect driver: ${err.message}`, true);
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* plugins/__manybot_integration__/index.ts
|
|
3
|
+
*
|
|
4
|
+
* Internal integration plugin for the WhatsApp test suite. NOT a
|
|
5
|
+
* user-facing plugin — only the test harness loads it, and only when
|
|
6
|
+
* integration mode is enabled (see `kernel/integrationMode.ts`).
|
|
7
|
+
*
|
|
8
|
+
* Responsibilities:
|
|
9
|
+
* - Refuse to act on any chat other than the configured TEST_CHAT.
|
|
10
|
+
* Anything arriving in another chat is dropped with a warning,
|
|
11
|
+
* never replied to.
|
|
12
|
+
* - Expose a small public API the test harness drives:
|
|
13
|
+
* - `testChat` — the JID we will respond in.
|
|
14
|
+
* - `isTestChat(jid)` — true iff `jid` matches the test chat.
|
|
15
|
+
* - `waitForMarker(marker, timeoutMs)`
|
|
16
|
+
* — resolves with the message id of the
|
|
17
|
+
* first inbound message in the test chat
|
|
18
|
+
* whose body starts with `marker`. Used
|
|
19
|
+
* by tests to observe real round-trips
|
|
20
|
+
* (e.g. "send a message, wait until the
|
|
21
|
+
* contact echoes it back").
|
|
22
|
+
* - Keep a bounded ring buffer of recent message bodies in the
|
|
23
|
+
* test chat so tests can also assert on what was sent earlier
|
|
24
|
+
* in the same run.
|
|
25
|
+
*
|
|
26
|
+
* This plugin is intentionally minimal — it does NOT register user
|
|
27
|
+
* commands, does NOT participate in the `commands.yaml` registry,
|
|
28
|
+
* and shuts down cleanly via the normal plugin cleanup path
|
|
29
|
+
* (cleanupPluginEvents is driven by the kernel, not by us).
|
|
30
|
+
*
|
|
31
|
+
* Imports are restricted to types and the `events` module from the
|
|
32
|
+
* kernel — never a driver package, never the raw socket. The
|
|
33
|
+
* `ctx.wa.contract` neutral access is the only driver surface used
|
|
34
|
+
* (and only for `me()` and any helper the test harness invokes).
|
|
35
|
+
*/
|
|
36
|
+
import { EventEmitter } from "node:events";
|
|
37
|
+
import { logger } from "#logger";
|
|
38
|
+
import { INTEGRATION_PLUGIN_NAME } from "#kernel/integrationMode.js";
|
|
39
|
+
const RING_BUFFER_LIMIT = 50;
|
|
40
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
41
|
+
let configuredTestChat = null;
|
|
42
|
+
const recent = new Map();
|
|
43
|
+
let recentSeq = 0;
|
|
44
|
+
const waiter = new EventEmitter();
|
|
45
|
+
function pushRecent(body, from) {
|
|
46
|
+
const id = ++recentSeq;
|
|
47
|
+
recent.set(id, { body, from, ts: Date.now() });
|
|
48
|
+
// Cap the ring buffer; eviction keeps insertion order.
|
|
49
|
+
while (recent.size > RING_BUFFER_LIMIT) {
|
|
50
|
+
const firstKey = recent.keys().next().value;
|
|
51
|
+
if (firstKey === undefined)
|
|
52
|
+
break;
|
|
53
|
+
recent.delete(firstKey);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function findByMarker(marker) {
|
|
57
|
+
for (const entry of recent.values()) {
|
|
58
|
+
if (entry.body.startsWith(marker))
|
|
59
|
+
return entry;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
export const api = {
|
|
64
|
+
testChat: "", // populated in setup()
|
|
65
|
+
isTestChat(jid) {
|
|
66
|
+
if (!jid)
|
|
67
|
+
return false;
|
|
68
|
+
if (!configuredTestChat)
|
|
69
|
+
return false;
|
|
70
|
+
return jid === configuredTestChat;
|
|
71
|
+
},
|
|
72
|
+
async waitForMarker(marker, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
73
|
+
const existing = findByMarker(marker);
|
|
74
|
+
if (existing) {
|
|
75
|
+
// Return a synthetic id; tests that need the real id should
|
|
76
|
+
// observe the `messages.upsert` payload directly.
|
|
77
|
+
return `recent:${existing.ts}`;
|
|
78
|
+
}
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
const onHit = (entry) => {
|
|
81
|
+
if (!entry.body.startsWith(marker))
|
|
82
|
+
return;
|
|
83
|
+
waiter.off("hit", onHit);
|
|
84
|
+
clearTimeout(timer);
|
|
85
|
+
resolve(`recent:${entry.ts}`);
|
|
86
|
+
};
|
|
87
|
+
const timer = setTimeout(() => {
|
|
88
|
+
waiter.off("hit", onHit);
|
|
89
|
+
reject(new Error(`[${INTEGRATION_PLUGIN_NAME}] waitForMarker("${marker}") timed out after ${timeoutMs}ms`));
|
|
90
|
+
}, timeoutMs);
|
|
91
|
+
waiter.on("hit", onHit);
|
|
92
|
+
});
|
|
93
|
+
},
|
|
94
|
+
recentBodies() {
|
|
95
|
+
return [...recent.values()].map((e) => e.body);
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* setup() runs once after the bot connects. We capture the test
|
|
100
|
+
* chat here so the runtime API can refuse any other chat.
|
|
101
|
+
*/
|
|
102
|
+
export async function setup(ctx) {
|
|
103
|
+
// The integration chat is decided by the harness BEFORE
|
|
104
|
+
// setupPlugins() is called. Historically the harness set
|
|
105
|
+
// MANYBOT_TEST_CHAT, but users often set TEST_CHAT (per docs).
|
|
106
|
+
// Accept either, normalizing the value to the canonical JID form.
|
|
107
|
+
// Keep behavior strict: if neither is set, fail.
|
|
108
|
+
const rawEnv = process.env.MANYBOT_TEST_CHAT ?? process.env.TEST_CHAT;
|
|
109
|
+
if (!rawEnv) {
|
|
110
|
+
throw new Error(`[${INTEGRATION_PLUGIN_NAME}] setup() called without MANYBOT_TEST_CHAT or TEST_CHAT — ` +
|
|
111
|
+
`set MANYBOT_TEST_CHAT (preferred) or TEST_CHAT before setupPlugins().`);
|
|
112
|
+
}
|
|
113
|
+
// Normalize and validate the provided chat identifier.
|
|
114
|
+
let normalized;
|
|
115
|
+
try {
|
|
116
|
+
// Importing normalizeTestChat here keeps the module load cheap
|
|
117
|
+
// when the plugin isn't used in production code paths.
|
|
118
|
+
const { normalizeTestChat } = await import("#kernel/testConfig.js");
|
|
119
|
+
normalized = normalizeTestChat(rawEnv);
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
throw new Error(`[${INTEGRATION_PLUGIN_NAME}] invalid test chat provided: ${e.message}`);
|
|
123
|
+
}
|
|
124
|
+
configuredTestChat = normalized;
|
|
125
|
+
api.testChat = normalized;
|
|
126
|
+
// Subscribe to the relevant events so waitForMarker works
|
|
127
|
+
// without requiring a polling loop. `messages.upsert` is the
|
|
128
|
+
// single source of truth for any new incoming message; `on()`
|
|
129
|
+
// returns an unsubscribe handle, but cleanupPluginEvents takes
|
|
130
|
+
// care of it for us.
|
|
131
|
+
ctx.events.on("messages.upsert", (payload) => {
|
|
132
|
+
const p = payload;
|
|
133
|
+
const messages = p.messages ?? [];
|
|
134
|
+
for (const m of messages) {
|
|
135
|
+
if (!m.chatId || m.chatId !== configuredTestChat)
|
|
136
|
+
continue;
|
|
137
|
+
const body = m.body ?? "";
|
|
138
|
+
const from = m.from ?? "";
|
|
139
|
+
pushRecent(body, from);
|
|
140
|
+
waiter.emit("hit", { body, from, ts: Date.now() });
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
logger.info(`[${INTEGRATION_PLUGIN_NAME}] loaded — locked to test chat ${configuredTestChat}`);
|
|
144
|
+
// ctx reserved for future use (e.g. registering a scheduler)
|
|
145
|
+
return void ctx;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* default(ctx) runs on every inbound message. We are deliberately
|
|
149
|
+
* quiet outside the test chat so a stray message in a production
|
|
150
|
+
* chat never gets a reply from the test plugin; we never throw,
|
|
151
|
+
* because throwing would propagate up to messageHandler and
|
|
152
|
+
* potentially break unrelated plugins.
|
|
153
|
+
*/
|
|
154
|
+
export default async function handle(ctx) {
|
|
155
|
+
if (!configuredTestChat) {
|
|
156
|
+
logger.warn(`[${INTEGRATION_PLUGIN_NAME}] default() called before setup() — ignoring`);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
if (ctx.chat.id !== configuredTestChat) {
|
|
160
|
+
logger.warn(`[${INTEGRATION_PLUGIN_NAME}] ignoring message from non-test chat ${ctx.chat.id}`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
// No automatic reply: the test harness drives every send through
|
|
164
|
+
// `ctx.send.*` or `contract.send*` and waits on `waitForMarker`
|
|
165
|
+
// for the round-trip. Keeping the plugin passive here is what
|
|
166
|
+
// makes the suite easy to reason about.
|
|
167
|
+
}
|