NodeJS botbuilder SDKv4 - node.js

How can I store the values of the user input? For example I've created this dialog and I want to create an email and in the same time store them in a DB, but I am not sure where to add the functions.
Thanks
Constructor:
constructor(conversationState) {
this.dialogStateAccessor = conversationState.createProperty(DIALOG_STATE_ACCESSOR);
this.holidayAccessor = conversationState.createProperty(HOLIDAY_ACCESSOR);
this.conversationState = conversationState;
this.dialogSet = new DialogSet(this.dialogStateAccessor);
this.dialogSet.add(new ChoicePrompt(MONTH_PROMPT));
this.dialogSet.add(new ChoicePrompt(START_DATE_PROMPT));
this.dialogSet.add(new ChoicePrompt(END_DATE_PROMPT));
this.dialogSet.add(new WaterfallDialog(HOLIDAY_DIALOG, [
this.promptForMonth.bind(this),
this.promptForstartDate.bind(this),
this.promptForendDate.bind(this),
]));
}
TurnContext:
case ActivityTypes.Message:
const holiday = await this.holidayAccessor.get(turnContext, null);
const dc = await this.dialogSet.createContext(turnContext);
if (!dc.activeDialog) {
if (!holiday) {
await dc.beginDialog(HOLIDAY_DIALOG);
}
else {
await turnContext.sendActivity(
`An email was sent to your manager for approval`);
}
}

