UNPKG

7.54 kBJavaScriptView Raw
1'use strict';
2
3/**
4 * Module dependencies
5 */
6
7var url = require('url');
8var Streamparser = require('./parser');
9var request = require('request');
10var extend = require('deep-extend');
11
12// Package version
13var VERSION = require('../package.json').version;
14
15function Twitter(options) {
16 if (!(this instanceof Twitter)) { return new Twitter(options) }
17
18 this.VERSION = VERSION;
19
20 // Merge the default options with the client submitted options
21 this.options = extend({
22 consumer_key: null,
23 consumer_secret: null,
24 access_token_key: null,
25 access_token_secret: null,
26 bearer_token: null,
27 rest_base: 'https://api.twitter.com/1.1',
28 stream_base: 'https://stream.twitter.com/1.1',
29 user_stream_base: 'https://userstream.twitter.com/1.1',
30 site_stream_base: 'https://sitestream.twitter.com/1.1',
31 media_base: 'https://upload.twitter.com/1.1',
32 request_options: {
33 headers: {
34 Accept: '*/*',
35 Connection: 'close',
36 'User-Agent': 'node-twitter/' + VERSION
37 }
38 }
39 }, options);
40
41 // Default to user authentication
42 var authentication_options = {
43 oauth: {
44 consumer_key: this.options.consumer_key,
45 consumer_secret: this.options.consumer_secret,
46 token: this.options.access_token_key,
47 token_secret: this.options.access_token_secret
48 }
49 };
50
51 // Check to see if we are going to use User Authentication or Application Authetication
52 if (this.options.bearer_token) {
53 authentication_options = {
54 headers: {
55 Authorization: 'Bearer ' + this.options.bearer_token
56 }
57 };
58 }
59
60 // Configure default request options
61 this.request = request.defaults(
62 extend(
63 this.options.request_options,
64 authentication_options
65 )
66 );
67
68 // Check if Promise present
69 this.allow_promise = (typeof Promise === 'function');
70}
71
72Twitter.prototype.__buildEndpoint = function(path, base) {
73 var bases = {
74 'rest': this.options.rest_base,
75 'stream': this.options.stream_base,
76 'user_stream': this.options.user_stream_base,
77 'site_stream': this.options.site_stream_base,
78 'media': this.options.media_base
79 };
80 var endpoint = (bases.hasOwnProperty(base)) ? bases[base] : bases.rest;
81
82 if (url.parse(path).protocol) {
83 endpoint = path;
84 }
85 else {
86 // If the path begins with media or /media
87 if (path.match(/^(\/)?media/)) {
88 endpoint = bases.media;
89 }
90 endpoint += (path.charAt(0) === '/') ? path : '/' + path;
91 }
92
93 // Remove trailing slash
94 endpoint = endpoint.replace(/\/$/, '');
95
96 // Add json extension if not provided in call
97 endpoint += (path.split('.').pop() !== 'json') ? '.json' : '';
98
99 return endpoint;
100};
101
102Twitter.prototype.__request = function(method, path, params, callback) {
103 var base = 'rest', promise = false;
104
105 // Set the callback if no params are passed
106 if (typeof params === 'function') {
107 callback = params;
108 params = {};
109 }
110 // Return promise if no callback is passed and promises available
111 else if (callback === undefined && this.allow_promise) {
112 promise = true;
113 }
114
115 // Set API base
116 if (typeof params.base !== 'undefined') {
117 base = params.base;
118 delete params.base;
119 }
120
121 // Build the options to pass to our custom request object
122 var options = {
123 method: method.toLowerCase(), // Request method - get || post
124 url: this.__buildEndpoint(path, base) // Generate url
125 };
126
127 // Pass url parameters if get
128 if (method === 'get') {
129 options.qs = params;
130 }
131
132 // Pass form data if post
133 if (method === 'post') {
134 var formKey = 'form';
135
136 if (typeof params.media !== 'undefined') {
137 formKey = 'formData';
138 }
139 options[formKey] = params;
140 }
141
142 // Promisified version
143 if (promise) {
144 var _this = this;
145 return new Promise(function(resolve, reject) {
146 _this.request(options, function(error, response, data) {
147 // request error
148 if (error) {
149 return reject(error);
150 }
151
152 // JSON parse error or empty strings
153 try {
154 // An empty string is a valid response
155 if (data === '') {
156 data = {};
157 }
158 else {
159 data = JSON.parse(data);
160 }
161 }
162 catch(parseError) {
163 return reject(new Error('JSON parseError with HTTP Status: ' + response.statusCode + ' ' + response.statusMessage));
164 }
165
166 // response object errors
167 // This should return an error object not an array of errors
168 if (data.errors !== undefined) {
169 return reject(data.errors);
170 }
171
172 // status code errors
173 if(response.statusCode < 200 || response.statusCode > 299) {
174 return reject(new Error('HTTP Error: ' + response.statusCode + ' ' + response.statusMessage));
175 }
176
177 // no errors
178 resolve(data);
179 });
180 });
181 }
182
183 // Callback version
184 this.request(options, function(error, response, data) {
185 // request error
186 if (error) {
187 return callback(error, data, response);
188 }
189
190 // JSON parse error or empty strings
191 try {
192 // An empty string is a valid response
193 if (data === '') {
194 data = {};
195 }
196 else {
197 data = JSON.parse(data);
198 }
199 }
200 catch(parseError) {
201 return callback(
202 new Error('JSON parseError with HTTP Status: ' + response.statusCode + ' ' + response.statusMessage),
203 data,
204 response
205 );
206 }
207
208
209 // response object errors
210 // This should return an error object not an array of errors
211 if (data.errors !== undefined) {
212 return callback(data.errors, data, response);
213 }
214
215 // status code errors
216 if(response.statusCode < 200 || response.statusCode > 299) {
217 return callback(
218 new Error('HTTP Error: ' + response.statusCode + ' ' + response.statusMessage),
219 data,
220 response
221 );
222 }
223 // no errors
224 callback(null, data, response);
225 });
226
227};
228
229/**
230 * GET
231 */
232Twitter.prototype.get = function(url, params, callback) {
233 return this.__request('get', url, params, callback);
234};
235
236/**
237 * POST
238 */
239Twitter.prototype.post = function(url, params, callback) {
240 return this.__request('post', url, params, callback);
241};
242
243/**
244 * STREAM
245 */
246Twitter.prototype.stream = function(method, params, callback) {
247 if (typeof params === 'function') {
248 callback = params;
249 params = {};
250 }
251
252 var base = 'stream';
253
254 if (method === 'user' || method === 'site') {
255 base = method + '_' + base;
256 }
257
258 var url = this.__buildEndpoint(method, base);
259 var request = this.request({url: url, qs: params});
260 var stream = new Streamparser();
261
262 stream.destroy = function() {
263 // FIXME: should we emit end/close on explicit destroy?
264 if ( typeof request.abort === 'function' ) {
265 request.abort(); // node v0.4.0
266 }
267 else {
268 request.socket.destroy();
269 }
270 };
271
272 request.on('response', function(response) {
273 if(response.statusCode !== 200) {
274 stream.emit('error', new Error('Status Code: ' + response.statusCode));
275 }
276 else {
277 stream.emit('response', response);
278 }
279
280 response.on('data', function(chunk) {
281 stream.receive(chunk);
282 });
283
284 response.on('error', function(error) {
285 stream.emit('error', error);
286 });
287
288 response.on('end', function() {
289 stream.emit('end', response);
290 });
291 });
292
293 request.on('error', function(error) {
294 stream.emit('error', error);
295 });
296 request.end();
297
298 if (typeof callback === 'function') {
299 callback(stream);
300 }
301 else {
302 return stream;
303 }
304};
305
306
307module.exports = Twitter;