This commit is contained in:
Talor Berthelson
2016-06-17 20:49:15 -04:00
commit 889faf9c1c
699 changed files with 112020 additions and 0 deletions

3
node_modules/twit/.npmignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
node_modules
config*.js
test.js

647
node_modules/twit/README.md generated vendored Normal file
View File

@@ -0,0 +1,647 @@
#twit
Twitter API Client for node
Supports both the **REST** and **Streaming** API.
#Installing
```
npm install twit
```
##Usage:
```javascript
var Twit = require('twit')
var T = new Twit({
consumer_key: '...',
consumer_secret: '...',
access_token: '...',
access_token_secret: '...',
timeout_ms: 60*1000, // optional HTTP request timeout to apply to all requests.
})
//
// tweet 'hello world!'
//
T.post('statuses/update', { status: 'hello world!' }, function(err, data, response) {
console.log(data)
})
//
// search twitter for all tweets containing the word 'banana' since July 11, 2011
//
T.get('search/tweets', { q: 'banana since:2011-07-11', count: 100 }, function(err, data, response) {
console.log(data)
})
//
// get the list of user id's that follow @tolga_tezel
//
T.get('followers/ids', { screen_name: 'tolga_tezel' }, function (err, data, response) {
console.log(data)
})
//
// Twit has promise support; you can use the callback API,
// promise API, or both at the same time.
//
T.get('account/verify_credentials', { skip_status: true })
.catch(function (err) {
console.log('caught error', err.stack)
})
.then(function (result) {
// `result` is an Object with keys "data" and "resp".
// `data` and `resp` are the same objects as the ones passed
// to the callback.
// See https://github.com/ttezel/twit#tgetpath-params-callback
// for details.
console.log('data', result.data);
})
//
// retweet a tweet with id '343360866131001345'
//
T.post('statuses/retweet/:id', { id: '343360866131001345' }, function (err, data, response) {
console.log(data)
})
//
// destroy a tweet with id '343360866131001345'
//
T.post('statuses/destroy/:id', { id: '343360866131001345' }, function (err, data, response) {
console.log(data)
})
//
// get `funny` twitter users
//
T.get('users/suggestions/:slug', { slug: 'funny' }, function (err, data, response) {
console.log(data)
})
//
// post a tweet with media
//
var b64content = fs.readFileSync('/path/to/img', { encoding: 'base64' })
// first we must post the media to Twitter
T.post('media/upload', { media_data: b64content }, function (err, data, response) {
// now we can assign alt text to the media, for use by screen readers and
// other text-based presentations and interpreters
var mediaIdStr = data.media_id_string
var altText = "Small flowers in a planter on a sunny balcony, blossoming."
var meta_params = { media_id: mediaIdStr, alt_text: { text: altText } }
T.post('media/metadata/create', meta_params, function (err, data, response) {
if (!err) {
// now we can reference the media and post a tweet (media will attach to the tweet)
var params = { status: 'loving life #nofilter', media_ids: [mediaIdStr] }
T.post('statuses/update', params, function (err, data, response) {
console.log(data)
})
}
})
})
//
// post media via the chunked media upload API.
// You can then use POST statuses/update to post a tweet with the media attached as in the example above using `media_id_string`.
// Note: You can also do this yourself manually using T.post() calls if you want more fine-grained
// control over the streaming. Example: https://github.com/ttezel/twit/blob/master/tests/rest_chunked_upload.js#L20
//
var filePath = '/absolute/path/to/file.mp4'
T.postMediaChunked({ file_path: filePath }, function (err, data, response) {
console.log(data)
})
//
// stream a sample of public statuses
//
var stream = T.stream('statuses/sample')
stream.on('tweet', function (tweet) {
console.log(tweet)
})
//
// filter the twitter public stream by the word 'mango'.
//
var stream = T.stream('statuses/filter', { track: 'mango' })
stream.on('tweet', function (tweet) {
console.log(tweet)
})
//
// filter the public stream by the latitude/longitude bounded box of San Francisco
//
var sanFrancisco = [ '-122.75', '36.8', '-121.75', '37.8' ]
var stream = T.stream('statuses/filter', { locations: sanFrancisco })
stream.on('tweet', function (tweet) {
console.log(tweet)
})
//
// filter the public stream by english tweets containing `#apple`
//
var stream = T.stream('statuses/filter', { track: '#apple', language: 'en' })
stream.on('tweet', function (tweet) {
console.log(tweet)
})
```
# twit API:
##`var T = new Twit(config)`
Create a `Twit` instance that can be used to make requests to Twitter's APIs.
If authenticating with user context, `config` should be an object of the form:
```
{
consumer_key: '...'
, consumer_secret: '...'
, access_token: '...'
, access_token_secret: '...'
}
```
If authenticating with application context, `config` should be an object of the form:
```
{
consumer_key: '...'
, consumer_secret: '...'
, app_only_auth: true
}
```
Note that Application-only auth will not allow you to perform requests to API endpoints requiring
a user context, such as posting tweets. However, the endpoints available can have a higher rate limit.
##`T.get(path, [params], callback)`
GET any of the REST API endpoints.
**path**
The endpoint to hit. When specifying `path` values, omit the **'.json'** at the end (i.e. use **'search/tweets'** instead of **'search/tweets.json'**).
**params**
(Optional) parameters for the request.
**callback**
`function (err, data, response)`
- `data` is the parsed data received from Twitter.
- `response` is the [http.IncomingMessage](http://nodejs.org/api/http.html#http_http_incomingmessage) received from Twitter.
##`T.post(path, [params], callback)`
POST any of the REST API endpoints. Same usage as `T.get()`.
##`T.postMediaChunked(params, callback)`
Helper function to post media via the POST media/upload (chunked) API. `params` is an object containing a `file_path` key. `file_path` is the absolute path to the file you want to upload.
```js
var filePath = '/absolute/path/to/file.mp4'
T.postMediaChunked({ file_path: filePath }, function (err, data, response) {
console.log(data)
})
```
You can also use the POST media/upload API via T.post() calls if you want more fine-grained control over the streaming; [see here for an example](https://github.com/ttezel/twit/blob/master/tests/rest_chunked_upload.js#L20).
##`T.getAuth()`
Get the client's authentication tokens.
##`T.setAuth(tokens)`
Update the client's authentication tokens.
##`T.stream(path, [params])`
Use this with the Streaming API.
**path**
Streaming endpoint to hit. One of:
- **'statuses/filter'**
- **'statuses/sample'**
- **'statuses/firehose'**
- **'user'**
- **'site'**
For a description of each Streaming endpoint, see the [Twitter API docs](https://dev.twitter.com/streaming/overview).
**params**
(Optional) parameters for the request. Any Arrays passed in `params` get converted to comma-separated strings, allowing you to do requests like:
```javascript
//
// I only want to see tweets about my favorite fruits
//
// same result as doing { track: 'bananas,oranges,strawberries' }
var stream = T.stream('statuses/filter', { track: ['bananas', 'oranges', 'strawberries'] })
stream.on('tweet', function (tweet) {
//...
})
```
# Using the Streaming API
`T.stream(path, [params])` keeps the connection alive, and returns an `EventEmitter`.
The following events are emitted:
##event: 'message'
Emitted each time an object is received in the stream. This is a catch-all event that can be used to process any data received in the stream, rather than using the more specific events documented below.
New in version 2.1.0.
```javascript
stream.on('message', function (msg) {
//...
})
```
##event: 'tweet'
Emitted each time a status (tweet) comes into the stream.
```javascript
stream.on('tweet', function (tweet) {
//...
})
```
##event: 'delete'
Emitted each time a status (tweet) deletion message comes into the stream.
```javascript
stream.on('delete', function (deleteMessage) {
//...
})
```
##event: 'limit'
Emitted each time a limitation message comes into the stream.
```javascript
stream.on('limit', function (limitMessage) {
//...
})
```
##event: 'scrub_geo'
Emitted each time a location deletion message comes into the stream.
```javascript
stream.on('scrub_geo', function (scrubGeoMessage) {
//...
})
```
##event: 'disconnect'
Emitted when a disconnect message comes from Twitter. This occurs if you have multiple streams connected to Twitter's API. Upon receiving a disconnect message from Twitter, `Twit` will close the connection and emit this event with the message details received from twitter.
```javascript
stream.on('disconnect', function (disconnectMessage) {
//...
})
```
##event: 'connect'
Emitted when a connection attempt is made to Twitter. The http `request` object is emitted.
```javascript
stream.on('connect', function (request) {
//...
})
```
##event: 'connected'
Emitted when the response is received from Twitter. The http `response` object is emitted.
```javascript
stream.on('connected', function (response) {
//...
})
```
##event: 'reconnect'
Emitted when a reconnection attempt to Twitter is scheduled. If Twitter is having problems or we get rate limited, we schedule a reconnect according to Twitter's [reconnection guidelines](https://dev.twitter.com/streaming/overview/connecting). The last http `request` and `response` objects are emitted, along with the time (in milliseconds) left before the reconnect occurs.
```javascript
stream.on('reconnect', function (request, response, connectInterval) {
//...
})
```
##event: 'warning'
This message is appropriate for clients using high-bandwidth connections, like the firehose. If your connection is falling behind, Twitter will queue messages for you, until your queue fills up, at which point they will disconnect you.
```javascript
stream.on('warning', function (warning) {
//...
})
```
##event: 'status_withheld'
Emitted when Twitter sends back a `status_withheld` message in the stream. This means that a tweet was withheld in certain countries.
```javascript
stream.on('status_withheld', function (withheldMsg) {
//...
})
```
##event: 'user_withheld'
Emitted when Twitter sends back a `user_withheld` message in the stream. This means that a Twitter user was withheld in certain countries.
```javascript
stream.on('user_withheld', function (withheldMsg) {
//...
})
```
##event: 'friends'
Emitted when Twitter sends the ["friends" preamble](https://dev.twitter.com/streaming/overview/messages-types#user_stream_messsages) when connecting to a user stream. This message contains a list of the user's friends, represented as an array of user ids. If the [stringify_friend_ids](https://dev.twitter.com/streaming/overview/request-parameters#stringify_friend_id) parameter is set, the friends
list preamble will be returned as Strings (instead of Numbers).
```javascript
var stream = T.stream('user', { stringify_friend_ids: true })
stream.on('friends', function (friendsMsg) {
//...
})
```
##event: 'direct_message'
Emitted when a direct message is sent to the user. Unfortunately, Twitter has not documented this event for user streams.
```javascript
stream.on('direct_message', function (directMsg) {
//...
})
```
##event: 'user_event'
Emitted when Twitter sends back a [User stream event](https://dev.twitter.com/streaming/overview/messages-types#Events_event).
See the Twitter docs for more information on each event's structure.
```javascript
stream.on('user_event', function (eventMsg) {
//...
})
```
In addition, the following user stream events are provided for you to listen on:
* `blocked`
* `unblocked`
* `favorite`
* `unfavorite`
* `follow`
* `unfollow`
* `user_update`
* `list_created`
* `list_destroyed`
* `list_updated`
* `list_member_added`
* `list_member_removed`
* `list_user_subscribed`
* `list_user_unsubscribed`
* `quoted_tweet`
* `retweeted_retweet`
* `favorited_retweet`
* `unknown_user_event` (for an event that doesn't match any of the above)
###Example:
```javascript
stream.on('favorite', function (event) {
//...
})
```
##event: 'error'
Emitted when an API request or response error occurs.
An `Error` object is emitted, with properties:
```js
{
message: '...', // error message
statusCode: '...', // statusCode from Twitter
code: '...', // error code from Twitter
twitterReply: '...', // raw response data from Twitter
allErrors: '...' // array of errors returned from Twitter
}
```
##stream.stop()
Call this function on the stream to stop streaming (closes the connection with Twitter).
##stream.start()
Call this function to restart the stream after you called `.stop()` on it.
Note: there is no need to call `.start()` to begin streaming. `Twit.stream` calls `.start()` for you.
-------
#What do I have access to?
Anything in the Twitter API:
* REST API Endpoints: https://dev.twitter.com/rest/public
* Public stream endpoints: https://dev.twitter.com/streaming/public
* User stream endpoints: https://dev.twitter.com/streaming/userstreams
* Site stream endpoints: https://dev.twitter.com/streaming/sitestreams
-------
Go here to create an app and get OAuth credentials (if you haven't already): https://dev.twitter.com/apps/new
#Advanced
You may specify an array of trusted certificate fingerprints if you want to only trust a specific set of certificates.
When an HTTP response is received, it is verified that the certificate was signed, and the peer certificate's fingerprint must be one of the values you specified. By default, the node.js trusted "root" CAs will be used.
eg.
```js
var twit = new Twit({
consumer_key: '...',
consumer_secret: '...',
access_token: '...',
access_token_secret: '...',
trusted_cert_fingerprints: [
'66:EA:47:62:D9:B1:4F:1A:AE:89:5F:68:BA:6B:8E:BB:F8:1D:BF:8E',
]
})
```
#Contributing
- Make your changes
- Make sure your code matches the style of the code around it
- Add tests that cover your feature/bugfix
- Run tests
- Submit a pull request
#How do I run the tests?
Create two files: `config1.js` and `config2.js` at the root of the `twit` folder. They should contain two different sets of oauth credentials for twit to use (two accounts are needed for testing interactions). They should both look something like this:
```
module.exports = {
consumer_key: '...'
, consumer_secret: '...'
, access_token: '...'
, access_token_secret: '...'
}
```
Then run the tests:
```
npm test
```
You can also run the example:
```
node examples/rtd2.js
```
![iRTD2](http://dl.dropbox.com/u/32773572/RTD2_logo.png)
The example is a twitter bot named [RTD2](https://twitter.com/#!/iRTD2) written using `twit`. RTD2 tweets about **github** and curates its social graph.
-------
[FAQ](https://github.com/ttezel/twit/wiki/FAQ)
-------
## License
(The MIT License)
Copyright (c) by Tolga Tezel <tolgatezel11@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
## Changelog
###2.2.2
* Emit `parser-error` instead of `error` event if Twitter sends back
an uncompressed HTTP response body.
###2.2.1
* Add promise support to Twit REST API calls.
###2.2.0
* Allow omission of `new` keyword; `var t = Twit(config)` works, and `var t = new Twit(config)` works too.
* Allow setting an array of trusted certificate fingerprints via `config.trusted_cert_fingerprints`.
* Automatically adjust timestamp for OAuth'ed HTTP requests
by recording the timestamp from Twitter HTTP responses, computing our local time offset, and applying the offset in the next HTTP request to Twitter.
###2.1.7
* Add `mime` as a dependency.
###2.1.6
* Emit `friends` event for `friends_str` message received when a user stream is requested with `stringify_friend_ids=true`.
* Handle receiving "Exceeded connection limit for user" message from Twitter while streaming. Emit `error` event for this case.
* Emit `retweeted_retweet` and `favorited_retweet` user events.
* Add MIT license to package.json (about time!)
###2.1.5
* Support config-based request timeout.
###2.1.4
* Support POST media/upload (chunked) and add `T.postMediaChunked()` to make it easy.
###2.1.3
* Fix bug in constructing HTTP requests for `account/update_profile_image` and `account/update_profile_background_image` paths.
###2.1.2
* Enable gzip on network traffic
* Add `quoted_tweet` event
###2.1.1
* Strict-mode fixes (Twit can now be run with strict mode)
* Fix handling of disconect message from Twitter
* If Twitter returns a non-JSON-parseable fragment during streaming, emit 'parser-error' instead of 'error' (to discard fragments like "Internal Server Error")
###2.1.0
* Add `message` event.
###2.0.0
* Implement Application-only auth
* Remove oauth module as a dependency
###1.1.20
* Implement support for POST /media/upload
* Reconnect logic fix for streaming; add stall abort/reconnect timeout on first connection attempt.
###1.1.14
* Emit `connected` event upon receiving the response from twitter
###1.0.0
* now to stop and start the stream, use `stream.stop()` and `stream.start()` instead of emitting the `start` and `stop` events
* If twitter sends a `disconnect` message, closes the stream and emits `disconnect` with the disconnect message received from twitter
###0.2.0
* Updated `twit` for usage with v1.1 of the Twitter API.
###0.1.5
* **BREAKING CHANGE** to `twit.stream()`. Does not take a callback anymore. It returns
immediately with the `EventEmitter` that you can listen on. The `Usage` section in
the Readme.md has been updated. Read it.
###0.1.4
* `twit.stream()` has signature `function (path, params, callback)`

127
node_modules/twit/examples/bot.js generated vendored Normal file
View File

@@ -0,0 +1,127 @@
//
// Bot
// class for performing various twitter actions
//
var Twit = require('../lib/twitter');
var Bot = module.exports = function(config) {
this.twit = new Twit(config);
};
//
// post a tweet
//
Bot.prototype.tweet = function (status, callback) {
if(typeof status !== 'string') {
return callback(new Error('tweet must be of type String'));
} else if(status.length > 140) {
return callback(new Error('tweet is too long: ' + status.length));
}
this.twit.post('statuses/update', { status: status }, callback);
};
Bot.prototype.searchFollow = function (params, callback) {
var self = this;
self.twit.get('search/tweets', params, function (err, reply) {
if(err) return callback(err);
var tweets = reply.statuses;
var rTweet = randIndex(tweets)
if(typeof rTweet != 'undefined')
{
var target = rTweet.user.id_str;
self.twit.post('friendships/create', { id: target }, callback);
}
});
};
//
// retweet
//
Bot.prototype.retweet = function (params, callback) {
var self = this;
self.twit.get('search/tweets', params, function (err, reply) {
if(err) return callback(err);
var tweets = reply.statuses;
var randomTweet = randIndex(tweets);
if(typeof randomTweet != 'undefined')
self.twit.post('statuses/retweet/:id', { id: randomTweet.id_str }, callback);
});
};
//
// favorite a tweet
//
Bot.prototype.favorite = function (params, callback) {
var self = this;
self.twit.get('search/tweets', params, function (err, reply) {
if(err) return callback(err);
var tweets = reply.statuses;
var randomTweet = randIndex(tweets);
if(typeof randomTweet != 'undefined')
self.twit.post('favorites/create', { id: randomTweet.id_str }, callback);
});
};
//
// choose a random friend of one of your followers, and follow that user
//
Bot.prototype.mingle = function (callback) {
var self = this;
this.twit.get('followers/ids', function(err, reply) {
if(err) { return callback(err); }
var followers = reply.ids
, randFollower = randIndex(followers);
self.twit.get('friends/ids', { user_id: randFollower }, function(err, reply) {
if(err) { return callback(err); }
var friends = reply.ids
, target = randIndex(friends);
self.twit.post('friendships/create', { id: target }, callback);
})
})
};
//
// prune your followers list; unfollow a friend that hasn't followed you back
//
Bot.prototype.prune = function (callback) {
var self = this;
this.twit.get('followers/ids', function(err, reply) {
if(err) return callback(err);
var followers = reply.ids;
self.twit.get('friends/ids', function(err, reply) {
if(err) return callback(err);
var friends = reply.ids
, pruned = false;
while(!pruned) {
var target = randIndex(friends);
if(!~followers.indexOf(target)) {
pruned = true;
self.twit.post('friendships/destroy', { id: target }, callback);
}
}
});
});
};
function randIndex (arr) {
var index = Math.floor(arr.length*Math.random());
return arr[index];
};

77
node_modules/twit/examples/rtd2.js generated vendored Normal file
View File

@@ -0,0 +1,77 @@
//
// RTD2 - Twitter bot that tweets about the most popular github.com news
// Also makes new friends and prunes its followings.
//
var Bot = require('./bot')
, config1 = require('../config1');
var bot = new Bot(config1);
console.log('RTD2: Running.');
//get date string for today's date (e.g. '2011-01-01')
function datestring () {
var d = new Date(Date.now() - 5*60*60*1000); //est timezone
return d.getUTCFullYear() + '-'
+ (d.getUTCMonth() + 1) + '-'
+ d.getDate();
};
setInterval(function() {
bot.twit.get('followers/ids', function(err, reply) {
if(err) return handleError(err)
console.log('\n# followers:' + reply.ids.length.toString());
});
var rand = Math.random();
if(rand <= 0.10) { // tweet popular github tweet
var params = {
q: 'github.com/'
, since: datestring()
, result_type: 'mixed'
};
bot.twit.get('search/tweets', params, function (err, reply) {
if(err) return handleError(err);
var max = 0, popular;
var tweets = reply.statuses
, i = tweets.length;
while(i--) {
var tweet = tweets[i]
, popularity = tweet.retweet_count;
if(popularity > max) {
max = popularity;
popular = tweet.text;
}
}
bot.tweet(popular, function (err, reply) {
if(err) return handleError(err);
console.log('\nTweet: ' + (reply ? reply.text : reply));
})
});
} else if(rand <= 0.55) { // make a friend
bot.mingle(function(err, reply) {
if(err) return handleError(err);
var name = reply.screen_name;
console.log('\nMingle: followed @' + name);
});
} else { // prune a friend
bot.prune(function(err, reply) {
if(err) return handleError(err);
var name = reply.screen_name
console.log('\nPrune: unfollowed @'+ name);
});
}
}, 40000);
function handleError(err) {
console.error('response status:', err.statusCode);
console.error('data:', err.data);
}

11
node_modules/twit/lib/endpoints.js generated vendored Normal file
View File

@@ -0,0 +1,11 @@
// Twitter Endpoints
module.exports = {
API_HOST : 'https://api.twitter.com/'
, REST_ROOT : 'https://api.twitter.com/1.1/'
, PUB_STREAM : 'https://stream.twitter.com/1.1/'
, USER_STREAM : 'https://userstream.twitter.com/1.1/'
, SITE_STREAM : 'https://sitestream.twitter.com/1.1/'
, MEDIA_UPLOAD : 'https://upload.twitter.com/1.1/'
, OA_REQ : 'https://api.twitter.com/oauth/request_token'
, OA_ACCESS : 'https://api.twitter.com/oauth/access_token'
}

143
node_modules/twit/lib/file_uploader.js generated vendored Normal file
View File

@@ -0,0 +1,143 @@
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

128
node_modules/twit/lib/helpers.js generated vendored Normal file
View File

@@ -0,0 +1,128 @@
var querystring = require('querystring');
var request = require('request');
var endpoints = require('./endpoints');
/**
* Encodes object as a querystring, to be used as the suffix of request URLs.
* @param {Object} obj
* @return {String}
*/
exports.makeQueryString = function (obj) {
var qs = querystring.stringify(obj)
qs = qs.replace(/\!/g, "%21")
.replace(/\'/g, "%27")
.replace(/\(/g, "%28")
.replace(/\)/g, "%29")
.replace(/\*/g, "%2A");
return qs
}
/**
* For each `/:param` fragment in path, move the value in params
* at that key to path. If the key is not found in params, throw.
* Modifies both params and path values.
*
* @param {Objet} params Object used to build path.
* @param {String} path String to transform.
* @return {Undefined}
*
*/
exports.moveParamsIntoPath = function (params, path) {
var rgxParam = /\/:(\w+)/g
var missingParamErr = null
path = path.replace(rgxParam, function (hit) {
var paramName = hit.slice(2)
var suppliedVal = params[paramName]
if (!suppliedVal) {
throw new Error('Twit: Params object is missing a required parameter for this request: `'+paramName+'`')
}
var retVal = '/' + suppliedVal
delete params[paramName]
return retVal
})
return path
}
/**
* When Twitter returns a response that looks like an error response,
* use this function to attach the error info in the response body to `err`.
*
* @param {Error} err Error instance to which body info will be attached
* @param {Object} body JSON object that is the deserialized HTTP response body received from Twitter
* @return {Undefined}
*/
exports.attachBodyInfoToError = function (err, body) {
err.twitterReply = body;
if (!body) {
return
}
if (body.error) {
// the body itself is an error object
err.message = body.error
err.allErrors = err.allErrors.concat([body])
} else if (body.errors && body.errors.length) {
// body contains multiple error objects
err.message = body.errors[0].message;
err.code = body.errors[0].code;
err.allErrors = err.allErrors.concat(body.errors)
}
}
exports.makeTwitError = function (message) {
var err = new Error()
if (message) {
err.message = message
}
err.code = null
err.allErrors = []
err.twitterReply = null
return err
}
/**
* Get a bearer token for OAuth2
* @param {String} consumer_key
* @param {String} consumer_secret
* @param {Function} cb
*
* Calls `cb` with Error, String
*
* Error (if it exists) is guaranteed to be Twit error-formatted.
* String (if it exists) is the bearer token received from Twitter.
*/
exports.getBearerToken = function (consumer_key, consumer_secret, cb) {
// use OAuth 2 for app-only auth (Twitter requires this)
// get a bearer token using our app's credentials
var b64Credentials = new Buffer(consumer_key + ':' + consumer_secret).toString('base64');
request.post({
url: endpoints.API_HOST + '/oauth2/token',
headers: {
'Authorization': 'Basic ' + b64Credentials,
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
},
body: 'grant_type=client_credentials',
json: true,
}, function (err, res, body) {
if (err) {
var error = exports.makeTwitError(err.toString());
exports.attachBodyInfoToError(error, body);
return cb(error, body, res);
}
if ( !body ) {
var error = exports.makeTwitError('Not valid reply from Twitter upon obtaining bearer token');
exports.attachBodyInfoToError(error, body);
return cb(error, body, res);
}
if (body.token_type !== 'bearer') {
var error = exports.makeTwitError('Unexpected reply from Twitter upon obtaining bearer token');
exports.attachBodyInfoToError(error, body);
return cb(error, body, res);
}
return cb(err, body.access_token);
})
}

56
node_modules/twit/lib/parser.js generated vendored Normal file
View File

@@ -0,0 +1,56 @@
//
// Parser - for Twitter Streaming API
//
var util = require('util')
, EventEmitter = require('events').EventEmitter;
var Parser = module.exports = function () {
this.message = ''
EventEmitter.call(this);
};
util.inherits(Parser, EventEmitter);
Parser.prototype.parse = function (chunk) {
this.message += chunk;
chunk = this.message;
var size = chunk.length
, start = 0
, offset = 0
, curr
, next;
while (offset < size) {
curr = chunk[offset];
next = chunk[offset + 1];
if (curr === '\r' && next === '\n') {
var piece = chunk.slice(start, offset);
start = offset += 2;
if (!piece.length) { continue; } //empty object
if (piece === 'Exceeded connection limit for user') {
this.emit('connection-limit-exceeded',
new Error('Twitter says: ' + piece + '. Only instantiate one stream per set of credentials.'));
continue;
}
try {
var msg = JSON.parse(piece)
} catch (err) {
this.emit('error', new Error('Error parsing twitter reply: `'+piece+'`, error message `'+err+'`'));
} finally {
if (msg)
this.emit('element', msg)
continue
}
}
offset++;
}
this.message = chunk.slice(start, size);
};

2
node_modules/twit/lib/settings.js generated vendored Normal file
View File

@@ -0,0 +1,2 @@
// set of status codes where we don't attempt reconnecting to Twitter
exports.STATUS_CODES_TO_ABORT_ON = [ 400, 401, 403, 404, 406, 410, 422 ];

358
node_modules/twit/lib/streaming-api-connection.js generated vendored Normal file
View File

@@ -0,0 +1,358 @@
var EventEmitter = require('events').EventEmitter;
var util = require('util');
var helpers = require('./helpers')
var Parser = require('./parser');
var request = require('request');
var zlib = require('zlib');
var STATUS_CODES_TO_ABORT_ON = require('./settings').STATUS_CODES_TO_ABORT_ON
var StreamingAPIConnection = function (reqOpts, twitOptions) {
this.reqOpts = reqOpts
this.twitOptions = twitOptions
this._twitter_time_minus_local_time_ms = 0
EventEmitter.call(this)
}
util.inherits(StreamingAPIConnection, EventEmitter)
/**
* Resets the connection.
* - clears request, response, parser
* - removes scheduled reconnect handle (if one was scheduled)
* - stops the stall abort timeout handle (if one was scheduled)
*/
StreamingAPIConnection.prototype._resetConnection = function () {
if (this.request) {
// clear our reference to the `request` instance
this.request.removeAllListeners();
this.request.destroy();
}
if (this.response) {
// clear our reference to the http.IncomingMessage instance
this.response.removeAllListeners();
this.response.destroy();
}
if (this.parser) {
this.parser.removeAllListeners()
}
// ensure a scheduled reconnect does not occur (if one was scheduled)
// this can happen if we get a close event before .stop() is called
clearTimeout(this._scheduledReconnect)
delete this._scheduledReconnect
// clear our stall abort timeout
this._stopStallAbortTimeout()
}
/**
* Resets the parameters used in determining the next reconnect time
*/
StreamingAPIConnection.prototype._resetRetryParams = function () {
// delay for next reconnection attempt
this._connectInterval = 0
// flag indicating whether we used a 0-delay reconnect
this._usedFirstReconnect = false
}
StreamingAPIConnection.prototype._startPersistentConnection = function () {
var self = this;
self._resetConnection();
self._setupParser();
self._resetStallAbortTimeout();
self._setOauthTimestamp();
self.request = request.post(this.reqOpts);
self.emit('connect', self.request);
self.request.on('response', function (response) {
self._updateOauthTimestampOffsetFromResponse(response)
// reset our reconnection attempt flag so next attempt goes through with 0 delay
// if we get a transport-level error
self._usedFirstReconnect = false;
// start a stall abort timeout handle
self._resetStallAbortTimeout();
self.response = response
if (STATUS_CODES_TO_ABORT_ON.indexOf(self.response.statusCode) !== -1) {
// We got a status code telling us we should abort the connection.
// Read the body from the response and return an error to the user.
var body = '';
var compressedBody = '';
self.response.on('data', function (chunk) {
compressedBody += chunk.toString('utf8');
})
var gunzip = zlib.createGunzip();
self.response.pipe(gunzip);
gunzip.on('data', function (chunk) {
body += chunk.toString('utf8')
})
gunzip.on('end', function () {
try {
body = JSON.parse(body)
} catch (jsonDecodeError) {
// Twitter may send an HTML body
// if non-JSON text was returned, we'll just attach it to the error as-is
}
// surface the error to the user
var error = helpers.makeTwitError('Bad Twitter streaming request: ' + self.response.statusCode)
error.statusCode = response ? response.statusCode: null;
helpers.attachBodyInfoToError(error, body)
self.emit('error', error);
// stop the stream explicitly so we don't reconnect
self.stop()
body = null;
});
gunzip.on('error', function (err) {
// If Twitter sends us back an uncompressed HTTP response, gzip will error out.
// Handle this by emitting an error with the uncompressed response body.
var errMsg = 'Gzip error: ' + err.message;
var twitErr = helpers.makeTwitError(errMsg);
twitErr.statusCode = self.response.statusCode;
helpers.attachBodyInfoToError(twitErr, compressedBody);
self.emit('parser-error', twitErr);
});
} else if (self.response.statusCode === 420) {
// close the connection forcibly so a reconnect is scheduled by `self.onClose()`
self._scheduleReconnect();
} else {
// We got an OK status code - the response should be valid.
// Read the body from the response and return to the user.
var gunzip = zlib.createGunzip();
self.response.pipe(gunzip);
//pass all response data to parser
gunzip.on('data', function (chunk) {
self._connectInterval = 0
// stop stall timer, and start a new one
self._resetStallAbortTimeout();
self.parser.parse(chunk.toString('utf8'));
});
gunzip.on('close', self._onClose.bind(self))
gunzip.on('error', function (err) {
self.emit('error', err);
})
self.response.on('error', function (err) {
// expose response errors on twit instance
self.emit('error', err);
})
// connected without an error response from Twitter, emit `connected` event
// this must be emitted after all its event handlers are bound
// so the reference to `self.response` is not interfered-with by the user until it is emitted
self.emit('connected', self.response);
}
});
self.request.on('close', self._onClose.bind(self));
self.request.on('error', function (err) { self._scheduleReconnect.bind(self) });
return self;
}
/**
* Handle when the request or response closes.
* Schedule a reconnect according to Twitter's reconnect guidelines
*
*/
StreamingAPIConnection.prototype._onClose = function () {
var self = this;
self._stopStallAbortTimeout();
if (self._scheduledReconnect) {
// if we already have a reconnect scheduled, don't schedule another one.
// this race condition can happen if the http.ClientRequest and http.IncomingMessage both emit `close`
return
}
self._scheduleReconnect();
}
/**
* Kick off the http request, and persist the connection
*
*/
StreamingAPIConnection.prototype.start = function () {
this._resetRetryParams();
this._startPersistentConnection();
return this;
}
/**
* Abort the http request, stop scheduled reconnect (if one was scheduled) and clear state
*
*/
StreamingAPIConnection.prototype.stop = function () {
// clear connection variables and timeout handles
this._resetConnection();
this._resetRetryParams();
return this;
}
/**
* Stop and restart the stall abort timer (called when new data is received)
*
* If we go 90s without receiving data from twitter, we abort the request & reconnect.
*/
StreamingAPIConnection.prototype._resetStallAbortTimeout = function () {
var self = this;
// stop the previous stall abort timer
self._stopStallAbortTimeout();
//start a new 90s timeout to trigger a close & reconnect if no data received
self._stallAbortTimeout = setTimeout(function () {
self._scheduleReconnect()
}, 90000);
return this;
}
/**
* Stop stall timeout
*
*/
StreamingAPIConnection.prototype._stopStallAbortTimeout = function () {
clearTimeout(this._stallAbortTimeout);
// mark the timer as `null` so it is clear via introspection that the timeout is not scheduled
delete this._stallAbortTimeout;
return this;
}
/**
* Computes the next time a reconnect should occur (based on the last HTTP response received)
* and starts a timeout handle to begin reconnecting after `self._connectInterval` passes.
*
* @return {Undefined}
*/
StreamingAPIConnection.prototype._scheduleReconnect = function () {
var self = this;
if (self.response && self.response.statusCode === 420) {
// we are being rate limited
// start with a 1 minute wait and double each attempt
if (!self._connectInterval) {
self._connectInterval = 60000;
} else {
self._connectInterval *= 2;
}
} else if (self.response && String(self.response.statusCode).charAt(0) === '5') {
// twitter 5xx errors
// start with a 5s wait, double each attempt up to 320s
if (!self._connectInterval) {
self._connectInterval = 5000;
} else if (self._connectInterval < 320000) {
self._connectInterval *= 2;
} else {
self._connectInterval = 320000;
}
} else {
// we did not get an HTTP response from our last connection attempt.
// DNS/TCP error, or a stall in the stream (and stall timer closed the connection)
if (!self._usedFirstReconnect) {
// first reconnection attempt on a valid connection should occur immediately
self._connectInterval = 0;
self._usedFirstReconnect = true;
} else if (self._connectInterval < 16000) {
// linearly increase delay by 250ms up to 16s
self._connectInterval += 250;
} else {
// cap out reconnect interval at 16s
self._connectInterval = 16000;
}
}
// schedule the reconnect
self._scheduledReconnect = setTimeout(function () {
self._startPersistentConnection();
}, self._connectInterval);
self.emit('reconnect', self.request, self.response, self._connectInterval);
}
StreamingAPIConnection.prototype._setupParser = function () {
var self = this
self.parser = new Parser()
// handle twitter objects as they come in - emit the generic `message` event
// along with the specific event corresponding to the message
self.parser.on('element', function (msg) {
self.emit('message', msg)
if (msg.delete) { self.emit('delete', msg) }
else if (msg.disconnect) { self._handleDisconnect(msg) }
else if (msg.limit) { self.emit('limit', msg) }
else if (msg.scrub_geo) { self.emit('scrub_geo', msg) }
else if (msg.warning) { self.emit('warning', msg) }
else if (msg.status_withheld) { self.emit('status_withheld', msg) }
else if (msg.user_withheld) { self.emit('user_withheld', msg) }
else if (msg.friends || msg.friends_str) { self.emit('friends', msg) }
else if (msg.direct_message) { self.emit('direct_message', msg) }
else if (msg.event) {
self.emit('user_event', msg)
// reference: https://dev.twitter.com/docs/streaming-apis/messages#User_stream_messages
var ev = msg.event
if (ev === 'blocked') { self.emit('blocked', msg) }
else if (ev === 'unblocked') { self.emit('unblocked', msg) }
else if (ev === 'favorite') { self.emit('favorite', msg) }
else if (ev === 'unfavorite') { self.emit('unfavorite', msg) }
else if (ev === 'follow') { self.emit('follow', msg) }
else if (ev === 'unfollow') { self.emit('unfollow', msg) }
else if (ev === 'user_update') { self.emit('user_update', msg) }
else if (ev === 'list_created') { self.emit('list_created', msg) }
else if (ev === 'list_destroyed') { self.emit('list_destroyed', msg) }
else if (ev === 'list_updated') { self.emit('list_updated', msg) }
else if (ev === 'list_member_added') { self.emit('list_member_added', msg) }
else if (ev === 'list_member_removed') { self.emit('list_member_removed', msg) }
else if (ev === 'list_user_subscribed') { self.emit('list_user_subscribed', msg) }
else if (ev === 'list_user_unsubscribed') { self.emit('list_user_unsubscribed', msg) }
else if (ev === 'quoted_tweet') { self.emit('quoted_tweet', msg) }
else if (ev === 'favorited_retweet') { self.emit('favorited_retweet', msg) }
else if (ev === 'retweeted_retweet') { self.emit('retweeted_retweet', msg) }
else { self.emit('unknown_user_event', msg) }
} else { self.emit('tweet', msg) }
})
self.parser.on('error', function (err) {
self.emit('parser-error', err)
});
self.parser.on('connection-limit-exceeded', function (err) {
self.emit('error', err);
})
}
StreamingAPIConnection.prototype._handleDisconnect = function (twitterMsg) {
this.emit('disconnect', twitterMsg);
this.stop();
}
/**
* Call whenever an http request is about to be made to update
* our local timestamp (used for Oauth) to be Twitter's server time.
*
*/
StreamingAPIConnection.prototype._setOauthTimestamp = function () {
var self = this;
if (self.reqOpts.oauth) {
var oauth_ts = Date.now() + self._twitter_time_minus_local_time_ms;
self.reqOpts.oauth.timestamp = Math.floor(oauth_ts/1000).toString();
}
}
/**
* Call whenever an http response is received from Twitter,
* to set our local timestamp offset from Twitter's server time.
* This is used to set the Oauth timestamp for our next http request
* to Twitter (by calling _setOauthTimestamp).
*
* @param {http.IncomingResponse} resp http response received from Twitter.
*/
StreamingAPIConnection.prototype._updateOauthTimestampOffsetFromResponse = function (resp) {
if (resp && resp.headers && resp.headers.date &&
new Date(resp.headers.date).toString() !== 'Invalid Date'
) {
var twitterTimeMs = new Date(resp.headers.date).getTime()
this._twitter_time_minus_local_time_ms = twitterTimeMs - Date.now();
}
}
module.exports = StreamingAPIConnection

485
node_modules/twit/lib/twitter.js generated vendored Normal file
View File

@@ -0,0 +1,485 @@
//
// Twitter API Wrapper
//
var assert = require('assert');
var Promise = require('bluebird');
var request = require('request');
var util = require('util');
var endpoints = require('./endpoints');
var FileUploader = require('./file_uploader');
var helpers = require('./helpers');
var StreamingAPIConnection = require('./streaming-api-connection');
var STATUS_CODES_TO_ABORT_ON = require('./settings').STATUS_CODES_TO_ABORT_ON;
// config values required for app-only auth
var required_for_app_auth = [
'consumer_key',
'consumer_secret'
];
// config values required for user auth (superset of app-only auth)
var required_for_user_auth = required_for_app_auth.concat([
'access_token',
'access_token_secret'
]);
var FORMDATA_PATHS = [
'media/upload',
'account/update_profile_image',
'account/update_profile_background_image',
];
var JSONPAYLOAD_PATHS = [
'media/metadata/create'
]
//
// Twitter
//
var Twitter = function (config) {
if (!(this instanceof Twitter)) {
return new Twitter(config);
}
var self = this
var credentials = {
consumer_key : config.consumer_key,
consumer_secret : config.consumer_secret,
// access_token and access_token_secret only required for user auth
access_token : config.access_token,
access_token_secret : config.access_token_secret,
// flag indicating whether requests should be made with application-only auth
app_only_auth : config.app_only_auth,
}
this._validateConfigOrThrow(config);
this.config = config;
this._twitter_time_minus_local_time_ms = 0;
}
Twitter.prototype.get = function (path, params, callback) {
return this.request('GET', path, params, callback)
}
Twitter.prototype.post = function (path, params, callback) {
return this.request('POST', path, params, callback)
}
Twitter.prototype.request = function (method, path, params, callback) {
var self = this;
assert(method == 'GET' || method == 'POST');
// if no `params` is specified but a callback is, use default params
if (typeof params === 'function') {
callback = params
params = {}
}
return new Promise(function (resolve, reject) {
var _returnErrorToUser = function (err) {
if (callback && typeof callback === 'function') {
callback(err, null, null);
}
reject(err);
}
self._buildReqOpts(method, path, params, false, function (err, reqOpts) {
if (err) {
_returnErrorToUser(err);
return
}
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);
if (self.config.trusted_cert_fingerprints) {
if (!resp.socket.authorized) {
// The peer certificate was not signed by one of the authorized CA's.
var authErrMsg = resp.socket.authorizationError.toString();
var err = helpers.makeTwitError('The peer certificate was not signed; ' + authErrMsg);
_returnErrorToUser(err);
return;
}
var fingerprint = resp.socket.getPeerCertificate().fingerprint;
var trustedFingerprints = self.config.trusted_cert_fingerprints;
if (trustedFingerprints.indexOf(fingerprint) === -1) {
var errMsg = util.format('Certificate untrusted. Trusted fingerprints are: %s. Got fingerprint: %s.',
trustedFingerprints.join(','), fingerprint);
var err = new Error(errMsg);
_returnErrorToUser(err);
return;
}
}
if (callback && typeof callback === 'function') {
callback(err, parsedBody, resp);
}
resolve({ data: parsedBody, resp: resp });
return;
})
})
});
});
}
/**
* Uploads a file to Twitter via the POST media/upload (chunked) API.
* Use this as an easier alternative to doing the INIT/APPEND/FINALIZE commands yourself.
* Returns the response from the FINALIZE command, or if an error occurs along the way,
* the first argument to `cb` will be populated with a non-null Error.
*
*
* `params` is an Object of the form:
* {
* file_path: String // Absolute path of file to be uploaded.
* }
*
* @param {Object} params options object (described above).
* @param {cb} cb callback of the form: function (err, bodyObj, resp)
*/
Twitter.prototype.postMediaChunked = function (params, cb) {
var self = this;
try {
var fileUploader = new FileUploader(params, self);
} catch(err) {
cb(err);
return;
}
fileUploader.upload(cb);
}
Twitter.prototype._updateClockOffsetFromResponse = function (resp) {
var self = this;
if (resp && resp.headers && resp.headers.date &&
new Date(resp.headers.date).toString() !== 'Invalid Date'
) {
var twitterTimeMs = new Date(resp.headers.date).getTime()
self._twitter_time_minus_local_time_ms = twitterTimeMs - Date.now();
}
}
/**
* Builds and returns an options object ready to pass to `request()`
* @param {String} method "GET" or "POST"
* @param {String} path REST API resource uri (eg. "statuses/destroy/:id")
* @param {Object} params user's params object
* @param {Boolean} isStreaming Flag indicating if it's a request to the Streaming API (different endpoint)
* @returns {Undefined}
*
* Calls `callback` with Error, Object where Object is an options object ready to pass to `request()`.
*
* Returns error raised (if any) by `helpers.moveParamsIntoPath()`
*/
Twitter.prototype._buildReqOpts = function (method, path, params, isStreaming, callback) {
var self = this
if (!params) {
params = {}
}
// 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)
delete finalParams.twit_options
// the options object passed to `request` used to perform the HTTP request
var reqOpts = {
headers: {
'Accept': '*/*',
'User-Agent': 'twit-client'
},
gzip: true,
encoding: null,
}
if (typeof self.config.timeout_ms !== 'undefined') {
reqOpts.timeout = self.config.timeout_ms;
}
try {
// finalize the `path` value by building it using user-supplied params
path = helpers.moveParamsIntoPath(finalParams, path)
} catch (e) {
callback(e, null, null)
return
}
if (isStreaming) {
// This is a Streaming API request.
var stream_endpoint_map = {
user: endpoints.USER_STREAM,
site: endpoints.SITE_STREAM
}
var endpoint = stream_endpoint_map[path] || endpoints.PUB_STREAM
reqOpts.url = endpoint + path + '.json'
} else {
// This is a REST API request.
if (path.indexOf('media/') !== -1) {
// For media/upload, use a different endpoint.
reqOpts.url = endpoints.MEDIA_UPLOAD + path + '.json';
} else {
reqOpts.url = endpoints.REST_ROOT + path + '.json';
}
if (FORMDATA_PATHS.indexOf(path) !== -1) {
reqOpts.headers['Content-type'] = 'multipart/form-data';
reqOpts.form = finalParams;
// set finalParams to empty object so we don't append a query string
// of the params
finalParams = {};
} else if (JSONPAYLOAD_PATHS.indexOf(path) !== -1) {
reqOpts.headers['Content-type'] = 'application/json';
reqOpts.json = true;
reqOpts.body = finalParams;
// as above, to avoid appending query string for body params
finalParams = {};
} else {
reqOpts.headers['Content-type'] = 'application/json';
}
}
if (Object.keys(finalParams).length) {
// not all of the user's parameters were used to build the request path
// add them as a query string
var qs = helpers.makeQueryString(finalParams)
reqOpts.url += '?' + qs
}
if (!self.config.app_only_auth) {
// with user auth, we can just pass an oauth object to requests
// to have the request signed
var oauth_ts = Date.now() + self._twitter_time_minus_local_time_ms;
reqOpts.oauth = {
consumer_key: self.config.consumer_key,
consumer_secret: self.config.consumer_secret,
token: self.config.access_token,
token_secret: self.config.access_token_secret,
timestamp: Math.floor(oauth_ts/1000).toString(),
}
callback(null, reqOpts);
return;
} else {
// we're using app-only auth, so we need to ensure we have a bearer token
// Once we have a bearer token, add the Authorization header and return the fully qualified `reqOpts`.
self._getBearerToken(function (err, bearerToken) {
if (err) {
callback(err, null)
return
}
reqOpts.headers['Authorization'] = 'Bearer ' + bearerToken;
callback(null, reqOpts)
return
})
}
}
/**
* Make HTTP request to Twitter REST API.
* @param {Object} reqOpts options object passed to `request()`
* @param {Object} twitOptions
* @param {String} method "GET" or "POST"
* @param {Function} callback user's callback
* @return {Undefined}
*/
Twitter.prototype._doRestApiRequest = function (reqOpts, twitOptions, method, callback) {
var request_method = request[method.toLowerCase()];
var req = request_method(reqOpts);
var body = '';
var response = null;
var onRequestComplete = function () {
if (body !== '') {
try {
body = JSON.parse(body)
} catch (jsonDecodeError) {
// there was no transport-level error, but a JSON object could not be decoded from the request body
// surface this to the caller
var err = helpers.makeTwitError('JSON decode error: Twitter HTTP response body was not valid JSON')
err.statusCode = response ? response.statusCode: null;
err.allErrors.concat({error: jsonDecodeError.toString()})
callback(err, body, response);
return
}
}
if (typeof body === 'object' && (body.error || body.errors)) {
// we got a Twitter API-level error response
// 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;
helpers.attachBodyInfoToError(err, body);
callback(err, body, response);
return
}
// success case - no errors in HTTP response body
callback(err, body, response)
}
req.on('response', function (res) {
response = res
// read data from `request` object which contains the decompressed HTTP response body,
// `response` is the unmodified http.IncomingMessage object which may contain compressed data
req.on('data', function (chunk) {
body += chunk.toString('utf8')
})
// we're done reading the response
req.on('end', function () {
onRequestComplete()
})
})
req.on('error', function (err) {
// transport-level error occurred - likely a socket error
if (twitOptions.retry &&
STATUS_CODES_TO_ABORT_ON.indexOf(err.statusCode) !== -1
) {
// retry the request since retries were specified and we got a status code we should retry on
self.request(method, path, params, callback);
return;
} else {
// pass the transport-level error to the caller
err.statusCode = null
err.code = null
err.allErrors = [];
helpers.attachBodyInfoToError(err, body)
callback(err, body, response);
return;
}
})
}
/**
* Creates/starts a connection object that stays connected to Twitter's servers
* using Twitter's rules.
*
* @param {String} path Resource path to connect to (eg. "statuses/sample")
* @param {Object} params user's params object
* @return {StreamingAPIConnection} [description]
*/
Twitter.prototype.stream = function (path, params) {
var self = this;
var twitOptions = (params && params.twit_options) || {};
var streamingConnection = new StreamingAPIConnection()
self._buildReqOpts('POST', path, params, true, function (err, reqOpts) {
if (err) {
// we can get an error if we fail to obtain a bearer token or construct reqOpts
// surface this on the streamingConnection instance (where a user may register their error handler)
streamingConnection.emit('error', err)
return
}
// set the properties required to start the connection
streamingConnection.reqOpts = reqOpts
streamingConnection.twitOptions = twitOptions
process.nextTick(function () {
streamingConnection.start()
})
})
return streamingConnection
}
/**
* Gets bearer token from cached reference on `self`, or fetches a new one and sets it on `self`.
*
* @param {Function} callback Function to invoke with (Error, bearerToken)
* @return {Undefined}
*/
Twitter.prototype._getBearerToken = function (callback) {
var self = this;
if (self._bearerToken) {
return callback(null, self._bearerToken)
}
helpers.getBearerToken(self.config.consumer_key, self.config.consumer_secret,
function (err, bearerToken) {
if (err) {
// return the fully-qualified Twit Error object to caller
callback(err, null);
return;
}
self._bearerToken = bearerToken;
callback(null, self._bearerToken);
return;
})
}
Twitter.prototype.normalizeParams = function (params) {
var normalized = params
if (params && typeof params === 'object') {
Object.keys(params).forEach(function (key) {
var value = params[key]
// replace any arrays in `params` with comma-separated string
if (Array.isArray(value))
normalized[key] = value.join(',')
})
} else if (!params) {
normalized = {}
}
return normalized
}
Twitter.prototype.setAuth = function (auth) {
var self = this
var configKeys = [
'consumer_key',
'consumer_secret',
'access_token',
'access_token_secret'
];
// update config
configKeys.forEach(function (k) {
if (auth[k]) {
self.config[k] = auth[k]
}
})
this._validateConfigOrThrow(self.config);
}
Twitter.prototype.getAuth = function () {
return this.config
}
//
// Check that the required auth credentials are present in `config`.
// @param {Object} config Object containing credentials for REST API auth
//
Twitter.prototype._validateConfigOrThrow = function (config) {
//check config for proper format
if (typeof config !== 'object') {
throw new TypeError('config must be object, got ' + typeof config)
}
if (typeof config.timeout_ms !== 'undefined' && isNaN(Number(config.timeout_ms))) {
throw new TypeError('Twit config `timeout_ms` must be a Number. Got: ' + config.timeout_ms + '.');
}
if (config.app_only_auth) {
var auth_type = 'app-only auth'
var required_keys = required_for_app_auth
} else {
var auth_type = 'user auth'
var required_keys = required_for_user_auth
}
required_keys.forEach(function (req_key) {
if (!config[req_key]) {
var err_msg = util.format('Twit config must include `%s` when using %s.', req_key, auth_type)
throw new Error(err_msg)
}
})
}
module.exports = Twitter

105
node_modules/twit/package.json generated vendored Normal file
View File

@@ -0,0 +1,105 @@
{
"_args": [
[
{
"name": "twit",
"raw": "twit",
"rawSpec": "",
"scope": null,
"spec": "latest",
"type": "tag"
},
"C:\\Users\\talor\\bots\\polibug"
]
],
"_from": "twit@latest",
"_id": "twit@2.2.4",
"_inCache": true,
"_installable": true,
"_location": "/twit",
"_nodeVersion": "0.12.0",
"_npmOperationalInternal": {
"host": "packages-12-west.internal.npmjs.com",
"tmp": "tmp/twit-2.2.4.tgz_1462157237561_0.11102098692208529"
},
"_npmUser": {
"email": "tolgatezel11@gmail.com",
"name": "ttezel"
},
"_npmVersion": "2.5.1",
"_phantomChildren": {},
"_requested": {
"name": "twit",
"raw": "twit",
"rawSpec": "",
"scope": null,
"spec": "latest",
"type": "tag"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/twit/-/twit-2.2.4.tgz",
"_shasum": "811e10092eb30ded476e21689dd8a90ee63963cb",
"_shrinkwrap": null,
"_spec": "twit",
"_where": "C:\\Users\\talor\\bots\\polibug",
"author": {
"name": "Tolga Tezel"
},
"bugs": {
"url": "https://github.com/ttezel/twit/issues"
},
"dependencies": {
"bluebird": "^3.1.5",
"mime": "^1.3.4",
"request": "2.58.0"
},
"description": "Twitter API client for node (REST & Streaming)",
"devDependencies": {
"async": "0.2.9",
"colors": "0.6.x",
"commander": "2.6.0",
"mocha": "2.1.0",
"rewire": "2.3.4",
"sinon": "1.15.4"
},
"directories": {},
"dist": {
"shasum": "811e10092eb30ded476e21689dd8a90ee63963cb",
"tarball": "https://registry.npmjs.org/twit/-/twit-2.2.4.tgz"
},
"engines": {
"node": ">=0.10.0"
},
"gitHead": "3230bc88906cfcb2608fbee360cee98729a6580c",
"homepage": "https://github.com/ttezel/twit",
"keywords": [
"twitter",
"api",
"rest",
"stream",
"streaming",
"oauth"
],
"license": "MIT",
"main": "./lib/twitter",
"maintainers": [
{
"email": "tolgatezel11@gmail.com",
"name": "ttezel"
}
],
"name": "twit",
"optionalDependencies": {},
"readme": "ERROR: No README data found!",
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/ttezel/twit.git"
},
"scripts": {
"test": "mocha tests/* -t 70000 -R spec --bail --globals domain,_events,_maxListeners"
},
"version": "2.2.4"
}

46
node_modules/twit/tests/helpers.js generated vendored Normal file
View File

@@ -0,0 +1,46 @@
var EventEmitter = require('events').EventEmitter;
var stream = require('stream');
var util = require('util');
// Used to stub out calls to `request`.
exports.FakeRequest = function () {
EventEmitter.call(this)
}
util.inherits(exports.FakeRequest, EventEmitter)
exports.FakeRequest.prototype.destroy = function () {
}
// Used to stub out the http.IncomingMessage object emitted by the "response" event on `request`.
exports.FakeResponse = function (statusCode, body) {
if (!body) {
body = '';
}
this.statusCode = statusCode;
stream.Readable.call(this);
this.push(body);
this.push(null);
}
util.inherits(exports.FakeResponse, stream.Readable);
exports.FakeResponse.prototype._read = function () {
}
exports.FakeResponse.prototype.destroy = function () {
}
exports.generateRandomString = function generateRandomString (length) {
var length = length || 10
var ret = ''
var alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789'
for (var i = 0; i < length; i++) {
// use an easy set of unicode as an alphabet - twitter won't reformat them
// which makes testing easier
ret += alphabet[Math.floor(Math.random()*alphabet.length)]
}
ret = encodeURI(ret)
return ret
}

BIN
node_modules/twit/tests/img/bigbird.jpg generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
node_modules/twit/tests/img/cutebird.png generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
node_modules/twit/tests/img/snoopy-animated.gif generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 B

BIN
node_modules/twit/tests/img/twitterbird.gif generated vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

74
node_modules/twit/tests/multiple-conn.js generated vendored Normal file
View File

@@ -0,0 +1,74 @@
var assert = require('assert');
var Twit = require('../lib/twitter');
var config1 = require('../config1');
var colors = require('colors');
var restTest = require('./rest.js');
/*
Don't run these tests often otherwise Twitter will rate limit you
*/
describe.skip('multiple connections', function () {
it('results in one of the streams closing', function (done) {
var twit = new Twit(config1);
var streams = [
twit.stream('statuses/sample'),
twit.stream('statuses/sample'),
twit.stream('statuses/sample'),
];
streams.forEach(function (stream, i) {
stream.on('disconnect', function (disconnect) {
console.log('Disconect for stream', i)
assert.equal(typeof disconnect, 'object');
done();
});
stream.on('error', function (errMsg) {
console.log('error for stream', i, errMsg)
})
stream.on('tweet', function (t) {
console.log(i)
})
stream.on('connected', function () {
console.log('Stream', i, 'connected.')
})
});
});
});
describe.skip('Managing multiple streams legally', function () {
this.timeout(60000);
it('updating track keywords without losing data', function (done) {
var twit = new Twit(config1);
var stream1 = twit.stream('statuses/filter', { track: ['#no'] });
stream1.once('tweet', function (tweet) {
console.log('got tweet from first stream')
restTest.checkTweet(tweet);
restTest.assertTweetHasText(tweet, '#no');
// update our track list and initiate a new connection
var stream2 = twit.stream('statuses/filter', { track: ['#fun'] });
stream2.once('connected', function (res) {
console.log('second stream connected')
// stop the first stream immediately
stream1.stop();
assert.equal(res.statusCode, 200)
stream2.once('tweet', function (tweet) {
restTest.checkTweet(tweet);
restTest.assertTweetHasText(tweet, '#fun');
return done();
})
});
});
});
});

823
node_modules/twit/tests/rest.js generated vendored Normal file
View File

@@ -0,0 +1,823 @@
var assert = require('assert')
, EventEmitter = require('events').EventEmitter
, fs = require('fs')
, sinon = require('sinon')
, Twit = require('../lib/twitter')
, config1 = require('../config1')
, config2 = require('../config2')
, helpers = require('./helpers')
, util = require('util')
, async = require('async')
describe('REST API', function () {
var twit = null
before(function () {
twit = new Twit(config1);
})
it('GET `account/verify_credentials`', function (done) {
twit.get('account/verify_credentials', function (err, reply, response) {
checkReply(err, reply)
assert.notEqual(reply.followers_count, undefined)
assert.notEqual(reply.friends_count, undefined)
assert.ok(reply.id_str)
checkResponse(response)
assert(response.headers['x-rate-limit-limit'])
done()
})
})
it('GET `account/verify_credentials` using promise API only', function (done) {
twit
.get('account/verify_credentials', { skip_status: true })
.catch(function (err) {
console.log('catch err', err.stack)
})
.then(function (result) {
checkReply(null, result.data)
assert.notEqual(result.data.followers_count, undefined)
assert.notEqual(result.data.friends_count, undefined)
assert.ok(result.data.id_str)
checkResponse(result.resp)
assert(result.resp.headers['x-rate-limit-limit'])
done()
})
})
it('GET `account/verify_credentials` using promise API AND callback API', function (done) {
var i = 0;
var _checkDataAndResp = function (data, resp) {
checkReply(null, data)
assert.notEqual(data.followers_count, undefined)
assert.notEqual(data.friends_count, undefined)
assert.ok(data.id_str)
checkResponse(resp)
assert(resp.headers['x-rate-limit-limit'])
i++;
if (i == 2) {
done()
}
}
var get = twit.get('account/verify_credentials', function (err, data, resp) {
assert(!err, err);
_checkDataAndResp(data, resp);
});
get.catch(function (err) {
console.log('Got error:', err.stack)
});
get.then(function (result) {
_checkDataAndResp(result.data, result.resp);
});
})
it('POST `account/update_profile`', function (done) {
twit.post('account/update_profile', function (err, reply, response) {
checkReply(err, reply)
console.log('\nscreen name:', reply.screen_name)
checkResponse(response)
done()
})
})
it('POST `statuses/update` and POST `statuses/destroy:id`', function (done) {
var tweetId = null
var params = {
status: '@tolga_tezel tweeting using github.com/ttezel/twit. ' + helpers.generateRandomString(7)
}
twit.post('statuses/update', params, function (err, reply, response) {
checkReply(err, reply)
console.log('\ntweeted:', reply.text)
tweetId = reply.id_str
assert(tweetId)
checkResponse(response)
var deleteParams = { id: tweetId }
// Try up to 2 times to delete the tweet.
// Even after a 200 response to statuses/update is returned, a delete might 404 so we retry.
exports.req_with_retries(twit, 2, 'post', 'statuses/destroy/:id', deleteParams, [404], function (err, body, response) {
checkReply(err, body)
checkTweet(body)
checkResponse(response)
done()
})
})
})
it('POST `statuses/update` with characters requiring escaping', function (done) {
var params = { status: '@tolga_tezel tweeting using github.com/ttezel/twit :) !' + helpers.generateRandomString(15) }
twit.post('statuses/update', params, function (err, reply, response) {
checkReply(err, reply)
console.log('\ntweeted:', reply.text)
checkResponse(response)
var text = reply.text
assert(reply.id_str)
twit.post('statuses/destroy/:id', { id: reply.id_str }, function (err, reply, response) {
checkReply(err, reply)
checkTweet(reply)
assert.equal(reply.text, text)
checkResponse(response)
done()
})
})
})
it('POST `statuses/update` with \'Hi!!\' works', function (done) {
var params = { status: 'Hi!!' }
twit.post('statuses/update', params, function (err, reply, response) {
checkReply(err, reply)
console.log('\ntweeted:', reply.text)
checkResponse(response)
var text = reply.text
var destroyRoute = 'statuses/destroy/'+reply.id_str
twit.post(destroyRoute, function (err, reply, response) {
checkReply(err, reply)
checkTweet(reply)
assert.equal(reply.text, text)
checkResponse(response)
done()
})
})
})
it('GET `statuses/home_timeline`', function (done) {
twit.get('statuses/home_timeline', function (err, reply, response) {
checkReply(err, reply)
checkTweet(reply[0])
checkResponse(response)
done()
})
})
it('GET `statuses/mentions_timeline`', function (done) {
twit.get('statuses/mentions_timeline', function (err, reply, response) {
checkReply(err, reply)
checkTweet(reply[0])
done()
})
})
it('GET `statuses/user_timeline`', function (done) {
var params = {
screen_name: 'tolga_tezel'
}
twit.get('statuses/user_timeline', params, function (err, reply, response) {
checkReply(err, reply)
checkTweet(reply[0])
checkResponse(response)
done()
})
})
it('GET `search/tweets` { q: "a", since_id: 12345 }', function (done) {
var params = { q: 'a', since_id: 12345 }
twit.get('search/tweets', params, function (err, reply, response) {
checkReply(err, reply)
assert.ok(reply.statuses)
checkTweet(reply.statuses[0])
checkResponse(response)
done()
})
})
it('GET `search/tweets` { q: "fun since:2011-11-11" }', function (done) {
var params = { q: 'fun since:2011-11-11', count: 100 }
twit.get('search/tweets', params, function (err, reply, response) {
checkReply(err, reply)
assert.ok(reply.statuses)
checkTweet(reply.statuses[0])
console.log('\nnumber of fun statuses:', reply.statuses.length)
checkResponse(response)
done()
})
})
it('GET `search/tweets`, using `q` array', function (done) {
var params = {
q: [ 'banana', 'mango', 'peach' ]
}
twit.get('search/tweets', params, function (err, reply, response) {
checkReply(err, reply)
assert.ok(reply.statuses)
checkTweet(reply.statuses[0])
checkResponse(response)
done()
})
})
it('GET `search/tweets` with count set to 100', function (done) {
var params = {
q: 'happy',
count: 100
}
twit.get('search/tweets', params, function (err, reply, res) {
checkReply(err, reply)
console.log('\nnumber of tweets from search:', reply.statuses.length)
// twitter won't always send back 100 tweets if we ask for 100,
// but make sure it's close to 100
assert(reply.statuses.length > 95)
done()
})
})
it('GET `search/tweets` with geocode', function (done) {
var params = {
q: 'apple', geocode: [ '37.781157', '-122.398720', '1mi' ]
}
twit.get('search/tweets', params, function (err, reply) {
checkReply(err, reply)
done()
})
})
it('GET `direct_messages`', function (done) {
twit.get('direct_messages', function (err, reply, response) {
checkResponse(response)
checkReply(err, reply)
assert.ok(Array.isArray(reply))
done()
})
})
it('GET `followers/ids`', function (done) {
twit.get('followers/ids', function (err, reply, response) {
checkReply(err, reply)
assert.ok(Array.isArray(reply.ids))
checkResponse(response)
done()
})
})
it('GET `followers/ids` of screen_name tolga_tezel', function (done) {
twit.get('followers/ids', { screen_name: 'tolga_tezel' }, function (err, reply, response) {
checkReply(err, reply)
assert.ok(Array.isArray(reply.ids))
checkResponse(response)
done()
})
})
it('POST `statuses/retweet`', function (done) {
// search for a tweet to retweet
twit.get('search/tweets', { q: 'apple' }, function (err, reply, response) {
checkReply(err, reply)
assert.ok(reply.statuses)
var tweet = reply.statuses[0]
checkTweet(tweet)
var tweetId = tweet.id_str
assert(tweetId)
twit.post('statuses/retweet/'+tweetId, function (err, reply, response) {
checkReply(err, reply)
var retweetId = reply.id_str
assert(retweetId)
twit.post('statuses/destroy/'+retweetId, function (err, reply, response) {
checkReply(err, reply)
done()
})
})
})
})
// 1.1.8 usage
it('POST `statuses/retweet/:id` without `id` in params returns error', function (done) {
twit.post('statuses/retweet/:id', function (err, reply, response) {
assert(err)
assert.equal(err.message, 'Twit: Params object is missing a required parameter for this request: `id`')
done()
})
})
// 1.1.8 usage
it('POST `statuses/retweet/:id`', function (done) {
// search for a tweet to retweet
twit.get('search/tweets', { q: 'banana' }, function (err, reply, response) {
checkReply(err, reply)
assert.ok(reply.statuses)
var tweet = reply.statuses[0]
checkTweet(tweet)
var tweetId = tweet.id_str
assert(tweetId)
twit.post('statuses/retweet/:id', { id: tweetId }, function (err, reply) {
checkReply(err, reply)
var retweetId = reply.id_str
assert(retweetId)
twit.post('statuses/destroy/:id', { id: retweetId }, function (err, reply, response) {
checkReply(err, reply)
done()
})
})
})
})
// 1.1.8 usage
// skip for now since this API call is having problems on Twitter's side (404)
it.skip('GET `users/suggestions/:slug`', function (done) {
twit.get('users/suggestions/:slug', { slug: 'funny' }, function (err, reply, res) {
checkReply(err, reply)
assert.equal(reply.slug, 'funny')
done()
})
})
// 1.1.8 usage
// skip for now since this API call is having problems on Twitter's side (404)
it.skip('GET `users/suggestions/:slug/members`', function (done) {
twit.get('users/suggestions/:slug/members', { slug: 'funny' }, function (err, reply, res) {
checkReply(err, reply)
assert(reply[0].id_str)
assert(reply[0].screen_name)
done()
})
})
// 1.1.8 usage
it('GET `geo/id/:place_id`', function (done) {
var placeId = 'df51dec6f4ee2b2c'
twit.get('geo/id/:place_id', { place_id: placeId }, function (err, reply, res) {
checkReply(err, reply)
assert(reply.country)
assert(reply.bounding_box)
assert.equal(reply.id, placeId)
done()
})
})
it('POST `direct_messages/new`', function (done) {
var dmId
async.series({
postDm: function (next) {
var dmParams = {
screen_name: 'tolga_tezel',
text: 'hey this is a direct message from twit! :) ' + helpers.generateRandomString(15)
}
// post a direct message from the sender's account
twit.post('direct_messages/new', dmParams, function (err, reply) {
assert(!err, err)
assert(reply)
dmId = reply.id_str
exports.checkDm(reply)
assert.equal(reply.text, dmParams.text)
assert(dmId)
return next()
})
},
deleteDm: function (next) {
twit.post('direct_messages/destroy', { id: dmId }, function (err, reply) {
assert(!err, err)
exports.checkDm(reply)
assert.equal(reply.id, dmId)
return next()
})
}
}, done);
})
describe('Media Upload', function () {
var twit = null
before(function () {
twit = new Twit(config1)
})
it('POST media/upload with png', function (done) {
var b64content = fs.readFileSync(__dirname + '/img/cutebird.png', { encoding: 'base64' })
twit.post('media/upload', { media_data: b64content }, function (err, data, response) {
assert.equal(response.statusCode, 200)
assert(!err, err)
exports.checkMediaUpload(data)
assert(data.image.image_type == 'image/png' || data.image.image_type == 'image\/png')
done()
})
})
it('POST media/upload with JPG', function (done) {
var b64content = fs.readFileSync(__dirname + '/img/bigbird.jpg', { encoding: 'base64' })
twit.post('media/upload', { media_data: b64content }, function (err, data, response) {
assert(!err, err)
exports.checkMediaUpload(data)
assert.equal(data.image.image_type, 'image/jpeg')
done()
})
})
it('POST media/upload with static GIF', function (done) {
var b64content = fs.readFileSync(__dirname + '/img/twitterbird.gif', { encoding: 'base64' })
twit.post('media/upload', { media_data: b64content }, function (err, data, response) {
assert(!err, err)
exports.checkMediaUpload(data)
assert.equal(data.image.image_type, 'image/gif')
done()
})
})
it('POST media/upload with animated GIF using `media_data` parameter', function (done) {
var b64content = fs.readFileSync(__dirname + '/img/snoopy-animated.gif', { encoding: 'base64' })
twit.post('media/upload', { media_data: b64content }, function (err, data, response) {
assert(!err, err)
exports.checkMediaUpload(data)
var expected_image_types = ['image/gif', 'image/animatedgif']
var image_type = data.image.image_type
assert.ok(expected_image_types.indexOf(image_type) !== -1, 'got unexpected image type:' + image_type)
done()
})
})
it('POST media/upload with animated GIF, then POST a tweet referencing the media', function (done) {
var b64content = fs.readFileSync(__dirname + '/img/snoopy-animated.gif', { encoding: 'base64' });
twit.post('media/upload', { media_data: b64content }, function (err, data, response) {
assert(!err, err)
exports.checkMediaUpload(data)
var expected_image_types = ['image/gif', 'image/animatedgif']
var image_type = data.image.image_type
assert.ok(expected_image_types.indexOf(image_type) !== -1, 'got unexpected image type:' + image_type)
var mediaIdStr = data.media_id_string
assert(mediaIdStr)
var params = { status: '#nofilter', media_ids: [mediaIdStr] }
twit.post('statuses/update', params, function (err, data, response) {
assert(!err, err)
var tweetIdStr = data.id_str
assert(tweetIdStr)
exports.req_with_retries(twit, 3, 'post', 'statuses/destroy/:id', { id: tweetIdStr }, [404], function (err, data, response) {
checkReply(err, data)
done()
})
})
})
})
it('POST media/upload with animated GIF using `media` parameter', function (done) {
var b64Content = fs.readFileSync(__dirname + '/img/snoopy-animated.gif', { encoding: 'base64' });
twit.post('media/upload', { media: b64Content }, function (err, data, response) {
assert(!err, err)
exports.checkMediaUpload(data)
var expected_image_types = ['image/gif', 'image/animatedgif']
var image_type = data.image.image_type
assert.ok(expected_image_types.indexOf(image_type) !== -1, 'got unexpected image type:' + image_type)
done()
})
})
it('POST media/upload with JPG, then POST media/metadata/create with alt text', function (done) {
var b64content = fs.readFileSync(__dirname + '/img/bigbird.jpg', { encoding: 'base64' })
twit.post('media/upload', { media_data: b64content }, function (err, data, response) {
assert(!err, err)
exports.checkMediaUpload(data)
assert.equal(data.image.image_type, 'image/jpeg')
var mediaIdStr = data.media_id_string
assert(mediaIdStr)
var altText = 'a very small Big Bird'
var params = { media_id: mediaIdStr, alt_text: { text: altText } }
twit.post('media/metadata/create', params, function (err, data, response) {
assert(!err, err)
// data is empty on media/metadata/create success; nothing more to assert
done();
})
})
})
})
it('POST account/update_profile_image', function (done) {
var b64content = fs.readFileSync(__dirname + '/img/snoopy-animated.gif', { encoding: 'base64' })
twit.post('account/update_profile_image', { image: b64content }, function (err, data, response) {
assert(!err, err);
exports.checkReply(err, data);
exports.checkUser(data);
done()
})
})
it('POST friendships/create', function (done) {
var params = { screen_name: 'tolga_tezel', follow: false };
twit.post('friendships/create', params, function (err, data, resp) {
assert(!err, err);
exports.checkReply(err, data);
exports.checkUser(data);
done();
});
})
describe('Favorites', function () {
it('POST favorites/create and POST favorites/destroy work', function (done) {
twit.post('favorites/create', { id: '583531943624597504' }, function (err, data, resp) {
assert(!err, err);
exports.checkReply(err, data);
var tweetIdStr = data.id_str;
assert(tweetIdStr);
twit.post('favorites/destroy', { id: tweetIdStr }, function (err, data, resp) {
assert(!err, err);
exports.checkReply(err, data);
assert(data.id_str);
assert(data.text);
done();
})
})
})
})
describe('error handling', function () {
describe('handling errors from the twitter api', function () {
it('should callback with an Error object with all the info and a response object', function (done) {
var twit = new Twit({
consumer_key: 'a',
consumer_secret: 'b',
access_token: 'c',
access_token_secret: 'd'
})
twit.get('account/verify_credentials', function (err, reply, res) {
assert(err instanceof Error)
assert(err.statusCode === 401)
assert(err.code > 0)
assert(err.message.match(/token/))
assert(err.twitterReply)
assert(err.allErrors)
assert(res)
assert(res.headers)
assert.equal(res.statusCode, 401)
done()
})
})
})
describe('handling other errors', function () {
it('should just forward errors raised by underlying request lib', function (done) {
var twit = new Twit(config1);
var fakeError = new Error('derp')
var FakeRequest = function () {
EventEmitter.call(this)
}
util.inherits(FakeRequest, EventEmitter)
var stubGet = function () {
var fakeRequest = new FakeRequest()
process.nextTick(function () {
fakeRequest.emit('error', fakeError)
})
return fakeRequest
}
var request = require('request')
var stubGet = sinon.stub(request, 'get', stubGet)
twit.get('account/verify_credentials', function (err, reply, res) {
assert(err === fakeError)
// restore request.get
stubGet.restore()
done()
})
})
})
describe('Request timeout', function () {
it('set to 1ms should return with a timeout error', function (done) {
config1.timeout_ms = 1;
var twit = new Twit(config1);
twit.get('account/verify_credentials', function (err, reply, res) {
assert(err)
assert.equal(err.message, 'ETIMEDOUT')
delete config1.timeout_ms
done()
})
})
})
});
});
describe('Twit agent_options config', function () {
it('config.trusted_cert_fingerprints works against cert fingerprint for api.twitter.com:443', function (done) {
config1.trusted_cert_fingerprints = [
'66:EA:47:62:D9:B1:4F:1A:AE:89:5F:68:BA:6B:8E:BB:F8:1D:BF:8E'
];
var t = new Twit(config1);
t.get('account/verify_credentials', function (err, data, resp) {
assert(!err, err)
assert(data)
assert(data.id_str)
assert(data.name)
assert(data.screen_name)
delete config1.trusted_cert_fingerprints
done();
})
})
it('config.trusted_cert_fingerprints responds with Error when fingerprint mismatch occurs', function (done) {
config1.trusted_cert_fingerprints = [
'AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA'
];
var t = new Twit(config1);
t.get('account/verify_credentials', function (err, data, resp) {
assert(err)
assert(err.toString().indexOf('Trusted fingerprints are: ' + config1.trusted_cert_fingerprints[0]) !== -1)
delete config1.trusted_cert_fingerprints
done();
})
})
})
describe('Local time offset compensation', function () {
it('Compensates for local time being behind', function (done) {
var t1 = Date.now();
var t = new Twit(config2);
var stubNow = function () {
return 0;
}
var stubDateNow = sinon.stub(Date, 'now', stubNow);
t.get('account/verify_credentials', function (err, data, resp) {
assert(err);
t.get('account/verify_credentials', function (err, data, resp) {
assert(!err, err);
exports.checkReply(err, data);
exports.checkUser(data);
assert(t._twitter_time_minus_local_time_ms > 0)
stubDateNow.restore();
done();
})
})
})
})
/**
* Basic validation to verify we have no error and reply is an object
*
* @param {error} err error object (or null)
* @param {object} reply reply object received from twitter
*/
var checkReply = exports.checkReply = function (err, reply) {
assert.equal(err, null, 'reply err:'+util.inspect(err, true, 10, true))
assert.equal(typeof reply, 'object')
}
/**
* check the http response object and its headers
* @param {object} response http response object
*/
var checkResponse = exports.checkResponse = function (response) {
assert(response)
assert(response.headers)
assert.equal(response.statusCode, 200)
}
/**
* validate that @tweet is a tweet object
*
* @param {object} tweet `tweet` object received from twitter
*/
var checkTweet = exports.checkTweet = function (tweet) {
assert.ok(tweet)
assert.equal('string', typeof tweet.id_str, 'id_str wasnt string:'+tweet.id_str)
assert.equal('string', typeof tweet.text)
assert.ok(tweet.user)
assert.equal('string', typeof tweet.user.id_str)
assert.equal('string', typeof tweet.user.screen_name)
}
/**
* Validate that @dm is a direct message object
*
* @param {object} dm `direct message` object received from twitter
*/
exports.checkDm = function checkDm (dm) {
assert.ok(dm)
assert.equal('string', typeof dm.id_str)
assert.equal('string', typeof dm.text)
var recipient = dm.recipient
assert.ok(recipient)
assert.equal('string', typeof recipient.id_str)
assert.equal('string', typeof recipient.screen_name)
var sender = dm.sender
assert.ok(sender)
assert.equal('string', typeof sender.id_str)
assert.equal('string', typeof sender.screen_name)
assert.equal('string', typeof dm.text)
}
exports.checkMediaUpload = function checkMediaUpload (data) {
assert.ok(data)
assert.ok(data.image)
assert.ok(data.image.w)
assert.ok(data.image.h)
assert.ok(data.media_id)
assert.equal('string', typeof data.media_id_string)
assert.ok(data.size)
}
exports.checkUser = function checkUser (data) {
assert.ok(data)
assert.ok(data.id_str)
assert.ok(data.name)
assert.ok(data.screen_name)
}
exports.assertTweetHasText = function (tweet, text) {
assert(tweet.text.toLowerCase().indexOf(text) !== -1, 'expected to find '+text+' in text: '+tweet.text);
}
exports.req_with_retries = function (twit_instance, num_tries, verb, path, params, status_codes_to_retry, cb) {
twit_instance[verb](path, params, function (err, data, response) {
if (!num_tries || (status_codes_to_retry.indexOf(response.statusCode) === -1)) {
return cb(err, data, response)
}
exports.req_with_retries(twit_instance, num_tries - 1, verb, path, params, status_codes_to_retry, cb)
})
}

55
node_modules/twit/tests/rest_app_only_auth.js generated vendored Normal file
View File

@@ -0,0 +1,55 @@
var assert = require('assert')
var config1 = require('../config1');
var Twit = require('../lib/twitter');
var checkReply = require('./rest').checkReply;
var checkResponse = require('./rest').checkResponse;
var checkTweet = require('./rest').checkTweet;
describe('REST API using app-only auth', function () {
var twit = null
before(function () {
var config = {
consumer_key: config1.consumer_key,
consumer_secret: config1.consumer_secret,
app_only_auth: true,
}
twit = new Twit(config)
})
it('GET `application/rate_limit_status`', function (done) {
twit.get('application/rate_limit_status', function (err, body, response) {
checkReply(err, body)
checkResponse(response)
assert(body.rate_limit_context)
done()
})
})
it('GET `application/rate_limit_status with specific resource`', function (done) {
var params = { resources: [ 'users', 'search' ]}
twit.get('application/rate_limit_status', params, function (err, body, response) {
checkReply(err, body)
checkResponse(response)
assert(body.rate_limit_context)
assert(body.resources.users)
assert(body.resources.search)
assert.equal(Object.keys(body.resources).length, 2)
done()
})
})
it('GET `search/tweets` { q: "a", since_id: 12345 }', function (done) {
var params = { q: 'a', since_id: 12345 }
twit.get('search/tweets', params, function (err, reply, response) {
checkReply(err, reply)
assert.ok(reply.statuses)
checkTweet(reply.statuses[0])
checkResponse(response)
done()
})
})
})

90
node_modules/twit/tests/rest_chunked_upload.js generated vendored Normal file
View File

@@ -0,0 +1,90 @@
var assert = require('assert');
var fs = require('fs');
var mime = require('mime');
var path = require('path');
var config = require('../config1');
var Twit = require('../lib/twitter');
describe('twit.postMediaChunked', function () {
it('Posting media via twit.postMediaChunked works with .mp4', function (done) {
var twit = new Twit(config);
var mediaFilePath = path.join(__dirname, './video/station.mp4');
twit.postMediaChunked({ file_path: mediaFilePath }, function (err, bodyObj, resp) {
exports.checkUploadMedia(err, bodyObj, resp)
done()
})
})
it('POST media/upload via manual commands works with .mp4', function (done) {
var mediaFilePath = path.join(__dirname, './video/station.mp4');
var mediaType = mime.lookup(mediaFilePath);
var mediaFileSizeBytes = fs.statSync(mediaFilePath).size;
var twit = new Twit(config);
twit.post('media/upload', {
'command': 'INIT',
'media_type': mediaType,
'total_bytes': mediaFileSizeBytes
}, function (err, bodyObj, resp) {
assert(!err, err);
var mediaIdStr = bodyObj.media_id_string;
var isStreamingFile = true;
var isUploading = false;
var segmentIndex = 0;
var fStream = fs.createReadStream(mediaFilePath, { highWaterMark: 5 * 1024 * 1024 });
var _finalizeMedia = function (mediaIdStr, cb) {
twit.post('media/upload', {
'command': 'FINALIZE',
'media_id': mediaIdStr
}, cb)
}
var _checkFinalizeResp = function (err, bodyObj, resp) {
exports.checkUploadMedia(err, bodyObj, resp)
done();
}
fStream.on('data', function (buff) {
fStream.pause();
isStreamingFile = false;
isUploading = true;
twit.post('media/upload', {
'command': 'APPEND',
'media_id': mediaIdStr,
'segment_index': segmentIndex,
'media': buff.toString('base64'),
}, function (err, bodyObj, resp) {
assert(!err, err);
isUploading = false;
if (!isStreamingFile) {
_finalizeMedia(mediaIdStr, _checkFinalizeResp);
}
});
});
fStream.on('end', function () {
isStreamingFile = false;
if (!isUploading) {
_finalizeMedia(mediaIdStr, _checkFinalizeResp);
}
});
});
})
})
exports.checkUploadMedia = function (err, bodyObj, resp) {
assert(!err, err)
assert(bodyObj)
assert(bodyObj.media_id)
assert(bodyObj.media_id_string)
assert(bodyObj.size)
assert(bodyObj.video)
assert.equal(bodyObj.video.video_type, 'video/mp4')
}

646
node_modules/twit/tests/streaming.js generated vendored Normal file
View File

@@ -0,0 +1,646 @@
var assert = require('assert')
, http = require('http')
, EventEmitter = require('events').EventEmitter
, rewire = require('rewire')
, sinon = require('sinon')
, Twit = require('../lib/twitter')
, config1 = require('../config1')
, config2 = require('../config2')
, colors = require('colors')
, helpers = require('./helpers')
, util = require('util')
, zlib = require('zlib')
, async = require('async')
, restTest = require('./rest');
/**
* Stop the stream and check the tweet we got back.
* Call @done on completion.
*
* @param {object} stream object returned by twit.stream()
* @param {Function} done completion callback
*/
exports.checkStream = function (stream, done) {
stream.on('connected', function () {
console.log('\nconnected'.grey)
});
stream.once('tweet', function (tweet) {
stream.stop()
assert.ok(tweet)
assert.equal('string', typeof tweet.text)
assert.equal('string', typeof tweet.id_str)
console.log(('\ntweet: '+tweet.text).grey)
done()
});
stream.on('reconnecting', function (req, res, connectInterval) {
console.log('Got disconnected. Scheduling reconnect! statusCode:', res.statusCode, 'connectInterval', connectInterval)
});
stream.on('error', function (err) {
console.log('Stream emitted an error', err)
return done(err)
})
}
/**
* Check the stream state is correctly set for a stopped stream.
*
* @param {object} stream object returned by twit.stream()
*/
exports.checkStreamStopState = function (stream) {
assert.strictEqual(stream._connectInterval, 0)
assert.strictEqual(stream._usedFirstReconnect, false)
assert.strictEqual(stream._scheduledReconnect, undefined)
assert.strictEqual(stream._stallAbortTimeout, undefined)
}
describe('Streaming API', function () {
it('statuses/sample', function (done) {
var twit = new Twit(config1);
var stream = twit.stream('statuses/sample')
exports.checkStream(stream, done)
})
it('statuses/filter using `track`', function (done) {
this.timeout(120000)
var twit = new Twit(config2);
var stream = twit.stream('statuses/filter', { track: 'fun' })
exports.checkStream(stream, done)
})
it('statuses/filter using `locations` string', function (done) {
var twit = new Twit(config1);
var world = '-180,-90,180,90';
var stream = twit.stream('statuses/filter', { locations: world })
exports.checkStream(stream, done)
})
it('statuses/filter using `locations` array for San Francisco and New York', function (done) {
var twit = new Twit(config2);
var params = {
locations: [ '-122.75', '36.8', '121.75', '37.8', '-74', '40', '73', '41' ]
}
var stream = twit.stream('statuses/filter', params)
exports.checkStream(stream, done)
})
it('statuses/filter using `track` array', function (done) {
var twit = new Twit(config1);
var params = {
track: [ 'twitter', ':)', 'fun' ]
}
var stream = twit.stream('statuses/filter', params)
exports.checkStream(stream, done)
})
it('statuses/filter using `track` and `language`', function (done) {
var twit = new Twit(config1);
var params = {
track: [ 'twitter', '#apple', 'google', 'twitter', 'facebook', 'happy', 'party', ':)' ],
language: 'en'
}
var stream = twit.stream('statuses/filter', params)
exports.checkStream(stream, done)
})
it('stopping & restarting the stream works', function (done) {
var twit = new Twit(config2);
var stream = twit.stream('statuses/sample')
//stop the stream after 2 seconds
setTimeout(function () {
stream.stop()
exports.checkStreamStopState(stream)
console.log('\nstopped stream')
}, 2000)
//after 3 seconds, start the stream, and stop after 'connect'
setTimeout(function () {
stream.once('connected', function (req) {
console.log('\nrestarted stream')
stream.stop()
exports.checkStreamStopState(stream)
console.log('\nstopped stream')
done()
})
//restart the stream
stream.start()
}, 3000)
})
it('stopping & restarting stream emits to previously assigned callbacks', function (done) {
var twit = new Twit(config1);
var stream = twit.stream('statuses/sample')
var started = false
var numTweets = 0
stream.on('tweet', function (tweet) {
process.stdout.write('.')
if (!started) {
started = true
numTweets++
console.log('received tweet', numTweets)
console.log('stopping stream')
stream.stop()
exports.checkStreamStopState(stream)
// we've successfully received a new tweet after restarting, test successful
if (numTweets === 2) {
done()
} else {
started = false
console.log('restarting stream')
setTimeout(function () {
stream.start()
}, 1000)
}
}
})
stream.on('limit', function (limitMsg) {
console.log('limit', limitMsg)
})
stream.on('disconnect', function (disconnMsg) {
console.log('disconnect', disconnMsg)
})
stream.on('reconnect', function (req, res, ival) {
console.log('reconnect. statusCode:', res.statusCode, 'interval:', ival)
})
stream.on('connect', function (req) {
console.log('connect')
})
})
})
describe('streaming API direct message events', function () {
var senderScreenName;
var receiverScreenName;
var twitSender;
var twitReceiver;
// before we send direct messages the user receiving the DM
// has to follow the sender. Make this so.
before(function (done) {
twitSender = new Twit(config1);
twitReceiver = new Twit(config2);
// get sender/receiver names in parallel, then make the receiver follow the sender
async.parallel({
// get sender screen name and set it for tests to use
getSenderScreenName: function (parNext) {
console.log('getting sender user screen_name')
twitSender.get('account/verify_credentials', { twit_options: { retry: true } }, function (err, reply) {
assert(!err, err)
assert(reply)
assert(reply.screen_name)
senderScreenName = reply.screen_name
return parNext()
})
},
// get receiver screen name and set it for tests to use
getReceiverScreenName: function (parNext) {
console.log('getting receiver user screen_name')
twitReceiver.get('account/verify_credentials', { twit_options: { retry: true } }, function (err, reply) {
assert(!err, err)
assert(reply)
assert(reply.screen_name)
receiverScreenName = reply.screen_name
return parNext()
})
}
}, function (err) {
assert(!err, err)
var followParams = { screen_name: senderScreenName }
console.log('making receiver user follow the sender user')
// make receiver follow sender
twitReceiver.post('friendships/create', followParams, function (err, reply) {
assert(!err, err)
assert(reply.following)
done()
})
})
})
it('user_stream `direct_message` event', function (done) {
// User A follows User B
// User A connects to their user stream
// User B posts a DM to User A
// User A receives it in their user stream
this.timeout(0);
// build out DM params
function makeDmParams () {
return {
screen_name: receiverScreenName,
text: helpers.generateRandomString(10) + ' direct message streaming event test! :-) ' + helpers.generateRandomString(20),
twit_options: {
retry: true
}
}
}
var dmIdsReceived = []
var dmIdsSent = []
var sentDmFound = false
// start listening for user stream events
var receiverStream = twitReceiver.stream('user')
console.log('\nlistening for DMs')
// listen for direct_message event and check DM once it's received
receiverStream.on('direct_message', function (directMsg) {
if (sentDmFound) {
// don't call `done` more than once
return
}
console.log('got DM event. id:', directMsg.direct_message.id_str)
restTest.checkDm(directMsg.direct_message)
dmIdsReceived.push(directMsg.direct_message.id_str)
// make sure one of the DMs sent was found
// (we can send multiple DMs if our stream has to reconnect)
sentDmFound = dmIdsSent.some(function (dmId) {
return dmId == directMsg.direct_message.id_str
})
if (!sentDmFound) {
console.log('this DM doesnt match our test DMs - still waiting for a matching one.')
console.log('dmIdsSent', dmIdsSent)
return
}
receiverStream.stop()
return done()
})
var lastTimeSent = 0
var msToWait = 0
var postDmInterval = null
receiverStream.on('connected', function () {
var dmParams = makeDmParams()
console.log('sending a new DM:', dmParams.text)
twitSender.post('direct_messages/new', dmParams, function (err, reply) {
assert(!err, err)
assert(reply)
restTest.checkDm(reply)
assert(reply.id_str)
// we will check this dm against the reply recieved in the message event
dmIdsSent.push(reply.id_str)
console.log('successfully posted DM:', reply.text, reply.id_str)
if (dmIdsReceived.indexOf(reply.id_str) !== -1) {
// our response to the DM posting lost the race against the direct_message
// listener (we already got the event). So we can finish the test.
done()
}
})
})
after(function (done) {
console.log('cleaning up DMs:', dmIdsSent)
// delete the DMs we posted
var deleteDms = dmIdsSent.map(function (dmId) {
return function (next) {
assert.equal(typeof dmId, 'string')
console.log('\ndeleting DM', dmId)
var params = { id: dmId, twit_options: { retry: true } }
twitSender.post('direct_messages/destroy', params, function (err, reply) {
assert(!err, err)
restTest.checkDm(reply)
assert.equal(reply.id, dmId)
return next()
})
}
})
async.parallel(deleteDms, done)
})
})
})
describe('streaming API friends preamble', function () {
it('returns an array of strings if stringify_friend_ids is true', function (done) {
var twit = new Twit(config1);
var stream = twit.stream('user', { stringify_friend_ids: true });
stream.on('friends', function (friendsObj) {
assert(friendsObj)
assert(friendsObj.friends_str)
if (friendsObj.friends_str.length) {
assert.equal(typeof friendsObj.friends_str[0], 'string')
} else {
console.log('\nEmpty friends preamble:', friendsObj, '. Make some friends on Twitter! ^_^')
}
done()
})
})
})
describe('streaming API bad request', function (done) {
it('emits an error for a 401 response', function (done) {
var badCredentials = {
consumer_key: 'a'
, consumer_secret: 'b'
, access_token: 'c'
, access_token_secret: 'd'
}
var twit = new Twit(badCredentials);
var stream = twit.stream('statuses/filter', { track : ['foo'] });
stream.on('parser-error', function (err) {
assert.equal(err.statusCode, 401)
assert(err.twitterReply)
return done()
})
})
})
describe('streaming API `messages` event', function (done) {
var request = require('request');
var originalPost = request.post;
var RewiredTwit = rewire('../lib/twitter');
var RewiredStreamingApiConnection = rewire('../lib/streaming-api-connection');
var revertParser, revertTwit;
var MockParser = function () {
var self = this;
EventEmitter.call(self);
process.nextTick(function () {
self.emit('element', {scrub_geo: 'bar'})
self.emit('element', {limit: 'buzz'})
});
}
util.inherits(MockParser, EventEmitter);
before(function () {
revertTwit = RewiredTwit.__set__('StreamingAPIConnection', RewiredStreamingApiConnection);
revertParser = RewiredStreamingApiConnection.__set__('Parser', MockParser);
request.post = function () { return new helpers.FakeRequest() }
})
after(function () {
request.post = originalPost;
revertTwit();
revertParser();
})
it('is returned for 2 different event types', function (done) {
var twit = new RewiredTwit(config1);
var stream = twit.stream('statuses/sample');
var gotScrubGeo = false;
var gotLimit = false;
var numMessages = 0;
var maybeDone = function () {
if (gotScrubGeo && gotLimit && numMessages == 2) {
done()
}
}
stream.on('limit', function () {
gotLimit = true;
maybeDone();
});
stream.on('scrub_geo', function () {
gotScrubGeo = true;
maybeDone();
})
stream.on('message', function (msg) {
numMessages++;
maybeDone();
})
})
})
describe('streaming reconnect', function (done) {
it('correctly implements connection closing backoff', function (done) {
var stubPost = function () {
var fakeRequest = new helpers.FakeRequest()
process.nextTick(function () {
fakeRequest.emit('close')
})
return fakeRequest
}
var request = require('request')
var stubPost = sinon.stub(request, 'post', stubPost)
var twit = new Twit(config1);
var stream = twit.stream('statuses/filter', { track: [ 'fun', 'yolo']});
var reconnects = [0, 250, 500, 750]
var reconnectCount = -1
var testDone = false
stream.on('reconnect', function () {
if (testDone) {
return
}
reconnectCount += 1
var expectedInterval = reconnects[reconnectCount]
// make sure our connect interval is correct
assert.equal(stream._connectInterval, expectedInterval);
// simulate immediate reconnect by forcing a new connection (`self._connectInterval` parameter unchanged)
stream._startPersistentConnection();
if (reconnectCount === reconnects.length -1) {
// restore request.post
stubPost.restore()
testDone = true
return done();
}
});
});
it('correctly implements 420 backoff', function (done) {
var stubPost = function () {
var fakeRequest = new helpers.FakeRequest()
process.nextTick(function () {
var fakeResponse = new helpers.FakeResponse(420)
fakeRequest.emit('response', fakeResponse)
fakeRequest.emit('close')
})
return fakeRequest
}
var request = require('request')
var stubPost = sinon.stub(request, 'post', stubPost)
var twit = new Twit(config1);
var stream = twit.stream('statuses/filter', { track: [ 'fun', 'yolo']});
var reconnects = [60000, 120000, 240000, 480000]
var reconnectCount = -1
var testComplete = false
stream.on('reconnect', function (req, res, connectInterval) {
if (testComplete) {
// prevent race between last connection attempt firing a reconnect and us validating the final
// reconnect value in `reconnects`
return
}
reconnectCount += 1
var expectedInterval = reconnects[reconnectCount]
// make sure our connect interval is correct
assert.equal(stream._connectInterval, connectInterval);
assert.equal(stream._connectInterval, expectedInterval);
// simulate immediate reconnect by forcing a new connection (`self._connectInterval` parameter unchanged)
stream._startPersistentConnection();
if (reconnectCount === reconnects.length -1) {
// restore request.post
stubPost.restore()
testComplete = true
return done();
}
});
});
});
describe('Streaming API disconnect message', function (done) {
it('results in stopping the stream', function (done) {
var stubPost = function () {
var fakeRequest = new helpers.FakeRequest()
process.nextTick(function () {
var body = zlib.gzipSync(JSON.stringify({disconnect: true}) + '\r\n')
var fakeResponse = new helpers.FakeResponse(200, body)
fakeRequest.emit('response', fakeResponse);
fakeResponse.emit('close')
});
return fakeRequest
}
var request = require('request')
var origRequest = request.post
var stubs = sinon.collection
stubs.stub(request, 'post', stubPost)
var twit = new Twit(config1);
var stream = twit.stream('statuses/filter', { track: ['fun']});
stream.on('disconnect', function (disconnMsg) {
stream.stop();
// restore stub
request.post = origRequest
done();
})
})
});
describe('Streaming API Connection limit exceeded message', function (done) {
it('results in an `error` event containing the message', function (done) {
var errMsg = 'Exceeded connection limit for user';
var stubPost = function () {
var fakeRequest = new helpers.FakeRequest();
process.nextTick(function () {
var body = zlib.gzipSync(errMsg + '\r\n');
var fakeResponse = new helpers.FakeResponse(200, body);
fakeRequest.emit('response', fakeResponse);
fakeResponse.emit('close');
});
return fakeRequest
}
var request = require('request');
var origRequest = request.post;
var stubs = sinon.collection;
stubs.stub(request, 'post', stubPost);
var twit = new Twit(config1);
var stream = twit.stream('statuses/filter');
stream.on('error', function (err) {
assert(err.toString().indexOf(errMsg) !== -1, 'Unexpected error msg:' + errMsg + '.');;
stream.stop();
// restore stub
request.post = origRequest;
done();
})
})
})
describe('Streaming API connection management', function () {
it('.stop() works in all states', function (done) {
var stubPost = function () {
var fakeRequest = new helpers.FakeRequest();
process.nextTick(function () {
var body = zlib.gzipSync('Foobar\r\n');
var fakeResponse = new helpers.FakeResponse(200, body);
fakeRequest.emit('response', fakeResponse);
});
return fakeRequest
}
var request = require('request');
var origRequest = request.post;
var stubs = sinon.collection;
stubs.stub(request, 'post', stubPost);
var twit = new Twit(config1);
var stream = twit.stream('statuses/sample');
stream.stop();
console.log('\nStopped. Restarting..');
stream.start();
stream.once('connect', function(request) {
console.log('Stream emitted `connect`. Stopping & starting stream..')
stream.stop();
stream.once('connected', function () {
console.log('Stream emitted `connected`. Stopping stream.');
stream.stop();
stubs.restore();
done();
});
stream.start();
});
})
})

13
node_modules/twit/tests/test_helpers.js generated vendored Normal file
View File

@@ -0,0 +1,13 @@
var assert = require('assert')
var helpers = require('../lib/helpers')
describe('makeQueryString', function () {
it('correctly encodes Objects with String values', function () {
assert.equal(helpers.makeQueryString({a: 'Ladies + Gentlemen'}), 'a=Ladies%20%2B%20Gentlemen');
assert.equal(helpers.makeQueryString({a: 'An encoded string!'}), 'a=An%20encoded%20string%21');
assert.equal(helpers.makeQueryString({a: 'Dogs, Cats & Mice'}), 'a=Dogs%2C%20Cats%20%26%20Mice')
assert.equal(helpers.makeQueryString({a: '☃'}), 'a=%E2%98%83')
assert.equal(helpers.makeQueryString({a: '#haiku #poetry'}), 'a=%23haiku%20%23poetry')
assert.equal(helpers.makeQueryString({a: '"happy hour" :)'}), 'a=%22happy%20hour%22%20%3A%29')
})
})

105
node_modules/twit/tests/twit.js generated vendored Normal file
View File

@@ -0,0 +1,105 @@
var assert = require('assert')
, Twit = require('../lib/twitter')
, config1 = require('../config1')
describe('twit', function () {
describe('instantiation', function () {
it('works with var twit = new Twit()', function () {
var twit = new Twit({
consumer_key: 'a',
consumer_secret: 'b',
access_token: 'c',
access_token_secret: 'd'
});
assert(twit.config)
assert.equal(typeof twit.get, 'function')
assert.equal(typeof twit.post, 'function')
assert.equal(typeof twit.stream, 'function')
})
it('works with var twit = Twit()', function () {
var twit = Twit({
consumer_key: 'a',
consumer_secret: 'b',
access_token: 'c',
access_token_secret: 'd'
});
assert(twit.config)
assert.equal(typeof twit.get, 'function')
assert.equal(typeof twit.post, 'function')
assert.equal(typeof twit.stream, 'function')
})
})
describe('config', function () {
it('throws when passing empty config', function (done) {
assert.throws(function () {
var twit = new Twit({})
}, Error)
done()
})
it('throws when config is missing a required key', function (done) {
assert.throws(function () {
var twit = new Twit({
consumer_key: 'a'
, consumer_secret: 'a'
, access_token: 'a'
})
}, Error)
done()
})
it('throws when config provides all keys but they\'re empty strings', function (done) {
assert.throws(function () {
var twit = new Twit({
consumer_key: ''
, consumer_secret: ''
, access_token: ''
, access_token_secret: ''
})
}, Error)
done()
})
})
describe('setAuth()', function () {
var twit;
beforeEach(function () {
twit = new Twit({
consumer_key: 'a',
consumer_secret: 'b',
access_token: 'c',
access_token_secret: 'd'
})
})
it('should update the client\'s auth config', function (done) {
// partial update
twit.setAuth({
consumer_key: 'x',
consumer_secret: 'y'
})
assert(twit.config.consumer_key === 'x')
assert(twit.config.consumer_secret === 'y')
// full update
twit.setAuth(config1)
assert(twit.config.consumer_key === config1.consumer_key)
assert(twit.config.consumer_secret === config1.consumer_secret)
assert(twit.config.access_token === config1.access_token)
assert(twit.config.access_token_secret === config1.access_token_secret)
twit.get('account/verify_credentials', { twit_options: { retry: true } }, function (err, reply, response) {
assert(!err, err);
assert(response.headers['x-rate-limit-limit'])
done()
})
})
})
});

38
node_modules/twit/tests/user_stream.js generated vendored Normal file
View File

@@ -0,0 +1,38 @@
var assert = require('assert')
var Twit = require('../lib/twitter')
var config1 = require('../config1')
var streaming = require('./streaming')
//verify `friendsMsg` is a twitter 'friends' message object
function checkFriendsMsg (friendsMsg) {
var friendIds = friendsMsg.friends
assert(friendIds)
assert(Array.isArray(friendIds))
assert(friendIds[0])
}
describe('user events', function () {
it('friends', function (done) {
var twit = new Twit(config1);
var stream = twit.stream('user');
//make sure we're connected to the right endpoint
assert.equal(stream.reqOpts.url, 'https://userstream.twitter.com/1.1/user.json')
stream.on('friends', function (friendsMsg) {
checkFriendsMsg(friendsMsg)
stream.stop()
done()
})
stream.on('connect', function () {
console.log('\nuser stream connecting..')
})
stream.on('connected', function () {
console.log('user stream connected.')
})
})
})

BIN
node_modules/twit/tests/video/station.mp4 generated vendored Normal file

Binary file not shown.