Botframework TextPrompt how to send user input as postback - node.js

I built a bot that needs to ask the user for a password.
In my current implementation, the bot logic sends an event to the webchat-client to trigger the rendering of a password input field.
const askPwd =
{
name: 'passwordInput',
type: 'event'
};
await stepContext.context.sendActivity(askPwd);
return await stepContext.prompt(SSD_PASSWORD_PROMPT);
In the webchat client, I use activity middleware to render the password input field (and hide the regular input field).
const activityMiddleware = () => next => ({ activity, nextVisibleActivity, ...otherArgs }) => {
const { from: { role }, name, type } = activity;
if (type === 'event' && name === 'passwordInput'){ return () => <PasswordInputActivity activity={activity} nextVisibleActivity={nextVisibleActivity} />;
}}
This works but is confusing to the user. I would rather use the regular input field. I would be able to dynamically turn this into a password type field. The problem is that the input is indeed masked but the password is shown in the chat timeline (unmasked ofcourse).
Is there a way to use a text-prompt and send the input as postback to the bot-logic?

Related

Telegraf.js pass data from button to a function that handle a WizardScene

I need to pass some data retrieved from a Database.
When i click into a button i send a private message to the user who have cliecked. I need to pass some datas from that button, to the message sent. Because, into that message i have an other button that starts a WizardScene. SO how can i do to pass data? Thank you.
Here is my code.
This is into a function that post a photo with a description and with a callbackup button.
my function() {
...
let productId = //obtain from a db;
await bot.telegram.sendPhoto(
channel_id,
{ source: filepath },
{
caption: description.join("\n"),
parse_mode: 'MarkdownV2',
reply_markup: productButtons.reply_markup
}
)
return productId;
...}
and the button is:
const productButtons = Extra.markup((m) =>
m.inlineKeyboard([
[m.callbackButton('TEST', 'test_btn')],
])
)
when someone clicsk on it, it sends a message on a private user with this:
bot.action('testa_btn', (ctx) => {
console.log('testa')
let text = `My text about ${productId}`
ctx.telegram.sendMessage(ctx.from.id, o_functions.escapeReplace(text), {
parse_mode: 'MarkdownV2',
reply_markup: startBtn.reply_markup
})
});
this sends a text where i need to write my productid, and an other button where i start my wizard scene:
const startBtn = Extra.markup((m) =>
m.inlineKeyboard([
[m.callbackButton('START', 'start_btn_wizard')],
])
);
bot.action('start_btn_wizard', (ctx) => {
return ctx.scene.enter('super-wizard');
})
So how can i pass my productId variable, first to the button TEST, then to the wizard scene? I need to use it on the Wizard on the user dialogue.
THank you
You need to do a couple of things to pass data from a callback button to a handler and then towards a wizard scene.
Add the desired data to the button. Notice how I attached the product Id to the callback data.
const startBtn = Extra.markup((m) =>
m.inlineKeyboard([
[m.callbackButton('START', `start_btn_wizard_${product_id}`)],
])
);
Receive the button callback using a regex rather than using a literal string and extract the product ID from the callback data.
bot.action(/start_btn_wizard_+/, (ctx) => {
let product_id = ctx.match.input.substring(17);
// add all necessary validations for the product_id here
return ctx.scene.enter('super-wizard');
});
Pass the acquired id to the wizard scene and extract it again from inside the scene using ctx.scene.state.
bot.action(/start_btn_wizard_+/, (ctx) => {
let product_id = ctx.match.input.substring(17);
// add all necessary validations for the product_id here
return ctx.scene.enter('super-wizard', {product_id: product_id});
});
From inside the wizard, you can access the product_id using ctx.scene.state.product_id.
Hope this helps someone even though it's probably too late for the OP

Ignore multiple fields in unique validation rule in AdonisJs

