Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

The big shift from twitter-node to node-twitter.

  • Loading branch information...
commit 83e82469692a3d0d32c821a2962349a5d02cf910 1 parent d1944db
@jdub authored
View
3  .gitignore
@@ -1,3 +0,0 @@
-*build/
-*.lock-wscript
-*.node
View
3  LICENSE
@@ -1,4 +1,5 @@
-Copyright (c) 2010 rick
+node-twitter: Copyright (c) 2010 Jeff Waugh
+parser.js: Copyright (c) 2010 rick
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
View
151 README.md
@@ -1,129 +1,52 @@
-# twitter-node
+Asynchronous Twitter client API for node.js
+===========================================
-Creates a streaming connection with twitter, and pushes any incoming statuses to a tweet event.
+`node-twitter` aims to provide a complete, asynchronous client library for Twitter (and other compliant endpoints), including REST, stream and search APIs.
-## Installation
+It was inspired by, and uses some code from, technoweenie's `twitter-node`.
-Depends on ntest.
+## Requirements
-Use NPM:
+- node 0.2+
+- [node-oauth](http://github.com/ciaranj/node-oauth)
- npm install twitter-node
+## Getting started
-Otherwise create a symlink in `~/.node_libraries`
+It's early days for `node-twitter`, so I'm going to assume a fair amount of knowledge for the moment. Better documentation to come as we head towards a stable release.
- $ ln -s /path/to/twitter-node/lib/twitter-node ~/.node_libraries/twitter-node
+### Setup
-## Events
+ var sys = require('sys'), twitter = require('node-twitter');
+ var twit = new twitter({
+ consumer_key: 'STATE YOUR NAME',
+ consumer_secret: 'STATE YOUR NAME',
+ access_token_key: 'STATE YOUR NAME',
+ access_token_secret: 'STATE YOUR NAME'
+ });
-TwitterNode emits these events:
+### REST API
-* tweet(json) - This is emitted when a new tweet comes in. This will be a parsed JSON object.
-* limit(json) - This is emitted when a new limit command comes in. Currently, limit detection only works with parsed JSON objects.
-* delete(json) - This is emitted when a new delete command comes in. Currently, delete detection only works with parsed JSON objects.
-* end(response) - This is emitted when the http connection is closed. The HTTP response object is sent.
+Functions can be chained:
-See the [streaming API docs][api-docs] for examples of the limit and delete commands.
+ twit.verifyCredentials(function (data) {
+ sys.puts(sys.inspect(data));
+ })
+ .updateStatus(
+ { status: 'Test tweet from node-twitter/' + twitter.VERSION },
+ function (data) {
+ sys.puts(sys.inspect(data));
+ }
+ );
-[api-docs]: http://apiwiki.twitter.com/Streaming-API-Documentation
+### Streaming API
-## Usage
+ twit.stream('user', {track:'nodejs', function(stream) {
+ stream.on('data', function (data) {
+ sys.puts(sys.inspect(data));
+ });
+ });
- // twitter-node does not modify GLOBAL, that's so rude
- var TwitterNode = require('twitter-node').TwitterNode
- , sys = require('sys')
+## Contributors
- // you can pass args to create() or set them on the TwitterNode instance
- var twit = new TwitterNode({
- user: 'username',
- password: 'password',
- track: ['baseball', 'football'], // sports!
- follow: [12345, 67890], // follow these random users
- locations: [-122.75, 36.8, -121.75, 37.8] // tweets in SF
- });
-
- // adds to the track array set above
- twit.track('foosball');
-
- // adds to the following array set above
- twit.follow(2345);
-
- // follow tweets from NYC
- twit.location(-74, 40, -73, 41)
-
- // http://apiwiki.twitter.com/Streaming-API-Documentation#QueryParameters
- twit.params['count'] = 100;
-
- // http://apiwiki.twitter.com/Streaming-API-Documentation#Methods
- twit.action = 'sample'; // 'filter' is default
-
- twit.headers['User-Agent'] = 'whatever';
-
- // Make sure you listen for errors, otherwise
- // they are thrown
- twit.addListener('error', function(error) {
- console.log(error.message);
- });
-
- twit
- .addListener('tweet', function(tweet) {
- sys.puts("@" + tweet.user.screen_name + ": " + tweet.text);
- })
-
- .addListener('limit', function(limit) {
- sys.puts("LIMIT: " + sys.inspect(limit));
- })
-
- .addListener('delete', function(del) {
- sys.puts("DELETE: " + sys.inspect(del));
- })
-
- .addListener('end', function(resp) {
- sys.puts("wave goodbye... " + resp.statusCode);
- })
-
- .stream();
-
- // We can also add things to track on-the-fly
- twit.track('#nowplaying');
- twit.follow(1234);
-
- // This will reset the stream
- twit.stream();
-
-## Pre-Launch Checklist
-
-See http://apiwiki.twitter.com/Streaming-API-Documentation. Keep these points in mind when getting ready to use TwitterNode in production:
-
-* Not purposefully attempting to circumvent access limits and levels?
-* Creating the minimal number of connections?
-* Avoiding duplicate logins?
-* Backing off from failures: none for first disconnect, seconds for repeated network (TCP/IP) level issues, minutes for repeated HTTP (4XX codes)?
-* Using long-lived connections?
-* Tolerant of other objects and newlines in markup stream? (Non <status> objects...)
-* Tolerant of duplicate messages?
-
-## TODO
-
-* Handle failures as recommended from the Twitter stream documentation.
-
-## \m/
-
-* Tim Smart
-* Matt Secoske (secos)
-* kompozer
-* Twitter
-
-## Note on Patches/Pull Requests
-
-* Fork the project.
-* Make your feature addition or bug fix.
-* Add tests for it. This is important so I don't break it in a
- future version unintentionally.
-* Commit, do not mess with version or history.
- (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
-* Send me a pull request. Bonus points for topic branches.
-
-## Copyright
-
-Copyright (c) 2010 rick. See LICENSE for details.
+- [Jeff Waugh](http://github.com/jdub) (author)
+- [rick](http://github.com/technoweenie) (parser.js)
View
1  index.js
@@ -0,0 +1 @@
+module.exports = require('./lib/node-twitter');
View
541 lib/node-twitter/index.js
@@ -1,239 +1,302 @@
-var http = require('http'),
- query = require('querystring'),
- OAuth = require('./oauth');
- Parser = require('./parser'),
- EventEmitter = require('events').EventEmitter,
- Buffer = require('buffer').Buffer;
-
-// process.mixin is gone, a function for replacement
-function extend(a, b) {
- Object.keys(b).forEach(function (key) {
- a[key] = b[key];
- });
- return a;
-}
-
-// Creates a streaming connection with twitter, and pushes any incoming
-// statuses to a tweet event.
-//
-// options - optional Object that specifies custom configuration values.
-//
-// Valid option keys:
-//
-// port - Integer of the streaming api connection port. Defaults to 80.
-// host - String of the streaming api host. Defaults to 'stream.twitter.com'.
-// path - String of the base path for the request.
-// action - String part of the URL that specifies what to query for.
-// track - Array of keywords to filter. See track()
-// following - Array of userIDs to filter. See follow()
-// locations - Array of lat/long tuples. See location()
-// params - Extra HTTP params Object to send with the request.
-// user - String Twitter login name or email.
-// password - String Twitter password.
-//
-// Returns TwitterNode instance.
-var TwitterNode = exports.TwitterNode = function(options) {
- EventEmitter.call(this);
- if(!options) options = {};
- var self = this;
- this.port = options.port || 80;
- this.host = options.host || 'stream.twitter.com';
- this.secure = options.secure || this.port == 443;
- this.path = options.path || '/1/statuses/';
- this.action = options.action || 'filter';
- this.trackKeywords = options.track || [];
- this.following = options.follow || [];
- this.locations = options.locations || [];
- this.params = options.params || {};
- this.user = options.user;
- this.password = options.password;
- this.headers = { "User-Agent": 'Twitter-Node' };
- this.debug = false;
- this.parser = new Parser();
- this.parser.addListener('object', processJSONObject(this));
- this.parser.addListener('error', function (error) {
- self.emit('error', new Error('TwitterNode parser error: ' + error.message));
- });
- // Custom config handling, only required for streams beta features
- //
- // See: http://dev.twitter.com/pages/user_streams
- // http://dev.twitter.com/pages/user_streams_suggestions
- // http://dev.twitter.com/pages/site_streams
- if (options.action == 'user' || options.action == 'site') {
- this.port = options.port || 443;
- this.host = options.host || 'userstream.twitter.com';
- this.secure = options.secure || this.port == 443;
- this.path = options.path || '/2/';
- }
- if (options.action == 'site') {
- this.host = options.host || 'betastream.twitter.com';
- this.path = options.path || '/2b/';
- }
- if (options.oauth) {
- var oa = options.oauth,
- consumer = OAuth.createConsumer(oa.consumer_key, oa.consumer_secret),
- token = OAuth.createToken(oa.token, oa.token_secret),
- signer = OAuth.createHmac(consumer, token);
- this.signer = signer;
- delete this.user;
- }
- if (options.headers) {
- extend(this.headers, options.headers);
- }
-}
-
-TwitterNode.prototype = Object.create(EventEmitter.prototype);
-
-// Track the following keyword. If called multiple times, all words are sent
-// as a comma-separated parameter to Twitter.
-//
-// See: http://apiwiki.twitter.com/Streaming-API-Documentation#track
-//
-// word - String word to track.
-//
-// Returns nothing.
-TwitterNode.prototype.track = function track(word) {
- this.trackKeywords.push(word);
- return this;
-};
-
-// Follow the given twitter user (specified by their userID, not screen name)
-// If called multiple times, all userIDs are sent as a comma-separated
-// parameter to Twitter.
-//
-// See: http://apiwiki.twitter.com/Streaming-API-Documentation#follow
-//
-// userID - Integer userID to track.
-//
-// Returns nothing.
-TwitterNode.prototype.follow = function follow(userId) {
- this.following.push(userId);
- return this;
-};
-
-// Match tweets in the given bounding box.
-//
-// See: http://apiwiki.twitter.com/Streaming-API-Documentation#locations
-//
-// Example: location(-122.75, 36.8, -121.75, 37.8) // SF
-//
-// lng1, lat1 - southwest corner of the bounding box.
-// lng2, lat2 - northeast corner.
-//
-// Returns nothing.
-TwitterNode.prototype.location = function location(lng1, lat1, lng2, lat2) {
- this.locations.push(lng1, lat1, lng2, lat2)
- return this;
-};
-
-TwitterNode.prototype.stream = function stream() {
- if (this._clientResponse && this._clientResponse.connection) {
- this._clientResponse.socket.end();
- }
-
- if (this.action === 'filter' && this.buildParams() === '') return;
-
- var client = this._createClient(this.port, this.host),
- headers = extend({}, this.headers),
- twit = this,
- request;
-
- headers['Host'] = this.host;
-
- if (this.user) {
- headers['Authorization'] = basicAuth(this.user, this.password);
- request = client.request("GET", this.requestUrl(), headers);
- } else if (this.signer) {
- request = client.request("GET", this.requestUrl(), headers, null, this.signer);
- }
-
- request.addListener('response', function(response) {
- twit._clientResponse = response;
-
- response.addListener('data', function(chunk) {
- twit._receive(chunk);
- });
-
- response.addListener('end', function() {
- twit.emit('end', this);
- twit.emit('close', this);
- });
- });
- request.end();
- return this;
-};
-
-// UTILITY METHODS
-
-// Passes the received data to the streaming JSON parser.
-//
-// chunk - String data received from the HTTP stream.
-//
-// Returns nothing.
-TwitterNode.prototype._receive = function(chunk) {
- this.parser.receive(chunk);
- return this;
-};
-
-// Creates the HTTP client object for the stream connection. Override this
-// to pass in your own client if needed.
-//
-// port - Integer port number to connect to.
-// host - String host name to connect to.
-//
-// returns Http Client instance.
-TwitterNode.prototype._createClient = function(port, host, secure) {
- if (this.signer) {
- return OAuth.createClient(this.port, this.host, this.secure);
- }
- return http.createClient(this.port, this.host, this.secure);
-};
-
-// Builds the URL for the streaming request.
-//
-// Returns a String absolute URL.
-TwitterNode.prototype.requestUrl = function() {
- return this.path + this.action + ".json" + this.buildParams();
-};
-
-// Builds the GET params for the streaming request.
-//
-// Returns URI encoded string: "?track=LOST"
-TwitterNode.prototype.buildParams = function() {
- var options = {};
- extend(options, this.params);
- if (this.trackKeywords.length > 0) options.track = this.trackKeywords.join(",");
- if (this.following.length > 0) options.follow = this.following.join(",");
- if (this.locations.length > 0) options.locations = this.locations.join(",");
- if (options.track || options.follow || options.locations) {
- return "?" + query.stringify(options);
- }
- return "";
-};
-
-// Base64 encodes the given username and password.
-//
-// user - String Twitter screen name or email.
-// pass - String password.
-//
-// Returns a Basic Auth header fit for HTTP.
-var basicAuth = function basicAuth(user, pass) {
- return "Basic " + new Buffer(user + ":" + pass).toString('base64');
-};
-
-// Creates a callback for the object Event of the JSON Parser.
-//
-// twit - an instance of this TwitterNode.
-//
-// Returns a function to be passed to the addListener call on the parser.
-var processJSONObject = function processJSONObject(twit) {
- return function(tweet) {
- if (tweet.limit) {
- twit.emit('limit', tweet);
- } else if (tweet['delete']) {
- twit.emit('delete', tweet);
- } else {
- twit.emit('tweet', tweet);
- }
- };
-};
+var sys = require('sys'),
+ http = require('http'),
+ querystring = require('querystring'),
+ events = require('events'),
+ oauth = require('node-oauth'),
+ streamparser = require('./parser');
+
+function merge(defaults, options) {
+ if (typeof options === 'object') {
+ var keys = Object.keys(options);
+ for (var i = 0, len = keys.length; i < len; i++) {
+ var k = keys[i];
+ if (options[k] !== undefined) defaults[k] = options[k];
+ }
+ }
+ return defaults;
+}
+
+
+function Twitter(options) {
+ if (!(this instanceof Twitter)) return new Twitter(options);
+
+ var defaults = {
+ consumer_key: null,
+ consumer_secret: null,
+ access_token_key: null,
+ access_token_secret: null,
+
+ headers: {
+ 'Accept': '*/*',
+ 'Connection': 'close',
+ 'User-Agent': 'node-twitter/' + Twitter.VERSION
+ },
+
+ request_token_url: 'https://api.twitter.com/oauth/request_token',
+ access_token_url: 'https://api.twitter.com/oauth/access_token',
+ authenticate_url: 'https://api.twitter.com/oauth/authenticate',
+ authorize_url: 'https://api.twitter.com/oauth/authorize',
+
+ rest_base: 'https://api.twitter.com/1',
+ search_base: 'http://search.twitter.com',
+ stream_base: 'http://stream.twitter.com/1',
+ user_stream_base: 'https://userstream.twitter.com/2',
+ site_stream_base: 'https://betastream.twitter.com/2b'
+ };
+ this.options = merge(defaults, options);
+
+ // FIXME: first null is auth dest; fix when we add auth helper funcs
+ this.oauth = new oauth.OAuth(
+ this.options.request_token_url,
+ this.options.access_token_url,
+ this.options.consumer_key,
+ this.options.consumer_secret,
+ '1.0', null, 'HMAC-SHA1', null,
+ this.options.headers);
+}
+Twitter.VERSION = '0.1';
+module.exports = Twitter;
+
+
+/*
+ * GET
+ */
+Twitter.prototype.get = function(url, params, callback) {
+ this.oauth.get(this.options.rest_base + url,
+ this.options.access_token_key,
+ this.options.access_token_secret,
+ function(error, data, response) {
+ if (error) {
+ callback(error);
+ } else {
+ try {
+ var json = JSON.parse(data);
+ } catch(error) {
+ callback(error);
+ }
+ callback(json);
+ }
+ });
+ return this;
+}
+
+
+/*
+ * POST
+ */
+Twitter.prototype.post = function(url, content, content_type, callback) {
+ this.oauth.post(this.options.rest_base + url,
+ this.options.access_token_key,
+ this.options.access_token_secret,
+ content, content_type,
+ function(error, data, response) {
+ if (error) {
+ callback(error);
+ } else {
+ try {
+ var json = JSON.parse(data);
+ } catch(error) {
+ callback(error);
+ }
+ callback(json);
+ }
+ });
+ return this;
+}
+
+
+/*
+ * STREAM
+ */
+Twitter.prototype.stream = function(method, params, callback) {
+ var stream_base = this.options.stream_base;
+
+ if (method === 'user')
+ stream_base = this.options.user_stream_base;
+ else if (method === 'site')
+ stream_base = this.options.site_stream_base;
+
+ var url = '/' + method + '.json?' + querystring.stringify(params);
+
+ var stream = new events.EventEmitter();
+ var request = this.oauth.get(stream_base + url,
+ this.options.access_token_key,
+ this.options.access_token_secret);
+
+ request.on('response', function(response) {
+ var parser = new streamparser();
+
+ response.on('data', function(chunk) {
+ parser.receive(chunk);
+ });
+ response.on('end', function () {
+ stream.emit('end');
+ stream.emit('close');
+ });
+
+ parser.on('object', function(data) {
+ stream.emit('data', data);
+ });
+ parser.on('error', function(error) {
+ stream.emit('error', new Error('Parser error: ' + error.message));
+ });
+ });
+ request.end();
+
+ callback(stream);
+ return this;
+}
+
+
+/*
+ * CONVENIENCE FUNCTIONS
+ */
+
+// Timeline resources
+
+Twitter.prototype.getPublicTimeline = function(params, callback) {
+ var url = '/statuses/public_timeline.json';
+ this.get(url, params, callback);
+ return this;
+}
+
+Twitter.prototype.getHomeTimeline = function(params, callback) {
+ if (typeof params === 'function') {
+ callback = params;
+ params = null;
+ }
+ var url = '/statuses/home_timeline.json';
+ this.get(url, params, callback);
+ return this;
+}
+
+Twitter.prototype.getFriendsTimeline = function(params, callback) {
+ var url = '/statuses/friends_timeline.json';
+ this.get(url, params, callback);
+ return this;
+}
+
+Twitter.prototype.getUserTimeline = function(params, callback) {
+ var url = '/statuses/user_timeline.json';
+ this.get(url, params, callback);
+ return this;
+}
+
+Twitter.prototype.getMentions = function(params, callback) {
+ var url = '/statuses/mentions.json';
+ this.get(url, params, callback);
+ return this;
+}
+
+Twitter.prototype.getRetweetedByMe = function(params, callback) {
+ var url = '/statuses/retweeted_by_me.json';
+ this.get(url, params, callback);
+ return this;
+}
+
+Twitter.prototype.getRetweetedToMe = function(params, callback) {
+ var url = '/statuses/retweeted_to_me.json';
+ this.get(url, params, callback);
+ return this;
+}
+
+Twitter.prototype.getRetweetsOfMe = function(params, callback) {
+ var url = '/statuses/retweets_of_me.json';
+ this.get(url, params, callback);
+ return this;
+}
+
+// Tweets resources
+
+Twitter.prototype.showStatus = function(id, callback) {
+ var url = '/statuses/show/' + id + '.json';
+ this.get(url, null, callback);
+ return this;
+}
+
+Twitter.prototype.updateStatus = function(params, callback) {
+ var url = '/statuses/update.json';
+ var defaults = {
+ trim_user: 1,
+ include_entities: 1
+ };
+ params = merge(defaults, params);
+ this.post(url, params, null, callback);
+ return this;
+}
+
+Twitter.prototype.destroyStatus = function(id, callback) {
+ var url = '/statuses/destroy/' + id + '.json';
+ this.post(url, null, null, callback);
+ return this;
+}
+
+Twitter.prototype.retweetStatus = function(id, callback) {
+ var url = '/statuses/retweet/' + id + '.json';
+ this.post(url, null, null, callback);
+ return this;
+}
+
+Twitter.prototype.retweets = function(id, params, callback) {
+ var url = '/statuses/retweets/' + id + '.json';
+ this.post(url, params, null, callback);
+ return this;
+}
+
+Twitter.prototype.retweetedBy = function(id, params, callback) {
+ var url = '/statuses/' + id + '/retweeted_by.json';
+ this.post(url, params, null, callback);
+ return this;
+}
+
+Twitter.prototype.retweetedByIds = function(id, params, callback) {
+ var url = '/statuses/' + id + '/retweeted_by/ids.json';
+ this.post(url, params, null, callback);
+ return this;
+}
+
+// User resources
+
+// Trends resources
+
+// Local Trends resources
+
+// List resources
+
+// List Members resources
+
+// List Subscribers resources
+
+// Direct Messages resources
+
+// Friendship resources
+
+// Friends and Followers resources
+
+// Account resources
+
+Twitter.prototype.verifyCredentials = function(callback) {
+ var url = '/account/verify_credentials.json';
+ this.get(url, null, callback);
+ return this;
+}
+
+// Notification resources
+
+// Block resources
+
+// Spam Reporting resources
+
+// Saved Searches resources
+
+// OAuth resources
+
+// Geo resources
+
+// Legal resources
+
+// Help resources
+
+// Streamed Tweets resources
+
+// Search resources
View
92 lib/node-twitter/math.js
@@ -1,92 +0,0 @@
-/*!
-Math.uuid.js (v1.4)
-http://www.broofa.com
-mailto:robert@broofa.com
-
-Copyright (c) 2010 Robert Kieffer
-Dual licensed under the MIT and GPL licenses.
-*/
-
-/*
- * Generate a random uuid.
- *
- * USAGE: Math.uuid(length, radix)
- * length - the desired number of characters
- * radix - the number of allowable values for each character.
- *
- * EXAMPLES:
- * // No arguments - returns RFC4122, version 4 ID
- * >>> Math.uuid()
- * "92329D39-6F5C-4520-ABFC-AAB64544E172"
- *
- * // One argument - returns ID of the specified length
- * >>> Math.uuid(15) // 15 character ID (default base=62)
- * "VcydxgltxrVZSTV"
- *
- * // Two arguments - returns ID of the specified length, and radix. (Radix must be <= 62)
- * >>> Math.uuid(8, 2) // 8 character ID (base=2)
- * "01001010"
- * >>> Math.uuid(8, 10) // 8 character ID (base=10)
- * "47473046"
- * >>> Math.uuid(8, 16) // 8 character ID (base=16)
- * "098F4D35"
- */
-(function() {
- // Private array of chars to use
- var CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
-
- Math.uuid = function (len, radix) {
- var chars = CHARS, uuid = [];
- radix = radix || chars.length;
-
- if (len) {
- // Compact form
- for (var i = 0; i < len; i++) uuid[i] = chars[0 | Math.random()*radix];
- } else {
- // rfc4122, version 4 form
- var r;
-
- // rfc4122 requires these characters
- uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
- uuid[14] = '4';
-
- // Fill in random data. At i==19 set the high bits of clock sequence as
- // per rfc4122, sec. 4.1.5
- for (var i = 0; i < 36; i++) {
- if (!uuid[i]) {
- r = 0 | Math.random()*16;
- uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
- }
- }
- }
-
- return uuid.join('');
- };
-
- // A more performant, but slightly bulkier, RFC4122v4 solution. We boost performance
- // by minimizing calls to random()
- Math.uuidFast = function() {
- var chars = CHARS, uuid = new Array(36), rnd=0, r;
- for (var i = 0; i < 36; i++) {
- if (i==8 || i==13 || i==18 || i==23) {
- uuid[i] = '-';
- } else if (i==14) {
- uuid[i] = '4';
- } else {
- if (rnd <= 0x02) rnd = 0x2000000 + (Math.random()*0x1000000)|0;
- r = rnd & 0xf;
- rnd = rnd >> 4;
- uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
- }
- }
- return uuid.join('');
- };
-
- // A more compact, but less performant, RFC4122v4 solution:
- Math.uuidCompact = function() {
- return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
- var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
- return v.toString(16);
- }).toUpperCase();
- };
-})();
View
324 lib/node-twitter/oauth.js
@@ -1,324 +0,0 @@
-require('./math');
-
-var http = require('http'),
- qs = querystring = require('querystring'),
- url = require('url'),
- crypto = require('crypto')
- sys = require('sys');
-
-var oauth = exports;
-
-var percentEncodeURI = function(str) {
- return encodeURIComponent(str).replace(/%/g,'%25');
-};
-
-var encodeURI = function(str) {
- // https://developer.mozilla.org/En/Core_javascript_1.5_reference:global_functions:encodeuricomponent
- return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28').
- replace(/\)/g, '%29').replace(/\*/g, '%2A');
-};
-
-var sortKeys = function(obj, fn) {
- if (!obj) return obj;
- var vals = Object.keys(obj).sort(fn)
- var sorted = {}
- for(var i = 0; i < vals.length; i++)
- sorted[vals[i]] = obj[vals[i]]
- obj = sorted
- return obj
-};
-
-Object.merge = function(a,b) {
- if (!b || !(b instanceof Object)) return a;
- var keys = Object.keys(b)
- for (var i = 0, len = keys.length; i < len; ++i)
- a[keys[i]] = b[keys[i]]
- return a;
-};
-
-exports.version = '1.0';
-
-exports.normalize = function(obj) {
- if (!(obj instanceof Object)) return obj;
- obj = sortKeys(obj);
- for(var prop in obj) {
- if (typeof(obj[prop]) === 'function') {
- delete obj[prop];
- continue;
- }
- if (obj[prop].constructor === Array) {
- obj[prop] = obj[prop].sort();
- for(var child in obj[prop])
- prop[child] = oauth.normalize(child);
- }
- else if (obj[prop] instanceof Object)
- obj[prop] = oauth.normalize(obj[prop]);
- }
- return obj;
-};
-
-exports.fillURL = function(path, host, port, secure) {
- var p = url.parse(path);
- p.protocol = p.protocol || (secure ? 'https:' : 'http:');
- p.hostname = p.hostname || host;
- p.port = p.port || port;
- return url.format(p);
-};
-
-exports.normalizeURL = function(path) {
- var p = url.parse(path.replace(/^https/i, 'https').replace(/^http/i, 'http'));
- var ret = {
- protocol: p.protocol,
- hostname: p.hostname,
- pathname: p.pathname,
- port: p.port,
- slashes: true,
- };
-
- if ((p.port == 80 && p.protocol == 'http:') || (p.port == 443 && p.protocol == 'https:'))
- delete ret.port;
-
- ret.protocol = ret.protocol.toLowerCase();
- ret.hostname = ret.hostname.toLowerCase();
-
- if (!ret.pathname && !ret.path)
- ret.pathname = ret.path = '/';
- return url.format(ret);
-}
-
-exports.createClient = function(port, host, secure, creds) {
- var c = http.createClient(port, host, secure, creds);
- c.secure = secure;
- c.defaultHeaders = {
- 'Content-Type': 'application/x-www-form-urlencoded',
- 'Host': host,
- 'User-Agent': 'node-oauth',
- 'Accept': '*/*',
- 'WWW-Authenticate': 'OAuth realm=' + (this.https ? 'https' : 'http') + '://' + host,
- }
-
- c.request = function(method, path, headers, body, signature) {
- if (!headers)
- headers = c.defaultHeaders;
- else
- Object.merge(c.defaultHeaders,headers);
-
- if (typeof(path) != "string") {
- headers = url;
- path = method;
- method = "GET";
- }
-
- var req = new Request(this, method, path, headers, body, signature);
- c._outgoing.push(req);
- if (this.readyState === 'closed') this._reconnect();
-
- return req;
- }
-
- return c;
-};
-
-function Request(socket, method, path, headers, body, signature) {
- var uri = oauth.fillURL(path, socket.host, socket.port, socket.secure);
- var signed_header = this.signRequest(method, uri, headers, body, signature);
- http.ClientRequest.call(this, socket, method, path, signed_header);
-}
-sys.inherits(Request,http.ClientRequest);
-
-exports.Request = Request;
-
-Request.prototype.signRequest = function(method, path, headers, body, signature) {
-
- if (body)
- this.hasBody = true;
-
- var auth = {
- 'oauth_nonce':this.nonce(),
- 'oauth_timestamp':this.timestamp(),
- 'oauth_signature_method':signature.name,
- 'oauth_version':oauth.version
- };
-
- //split out any oauth_* params in the querystring and merge them into
- // the authorization header
- var parsed = url.parse(path);
-
- // if any parameters are passed with the path we need them
- if(parsed.query)
- var params = querystring.parse(parsed.query);
-
- var removed = this.splitParams(parsed.query);
-
- var t = signature.token;
- var c = signature.consumer;
-
- Object.merge(auth,removed ? removed : null);
- Object.merge(auth,t ? t.encode() : null);
- Object.merge(auth,c ? c.encode() : null);
-
- var base64;
- var joined = {};
-
- Object.merge(joined,body ? body : null);
- Object.merge(joined,auth);
- Object.merge(joined,params);
-
- if (signature instanceof HMAC)
- base64 = signature.sign(method, path, joined);
- else if (signature instanceof Signature)
- base64 = signature.sign();
- else
- throw new TypeError("Invalid signature type");
-
- querystring.escape = encodeURI;
- auth = querystring.stringify(oauth.normalize(auth),'\",','=\"');
-
- headers['Authorization'] = "OAuth " + auth + '\",oauth_signature=\"' + encodeURI(base64) + '\"';
- return headers;
-}
-
-Request.prototype.nonce = function() {
- return Math.uuidFast();
-};
-
-Request.prototype.normalizeBody = function(chunk) {
- qs.escape = function(str) { return encodeURIComponent(str).replace('%20','+') }
- return qs.stringify(chunk);
-};
-
-Request.prototype.splitParams = function(obj) {
- var removed = null;
- for (var prop in obj)
- if (/^oauth_\w+$/.test(prop)) {
- if(!removed) removed = {};
- removed[prop] = obj[prop];
- delete obj[prop];
- }
- return removed;
-};
-
-Request.prototype.timestamp = function() {
- return parseInt(new Date().getTime()/1000);
-};
-
-Request.prototype.write = function(chunk,normalize) {
- if (normalize || this.hasBody)
- chunk = this.normalizeBody(chunk);
- return http.ClientRequest.prototype.write.call(this,chunk,'utf8');
-};
-
-function Consumer() {};
-
-exports.Consumer = Consumer;
-
-exports.createConsumer = function(key,secret) {
- var c = new Consumer();
- c.oauth_consumer_key = key;
- c.oauth_consumer_secret = secret;
- return c;
-};
-
-Consumer.prototype.decode = function(str) {
- var parsed = qs.parse(str);
- for (var prop in parsed)
- this[prop] = parsed[prop];
-};
-
-Consumer.prototype.encode = function() {
- return {
- oauth_consumer_key:this.oauth_consumer_key
- }
-};
-
-function Token() {};
-
-exports.Token = Token;
-
-exports.createToken = function(key,secret) {
- var t = new Token();
- t.oauth_token = key || '';
- t.oauth_token_secret = secret || '';
- return t;
-};
-
-Token.prototype.decode = function(str) {
- var parsed = qs.parse(str);
- for (var prop in parsed)
- this[prop] = parsed[prop];
-};
-
-Token.prototype.encode = function(str) {
- var ret = {
- oauth_token:this.oauth_token
- }
-
- if (this.oauth_verifier)
- Object.merge(ret,{oauth_verifier:this.oauth_verifier});
-
- return ret;
-}
-
-function Signature() {};
-
-exports.Signature = Signature;
-
-exports.createSignature = function(consumer,token){
- var p = new Signature();
- p.consumer = consumer;
- p.token = token;
- p.name = 'PLAINTEXT';
- return p;
-};
-
-Signature.prototype.key = function () {
- return [
- encodeURI(this.consumer ? this.consumer.oauth_consumer_secret : ''),
- encodeURI( this.token ? this.token.oauth_token_secret : '')
- ].join('&');
-};
-
-Signature.prototype.sign = function() {
- return key;
-};
-
-Signature.prototype.baseString = function(method, url, params) {
- querystring.escape = encodeURI;
- return [
- method.toUpperCase(),
- encodeURI(oauth.normalizeURL(url)),
- encodeURI(querystring.stringify(oauth.normalize(params))),
- ].join('&');
-};
-
-function HMAC() {};
-
-sys.inherits(HMAC,Signature);
-
-exports.HMAC = HMAC;
-
-exports.createHmac = function(consumer,token){
- var h = new HMAC(consumer,token);
- h.algo = 'sha1';
- h.encoding = 'base64';
- h.name = 'HMAC-SHA1';
- h.consumer = consumer;
- h.token = token;
- return h;
-};
-
-HMAC.prototype.base = function(method,path,params) {
- return this.baseString(method, path, params);
-};
-
-HMAC.prototype.sign = function(method,path,params) {
- if (!this.consumer && !this.token)
- throw new Error('Must provide a valid consumer or token');
-
- var base = this.baseString(method,path,params);
- var hmac = crypto.createHmac(this.algo,this.key());
-
- hmac.update(base);
-
- return hmac.digest(this.encoding);
-};
View
25 package.json
@@ -1,12 +1,15 @@
-{
- "name": "twitter-node",
- "description": "node.js stream API for the twitter streaming HTTP API",
- "version": "0.0.2",
- "author": "technoweenie",
- "repository": {
- "type": "git",
- "url": "http://github.com/technoweenie/twitter-node.git"
- },
- "engine": [ "node >=0.2.0" ],
- "main": "./lib/twitter-node"
+{ "name": "node-twitter"
+, "description": "Asynchronous Twitter REST/stream client API for node.js."
+, "version": "0.1"
+, "author": "jdub"
+, "licenses":
+ [ { "type": "MIT"
+ , "url": http://github.com/jdub/node-twitter/raw/master/LICENSE"
+ } ]
+, "reponsitory":
+ { "type": "git"
+ , "url": "http://github.com/jdub/node-twitter.git"
+ }
+, "engine": [ "node >= 0.2" ]
+, "main": "./index"
}
View
153 test/twitter_node_config_test.js
@@ -1,153 +0,0 @@
-var TwitterNode = require('../lib/twitter-node').TwitterNode,
- assert = require('assert'),
- sys = require('sys');
-
-process.mixin(GLOBAL, require('ntest'));
-
-describe("streaming json parser")
- it("accepts JSON in chunks", function() {
- var parser = require("../lib/twitter-node/parser"),
- p = new parser.instance(),
- result
-
- p.addListener('object', function(tweet) {
- result = tweet
- })
-
- p.receive("")
- p.receive(" ")
- p.receive("{")
- p.receive('"a":{')
- p.receive('"b":1')
- p.receive("}\n}\n{\"a\":1}")
-
- assert.ok(result)
- assert.equal(1, result.a.b)
- })
-
-describe("json TwitterNode instance")
- before(function() { this.twit = new TwitterNode(); })
-
- it("emits tweet with parsed JSON tweet", function() {
- var result;
- this.twit
- .addListener('tweet', function(tweet) {
- result = tweet
- })
- .addListener('limit', function(tweet) {
- result = {a:null}
- })
- .addListener('delete', function(tweet) {
- result = {a:null}
- })
- .receive('{"a":1}')
-
- assert.equal(1, result.a)
- })
-
- it("emits delete with parsed JSON delete command", function() {
- var result;
- this.twit
- .addListener('tweet', function(tweet) {
- result = {status:null}
- })
- .addListener('limit', function(tweet) {
- result = {status:null}
- })
- .addListener('delete', function(tweet) {
- result = tweet
- })
- .receive('{"delete":{"status":{"id": 1234}}}')
-
- assert.equal(1234, result.status.id)
- })
-
- it("emits limit with parsed JSON limit command", function() {
- var result;
- this.twit
- .addListener('tweet', function(tweet) {
- result = {track:null}
- })
- .addListener('delete', function(tweet) {
- result = {track:null}
- })
- .addListener('limit', function(tweet) {
- result = tweet
- })
- .receive('{"limit":{"track": 1234}}')
-
- assert.equal(1234, result.track)
- })
-
-describe("default TwitterNode instance")
- before(function() { this.twit = new TwitterNode(); })
-
- it("has default requestUrl()", function() {
- assert.equal("/1/statuses/filter.json", this.twit.requestUrl())
- })
-
- it("has empty params", function() {
- assert.equal('', this.twit.buildParams())
- })
-
- it("has default port", function() {
- assert.equal(80, this.twit.port)
- })
-
- it("has default host", function() {
- assert.equal('stream.twitter.com', this.twit.host)
- })
-
- it("adds tracking keywords", function() {
- this.twit.track('abc+def')
- this.twit.track('ghi')
- assert.equal('?track=abc%2Bdef%2Cghi', this.twit.buildParams())
- })
-
- it("adds following users", function() {
- this.twit.follow(123)
- this.twit.follow(456)
- assert.equal('?follow=123%2C456', this.twit.buildParams())
- })
-
- it("adds locations", function() {
- this.twit.location(122.75, 36.8, -121.75, 37.8) // SF
- this.twit.location(-74, 40, -73, 41) // NYC
- assert.equal('?locations=122.75%2C36.8%2C-121.75%2C37.8%2C-74%2C40%2C-73%2C41', this.twit.buildParams())
- })
-
-describe("custom TwitterNode instance")
- before(function() {
- this.options = {
- port: 81,
- host: '10.0.0.1',
- path: 'abc/',
- action: 'retweet',
- follow: [123,456],
- track: ['abc', 'def'],
- headers: {'a': 'abc'},
- params: {count: 5}
- }
- this.twit = new TwitterNode(this.options);
- })
-
- it("has default requestUrl()", function() {
- assert.equal("abc/retweet.json" + this.twit.buildParams(), this.twit.requestUrl())
- })
-
- it("merges given headers with defaults", function() {
- assert.equal('abc', this.twit.headers.a)
- assert.ok(this.twit.headers['User-Agent'])
- })
-
- it("has empty params", function() {
- assert.equal('?count=5&track=abc%2Cdef&follow=123%2C456', this.twit.buildParams())
- })
-
- it("sets port", function() {
- assert.equal(this.options.port, this.twit.port)
- })
-
- it("sets host", function() {
- assert.equal(this.options.host, this.twit.host)
- })
Please sign in to comment.
Something went wrong with that request. Please try again.