Skip to content

Commit

Permalink
handle chunked json
Browse files Browse the repository at this point in the history
  • Loading branch information
technoweenie committed Jan 16, 2010
1 parent 4033c6b commit 606bec1
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 31 deletions.
20 changes: 20 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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
"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.
72 changes: 72 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# twitter-node

Creates a streaming connection with twitter, and pushes any incoming statuses to a tweet event.

## Installation

Depends on ntest.

There's no packaging system for node.js yet, so I've just been creating symlinks
in my `~/.node_libraries` path.

$ ln -s /path/to/twitter-node/lib ~/.node_libraries/twitter-node

## Events

TwitterNode emits these events:

* tweet(json) - This is emitted when a new tweet comes in. If the format is set to 'json' (default), then this is 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.
* close(response) - This is emitted when the http connection is closed. The HTTP response object is sent.

See the [streaming API docs][api-docs] for examples of the limit and delete commands.

[api-docs]: http://apiwiki.twitter.com/Streaming-API-Documentation

## Usage

var TwitterNode = require('twitter-node')
// you can pass args to create() or set them on the TwitterNode instance
var twit = TwitterNode.create({
user: 'username',
password: 'password',
track: ['baseball', 'football'],
follow: [12345, 67890]})

// adds to the track array set above
twit.track('foosball')

// adds to the following array set above
twit.follow(2345)

twit.params['count'] = 100
twit.headers['User-Agent'] = 'whatever'
twit.action = 'sample' // 'filter' is default

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('close', function(resp) {
sys.puts("wave goodbye... " + resp.statusCode)
})

.stream()

## TODO

* XML Parsing?

## Copyright

Copyright (c) 2010 rick. See LICENSE for details.
67 changes: 36 additions & 31 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,13 @@ var sys = require('sys'),
http = require('http'),
b64 = require('./base64'),
query = require('querystring')

exports.create = function(options) {
return new TwitterNode(options);
}

// Creates a streaming connection with twitter, and pushes any incoming
// statuses to a tweet event.
//
// var twit = new TwitterNode({user: 'username', password: 'password'});
// twit.action = 'sample' // filter is default
// twit.format = 'xml' // json is default
// twit.follow(123) // needs userIDs, not screen names
// twit.track('#nowplaying')
// twit.addListener('tweet', function(tweet) {
// // tweet is a parsed JSON object or an xml string
// // xml parsing contributions welcome
// })
//
// see http://apiwiki.twitter.com/Streaming-API-Documentation for
// twitter delete messages
//
// twit.addListener('delete', function(delete) {
// })
// twit.addListener('limit', function(delete) {
// })
// twit.addListener('close', function() {
// sys.puts("wave goodbye")
// })
// twit.stream();
var TwitterNode = function(options) {
if(!options) options = {}
this.port = options.port || 80
Expand All @@ -42,7 +21,9 @@ var TwitterNode = function(options) {
this.params = options.params || {}
this.user = options.user
this.password = options.password
this.headers = {"User-Agent": 'Twitter-Node: node.js streaming client'}
this.headers = {"User-Agent": 'Twitter-Node: node.js streaming client'}
if(options.headers)
process.mixin(this.headers, options.headers)

this.track = function(word) {
this.trackKeywords.push(word);
Expand All @@ -66,21 +47,45 @@ var TwitterNode = function(options) {
node.processTweet(chunk)
});
resp.addListener('complete', function() {
node.emit('close')
node.emit('close', this)
});
});
return this;
}

this.startJSON = /^\s*\{/
this.endJSON = /\}\s*$/
this.chunks = ""
this.debug = false
this.processTweet = function(text) {
if (this.format == 'json') {
var tweet = JSON.parse(text)
if(tweet.limit) {
this.emit('limit', tweet.limit)
} else if(tweet['delete']) {
this.emit('delete', tweet['delete'])
} else {
this.emit('tweet', tweet)
this.chunks += text

// quick check for valid JSON
if(!this.chunks.match(this.startJSON) || !this.chunks.match(this.endJSON))
return

text = this.chunks

try {
var tweet = JSON.parse(text)
this.chunks = ''
if(tweet.limit) {
this.emit('limit', tweet.limit)
} else if(tweet['delete']) {
this.emit('delete', tweet['delete'])
} else {
this.emit('tweet', tweet)
}
} catch(err) {
// sometimes what looks like valid json from the regexes isn't:
// {"a": {"b": 1}
// starting and ending brackets, but still invalid.
// hopefully the next chunk will finish the json.
if(this.debug || err.name != 'SyntaxError') {
sys.puts(text)
sys.puts(err.stack)
}
}
} else {
this.emit('tweet', text)
Expand Down
22 changes: 22 additions & 0 deletions test/twitter_node_config_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,28 @@ process.mixin(GLOBAL, require('ntest'));
describe("json TwitterNode instance")
before(function() { this.twit = twitterNode.create(); })

it("accepts JSON in chunks", function() {
var promise = new process.Promise();
var result;

this.twit.addListener('tweet', function(tweet) {
result = tweet
promise.emitSuccess()
})

this.twit.processTweet("")
this.twit.processTweet(" ")
this.twit.processTweet("{")
this.twit.processTweet('"a":{')
this.twit.processTweet('"b":1}')
this.twit.processTweet("}")

if(!promise.hasFired && promise._blocking) promise.wait()

assert.ok(result)
assert.equal(1, result.a.b)
})

it("emits tweet with parsed JSON tweet", function() {
var promise = new process.Promise();
var result;
Expand Down

0 comments on commit 606bec1

Please sign in to comment.