I am trying to to update my user and applying Unique validator on email to prevent duplicates.
I need to ignore email uniqueness for provided user_id, and those records which are marked is_deleted to 1.
Only first statement works, if I place is_deleted,1 before id,${data.user_id} it works for deleted. but not for user_id.
get rules() {
const data = this.ctx.request.post()
console.log('current context: ', data.user_id)
return {
// email: `required|email|unique:users,email,id,${data.user_id},is_deleted,1`,
email: `required|email|unique:users,email,id,${data.user_id}|unique:users,email,is_deleted,1`,
phone_number: 'required|max:10',
status_id: 'required'
}
}
However, only first statement for ignore works, second one is not working
I would recommend extending the validation framework and add a custom rule (a good name would be unique_email). You will find it more productive and testable. The code would be similar to this:
const uniqueEmailFn = async (data, field, message, args, get) => {
const email = get(data, 'email')
const userId = get(data, 'user_id')
if (!email) {
/**
* skip validation if value is not defined. `required` rule
* should take care of it.
*/
return
}
const [table, column] = args
const row = await Database.table('users')
.where('id', userId)
.where('email', email)
.where('is_deleted', false)
.first()
if (row) {
throw 'The inputted e-mail is already taken'
}
}
Generally speaking, it's preferable to use the default rules for simple and generic validations, and business-specific ones can be added by extending the framework.

Assigning a default value to a required parameter in Dialogflow Fulfillment

TLDR; (title) How to assign a default value to a required parameter via Dialoflow Fulfillment?
Long question:
I have these 2 required parameters. user_id and user_pin.
I utilized Fulfillment webhook call for slot-filling and everything seems to work well except this behavior:
After an API call to the database to verify if user_id and user_pin matches and they do not match, Dialogflow chatbot responds with "Incorrect user pin please provide the correct pin."
User then provides another user_pin but Dialogflow triggers the slot-filling and asks for the user_id again. - "Please provide a user ID".
I don't want it to ask for the user_id again. So to fix that, after an incorrect user_pin is provided, I created a context: "incorrectPinContext" with parameters {user_pin:user_pin}. I then set the default values to:
user_id = #incorrectPinContext.user_id
This fixes the problem as Dialogflow is smart enough to know if user_id is already provided or not and no longer asks again for user_id. But all of the default value assignment I've done was only through the console / UX of Dialogflow not in fulfillment. I've browsed through Google's documentations but cant seem a reference on how to do that.
So that brings me to the question: How to assign a default value to a required parameter via Dialoflow Fulfillment?
Short answer, use a structure like
agent.setContext({
name: 'question2',
lifespan: 5,
parameters: {'name': name,'id': id},
});
const context = agent.getContext('question2');
const name = context.parameters.name
const id = context.parameters.id;
Long answer, let me show it to you with an example.
Assume you have a conversation where you are trying to extract the name and the id. This is done to mimic your two parameters required.
Then, the fulfillment structure has to be like
function welcome(agent){
agent.add(`Can you tell me your name and id?`);
agent.setContext({
name: 'question',
lifespan: 3,
});
}
function answers(agent){
agent.getContext('question');
const name = agent.parameters['given-name'];
const id = agent.parameters.id;
if (name && id) {
agent.add(`So your name is ${name} and your id ${id}`);
} else if (name) {
agent.add(`Hello ${name}, what was your id?`);
} else if (id) {
agent.add(`So your id is ${id} but what is your name?`);
} else {
agent.add(`Can you tell me your name and id?`);
}
agent.setContext({
name: 'question2',
lifespan: 5,
parameters: {'name': name,'cid': id},
});
}
function answers2(agent){
const cont = agent.getContext('question2');
const cname = cont.parameters.name;
const cid = cont.parameters.cid;
const nname = agent.parameters['given-name'];
const nid = agent.parameters.id;
if (cname){
if (cid){
agent.add(`So your name is ${cname} and your id ${cid}`);
} else if (nid){
agent.add(`So your name is ${cname} and your id ${nid}`);
} else {
agent.add(`Sorry still I do not know what is your id`);
}
} else if (cid){
if (cname){
agent.add(`So your name is ${cname} and your id ${cid}`);
} else if (nname){
agent.add(`So your name is ${nname} and your id ${cid}`);
} else {
agent.add(`Sorry still I do not know what is your name`);
}
} else {
agent.add(`I still need both your name and your id`);
}
}
Then, you can perform the following conversation:
Hello!-> Can you tell me your name and id?
Sure, my name is Alan -> Hello Alan, what was your id?
77678 -> So your name is Alan and your id 77678
You will need to modify the functions and the structure to fit your needs but I think this example is illustrative of how to pass values using context in fulfillment.
Notice that if you use the same variable name in two contexts, for example id, it would get changed with the new value and thus would not be remembered. That is why I used "cid".

Filter channels to only those that user is moderating

