
Share:
)
Michael ist ein polyglotter Software-Ingenieur, der sich dafür einsetzt, die Komplexität von Systemen zu reduzieren und sie berechenbarer zu machen. Er arbeitet mit einer Vielzahl von Sprachen und Tools und gibt sein technisches Fachwissen auf Benutzergruppen und Konferenzen in der ganzen Welt weiter. Im Alltag ist Michael ein ehemaliger Developer Advocate bei Vonage, wo er seine Zeit damit verbrachte, über alle Arten von Technologie zu lernen, zu lehren und zu schreiben.
Behalten Sie den Überblick über Ihr Budget mit Dial YNAB
Lesedauer: 16 Minuten
Zwischen dem Bezahlen der Hypothek, dem Sparen für den Notfall und dem Kauf von viel zu vielen Brettspielen fiel es mir immer schwer, den Überblick zu behalten, wohin mein Geld jeden Monat ging. Zum Glück entdeckte ich Du brauchst ein Budget (YNAB) entdeckt, mit dem ich jeden Monat Geld in verschiedene Kategorien einzahlen und den Überblick behalten kann, wie viel in jeder Kategorie steckt.
Die mobile Anwendung und die Website sind ziemlich gut, aber als ich sah, dass YNAB vor kurzem eine API eingeführt hat, dachte ich über andere Möglichkeiten nach, auf die Daten in meinem Budget zuzugreifen. Es dauerte nicht lange, bis die Inspiration zuschlug.
Seit Jahren kann man bei seiner Bank anrufen und den Kontostand abfragen, aber das ist für mich nicht sinnvoll. Der Gesamtsaldo spiegelt nicht das Geld wider, das bereits für einen zukünftigen Kauf vorgesehen ist. Stattdessen wollte ich eine Nummer anrufen, um herauszufinden, wie viel ich noch in der Kategorie Brettspiele habe, und so, dial-ynab war geboren.
Übersicht
In diesem Beitrag werden wir eine node.js-Anwendung erstellen, die die Nexmo-Plattform nutzt, um Folgendes zu tun:
Einen Sprachanruf entgegennehmen.
Einspeisung der Audiodaten in die Sprach-zu-Text-API von Google
Fragen Sie die YNAB-API ab, um den aktuellen Saldo der gewünschten Kategorie zu ermitteln.
Nutzen Sie die Text-to-Speech-Funktion von Nexmo, um den Saldo in den Anruf zurückzusprechen.
Dial YNAB Sequence Diagram
Um dies zu erreichen, müssen wir die folgenden Schritte durchführen:
Bootstrap ein Node.js-Projekt mit
express
undexpress-ws
Konfigurieren Sie eine Nexmo-Anwendung
Authentifizierungsdaten für Google Cloud und YNAB einholen
Einen eingehenden Anruf mit Nexmo bearbeiten
Verbinden Sie den Aufruf mit unserer Anwendung über einen Websocket
Übergabe der Audiodaten von Nexmo an Google zur Transkription
Umgang mit den von Google zurückgegebenen transkribierten Daten
Abrufen unserer aktuellen Kontostände aus YNAB
Sprechen Sie den Saldo mit Nexmos Text-To-Speech-Funktion in den Anruf zurück
Da gibt es eine Menge, also sollten wir anfangen!
Voraussetzungen
Um dieses Tutorial durchzuarbeiten, benötigen Sie Folgendes:
node.js (ich verwende die Version 10.0.0) und npm installiert
ngrok um Ihre lokale Anwendung für das Internet freizugeben, damit Nexmo sie erreichen kann
nexmo-cli verfügbar (dies ist optional, da Sie dieselben Aufgaben auch über das Nexmo-Dashboard ausführen können)
Google- und YNAB-Anmeldeinformationen (diese werden später behandelt)
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.
Sobald Sie alles zur Hand haben, starten Sie einen ngrok
Tunnel durch Ausführen von ngrok http 3000
und notieren Sie sich die URL (in meinem Fall ist es http://e7dddad9.ngrok.io
). Jedes Mal, wenn Sie eine ngrok
URL in diesem Beitrag sehen, tauschen Sie sie gegen Ihre eigene aus.
dial ynab ngrok
Bootstrap für ein Projekt
Beginnen wir mit der Erstellung eines Ordners namens dial-ynab
und wechseln in das Verzeichnis. Um unser Projekt zu starten, müssen wir npm init
ausführen und ein paar Abhängigkeiten installieren:
Wir brauchen nicht alle diese Abhängigkeiten zu Beginn, aber es ist einfacher, sie alle im Voraus zu installieren, damit wir uns später nicht um sie kümmern müssen.
Erstellen einer Nexmo-Anwendung
Bevor wir einen eingehenden Anruf bearbeiten können, müssen wir eine Nexmo-Anwendung erstellen und eine Nummer mit ihr verknüpfen. Wir werden dazu das Nexmo CLI-Tool verwenden, aber Sie können auch eine Anwendung erstellen erstellen und mit einer Nummer im Dashboard verknüpfen, wenn Sie dies bevorzugen.
Sobald Sie dies getan haben, wird Nexmo jedes Mal, wenn ein Anruf zu der von Ihnen erworbenen Nummer getätigt wird, eine GET
Anfrage an http://e7dddad9.ngrok.io/webhook/answer
um herauszufinden, wie der Anruf zu behandeln ist. Lassen Sie uns diesen Endpunkt jetzt mit Express implementieren.
Eingehende Anrufe bearbeiten
Es ist eine Menge Code erforderlich, um unsere Express-Instanz zu booten. Erstellen Sie eine Datei namens index.js
mit dem folgenden Inhalt, die dotenv
für die Konfigurationswerte und erstellt eine express
Instanz ohne definierte Routen erstellt:
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const expressWs = require('express-ws')(app);
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// Routes go here
app.listen(process.env.PORT, function () {
console.log(`dial-ynab listening on port ${process.env.PORT}!`);
});
Wir haben auch auf eine Variable namens process.env.PORT
referenziert, sie aber noch nicht definiert. Erstellen Sie eine Datei namens .env
mit folgendem Inhalt, um dies zu tun:
Das letzte Teil des Puzzles ist die Definition unserer /webhooks/answer
URL. Dies geschieht durch die Definition einer app.get()
Methode kurz vor dem Aufruf von app.listen()
. Wenn Nexmo eine Anfrage an unsere Anwendung stellt, erwarten sie, dass wir eine NCCO. In diesem Fall geben wir eine talk
Aktion zurück, die dem Anrufer eine Antwort mittels Text-To-Speech vorspricht:
app.get('/webhooks/answer', function (req, res) {
return res.json([
{
"action": "talk",
"text": "This is a text to speech demo from Nexmo. Thanks for calling"
}
]);
});
Das ist alles, was Sie brauchen, um einen eingehenden Anruf mit Nexmo zu bearbeiten. Probieren Sie es aus, indem Sie node index.js
und rufen Sie dann die Nummer an, die Sie zuvor gekauft haben. Sie sollten hören This is a text to speech demo from Nexmo. Thanks for calling
bevor der Anruf beendet wird.
Herzlichen Glückwunsch! Sie haben den schwierigen Teil erledigt - der Rest dieses Beitrags besteht nur noch aus der Verkabelung verschiedener externer Dienste
Dienste konfigurieren
Bevor wir mit dem Rest des Beitrags fortfahren können, benötigen wir die Authentifizierungsdaten für Google Cloud Speech und You Need A Budget.
Für Google Cloud-Sprache müssen Sie einen Schlüssel für ein Dienstkonto erstellen erstellen und die Anmeldeinformationen als JSON herunterladen. Erstellen Sie ein neues Dienstkonto, nennen Sie es dial-ynab
und geben Sie ihm die Project->Owner
Rolle. Für die Bereitstellung in der Produktion müssen Sie eine spezielle IAM-Rolle erstellen, aber für den Anfang ist dies der einfachste Weg. Laden Sie die Datei mit den Anmeldeinformationen herunter, benennen Sie sie um in google-creds.json
und legen Sie sie in Ihrem Projektordner neben index.js
.
YNAB API-Zugangsdaten sind etwas einfacher zu finden. Sie können ein persönliches Zugangs-Token in Ihren Kontoeinstellungen. Außerdem benötigen Sie Ihre Budget-ID, die Sie über die folgende Website finden können die Weboberfläche besuchen und die ID in die URL kopieren (sie sieht dann ähnlich aus wie 58f1ca9a-abcd-123a-96ef-21aac7e2865c
)
Zu diesem Zeitpunkt sollten Sie es haben:
Nexmo-Anwendungs-ID
Anmeldeinformationen für Google-Anwendungen
YNAB-Budget-ID und Zugriffstoken
Fügen wir diese zu unserer .env
Datei hinzu, damit wir sie in unserer Anwendung verwenden können:
NEXMO_PRIVATE_KEY
und GOOGLE_APPLICATION_CREDENTIALS
sind Pfade zu Dateien, die sich in unserem Projektordner neben index.js
die unsere Anmeldeinformationen enthalten.
Verbindung zu unseren WebSockets
Jetzt, da wir einen eingehenden Anruf bearbeiten können und unsere Google-Anmeldedaten haben, ist es an der Zeit, die Audiodaten des Telefongesprächs in den Transkriptionsdienst von Google einzuspeisen. Dies geschieht über zwei Websockets: einen von Nexmo zu unserer Anwendung und einen weiteren von unserer Anwendung zu Google.
Beginnen wir mit der Änderung unseres /webhooks/answer
Endpunkt zur Verwendung einer connect
Aktion. Dadurch wird Nexmo angewiesen, sich mit dem /transcription
Endpunkt in unserer Anwendung über einen Websocket zu verbinden. Wir weisen Nexmo auch an, die UUID des Aufrufs an den Websocket zu übergeben, indem wir die Option headers
an den Websocket zu übergeben, da wir diese später noch brauchen werden.
Ersetzen Sie Ihren bestehenden /webhooks/answer
Endpunkt durch den folgenden:
app.get('/webhooks/answer', function (req, res) {
return res.json([
{
"action": "talk",
"text": "Please say the name of the category you would like the balance for"
},
{
"action": "connect",
"endpoint": [
{
"type": "websocket",
"content-type": "audio/l16;rate=8000",
"uri": `ws://${req.get('host')}/transcription`,
"headers": {
"user": req.query.uuid
}
}
]
}
]);
});
Wir müssen Nexmo nicht nur sagen, dass es sich mit /transcription
zu verbinden, müssen wir einen Endpunkt erstellen, der auf eine Websocket-Verbindung wartet. Dies ist der Punkt, an dem das express-ws
Paket ins Spiel. Es fügt eine app.ws()
Methode als Wrapper um einen Websocket-Server. Fügen Sie das Folgende unter Ihrer app.get()
Methode:
app.ws('/transcription', function(ws, req) {
let UUID;
ws.on('message', function(msg) {
});
ws.on('close', function(){
});
});
Die erste Nachricht, die von Nexmo empfangen wird, ist eine JSON-Nachricht, die alle headers
die wir in der NCCO angefordert haben (in diesem Fall die UUID des Anrufs), und alle folgenden Nachrichten sind Puffer mit Audiodaten. Wir können dieses Wissen nutzen, um Folgendes zu implementieren ws.on('message')
Wenn die Nachricht ein Puffer ist, leiten wir sie an Google weiter, andernfalls speichern wir die UUID für später.
let UUID;
ws.on('message', function(msg) {
if (!Buffer.isBuffer(msg)) {
let data = JSON.parse(msg);
UUID = data.user;
return;
}
});
Handhabung einer Abschrift von Google
Bevor wir die Audiodaten an Google senden können, müssen wir eine Instanz des Cloud-Sprachclients konfigurieren. Fügen Sie Folgendes am Anfang der Datei hinzu, direkt nach require('dotenv').config();
const Speech = require('@google-cloud/speech');
const speech = new Speech.SpeechClient();
const googleConfig = {
config: {
encoding: 'LINEAR16',
sampleRateHertz: 8000,
languageCode: 'en-GB'
},
interimResults: false
};
Dadurch wird eine neue Instanz des Cloud-Sprachclients erstellt, die wir verwenden können. Die bereitgestellten Konfigurationsoptionen funktionieren gut mit Nexmo, aber Sie möchten vielleicht die languageCode
wenn Sie etwas anderes sprechen als en-GB
. Eine vollständige Liste der unterstützten Sprachen finden Sie in den Google Cloud Speech-Dokumenten.
Um die Sprache-zu-Text-Funktionalität in der SpeechClient
zu nutzen, verwenden wir die speech.streamingRecognize()
Methode. Aktualisieren app.ws('/transcription')
und erzeugen eine neue Instanz von speech.streamingRecognize
wenn eine neue Websocket-Verbindung empfangen wird:
app.ws('/transcription', function(ws, req) {
let UUID;
const speechStream = speech.streamingRecognize(googleConfig)
.on('error', console.log)
.on('data', async (data) => {
if (!data.results) { return; }
const translation = data.results[0].alternatives[0];
console.log(translation.transcript);
});
ws.on('message', function(msg) {
Sie werden feststellen, dass in der .on('data')
Methode die Ergebnisse von data.results[0].alternatives[0].transcript
. Dies ist der transkribierte Text, der von Google zurückgegeben wird. Wir wissen, dass das erste zurückgegebene Element immer die endgültige Übersetzung ist, da wir interimResults: false
in unserer Konfiguration festgelegt haben.
Da wir eine neue Instanz erstellt haben speech.streamingRecognize()
Instanz erstellt haben, müssen wir auch die Instanz aufräumen, wenn unser Aufruf die Verbindung unterbricht. Dies geschieht durch die Zerstörung unserer speechStream
Instanz in der ws.on('close')
Methode:
ws.on('close', function(){
speechStream.destroy();
});
Als Letztes müssen Sie die ws.on('message')
um die Daten an speechStream
weiterzuleiten, wenn es sich um einen Puffer handelt.
ws.on('message', function(msg) {
if (!Buffer.isBuffer(msg)) {
let data = JSON.parse(msg);
UUID = data.user;
return;
}
speechStream.write(msg);
});
Wenn Sie Ihre Anwendung starten (node index.js
) und Ihre Nexmo-Nummer anrufen, sollten Sie in den Anruf hineinsprechen können und den transkribierten Text in der Konsole in Echtzeit sehen.
Mit YNAB verbinden
Jetzt, da die Transkription funktioniert, müssen wir als Nächstes unsere YNAB-Budgetdaten abrufen. Fügen Sie am Anfang Ihrer Datei (nachdem Sie Ihr googleConfig
Objekts) fügen Sie Folgendes hinzu, um einen ynab
API-Client:
const ynabClient = require("ynab");
const ynab = new ynabClient.API(process.env.YNAB_ACCESS_TOKEN);
Mit diesem Client können wir eine Verbindung zur YNAB-API herstellen und alle unsere Kategoriegruppen und Kategorien auflisten. Da wir nicht an den Hauptgruppen, sondern nur an den Kategorien selbst interessiert sind, können wir mit der folgenden Funktion eine Liste der Kategorienamen und Salden erstellen. Fügen Sie diese Funktion am Ende Ihrer Datei ein:
async function fetchYnabBalanceData() {
let r = await ynab.categories.getCategories(process.env.YNAB_BUDGET_ID);
return r.data.category_groups.reduce((acc, v) => acc.concat(
v.categories.map((c) => { return {"name":c.name, "balance":c.balance/1000}; })
), []);
}
Diese Funktion holt alle Kategorien aus YNAB und gibt eine Liste im folgenden Format zurück:
[
{ name: 'Dining Out', balance: 38.11 },
{ name: 'Gaming', balance: 12.74 },
{ name: 'Music', balance: 43.85 },
{ name: 'Fun Money', balance: -13.44 }
]
Wir verwenden diese fetchYnabBalanceData()
Methode in unserer .on('data')
Funktion verwenden, wenn wir eine Transkription erhalten, um das Gesagte mit einem Kategorienamen abzugleichen. Leider ist es sehr unwahrscheinlich, dass das, was Google zurückgibt, genau Ihrem Kategorienamen entspricht. Wir müssen ein wenig kreativ sein, um herauszufinden, welche Kategorie der Anrufer wollte. Zu diesem Zweck können wir das fast-levenshtein
Paket verwenden, das wir zuvor installiert haben.
Um herauszufinden, welche Kategorie unser Anrufer wollte, können wir die Eingabe (needle
) nehmen und alle Kategorienamen durchsuchen (haystack
) und verwenden fast-levenshtein
um die geringste Anzahl von Buchstabenänderungen zu berechnen, die erforderlich sind, damit ein Kategoriename mit unserer Eingabe übereinstimmt. Dies ist eine grobe Annäherung, aber für unsere Zwecke reicht sie aus. Fügen Sie am Ende Ihrer Datei Folgendes hinzu function fetchYnabBalanceData()
:
function findClosestName(needle, haystack) {
needle = needle.toLowerCase();
let shortestDistance = {"value": [], "distance": Number.MAX_SAFE_INTEGER};
for (let k of haystack) {
let name = k.name.toLowerCase();
if (needle == name) {
return k;
}
let distance = levenshtein.get(needle, name);
if (distance < shortestDistance.distance) {
shortestDistance.value = k;
shortestDistance.distance = distance;
}
}
return shortestDistance.value;
}
Sie müssen auch das fast-levenshtein
Paket am Anfang Ihrer Datei benötigen. Fügen Sie es direkt nach require('dotenv').config()
:
const levenshtein = require('fast-levenshtein');
Wir haben jetzt alles, was wir brauchen, um unsere .on('data')
Funktion zu aktualisieren, um eine Kategorie und einen Saldo auf der Konsole zu protokollieren:
const speechStream = speech.streamingRecognize(googleConfig)
.on('error', console.log)
.on('data', async (data) => {
if (!data.results) { return; }
const translation = data.results[0].alternatives[0];
console.log(translation.transcript);
const categories = await fetchYnabBalanceData();
const category = findClosestName(translation.transcript, categories);
console.log(category);
});
Dies ist ein guter Zeitpunkt, um Ihre Anwendung erneut zu starten (node index.js
) und rufen Sie Ihre Nexmo-Nummer an, um Ihren Code zu testen. Versuchen Sie, "Auswärts essen" zu sagen und beobachten Sie, wie Ihre Kategorie "Auswärts essen" zurückgegeben wird.
Sprechen Sie in den Anruf zurück
Um unser Projekt abzuschließen, bleibt nur noch eines zu tun dial-ynab
Projekt abzuschließen: Es soll den Kategoriensaldo mit Text-To-Speech in den Anruf zurücklesen.
Hierfür müssen wir das nexmo
Paket verwenden. Sie brauchen kein apiKey
oder apiSecret
um die Sprach-API zu verwenden, also können Sie diese Werte ruhig ignorieren. Um auf die Sprach-API zuzugreifen, müssen wir ein applicationId
und privateKey
angeben, das wir zufällig in unsere .env
Datei hinzugefügt haben.
Fügen Sie den folgenden Code direkt unter require('fast-levenshtein')
am Anfang Ihrer Datei ein:
const Nexmo = require('nexmo');
const nexmo = new Nexmo({
apiKey: 'unused',
apiSecret: 'unused',
applicationId: process.env.NEXMO_APPLICATION_ID,
privateKey: process.env.NEXMO_PRIVATE_KEY,
});
Als nächstes aktualisieren Sie Ihre .on('data')
Methode, um die Nexmo API aufzurufen, indem Sie den folgenden Code hinzufügen console.log(category);
:
const balanceText = `${category.name} has ${category.balance} available.`;
nexmo.calls.talk.start(UUID, { text: balanceText }, (err, res) => {
if(err) { console.error(err); }
});
Wenn Sie jetzt erneut Ihre Nexmo-Nummer anrufen, wird Ihnen der Kategoriesaldo vorgelesen. Allerdings klingt der Kategoriesaldo nicht ganz richtig, da er als Dezimalzahl vorgelesen wird. Wir können der Text-To-Speech-Engine mitteilen, dass es sich um einen Währungswert handelt, indem wir SSML. Aktualisieren Sie Ihre balanceText
Definition wie folgt:
const balanceText = `<speak>${category.name} has <say-as interpret-as="vxml:currency">GBP${category.balance}</say-as> available</speak>`;
Rufen Sie Ihre Nexmo-Nummer ein letztes Mal an und Sie werden hören, dass die Nummer dank der interpret-as="vxml:currency"
.
Schlussfolgerung
In weniger als 125 Zeilen Code haben wir eine Anwendung entwickelt, mit der Sie Ihr YNAB-Budget abrufen und sicherstellen können, dass Sie genug in der Kategorie "Essen gehen" übrig haben, bevor Sie nach einem Heißhunger auf Ihr Lieblingsessen zum Mitnehmen losziehen.
Wir haben Nexmo, Google und YNAB mit ihren APIs und Websockets verkabelt, um Anrufe in Echtzeit zu transkribieren und Audio-Feedback zu einem aktiven Sprachanruf zu liefern. Ich weiß nicht, wie es Ihnen geht, aber ich finde das ziemlich genial!
Wenn Sie mehr über die Nexmo-Sprach-API erfahren möchten, finden Sie in der Überblick über die Sprach-API ein guter Startpunkt. Besonders interessant könnte die NCCO-Referenz oder die Websockets-Konzept-Leitfaden.
Wenn Sie über diesen Beitrag, die Nexmo Voice API oder Kommunikation im Allgemeinen sprechen möchten, können Sie sich gerne in der Nexmo Community Slackein, wo der @NexmoDev Team bereit ist zu helfen.
Bonus-Guthaben
Sie lesen immer noch? Ausgezeichnet! Mein Lieblingsteil in diesem Beitrag ist, dass der einzige YNAB-spezifische Teil die fetchYnabBalanceData
Methode ist. Es wäre trivial, dies mit der Topf-Funktion von Monzo anstelle von YNAB zu machen. Tatsächlich sollten wir es jetzt tun!
Holen Sie sich zunächst Ihr Monzo-Zugangs-Token aus dem Monzo Spielplatz und fügen Sie ihn hinzu zu .env
:
Wir werden die request-promise
Bibliothek verwenden, um auf die Monzo-API zuzugreifen, also installieren wir sie jetzt
Fügen Sie am Ende Ihrer Datei Folgendes hinzu, um die fetchMonzoBalanceData
Funktion. Die Monzo-API gibt Daten zurück, die name
und balance
enthält. Wir müssen also nur den Saldo in eine Dezimalwährung umformatieren:
const request = require("request-promise");
async function fetchMonzoBalanceData() {
const data = JSON.parse(await request({"uri": "https://api.monzo.com/pots", "headers": {"Authorization": `Bearer ${process.env.MONZO_ACCESS_TOKEN}`}}));
return data.pots.map((v) => { v.balance = v.balance/100; return v; });
}
Ändern Sie schließlich den Aufruf von fetchYnabBalanceData
so, dass er fetchMonzoBalanceData
stattdessen. Rufen Sie nun Ihre Nexmo-Nummer an und sagen Sie den Namen eines Ihrer Monzo-Töpfe. Glückwunsch! Sie arbeiten jetzt mit der Monzo-API anstelle von YNAB mit nur 6 Zeilen zusätzlichem Code.
Share:
)
Michael ist ein polyglotter Software-Ingenieur, der sich dafür einsetzt, die Komplexität von Systemen zu reduzieren und sie berechenbarer zu machen. Er arbeitet mit einer Vielzahl von Sprachen und Tools und gibt sein technisches Fachwissen auf Benutzergruppen und Konferenzen in der ganzen Welt weiter. Im Alltag ist Michael ein ehemaliger Developer Advocate bei Vonage, wo er seine Zeit damit verbrachte, über alle Arten von Technologie zu lernen, zu lehren und zu schreiben.