mirror of
https://github.com/thewesker/bug-em.git
synced 2025-12-22 04:51:05 -05:00
lol
This commit is contained in:
485
node_modules/twit/lib/twitter.js
generated
vendored
Normal file
485
node_modules/twit/lib/twitter.js
generated
vendored
Normal 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
|
||||
Reference in New Issue
Block a user