From cb248fe92f1f4b66ab8db48bbece9ed3b779f217 Mon Sep 17 00:00:00 2001 From: Tunde Ashafa Date: Sun, 29 May 2016 00:34:08 -0400 Subject: [PATCH 01/17] Add new json payload path --- lib/twitter.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/twitter.js b/lib/twitter.js index 6a0142fb..80df8b6c 100644 --- a/lib/twitter.js +++ b/lib/twitter.js @@ -30,7 +30,8 @@ var FORMDATA_PATHS = [ ]; var JSONPAYLOAD_PATHS = [ - 'media/metadata/create' + 'media/metadata/create', + 'collections/entries/curate' ] // From bb1dd900359d58ac80527d3106e3252c29ff83ea Mon Sep 17 00:00:00 2001 From: joey patino Date: Sat, 22 Oct 2016 18:46:50 +0200 Subject: [PATCH 02/17] Allow uploading media files from file streams --- lib/file_uploader.js | 83 ++++++++++++++++++++++++++++++++++---------- package.json | 1 + 2 files changed, 66 insertions(+), 18 deletions(-) diff --git a/lib/file_uploader.js b/lib/file_uploader.js index 6804c490..f57362eb 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,17 +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; + + if (self._remoteUpload) {self._file_stream.pause();} } /** @@ -43,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. @@ -75,6 +88,7 @@ FileUploader.prototype.upload = function (cb) { self._finalizeMedia(mediaTmpId, cb); } }); + mediaFile.resume(); } }) } @@ -116,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. * @@ -123,21 +169,22 @@ 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; - - // 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)); - } + + self._getFileInfoForUpload(function(err, mediaType, mediaFileSizeBytes){ + + // 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 diff --git a/package.json b/package.json index 66da3c16..14040c53 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" }, From f3ce6629dc4b0a2cdbb38f3309f6671216c9ef96 Mon Sep 17 00:00:00 2001 From: Michael Pimentel Date: Fri, 20 Oct 2017 08:03:48 -0700 Subject: [PATCH 03/17] dm example for twitter api --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index fe242b74..b8c8cca7 100644 --- a/README.md +++ b/README.md @@ -407,6 +407,32 @@ stream.on('direct_message', function (directMsg) { //... }) ``` +Example for a DM response when a user follows +```javascript + +// Settig up a user stream +var stream = T.stream('user'); + +function followed(eventMsg) { + console.log('Follow event!'); + var name = eventMsg.source.name; + var screenName = eventMsg.source.screen_name; + + // Anytime Someone follows me + stream.on('follow', followed); + + // the post request for direct messages > need to add a function to handle errors + + setTimeout(function() { // wait 60 sec before sending direct message. + console.log("Direct Message sent"); + T.post("direct_messages/new", { + screen_name: screenName, + text: 'Thanks for following' + ' ' + screenName + '! ' + ' What you want to be sent to a new follower ' + }); + }, 1000*10); // will respond via direct message 10 seconds after a user follows. +}; +``` + ## event: 'user_event' From fa83df40482df2892423567a0934f65ff1832b45 Mon Sep 17 00:00:00 2001 From: Michael Pimentel Date: Tue, 29 May 2018 20:55:56 -0700 Subject: [PATCH 04/17] added an examples.md file with example to create a user stream --- README.md | 26 -------------------------- examples.md | 25 +++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 26 deletions(-) create mode 100644 examples.md diff --git a/README.md b/README.md index b8c8cca7..4d5e75fd 100644 --- a/README.md +++ b/README.md @@ -406,32 +406,6 @@ Emitted when a direct message is sent to the user. Unfortunately, Twitter has no stream.on('direct_message', function (directMsg) { //... }) -``` -Example for a DM response when a user follows -```javascript - -// Settig up a user stream -var stream = T.stream('user'); - -function followed(eventMsg) { - console.log('Follow event!'); - var name = eventMsg.source.name; - var screenName = eventMsg.source.screen_name; - - // Anytime Someone follows me - stream.on('follow', followed); - - // the post request for direct messages > need to add a function to handle errors - - setTimeout(function() { // wait 60 sec before sending direct message. - console.log("Direct Message sent"); - T.post("direct_messages/new", { - screen_name: screenName, - text: 'Thanks for following' + ' ' + screenName + '! ' + ' What you want to be sent to a new follower ' - }); - }, 1000*10); // will respond via direct message 10 seconds after a user follows. -}; -``` ## event: 'user_event' diff --git a/examples.md b/examples.md new file mode 100644 index 00000000..d21d67e0 --- /dev/null +++ b/examples.md @@ -0,0 +1,25 @@ +Example for a DM response when a user follows +```javascript + +// Setting up a user stream +var stream = T.stream('user'); + +function followed(eventMsg) { + console.log('Follow event!'); + var name = eventMsg.source.name; + var screenName = eventMsg.source.screen_name; + + // Anytime Someone follows me + stream.on('follow', followed); + + // the post request for direct messages > need to add a function to handle errors + + setTimeout(function() { // wait 60 sec before sending direct message. + console.log("Direct Message sent"); + T.post("direct_messages/new", { + screen_name: screenName, + text: 'Thanks for following' + ' ' + screenName + '! ' + ' What you want to be sent to a new follower ' + }); + }, 1000*10); // will respond via direct message 10 seconds after a user follows. +}; +``` \ No newline at end of file From 6d71dbb6b5b8aa563f9648030819abda8dc53c61 Mon Sep 17 00:00:00 2001 From: Phong Thieu Date: Sun, 9 Sep 2018 02:41:07 -0400 Subject: [PATCH 05/17] FileUploader: use correct highWaterMark field --- lib/file_uploader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/file_uploader.js b/lib/file_uploader.js index ffaf1b94..1028bdae 100644 --- a/lib/file_uploader.js +++ b/lib/file_uploader.js @@ -44,7 +44,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 = 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. From 7960a09ee96a9f91021c13534abbfa5461cbbf81 Mon Sep 17 00:00:00 2001 From: Hal Massey Date: Fri, 19 Oct 2018 10:58:44 -0500 Subject: [PATCH 06/17] Add headers to REST error object --- lib/twitter.js | 1 + tests/rest.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/lib/twitter.js b/lib/twitter.js index 9540857e..aa315967 100644 --- a/lib/twitter.js +++ b/lib/twitter.js @@ -343,6 +343,7 @@ Twitter.prototype._doRestApiRequest = function (reqOpts, twitOptions, method, ca // place the errors in the HTTP response body into the Error object and pass control to caller var err = helpers.makeTwitError('Twitter API Error') err.statusCode = response ? response.statusCode: null; + err.headers = response ? response.headers : null; helpers.attachBodyInfoToError(err, body); callback(err, body, response); return diff --git a/tests/rest.js b/tests/rest.js index 63fcbdbf..5d95d5a9 100644 --- a/tests/rest.js +++ b/tests/rest.js @@ -638,6 +638,7 @@ describe('REST API', function () { assert(err.statusCode === 401) assert(err.code > 0) assert(err.message.match(/token/)) + assert(typeof err.headers === 'object') assert(err.twitterReply) assert(err.allErrors) assert(res) @@ -654,6 +655,7 @@ describe('REST API', function () { .catch(err => { assert(err instanceof Error) assert(err.statusCode === 401) + assert(typeof err.headers === 'object') assert(err.code > 0) assert(err.message.match(/token/)) assert(err.twitterReply) From ad9376a990cf935e111dfd483d2650ab6c33d7f8 Mon Sep 17 00:00:00 2001 From: Phong Thieu Date: Sun, 9 Sep 2018 15:59:49 -0400 Subject: [PATCH 07/17] FileUploader: add case for video upload size Twitter allows video to be 512MB, so limiting to 15MB is severely limiting a lot of functionality. This will keep all formats not video to remain the same while changing the allowed total file byte size and max chunk byte size to allow for correct limits --- lib/file_uploader.js | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/file_uploader.js b/lib/file_uploader.js index 1028bdae..8faab551 100644 --- a/lib/file_uploader.js +++ b/lib/file_uploader.js @@ -5,6 +5,9 @@ var util = require('util'); var MAX_FILE_SIZE_BYTES = 15 * 1024 * 1024; var MAX_FILE_CHUNK_BYTES = 5 * 1024 * 1024; +var MAX_VIDEO_SIZE_BYTES = 512 * 1024 * 1024; +var MAX_VIDEO_CHUNK_BYTES = + parseInt(process.env.MAX_VIDEO_CHUNK_BYTES) || 15 * 1024 * 1024; /** * FileUploader class used to upload a file to twitter via the /media/upload (chunked) API. @@ -42,9 +45,10 @@ FileUploader.prototype.upload = function (cb) { cb(err); return; } else { + var MAX_CHUNK_BYTES = bodyObj.MAX_CHUNK_BYTES || MAX_FILE_CHUNK_BYTES; var mediaTmpId = bodyObj.media_id_string; var chunkNumber = 0; - var mediaFile = fs.createReadStream(self._file_path, { highWaterMark: MAX_FILE_CHUNK_BYTES }); + var mediaFile = fs.createReadStream(self._file_path, { highWaterMark: MAX_CHUNK_BYTES }); mediaFile.on('data', function (chunk) { // Pause our file stream from emitting `data` events until the upload of this chunk completes. @@ -128,25 +132,35 @@ FileUploader.prototype._initMedia = function (cb) { var mediaFileSizeBytes = fs.statSync(self._file_path).size; var shared = self._isSharedMedia; var media_category = 'tweet_image'; + var MAX_FILE_BYTES = MAX_FILE_SIZE_BYTES; + var MAX_CHUNK_BYTES = MAX_FILE_CHUNK_BYTES; + if(mediaType.toLowerCase().indexOf('octet-stream') > -1) { + mediaType = 'video/mp4'; + } if (mediaType.toLowerCase().indexOf('gif') > -1) { media_category = 'tweet_gif'; } else if (mediaType.toLowerCase().indexOf('video') > -1) { + MAX_FILE_BYTES = MAX_VIDEO_SIZE_BYTES; + MAX_CHUNK_BYTES = MAX_VIDEO_CHUNK_BYTES; 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) { + if (mediaFileSizeBytes < MAX_FILE_BYTES) { self._twit.post('media/upload', { 'command': 'INIT', 'media_type': mediaType, 'total_bytes': mediaFileSizeBytes, 'shared': shared, 'media_category': media_category - }, cb); + }, function(err, bodyObj, resp) { + bodyObj.MAX_CHUNK_BYTES = MAX_CHUNK_BYTES; + cb(err, bodyObj, resp); + }); } else { - var errMsg = util.format('This file is too large. Max size is %dB. Got: %dB.', MAX_FILE_SIZE_BYTES, mediaFileSizeBytes); + var errMsg = util.format('This file is too large. Max size is %dB. Got: %dB.', MAX_FILE_BYTES, mediaFileSizeBytes); cb(new Error(errMsg)); } } From 63740e0476573fcadef14cb537f63361683db5ab Mon Sep 17 00:00:00 2001 From: it-fm <42847248+it-fm@users.noreply.github.com> Date: Sun, 20 Jan 2019 08:10:55 -0200 Subject: [PATCH 08/17] Fix banner update URL Fix banner update --- lib/twitter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/twitter.js b/lib/twitter.js index 9540857e..ef3bf733 100644 --- a/lib/twitter.js +++ b/lib/twitter.js @@ -26,7 +26,7 @@ var required_for_user_auth = required_for_app_auth.concat([ var FORMDATA_PATHS = [ 'media/upload', 'account/update_profile_image', - 'account/update_profile_background_image', + 'account/update_profile_banner', ]; var JSONPAYLOAD_PATHS = [ From f74c8ecc9cdbef0c810f6c8fc5b240398e569ef1 Mon Sep 17 00:00:00 2001 From: Andrew Barba Date: Thu, 2 May 2019 16:17:41 -0400 Subject: [PATCH 09/17] Use correct-content type when GET-ing a media/ path --- lib/twitter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/twitter.js b/lib/twitter.js index 9540857e..217a72ce 100644 --- a/lib/twitter.js +++ b/lib/twitter.js @@ -251,7 +251,7 @@ Twitter.prototype._buildReqOpts = function (method, path, params, isStreaming, c reqOpts.url = endpoints.REST_ROOT + path + '.json'; } - if (FORMDATA_PATHS.indexOf(path) !== -1) { + if (FORMDATA_PATHS.indexOf(path) !== -1 && method === 'POST') { reqOpts.headers['Content-type'] = 'multipart/form-data'; reqOpts.form = finalParams; // set finalParams to empty object so we don't append a query string From 09e14fa3ed27f1d20387c05013d70efcfcab5fd6 Mon Sep 17 00:00:00 2001 From: dyep49 Date: Tue, 11 Jun 2019 11:42:46 -0700 Subject: [PATCH 10/17] Add support for subtitles create and delete --- lib/file_uploader.js | 2 ++ lib/twitter.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/lib/file_uploader.js b/lib/file_uploader.js index ffaf1b94..e3015756 100644 --- a/lib/file_uploader.js +++ b/lib/file_uploader.js @@ -133,6 +133,8 @@ FileUploader.prototype._initMedia = function (cb) { media_category = 'tweet_gif'; } else if (mediaType.toLowerCase().indexOf('video') > -1) { media_category = 'tweet_video'; + } else if (mediaType.toLowerCase().indexOf('subrip') > -1) { + media_category = 'SUBTITLES' } // Check the file size - it should not go over 15MB for video. diff --git a/lib/twitter.js b/lib/twitter.js index 9540857e..b5bb7124 100644 --- a/lib/twitter.js +++ b/lib/twitter.js @@ -31,6 +31,8 @@ var FORMDATA_PATHS = [ var JSONPAYLOAD_PATHS = [ 'media/metadata/create', + 'media/subtitles/create', + 'media/subtitles/delete', 'direct_messages/events/new', 'direct_messages/welcome_messages/new', 'direct_messages/welcome_messages/rules/new', From 29d6c3e2c15c9d140b5b1184b1cbf7c0b73aa677 Mon Sep 17 00:00:00 2001 From: Karol Tyka Date: Tue, 23 Jul 2019 21:45:58 +0200 Subject: [PATCH 11/17] Change MAX_VIDEO_CHUNK_BYTES to 5MB --- lib/file_uploader.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/file_uploader.js b/lib/file_uploader.js index a24f2ee9..3d462ea7 100644 --- a/lib/file_uploader.js +++ b/lib/file_uploader.js @@ -8,7 +8,7 @@ var fileType = require('file-type'); var MAX_FILE_SIZE_BYTES = 15 * 1024 * 1024; var MAX_FILE_CHUNK_BYTES = 5 * 1024 * 1024; var MAX_VIDEO_SIZE_BYTES = 512 * 1024 * 1024; -var MAX_VIDEO_CHUNK_BYTES = 15 * 1024 * 1024; +var MAX_VIDEO_CHUNK_BYTES = 5 * 1024 * 1024; /** * FileUploader class used to upload a file to twitter via the /media/upload (chunked) API. From 5a98f9fd87f2bd9ee767d65311b02204a5b22552 Mon Sep 17 00:00:00 2001 From: Karol Tyka Date: Tue, 23 Jul 2019 21:47:34 +0200 Subject: [PATCH 12/17] Bump version --- README.md | 13 ++++++++++++- package.json | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 54972fff..4e89adef 100644 --- a/README.md +++ b/README.md @@ -577,8 +577,19 @@ THE SOFTWARE. ## Changelog +### 2.3.0 + * Allow uploading media files from file streams #1 @joeypatino + * Added an Direct message example for Twit API readme docs #2 @mtpjr88 + * Add headers to REST error object #3 @Harrison-M + * Fix banner update URL (update_profile_background_image is deprecated) #4 @it-fm + * Use correct-content type when GET-ing a media/ path #5 @AndrewBarba + * Add support for subtitles create and delete #6 @dyep49 + * Add new json payload path #7 @ashafa + * Video/upload chunk size #8 @pthieu + * Change MAX_VIDEO_CHUNK_BYTES to 5MB + ### 2.2.11 -* Fix media_category used for media uploads (thanks @BooDoo) + * Fix media_category used for media uploads (thanks @BooDoo) ### 2.2.10 * Update maximum Tweet characters to 280 (thanks @maziyarpanahi) diff --git a/package.json b/package.json index f0821e40..686f0d8f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "twit", "description": "Twitter API client for node (REST & Streaming)", - "version": "2.2.11", + "version": "2.3.0", "author": "Tolga Tezel", "keywords": [ "twitter", From 42f8770a64c884c16aca08fdf3dcf9220afe3a08 Mon Sep 17 00:00:00 2001 From: John Adams Date: Tue, 2 Jun 2020 17:17:47 -0700 Subject: [PATCH 13/17] patch the API to support proper post handing, and do not molest the reqOpts.body on POST requests --- lib/twitter.js | 74 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 11 deletions(-) diff --git a/lib/twitter.js b/lib/twitter.js index 217a72ce..a946d1b1 100644 --- a/lib/twitter.js +++ b/lib/twitter.js @@ -29,11 +29,19 @@ var FORMDATA_PATHS = [ 'account/update_profile_background_image', ]; +// if the path starts with anything on this list, then the +// payload will be sent as a json payload. var JSONPAYLOAD_PATHS = [ - 'media/metadata/create', - 'direct_messages/events/new', - 'direct_messages/welcome_messages/new', - 'direct_messages/welcome_messages/rules/new', + 'media/metadata/create', + 'direct_messages/events/new', + 'direct_messages/welcome_messages/new', + 'direct_messages/welcome_messages/rules/new', + 'insights/engagement', + 'insights/engagement/totals', + 'insights/engagement/28hr', + 'insights/engagement/historical', + 'search/30day', + 'search/fullarchive' ]; // @@ -97,9 +105,10 @@ Twitter.prototype.request = function (method, path, params, callback) { } var twitOptions = (params && params.twit_options) || {}; - + process.nextTick(function () { // ensure all HTTP i/o occurs after the user has a chance to bind their event handlers + self._doRestApiRequest(reqOpts, twitOptions, method, function (err, parsedBody, resp) { self._updateClockOffsetFromResponse(resp); var peerCertificate = resp && resp.socket && resp.socket.getPeerCertificate(); @@ -195,8 +204,18 @@ Twitter.prototype._buildReqOpts = function (method, path, params, isStreaming, c } // clone `params` object so we can modify it without modifying the user's reference var paramsClone = JSON.parse(JSON.stringify(params)) - // convert any arrays in `paramsClone` to comma-seperated strings - var finalParams = this.normalizeParams(paramsClone) + + // jna: only permit this to happen if we are doing a GET request. There is no + // reason to do this in a POST. it breaks things like the enterprise API. + + // convert any arrays in `paramsClone` to comma-separated strings + var finalParams; + if (method == 'GET') { + finalParams = this.normalizeParams(paramsClone) + } else { + finalParams = paramsClone; + } + delete finalParams.twit_options // the options object passed to `request` used to perform the HTTP request @@ -219,7 +238,31 @@ Twitter.prototype._buildReqOpts = function (method, path, params, isStreaming, c // finalize the `path` value by building it using user-supplied params // when json parameters should not be in the payload - if (JSONPAYLOAD_PATHS.indexOf(path) === -1) { + + // jna: The public version of this library does not deal with + // full paths. The matcher breaks substantially when the path + // starts with http(s)://. Fix this by extracting the path + // component and testing that instead of the full "path", which is + // actually a "url". + let testPath; + + try { + // if we can parse the string as a url, we must extract the path + const parsedUrl = new URL(path); + testPath = parsedUrl.pathname; + if (testPath[0] == '/') { + testPath = testPath.substring(1, testPath.length); + } + } catch(e) { + // we only expect TypeError here, which reads as "Invalid URL." + if (! e instanceof TypeError) { + throw e; // unexpected, so throw it again. Not our problem. + } + // just pass it through untouched. + testPath = path; + } + + if (JSONPAYLOAD_PATHS.indexOf(testPath) === -1) { try { path = helpers.moveParamsIntoPath(finalParams, path) } catch (e) { @@ -230,11 +273,20 @@ Twitter.prototype._buildReqOpts = function (method, path, params, isStreaming, c if (path.match(/^https?:\/\//i)) { // This is a full url request - reqOpts.url = path + reqOpts.url = path; + + // if the endpoint requires JSON, set up options correctly. + if (JSONPAYLOAD_PATHS.indexOf(testPath) !== -1) { + reqOpts.headers['Content-type'] = 'application/json'; + reqOpts.json = true; + reqOpts.body = finalParams; + + // avoid appending query string for body params + finalParams = {}; + } } else if (isStreaming) { // This is a Streaming API request. - var stream_endpoint_map = { user: endpoints.USER_STREAM, site: endpoints.SITE_STREAM @@ -257,7 +309,7 @@ Twitter.prototype._buildReqOpts = function (method, path, params, isStreaming, c // set finalParams to empty object so we don't append a query string // of the params finalParams = {}; - } else if (JSONPAYLOAD_PATHS.indexOf(path) !== -1) { + } else if (JSONPAYLOAD_PATHS.indexOf(testPath) !== -1) { reqOpts.headers['Content-type'] = 'application/json'; reqOpts.json = true; reqOpts.body = finalParams; From 4e41c25641578cb0301db6e4dffbcd72ec29d328 Mon Sep 17 00:00:00 2001 From: John Adams Date: Wed, 3 Jun 2020 09:46:53 -0700 Subject: [PATCH 14/17] bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 686f0d8f..1704bbd2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "twit", "description": "Twitter API client for node (REST & Streaming)", - "version": "2.3.0", + "version": "2.3.0-alembic", "author": "Tolga Tezel", "keywords": [ "twitter", From 833e3b71e2fb9165342a104948052d167263925d Mon Sep 17 00:00:00 2001 From: John Adams Date: Wed, 3 Jun 2020 09:50:16 -0700 Subject: [PATCH 15/17] bump alembic version to 2.3.1 --- README.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 89be301c..fc02b4ab 100644 --- a/README.md +++ b/README.md @@ -577,7 +577,7 @@ THE SOFTWARE. ## Changelog -### 2.3.0-alembic +### 2.3.1 (Alembic/Twothink, Inc. Internal Version) * Patch path handling when using full URLs instead of a path @netik * Fold-in @tykarol's 2.3.0 changes * Possible breaking change: Stop collapsing arrays in reqOpts.body if a POST request is in use (i.e. JSON). This subtly changes the creation of JSON objects and will break the enterprise API. diff --git a/package.json b/package.json index 1704bbd2..2f36d9e4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "twit", "description": "Twitter API client for node (REST & Streaming)", - "version": "2.3.0-alembic", + "version": "2.3.1", "author": "Tolga Tezel", "keywords": [ "twitter", From 827426480fdcb7b6f6b6d2d4165d85b4b9ffaf69 Mon Sep 17 00:00:00 2001 From: John Adams Date: Wed, 3 Jun 2020 09:51:45 -0700 Subject: [PATCH 16/17] fix readme a bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fc02b4ab..724c898f 100644 --- a/README.md +++ b/README.md @@ -580,7 +580,7 @@ THE SOFTWARE. ### 2.3.1 (Alembic/Twothink, Inc. Internal Version) * Patch path handling when using full URLs instead of a path @netik * Fold-in @tykarol's 2.3.0 changes - * Possible breaking change: Stop collapsing arrays in reqOpts.body if a POST request is in use (i.e. JSON). This subtly changes the creation of JSON objects and will break the enterprise API. + * Possible breaking change: Stop collapsing arrays in reqOpts.body if a POST request is in use (i.e. JSON). This fixes JSON object creation which is subtly broken in prior releases. * Clean up and expand JSONPAYLOD_PATHS to have enterprise API locations ### 2.3.0 From 9c641acd673a30485c190c4843a4398030729cfb Mon Sep 17 00:00:00 2001 From: John Adams Date: Wed, 3 Jun 2020 10:29:00 -0700 Subject: [PATCH 17/17] fix readme a bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 724c898f..d502d0ae 100644 --- a/README.md +++ b/README.md @@ -580,7 +580,7 @@ THE SOFTWARE. ### 2.3.1 (Alembic/Twothink, Inc. Internal Version) * Patch path handling when using full URLs instead of a path @netik * Fold-in @tykarol's 2.3.0 changes - * Possible breaking change: Stop collapsing arrays in reqOpts.body if a POST request is in use (i.e. JSON). This fixes JSON object creation which is subtly broken in prior releases. + * Possible breaking change: Stop collapsing arrays in reqOpts.body if a POST request is in use (i.e. JSON). This fixes JSON object creation which is subtly broken in prior releases. * Clean up and expand JSONPAYLOD_PATHS to have enterprise API locations ### 2.3.0