1 | 'use strict';
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | var url = require('url');
|
8 | var Streamparser = require('./parser');
|
9 | var request = require('request');
|
10 | var extend = require('deep-extend');
|
11 |
|
12 |
|
13 | var VERSION = require('../package.json').version;
|
14 |
|
15 | function Twitter(options) {
|
16 | if (!(this instanceof Twitter)) { return new Twitter(options) }
|
17 |
|
18 | this.VERSION = VERSION;
|
19 |
|
20 |
|
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 |
|
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 |
|
52 | if (this.options.bearer_token) {
|
53 | authentication_options = {
|
54 | headers: {
|
55 | Authorization: 'Bearer ' + this.options.bearer_token
|
56 | }
|
57 | };
|
58 | }
|
59 |
|
60 |
|
61 | this.request = request.defaults(
|
62 | extend(
|
63 | this.options.request_options,
|
64 | authentication_options
|
65 | )
|
66 | );
|
67 |
|
68 |
|
69 | this.allow_promise = (typeof Promise === 'function');
|
70 | }
|
71 |
|
72 | Twitter.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 |
|
87 | if (path.match(/^(\/)?media/)) {
|
88 | endpoint = bases.media;
|
89 | }
|
90 | endpoint += (path.charAt(0) === '/') ? path : '/' + path;
|
91 | }
|
92 |
|
93 |
|
94 | endpoint = endpoint.replace(/\/$/, '');
|
95 |
|
96 |
|
97 | endpoint += (path.split('.').pop() !== 'json') ? '.json' : '';
|
98 |
|
99 | return endpoint;
|
100 | };
|
101 |
|
102 | Twitter.prototype.__request = function(method, path, params, callback) {
|
103 | var base = 'rest', promise = false;
|
104 |
|
105 |
|
106 | if (typeof params === 'function') {
|
107 | callback = params;
|
108 | params = {};
|
109 | }
|
110 |
|
111 | else if (callback === undefined && this.allow_promise) {
|
112 | promise = true;
|
113 | }
|
114 |
|
115 |
|
116 | if (typeof params.base !== 'undefined') {
|
117 | base = params.base;
|
118 | delete params.base;
|
119 | }
|
120 |
|
121 |
|
122 | var options = {
|
123 | method: method.toLowerCase(),
|
124 | url: this.__buildEndpoint(path, base)
|
125 | };
|
126 |
|
127 |
|
128 | if (method === 'get') {
|
129 | options.qs = params;
|
130 | }
|
131 |
|
132 |
|
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 |
|
143 | if (promise) {
|
144 | var _this = this;
|
145 | return new Promise(function(resolve, reject) {
|
146 | _this.request(options, function(error, response, data) {
|
147 |
|
148 | if (error) {
|
149 | return reject(error);
|
150 | }
|
151 |
|
152 |
|
153 | try {
|
154 |
|
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 |
|
167 |
|
168 | if (data.errors !== undefined) {
|
169 | return reject(data.errors);
|
170 | }
|
171 |
|
172 |
|
173 | if(response.statusCode < 200 || response.statusCode > 299) {
|
174 | return reject(new Error('HTTP Error: ' + response.statusCode + ' ' + response.statusMessage));
|
175 | }
|
176 |
|
177 |
|
178 | resolve(data);
|
179 | });
|
180 | });
|
181 | }
|
182 |
|
183 |
|
184 | this.request(options, function(error, response, data) {
|
185 |
|
186 | if (error) {
|
187 | return callback(error, data, response);
|
188 | }
|
189 |
|
190 |
|
191 | try {
|
192 |
|
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 |
|
210 |
|
211 | if (data.errors !== undefined) {
|
212 | return callback(data.errors, data, response);
|
213 | }
|
214 |
|
215 |
|
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 |
|
224 | callback(null, data, response);
|
225 | });
|
226 |
|
227 | };
|
228 |
|
229 |
|
230 |
|
231 |
|
232 | Twitter.prototype.get = function(url, params, callback) {
|
233 | return this.__request('get', url, params, callback);
|
234 | };
|
235 |
|
236 |
|
237 |
|
238 |
|
239 | Twitter.prototype.post = function(url, params, callback) {
|
240 | return this.__request('post', url, params, callback);
|
241 | };
|
242 |
|
243 |
|
244 |
|
245 |
|
246 | Twitter.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 |
|
264 | if ( typeof request.abort === 'function' ) {
|
265 | request.abort();
|
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 |
|
307 | module.exports = Twitter;
|