Slack API (files.upload) using NodeJS - node.js

EDITED
I'm trying to structure the files.upload() API provided via Slack but am having a hard time understanding the correct format. At the moment, I am able to use the API to upload a text file but cannot for the life of me figure out how to upload an image.
Here's my issue: I have an image on my development server, let's call it image.png. I want to use the files.upload() API to post that image into a #general Slack channel. Below is the code I have that is successfully generating the image, but currently is just sending the text:
var myBarChart = new Chart(ctx).Bar(barChartData, barChartOptions);
var myBarChartDataURL = leaderboardBarChart.toBase64Image();
canvas.toBuffer(function(err, buf) {
if (err) throw err;
fs.writeFile(__dirname + "/leaderboard.png", buf);
});
bot.api.files.upload({
token: process.env.token,
title: "Image",
filename: "image.png",
filetype: "auto",
//content: "Posted with files.upload API",
file: fs.createReadStream("path/to/image_file.png"),
channels: filtered[0].id
}, function(err, response) {
if (err) {
console.log("Error (files.upload) " + err);
} else {
console.log("Success (files.upload) " + response);
};
});
When I run the code I get one of the following error:
"invalid_array_arg" which Slack details as: "The method was passed a PHP-style array argument (e.g. with a name like foo[7]). These are never valid with the Slack API."
I'm not entirely sure what to make of this error as I'm not using PHP nor anything that I can identify that would be PHP-like.
I've experimented with several different approaches for including the file path, whether using the 'fs' module, storing it in a variable, or just referencing it's absolute path (and even a relative path). I'm a bit lost and am just looking for some guidance.
I understand that this particular API uses multipart/form-data but I don't have a form. This app is strictly a NodeJS app. There is no framework (like Express) working in tandem with the main node script.
Any and all help is really appreciated. Again, just looking for some insight/guidance on what I'm missing or doing wrong.
Thanks in advance!

It looks like you'll have to go outside of Botkit's API here, since Botkit doesn't seem to support sending multipart/form-data.
Give this a try, using request directly (already in use by Botkit itself):
var request = require('request');
...
request.post({
url: 'https://slack.com/api/files.upload',
formData: {
token: bot.config.token,
title: "Image",
filename: "image.png",
filetype: "auto",
channels: filtered[0].id,
file: fs.createReadStream('test.png'),
},
}, function (err, response) {
console.log(JSON.parse(response.body));
});

I recommend you using the nodejslack.
It uses the Promises pattern, powered by Bluebird .
There is a sample code for uploading file in its documentations, here it is:
var Slack = require('nodejslack');
var fs = require('fs');
var SLACK_TOKEN = process.env.SLACK_TOKEN || 'YOUR_GENERATED_SLACK_TOKEN';
var slack = new Slack(SLACK_TOKEN);
var form = {
file: fs.createReadStream('test.csv'), // Optional, via multipart/form-data. If omitting this parameter, you MUST submit content
// content: 'Your text here', // Optional, File contents. If omitting this parameter, you must provide a `file`
filename: 'test.csv', // Required
fileType: 'post', // Optional, See more file types in https://api.slack.com/types/file#file_types
title: 'Title of your file!', // Optional
initial_comment: 'First comment about this file.', // Optional
channels: 'general' //Optional, If you want to put more than one channel, separate using comma, example: 'general,random'
};
slack.fileUpload(form)
.then(function(response){
// Slack sends a json with a boolean var ok.
// Error example : data = { ok: false, error: 'user_not_found' }
// Error example : data = { ok: true, file: 'user_not_found' }
if(!response || !response.ok){
return Promise.reject(new Error('Something wrong happened during the upload.'));
}
console.log('Uploaded Successfully:',response);
return Promise.resolve(response);
})
.catch(function(err){
return err;
});

Related

no_file_data response in slack file upload

