I'm building a facebook bot in nodejs with facebook messenger API. I'm trying to send a image from the bot by directly uploading the image file from the heroku server itself (not through URL) and it does not work.
Here is a error log from the console.
Failed calling Send API 400 Bad Request { message: '(#100) Incorrect
number of files uploaded. Must upload exactly one file.',type:
'OAuthException', code: 100,error_subcode: 2018005,fbtrace_id:
'E32ogm/ofxd' }
The official facebook document only contains an example in curl format and I'dont know how to replicate this curl into node format.
I've tested with curl and it worked like a charm.
curl \ -F 'recipient={"id":"recipientId"}' \ -F 'message={"attachment":{"type":"image", "payload":{}}}' \ -F 'filedata=#resource/pdf_img/sample.jpg;type=image/jpeg' \ "https://graph.facebook.com/v2.6/me/messages?access_token=PAGE_ACCESS_TOKEN"
This is my node implementation that seems to be problematic,
//file_loc = __dirname+"/resource/pdf_img/sample.jpg"
function sendImageMessage(recipientId, file_loc){
let fs = require('fs');
var readStream = fs.createReadStream(file_loc);
var messageData = {
recipient : {
id : recipientId
},
message : {
attachment : {
type : "image",
payload :{}
}
},
filedata:readStream
}
callSendAPI(messageData);
}
function callSendAPI(messageData) {
request({
uri: "https://graph.facebook.com/v2.6/me/messages",
qs: {access_token: process.env.PAGE_ACCESS_TOKEN},
method: "POST",
json: messageData
}, function(error, response, body) {
if (!error && response.statusCode == 200) {
var recipientId = body.recipient_id;
var messageId = body.message_id;
if (messageId) {
console.log("Successfully sent message with id %s to recipient %s",
messageId, recipientId);
} else {
console.log("Successfully called Send API for recipient %s",
recipientId);
}
} else {
console.error("Failed calling Send API", response.statusCode, response.statusMessage, body.error);
}
});
}
Please any help with fixing my node implementation or translating that curl in to node would be appreciated.
You can use forms /form-data/ in the request module (which has integrated module 'form-data').
But all requests needs to be stringified.
So, based on your example, the following should do the job >>
function sendImageMessage(recipientId, file_loc){
let fs = require('fs');
var readStream = fs.createReadStream(file_loc);
var messageData = {
recipient : {
id : recipientId
},
message : {
attachment : {
type : "image",
payload :{}
}
},
filedata:readStream
}
callSendAPI(messageData);
}
function callSendAPI(messageData) {
var endpoint = "https://graph.facebook.com/v2.6/me/messages?access_token=" + process.env.PAGE_ACCESS_TOKEN;
var r = request.post(endpoint, function(err, httpResponse, body) {
if (err) {return console.error("upload failed >> \n", err)};
console.log("upload successfull >> \n", body); //facebook always return 'ok' message, so you need to read error in 'body.error' if any
});
var form = r.form();
form.append('recipient', JSON.stringify(messageData.recipient));
form.append('message', JSON.stringify(messageData.message));
form.append('filedata', messageData.filedata); //no need to stringify!
}
Details here https://github.com/request/request#forms
I think sending variable messagedata as formdata would solve the problem.
var FormData = require('form-data');
var fs = require('fs');
var https = require('https');
function sendImageMessage(recipientId, file_loc){
var readStream = fs.createReadStream(file_loc);
var messageData = new FormData();
messageData.append('recipient', '{id:' +recipientId+ '}');
messageData.append('message', '{attachment :{type:"image", payload:{}}}');
messageData.append('filedata', readStream);
callSendAPI(messageData);
}
Secondly you need to change request a bit since now you are using formdata. I have done it using module https and so have changed the callSendAPI() code accordingly. You can find out how to send the formdata using request module.
function callSendAPI(messageData) {
var options = {
method: 'post',
host: 'graph.facebook.com',
path: '/v2.6/me/messages?access_token=' + pagetoken,
headers: messageData.getHeaders()
};
var request = https.request(options);
messageData.pipe(request);
request.on('error', function(error) {
console.log("Unable to send message to recipient %s", recipientId);
return;
});
request.on('response', function(res) {
if (res.statusMessage == "OK") {
console.log("Successfully sent message to recipient %s", recipientId);
} else {
console.log("Unable to send message to recipient %s", recipientId);
}
return;
});
}
Related
I'm trying to pass JWT token and data fields via Jquery and then consume those values in Flask.
So, my client side query looks like this:
function getData() {
var data = {
UserPoolId : 'AWS CognitoUser Pool Id',
ClientId : 'AWS CognitoUser client Id'
};
var userPool = new AmazonCognitoIdentity.CognitoUserPool(data);
var cognitoUser = userPool.getCurrentUser();
cognitoUser.getSession(function(err, session) {
if (err) {
alert(err);
return;
}
console.log('session validity: ' + session.isValid());
console.log(cognitoUser.signInUserSession.accessToken.jwtToken);
//
$.ajax({
url: "/api/v1/count",
type: "POST",
headers: { "X-Test-Header": cognitoUser.signInUserSession.accessToken.jwtToken },
// data: JSON.stringify(data),
data: JSON.stringify("{'UserPoolId': 'XXXX','ClientId': 'XXXX'}"),
contentType: 'application/json; charset=utf-8',
error: function(err) {
switch (err.status) {
case "400":
// bad request
break;
case "401":
// unauthorized
break;
case "403":
// forbidden
break;
default:
//Something bad happened
break;
}
},
success: function(data) {
console.log(data);
}
});
//
});
}
Now, in my serverside flask:
I tried to catch the token value using below: which is not working
#app.route("/api/v1/count", methods=["GET", "POST"])
def get_data_count():
if 'X-Test-Header' in request.headers:
headers = request.headers
app.logger.info(headers)
auth = headers.get("X-Test-Header")
app.logger.info('testing info log' + auth)
Also I tried to catch the data fields , using result = request.get_json() also not working.
I tried to inspect in chrome, and I don't see these values being added to the request header.
Can anyone suggest to me if I'm doing it correctly while passing the values from client to server? I also don't see console.log(cognitoUser.signInUserSession.accessToken.jwtToken) in the console log.
if not can anyone suggest to me, how to fix it?
Got a 403 signature error , when using the below fetch function:
function elasticsearchFetch(AWS, elasticsearchDomain, endpointPath, options = {}, region = process.env.AWS_REGION) {
return new Promise((resolve, reject) => {
const { body, method = 'GET' } = options;
const endpoint = new AWS.Endpoint(elasticsearchDomain);
const request = new AWS.HttpRequest(endpoint, region);
request.method = method;
request.path += endpointPath;
request.headers.host = elasticsearchDomain;
if (body) {
request.body = body;
request.headers['Content-Type'] = 'application/json';
request.headers['Content-Length'] = request.body.length;
}
const credentials = new AWS.EnvironmentCredentials('AWS');
const signer = new AWS.Signers.V4(request, 'es');
signer.addAuthorization(credentials, new Date());
const client = new AWS.HttpClient();
client.handleRequest(request, null, (res) => {
let chunks = '';
res.on('data', (chunk) => {
chunks += chunk;
});
res.on('end', () => {
if (res.statusCode !== 201) console.log('Got these options STATUSCODE', JSON.stringify(options, false, 2));
return resolve({ statusCode: res.statusCode, body: chunks });
});
}, (error) => {
console.log('Got these options ERROR', JSON.stringify(options, false, 2));
return reject(error);
});
});
}
This is the options used for the request in above function :
{
"method": "POST",
"body": "{\"prefix\":\"image_233/ArtService/articles-0/GB/ART-60297885/\",\"id\":\"ART-60297885\",\"retailUnit\":\"GB\",\"commercial\":{\"name\":{\"en-GB\":\"FÖRBÄTTRA\"}},\"schemaType\":\"product\",\"productType\":\"ART\"}"
}
and got this error :
{
"statusCode": 403,
"body": "{\"message\":\"The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.\"}"
}
This is the endpoint : 233/_doc/
I believe your Content-Length header is incorrect, causing the signature mismatch.
Your payload includes the string FÖRBÄTTRA, which has two double-byte characters.
You're setting the Content-Length to request.body.length, which comes to 186.
While this is the number of characters in the body, it is not the number of bytes in the body (188).
To calculate the Content-Length, use Buffer.byteLength(request.body). For a POST request like this, you can even remove that line of code altogether, and the request will succeed.
// Content-Length is only needed for DELETE requests that include a request
// body, but including it for all requests doesn't seem to hurt anything.
request.headers['Content-Length'] = Buffer.byteLength(request.body);
Source: https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-request-signing.html#es-request-signing-node
By the way, why not use elasticsearch client for nodejs to communicate with elasticsearch rather than writing your own logic. You can consider using http-aws-es which does the request signing part for you. The code will look like
const { Client } = require("elasticsearch");
const esConnectionClass = require("http-aws-es");
const elasticsearchConfig = {
host: "somePath",
connectionClass: esConnectionClass
};
const nativeClient = new Client(elasticsearchConfig);
const result = await nativeClient.search({});
I am trying to send an email using POST request with just Node standard modules in NodeJS v8.10 via GMail API.
The sample code is given below.
I am getting an error:
{
"error": {
"errors": [
{
"domain": "global",
"reason": "invalidArgument",
"message": "Recipient address required"
}
],
"code": 400,
"message": "Recipient address required"
}
}
It says recipient address required but according to what I think, (I may be wrong), my base64url conversion is proper since I checked it in Google API Explorer but my problem is in passing the data, I am doing it properly as told in the guide i.e. inside body with 'raw' key but it still does not work and this is where my problem must be. Maybe I am missing something, maybe I do not know the proper structure.
Yes, there are multiple posts regarding this but none of them provided solution.
I referred the guide on https://developers.google.com/gmail/api/v1/reference/users/messages/send
but the given example is of with the use of client library.
I tried everything, passing the 'raw' with base64url encoded data into write function of the request, passing it as a data parameter in options, passing it through body parameters in options, everything I can think of.
Am I missing something? Where am I going wrong?
I am a newbie in nodejs so please explain and if possible, an example structure of solution would be most welcome.
Base64url produced is working fine, I guess. I copied the string produced by conversion and tried it at https://developers.google.com/gmail/api/v1/reference/users/messages/send?apix=true
It works fine and sends me the mail but it does not work on my code.
var email = (
"Content-Type: text/plain; charset=\"UTF-8\"\n" +
"Content-length: 5000\n" +
"MIME-Version: 1.0\n" +
"Content-Transfer-Encoding: message/rfc2822\n" +
"to: something#something.com\n" +
"from: \"Some Name\" <something#gmail.com>\n" +
"subject: Hello world\n\n" +
"The actual message text goes here"
);
async function sendMail(token,resp) {
return new Promise((resolve,reject) => {
var base64EncodedEmail = Buffer.from(email).toString('base64');
var base64urlEncodedEmail = base64EncodedEmail.replace(/\+/g, '-').replace(/\//g, '_');
var params = {
userId: 'me',
resource: {
'raw': base64urlEncodedEmail
}
};
var body2 = {
"raw": base64urlEncodedEmail,
}
var options = {
hostname: 'www.googleapis.com',
path:'/upload/gmail/v1/users/me/messages/send',
headers: {
'Authorization':'Bearer '+token,
'Content-Type':'message/rfc822',
},
body: {
"raw": base64urlEncodedEmail,
'resource': {
'raw': base64urlEncodedEmail,
}
},
data: JSON.stringify({
'raw': base64urlEncodedEmail,
'resource': {
'raw': base64urlEncodedEmail,
}
}),
message: {
'raw': base64urlEncodedEmail,
},
payload: {
"raw": base64urlEncodedEmail, //this is me trying everything I can think of
},
// body: raw,
// }
userId: 'me',
// resource: {
// 'raw': base64urlEncodedEmail
// },
method: 'POST',
};
var id='';
console.log(base64urlEncodedEmail);
const req = https.request(options, (res) => {
var body = '';
res.on('data', (d) => {
body += d;
});
res.on('end', () => {
var parsed = body;
console.log(parsed);
})
});
req.on('error', (e) => {
console.error(e);
});
req.write(JSON.stringify(body2));
req.end();
});
};
Thank you for your time and answers.
I found the solution.
It says everywhere to convert the rfc822 formatted string to Base64url to send and attach it to 'raw' property in the POST body but I don't know what has changed and you don't need to do that anymore.
First things first, the Content-Type in header should be
'Content-Type':'message/rfc822'
Now, since we are specifying the content-type as message/rfc822, we don't need to convert the data we want to send into base64url format anymore, I guess (Not sure of the reason because I have a very little knowledge about this.)
Only passing "To: something#any.com" as body works.
Here is the complete code of how to get it done for someone who is struggling for the same problem.
function makeBody(to, from, subject, message) {
let str = [
"to: ", to, "\n",
"from: ", from, "\n",
"subject: ", subject, "\n\n",
message,
].join('');
return str;
}
async function getIdAsync(token,resp) {
return new Promise((resolve,reject) => {
let raw = makeBody("something#gmail.com", "something#gmail.com", "Subject Here", "blah blah blah");
var options = {
hostname: 'www.googleapis.com',
path:'/upload/gmail/v1/users/me/messages/send',
headers: {
'Authorization':'Bearer '+token,
'Content-Type':'message/rfc822'
},
method: 'POST',
};
const req = https.request(options, (res) => {
var body = '';
res.on('data', (d) => {
body += d;
});
res.on('end', () => {
var parsed = body;
console.log(parsed);
})
});
req.on('error', (e) => {
console.error(e);
});
req.write(raw);
req.end();
});
};
Happy Coding :)
I am using fcm node modules to send push notifications. Tried to send to a group(group with one device token). And all it delivers duplicated copies(4 copies everytime) of the same push for a single alert. It was supposed to be delivered only one. I have used fcm-node (version: 1.2.1), fcm-push (version: 1.1.3) etc.
The nodejs code is as shown below:
Alerts.pushTry = function (cb) {
var serverKey = "abcd"; //put your server key here
var deviceToken = 'group_key'; // required
var FCM = require('fcm-node');
var message = { //this may vary according to the message type (single recipient, multicast, topic, et cetera)
to: deviceToken,
notification: {
title: 'Group 555 test'+ ((new Date()).getTime()),
body: 'Body of your push notification'
},
data: {
my_key: 'my value',
my_another_key: 'my another value'
}
};
fcm.send(message, function(err, response) {
console.log("sent");
if (err) {
console.log(err);
console.log("Something has gone wrong!");
cb(err);
} else {
console.log("Successfully sent with response: ", response);
cb(null, response);
}
});
};
Can anyone help me?
There are some open issues exists for the above mentioned node modules.. I have used the request module and connected the fcm push notification url directly without depending on these types of npm modules with open issues.
var request = require('request');
request({
url: 'fcm.googleapis.com/fcm/send',
method: "POST",
headers: { "content-type" : "application/json",
"Authorization": key=${constants.FCM_SERVER_KEY},
"project_id": constants.FCM_SENDER_ID, },
json: message
}, function(error, response, body) {
//You code
});
I am trying to call an external rest API from node server by using request node module.
let request = require('request');
var options = {
method: 'POST',
url: 'https://somerestURI:3000',
qs: { msg: 'some|data|for|other|server' }
};
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(body);
});
If I try to run the above code, query string value is being encoded to
some%7cdata%7cfor%7cother%7cserver
as a result I am not receiving correct response.
But if I fire the same request in POSTMAN. I am receiving the expected output(I think postman is not encoding query string).
So what I want is don't encode the query string value.
Any help would be greatly appreciated.
As answered here, you can disable encoding in qsStringifyOptions
var options = {
method: 'POST',
url: 'https://somerestURI:3000',
qs: { msg: 'some|data|for|other|server' },
qsStringifyOptions: {
encoding: false
}
};
You can use node-rest-client package. It allows connecting to any REST API and get results as javascript Object.
var HttpClient = require('node-rest-client').Client;
var httpClient = new HttpClient();
// GET Call
httpClient.get("http://remote.site/rest/xml/method", function (data, response) {
// parsed response body as js object
console.log(data);
// raw response
console.log(response);
});)
or for POST Call
var args = {
data: { test: "hello" },
headers: { "Content-Type": "application/json" }
};
//POST Call
httpClient.post("http://remote.site/rest/xml/method", args, function (data, response) {
// parsed response body as js object
console.log(data);
// raw response
console.log(response);
});