To start, you need to create and pass the userState store in your index.js file first.
const { ConversationState, MemoryStorage, UserState } = require('botbuilder');
[...]
const conversationState = new ConversationState(memoryStorage);
const memoryStorage = new MemoryStorage();
const userState = new UserState(memoryStorage);
[...]
const bot = new ExampleBot(conversationState, userState);
In your bot.js file, include and instantiate userState and assign a user profile:
class ExampleBot {
constructor(conversationState, userState) {
[...]
const USER_PROFILE = 'userProfile';
this.userProfile = userState.createProperty(USER_PROFILE);
this.userState = userState;
[...]
}
Now you can access the userState. You can do so as part of the OnTurn:
async onTurn(turnContext) {
if (turnContext.activity.type === ActivityTypes.Message) {
const userProfile = await this.userProfile.get(turnContext, {});
const conversationData = await this.conversationData.get(
turnContext, { promptedForUserName: false });
if (!userProfile.name) {
if (conversationData.promptedForUserName) {
userProfile.name = turnContext.activity.text;
await turnContext.sendActivity(`Thanks ${userProfile.name}.`);
conversationData.promptedForUserName = false;
} else {
await turnContext.sendActivity('What is your name?');
conversationData.promptedForUserName = true;
}
await this.userProfile.set(turnContext, userProfile);
await this.userState.saveChanges(turnContext);
}
}
}
Or as part of the waterfall / step:
async someWaterfallStep(step) {
const user = await this.userProfile.get(step.context, {});
if (!user.name) {
[do something]
} else {
await step.context.sendActivity(`Hello ${user.name}. Nice to meet you!`);
return step.endDialog()
}
}
You can read more about setting user state in this doc.
Hope of help!

Related

MongoError: Document must be a valid JavaScript object

I have a problem where MongoDB says that my object is not a valid JavaScript Object, Even though it is! This has been staying for days!
Basically, this is an account system that uses MongoDB's client, and the ObjectId for the ID.
I want to be able to fix the MongoError that says object (sent to updateOne, not filter) is not a valid JavaScript object.
Here is the code:
const { MongoClient, ObjectId } = require("mongodb");
const fs = require("node:fs");
const uri = "mongodb://127.0.0.1:27017";
if (!fs.existsSync("./db")) {fs.mkdirSync("./db")};
const client = new MongoClient(uri,{ useUnifiedTopology: true });
async function conn() {
await client.connect();
}
conn();
const database = client.db("login");
const accs = database.collection("accounts");
const myfil = {
_id: new ObjectId('63b6441832087ccc7e3edea2')
};
const users = accs.findOne(myfil);
const path = require("node:path");
const bcrypt = require('bcrypt');
const env = process.env;
var saltRounds = 10;
const AddSet = class AddSet {
constructor(user,pass) {
console.log(pass);
this.set = {[user]:pass};
this.set = Object.keys(this.set).reduce((acc, key) => {
acc[key.toString()] = this.set[key];
return acc;
}, {});
console.log(this.set);
return this.set;
}
}
const Account = class Account {
constructor(user,password) {
conn();
if (!users[user]) {
conn();
accs.updateOne(myfil,bcrypt.hash(password, saltRounds, function(err, hash)
{
try {
var a = ""+user;
return new AddSet(a.toString(),hash);
} catch(err) {
console.error("bcrypt",err);
}
}));
this.assetDir = path.join(path.join(env.SAVED_FOLDER,"/"+this.user),"/assets");
this.metaDir = this.assetDir + '/meta';
this.starterDir = path.join(path.join(env.SAVED_FOLDER,"/"+this.user),"/starters");
this.videoDir = path.join(path.join(env.SAVED_FOLDER,"/"+this.user),"/videos");
var fs = require('fs');
if (!fs.existsSync(this.assetDir)) fs.mkdirSync(this.assetDir, { recursive: true });
if (!fs.existsSync(this.starterDir)) fs.mkdirSync(this.assetDir, { recursive: true });
if (!fs.existsSync(this.videoDir)) fs.mkdirSync(this.assetDir, { recursive: true });
}
}
getAssetDir() {
return this.assetDir;
}
getStarterDir() {
return this.starterDir;
}
getVideoDir() {
return this.videoDir;
}
getMetaDir() {
return this.metaDir;
}
checkSession(pswd) {
conn();
bcrypt.compare(pswd, users[this.user], function(err, result) {
if (result) return true;
else return false;
});
}
}
module.exports = { Account, users };
I tried fixing it, making the keys strings, removing the $set, and it did not work.

TypeError: Cannot read properties of undefined (reading 'send')

I am getting the following error using discord.js V14 :
TypeError: Cannot read properties of undefined (reading 'send')
Thank you to anyone who can help me.
const { Client, Message, MessageEmbed } = require("discord.js");
const {SlashCommandBuilder} = require('discord.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('test')
.setDescription('this is a test thing'),
/**
* #parom {Client} client
* #parom {Message} message
* #parom {String[]} args
*/
async execute(client, message, args, interaction) {
const questions = [
"test",
"test1?"
];
let collectCounter = 0;
let endCounter = 0;
const filter = (m) => m.author.id === message.author.id;
const author = message.author;
const appStart = await author.send(questions[collectCounter++]);
const channel = appStart.channel;
const collector = channel.createMessageCollector(filter);
message.delete({timeout: 100})
collector.on("collect", () => {
if (collectCounter < questions.length) {
channel.send(questions[collectCounter++]);
} else {
channel.send("submited!");
collector.stop("fulfilled");
}
});
const appsChannel = client.channels.cache.get("976110575547482152");
collector.on("end", (collected, reason) =>{
if (reason === "fulfilled") {
let index = 1;
const mappedResponses = collected
.map((msg) => {
return `${index++}) ${questions[endCounter++]}\n-> ${msg.content}`;
})
.join("\n\n");
appsChannel.send(
new MessageEmbed()
.setAuthor(message.author.tag, message.author.displayAvatarURL({ dynamic: true}))
.setTitle("!New Report!")
.setDescription(mappedResponses)
.setColor(`RANDOM`)
.setTimestamp()
);
appsChannel.send(`<#1057374161846145076>`);
}
});
},
};
Note I was using this before and once I updated to V14 I started getting this error.
it should message the user the set questions then post the answers in a channel.
EDIT: I am getting the error on this line
const appStart = await author.send(questions[collectCounter++]);
My interactions.js file
module.exports = {
name: "interactionCreate",
async execute(interaction, client) {
if (interaction.isChatInputCommand()) {
const {commands} = client;
const {commandName} = interaction;
const command = commands.get(commandName);
if (!command) return;
try {
await command.execute(interaction, client);
} catch (error) {
console.error(error);
await interaction.reply({
content: `Something went wrong while executing this command...`,
ephemeral: true,
});
}
} else if (interaction.isButton()) {
const {buttons} = client;
const {customId} = interaction;
const button = buttons.get(customId);
if (!button) return new Error('This Button Does not exist');
try{
await button.execute(interaction, client);
} catch (err) {
console.error(err);
}
}
},
};
You're calling the execute method of your /test command file with (interaction, client) but the method expects (client, message, args, interaction).
A possible solution would be to change the /test command file to
// Optimized imports
const { Client, MessageEmbed, BaseInteraction,
SlashCommandBuilder } = require("discord.js");
module.exports = {
data: new SlashCommandBuilder()
.setName('test')
.setDescription('this is a test thing'),
// Fixed typo in #param
/**
* #param {Client} client
* #param {BaseInteraction} interaction
*/
async execute(interaction, client) {
const questions = [
"test",
"test1?"
];
let collectCounter = 0;
let endCounter = 0;
// Get "Author" from interaction
const author = interaction.user;
const filter = (m) => m.author.id === author.id;
const appStart = await author.send(questions[collectCounter++]);
const channel = appStart.channel;
const collector = channel.createMessageCollector(filter);
// Removed message.delete() because interactions aren't messages and cant be deleted.
collector.on("collect", () => {
if (collectCounter < questions.length) {
channel.send(questions[collectCounter++]);
} else {
channel.send("submited!");
collector.stop("fulfilled");
}
});
const appsChannel = client.channels.cache.get("976110575547482152");
collector.on("end", (collected, reason) => {
if (reason === "fulfilled") {
let index = 1;
const mappedResponses = collected
.map((msg) => {
return `${index++}) ${questions[endCounter++]}\n-> ${msg.content}`;
})
.join("\n\n");
appsChannel.send(
new MessageEmbed()
.setAuthor(message.author.tag, message.author.displayAvatarURL({ dynamic: true }))
.setTitle("!New Report!")
.setDescription(mappedResponses)
.setColor(`RANDOM`)
.setTimestamp()
);
appsChannel.send(`<#1057374161846145076>`);
}
});
},
};

switching bot.on listeners for different modules

I'm creating the telegram bot, and I have question.
In app.js
const { admin } = require('./admin');
require('dotenv').config();
const token = process.env.token;
const bot = new TelegramBot(token, { polling: true });
let chatId = [];
async function startApp() {
let restTime = await moment.tz('America/Chicago').format('hh:mm A');
let weekend = await moment.tz('America/Chicago').day();
console.log("start bot " + restTime);
const nowHours = new Date().getHours()
const restMessage = {
text: process.env.restText
}
const weekendMessage = {
text: process.env.weekendText
}
function startBot() {
return bot.on('message', async(msg) => {
if (msg.text === '/admin'){
return admin(msg);
}
...
and I have:
admin.js:
require('dotenv').config();
const pass = process.env.passwordAdmin
function admin(bot,msg) {
bot.sendMessage(msg.from.id, 'Enter admin password')
bot.onText(`${pass}`,()=>{
bot.sendMessage(msg.from.id, "Good")
})
}
module.exports = {admin}
error: [polling_error] {}
How I can pause the bot.on listener in app.js when I start bot.onText listener or other listener, and then, when admin end they work, return to app.js listener?
Me helps my friend,
const pass = new RegExp(process.env.passwordAdmin)
...
bot.onText (pass, () => {
but listener bot.on('message'..) is still working.

How to mock redis client in nodejs using jest

I have tried to mock the redisClient.js using redis-mock using jest. But I couldn't find the solution for it. please give me a code sample for it. I need to mock it in controller.
redisClient.js
const redis = require('redis');
const asyncRedis = require("async-redis");
//Redis
const connection = redis.createClient(process.env.REDIS_PORT,
{
retry_strategy: function(options) {
if (options.error && options.error.code === "ECONNREFUSED") {
// End reconnecting on a specific error and flush all commands with
// a individual error
return new Error("The server refused the connection");
}
if (options.total_retry_time > 1000 * 60 * 60) {
// End reconnecting after a specific timeout and flush all commands
// with a individual error
return new Error("Retry time exhausted");
}
if (options.attempt > 10) {
// End reconnecting with built in error
return undefined;
}
// reconnect after
return Math.min(options.attempt * 100, 3000);
},
}
);
module.exports = asyncRedis.decorate(connection);
Controller
const logger = require('../../helper/logger');
const response = require("../../config/response");
const constant = require('../../config/constant');
const QuizService = require('../../services/quiz/quizService');
class QuizController {
constructor() {
this.quizService = new QuizService();
}
async getQuiz(req, res) {
const { userId, query: { campaignId } } = req;
try {
const question = await this.quizService.getQuestion(userId, campaignId);
res.send(response.res(true, constant.MSG.Quiz_FETCHED, question));
} catch (error) {
res.status(constant.RESPONSE.INTERNAL_ERROR.CODE)
.send(response.res(false, error.message, null, error.code))
}
}
}
Service
const _ = require('lodash');
const moment = require('moment');
const { Op } = require('sequelize');
const { v4: uuidv4 } = require("uuid");
const shuffle = require('shuffle-array');
const serialize = require("serialize-javascript");
const utill = require("../../helper/util");
const redis = require("../../cache/redisClient");
const constant = require('../../config/constant');
const scoreHelper = require('./../../helper/scoreHelper');
const db = require("../../models");
const Quiz = db.quiz;
const Campaign = db.campaign;
const campaign = require('../campaign/campaignService')
const SubscriberAnswer = require('../subscriberAnswer/subscriberAnswerService')
const SubscriberProgram = require('../subscriberProgram/SubsciberProgramService')
class quizService {
constructor() {
this.subscriberAnswer = new SubscriberAnswer()
this.subscriberProgram = new SubscriberProgram()
this.campaign = new campaign()
}
async getQuestion(userId, campaignId) {
const subscribedProgramData = await this._checkAvailableQuestionLimit(userId, campaignId)
if(!subscribedProgramData){
throw { message: constant.MSG.TRY_AGAIN }
}
if (subscribedProgramData.no_of_questions > 0) {
const question = await Quiz.findQuestion(userId, campaignId);
if (question.length) {
const data = {
subscriber_id: userId,
campaign_id: campaignId,
questions_id: question[0].id
}
const updateData = {
id: subscribedProgramData.id,
no_of_questions: (subscribedProgramData.no_of_questions - 1)
}
await this.subscriberAnswer.create(data);
await this.subscriberProgram.updateQuota(updateData);
const id = uuidv4();
const {answer, ...questionData } = question[0];
const responseData = await this.handleQuestionData(id, userId, campaignId, questionData, answer);
return responseData;
} else {
throw { code:constant.RESPONSE_COEDES.ALL_ANSWERED, message: constant.MSG.ANSWER_ALL }
}
} else {
throw { message: constant.MSG.QUOTA_OVER }
}
}
}
My Unit Testing Code
const QuizService = require("../../src/services/quiz/quizService");
const QuizController = require("../../src/controllers/quiz/quizController");
const quizService = new QuizService();
const quizController = new QuizController();
const httpMocks = require("node-mocks-http");
jest.mock("../../src/helper/logger");
jest.mock("../../src/cache/redisClient.js");
beforeEach(() => {
req = httpMocks.createRequest();
res = httpMocks.createResponse();
next = jest.fn();
jest.resetAllMocks();
quizService.getQuestion = jest.fn();
});
quizService.getQuestion = jest.fn();
const response = {
id: 1,
name: 'Sandun',
msisdn: '94704377575',
otp: '1234',
deleted: 0,
attempts: 0,
img_url: 'https://'
}
// This test shows how the constructor can be mocked, and how to spy on passed parameters.
describe("Test QuizController", () => {
afterEach(() => {
jest.resetAllMocks();
});
//Because getQuestion is prototype method
it("Test - GetQuiz - Success", async () => {
req.query.programId = 1;
req.userId = 1;
jest.spyOn(QuizService.prototype, "getQuestion").mockReturnValue(response);
await quizController.getQuiz(req, res);
expect(res.statusCode).toBe(200);
});
});
ERROR
FAIL test/controllers/quiz.controller.test.js
● Test suite failed to run
TypeError: Cannot read property 'startsWith' of undefined
//Redis
const connection = redis.createClient(process.env.REDIS_PORT,
^
{
retry_strategy: function(options) {
if (options.error && options.error.code === "ECONNREFUSED") {
at normalizeUrl (node_modules/redis-mock/lib/utils/parseRedisUrl.js:4:11)
at Object.<anonymous>.module.exports (node_modules/redis-mock/lib/utils/parseRedisUrl.js:61:34)
at generateUrlOptions (node_modules/redis-mock/lib/client/createClient.js:25:30)
at unifyOptions (node_modules/redis-mock/lib/client/createClient.js:61:10)
at Object.createClient (node_modules/redis-mock/lib/client/createClient.js:64:47)
at Object.<anonymous> (src/cache/redisClient.js:5:26)
at Object.<anonymous> (src/services/quiz/quizService.js:8:15)
at Object.<anonymous> (test/controllers/quiz.controller.test.js:1:21)

Session expiring in Dialogflow

I came to know that context expires in 15 minutes but is there any way to solve it manually i.e by storing the previous conversation in dB so can we handle that session expiring issue or else the whole conversation(output context) under that session ID will get clear and need to start from the first.
exports.fulfillmenttext = functions.https.onRequest((req,res) =>{
const answer1 = req.body.Text;
console.log("Text said by the user",answer1);
const uid = answer1.substring(0,28);
console.log("uid1 is",uid);
const answer = answer1.substring(28);
console.log("answer is",answer);
const sessionId = uid;
var count,questvalue;
runSample();
async function runSample(projectId = 'xxxxxxx') {
const languageCode = 'en-US';
const credentials = {
client_email: 'xxxxxxxxxx',
private_key: 'xxxxxxxxx'
};
//Instantiate a DialogFlow client.
const dialogflow = require('dialogflow');
const sessionClient = new dialogflow.SessionsClient({
projectId,
credentials,
});
// Define session path
const sessionPath = sessionClient.sessionPath(projectId, sessionId);
// The text query request.
const request = {
session: sessionPath,
queryInput: {
text: {
text: answer,
languageCode,
},
},
};
const responses = await sessionClient.detectIntent(request);
console.log('Detected intent');
const result = responses[0].queryResult;
let action = result.action;
console.log("action is"+action);
console.log(` Query: ${result.queryText}`);
console.log(` Response: ${result.fulfillmentText}`);
if (result.intent) {
const question = result.fulfillmentText;
console.log("question is",question);
const actionHandlers = {
'early': () => {
console.log('earlyaction1', action);
let name1 = JSON.stringify(result.parameters.fields.Name.stringValue);
name1 = name1.toString().replace(/"/g,"");
var data1 = {
Name: name1
};
var setDoc1 = admin.firestore().collection('User').doc(uid).collection("Popop").doc(uid).collection('Answers').doc('Earlyyears').update(data1);
},
'family': () => {
console.log('familyaction1', action);
let mname1 = JSON.stringify(result.parameters.fields.M_Name.stringValue);
let mname_string = mname1.toString().replace(/"/g,"");
var data20 = {
MName: mname_string
};
var setDoc20 = admin.firestore().collection('User').doc(uid).collection("Popop").doc(uid).collection('Answers').doc('Family').update(data20);
}
};
if (action === 'early') {
console.log('1');
actionHandlers[action]();
}
else if (action === 'family') {
console.log('2');
actionHandlers[action]();
}
res.status(200).send({"question":result.fulfillmentText,"action":action});
} else {
console.log(` No intent matched.`);
res.status(400).send({"action":"empty"});
}
}
});
I stumbled upon this problem as well. My solution was to save the userID and save the contexts to Firestore.
UPDATE:
This is how I stored Dialogflow's contexts in Firestore:
function saveContexts(userId, contexts) {
let UID = userId;
//get all contexts + parameters
if (contexts === undefined) {
console.log("contexts are undefined! returning");
return false;
}
db.collection("user-contexts-prod").doc(UID).set({
dateCreated: new Date(),
contexts: JSON.stringify(contexts)
})
.then(function () {
console.log("success!");
return true;
})
.catch(function (error) {
console.log("error writing document..", error);
return false;
});
}
Retrieving user contexts:
async function getContexts(userId) {
let UID = userId;
let docRef = db.collection("user-contexts-prod").doc(UID);
return docRef.get()
.then(res => {
if (res.exists) {
let contexts = JSON.parse(res.data().contexts);
console.log("<><> parsed contexts <><>: ");
console.log(contexts);
return contexts;
} else {
console.log(" UID DOES NOT EXIST!");
return false;
}
})
}
You can set the contexts again by looping over them and using the contextClient to create new contexts. Or use this method to loop through the contexts and find the one you need:
contexts.forEach(function(context) {
if (context.name === 'projects/{DIALOGFLOWPROJECTID}/agent/sessions/' + senderId + '/contexts/{CONTEXTNAME}') {
sessionData = context.parameters;
// all data that you saved in CONTEXTNAME is now available in the sessionData variable
}
});
Original answer:
Whenever a user started talking that didn't have any active contexts I check if I had the userID stored in my Database. If this user existed in my DB I retrieved the user information with all his data like this:
knownUser = await db.isKnownUser(senderId);
if (knownUser) {
//knownUser
console.log("Known user");
let userData = db.getUserDataById(senderId)
//initialize contexts with data you need
payload = returningUser_useSameData();
messenger.send(payload, senderId);
dashbot.logBotMessage(payload.toString, sessionId, intentName);
break;
} else {
//newUser
console.log("new user");
createContext('await_fillInTogether', '', sessionPath, sessionId, 1);
createContext('session', '', sessionPath, sessionId, 500);
payload = fillInTogetherNewUser();
messenger.send(payload, senderId);
dashbot.logBotMessage(payload.toString, sessionId, intentName);
break;
}

Resources