Repository: technoweenie/twitter-node
Branch: master
Commit: 39ab640327c5
Files: 7
Total size: 17.6 KB
Directory structure:
gitextract_bwrkckrb/
├── .gitignore
├── LICENSE
├── README.md
├── lib/
│ └── twitter-node/
│ ├── index.js
│ └── parser.js
├── package.json
└── test/
└── twitter_node_config_test.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*build/
*.lock-wscript
*.node
.DS_Store
================================================
FILE: LICENSE
================================================
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.
================================================
FILE: README.md
================================================
# twitter-node
Creates a streaming connection with twitter, and pushes any incoming statuses to a tweet event.
## Installation
Depends on ntest.
Use NPM:
npm install twitter-node
Otherwise create a symlink in `~/.node_libraries`
$ ln -s /path/to/twitter-node/lib/twitter-node ~/.node_libraries/twitter-node
## Events
TwitterNode emits these events:
* 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.
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
// twitter-node does not modify GLOBAL, that's so rude
var TwitterNode = require('twitter-node').TwitterNode
, util = require('util')
// you can pass args to create() or set them on the TwitterNode instance
var twit = new TwitterNode({
user: 'username',
password: 'password',
host: 'my_proxy.my_company.com', // proxy server name or ip addr
port: 8080, // proxy port!
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) {
util.puts("@" + tweet.user.screen_name + ": " + tweet.text);
})
.addListener('limit', function(limit) {
util.puts("LIMIT: " + util.inspect(limit));
})
.addListener('delete', function(del) {
util.puts("DELETE: " + util.inspect(del));
})
.addListener('end', function(resp) {
util.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.
================================================
FILE: lib/twitter-node/index.js
================================================
var https = require('https'),
query = require('querystring'),
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 proxy port
// host - String or ip address of the proxy server. 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 || 443;
this.host = options.host || 'stream.twitter.com';
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));
});
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 headers = extend({}, this.headers),
twit = this,
request;
headers['Host'] = this.host;
if (this.user) {
headers['Authorization'] = basicAuth(this.user, this.password);
}
postdata = this.buildParams();
headers['Content-Type'] = 'application/x-www-form-urlencoded';
headers['Content-Length'] = postdata.length;
var requestOptions = {
host: this.host,
port: this.port,
path: this.requestUrl(),
method: 'POST',
headers: headers
};
request = https.request(requestOptions, function(response) {
twit._clientResponse = response;
response.on('close', function(err){
// If underlaying connection was terminated reconnect to the stream
twit.stream();
});
response.on('data', function(chunk){
twit._receive(chunk);
});
response.on('end', function() {
twit.emit('end', this);
});
response.on('close', function() {
twit.emit('close', this);
});
});
request.write(postdata);
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;
};
// Builds the URL for the streaming request.
//
// Returns a String absolute URL.
TwitterNode.prototype.requestUrl = function() {
return this.path + this.action + ".json";
};
// 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.limit);
} else if (tweet['delete']) {
twit.emit('delete', tweet['delete']);
} else {
twit.emit('tweet', tweet);
}
};
};
================================================
FILE: lib/twitter-node/parser.js
================================================
// glorious streaming json parser, built specifically for the twitter streaming api
// assumptions:
// 1) ninjas are mammals
// 2) tweets come in chunks of text, surrounded by {}'s, separated by line breaks
// 3) only one tweet per chunk
//
// p = new parser.instance()
// p.addListener('object', function...)
// p.receive(data)
// p.receive(data)
// ...
var EventEmitter = require('events').EventEmitter;
var Parser = module.exports = function Parser() {
// Make sure we call our parents constructor
EventEmitter.call(this);
this.buffer = '';
return this;
};
// The parser emits events!
Parser.prototype = Object.create(EventEmitter.prototype);
Parser.END = '\r\n';
Parser.END_LENGTH = 2;
Parser.prototype.receive = function receive(buffer) {
this.buffer += buffer.toString('utf8');
var index, json;
// We have END?
while ((index = this.buffer.indexOf(Parser.END)) > -1) {
json = this.buffer.slice(0, index);
this.buffer = this.buffer.slice(index + Parser.END_LENGTH);
if (json.length > 0) {
try {
json = JSON.parse(json);
this.emit('object', json);
} catch (error) {
this.emit('error', error);
}
}
}
};
================================================
FILE: package.json
================================================
{
"name": "twitter-node",
"description": "node.js stream API for the twitter streaming HTTP API",
"version": "0.1.1",
"author": {"name": "technoweenie" },
"contributors": [
{ "name": "tim-smart" },
{ "name": "Chris Verwymeren", "email": "verwymeren@gmail.com", "web": "http://www.github.com/cvee" },
{ "name": "Makis Tracend", "email": "makis.tracend@gmail.com", "web": "http://www.github.com/tracend" },
{ "name": "Dustin Dobervich", "email": "ddobervich@gmail.com", "web": "http://www.github.com/dustin10" },
{ "name": "Chris M. Welsh", "email": "chris@cmwelsh.com", "web": "http://www.github.com/cmwelsh" },
{ "name": "Michael Nutt", "email": "michael@nuttnet.net", "web": "http://www.github.com/mnutt" }
],
"repository": {
"type": "git",
"url": "http://github.com/istrategylabs/twitter-node.git"
},
"engine": [ "node >=0.2.0" ],
"main": "./lib/twitter-node"
}
================================================
FILE: test/twitter_node_config_test.js
================================================
var TwitterNode = require('../lib/twitter-node').TwitterNode,
assert = require('assert'),
util = require('util');
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)
})
gitextract_bwrkckrb/
├── .gitignore
├── LICENSE
├── README.md
├── lib/
│ └── twitter-node/
│ ├── index.js
│ └── parser.js
├── package.json
└── test/
└── twitter_node_config_test.js
SYMBOL INDEX (1 symbols across 1 files)
FILE: lib/twitter-node/index.js
function extend (line 8) | function extend(a, b) {
Condensed preview — 7 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (19K chars).
[
{
"path": ".gitignore",
"chars": 40,
"preview": "*build/\n*.lock-wscript\n*.node\n.DS_Store\n"
},
{
"path": "LICENSE",
"chars": 1048,
"preview": "Copyright (c) 2010 rick\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software a"
},
{
"path": "README.md",
"chars": 4182,
"preview": "# twitter-node\n\nCreates a streaming connection with twitter, and pushes any incoming statuses to a tweet event.\n\n## Inst"
},
{
"path": "lib/twitter-node/index.js",
"chars": 6577,
"preview": "var https = require('https'),\n query = require('querystring'),\n Parser = require('./parser'),\n"
},
{
"path": "lib/twitter-node/parser.js",
"chars": 1210,
"preview": "// glorious streaming json parser, built specifically for the twitter streaming api\n// assumptions:\n// 1) ninjas are m"
},
{
"path": "package.json",
"chars": 904,
"preview": "{\n \"name\": \"twitter-node\",\n \"description\": \"node.js stream API for the twitter streaming HTTP API\",\n \"version\": \"0.1."
},
{
"path": "test/twitter_node_config_test.js",
"chars": 4042,
"preview": "var TwitterNode = require('../lib/twitter-node').TwitterNode,\n assert = require('assert'),\n util = re"
}
]
About this extraction
This page contains the full source code of the technoweenie/twitter-node GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 7 files (17.6 KB), approximately 4.6k tokens, and a symbol index with 1 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.