I have a public SPA website (written in Svelte), with no user authentication.
I'd like to use the Azure Text-to-speech service (as either REST Api or with the azure-cognitive-services npm package).
I now need to provide the api key to use the service... But I understand it is a bad practice to store that key in the client code.
What would be the way to use that service then? Do I really need a backend service? Do I need to wrap my text-to-speech code in something like an Azure Function?
Thanks
If you look at the Source Code of the microsoft-cognitiveservices-speech-sdk NPM package it is clear the only way to auth to the service is by using some form of token/id which you will have to store at client side.
Instead of that you can use an azure function which will take text and convert to text .
My index,js file in azure function .
var t = require('./test');
var fs = require('fs');
module.exports = async function (context, req) {
const name = (req.query.name || (req.body && req.body.name));
t(name);
context.res = {
body:""
}
}
The main processing will be in the file test.js
function test (text) {
var sdk = require("microsoft-cognitiveservices-speech-sdk");
var readline = require("readline");
// This will be the name of the file created later
var audioFile = "YourAudioFile.wav";
const speechConfig = sdk.SpeechConfig.fromSubscription(process.env.SPEECH_KEY, process.env.SPEECH_REGION);
const audioConfig = sdk.AudioConfig.fromAudioFileOutput(audioFile);
speechConfig.speechSynthesisVoiceName = "en-US-JennyNeural";
var synthesizer = new sdk.SpeechSynthesizer(speechConfig, audioConfig);
synthesizer.speakTextAsync(text,
function (result) {
if (result.reason === sdk.ResultReason.SynthesizingAudioCompleted) {
console.log("synthesis finished.");
} else {
console.error("Speech synthesis canceled, " + result.errorDetails +
"\nDid you set the speech resource key and region values?");
}
synthesizer.close();
synthesizer = null;
},
function (err) {
console.trace("err - " + err);
synthesizer.close();
synthesizer = null;
});
}
module.exports = test ;
Majority of code from above is from the MSDOC
At the end all you have to do is return the file created by the synthesizer.
Related
I want to store the conversation data to the storage account or cosmos DB. By trying this https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-howto-v4-storage?view=azure-bot-service-4.0&tabs=javascript#using-blob-storage
I am able to send the utteranceslog into blob storage. But I want to store end-to-end conversation data which includes data of both users as well as bot responses using javascript.
I tried using saving user state and conversation state but didn't achieve the desired output.
I created a custom logger (based on an old botduilder-samples sample that isn't there anymore) that accomplishes this using TranscriptLoggerMiddleware. I chose CosmosDB instead of Blob Storage because I felt it was easier to store (and retrieve) as a JSON document. But you could tweak this concept to use any DB. Here is what I did.
First, create your custom logger code. As mentioned, I used CosmosDB so you might have to change some things if you're using a different DB. The timing of the activities was creating concurrency issues, so instead of working around that, I'm storing the transcript object locally and overwriting the DB object on each turn. Maybe not the most elegant, but it works. Also, I've found my wait function to be required. Otherwise you only get one side of the conversation. I've been told this type of wait function is not a best practice, but awaiting a promise or other methods of creating a delay did not work for me. Here is the code:
customerLogger.js
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { CosmosDbStorage } = require('botbuilder-azure');
const path = require('path');
/**
* CustomLogger, takes in an activity and saves it for the duration of the conversation, writing to an emulator compatible transcript file in the transcriptsPath folder.
*/
class CustomLogger {
/**
* Log an activity to the log file.
* #param activity Activity being logged.
*/
// Set up Cosmos Storage
constructor(appInsightsClient) {
this.transcriptStorage = new CosmosDbStorage({
serviceEndpoint: process.env.COSMOS_SERVICE_ENDPOINT,
authKey: process.env.COSMOS_AUTH_KEY,
databaseId: process.env.DATABASE,
collectionId: 'bot-transcripts'
});
this.conversationLogger = {};
this.appInsightsClient = appInsightsClient;
this.msDelay = 250;
}
async logActivity(activity) {
if (!activity) {
throw new Error('Activity is required.');
}
// Log only if this is type message
if (activity.type === 'message') {
if (activity.attachments) {
var logTextDb = `${activity.from.name}: ${activity.attachments[0].content.text}`;
} else {
var logTextDb = `${activity.from.name}: ${activity.text}`;
}
if (activity.conversation) {
var id = activity.conversation.id;
if (id.indexOf('|') !== -1) {
id = activity.conversation.id.replace(/\|.*/, '');
}
// Get today's date for datestamp
var currentDate = new Date();
var day = currentDate.getDate();
var month = currentDate.getMonth()+1;
var year = currentDate.getFullYear();
var datestamp = year + '-' + month + '-' + day;
var fileName = `${datestamp}_${id}`;
var timestamp = Math.floor(Date.now()/1);
// CosmosDB logging (JK)
if (!(fileName in this.conversationLogger)) {
this.conversationLogger[fileName] = {};
this.conversationLogger[fileName]['botName'] = process.env.BOTNAME;
}
this.conversationLogger[fileName][timestamp] = logTextDb;
let updateObj = {
[fileName]:{
...this.conversationLogger[fileName]
}
}
// Add delay to ensure messages logged sequentially
await this.wait(this.msDelay);
try {
let result = await this.transcriptStorage.write(updateObj);
} catch(err) {
this.appInsightsClient.trackTrace({message: `Logger ${err.name} - ${path.basename(__filename)}`,severity: 3,properties: {'botName': process.env.BOTNAME, 'error':err.message,'callStack':err.stack}});
}
}
}
}
async wait(milliseconds) {
var start = new Date().getTime();
for (var i = 0; i < 1e7; i++) {
if ((new Date().getTime() - start) > milliseconds) {
break;
}
}
}
}
exports.CustomLogger = CustomLogger;
Now you need to attach this to the botframework adapter in your index.js file. The relevant pieces of code are:
index.js
const { TranscriptLoggerMiddleware } = require('botbuilder');
const { CustomLogger } = require('./helpers/CustomLogger');
//
//Your code to create your adapter, etc.
//
const transcriptLogger = new TranscriptLoggerMiddleware(new CustomLogger(appInsightsClient));
adapter.use(transcriptLogger);
I'm assuming here you already have your index.js file figured out, but if you need any assistance getting that set up and getting the transcript logger to work with it, just let me know.
EDIT: By request, here is what the object looks like in CosmosDB. Normally I would have the "from name" displayed, but because of the way I was testing the bot it came through "undefined".
{
"id": "2020-3-21_IfHK46rZV42KH5g3dIUgKu-j",
"realId": "2020-3-21_IfHK46rZV42KH5g3dIUgKu-j",
"document": {
"botName": "itInnovationBot",
"1584797671549": "Innovation Bot: Hi! I'm the IT Innovation Bot. I can answer questions about the innovation team and capture your innovation ideas. Let me know how I can help!",
"1584797692355": "undefined: Hello",
"1584797692623": "Innovation Bot: Hello.",
"1584797725223": "undefined: Tell me about my team",
"1584797725490": "Innovation Bot: The innovation team is responsible for investigating, incubating, and launching new technologies and applications. The innovation focus areas are:\n\n* Chatbots\n\n* Augmented Reality/Virtual Reality\n\n* Blockchain\n\n* Robotic Process Automation\n\n* AI & Machine Learning\n\nLet me know if you want to learn more about any of these technologies!",
"1584797746279": "undefined: Thanks",
"1584797746531": "Innovation Bot: You're welcome."
},
"_rid": "OsYpALLrTn2TAwAAAAAAAA==",
"_self": "dbs/OsYpAA==/colls/OsYpALLrTn0=/docs/OsYpALLrTn2TAwAAAAAAAA==/",
"_etag": "\"a4008d12-0000-0300-0000-5e7618330000\"",
"_attachments": "attachments/",
"_ts": 1584797747
}
To read the conversation back (even if still in the middle of the conversation), you just create a connector in your bot, recreate the key, and read the file as below (in this case id is passed into my function and is the conversation id):
const transcriptStorage = new CosmosDbStorage({
serviceEndpoint: process.env.COSMOS_SERVICE_ENDPOINT,
authKey: process.env.COSMOS_AUTH_KEY,
databaseId: process.env.DATABASE,
collectionId: 'bot-transcripts',
partitionKey: process.env.BOTNAME
});
// Get today's date for datestamp
var currentDate = new Date();
var day = currentDate.getDate();
var month = currentDate.getMonth()+1;
var year = currentDate.getFullYear();
var datestamp = year + '-' + month + '-' + day;
var filename = `${datestamp}_${id}`;
var transcript = await transcriptStorage.read([filename]);
I'm building a Firebase Admin SDK local nodejs server to which I'm trying to add recursive delete functionality. The examples I've been able to find, such as:
https://firebase.google.com/docs/firestore/solutions/delete-collections
use firebase-functions and appear to require an auth token, which all seems applicable when using the Firebase SDK rather than the Firebase Admin SDK.
Here is my code, which I know isn't the way it can be done, but it hopefully conveys what I am attempting to do. Any leads would be greatly appreciated.
const firebaseTools = require('firebase-tools');
async function recursiveDocumentDelete(fsDb, docRefPath) {
const collRefPath = extractCollRefPath(docRefPath);
const docId = extractDocId(docRefPath);
try {
const docToBeDeleted = await fsDb.collection(collRefPath).doc(docId).get();
if (docToBeDeleted.exists) {
recursiveDeleteFn = firebaseTools.functions().httpsCallable('recursiveDelete');
const recursiveDeleteResponse = await recursiveDeleteFn({ path: docRefPath })
console.log(`Document with ID ${docId} in the ${collRefPath} collection/subcollection, and all of its decendants, were successfully deleted at ${recursiveDeleteResponse.writeTime.toDate()}!`);
return JSON.stringify(recursiveDeleteResponse);
} else {
console.log(`Document with ID ${docId} in the ${collRefPath} collection/subcollection was not found`);
}
} catch(err) {
console.log(err);
}
}
It seems I can't find a proper way to use the read/write functions for admin in the Cloud Functions. I am working on a messaging function that reads new messages created in the Realtime Database with Cloud Functions Node.js and uses the snapshot to reference a path. Here is my initial exports function:
var messageRef = functions.database.ref('Messages/{chatPushKey}/Messages/{pushKey}');
var messageText;
exports.newMessageCreated = messageRef.onCreate((dataSnapshot, context) => {
console.log("Exports function executed");
messageText = dataSnapshot.val().messageContent;
var chatRef = dataSnapshot.key;
var messengerUID = dataSnapshot.val().messengerUID;
return readChatRef(messengerUID, chatRef);
});
And here is the function that reads from the value returned:
function readChatRef(someUID, chatKey){
console.log("Step 2");
admin.database.enableLogging(true);
var db;
db = admin.database();
var userInfoRef = db.ref('Users/' + someUID + '/User Info');
return userInfoRef.on('value', function(snap){
return console.log(snap.val().firstName);
});
}
In the firebase cloud functions log I can read all console.logs except for the one inside return userInfoRef.on.... Is my syntax incorrect? I have attempted several other variations for reading the snap. Perhaps I am not using callbacks efficiently? I know for a fact that my service account key and admin features are up to date.
If there is another direction I need to be focusing on please let me know.
Here is my code snippet
var sendgrid = require('sendgrid')('xxxxxx', 'xxxxxx');
var email = new sendgrid.Email();
email.addTo('xyz#gmail.com');
email.setFrom('xyz#gmail.com');
email.setSubject('welcome to send grid');
email.setHtml('<html><body>HELLO evryone ...,</body></html>');
sendgrid.send(email, function(err, json) {
if(!err)
{
console.log("mail sent successssss");
res.send({"status":0,"msg":"failure","result":"Mail sent successfully"});
}
else
{
console.log("error while sending mail")
res.send({"status":1,"msg":"failure","result":"Error while sending mail."});
}
});
Installed sendgrid throgh npm also.am getting "TypeError: object is not a function" error.MAy i know why.??
Version:--
sendgrid#3.0.8 node_modules\sendgrid
└── sendgrid-rest#2.2.1
It looks like you're using sendgrid#3.0.8 but trying to call on the sendgrid#2.* api.
v2 implementation: https://sendgrid.com/docs/Integrate/Code_Examples/v2_Mail/nodejs.html
v3 implementation:
https://sendgrid.com/docs/Integrate/Code_Examples/v3_Mail/nodejs.html
Give the v3 a go.
As for the type error:
v2
var sendgrid = require("sendgrid")("SENDGRID_APIKEY");
you're invoking a function
however you have v3 installed
require('sendgrid').SendGrid(process.env.SENDGRID_API_KEY)
and it's now an object
REQUESTED UPDATE:
I don't know too much about the keys given, but since they have tons of different supported libraries, it's completely possible that some of them use both while others use only one. If you really only have a USER_API_KEY nad PASSWORD_API_KEY, just use the user_api_key
Here is their source for the nodejs implementation module SendGrid:
function SendGrid (apiKey, host, globalHeaders) {
var Client = require('sendgrid-rest').Client
var globalRequest = JSON.parse(JSON.stringify(require('sendgrid-rest').emptyRequest));
globalRequest.host = host || "api.sendgrid.com";
globalRequest.headers['Authorization'] = 'Bearer '.concat(apiKey)
globalRequest.headers['User-Agent'] = 'sendgrid/' + package_json.version + ';nodejs'
globalRequest.headers['Accept'] = 'application/json'
if (globalHeaders) {
for (var obj in globalHeaders) {
for (var key in globalHeaders[obj] ) {
globalRequest.headers[key] = globalHeaders[obj][key]
}
}
}
The apiKey is attached to the header as an auth, and it looks like that's all you need.
Try following their install steps, without your own implementation,
1) (OPTIONAL) Update the development environment with your SENDGRID_API_KEY, for example:
echo "export SENDGRID_API_KEY='YOUR_API_KEY'" > sendgrid.env
echo "sendgrid.env" >> .gitignore
source ./sendgrid.env
========
2) Make this class and if you did the above use process.env.SENDGRID_API_KEY else put your USER_API_KEY
var helper = require('sendgrid').mail
from_email = new helper.Email("test#example.com")
to_email = new helper.Email("test#example.com")
subject = "Hello World from the SendGrid Node.js Library!"
content = new helper.Content("text/plain", "Hello, Email!")
mail = new helper.Mail(from_email, subject, to_email, content)
//process.env.SENDGRID_API_KEY if above is done
//else just use USER_API_KEY as is
var sg = require('sendgrid').SendGrid(process.env.SENDGRID_API_KEY)
var requestBody = mail.toJSON()
var request = sg.emptyRequest()
request.method = 'POST'
request.path = '/v3/mail/send'
request.body = requestBody
sg.API(request, function (response) {
console.log(response.statusCode)
console.log(response.body)
console.log(response.headers)
})
I've been trying to retrieve the Resource with the path "/" (the root) from AWS Api Gateway using the Nodejs AWS SDK. I know the naïve solution would be to do it this way:
var AWS = require('aws-sdk');
var __ = require('lodash');
var Promise = require('bluebird');
var resources = [];
var apiGateway = Promise.promisifyAll(new AWS.APIGateway({apiVersion: '2015-07-09', region: 'us-west-2'}));
var _finishRetrievingResources = function (resources) {
var orderedResources = __.sortBy(resources, function (res) {
return res.path.split('/').length;
});
var firstResource = orderedResources[0];
};
var _retrieveNextPage = function (resp) {
resources = resources.concat(resp.data.items);
if (resp.hasNextPage()) {
resp.nextPage().on('success', _retrieveNextPage).send();
} else {
_finishRetrievingResources(resources);
}
};
var foo = apiGateway.getResources({restApiId: 'mah_rest_api_id'}).on('success', _retrieveNextPage).send();
However, does anybody know of an alternate method? I'd prefer to know that I'll alway have to do at most one call than having to do multiple.
PS: I know there are several optimizations that could be made (e.g. check for root path on every response), I really want to know if there's a single SDK Call that could fix this.
There is not a single call, though it can be if you have less than 500 resources. As a consolation prize, this is the best-practice, using position to prevent accidental misses if there are over 500 resources. If there are less than 500 resources, this will work with one call:
https://github.com/andrew-templeton/cfn-api-gateway-restapi/blob/bd964408bcb4bc6fc8ec91b5e1f0387c8f11691a/index.js#L77-L102