Trying to edit a specific embed field on a user action - node.js

I'm trying to triggering an edit on my embedded (already sent) message while keeping all the other fields the same value
I have found this answer as an inspiration (which works with the example): Embed message doesn't update but that doesn't seem to get all the fields, only the first. There isn't much more on the subject to find (or i'm not good at Googling :)).
So the new embed is just the first field and not all the (not changed) fields.
activityMsg = new Discord.RichEmbed({
title: 'Some text',
description: 'Description',
color: 3447003,
footer: {
icon_url: image,
text: image
},
thumbnail: {
url: image
},
fields: [
{
name: 'Text',
value: 'Text2',
},
{
name: 'Date and time',
value: '2pm',
},
{
name: 'Participants',
value: '#User',
},
{
name: 'Waiting list',
value: '#user2',
},
{
name: 'Max players',
value: '22',
}
]
});
const reactionFilterPlus = (reaction, user) => reaction.emoji.name === emoji_plus;
if(typeof title != undefined && title != null && data.length == 4 && error == ''){
var title = title[0].replace('[','').replace(']','');
// add reaction emoji to message
msg.channel.send(activityMsg)
.then(msg => msg.react(constants.emoji_plus))
.then(mReaction => {
// createReactionCollector - responds on each react, AND again at the end.
const collector = mReaction.message
.createReactionCollector(reactionFilterPlus, {
time: 15000
});
// set collector events
collector.on('collect', r => {
// immutably copy embed's Like field to new obj
let embedLikeField = Object.assign({}, activityMsg.fields[0]);
// update 'field' with new value
embedLikeField.value = `${user} <3`;
// create new embed with old title & description, new field
const newEmbed = new Discord.RichEmbed({
title: activityMsg.title,
description: activityMsg.description,
fields: [embedLikeField]
});
// edit message with new embed
// NOTE: can only edit messages you author
r.message.edit(newEmbed)
.catch(console.log);
});
})
.catch(console.log);
}
I expected this line to get all the fields, but that isn't the case.
// immutably copy embed's Like field to new obj
let embedLikeField = Object.assign({}, activityMsg.fields[0]);
I have tried let embedLikeField = Object.assign({}, activityMsg.fields[0] === 'Participants') but then I get the following error about a fieldname not present.
{ DiscordAPIError: Invalid Form Body
embed.fields[0].name: This field is required
at item.request.gen.end (/usr/src/app/node_modules/discord.js/src/client/rest/RequestHandlers/Sequential.js:79:15)
at then (/usr/src/app/node_modules/snekfetch/src/index.js:215:21)
at process._tickCallback (internal/process/next_tick.js:68:7)
English isn't my native language and I'm stilling learning nodejs sorry sorry in advance about these points.

Object.assign() performes a shallow clone on the source, Are you trying to clone your entire embed or only its first field?
activityMsg.fields[0] refers to the first element in the list called fields within your activityMsg object. Try calling your assign() with activityMsg as source.

Related

Can't push object into the array after running mongodb query

I was trying to set up the logic for adding some items into an array, which id's express server receives from a client. My program receives the id of the product and then I was fetching the product details from MongoDB query findOne, and then with some customized details I used to push that item into an array but it's not working, whenever I try to push any element after MongoDB query it's not working, I don't know why, but please help me, Sorry for my bad English !
It's an ExpressJS server using MongoDB
Items received: (But it is actually received from the client in the form of JSON) :
const items= [
{
productId:"61e01e7e24612b56c33b06c3",
quantity:"4"
},
{
productId:"61e01e9024612b56c33b06c6",
quantity:"10"
}
]
The actual code here is the problem
let itemsData = [];
items.forEach(async (item) => {
const itemData = await findProduct({ _id: item.productId });
// Check if product found
if (!itemData) return res.status(400).json({ message: "Product is Invalid" });
// If found add that in object
itemsData.push({
productId: itemData._id,
name: itemData.name,
price: itemData.price,
quantity: item.quantity,
unit: "Nos",
totalPrice: parseInt(itemData.price) * parseInt(item.quantity)
});
});
The code above doesn't push that object into the itemsData
array
findProduct Function
// Service to find Product
async findProduct(filter) {
return await ProductModel.findOne(filter);
}
If I used that push method and tried only to itemsData.push("hello"); before the MongoDB query it works, but if I put it after the findProduct Query it doesn't work! I don't know what is wrong with it! Somebody help me!
I just want to push those items with detail into itemData object happily which is not happening I tried to console.log(itemsData) it just return [], what should I do?
Try using For Of instead of forEach (don't forget to add async)
let itemsData = [];
for (const item of items) {
const itemData = await findProduct({ _id: item.productId });
// Check if product found
if (!itemData) return res.status(400).json({ message: "Product is Invalid" });
// If found add that in object
itemsData.push({
productId: itemData._id,
name: itemData.name,
price: itemData.price,
quantity: item.quantity,
unit: "Nos",
totalPrice: parseInt(itemData.price) * parseInt(item.quantity)
});
}
It's because forEach function is not designed to work well with async calls.
You could use map instead.
This should work:
let itemsData = [];
const promises = items.map(async (item) => {
const itemData = await findProduct({ _id: item.productId });
// Check if product found
if (!itemData) return res.status(400).json({ message: "Product is Invalid" });
return {
productId: itemData._id,
name: itemData.name,
price: itemData.price,
quantity: item.quantity,
unit: "Nos",
totalPrice: parseInt(itemData.price) * parseInt(item.quantity)
};
});
itemsData = await Promise.all(promises);
When you use map with async, you will have an array of promises, so you can use Promise.all to wait for the values to get resolved.
Check this out for more details.

I couldn't find a working Telegraf inline query project

I want to make a telegram bot about algebra. I need to send code to http://api.mathjs.org/v4/?expr=2*(7-3) after the expr part. I want to send numbers with inline query, but how can I do it?
The original example uses context object deconstruction, which doesn't seem to work and spits out error: TypeError: Cannot read property 'assert' of undefined
Here is the code without object deconstruction, which works for me (I've made it superfluously verbose for better understanding ):
bot.on('inline_query', async (ctx) => {
const offset = parseInt(ctx.inlineQuery.offset) || 0;
let items = [];
for(var i = 0; i < 100; i++) {
items.push({ title: 'Item '+i, desc: 'item '+i+' desc', id: '0000'+i, moreinfo: 'More info about item'+i+', mucho importante information'})
}
let results = items.slice(offset, offset+10).map((item) => ({
type: "article",
id: item.id,
title: item.title,
description: item.desc,
input_message_content: {
message_text: '*'+item.title+'*\n'+item.desc,
parse_mode: 'Markdown'
},
reply_markup: {
inline_keyboard: [
[{ text: 'More info', callback_data: 'moreinfo' }]
]},
hide_url: true,
url: 'http://www.domain.se/'+item.id,
}));
console.log('hello');
let ourReturn = ctx.answerInlineQuery(results, {is_personal: true, next_offset: offset+results.length, cache_time: 10});
return ourReturn;
});
Here is the article that helped me to solve this problem.
You can find multiple examples of Telegraf usage at https://github.com/telegraf/telegraf/tree/develop/docs/examples
Here is an example of a bot utilizing an inline query

How to push data into existing element of the array of objects in nodejs?

I have this array list of objects.
var list = [{
'ID':1,
'name' : 'Vikas Yadav',
'mobile':8095638475,
'sent':false
},
{
'ID':2,
'name' : 'Rajat Shukla',
'mobile':7486903546,
'sent':false
},
{
'ID':3,
'name' : 'Munna Bhaiya',
'mobile':9056284550,
'sent':false
},
{
'ID':4,
'name' : 'Guddu Pandit',
'mobile':7780543209,
'sent':false
},
{
'ID':5,
'name' : 'Srivani Iyer',
'mobile':8880976501,
'sent':false
}];
Now I want to push two more datas in specific element of this array via forLoop as:
var timeAndOTPArray = {
"time" : new Date(),
"OTP": req.params.ran
}
I am retrieving the list data via cookies into one of the route.
Below is the code I am trying to push the element according to the matching condition.
var lists = req.cookies.list;
Object.keys(lists).forEach(function(item) {
if(req.params.ID == lists[item].ID){ //look for match with name
(lists[item]).push(timeAndOTPArray);
newAddedList.push(lists[item]);
console.log(item, lists[item]);
}
});
Perhaps it's not the correct way. Please help!
Wish you a happy and a prosperous Diwali.
Cheers!
You can use findIndex and append to update the object into list like this:
//List only with ID, easier to read the code
var list = [{'ID':1,},{'ID':2,}]
//your object
var timeAndOTPArray = {
"time" : new Date(),
"OTP": "otp"
}
//Index where object with ID == 2 is
var index = list.findIndex(obj => obj.ID == 2);
//Append the 'timeAndOTPArray' properties into the object itself
list[index] = {"time": timeAndOTPArray.time, "OTP":timeAndOTPArray.OTP, ...list[index]}
console.log(list)
I guess this will help
var lists = req.cookies.list;
Object.keys(lists).forEach(function(item) {
if(req.params.ID == lists[item].ID){ //look for match with ID
Object.keys(timeAndOTPArray).forEach(key=>{
lists[item][key]=timeAndOTPArray[key];
})
}
});
Good evening) I can advice you the best option is update with map
const listItems = [
{
ID: 1,
name: 'Vikas Yadav',
mobile: 8095638475,
sent: false,
},
{
ID: 2,
name: 'Rajat Shukla',
mobile: 7486903546,
sent: false,
},
{
ID: 3,
name: 'Munna Bhaiya',
mobile: 9056284550,
sent: false,
},
{
ID: 4,
name: 'Guddu Pandit',
mobile: 7780543209,
sent: false,
},
{
ID: 5,
name: 'Srivani Iyer',
mobile: 8880976501,
sent: false,
},
];
const paramId = 4;
const result = listItems.map((item) => {
if (paramId === item.ID) {
return {
...item,
time: new Date(),
OTP: 'smth',
};
}
return item;
});
console.log('result', result);
for appending, you can do this,
lists[index] = Object.assign(lists[index], timeAndOTPArray);
If you are using es6,
lists[index] = {...lists[index], timeAndOTPArray};
Here lists is an array of objects.
so lists[item] is an object, so you cant push an object to an object.
In your code timeAndOTPArray is an object.
In your lists object, initialize an empty array called timeAndOTPArray
var index = lists.findIndex(function(item){ return item.ID == req.params.ID});
lists[index].timeAndOTPArray.push(timeAndOTPArray);

Bot framework Hero card without image parameter

How to avoid the image parameter in Hero card bot framework so that I can display only the options.
following code is not working, only BotFramework Hero Card title is diplaying
createHeroCard() {
return CardFactory.heroCard(
'BotFramework Hero Card',
CardFactory.actions([
{
type: 'imBack',
title: 'ABC',
value: 'ABC'
},
{
type: 'imBack',
title: 'DATA',
value: 'DATA'
},
])
);
}
I am noob at node.js I did same on C# which worked as expected. You could try converting the same. Here is the code snippet for your assistance.
public IMessageActivity YesCreateSolutionFlow()
{
try
{
var yesNoActivity = Activity.CreateMessageActivity();
var buttonList = new List<CardAction>();
//Btn 1
buttonList.Add(
new CardAction()
{
Value = "https://partner.microsoft.com/en-US/solutions/my-solutions/create-solution",
Type = "openUrl",
Title = "Yes, please"
});
//Btn 2
buttonList.Add(
new CardAction()
{
Value = "I Would like a tutorial first",
Type = "imBack",
Title = "I Would like a tutorial first"
});
var responseCard = new HeroCard()
{
Text = "",
Subtitle = string.Empty,
Buttons = buttonList
};
// Create the attachment.
var attachment = responseCard.ToAttachment();
yesNoActivity.Attachments.Add(attachment);
yesNoActivity.AttachmentLayout = AttachmentLayoutTypes.Carousel;
return yesNoActivity;
}
catch (Exception ex)
{
throw new NotImplementedException(ex.Message, ex.InnerException);
}
}
Finally, pass this card on turnContext.SendActivityAsync like below:
var yesCreateSolutionFlow= _customFlowRepository.YesCreateSolutionFlow();
await turnContext.SendActivityAsync(yesCreateSolutionFlow).ConfigureAwait(false);
Bot Conversation Output:
Hope that would help.
CardFactory.heroCard is expecting 3 or 4 parameters, You have only provided 2, so I believe the method is trying to interpret your buttons as images. if you pass an empty array as your second argument, I believe it will work as expected i.e.
createHeroCard() {
return CardFactory.heroCard(
'BotFramework Hero Card',
[],
CardFactory.actions([
{
type: 'imBack',
title: 'ABC',
value: 'ABC'
},
{
type: 'imBack',
title: 'DATA',
value: 'DATA'
},
])
);
}
If you don't want a title at all, you can just pass a blank string (i.e. '') as the first argument.

How to write the parameter to update a particular property's value in an object from an array?

How to update quantity value based on title in the movies array and Item id (123)
I only manage to update value at the first layer like name (David), but don't know how to update the second layer with additional filter for the array (movies).
From:
Item:
{
id: 123,
name: 'David',
movies: [
{
id: 1,
title: 'The lord of the ring',
quantity: 1
},
{
id: 2,
title: 'Star Wars',
quantity: 1
}
]
}
To:
Item:
{
id: 123,
Name: 'David',
movies: [
{
id: 1,
title: 'The lord of the ring',
quantity: 2
},
{
id: 2,
title: 'Star Wars',
quantity: 1
}
]
}
By the way, I'm using aws DynamoDB document client in node.js, it will be nice if you can share me how you do it in your update parameter.
There is no way to update an object inside of a list without replacing it.
You probably want to restructure your table to emulate a relational data model. AWS has some documentation on this.
As an example, create your table like this:
aws dynamodb create-table \
--table-name movie-table \
--attribute-definitions AttributeName=rId,AttributeType=N AttributeName=rKey,AttributeType=S \
--key-schema AttributeName=rId,KeyType=HASH AttributeName=rKey,KeyType=RANGE
The table will have generically named hash and range keys. This script demonstrates how to structure the data and add to the "count":
const { DynamoDB } = require('aws-sdk');
const client = new DynamoDB.DocumentClient({ region: 'us-east-1' });
const addItem = (rId, rKey, attributes) => {
const item = { rId, rKey };
Object.assign(item, attributes);
return client.put({ TableName: 'movie-table', Item: item }).promise();
};
// NOTE: this is where the count attribute gets iterated
const addToCount = (rId, rKey) => client.update({
TableName: 'movie-table',
Key: { rId, rKey },
UpdateExpression: 'ADD #count :n',
ExpressionAttributeNames: { '#count': 'count' },
ExpressionAttributeValues: { ':n': 1 },
}).promise();
const run = async () => {
await addItem(123, 'USER|123', { name: 'David' });
await addItem(1, 'MOVIE|1', { title: 'The lord of the ring' });
await addItem(2, 'MOVIE|2', { title: 'Star Wars' });
await addItem(123, 'COUNT|1', { count: 1 });
await addItem(123, 'COUNT|2', { count: 1 });
await addToCount(123, 'COUNT|1');
};
run();
This is what the table looks like after the script runs:
I know this is a bit old but there is a way. Using the document client SDK, you can reference object properties and array elements in the UpdateExpression. However, you can't run any logic so you have to know/assume/expect that the element indexes are enough.
For example, you can do something like this:
let params = {
TableName: 'your-table-name',
Key: { id: 123 },
UpdateExpression: 'set movies[0].quantity = :x',
ExpressionAttributeValues: { ':x': 5 }
};
const client = AWS.DynamoDB.DocumentClient();
client.update(params);
NOTE: You cannot make the index an Expression Attribute Value. You would have to dynamically build that update expression based on the index you know has to be updated. It's not a perfect solution but it could get the job done.
For reference, I derived this from the base (non-DocumentClient) example from here: Adding Nested Map Attributes

Resources