Avec ça node.js micro-framework utilisant Robot venimeux sous la capuche, vous pouvez facilement créer un Chatbot WhatsApp 🤖 . Vous n'aurez qu'à éditer votre flux de conversation dans un seul fichier.
Commencer
- Créer un nouveau référentiel à partir de ce modèle
- Installation dans votre environnement de développement
- Configurer port(s), crédits, etc
- Écrivez votre flux de conversation
- Commencer
Installation
Docker
Exigences: docker
Construire et exécuter avec Dockerfile
$ docker build -t wchatbot .
$ docker run --name wchatbot -p 3000:3000 -v /your_project_absolute_path/src:/wchatbot/src wchatbot
ou Construire et exécuter avec Docker Compose
$ docker-compose build
$ docker-compose up
Visitez http://hôte local:3000 et jouez avec votre chatbot!
Machine virtuelle
Exigences: nodejs (Dernière maintenance LTS version), yarn (ou npm), pm2, chrome/chromium
Utilisez un nginx proxy inverse pour exposer publiquement le panneau de contrôle http (exemple de configuration).
$ yarn install
Lancer le chatbot et le panneau de contrôle http
$ yarn start
$ yarn http-ctrl:start
Visitez http://hôte local:3000 et jouez avec votre chatbot!
Machine locale
Exigences: nodejs (Dernière maintenance LTS version), yarn (ou npm), chrome/chromium
$ yarn install
Lancer le chatbot et le panneau de contrôle http
$ yarn http-ctrl:dev:detach
$ yarn dev
Visitez http://hôte local:3000 et jouez avec votre chatbot!
Configuration
Éditer ./src/config.js
fichier
Basic
export const chatbotOptions = {
httpCtrl: {
port: 3000, // httpCtrl port (http://localhost:3000/)
username: "admin", // httpCtrl auth login
password: "chatbot",
},
};
Advancé
export const venomOptions = {
...
browserArgs: [
"--no-sandbox", // Will be passed to browser. Use --no-sandbox with Docker
],
puppeteerOptions: { // Will be passed to puppeteer.launch.
args: ["--no-sandbox"] // Use --no-sandbox with Docker
},
...
};
Commands
Docker
Contrôles du chatbot
$ docker exec wchatbot yarn start
$ docker exec wchatbot yarn stop
$ docker exec wchatbot yarn restart
$ docker exec wchatbot yarn reload
Contrôles du panneau de configuration HTTP
$ docker exec wchatbot yarn http-ctrl:start
$ docker exec wchatbot yarn http-ctrl:stop
$ docker exec wchatbot yarn http-ctrl:restart
$ docker exec wchatbot yarn http-ctrl:reload
Machine virtuelle
Contrôles du chatbot
$ yarn start
$ yarn stop
$ yarn restart
$ yarn reload
Contrôles du panneau de configuration HTTP
$ yarn http-ctrl:start
$ yarn http-ctrl:stop
$ yarn http-ctrl:restart
$ yarn http-ctrl:reload
Machine locale
Directement dans votre OS sans Docker
Chatbot
$ yarn dev
$ yarn dev:detach
Lancer le panneau de configuration HTTP
$ yarn http-ctrl:dev
$ yarn http-ctrl:dev:detach
Séances
Les sessions et les jetons d'authentification sont écrits ./tokens
dossier.
Journaux
Les journaux sont écrits ./logs
dossier.
Attention: console.log
et http-ctrl-console.log
n'écrivez que dans ./logs
dossier avec yarn dev:detach
et yarn http-ctrl:dev:detach
autrement géré par pm2
.
Docker
Chatbot
$ docker exec wchatbot yarn log
Panneau de configuration HTTP
$ docker exec wchatbot yarn http-ctrl:log
Conversations
$ docker exec wchatbot yarn conversations
Machine virtuelle
Chatbot
$ yarn log
Panneau de configuration HTTP
$ yarn http-ctrl:log
Conversations
$ yarn conversations
Machine locale
Chatbot
$ yarn log:dev
Panneau de configuration HTTP
$ yarn log:http-ctrl:dev
Conversations
$ yarn conversations
Flux de conversations
Éditer ./src/conversations/conversation.js
fichier.
Le flux de conversation est un tableau d'objets de réponse ordonnés.
Une réponse n'est déclenchée que si son parent
(peut être un entier ou un tableau)
est égal au id
de la réponse précédente.
Pour indiquer qu'une réponse est la fin de la conversation, ajoutez la propriété suivante:
Propriété | Taper | Description |
---|---|---|
end |
booléen | La fin de la conversation |
Vous pouvez protéger afin qu'un seul numéro ou une liste de numéros soit répondu avec:
Propriété | Taper | Description |
---|---|---|
from |
Chaîne / Array | Ne répondez qu'à ce ou ces chiffres |
Une réponse nécessite obligatoirement les propriétés suivantes:
Types de réponses
Envoyer du texte
Propriété | Taper | Description |
---|---|---|
id |
Entier | Repondre id est utilisé pour établir un lien avec parent |
parent |
Entier | Id du parent de réponse ou du tableau d'ids [2, 3] . S'il n'a pas de parent, c'est 0 par défaut |
pattern |
RegExp | Expression régulière à faire correspondre en minuscules |
message |
Chaîne | Répondre par SMS |
Exemple
[
{
id: 1,
parent: 0,
pattern: /.*/, // Match with all text
message: "Hi I am a Chatbot!",
}
]
Boutons d'envoi
Attention: Il ne fonctionne pas actuellement!.
Propriété | Taper | Description |
---|---|---|
id |
Entier | Repondre id est utilisé pour établir un lien avec parent |
parent |
Entier | Id du parent de réponse ou du tableau d'ids [2, 3] . S'il n'a pas de parent, c'est 0 par défaut |
pattern |
RegExp | Expression régulière à faire correspondre en minuscules |
message |
Chaîne | Répondre par SMS |
description |
Chaîne | Sous-titre du texte de la réponse |
buttons |
Array | Objet bouton, Regardez l'exemple |
Exemple
[
{
id: 1,
parent: 0,
pattern: /.*/,
message: "Hello!",
description: "Can I help with something?",
buttons: buttons([
"Website",
"LinkedIn",
"Github",
]),
}
]
Envoyer la liste
Attention: Il ne fonctionne pas actuellement!.
Propriété | Taper | Description |
---|---|---|
id |
Entier | Repondre id est utilisé pour établir un lien avec parent |
parent |
Entier | Id du parent de réponse ou du tableau d'ids [2, 3] . S'il n'a pas de parent, c'est 0 par défaut |
pattern |
RegExp | Expression régulière à faire correspondre en minuscules |
message |
Chaîne | Répondre par SMS |
description |
Chaîne | Sous-titre du texte de la réponse |
button |
Chaîne | Texte du bouton de liste |
list |
Array | Objet de liste, Regardez l'exemple |
Exemple
[
{
id: 1,
parent: 0,
pattern: /other country/,
message: "Choice one country",
description: "Choice one option!",
button: "Countries list",
list: list([
"Argentina",
"Belize",
"Bolivia",
]),
},
]
Envoyer un lien
Propriété | Taper | Description |
---|---|---|
id |
Entier | Repondre id est utilisé pour établir un lien avec parent |
parent |
Entier | Id du parent de réponse ou du tableau d'ids [2, 3] . S'il n'a pas de parent, c'est 0 par défaut |
pattern |
RegExp | Expression régulière à faire correspondre en minuscules |
message |
Chaîne | Répondre par SMS |
link |
Chaîne | URL de l'aperçu du lien généré |
Exemple
[
{
id: 2,
parent: 1, // Relation with id: 1
pattern: /github/,
message: "Check my Github repositories!",
link: "https://github.com/jfadev",
}
]
Envoyer l'image
Propriété | Taper | Description |
---|---|---|
id |
Entier | Repondre id est utilisé pour établir un lien avec parent |
parent |
Entier | Id du parent de réponse ou du tableau d'ids [2, 3] . S'il n'a pas de parent, c'est 0 par défaut |
pattern |
RegExp | Expression régulière à faire correspondre en minuscules |
image |
Chemin / Objet | Chemin ou objet renvoyé par remoteImg() fonction |
Exemple
[
{
id: 1,
parent: 0,
pattern: /.*/, // Match all
image: remoteImg("https://remote-server.com/menu.jpg"),
// image: "./images/menu.jpg",
}
]
Envoyer le son
Propriété | Taper | Description |
---|---|---|
id |
Entier | Repondre id est utilisé pour établir un lien avec parent |
parent |
Entier | Id du parent de réponse ou du tableau d'ids [2, 3] . S'il n'a pas de parent, c'est 0 par défaut |
pattern |
RegExp | Expression régulière à faire correspondre en minuscules |
audio |
Chemin / Objet | Chemin ou objet renvoyé par remoteAudio() fonction. |
Exemple
[
{
id: 1,
parent: 0,
pattern: /.*/, // Match all
audio: remoteAudio("https://remote-server.com/audio.mp3"),
// audio: "./audios/audio.mp3",
}
]
Transférer le message
Propriété | Taper | Description |
---|---|---|
id |
Entier | Repondre id est utilisé pour établir un lien avec parent |
parent |
Entier | Id du parent de réponse ou du tableau d'ids [2, 3] . S'il n'a pas de parent, c'est 0 par défaut |
pattern |
RegExp | Expression régulière à faire correspondre en minuscules |
message |
Chaîne | Répondre par SMS |
forward |
Chaîne | Numéro où le message est transféré |
Exemple
[
{
id: 1,
parent: 0,
pattern: /forward/,
message: "Text to forward",
forward: "[email protected]", // forward this message to this number
}
]
Aides
Assistant | Retour | Description |
---|---|---|
buttons(buttonTexts) |
Array | Générer des boutons |
remoteTxt(url) |
Chaîne | Renvoyer un fichier TXT distant |
remoteJson(url) |
JSON | Renvoyer un fichier JSON distant |
remoteImg(url) |
Objet | Renvoyer un fichier image distant |
remoteAudio(url) |
Objet | Renvoyer un fichier audio distant |
list(listRows) |
Array | Générer la liste |
inp(id, parents) |
Chaîne | Renvoyer la chaîne d'entrée par ID de réponse. Utiliser avantRépondre, afterReply et beforeForward |
med(id, parents) |
Médias / nul | Retourner le média ({amortir, extension}) par identifiant de réponse. Utiliser avantRépondre, afterReply et beforeForward |
Crochets
Propriété | Taper | Description |
---|---|---|
beforeReply(from, input, output, parents, media) |
Fonction | Injecter du code personnalisé avant une réponse |
afterReply(from, input, parents, media) |
Fonction | Injecter du code personnalisé après une réponse |
beforeForward(from, forward, input, parents, media) |
Fonction | Injecter du code personnalisé avant un transfert |
Boucles
Propriété | Taper | Description |
---|---|---|
goTo(from, input, output, parents, media) |
Fonction | Devrait renvoyer l'identifiant de réponse où sauter |
clearParents |
booléen | Effacer les données des parents, utiliser avec goTo() |
Panneau de configuration HTTP
Avec le panneau de contrôle, vous pouvez vous connecter, commencer, arrêter ou redémarrer le bot et surveiller les journaux.
Définissez votre username
et password
pour accéder à votre panneau de contrôle dans le fichier ./src/config.js
export const chatbotOptions = {
httpCtrl: {
port: 3000, // httpCtrl port (http://localhost:3000/)
username: "admin",
password: "chatbot"
}
};
Utiliser un proxy inverse nginx pour exposer publiquement le panneau de configuration http (exemple de configuration).
Exemples
Modifier votre dossier ./src/conversations/conversation.js
et créez votre workflow de conversation personnalisé.
Exemple 1
import { buttons } from "../helpers";
/**
* Chatbot conversation flow
* Example 1
*/
export default [
{
id: 1,
parent: 0,
pattern: /hello|hi|howdy|good day|good morning|hey|hi-ya|how are you|how goes it|howdy\-do/,
message: "Hello! Thank you for contacting me, I am a Chatbot 🤖 , we will gladly assist you.",
description: "Can I help with something?",
buttons: buttons([
"Website",
"Linkedin",
"Github",
"Donate",
"Leave a Message",
]),
},
{
id: 2,
parent: 1, // Relation with id: 1
pattern: /website/,
message: "Visit my website and learn more about me!",
link: "https://jordifernandes.com/",
end: true,
},
{
id: 3,
parent: 1, // Relation with id: 1
pattern: /linkedin/,
message: "Visit my LinkedIn profile!",
link: "https://www.linkedin.com/in/jfadev",
end: true,
},
{
id: 4,
parent: 1, // Relation with id: 1
pattern: /github/,
message: "Check my Github repositories!",
link: "https://github.com/jfadev",
end: true,
},
{
id: 5,
parent: 1, // Relation with id: 1
pattern: /donate/,
message: "A tip is always good!",
link: "https://jordifernandes.com/donate/",
end: true,
},
{
id: 6,
parent: 1, // Relation with id: 1
pattern: /leave a message/,
message: "Write your message, I will contact you as soon as possible!",
},
{
id: 7,
parent: 6, // Relation with id: 6
pattern: /.*/, // Match with all text
message: "Thank you very much, your message will be sent to Jordi! Sincerely the Chatbot 🤖 !",
end: true,
},
];
Exemple 2
import { buttons, remoteTxt, remoteJson } from "../helpers";
const customEndpoint = "https://jordifernandes.com/examples/chatbot";
/**
* Chatbot conversation flow
* Example 2
*/
export default [
{
id: 1,
parent: 0,
pattern: /.*/,
message: "Hello! I am a Delivery Chatbot.",
description: "Choice one option!",
buttons: buttons([
"See today's menu?",
"Order directly!",
"Talk to a human!",
]),
},
{
id: 2,
parent: 1, // Relation with id: 1
pattern: /menu/,
message: remoteTxt(`${customEndpoint}/menu.txt`),
// message: remoteJson(`${customEndpoint}/menu.json`)[0].message,
end: true,
},
{
id: 3,
parent: 1, // Relation with id: 1
pattern: /order/,
message: "Make a order!",
link: `${customEndpoint}/delivery-order.php`,
end: true,
},
{
id: 4,
parent: 1, // Relation with id: 1
pattern: /human/,
message: "Please call the following WhatsApp number: +1 206 555 0100",
end: true,
},
];
Exemple 3
import fetch from "sync-fetch";
import { remoteImg } from "../helpers";
const customEndpoint = "https://jordifernandes.com/examples/chatbot";
/**
* Chatbot conversation flow
* Example 3
*/
export default [
{
id: 1,
parent: 0,
pattern: /.*/, // Match all
message: "Hello! I am a Delivery Chatbot. Send a menu item number!",
},
{
id: 2,
parent: 0, // Same parent (send reply id=1 and id=2)
pattern: /.*/, // Match all
image: remoteImg(`${customEndpoint}/menu.jpg`),
},
{
id: 3,
parent: 1, // Relation with id: 1
pattern: /\d+/, // Match any number
message: "You are choice item number $input. How many units do you want?", // Inject input value ($input) in message
},
{
id: 4,
parent: 2, // Relation with id: 2
pattern: /\d+/, // Match any number
message: "You are choice $input units. How many units do you want?",
// Inject custom code or overwrite output 'message' property before reply
beforeReply(from, input, output, parents) {
// Example check external api and overwrite output 'message'
const response = fetch(
`${customEndpoint}/delivery-check-stock.php/?item=${input}&qty=${parents.pop()}`
).json();
return response.stock === 0
? "Item number $input is not available in this moment!"
: output;
},
end: true,
},
];
Exemple 4
import { remoteImg } from "../helpers";
const customEndpoint = "https://jordifernandes.com/examples/chatbot";
/**
* Chatbot conversation flow
* Example 4
*/
export default [
{
id: 1,
parent: 0,
pattern: /.*/, // Match all
message: "Image local and remote! Send [local] or [remote]",
},
{
id: 2,
parent: 1,
pattern: /local/,
image: "./images/image1.jpg",
end: true,
},
{
id: 3,
parent: 1,
pattern: /remote/,
image: remoteImg(`${customEndpoint}/image1.jpg`),
end: true,
},
];
Exemple 5
import { remoteImg } from "../helpers";
const customEndpoint = "https://jordifernandes.com/examples/chatbot";
/**
* Chatbot conversation flow
* Example 5
*/
export default [
{
id: 1,
parent: 0,
pattern: /.*/, // Match all
message: "Audio local and remote! Send [local] or [remote]",
},
{
id: 2,
parent: 1,
pattern: /local/,
audio: "./audios/audio1.mp3",
end: true,
},
{
id: 3,
parent: 1,
pattern: /remote/,
audio: remoteAudio(`${customEndpoint}/audio1.mp3`),
end: true,
},
];
Exemple 6
import fetch from "sync-fetch";
const customEndpoint = "https://jordifernandes.com/examples/chatbot";
/**
* Chatbot conversation flow
* Example 6
*/
export default [
{
id: 1,
parent: 0,
pattern: /.*/, // Match all
message: "",
// Inject custom code or overwrite output 'message' property before reply
beforeReply(from, input, output, parents) {
// Get reply from external api and overwrite output 'message'
const response = fetch(`${customEndpoint}/ai-reply.php/?input=${input}`).json();
return response.message;
},
end: true,
},
];
Exemple 7
import fetch from "sync-fetch";
const customEndpoint = "https://jordifernandes.com/examples/chatbot";
/**
* Chatbot conversation flow
* Example 7
*/
export default [
{
id: 1,
parent: 0,
pattern: /.*/, // Match all
message: "Hello!",
// Inject custom code after reply
afterReply(from, input, parents) {
// Send WhatApp number to external api
const response = fetch(`${customEndpoint}/number-lead.php/`, {
method: "POST",
body: JSON.stringify({ number: from }),
headers: { "Content-Type": "application/json" },
}).json();
console.log('response:', response);
},
end: true,
},
];
Exemple 8
import { buttons, inp } from "../helpers";
/**
* Chatbot conversation flow
* Example 8
*/
export default [
{
id: 1,
parent: 0,
pattern: /.*/,
message: "Choice one option",
description: "choice option:",
buttons: buttons(["Option 1", "Option 2"]),
},
{
id: 2,
parent: 1,
pattern: /.*/,
message: "We have received your request. Thanks.\n\n",
beforeReply(from, input, output, parents) {
output += `Your option: ${inp(2, parents)}`;
return output;
},
forward: "[email protected]", // default number or empty
beforeForward(from, forward, input, parents) { // Overwrite forward number
switch (inp(2, parents)) { // Access to replies inputs by id
case "option 1":
forward = "[email protected]";
break;
case "option 2":
forward = "[email protected]";
break;
default:
forward = "[email protected]";
break;
}
return forward;
},
end: true,
},
];
Exemple 9
/**
* Chatbot conversation flow
* Example 9
*/
export default [
{
id: 1,
parent: 0,
pattern: /.*/, // Match all
message: "",
// Inject custom code or overwrite output 'message' property before reply
beforeReply(from, input, output, parents, media) {
if (media) {
console.log("media buffer", media.buffer);
return `You send file with .${media.extension} extension!`;
} else {
return "Send a picture please!";
}
},
end: true,
},
];
Exemple 10
doc/examples/conversation10.js
import { promises as fs } from "fs";
/**
* Chatbot conversation flow
* Example 10
*/
export default [
{
id: 1,
parent: 0,
pattern: /\b(?!photo\b)\w+/, // different to photo
message: `Write "photo" for starting.`,
},
{
id: 2,
parent: [0, 1],
pattern: /photo/,
message: `Hi I'm a Chatbot, send a photo(s)`,
},
{
id: 3,
parent: 2,
pattern: /\b(?!finalize\b)\w+/, // different to finalize
message: "",
async beforeReply(from, input, output, parents, media) {
const uniqId = (new Date()).getTime();
// Download media
if (media) {
const dirName = "./downloads";
const fileName = `${uniqId}.${media.extension}`;
const filePath = `${dirName}/${fileName}`;
await fs.mkdir(dirName, { recursive: true });
await fs.writeFile(filePath, await media.buffer);
return `Photo download successfully! Send another or write "finalize".`;
} else {
return `Try send again or write "finalize".`;
}
},
goTo(from, input, output, parents, media) {
return 3; // return to id = 3
},
},
{
id: 4,
parent: 2,
pattern: /finalize/,
message: "Thank's you!",
end: true,
},
];
Exemple 11
doc/examples/conversation11.js
import { inp, med } from "../helpers";
import { promises as fs } from "fs";
const menu = "Menu:\n\n" +
"1. Send Text\n" +
"2. Send Image\n";
/**
* Chatbot conversation flow
* Example 11
*/
export default [
{
id: 1,
parent: 0,
pattern: /\/admin/,
from: "[email protected]", // only respond to this number
message: menu
},
{
id: 2,
parent: [1, 5],
pattern: /.*/,
message: "",
async beforeReply(from, input, output, parents, media) {
switch (input) {
case "1":
return `Write your text:`;
case "2":
return `Send your image:`;
}
},
},
{
id: 3,
parent: 2,
pattern: /.*/,
message: `Write "/save" to save or cancel with "/cancel".`,
},
{
id: 4,
parent: 3,
pattern: /\/save/,
message: "",
async beforeReply(from, input, output, parents, media) {
let txt = "";
let img = null;
let filePath = null;
const type = inp(2, parents);
if (type === "1") {
txt = inp(3, parents);
} else if (type === "2") {
img = med(3, parents); // media from parent replies
}
if (img) {
const uniqId = new Date().getTime();
const dirName = ".";
const fileName = `${uniqId}.${img.extension}`;
filePath = `${dirName}/${fileName}`;
await fs.writeFile(filePath, await img.buffer);
} else {
const uniqId = new Date().getTime();
const dirName = ".";
const fileName = `${uniqId}.txt`;
await fs.writeFile(filePath, txt);
}
return `Ok, text or image saved. Thank you very much!`;
},
end: true,
},
{
id: 5,
parent: 3,
pattern: /\/cancel/,
message: menu,
goTo(from, input, output, parents, media) {
return 2;
},
clearParents: true, // reset parents
},
];
Advancé
Plusieurs flux de conversation
Éditer ./src/main.js
fichier.
import { session } from "./core";
import info from "./conversations/info";
import delivery from "./conversations/delivery";
session("chatbotSession", info);
session("chatbotSession", delivery);
Comptes multiples
Éditer ./src/main.js
fichier.
import { session } from "./core";
import commercial from "./conversations/commercial";
import delivery from "./conversations/delivery";
session("commercial_1", commercial);
session("commercial_2", commercial);
session("delivery", delivery);
Éditer ./src/httpCtrl.js
fichier.
import { httpCtrl } from "./core";
httpCtrl("commercial_1", 3000);
httpCtrl("commercial_2", 3001);
httpCtrl("delivery", 3002);
Accès au client Venom
Éditer ./src/main.js
fichier.
import { session } from "./core";
import conversation from "./conversations/conversation";
// Run conversation flow and return a Venom client
const chatbot = await session("chatbotSession", conversation);
Planifier des tâches
Éditer ./src/main.js
fichier.
import schedule from "node-schedule"; // Add node-schedule in your project
import { session, log } from "./core";
import { jobsOptions } from "./config";
import conversation from "./conversations/conversation";
// Run conversation flow and return a Venom client
const chatbot = await session("chatbotSession", conversation);
const job1 = schedule.scheduleJob(
jobsOptions.job1.rule, // "*/15 * * * *"
async () => {
// custom logic example
await chatbot.sendText("[email protected]", "test");
}
);
Essai
Les tests unitaires écrivent avec Est
$ yarn test
Testez votre structure de tableau de flux de conversation avec conversation.test.js fichier comme exemple.
$ yarn test src/conversations/conversation
Dépannage
Attention: Ne vous connectez pas à WhatsApp Web avec le même compte que celui utilisé par le chatbot. Cela empêchera le chatbot d'entendre les messages.
Attention: Vous avez besoin d'un compte WhatsApp pour le chatbot et d'un compte différent pour pouvoir lui parler.