I am having a setup where I need to show channels based on whether id the user is an admin of the group or not. To flag user as admin, I promote the user as a moderator.
I have a view that I would need to show only the list of channels that the user is currently moderating. Is there a way to filter that?
One simple approach is to track this as part of channels' custom data (ie. {moderatorId}) and then use that attribute to match a query.
Track moderatorId on channel data
const channel = client.channel('messaging', 'travel', {
moderatorId: 42,
});
channel.update();
Retrieve moderated channels:
const filter = { moderatorId: 42 };
const sort = { last_message_at: -1 };
const options = {
state: true,
watch: true
};
const channels = await authClient.queryChannels(filter, sort, options);

Securing access to collection from the client's side

I have a meteor app prototype that works well, but is very insecure as of now: I needed to display a list of matching users to the currently logged-in user. For starters, I decided to publish all users, limiting the fields to what I would need to filter the user list on the client side.
Meteor.publish('users', function () {
return Meteor.users.find({}, {
fields: {
'profile.picture': 1,
'profile.likes': 1,
'profile.friends': 1,
'profile.type': 1
}
});
});
Then in my router, I would do a request to only show what I wanted on the client side:
Router.map(function() {
this.route('usersList', {
path: '/users',
waitOn: function () {
return Meteor.subscribe('users');
},
data: function () {
var user = Meteor.user();
return {
users: Meteor.users.find({ $and: [
{_id: {$ne : user._id}},
{'profile.type': user.profile.interest}
]})
};
}
});
});
In the code above, I query all users who are not the current user and whose type correspond the current user's interest. I also display a certain border on the photos of users who have my user in their "profile.friends" array, using this client helper:
Template.userItem.helpers({
getClass: function(userId) {
var user = Meteor.user();
var lookedup = Meteor.users.findOne({_id: userId});
if ($.inArray(user._id, lookedup.profile.friends) !== -1)
return "yes";
return "no";
}
});
Now this all worked great, but with this setup, every client can query every users and get their type, picture, list of friends and number of likes. If I was in an MVC, this info would only be accessible on server side. So I decided my next iteration to be a security one. I would move my query from the router file to the publications file. That's where trouble began...
Meteor.publish('users', function () {
var user = Meteor.users.findOne({_id: this.userId});
var interest = user.profile.interest;
// retrieve all users, with their friends for now
allUsers = Meteor.users.find({ $and: [
{'_id': {$ne: user._id}},
{'profile.type':interest}
]},
{ fields: {'profile.picture': 1, 'profile.friends': 1}}
);
return allUsers;
});
And in the router:
Router.map(function() {
this.route('usersList', {
path: '/users',
waitOn: function () {
return Meteor.subscribe('users');
},
data: function () {
var user = Meteor.user();
return {users: Meteor.users.find({_id: {$ne : user._id}})};
}
});
});
(note that I still need to exclude the current user from the router query since the current user is always fully published)
This works, but:
the user list does not get updated when I change the user interest and then do a Router.go('usersList'). Only when I refresh the browser, my list is updated according to the user's new interest. No idea why.
this solution still publishes the users' friends in order to display my matching borders. I wish to add a temporary field in my publish query, setting it to "yes" if the user is in the user's friends and "no" otherwise, but... no success so far. I read I could use aggregate to maybe achieve that but haven't managed to so far. Also, aggregate doesn't return a cursor which is what is expected from a publication.
This problem makes me doubt about the praises about meteor being suitable for secure apps... This would be so easy to achieve in Rails or others!
EDIT: As requested, here is the code I have so far for the transition of my "matching" check to the server:
Meteor.publish('users', function () {
var user = Meteor.users.findOne({_id: this.userId});
var interest = user.profile.interest;
// retrieve all users, with their friends for now
allUsers = Meteor.users.find({ $and: [
{'_id': {$ne: user._id}},
{'profile.type':interest}
]},
{ fields: {'profile.picture': 1, 'profile.friends': 1}}
);
// ------------- ADDED ---------------
allUsers.forEach(function (lookedup) {
if (_.contains(lookedup.profile.friends, user._id))
lookedup.profile.relation = "yes";
else
lookedup.profile.relation = "no";
lookedup.profile.friends = undefined;
return lookedup;
});
// ------------- END ---------------
return allUsers;
});
Obviously this code doesn't work at all, since I cannot modify cursor values in a foreach loop. But it gives an idea of what I want to achieve: give a way to the client to know if a friend is matched or not, without giving access to the friend lists of all users to the client. (and also, avoiding having to do one request per each user during display to ask the server if this specific user matches this specific one)
You can add a transform function and modify a cursor docs on the fly
meteor Collection.find

Resources