How to upload an image to Slack using node.js on W

2019-08-27 16:33发布

问题:

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:

  • Can I upload an image as attachment with Slack API?
  • Slack API (files.upload) using NodeJS
  • fix files.upload from Buffer with formData options #307

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 ?

回答1:

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


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