1

I'm trying to upload an image via Slack using node.js and the request package, but not having much luck. Either I receive invalid_array_arg or no_file_data errors from the API.

Here is my request:

    var options = { method: 'POST',
      url: 'https://slack.com/api/files.upload',
      headers: 
       { 'cache-control': 'no-cache',
         'content-type': 'application/x-www-form-urlencoded' },
      form: 
       { token: SLACK_TOKEN,
         channels: SLACK_CHANNEL,
         file: fs.createReadStream(filepath)
        } };

    request(options, function (error, response, body) {
      if (error) throw new Error(error);

      console.log(body);
    });

I had a look at a few relevant posts:

The only thing that worked was using the curl command directly, but using cygwin (CommandPrompt failed: curl: (1) Protocol https not supported or disabled in libcurl). The issue calling curl from node (using child_process) but that silently fails in Command Prompt and still returns no_file_data using cygwin (passing an absolute path to the file):

stdout: {"ok":false,"error":"no_file_data"}
stderr:   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   469  100    35  100   434    359   4461 --:--:-- --:--:-- --:--:--  6112

I'm using node v6.9.1 on Windows.

What am I missing ? How can I upload an image to slack via node.js on Windows ?

George Profenza
  • 50,687
  • 19
  • 144
  • 218
  • I know this is not answer for your question, maybe you should consider to use [node-slack-sdk/](https://slackapi.github.io/node-slack-sdk/), in that case you don't need to implement these feature for yourself. [file upload documentation](https://slackapi.github.io/node-slack-sdk/web_api#uploading-a-file) – Peter Feb 06 '18 at 21:00
  • @Peter surprisingly getting the same error with the node-slack-sdk:`error: Response not OK: no_file_data Error: no_file_data` – George Profenza Feb 06 '18 at 22:17
  • @GeorgeProfenza the way to go is [files.upload](https://api.slack.com/methods/files.upload). – Erik Kalkoken Feb 06 '18 at 22:21
  • why is the solution from your #2 linked question not working for your? (https://stackoverflow.com/questions/38084459/slack-api-files-upload-using-nodejs) have you tried to reproduce with the same syntax? – Erik Kalkoken Feb 06 '18 at 22:25
  • Possible duplicate of [Slack API (files.upload) using NodeJS](https://stackoverflow.com/questions/38084459/slack-api-files-upload-using-nodejs) – Erik Kalkoken Feb 06 '18 at 22:29
  • what `fs.createReadStream(filepath).stat()` says? – Peter Feb 06 '18 at 22:29
  • anyway [this](https://github.com/slackapi/node-slack-sdk/issues/307#issuecomment-289231737) looks relevant too. – Peter Feb 06 '18 at 22:32
  • @Peter `console.log(fs.createReadStream(filename).stat()); ^ TypeError: fs.createReadStream(...).stat is not a function` – George Profenza Feb 06 '18 at 22:54
  • @ErikKalkoken I've mentioned in my question I already checked out the question you've marked as a duplicate. I admit they are very similar, but the solution there does not work in my case. Even though the error at the end is the same, the source of the error might be different. – George Profenza Feb 06 '18 at 22:56
  • How about trying this sample script? https://gist.github.com/tanaikech/40c9284e91d209356395b43022ffc5cc In my environment, your script also occurs the same error. So I created this. If this was not useful for you, I'm sorry. – Tanaike Feb 07 '18 at 00:57
  • @Tanaike the first explicit multipart header worked like charm! You should post this as an answer as well: it's super useful! Thank you very much – George Profenza Feb 08 '18 at 18:51
  • Thank you for your reply. I'm glad this was useful for you. I posted this script. Please confirm it. – Tanaike Feb 08 '18 at 21:55
  • If you have problems for my sample script, please tell me. I would like to study from the problems. – Tanaike Feb 10 '18 at 23:47
  • 1
    @Tanaike I wish I could give you more than one vote. Both solutions worked ! In the meantime I've ran more tests and found the problem: I tried to upload an image before it was fully written to disk. Once I ensure the file is fully written to disk, the second method works too. What's interesting is that your first method, manually creating the multipart header worked even when the image wasn't fully written to disk: it simply displayed gray pixels for the missing data. – George Profenza Feb 16 '18 at 16:34
  • Thank you for your additional information. I'm glad your problem was solved. And also I could study from your question. Thank you, too. – Tanaike Feb 16 '18 at 23:11
  • Hi @GeorgeProfenza, I'm running into the same error and I was wondering how you ensured that your image was fully written to disk. You said " I tried to upload an image before it was fully written to disk. Once I ensure the file is fully written to disk" that the methods worked. I'm trying to send a PDF as a opposed to an image but I am getting the same error and I have a feeling it's for the same reason – Carol Gonzalez Sep 17 '19 at 20:58
  • @CarolGonzalez In my case, I was downloading a couple of images, using graphics magick to make a montage of them, then upload the result, so in my case starting the upload process from `gm`'s `'close'` event. In your case it might be even listener available when the pdf file completed writing to disk. As a proof of concept you can try a hacky `setTimeout` to 10 seconds or something (if the PDF isn't huge) to isolate the issue and confirm it's the same issue. If that's the case ideally you would remove the hacky delay and use the correct event. Sorry about the delay in getting back. – George Profenza Sep 19 '19 at 17:32

2 Answers2

3

The Slack API error invalid_array_arg means that there is a problem with the format of the arguments passed to Slack. (see here)

When using the file property for files.upload, Slack excepts the data as multipart/form-data, not as application/x-www-form-urlencoded. So instead of form, you need to use formData in your request object. I also removed the incorrect part in the header.

This works:

      var fs = require('fs');
      var request = require('request');

      var SLACK_TOKEN = "xoxp-xxx";
      var SLACK_CHANNEL = "general";
      var filepath = "file.txt";

      var options = { method: 'POST',
      url: 'https://slack.com/api/files.upload',
      headers: 
       { 'cache-control': 'no-cache' },
      formData: 
       { token: SLACK_TOKEN,
         channels: SLACK_CHANNEL,
         file: fs.createReadStream(filepath)
        } };

    request(options, function (error, response, body) {
      if (error) throw new Error(error);

      console.log(body);
    });
Erik Kalkoken
  • 30,467
  • 8
  • 79
  • 114
  • Thank you for your answer. I've tried using `formData` as you suggested above, unfortunately I'm getting the same error. I will run a few more tests. Perhaps the data I'm uploading isn't ready ? – George Profenza Feb 08 '18 at 18:52
  • @GeorgeProfenza This code is tested and works. I have added the initialization just to make sure. Have you tried this exact code? And please let me know what error you are getting from the API exactly. – Erik Kalkoken Feb 08 '18 at 19:17
  • 1
    Apologies for the delay. I did some further tests and found the source of the problem: not the request itself, but the data and createReadStream results. I didn't wait for the image I was uploading to be fully written to disk before uploading. That is fixed now and both your code and Tanaike's code work. The interesting thing about his manual multipart approach is that it worked even when the jpeg wasn't fully written to disk (displaying the jpeg at the correct dimensions with a small portion of grey pixels) – George Profenza Feb 16 '18 at 16:22
2

In this sample script, it supposes to upload a binary file (zip file). When you use this, please modify for your environment. When files are uploaded to Slack, multipart/form-data is used. In my environment, there were the situations that files couldn't be uploaded by some libraries. So I created this. If this is useful for your environment, I'm glad.

Users can upload the binary file by converting byte array as follows.

  • At first, it builds form-data.
  • Adds the zip file converted to byte array and boundary using Buffer.concat().
  • This is used as body in request.

The sample script is as follows.

Sample script :

var fs = require('fs');
var request = require('request');
var upfile = 'sample.zip';
fs.readFile(upfile, function(err, content){
    if(err){
        console.error(err);
    }
    var metadata = {
        token: "### access token ###",
        channels: "sample",
        filename: "samplefilename",
        title: "sampletitle",
    };
    var url = "https://slack.com/api/files.upload";
    var boundary = "xxxxxxxxxx";
    var data = "";
    for(var i in metadata) {
        if ({}.hasOwnProperty.call(metadata, i)) {
            data += "--" + boundary + "\r\n";
            data += "Content-Disposition: form-data; name=\"" + i + "\"; \r\n\r\n" + metadata[i] + "\r\n";
        }
    };
    data += "--" + boundary + "\r\n";
    data += "Content-Disposition: form-data; name=\"file\"; filename=\"" + upfile + "\"\r\n";
    data += "Content-Type:application/octet-stream\r\n\r\n";
    var payload = Buffer.concat([
            Buffer.from(data, "utf8"),
            new Buffer(content, 'binary'),
            Buffer.from("\r\n--" + boundary + "\r\n", "utf8"),
    ]);
    var options = {
        method: 'post',
        url: url,
        headers: {"Content-Type": "multipart/form-data; boundary=" + boundary},
        body: payload,
    };
    request(options, function(error, response, body) {
        console.log(body);
    });
});
Tanaike
  • 181,128
  • 11
  • 97
  • 165