|
1 READ_OLD_TWEETS = true; |
|
2 RECORD_NEW_TWEETS = true; |
|
3 |
|
4 tweet_file = flagOption('-f', 'tweets.txt'); |
|
5 app_port = flagOption('-p', 8000); |
|
6 tracking_keyword = flagOption('-t', null); |
|
7 user_pass = flagOption('-u', null); |
|
8 |
|
9 if (!user_pass) { |
|
10 console.log("You must provide user credentials on the command-line, e.g. node tweetcast.js -u user:pass") |
|
11 process.exit(); |
|
12 } |
|
13 if (!tracking_keyword) { |
|
14 console.log("You must provide keyword(s) on the command-line, e.g. node tweetcast.js -t Bieber") |
|
15 process.exit(); |
|
16 } |
|
17 |
|
18 console.log("Tracking keyword "+tracking_keyword); |
|
19 |
|
20 var fs = require('fs'), |
|
21 http = require('http'), |
|
22 https = require('https'), |
|
23 socketio = require('socket.io'), |
|
24 app = http.createServer(httpHandler), |
|
25 io = socketio.listen(app) |
|
26 tweets = [], |
|
27 tweet_ids = [], |
|
28 keys_to_delete = [ |
|
29 'in_reply_to_screen_name', |
|
30 'in_reply_to_user_id', |
|
31 'retweeted', |
|
32 'place', |
|
33 'geo', |
|
34 'source', |
|
35 'contributors', |
|
36 'coordinates', |
|
37 'retweet_count', |
|
38 'favorited', |
|
39 'truncated', |
|
40 'possibly_sensitive' |
|
41 ], |
|
42 user_keys_to_delete = [ |
|
43 'default_profile_image', |
|
44 'show_all_inline_media', |
|
45 'contributors_enabled', |
|
46 'profile_sidebar_fill_color', |
|
47 'created_at', |
|
48 'lang', |
|
49 'time_zone', |
|
50 'profile_sidebar_border_color', |
|
51 'follow_request_sent', |
|
52 'profile_background_image_url', |
|
53 'profile_background_image_url_https', |
|
54 'followers_count', |
|
55 'description', |
|
56 'url', |
|
57 'geo_enabled', |
|
58 'profile_use_background_image', |
|
59 'default_profile', |
|
60 'following', |
|
61 'profile_text_color', |
|
62 'is_translator', |
|
63 'favourites_count', |
|
64 'listed_count', |
|
65 'friends_count', |
|
66 'profile_link_color', |
|
67 'protected', |
|
68 'location', |
|
69 'notifications', |
|
70 'profile_image_url_https', |
|
71 'statuses_count', |
|
72 'verified', |
|
73 'profile_background_color', |
|
74 'profile_background_tile', |
|
75 'utc_offset' |
|
76 ], |
|
77 content_types = { |
|
78 css : "text/css; charset=utf-8", |
|
79 html : "text/html; charset=utf-8", |
|
80 js : "text/javascript; charset=utf-8", |
|
81 png : "image/png" |
|
82 }; |
|
83 |
|
84 function wrapContentType(ext) { |
|
85 return { |
|
86 "Content-Type" : ( content_types[ext] ? content_types[ext] : 'text/plain' ) |
|
87 } |
|
88 } |
|
89 |
|
90 function httpHandler(req, res) { |
|
91 console.log("HTTP Request for URL "+req.url); |
|
92 var url = req.url.split('?')[0]; |
|
93 url = __dirname + "/client" + url + ( url[url.length - 1] == "/" ? "index.html" : "" ); |
|
94 var ext = url.split('.').slice(-1)[0].toLowerCase(); |
|
95 fs.readFile( url, function(err, data) { |
|
96 if (err) { |
|
97 console.log("Error 404"); |
|
98 res.writeHead(404); |
|
99 return res.end('File not found'); |
|
100 } |
|
101 res.writeHead(200, wrapContentType(ext)); |
|
102 res.end(data); |
|
103 }); |
|
104 } |
|
105 |
|
106 function flagOption(flag, defaultValue) { |
|
107 var flagPos = process.argv.indexOf(flag); |
|
108 return ( flagPos != -1 && flagPos < process.argv.length - 1) ? process.argv[flagPos + 1] : defaultValue; |
|
109 } |
|
110 |
|
111 function addToList(tweet) { |
|
112 if (tweet_ids.indexOf(tweet.id) != -1) { |
|
113 console.log("Error: Tweet already in list"); |
|
114 return false; |
|
115 } |
|
116 tweets.push(tweet); |
|
117 tweet_ids.push(tweet.id); |
|
118 return true; |
|
119 } |
|
120 |
|
121 function textids(object) { |
|
122 for (var key in object) { |
|
123 // Workaround for Unicode bug in socket.io. |
|
124 |
|
125 if (typeof object[key] == "string") { |
|
126 var tmp = ''; |
|
127 for (var i = 0; i < object[key].length; i++) { |
|
128 tmp += ( object[key].charCodeAt(i) < 128 ? object[key].charAt(i) : "&#" + object[key].charCodeAt(i) + ";" ); |
|
129 } |
|
130 object[key] = tmp; |
|
131 } |
|
132 if (key.substr(-2) == 'id') { |
|
133 object[key] = object[key + '_str']; |
|
134 delete object[key + '_str']; |
|
135 } |
|
136 } |
|
137 } |
|
138 |
|
139 function readTweetsFromFile(file_name) { |
|
140 console.log("Trying to read tweets from " + file_name); |
|
141 try { |
|
142 var oldtweets = fs.readFileSync(file_name, 'utf8').split('\n'); |
|
143 var tweetscopied = 0; |
|
144 for (var i in oldtweets) { |
|
145 if (oldtweets[i].length > 0) { |
|
146 addToList(JSON.parse(oldtweets[i])); |
|
147 tweetscopied++; |
|
148 } |
|
149 } |
|
150 console.log(tweetscopied + " tweets copied"); |
|
151 } |
|
152 catch (err) { |
|
153 console.log("Error opening "+file_name); |
|
154 } |
|
155 } |
|
156 |
|
157 function requestTweets() { |
|
158 console.log("Fetching tweets from https://stream.twitter.com/1/statuses/filter.json") |
|
159 var writestream = null; |
|
160 var req = https.request({ |
|
161 host: "stream.twitter.com", |
|
162 path: "/1/statuses/filter.json", |
|
163 method: "POST", |
|
164 headers: { |
|
165 'Authorization': 'Basic ' + new Buffer( user_pass ).toString('base64'), |
|
166 'Content-Type': 'application/x-www-form-urlencoded' |
|
167 } |
|
168 }, function(res) { |
|
169 writestream = fs.createWriteStream( tweet_file, { flags: 'a+', encoding: 'utf-8' } ); |
|
170 console.log('Response received, status : ' + res.statusCode); |
|
171 res.setEncoding('utf8'); |
|
172 res.on('data', function(chunk) { |
|
173 var newdata = chunk.split('\r\n'), |
|
174 tweetpos = tweets.length; |
|
175 try { |
|
176 for (var i in newdata) { |
|
177 if (newdata[i].length > 0) { |
|
178 var tweet = JSON.parse(newdata[i]); |
|
179 textids(tweet); |
|
180 for (var j in keys_to_delete) { |
|
181 delete tweet[keys_to_delete[j]]; |
|
182 } |
|
183 textids(tweet.user); |
|
184 for (var j in user_keys_to_delete) { |
|
185 delete tweet.user[user_keys_to_delete[j]]; |
|
186 } |
|
187 if (tweet.retweeted_status) { |
|
188 textids(tweet.retweeted_status); |
|
189 for (var j in keys_to_delete) { |
|
190 delete tweet.retweeted_status[keys_to_delete[j]]; |
|
191 } |
|
192 } |
|
193 /* tweet.date_value = Date.parse(tweet.created_at); */ |
|
194 if (addToList(tweet)) { |
|
195 writestream.write(JSON.stringify(tweet)+'\n'); |
|
196 } |
|
197 } |
|
198 } |
|
199 io.sockets.emit('update', { |
|
200 "new_tweets" : tweets.slice(tweetpos) |
|
201 }); |
|
202 console.log("New tweets received. We now have", tweets.length, "tweets in memory"); |
|
203 } |
|
204 catch(err) { |
|
205 console.log(err.message); |
|
206 } |
|
207 }); |
|
208 }); |
|
209 |
|
210 req.write('track=' + encodeURIComponent( ( tracking_keyword ) ) ); |
|
211 req.end(); |
|
212 } |
|
213 |
|
214 app.listen(app_port); |
|
215 console.log("Listening on port: "+app_port); |
|
216 |
|
217 if (READ_OLD_TWEETS) { |
|
218 readTweetsFromFile(tweet_file); |
|
219 } else { |
|
220 console.log("Reading old tweets disabled !"); |
|
221 } |
|
222 |
|
223 if (RECORD_NEW_TWEETS) { |
|
224 requestTweets(); |
|
225 } else { |
|
226 console.log("Recording new tweets disabled !"); |
|
227 } |
|
228 |
|
229 io.set('log level', 0); |
|
230 io.sockets.on('connection', function(socket) { |
|
231 console.log("New connection from "+socket.handshake.address.address); |
|
232 socket.emit('initial_data', { |
|
233 "tweets" : tweets |
|
234 }); |
|
235 }); |