I'm playing around with building a simple Facebook Messenger chatbot and I'm having trouble sending messages in sequence.
In the example above, it should have printed "Hello!", "1", "2", "3" in order. I'm currently following the Facebook docs found here to implement this simple text message function. I've included my Express Node.JS server code below:
Defining the sendTextMessage() function:
var request = require("request");
function sendTextMessage(user, text) {
messageData = {
text: text
};
request({
url: "https://graph.facebook.com/v2.6/me/messages",
qs: {access_token: PAGE_ACCESS_TOKEN},
method: "POST",
json: {
recipient: {id: user},
message: messageData
}
}, function(error, response, body) {
if (error) {
console.log("Error sending message: ", error);
} else if (response.body.error) {
console.log("Error: ", response.body.error);
} else {
console.log("Message successfully send.")
}
});
}
Using it to send a response:
sendTextMessage(user, "Hello!");
sendTextMessage(user, "1");
sendTextMessage(user, "2");
sendTextMessage(user, "3");
I even tried implementing a simple queue that queues messages and only sends one message at a time after each request's success callback. This is making me suspect that I'm not interacting with the Messenger API correctly.
Has anyone encountered this issue? How can I get messages to send in sequence? Thanks!
EDIT
Because I implemented a simple queue but still experiencing this problem, I'm including the code for my simple queue system here.
var queue = [];
var queueProcessing = false;
function queueRequest(request) {
queue.push(request);
if (queueProcessing) {
return;
}
queueProcessing = true;
processQueue();
}
function processQueue() {
if (queue.length == 0) {
queueProcessing = false;
return;
}
var currentRequest = queue.shift();
request(currentRequest, function(error, response, body) {
if (error || response.body.error) {
console.log("Error sending messages!");
}
processQueue();
});
}
queueRequest(/* Message 1 */);
queueRequest(/* Message 2 */);
queueRequest(/* Message 3 */);
UPDATE
This "bug" was reported to Facebook but it sounds like they aren't going to fix it. Please read the ticket thread on Facebook's post here for details on what they say is going on. (Thank you to Louise for getting Facebook's attention on this)
I submitted a bug report to Facebook about this because I was having the same problem. They acknowledged that it is indeed a bug and are working to fix it: https://developers.facebook.com/bugs/565416400306038
After you send a POST to /me/messages, you'll receive a response that has a message id (mine start with 'mid.' which maybe stands for message id?):
{ recipient_id: '1015411228555555',
message_id: 'mid.1464375085492:b9606c00ca33c12345' }
After being completely received by the FB Messenger API, you'll get a call to your webhook (with no message events) that confirms receipt:
{ sender: { id: '1015411228555555' },
recipient: { id: '566031806XXXXXX' },
delivery:
{ mids: [ 'mid.1464375085492:b9606c00ca33c12345' ],
watermark: 1464375085604,
seq: 176 } }
I think that delivery receipt is the best way to ensure delivery, then send the next message.
Implement the send request as a Promise and only send consequent messages once the previous one is resolved
const send = (userId, messageData) => {
return new Promise((resolve, reject) => {
request
(
{
url : BASE_URL + "me/messages",
qs : { access_token : PAGE_ACCESS_TOKEN },
method : "POST",
json :
{
recipient: { id : userId },
message: messageData,
}
}, (error, response, body) =>
{
if (error) { console.log("Error sending message: " + response.error); return reject(response.error); }
else if (response.body.error) { console.log('Response body Error: ' + response.body.error); return reject(response.body.error); }
console.log("Message sent successfully to " + userId);
return resolve(response);
}
);
});
};
You can achieve QUEUING by promises.
function delay(time) {
return new Promise(function(resolve, reject) {
setTimeout(resolve, time);
});
}
delay(2000).then(() => {
console.log('hi');
delay(2000).then(() => {
console.log('hello');
delay(2000).then(() => {
console.log('welcome');
})
})
})
Instead of adding static timeouts, I would create a queue data structure. When the bot wants to send a message, append the contents to the end of the queue. On the message post callback, check if there are any messages still in the queue and call the function again using recursion and remove from the queue accordingly.
They should be received in the order that they are sent. Make sure you're actually sending them in order and not calling an async function 4 times (and send order isn't guaranteed). (I read that you tested it but in all my testing I've never seen a receive come out of order if the send order was guaranteed.)
I added a messageId counter to the app that resets to 0 on every start of messagehandling. Then I delay with that number * 100 ms. This way I can add intentional delays as well with code like messageDelay += 15
receivedMessage(event) {
messageDelay = 0;
//...
sendMessage extend:
function sendTextMessage(recipientId, messageText) {
//...
setTimeout(function() {
callSendAPI(messageData);
}, messageDelay++ * 100)
}
The message is not sending in order because, the request is sent asynchronously to facebook, and can be sent in any order.
To solve this you have to call the next sendTextMessage when the message that should be sent before it has received a response.
Based on the recursive solution proposed by #user3884594, I kind of make it work using this (I removed the error handling in order to simplify):
send_messages (["message 01", "message 02", "message 03"]);
function send_messages (which, i = 0)
{
request({
url: 'https://graph.facebook.com/v2.10/me/messages',
qs: { access_token: FACEBOOK_ACCESS_TOKEN },
method: 'POST',
json: { recipient: { id: senderId }, message: { text: which [i] }
}, (error, response, body) =>
{
// You need to put your error handling logic here
if (i++ < which.length - 1)
send_messages (which, i);
});
}
I had exactly same problem, that solution worked for me:
function sendMessage(recipient, messages, accessToken, i) {
axios.post(baseURL + 'v2.11/me/messages/?access_token=' + accessToken,
Object.assign({}, {
messaging_type: "RESPONSE",
recipient: {
id: recipient
}
}, messages[i]['payload']) )
.then(response => {
if(i < messages.length) sendMessage( recipient, messages, accessToken, i+1 );
},
error => {})
.catch(error => {});
}
sendMessage(recipient, flow['messages'], flow['page']['accessToken'], 0);
That's my question: Sequential Message Sending Using Facebook Send-API
You can try putting them inside a setTimeout function so each one goes after a certain period of time.
So replace this:
sendTextMessage(user, "Hello!");
sendTextMessage(user, "1");
sendTextMessage(user, "2");
sendTextMessage(user, "3");
With this:
sendTextMessage(user, "Hello!");
// 1 second
setTimeout(function() {
sendTextMessage(user, "1");
}, 1000)
// 2 seconds
setTimeout(function() {
sendTextMessage(user, "2");
}, 2000)
// 3 seconds
setTimeout(function() {
sendTextMessage(user, "3");
}, 3000)
And they should go one after another. You could also embed the functions inside each other if need be.
Related
I have 3 messages at my aws message inbox. But i am only getting one message when I'm trying
to retrieve all. I am getting only the last message, but the other two messages are not showing.
Here is my code:
const AWS = require("aws-sdk");
AWS.config.update({ region: "REGION" });
const sqs = new AWS.SQS({ apiVersion: "2012-11-05" });
const readMessage = function () {
const params = {
MaxNumberOfMessages: 10,
QueueUrl: "https://sqs.us-east-1.amazonaws.com/56622448/my-queue",
};
sqs.receiveMessage(params, function (err, data) {
if (err) {
console.log("Error", err);
} else if (data.Messages) {
console.log(data);
}
});
};
readMessage();
This is the output:
{
ResponseMetadata: { RequestId: 'a9e53cee-afc8-57c-664408f1e602' },
Messages: [
{
MessageId: 'bb3e23f9-07fd-4205-9b53-a48f826',
ReceiptHandle: 'AQEBD6rCOZlaBfCNn3nU4AEE7OlYwFJFeblTaiDoxIy8HiwsjdxZX+3SICY/YW5PI+RuFscMMh6VyExoo1i8Zo2JlbYj3t32b9CXnToYugzBqgZuxuYOTzXRAnrGwlavSL7hcLQvW6y8me1gnj65N3tPYEmcfXX5GIiQTn1yNEou3rUNff9DfkSije/0zvp33yfWfcW+RDzB2y6ND6eKHxfsP/cqmHjRaT0bE9rlXorjgh36YwVJ57e5bjUa/1dVqOf3ybXfEX/5C2eZM+T1V2JBxlguvuL1B3aHKAC+R9Pdgpdg2kmK3+bVmOxbQJKfU0s3sD9fElZJmLuLLMPb835z5hbVv44fKJVuEc7ad2uL3d1AUCbq3MKRCb38t77L4Ifa/ob3QQ==',
MD5OfBody: '7b84813a4b4bf10f0edb9e8da7',
Body: "Handsome Person Basic Information."
}
]
}
My Expected Output:
{
ResponseMetadata: { RequestId: 'a9e53cee-afc8-57c-664408f1e602' },
Messages: [
{
MessageId: 'bb3e23f9-07fd-4205-9b53-a48f826',
ReceiptHandle: 'AQEBD6rCOZlaBfCNn3nU4AEE7OlYwFJFeblTaiDoxIy8HiwsjdxZX+3SICY/YW5PI+RuFscMMh6VyExoo1i8Zo2JlbYj3t32b9CXnToYugzBqgZuxuYOTzXRAnrGwlavSL7hcLQvW6y8me1gnj65N3tPYEmcfXX5GIiQTn1yNEou3rUNff9DfkSije/0zvp33yfWfcW+RDzB2y6ND6eKHxfsP/cqmHjRaT0bE9rlXorjgh36YwVJ57e5bjUa/1dVqOf3ybXfEX/5C2eZM+T1V2JBxlguvuL1B3aHKAC+R9Pdgpdg2kmK3+bVmOxbQJKfU0s3sD9fElZJmLuLLMPb835z5hbVv44fKJVuEc7ad2uL3d1AUCbq3MKRCb38t77L4Ifa/ob3QQ==',
MD5OfBody: '7b84813a4b4bf10f0edb9e8da7',
Body: "Handsome Person Basic Information."
},
{
Body: "Handsome Person Basic Information II"
},
{
Body: "Handsome Person Basic Information III"
}
]
}
Under messages, i should be getting three. but retrieving is only one message.
Take note: I have just short cut the value of my expected value just to prove my point.
How would I able to get all of the three messages? Thanks !
This is the default behavior of AWS SQS
"If the number of messages in the queue is small (fewer than 1,000), you most likely get fewer messages than you requested per ReceiveMessage call. If the number of messages in the queue is extremely small, you might not receive any messages in a particular ReceiveMessage response. If this happens, repeat the request. "
Class ReceiveMessageCommand
Long polling might help, but not guarantee you will receive all message at once
Long polling is the solution. You can achieve by 2 ways :
Recall your "readMessage()" recursively(I won't recommend to use setInterval)
Use npm SQS-consumer
Good morning,
I'm having an annoying problem with some thing that's i'm trying to make using the youtube comment api , i'm trying to add automatically a comment when a video on my channel has been uploaded , it works as intended for the first 5 seconds and i get a notification that my comment was added , ,and i can see it after i uploaded my video , but after 3 - 5 seconds after , my comment gets deleted on it's own.
i looked for what could the cause of this could be , and i thought " oh maby it's considered as spam " , so i look in my uploaded video comments spam tab , and NOTHING , the comments are no where to be found.
i tried this process for around 10 times and still nothing , can someone help me out please ?
Thank you !
Here's what i do :
I subscribe to the youtube webhook to get a notification when a user uploads a new video
when i receive a notification , i send a push notification to my android app , so i can get an access_token from my android app using the google api.
once i get the token , isend it to my server so i can use it to comment on my new video.
Here is the code i use to insert a comment in the backend ( in node js ) :
function insertYoutubeComment(videoId,channel_id,message,access_token,comment){
return new Promise((resolve, reject) => {
request({
method: 'POST',
url: 'https://www.googleapis.com/youtube/v3/commentThreads',
headers: {
'User-Agent': 'Request-Promise'
},
body: {
"snippet": {
"videoId": videoId,
"channelId": channel_id,
"topLevelComment": {
"snippet": {
"textOriginal": message
}
}
}
},
qs: {
part: 'snippet',
access_token: access_token
},
json: true
}, function (error, response, body) {
console.log('body ==== ', body);
if (error) {
console.log('body', error.stack);
console.log('error in when posting comment ', error.stack);
resolve(error.stack);
// return reject(error);
}
console.log("Videos ==== ",comment.video);
let videos = comment.video
videos.push(videoId)
let bod = {}
console.log("Updating videos ");
Object.assign(bod, {
video: videos,
});
console.log("Body in update =====",body);
commentmodel.updateOne(
{ _id: comment._id },
bod,
function (err, user) {
if (err) {
console.log("Error updating ")
resolve("comment Error");
} else {
console.log("Updated user ===",user);
resolve("comment inserted");
}
}
);
//res.json({ message: "Comment inserted", status: 200, data: comment });
// return resolve(body);
});
});
}
I have Lambda Node function behind API Gateway, what I am trying to do is:
Make call to API
Do some logic
Send a message to Slack
Problem:
When I run default test request, the function finishes successfully but it doesn't send a message to Slack (logs only print Slack and not HAHA1/2),however, it does when I run this function from local machine which leads me to believe that AWS stops any none client-server traffic. My function is not in VPC. What can I do to allow outgoing traffic to Slack? Thanks.
const AWS = require('aws-sdk');
const Slack = require('slack-node');
const dynamo = new AWS.DynamoDB.DocumentClient();
/**
* Demonstrates a simple HTTP endpoint using API Gateway. You have full
* access to the request and response payload, including headers and
* status code.
*
* To scan a DynamoDB table, make a GET request with the TableName as a
* query string parameter. To put, update, or delete an item, make a POST,
* PUT, or DELETE request respectively, passing in the payload to the
* DynamoDB API as a JSON body.
*/
sendSlack()
function sendSlack() {
webhookUri = "https://hooks.slack.com/services/....";
slack = new Slack();
slack.setWebhook(webhookUri);
console.log('Slack');
slack.webhook({
channel: "...",
username: "...",
text: "..."
}, function(err, response) {
console.log("HAHA1");
console.log(response);
console.log("HAHA2");
});
}
exports.handler = async (event, context) => {
console.log('Received event:', JSON.stringify(event, null, 2));
sendSlack()
let body;
let statusCode = '200';
const headers = {
'Content-Type': 'application/json',
};
try {
switch (event.httpMethod) {
case 'DELETE':
body = await dynamo.delete(JSON.parse(event.body)).promise();
break;
case 'GET':
body = await dynamo.scan({ TableName: event.queryStringParameters.TableName }).promise();
break;
case 'POST':
body = await dynamo.put(JSON.parse(event.body)).promise();
break;
case 'PUT':
body = await dynamo.update(JSON.parse(event.body)).promise();
break;
default:
throw new Error(`Unsupported method "${event.httpMethod}"`);
}
} catch (err) {
statusCode = '400';
body = err.message;
} finally {
body = JSON.stringify(body);
}
return {
statusCode,
body,
headers,
};
};
You will need to wait for the slack call by either providing a callback or promisifying it:
function sendSlack() {
return new Promise((resolve, reject) => {
webhookUri = "https://hooks.slack.com/services/....";
slack = new Slack();
slack.setWebhook(webhookUri);
console.log('Slack');
slack.webhook({
channel: "...",
username: "...",
text: "..."
}, function(err, response) {
if(err) return reject(err)
resolve(response)
});
})
}
Then in your code
await sendSlack()
It could be that your lambda handler is completing before your call to Slack is successfully executed.
Since the call to Slack is async, I would suggest you try to await its response before your handler returns a response.
I'm not 100% certain, by my guess is that the async Slack call gets scheduled but not run until the main body of the function has completed, at which point the lambda is "spun down" and nothing further is run.
I'm using the serverless apigateway websockets and I can successfully get messages sent back and forth between the lambda function and client.
However I can't figure out how to get my function to send only a single message. It's currently sending two messages due to the callback at the end of the function. This is more of a nodejs issue, but I've been trying for the past couple of hours to figure out how, but can't seem to.
var params2 = {
TableName: "UserConnections",
FilterExpression: "cameraId = :val",
ExpressionAttributeValues: { ":val": {"S" : JSON.parse(event.body).data.camera_id}}
};
DDB.scan(params2, function(err, data) {
if (err) throw err;
console.log("DATA: " + JSON.stringify(data));
for(var i = 0; i<data['Items'].length; i++){
var id = data['Items'][i]['connectionId'].S;
console.log("List of connection ids: " + id);
var params3 = {
ConnectionId: id,
Data: JSON.stringify(message)
};
apigatewaymanagementapi.postToConnection(params3, function(err, data) {
if (err){
throw err; // an error occurred
}else{
console.log("Success sending message to clients: " + JSON.stringify(data));
}
});
}
});
callback(null, {
statusCode: 200,
body: "Message Processed in Lambda!"
});
In postToConnection method, it sends a message back to multiple users, and the callback function sends the body to the same users. How can I just send the params3 back to the users and not use the callback to end the function
Edit1:______________________________________________
Adding
callback(null, {});
Still sends two messages except the second one is now empty. How can I get it to strictly send only one message
Return with a empty object, the return value is ignored when this function is invoked from WebSocket gateway
return {}; // callback(null, {});
I've got the following code that can send push notifications using Azure Notification Hubs. When a new item is inserted into the database, this code sends a push notification to the devices registered with the tag.
I'm using Ionic/Phonegap for the iOS app and the ngCordova Push Plugin. I want to add badge counts for iOS devices, but I can't seem to find a way to do this. I've tried using the push.apns.send function, but can't get it to work.
Azure Mobile Services
function insert(item, user, request) {
// Execute the request and send notifications.
request.execute({
success: function() {
// Create a template-based payload.
var payload = '{ "message" : "This is my message" }';
push.send("My Tag", payload, {
success: function(pushResponse){
// Send the default response.
request.respond();
},
error: function (pushResponse) {
console.log("Error Sending push:", pushResponse);
// Send the an error response.
request.respond(500, { error: pushResponse });
}
});
}
});
}
Phonegap
var iosConfig = {
"badge": true,
"sound": true,
"alert": true
};
$cordovaPush.register(iosConfig).then(function (deviceToken) {
var hub = new NotificationHub(mobileClient);
// This is a template registration.
var template = "{\"aps\":{\"alert\":\"$(message)\"}}";
// Register for notifications.
// (deviceId, ["tag1","tag2"], templateName, templateBody, expiration)
hub.apns.register(deviceToken, myTags, "myTemplate", template, null).done(function () {
// Registered with hub!
}).fail(function (error) {
alert("Failed registering with hub: " + error);
});
}, function (err) {
alert("Registration error: " + err)
});
I've searched through dozens of articles/tutorials and none of them work. Any help would be greatly appreciated.
I finally figured it out. The issue was that the template registration needed to include the badge. Here's what works:
Azure Mobile Services
function insert(item, user, request) {
// Execute the request and send notifications.
request.execute({
success: function() {
// Create a template-based payload.
var payload = '{ "message" : "' + originalMessage + '", "badge" : "100" }';
push.send("My Tag", payload, {
success: function(pushResponse){
// Send the default response.
request.respond();
},
error: function (pushResponse) {
console.log("Error Sending push:", pushResponse);
// Send the an error response.
request.respond(500, { error: pushResponse });
}
});
}
});
}
Phonegap
var iosConfig = {
"badge": true,
"sound": true,
"alert": true
};
$cordovaPush.register(iosConfig).then(function (deviceToken) {
var hub = new NotificationHub(mobileClient);
// This is a template registration.
var template = "{\"aps\":{\"alert\":\"$(message)\",\"badge\":\"#(badge)\" }}";
// Register for notifications.
// (deviceId, ["tag1","tag2"], templateName, templateBody, expiration)
hub.apns.register(deviceToken, myTags, "myTemplate", template, null).done(function () {
// Registered with hub!
}).fail(function (error) {
alert("Failed registering with hub: " + error);
});
}, function (err) {
alert("Registration error: " + err)
});