-
Notifications
You must be signed in to change notification settings - Fork 0
/
server.js
336 lines (288 loc) · 11.5 KB
/
server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
var express = require('express');
var app = express();
var async = require('async');;
var path = require('path');
var bodyParser = require('body-parser');
var multer = require('multer');
var upload = multer();
var Twit = require('twit');
//var config = require('./config'); //use this when testing on local branches with other code commented out below
let fs = require('fs');
const OpenWeatherMapHelper = require('openweathermap-node');
const helperFunctions = require('./helpers');
var afinnStr = fs.readFileSync('AFINN-111.txt', 'utf8');
let afinnArr = helperFunctions.parse_String(afinnStr);
var Sentiment = require('sentiment');
var sentiment = new Sentiment();
//set up Twitter API connection
//use when testing local branch
// var T = new Twit(config.twitter); //now pulling data from config.js and gitignored
//use when deployed on heroku
var T = new Twit({
consumer_key: process.env.T_CKEY,
consumer_secret: process.env.T_CSECRET,
access_token: process.env.T_ATOKEN,
access_token_secret: process.env.T_TOKENS,
timeout_ms: 60*1000,
strictSSL: true
});
//set up OpenWeatherMap API
//use when testing local branch
// const helper = new OpenWeatherMapHelper(config.weather);
//use when deployed on heroku
const helper = new OpenWeatherMapHelper({
APPID: process.env.WEATHER_APPID,
units: "imperial"
});
//set up Plotly API
// var plotly = require('plotly')(config.plot.un, config.plot.key); //use when testing branch locally
var plotly = require('plotly')(process.env.PLOT_UN, process.env.PLOT_K);
const Query = require('./query.js');
//configure EJS templating
app.set('view engine', 'ejs');
//tell app what dir we want for views
app.set('views', path.join(__dirname, 'views'));
// for parsing application/json
app.use(bodyParser.json());
// for parsing application/xwww-form-urlencoded
app.use(bodyParser.urlencoded({ extended: true }));
// for parsing multipart/form-data
app.use(upload.array());
// Set static path
app.use(express.static(path.join(__dirname, 'public')));
//route for homepage
app.get('/', function(req, res) {
res.render('index.ejs');
});
//route for query page
app.get('/query', function(req, res) {
res.render('query.ejs');
});
app.post('/gettweets', function(req, res){
var temp_query = new Query(req.body.search_word, req.body.sample_size, req.body.start_date, req.body.end_date);
masterObject = {}; // object will contain: list of raw twitter tweet objects (.data),
//list of tweet statuses as strings(.statusStrings)
masterObject.statusStrings = [];
var weatherCounter = 0;
//Begin async Block
//Until the tweets in tweetStatusList match the requested sample size or greater, don't render the page
async.until(function(){
if(masterObject.statusStrings.length >= temp_query.sample_size){
scores = scoreTweets(masterObject.statusStrings);
renderPage(masterObject.statusStrings, res, scores, '/views/tweets.ejs');
console.log('locations grabbed: ' + weatherCounter);
}
return masterObject.statusStrings.length >= temp_query.sample_size;
}, function processQueryHelper(cb){
var _masterObject = {};
var tweetStatusList = [];
var params = {
q: temp_query.search_word,
count: temp_query.sample_size
}
T.get('search/tweets', params, function(err, data, response){
var raw_tweets = data.statuses;
masterObject.data = data;
//console.log('data: ' + JSON.stringify(data));
for (var i=0; i<raw_tweets.length; i++){
//check if tweet is written in English
if (String(raw_tweets[i].lang) == 'en') {
//masterObject.statusStrings.push(String(raw_tweets[i].text));
//experimenting
// console.log('master: ' + masterObject.data.statuses.length);
// console.log('raw :' + raw_tweets.length);
if (raw_tweets[i].place!= null) {
console.log("There is a location");
weatherCounter += helperFunctions.getWeatherData(raw_tweets[i]); //TODO: Issues with this function see notes below
// Also, I think this is getting called a bunch of times
//on similar data and giving weatherCounter a
//much higher value than the actual number of
//tweets with location data...
masterObject.statusStrings.push(String(raw_tweets[i].text));
}
}
}
cb();
});
}); //end of processQueryHelper, end of .until function argument list
}); //end of app.post
app.post('/showResults', function(req, res){
var temp_query = new Query(req.body.search_word, req.body.sample_size, req.body.start_date, req.body.end_date);
masterObject = {}; // object will contain: list of raw twitter tweet objects (.data),
//list of tweet statuses as strings(.statusStrings)
masterObject.statusStrings = [];
masterObject.sequenceNums = []; //For plotly feasibility
var weatherCounter = 0;
//Begin async Block
//Until the tweets in tweetStatusList match the requested sample size or greater, don't render the page
async.until(function(){
if(masterObject.statusStrings.length >= temp_query.sample_size){
//once if statement is satisifed (tweets are grabbed)
//get afinn111 scores
scores = scoreTweets(masterObject.statusStrings, afinnArr);
//more NLP to improve scores
//calculate score average
var scoreAverage = (scores.reduce((a, b) => a + b, 0)) / scores.length;
var numNegTweets = scores.reduce(function(acc, x) {
return x < 0 ? acc + 1 : acc;
}, 0);
var numPosTweets = scores.reduce(function(acc, x) {
return x > 0 ? acc + 1 : acc;
}, 0);
var numNeutralTweets = scores.reduce(function(acc, x) {
return x == 0 ? acc + 1 : acc;
}, 0);
var oneWordSentiment = getOneWordSentiment(scoreAverage);
graph_results(scores, masterObject.sequenceNums, numPosTweets, numNegTweets, numNeutralTweets);
extreme_tweets = getMostEmotionalTweets(masterObject.statusStrings, scores)
extreme_words = get_emotional_words(masterObject.statusStrings, afinnArr);
if(extreme_words > 20){
extreme_words = extreme_words.slice(0, 100);
}
res.render(path.join(__dirname + '/views/results.ejs'), {tweets: masterObject.statusStrings, scores: scores, avg: scoreAverage, numNegTweets: numNegTweets, numPosTweets: numPosTweets, numNeutralTweets: numNeutralTweets, oneWordSentiment: oneWordSentiment, searchPhrase: temp_query.search_word, extreme_tweets: extreme_tweets, extreme_words: extreme_words});
// render results page
//res.render(path.join(__dirname + '/views/results.ejs'), {tweets: masterObject.statusStrings, scores: scores, avg: scoreAverage, numNegTweets: numNegTweets, numPosTweets: numPosTweets, numNeutralTweets: numNeutralTweets});
console.log('locations grabbed: ' + weatherCounter);
}
return masterObject.statusStrings.length >= temp_query.sample_size;
}, function processQueryHelper(cb){
var _masterObject = {};
var tweetStatusList = [];
var params = {
q: temp_query.search_word,
count: temp_query.sample_size
}
T.get('search/tweets', params, function(err, data, response){
var raw_tweets = []
raw_tweets = data.statuses;
masterObject.data = data;
//console.log('data: ' + JSON.stringify(data));
for (var i=0; i<raw_tweets.length; i++){
//check if tweet is written in English
if (String(raw_tweets[i].lang) == 'en') {
masterObject.statusStrings.push(String(raw_tweets[i].text));
if (masterObject.sequenceNums.length < 1){
masterObject.sequenceNums.push(0);
}else{
masterObject.sequenceNums.push(masterObject.sequenceNums[masterObject.sequenceNums.length-1]+1);
}
//experimenting
// console.log('master: ' + masterObject.data.statuses.length);
// console.log('raw :' + raw_tweets.length);
if (raw_tweets[i].place!= null) {
weatherCounter += helperFunctions.getWeatherData(raw_tweets[i]); //TODO: Issues with this function see notes below
// Also, I think this is getting called a bunch of times
//on similar data and giving weatherCounter a
//much higher value than the actual number of
//tweets with location data...
}
}
}
cb();
});
}); //end of processQueryHelper, end of .until function argument list
}); //end of app.post
function getOneWordSentiment(score) {
if (score <= -3) {
return "SUPER NEGATIVE";
}
else if (score <= -1) {
return "REALLY NEGATIVE";
}
else if (score < 0) {
return "NEGATIVE";
}
else if (score <= 1) {
return "POSTIVE";
}
else if (score <= 3) {
return "HAPPY";
}
else if (score <=5) {
return "SUPER HAPPY a.k.a. #winning";
}
else {
return "CALCULATION ERROR";
}
}
function getMostEmotionalTweets(tweets, scores){
var results = []; //0 = most positive 1 = most negative 2 = most neutral
var highest = scores[0];
var lowest = scores[0];
var neutral = scores[0];
for(var i=0; i<scores.length; i++){
//console.log("sannnity");
if(scores[i]>highest){
highest = scores[i];
results[0] = tweets[i];
}
if(scores[i]<lowest){
lowest = scores[i];
results[1] = tweets[i];
}
if(scores[i]==0){
neutral = scores[i];
results[2] = tweets[i];
}
}
return results;
}
function scoreTweets(tweets, afinnArr){
let scores = [];
for (var t in tweets) {
var result = sentiment.analyze(tweets[t]);
//console.log(result.words);
//console.log(result.score);
//var score = helperFunctions.getScore(tweets[t], afinnArr);
scores.push(result.score);
//console.log("Tweet: " + tweets[t] + " Score: " + score);
}
return scores;
}
function get_emotional_words(tweets, afinnArr){
var words = [];
for (var t in tweets){
var result = sentiment.analyze(tweets[t]);
var result = result.words
for(var r in result){
//&& afinnArr.includes(result[r])
//console.log(result[r])
if(!words.includes(result[r])){
//console.log("This word isn't in words:");
//console.log(result[r])
words.push(result[r]);
}
}
}
return words;
}
function graph_results(scores, sequence, numPosTweets, numNegTweets, numNeutralTweets){
//Create Graph and Send it to Plotly
var general_plot = {
x: sequence,
y: scores,
type: "scatter"
};
var data = [general_plot];
var graphOptions = {filename: "tweetplot", fileopt: "overwrite"};
plotly.plot(data, graphOptions, function(err, msg){
console.log(msg);
});
var pie_chart = [{
values: [numPosTweets, numNegTweets, numNeutralTweets],
labels:['Positive Tweets', 'Negative Tweets', 'Neutral Tweets'],
type: 'pie'
}];
var pie_data = [pie_chart];
var graphOptions2 = {filename: 'tweet_pie', fileopt: 'overwrite'};
plotly.plot(pie_data, graphOptions2, function(err, msg){
console.log(msg);
});
}
function renderPage(tweets, res, scores, pageName){
//console.log('TWEETS FROM RENDER FN: ' + tweets);
res.render(path.join(__dirname + pageName), {tweets: tweets, scores: scores});
}
app.listen((process.env.PORT || 5000), function(){
console.log('server started on Port 5000...'); ` `
})