I'm using node.js to try to upload a csv file via slackAPI's upload file method. The method is post. I'm unsure how to make this possible because if I use the content argument instead of the file, I get the error:
{ ok: false, error: 'invalid_array_arg' }
If I use the file aargument, I still get the error:
{ ok: false, error: 'invalid_array_arg' }
There are multiple fault points in this code and I've tried to test each one but I'm sure I'm missing some information here. Here's the uploadFile Method that I created:
function uploadFile(file){
console.log(botToken);
axios.post('https://slack.com/api/files.upload', qs.stringify({token: botToken, file: file, channels: 'testing'}))
.then(function (response) {
var serverMessage = response.data;
console.log(serverMessage);
console.log("inside file upload function");
})
}
here's how I call the method:
var file = fs.createReadStream(__dirname + '/' + csvFilePath); // <--make sure this path is correct
console.log(__dirname + '/' + csvFilePath);
uploadFile(file);
And finally the output:
Bot has started!
C:\Users\i502153\WebstormProjects\slackAPIProject/accessLogs/CSV/1548430592860output.csv*
{ ok: false, error: 'invalid_array_arg' }
inside file upload function
What am I doing wrong and how to rectify this?
Links:
https://api.slack.com/methods/files.upload
https://www.npmjs.com/package/axios
Your solution won't work because you are attempting to take a stream object (file) and stringify it into a query string, which is just going to insert the nonsense string "[object]" into the query. It won't actually stream data to Slack.
Axios, unfortunately, doesn't work in node exactly like it does in the browser, and their docs can be a little confusing.
I would suggest an approach like this (untested):
const axios = require('axios');
const FormData = require('form-data');
function uploadFile(file) {
const form = new FormData();
form.append('token', botToken);
form.append('channels, 'testing');
form.append('file', file, 'optionalfilenamehere');
return axios.post('https://slack.com/api/files.upload', form, {
headers: form.getHeaders()
}).then(function (response) {
var serverMessage = response.data;
console.log(serverMessage);
console.log('inside file upload function');
});
}
I adapted this code from the suggestion in ticket https://github.com/axios/axios/issues/1006#issuecomment-320165427, there may be other helpful comments there as well if you run into issues. Good luck!
EDIT: For people reading this later, for a similar approach using request instead of axios, see related question Slack API (files.upload) using NodeJS.

Streaming a PDF File to the Browser Using Dynamically Generated HTML in Node.js

Okay, I see a few versions of this question out here, but I think mine goes a bit deeper than some of the others and I don't see satisfactory answers.
What I Want:
I have a page on a website that will have its contents frequently changed by users, and I want them to hit a button that says 'Generate PDF', and be able to turn the relevant information on the page into a nice looking PDF report.
I'm trying to do this with NodeJS, Express, with a deployment on Heroku.
Generically, this is how I'm trying to accomplish this:
MY PLAN:
Initiate a GET Request
Query a database for relevant results
Load data into an EJS template
Save the resulting HTML
Use the npm module HTML-pdf to stream it into the browser
Other important details:
This file is meant to be ephemeral. I don't want to save it
anywhere.
I prefer not to even have to write it anywhere, since it's only
meant to exist in the browser at that moment.
This app is deployed on Heroku, and need to work within its
constructs. I prefer not to use a file store like S3.
MY CODE:
router.get('/formulas/:id/pdf', function(req, res){
var db = req.db.collection('users');
var id = new ObjectID(req.params.id);
var pointer = {"formulas.$": 1, "_id": 0};
//query database, get the info out
db.aggregate([
{$match: {"formulas.f_id": id}},
{$unwind: "$formulas"},
{$match: {"formulas.f_id": id}},
{$project : {"formulas": 1, "_id": 0}}
]).toArray(function(e, doc){
if (e) {
throw e;
} else {
var html = null;
ejs.renderFile('./views/pdf.ejs', { //create template from db query
project: doc[0].formulas,
title: 'Formula Info Report',
description: 'PDF Report For Your Formula by Nutraceutical Pro',
ID: 'pdf',
keywords: 'PDF, PDF generator, Formula Info Report',
user: req.user,
loggedIn: req.isAuthenticated()
}, function(err, results){
if (err) {
console.log(err);
}
html = results; //save results of HTML output
});
var options = { format: 'Letter' };
var path = './public/pdf/formula-' + req.params.id + '.pdf';
pdf.create(html, options).toStream(function(err, stream) {//initiate stream
if (err) {
return console.log(err);
}
if (stream) {
console.log(stream);
stream.pipe(fs.createWriteStream(path));
console.log("the pdf was streamed.");
res.end();
}
});
}
});
});
I initiate the GET request with an AJAX call like so:
$('.getPDF').click(function(){
var filepath = 'https://my-domain.herokuapp.com/pdf/formula-' + this.id + '.pdf';
$.ajax({
url: '/formulas/'+ this.id +'/pdf',
type: 'GET',
success: function () {window.open(filepath);}
});
When I run the script I generate a stream, and the console logs that the PDF was created. If I just save the resulting file to my local system it comes out fine, but in my app I get the following error message:
Cannot GET /pdf/formula-59cc38992fb99b00045832ad.pdf
So it's like the raw material for the PDF is being created, but it's not being logged and created in such a way that allows the app to read it.
I'm open to a variety of solutions for this, so if there are much easier ways to do it I'm all ears. Prefer to stick with my current stack though.

Efficient way to read file in NodeJS

I am receiving an image file sent from an Ajax request:
var data = canvas.toDataURL('image/jpg', 1.0);
$.post({
url: "/upload-image",
data: {
file: data
}
}).done(function(response) {
....
})
}
And on the server side, I want to transmit the image file to an API
function getOptions(buffer) {
return {
url: '.../face_detection',
headers: headers,
method: 'POST',
formData: {
filename: buffer
}
}
}
router.post('/upload-image', function(req, res, next) {
console.log('LOG 0' + Date.now());
var data_url = req.body.file;
var matches = data_url.match(/^data:.+\/(.+);base64,(.*)$/);
var ext = matches[1];
var base64_data = matches[2];
var buffer = new Buffer(base64_data, 'base64');
console.log('LOG 1' + Date.now());
request(getOptions(buffer), function(error, response, body) {
res.json(body);
console.log(Date.now());
});
});
The problem that I have is that the lines between LOG 0 and LOG 1 are very slow, a few seconds. But the image is only 650kb. Is there a way to accelerate this?
Using another method to read the header, avoid the buffer, change the uploading process. I don't know but I'd like to be faster.
Thank you very much.
I would suggest using a library to handle some of this logic. If you would prefer to keep a lean dependency list, you can take a look at the source of some of these modules and base your own solution off of them.
For converting a data URI to a buffer: data-uri-to-buffer
For figuring out a file type: file-type
I would especially recommend the file-type solution. A safer (can't say safest) way to ensure what kind of file a Buffer is is to inspect aspects of the file. file-type seems to at least take a look at the Magic Number of the file to check type. Not foolproof, but if you are accepting files from users, you have to accept the risks involved.
Also have a look at Security Stack Exchange questions for good practices. Although the following say PHP, all server software runs the risk of being vulnerable to user input:
Hacker used picture upload to get PHP code into my site
Can simply decompressing a JPEG image trigger an exploit?
Risks of a PHP image upload form
"use strict";
const dataUriToBuffer = require('data-uri-to-buffer'),
fileType = require("file-type"),
express = require("express"),
router = express.Router(),
util = require("util"),
fs = require("fs"),
path = require("path");
const writeFileAsync = util.promisify(fs.writeFile);
// Keep track of file types you support
const supportedTypes = [
"png",
"jpg",
"gif"
];
// Handle POSTs to upload-image
router.post("/upload-image", function (req, res, next) {
// Did they send us a file?
if (!req.body.file) {
// Unprocessable entity error
return res.sendStatus(422);
}
// Get the file to a buffer
const buff = dataUriToBuffer(req.body.file);
// Get the file type
const bufferMime = fileType(buff); // {ext: 'png', mime: 'image/png'}
// Is it a supported file type?
if (!supportedTypes.contains(bufferMime.ext)) {
// Unsupported media type
return res.sendStatus(415);
}
// Save or do whatever with the file
writeFileAsync(path.join("imageDir", `userimage.${bufferMime.ext}`), buff)
// Tell the user that it's all done
.then(() => res.sendStatus(200))
// Log the error and tell the user the save failed
.catch((err) => {
console.error(err);
res.sendStatus(500);
});
});

How to post multiple text files in Node.js?

I'm developing a node.js application. And I want to use HTTP POST for posting multiple local text files to target web server.
Now I use request npm, so are there any ways to implement my goal with request npm? And of course, I will appreciate other solutions with different libraries.
I meant HTTP POST is executed by node.js itself, not client javascript. With node.js, I want to post multiple local text files to another server.
the most friendly method to send file is proposed by needle
var needle = require('needle');
var data = {
file: '/home/johnlennon/walrus.png',
content_type: 'image/png'
};
needle
.post('https://my.server.com/foo', data, { multipart: true })
.on('readable', function() { /* eat your chunks */ })
.on('end', function() {
console.log('Ready-o, friend-o.');
})
or
needle.post('https://my.server.com/foo', data, {multipart: true},
function(err,result) {
if(err) {
console.log(err);
}
});
Also, i haven't tried, but documentation says that you can pass an array of objects
var data = [
{
file: '/home/johnlennon/walrus1.png',
content_type: 'image/png'
},
{
file: '/home/johnlennon/walrus2.png',
content_type: 'image/png'
}
]

Node.js using heroku temp directory and nodemailer

I want to save files temporarily to my heroku file system so that I can send them in an email. As long as the email sends with the attachment, I don't care what happens to the file after that.
var path = require("path");
var temp_dir = path.join(process.cwd(), 'temp/');
if (!fs.existsSync(temp_dir))
{fs.mkdirSync(temp_dir);}
request.get({
url: 'https://docs.google.com/spreadsheets/export?id='+posting.driveID+'&exportFormat=xlsx',
encoding: null, // Force Request to return the data as Buffer
headers: {
Authorization: "Bearer "+access_token
}
}, function done (err, res) {
fs.writeFile('temp/temp.xlsx', res.body, function (err) {
console.log(err);
})
});
var mailOptions={
from : "",
to : "",
subject : "new download",
generateTextFromHTML : true,
html : "<h2>Download "+posting.title+"</h2>",
attachments: [{
filePath: "temp/temp.xlsx"
}]
};
transporter.sendMail(mailOptions, function(error, resp){
if(error) {
}
else{
console.log("Message sent: " + resp.message);
}
transporter.close();
});
I heard that heroku had the ability to host a /temp directory that wipes itself when the dyno recharges. I have tried to use that but have not had any luck. I receive the email with the temp.xlsx document but it is corrupt with no content. Making wonder if nodemailer cannot find the file so it just makes one with that name.
There is no clear documentation on using heroku's file system so I am wondering where my issue is. Otherwise I may just switch over to S3.
Heroku doesn't really do that. There is a way, but it's unreliable.
Heroku instances are designed to be read only. This let's you spin up/down as many as you like to scale.
The Heroku way would be to either:
Store them in memory (if they are only needed for that instance)
Store them in a database (if they are used by all instances)
Note that you can send a stream as an attachment. You don't need to use an actual file.
res.attachment('pdfname.pdf');
someStream.pipe(res);
http://expressjs.com/en/api.html#res.attachment

Resources