diff --git a/lib/file_uploader.js b/lib/file_uploader.js index ffaf1b94..95d909c0 100644 --- a/lib/file_uploader.js +++ b/lib/file_uploader.js @@ -2,6 +2,8 @@ var assert = require('assert'); var fs = require('fs'); var mime = require('mime'); var util = require('util'); +var request = require('http'); +var fileType = require('file-type'); var MAX_FILE_SIZE_BYTES = 15 * 1024 * 1024; var MAX_FILE_CHUNK_BYTES = 5 * 1024 * 1024; @@ -14,18 +16,28 @@ var MAX_FILE_CHUNK_BYTES = 5 * 1024 * 1024; * console.log(err, bodyObj); * }) * - * @param {Object} params Object of the form { file_path: String }. + * var request = require('request') + * var stream = request('http://www.domain.com/file_to_upload.ext') + * var fu = new FileUploader({ file_stream: stream }, twit); + * fu.upload(function (err, bodyObj, resp) { + * console.log(err, bodyObj); + * }) + * + * @param {Object} params Object of the form { file_path: String } or { file_strea: ReadableStream } * @param {Twit(object)} twit Twit instance. */ var FileUploader = function (params, twit) { assert(params) - assert(params.file_path, 'Must specify `file_path` to upload a file. Got: ' + params.file_path + '.') + assert(params.file_path || params.file_stream, 'Must specify `file_path` or `file_stream` to upload a file.') var self = this; self._file_path = params.file_path; + self._file_stream = params.file_stream; + self._remoteUpload = self._file_stream ? true : false; self._twit = twit; self._isUploading = false; self._isFileStreamEnded = false; self._isSharedMedia = !!params.shared; + if (self._remoteUpload) {self._file_stream.pause();} } /** @@ -44,7 +56,7 @@ FileUploader.prototype.upload = function (cb) { } else { var mediaTmpId = bodyObj.media_id_string; var chunkNumber = 0; - var mediaFile = fs.createReadStream(self._file_path, { highWatermark: MAX_FILE_CHUNK_BYTES }); + var mediaFile = self._file_stream || fs.createReadStream(self._file_path, { highWatermark: MAX_FILE_CHUNK_BYTES }); mediaFile.on('data', function (chunk) { // Pause our file stream from emitting `data` events until the upload of this chunk completes. @@ -76,6 +88,7 @@ FileUploader.prototype.upload = function (cb) { self._finalizeMedia(mediaTmpId, cb); } }); + mediaFile.resume(); } }) } @@ -117,6 +130,38 @@ FileUploader.prototype._appendMedia = function(media_id_string, chunk_part, segm }, cb); } +FileUploader.prototype._getFileInfoForUpload = function(cb) { + var self = this; + + if (self._remoteUpload === true) { + + request.get(self._file_stream.uri.href, function(res){ + res.once('data', function(chunk){ + + var len = res.headers['content-length'] + if (!len) { + return cb(new Error('Unable to determine file size')) + } + len = +len + if (len !== len) { + return cb(new Error('Invalid Content-Length received')) + } + + var mediaFileSizeBytes = +res.headers['content-length'] + var mediaType = fileType(chunk)["mime"]; + + cb(null, mediaType, mediaFileSizeBytes); + res.destroy(); + }); + }); + } + else { + var mediaType = mime.lookup(self._file_path); + var mediaFileSizeBytes = fs.statSync(self._file_path).size; + cb(null, mediaType, mediaFileSizeBytes); + } +} + /** * Send INIT command for our underlying media object. * @@ -124,31 +169,30 @@ FileUploader.prototype._appendMedia = function(media_id_string, chunk_part, segm */ FileUploader.prototype._initMedia = function (cb) { var self = this; - var mediaType = mime.lookup(self._file_path); - var mediaFileSizeBytes = fs.statSync(self._file_path).size; - var shared = self._isSharedMedia; - var media_category = 'tweet_image'; - - if (mediaType.toLowerCase().indexOf('gif') > -1) { - media_category = 'tweet_gif'; - } else if (mediaType.toLowerCase().indexOf('video') > -1) { - media_category = 'tweet_video'; - } - // Check the file size - it should not go over 15MB for video. - // See https://dev.twitter.com/rest/reference/post/media/upload-chunked - if (mediaFileSizeBytes < MAX_FILE_SIZE_BYTES) { - self._twit.post('media/upload', { - 'command': 'INIT', - 'media_type': mediaType, - 'total_bytes': mediaFileSizeBytes, - 'shared': shared, - 'media_category': media_category - }, cb); - } else { - var errMsg = util.format('This file is too large. Max size is %dB. Got: %dB.', MAX_FILE_SIZE_BYTES, mediaFileSizeBytes); - cb(new Error(errMsg)); - } + self._getFileInfoForUpload(function(err, mediaType, mediaFileSizeBytes){ + var shared = self._isSharedMedia; + var media_category = 'tweet_image'; + if (mediaType.toLowerCase().indexOf('gif') > -1) { + media_category = 'tweet_gif'; + } else if (mediaType.toLowerCase().indexOf('video') > -1) { + media_category = 'tweet_video'; + } + // Check the file size - it should not go over 15MB for video. + // See https://dev.twitter.com/rest/reference/post/media/upload-chunked + if (mediaFileSizeBytes < MAX_FILE_SIZE_BYTES) { + self._twit.post('media/upload', { + 'command': 'INIT', + 'media_type': mediaType, + 'total_bytes': mediaFileSizeBytes, + 'shared': shared, + 'media_category': media_category + }, cb); + } else { + var errMsg = util.format('This file is too large. Max size is %dB. Got: %dB.', MAX_FILE_SIZE_BYTES, mediaFileSizeBytes); + cb(new Error(errMsg)); + } + }); } module.exports = FileUploader diff --git a/package.json b/package.json index a8bcdcaa..f0821e40 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ ], "dependencies": { "bluebird": "^3.1.5", + "file-type": "^3.9.0", "mime": "^1.3.4", "request": "^2.68.0" },