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