mirror of
https://github.com/thewesker/bug-em.git
synced 2025-12-21 20:41:05 -05:00
144 lines
4.5 KiB
JavaScript
144 lines
4.5 KiB
JavaScript
var assert = require('assert');
|
|
var fs = require('fs');
|
|
var mime = require('mime');
|
|
var util = require('util');
|
|
|
|
var MAX_FILE_SIZE_BYTES = 15 * 1024 * 1024;
|
|
var MAX_FILE_CHUNK_BYTES = 5 * 1024 * 1024;
|
|
|
|
/**
|
|
* FileUploader class used to upload a file to twitter via the /media/upload (chunked) API.
|
|
* Usage:
|
|
* var fu = new FileUploader({ file_path: '/foo/bar/baz.mp4' }, twit);
|
|
* fu.upload(function (err, bodyObj, resp) {
|
|
* console.log(err, bodyObj);
|
|
* })
|
|
*
|
|
* @param {Object} params Object of the form { file_path: String }.
|
|
* @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 + '.')
|
|
var self = this;
|
|
self._file_path = params.file_path;
|
|
self._twit = twit;
|
|
self._isUploading = false;
|
|
self._isFileStreamEnded = false;
|
|
}
|
|
|
|
/**
|
|
* Upload a file to Twitter via the /media/upload (chunked) API.
|
|
*
|
|
* @param {Function} cb function (err, data, resp)
|
|
*/
|
|
FileUploader.prototype.upload = function (cb) {
|
|
var self = this;
|
|
|
|
// Send INIT command with file info and get back a media_id_string we can use to APPEND chunks to it.
|
|
self._initMedia(function (err, bodyObj, resp) {
|
|
if (err) {
|
|
cb(err);
|
|
return;
|
|
} else {
|
|
var mediaTmpId = bodyObj.media_id_string;
|
|
var chunkNumber = 0;
|
|
var mediaFile = 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.
|
|
// Any data that becomes available will remain in the internal buffer.
|
|
mediaFile.pause();
|
|
self._isUploading = true;
|
|
|
|
self._appendMedia(mediaTmpId, chunk.toString('base64'), chunkNumber, function (err, bodyObj, resp) {
|
|
self._isUploading = false;
|
|
if (err) {
|
|
cb(err);
|
|
} else {
|
|
if (self._isUploadComplete()) {
|
|
// We've hit the end of our stream; send FINALIZE command.
|
|
self._finalizeMedia(mediaTmpId, cb);
|
|
} else {
|
|
// Tell our file stream to start emitting `data` events again.
|
|
chunkNumber++;
|
|
mediaFile.resume();
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
mediaFile.on('end', function () {
|
|
// Mark our file streaming complete, and if done, send FINALIZE command.
|
|
self._isFileStreamEnded = true;
|
|
if (self._isUploadComplete()) {
|
|
self._finalizeMedia(mediaTmpId, cb);
|
|
}
|
|
});
|
|
}
|
|
})
|
|
}
|
|
|
|
FileUploader.prototype._isUploadComplete = function () {
|
|
return !this._isUploading && this._isFileStreamEnded;
|
|
}
|
|
|
|
/**
|
|
* Send FINALIZE command for media object with id `media_id`.
|
|
*
|
|
* @param {String} media_id
|
|
* @param {Function} cb
|
|
*/
|
|
FileUploader.prototype._finalizeMedia = function(media_id, cb) {
|
|
var self = this;
|
|
self._twit.post('media/upload', {
|
|
command: 'FINALIZE',
|
|
media_id: media_id
|
|
}, cb);
|
|
}
|
|
|
|
/**
|
|
* Send APPEND command for media object with id `media_id`.
|
|
* Append the chunk to the media object, then resume streaming our mediaFile.
|
|
*
|
|
* @param {String} media_id media_id_string received from Twitter after sending INIT comand.
|
|
* @param {String} chunk_part Base64-encoded String chunk of the media file.
|
|
* @param {Number} segment_index Index of the segment.
|
|
* @param {Function} cb
|
|
*/
|
|
FileUploader.prototype._appendMedia = function(media_id_string, chunk_part, segment_index, cb) {
|
|
var self = this;
|
|
self._twit.post('media/upload', {
|
|
command: 'APPEND',
|
|
media_id: media_id_string.toString(),
|
|
segment_index: segment_index,
|
|
media: chunk_part,
|
|
}, cb);
|
|
}
|
|
|
|
/**
|
|
* Send INIT command for our underlying media object.
|
|
*
|
|
* @param {Function} cb
|
|
*/
|
|
FileUploader.prototype._initMedia = function (cb) {
|
|
var self = this;
|
|
var mediaType = mime.lookup(self._file_path);
|
|
var mediaFileSizeBytes = fs.statSync(self._file_path).size;
|
|
|
|
// 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
|
|
}, 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
|