
Teilen Sie:
Ich bin ein JavaScript-Entwickler und ein Developer Educator bei Vonage. Im Laufe der Jahre habe ich mich für Templates, Node.js, progressive Web-Apps und Offline-First-Strategien begeistert, aber was ich immer geliebt habe, ist eine nützliche, gut dokumentierte API. Mein Ziel ist es, Ihre Erfahrung mit unseren APIs so gut wie möglich zu gestalten.
Zustandsautomaten für WhatsApp-Messaging-Bots mit Node.js
Lesedauer: 6 Minuten
Bei einem typischen Webserver muss man sich nicht viel Gedanken über den Zustand machen. Ein Benutzer sendet eine Anfrage und Sie liefern eine Antwort. Es ist nicht notwendig, dass die Anwendung sich selbst durch einen Pfad von Auswahlmöglichkeiten und Aktionen führt; das macht der Endbenutzer. Ein Bot funktioniert jedoch anders.
Auch wenn ein Endbenutzer eine Konversation mit einem Bot einleitet, muss der Bot den weiteren Weg definieren und dem Benutzer Fragen stellen, um ihn über die möglichen nächsten Schritte zu informieren. Wenn der Bot nicht nur Fragen beantwortet, sondern den Endbenutzer durch eine Reihe von Schritten führt, spricht man von einer Zustandsmaschine.
Die Implementierung eines Zustandsautomaten als Messaging-Bot ist ein wenig schwierig, da Messaging-Bots von Natur aus kein Konzept für einen Zustand haben. Standardmäßig wird eine Nachricht, die an einen von Ihnen kontrollierten Server gesendet wird, ohne eine Sitzung, einen Status oder andere Informationen über ein großes Bild, zu dem die einzelne Nachricht gehören könnte, empfangen. Das bedeutet aber eigentlich nur, dass Sie den letzten Zustand einer "Sitzung" zwischen Ihrem Server und einer bestimmten Telefonnummer manuell speichern müssen. In Wirklichkeit muss eine Webanwendung das Gleiche tun. Plattformen und Bibliotheken erledigen diese Arbeit routinemäßig für uns.
Voraussetzungen
Unser Bot-Server wird einen dieser traditionellen Webserver auf eine nicht-traditionelle Weise nutzen. Um diesem Beispiel zu folgen, benötigen Sie:
Knotenpunkt und Express installiert
A SQLite Datenbank installiert und konfiguriert
Ein Vonage Developer-Konto
Vonage API-Konto
Um dieses Tutorial durchzuführen, benötigen Sie ein Vonage API-Konto. Wenn Sie noch keines haben, können Sie sich noch heute anmelden und mit einem kostenlosen Guthaben beginnen. Sobald Sie ein Konto haben, finden Sie Ihren API-Schlüssel und Ihr API-Geheimnis oben auf dem Vonage-API-Dashboard.
Der Code, den wir uns ansehen werden, ist Teil eines größeren WhatsApp-Bot-Beispielprojekts auf Glitch. Sie können ihn auch von dort kopieren und einfügen oder ihn abwandeln, um mit einer funktionierenden App zu beginnen.
Server-Endpunkte
Alle Anweisungen und Anfragen von Endbenutzern werden über einen einzigen Endpunkt auf unserem Server geleitet. Es ist Aufgabe unseres Servers, sie zu analysieren und zu entscheiden, was als nächstes zu tun ist. Eingehende Nachrichten sind POST-Anfragen, die die Nachricht selbst und ihre Metadaten enthalten. Wir können Express verwenden, um sie zu verarbeiten, und dann bestimmte Typen später an andere Handler weiterleiten.
Zunächst richten wir einen Express-Server in server.jsein und konfigurieren ihn so, dass er den Body eingehender Anfragen analysiert und statische Seiten serverseitig verarbeitet. Wir können auch unsere Zustände definieren. Ich habe erklärende Eigenschaftsnamen verwendet, die auf Ganzzahlen abgebildet sind, um String-Vergleiche zu vermeiden. Davon wird es später noch viele geben!
const fs = require('fs');
const express = require('express');
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(express.static('public'));
const states = {
waiting: 0,
getUsername: 1,
getEmail: 2,
getAddress: 3,
confirmPayment: 4
};
// CONFIGURE DATABASE
// APP CODE
const listener = app.listen(process.env.PORT, () => {
console.log("Your app is listening on port " + listener.address().port);
});
Da die Vonage-API zwei Webhooks bereitstellt, gibt es zwei Endpunkte auf dem Server. In diesem Beispiel wird jedoch nur einer tatsächlich etwas tun. Um die Dinge ordentlich zu halten, bestätigt der /status Endpunkt lediglich alle empfangenen Anfragen. Der /inbound Endpunkt ist der Ort, an dem die Arbeit der Anwendung wirklich beginnt.
Vor dem /inbound Endpunkt erstellen wir eine Vonage-Instanz, die wir zum Senden von Antworten verwenden können. Das Beispiel verwendet die Vonage Message API Sandbox, die das Setzen der apiHost.
// APP CODE
// this endpoint receives information about events in the app
app.post('/status', function(req, res) {
res.status(204).end();
});
const Vonage = require('@vonage/server-sdk');
const vonage = new Vonage({
apiKey: process.env.API_KEY,
apiSecret: process.env.API_SECRET,
applicationId: process.env.APP_ID,
privateKey: __dirname + '/.data/private.key'
},{
apiHost: 'https://messages-sandbox.nexmo.com/'
});
app.post('/inbound', function(req, res) {});
app.post('/signup', function(req, res) {});
function setUsername(phone, username) {}Bevor wir den Handler für eingehende Nachrichten und andere Funktionen ausarbeiten, konfigurieren wir die anderen Teile, die wir benötigen.
Konfigurieren der Webhooks
Um Nachrichten zwischen einem persönlichen Messaging-App-Konto und dem Server hin und her zu senden, müssen Sie die Vonage Messages API konfigurieren. Nachrichten, die an ein Vonage-eigenes Konto oder ein Konto, das Sie mit einer Vonage-Applikation registriert haben, gesendet werden, werden an den von Ihnen angegebenen Endpunkt weitergeleitet. Je nachdem, ob Sie die Messages API Sandbox verwenden oder nicht, gibt es zwei Möglichkeiten, dies zu tun.
Wenn Sie die Sandbox verwenden, müssen Sie keine Anwendung erstellen, um das Messaging zu testen. Sie können Ihre Webhooks direkt auf der Sandbox-Seite konfigurieren. Geben Sie einfach einen Endpunkt für die Verarbeitung eingehender Nachrichten und einen für die Verarbeitung von Statusmeldungen auf Ihrem öffentlich zugänglichen Server an.

