2
votes

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.

2
One issue is that you have to send message, filedata and recipient as form data, not json in the body. - amuramoto

2 Answers

2
votes

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

0
votes

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;
  }); 
}