Wenn Sie eine Nummer für Nachrichten besitzen, können Sie die Webhooks innerhalb Ihrer Anwendung konfigurieren. Scrollen Sie beim Erstellen der Anwendung nach unten zu den Funktionen und schalten Sie "Nachrichten" ein. Dadurch werden die Felder angezeigt, in denen Sie die Webhook-Endpunkte angeben können.

Einrichten eines Datenspeichers
Der Status, den Sie speichern, kann einfach oder komplex sein, je nach Ihren Bedürfnissen. Neben dem Zustand der Interaktion des Servers mit einem bestimmten Benutzer möchten Sie vielleicht auch Informationen speichern, die Sie auf einem herkömmlichen Webserver in einer Sitzungsvariablen aufbewahren würden. Manchmal werden diese Informationen jedoch in der Session gespeichert, um zu vermeiden, dass sie ständig in der Datenbank nachgeschlagen werden müssen, so dass der Nutzen gering sein kann. Zusätzliche Informationen in Ihrer Zustandsdatenbank werden wahrscheinlich am besten für zusätzlichen Kontext verwendet, der für den aktuellen Zustand relevant ist.
Zunächst erstellen wir die Datenbank selbst und fügen dann eine Zustandstabelle hinzu. Dieses Beispiel wird nicht komplex sein. Anstatt mehrere Spalten für verschiedene Informationen zu enthalten, die ein bestimmter Zustand möglicherweise benötigt, verwenden wir nur eine Sammelspalte namens memo:
// CONFIGURE DATABASE
const dbFile = './.data/sqlite.db';
var exists = fs.existsSync(dbFile);
const sqlite3 = require('sqlite3').verbose();
const db = new sqlite3.Database(dbFile);
db.serialize(function(){
if (!exists) {
db.run('CREATE TABLE State (phone NUMERIC UNIQUE, state NUMERIC, memo TEXT)')
db.run('CREATE TABLE Users (phone NUMERIC UNIQUE, username TEXT, email TEXT, address TEXT)');
}
});Wir erstellen auch eine Users Tabelle, da der zustandsabhängige Prozess in diesem Beispiel die Benutzerregistrierung sein wird.
Überprüfung des Zustands
Wenn wir nun eine eingehende Nachricht erhalten, sind wir bereit zu prüfen, ob sich unser Bot in einem zustandsabhängigen Prozess mit dem Absender befindet. Wir sehen uns den Inhalt der Nachricht erst dann an, wenn wir in der Statusdatenbank nachsehen und feststellen, welchen Status wir erwarten. Wenn man davon ausgeht, dass es möglich ist, einen zustandsabhängigen Prozess zu beenden, könnte man prüfen, ob der Benutzer dies möchte, bevor man die Datenbank aufruft. Aber die meisten Prozesse, wenn sie einmal begonnen haben, müssen bereinigt werden, wenn sie abgebrochen werden, also ist es genauso wahrscheinlich, dass Sie die Datenbank sowieso überprüfen wollen.
Das Wichtigste, was wir aus dem Anfragetext brauchen, ist die Telefonnummer des Absenders. Damit können wir die Zustandsdatenbank abfragen und, wenn wir herausfinden, dass die Nummer einen Zustand hat, eine Funktion aufrufen, der sie entspricht. Wenn nicht, können wir die gesamte Nachricht an eine parseIncoming Funktion übergeben, die nach neuen Anweisungen sucht.
app.post('/inbound', function(req, res) {
let phone = req.body.from.number;
let message = req.body.message.content;
db.get('SELECT * FROM State WHERE (phone = $phone)', {
$phone: phone
}, function(error, userState) {
switch(userState.state) {
case states.getUsername:
setUsername(phone, message.text);
break;
case states.getEmail:
setEmail(phone, message.text, true);
break;
case states.getAddress:
setAddress(phone, message.text, true);
break;
case states.confirmPayment:
completeBuy(phone, message.text);
break;
default:
parseIncoming(phone, message);
}
});
res.status(204).end();
}); Aktualisierung des Status
Angenommen, wir setzen den Prozess fort, dann wird der nächste Zustand durch den aktuellen Zustand bestimmt. Anfänglich gibt es natürlich keinen. Der Benutzer muss irgendwie in einen zustandsabhängigen Prozess eintreten. Für die meisten der im Beispiel verfügbaren Zustände ist dieser Weg die Anmeldung.
Das Muster im /signup Endpunkt ist die Hälfte des Musters, dem die meisten anderen Schritte des Anmeldevorgangs folgen werden. Es sendet eine Nachricht an die Telefonnummer, die im Anforderungskörper gefunden wurde (in diesem Fall von einem Webformular statt von einer Nachricht), und fordert den Benutzer auf, den nächsten Schritt auszuführen. Anschließend wird eine neue Zeile in der Statusdatenbank erstellt, um den Platz des Benutzers im Prozess zu markieren. In den folgenden Schritten wird dies eine Aktualisierung sein:
app.post('/signup', function(req, res) {
let phone = req.body.number;
vonage.channel.send(
{ type: 'whatsapp', number: phone },
{ type: 'whatsapp', number: process.env.WHATSAPP_NUM },
{ content: {
type: 'text',
text: 'Welcome to Nice Cool Shoes! What should we call you?'
}}, (e, data) => {
if (e) {
console.error(e);
} else {
db.run('INSERT INTO State (phone, state) VALUES ($phone, $state)', {
$phone: parseInt(phone),
$state: states.getUsername
}, (err) => {
if (err) {
console.error(err);
}
});
}
}
);
res.send({});
});
Die nächste Funktion des Prozesses, setUsername zeigt einen vollständigen Zustandsübergang. Da der vorherige Schritt eine Eingabeaufforderung gesendet hat, wird davon ausgegangen, dass die nächste Nachricht, die zurückkommt, die Antwort ist. Daher wird der Nachrichtentext in die Tabelle Users Tabelle als der Benutzername des neuen Benutzers eingefügt. Sobald das erledigt ist, ist der Rest wie der /signup Endpunkt. Der Server sendet die nächste Eingabeaufforderung und aktualisiert die State Tabelle:
function setUsername(phone, username) {
db.run('INSERT INTO Users (phone, username) VALUES ($phone, $username)', {
$phone: parseInt(phone),
$username: username
}, (err, row) => {
if (err) {
console.error(err);
}
});
vonage.channel.send(
{ type: 'whatsapp', number: phone },
{ type: 'whatsapp', number: process.env.WHATSAPP_NUM },
{ content: {
type: 'text',
text: 'Nice to meet you, ' + username + '! What\'s your email address?'
}}, (e, data) => {
if (e) {
console.error(e);
} else {
db.run('UPDATE State SET state = $state WHERE phone = $phone', {
$phone: parseInt(phone),
$state: states.getEmail
}, (err, row) => {
if (err) {
console.error(err);
}
});
}
}
);
}
Nächste Schritte
Wenn Ihr Bot hauptsächlich Fragen beantwortet, ist Ihr Bedarf an einem Zustandsautomaten möglicherweise begrenzt und die Festcodierung einiger Funktionen kann die sinnvollste Lösung sein. Vielleicht ist Ihnen aber auch aufgefallen, dass Workflows wie die Benutzeranmeldung im Beispiel bei jedem Schritt leicht unterschiedliche Informationen für dieselben Aufgaben verwenden. Indem Sie die gemeinsamen Elemente in eine einzige Funktion abstrahieren und ein detaillierteres Array von Zuständen bereitstellen, können Sie den Server den Prozess auf weniger manuelle Weise vorantreiben lassen. Für unser Beispiel könnte eine erweiterte Definition eines Zustands Folgendes umfassen:
Name der zu aktualisierenden Spalte
nächste Aufforderung
nächster Stand
Sie können weitere Informationen hinzufügen, z. B. Tabellennamen, um Zustände in mehreren verschiedenen Prozessen zu behandeln.
Es gibt viele interessante Möglichkeiten, wie Sie einen Messaging-Bot strukturieren können. Sehen Sie sich die Vonage Messages API-Dokumentation um mehr über Funktionen und Anwendungsfälle zu erfahren, die für Ihr Projekt hilfreich sein könnten.
Teilen Sie:
Ich bin ein JavaScript-Entwickler und ein Developer Educator bei Vonage. Im Laufe der Jahre habe ich mich für Templates, Node.js, progressive Web-Apps und Offline-First-Strategien begeistert, aber was ich immer geliebt habe, ist eine nützliche, gut dokumentierte API. Mein Ziel ist es, Ihre Erfahrung mit unseren APIs so gut wie möglich zu gestalten.
