├── .gitattributes
├── .github
└── FUNDING.yml
├── .gitignore
├── LICENSE
├── README.md
├── index.html
├── js
├── canvasjs.min.js
└── sentiment-analysis.js
├── php
├── TwitterAPIExchange.php
└── queryTwitter.php
└── style
└── sentiment-analysis.css
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | ko_fi: bensonruan
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | php/cacert.pem
3 | php/phpInfo.php
4 | node_modules/*
5 | twitter-sentiment-analysis.gif
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Benson Ruan
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sentiment-Analysis
2 | Twitter Sentiment Analysis with Tensorflow.js
3 |
4 | Connect to Twitter API, gather tweets by hashtag, compute the sentiment of each tweet, and build a real-time dashboard to show the result.
5 |
6 | ## Live Demo
7 | **[https://bensonruan.com/twitter-sentiment-analysis-with-tensorflowjs](https://bensonruan.com/twitter-sentiment-analysis-with-tensorflowjs)**
8 |
9 | 
10 |
11 |
12 | ## Installing
13 | 1. Clone this repository to your local computer
14 | ``` bash
15 | git https://github.com/bensonruan/Sentiment-Analysis.git
16 | ```
17 |
18 | 2. On Twitter developer platform https://developer.twitter.com/
19 | * Register a Twitter dev account
20 | * Create a Twitter App
21 | * Get the Consumer API keys and Access tokens
22 | * Replace your API keys in queryTwitter.php
23 |
24 |
25 | 3. Config your path to the queryTwitter.php inside sentiment-analysis.js and sentiment-analysis-bundle.js
26 | ``` bash
27 | queryTwitter: window.location.protocol + '//'+ window.location.hostname + '/js/sentiment/queryTwitter.php?q='
28 | ```
29 |
30 | 4. Point your localhost to the cloned root directory. Browse to http://localhost/index.html
31 |
32 |
33 | ## Note
34 | If you are on Windows, you would need to install PHP via Web Platform Installer
35 |
36 | ## Library
37 | * [twitter-api-php](https://github.com/J7mbo/twitter-api-php) - PHP Wrapper for Twitter API v1.1 calls
38 | * [jquery](https://code.jquery.com/jquery-3.3.1.min.js) - JQuery
39 | * [tensorflow.js sentiment](https://github.com/tensorflow/tfjs-examples/tree/master/sentiment) - Perform text sentiment analysis on text using the Layers API of TensorFlow.js
40 | * [canvasjs](https://canvasjs.com/jquery-charts/) - JQuery chart library
41 |
42 | ## Support me
43 | [](https://ko-fi.com/W7W6METMY)
44 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Twitter Sentiment Analysis
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
Twitter Sentiment Analysis
20 |
21 |
22 |
23 |
24 | Type in any hashtag or keyword and press enter to visualize Tweet Sentiment.
25 |
26 |
27 |
28 |
29 | #
30 |
31 |
32 | Search
33 |
34 |
35 |
36 |
37 |
38 |
39 | Loading...
40 |
41 |
42 |
45 |
46 |
47 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/js/sentiment-analysis.js:
--------------------------------------------------------------------------------
1 | const HOSTED_URLS = {
2 | queryTwitter: window.location.protocol + '//'+ window.location.hostname + '/js/sentiment/queryTwitter.php?q=',
3 | model: 'https://storage.googleapis.com/tfjs-models/tfjs/sentiment_cnn_v1/model.json',
4 | metadata: 'https://storage.googleapis.com/tfjs-models/tfjs/sentiment_cnn_v1/metadata.json'
5 | };
6 | const LOCAL_URLS = {
7 | queryTwitter: 'php/queryTwitter.php?q=',
8 | model: 'https://storage.googleapis.com/tfjs-models/tfjs/sentiment_cnn_v1/model.json',
9 | metadata: 'https://storage.googleapis.com/tfjs-models/tfjs/sentiment_cnn_v1/metadata.json'
10 | };
11 | const SentimentThreshold = {
12 | Positive: 0.66,
13 | Neutral: 0.33,
14 | Negative: 0
15 | }
16 | const PAD_INDEX = 0;
17 | const OOV_INDEX = 2;
18 |
19 | let urls, model, metadata;
20 |
21 | $("#tag-input").on('keyup', function (e) {
22 | if (e.keyCode === 13) {
23 | twitterSentiment();
24 | }
25 | });
26 |
27 | $(".btn-search").click(function () {
28 | twitterSentiment();
29 | });
30 |
31 | function init(){
32 | if(window.location.hostname == 'localhost'){
33 | urls = LOCAL_URLS;
34 | }else {
35 | urls = HOSTED_URLS;
36 | }
37 | }
38 |
39 | async function setupSentimentModel(){
40 | if(typeof model === 'undefined'){
41 | model = await loadModel(urls.model);
42 | }
43 | if(typeof metadata === 'undefined'){
44 | metadata = await loadMetadata(urls.metadata);
45 | }
46 | }
47 |
48 | function twitterSentiment(){
49 | $('#tweet-list').addClass('d-none');
50 | $('#positive').empty();
51 | $('#neutral').empty();
52 | $('#negative').empty();
53 | $('#chartContainer').empty();
54 | $('.spinner-border').removeClass('d-none');
55 |
56 | getTwitterHashTagData($("#tag-input").val(), processTwitterData);
57 | }
58 |
59 | function processTwitterData(tweets){
60 | setupSentimentModel().then(
61 | result => {
62 | const twitterData = [];
63 | $.each(tweets, function( index, tweet ) {
64 | const tweet_text = tweet.full_text.replace(/(?:https?|ftp):\/\/[\n\S]+/g, '');
65 | const sentiment_score = getSentimentScore(tweet_text);
66 | let tweet_sentiment = '';
67 | if(sentiment_score > SentimentThreshold.Positive){
68 | tweet_sentiment = 'positive'
69 | }else if(sentiment_score > SentimentThreshold.Neutral){
70 | tweet_sentiment = 'neutral'
71 | }else if(sentiment_score >= SentimentThreshold.Negative){
72 | tweet_sentiment = 'negative'
73 | }
74 | twitterData.push({
75 | sentiment: tweet_sentiment,
76 | score: sentiment_score.toFixed(4),
77 | tweet: tweet_text
78 | });
79 | });
80 | console.log(twitterData);
81 | $('.spinner-border').addClass('d-none');
82 | displayTweets(twitterData.filter(t => t.sentiment == 'positive'), 'positive');
83 | displayTweets(twitterData.filter(t => t.sentiment == 'neutral'), 'neutral');
84 | displayTweets(twitterData.filter(t => t.sentiment == 'negative'), 'negative');
85 | $('#tweet-list').removeClass('d-none');
86 | displayPieChart(twitterData);
87 | }
88 | )
89 | }
90 |
91 | async function loadModel(url) {
92 | try {
93 | const model = await tf.loadLayersModel(url);
94 | return model;
95 | } catch (err) {
96 | console.log(err);
97 | }
98 | }
99 |
100 | async function loadMetadata(url) {
101 | try {
102 | const metadataJson = await fetch(url);
103 | const metadata = await metadataJson.json();
104 | return metadata;
105 | } catch (err) {
106 | console.log(err);
107 | }
108 | }
109 |
110 | function padSequences(sequences, maxLen, padding = 'pre', truncating = 'pre', value = PAD_INDEX) {
111 | return sequences.map(seq => {
112 | if (seq.length > maxLen) {
113 | if (truncating === 'pre') {
114 | seq.splice(0, seq.length - maxLen);
115 | } else {
116 | seq.splice(maxLen, seq.length - maxLen);
117 | }
118 | }
119 |
120 | if (seq.length < maxLen) {
121 | const pad = [];
122 | for (let i = 0; i < maxLen - seq.length; ++i) {
123 | pad.push(value);
124 | }
125 | if (padding === 'pre') {
126 | seq = pad.concat(seq);
127 | } else {
128 | seq = seq.concat(pad);
129 | }
130 | }
131 |
132 | return seq;
133 | });
134 | }
135 |
136 | function getSentimentScore(text) {
137 | const inputText = text.trim().toLowerCase().replace(/(\.|\,|\!)/g, '').split(' ');
138 | // Convert the words to a sequence of word indices.
139 | const sequence = inputText.map(word => {
140 | let wordIndex = metadata.word_index[word] + metadata.index_from;
141 | if (wordIndex > metadata.vocabulary_size) {
142 | wordIndex = OOV_INDEX;
143 | }
144 | return wordIndex;
145 | });
146 | // Perform truncation and padding.
147 | const paddedSequence = padSequences([sequence], metadata.max_len);
148 | const input = tf.tensor2d(paddedSequence, [1, metadata.max_len]);
149 |
150 | const predictOut = model.predict(input);
151 | const score = predictOut.dataSync()[0];
152 | predictOut.dispose();
153 |
154 | return score;
155 | }
156 |
157 | function getTwitterHashTagData(query, callback) {
158 | $.getJSON( urls.queryTwitter + query, function(result) {
159 | console.log(result);
160 | if(result !== null && result.statuses !== null){
161 | callback(result.statuses);
162 | }
163 | });
164 | }
165 |
166 | function displayTweets(twitterData, sentiment){
167 | var tbl = document.createElement('table');
168 | var tr = tbl.insertRow();
169 | for( var j in twitterData[0] ) {
170 | if(j !=='sentiment'){
171 | var td = tr.insertCell();
172 | td.appendChild(document.createTextNode(j));
173 | }
174 | }
175 |
176 | for( var i = 0; i < twitterData.length; i++) {
177 | var tr = tbl.insertRow();
178 | for( var j in twitterData[i] ) {
179 | if(j !=='sentiment'){
180 | var td = tr.insertCell();
181 | var text = twitterData[i][j];
182 | td.appendChild(document.createTextNode(text));
183 | }
184 | }
185 | }
186 | tbl.setAttribute('class', 'tweet-table')
187 | $('#'+sentiment).append(tbl);
188 | $('#'+sentiment+'-counter').html('('+ twitterData.length +')');
189 | }
190 |
191 | function displayPieChart(twitterData){
192 | var sentimentsCounter = {"Negative": 0, "Neutral": 0, "Positive": 0};
193 | for( var i = 0; i < twitterData.length; i++) {
194 | switch(twitterData[i].sentiment) {
195 | case 'positive':
196 | sentimentsCounter["Positive"] += 1;
197 | break;
198 | case 'negative':
199 | sentimentsCounter["Negative"] += 1;
200 | break;
201 | case 'neutral':
202 | sentimentsCounter["Neutral"] += 1;
203 | break;
204 | }
205 | }
206 |
207 | var chart = new CanvasJS.Chart("chartContainer", {
208 | theme: "light2",
209 | exportEnabled: true,
210 | animationEnabled: true,
211 | data: [{
212 | type: "pie",
213 | startAngle: 25,
214 | toolTipContent: "{label} : {y}%",
215 | showInLegend: "true",
216 | legendText: "{label}",
217 | indexLabelFontSize: 16,
218 | indexLabel: "{label} - {y}%",
219 | dataPoints: [
220 | { y: (sentimentsCounter["Positive"] * 100.00/twitterData.length).toFixed(2), label: "Positive" },
221 | { y: (sentimentsCounter["Neutral"] * 100.00/twitterData.length).toFixed(2), label: "Neutral" },
222 | { y: (sentimentsCounter["Negative"] * 100.00/twitterData.length).toFixed(2), label: "Negative" },
223 | ]
224 | }]
225 | });
226 | chart.render();
227 | }
228 |
229 | init();
--------------------------------------------------------------------------------
/php/TwitterAPIExchange.php:
--------------------------------------------------------------------------------
1 |
11 | * @license MIT License
12 | * @version 1.0.4
13 | * @link http://github.com/j7mbo/twitter-api-php
14 | */
15 | class TwitterAPIExchange
16 | {
17 | /**
18 | * @var string
19 | */
20 | private $oauth_access_token;
21 |
22 | /**
23 | * @var string
24 | */
25 | private $oauth_access_token_secret;
26 |
27 | /**
28 | * @var string
29 | */
30 | private $consumer_key;
31 |
32 | /**
33 | * @var string
34 | */
35 | private $consumer_secret;
36 |
37 | /**
38 | * @var array
39 | */
40 | private $postfields;
41 |
42 | /**
43 | * @var string
44 | */
45 | private $getfield;
46 |
47 | /**
48 | * @var mixed
49 | */
50 | protected $oauth;
51 |
52 | /**
53 | * @var string
54 | */
55 | public $url;
56 |
57 | /**
58 | * @var string
59 | */
60 | public $requestMethod;
61 |
62 | /**
63 | * The HTTP status code from the previous request
64 | *
65 | * @var int
66 | */
67 | protected $httpStatusCode;
68 |
69 | /**
70 | * Create the API access object. Requires an array of settings::
71 | * oauth access token, oauth access token secret, consumer key, consumer secret
72 | * These are all available by creating your own application on dev.twitter.com
73 | * Requires the cURL library
74 | *
75 | * @throws \RuntimeException When cURL isn't loaded
76 | * @throws \InvalidArgumentException When incomplete settings parameters are provided
77 | *
78 | * @param array $settings
79 | */
80 | public function __construct(array $settings)
81 | {
82 | if (!function_exists('curl_init'))
83 | {
84 | throw new RuntimeException('TwitterAPIExchange requires cURL extension to be loaded, see: http://curl.haxx.se/docs/install.html');
85 | }
86 |
87 | if (!isset($settings['oauth_access_token'])
88 | || !isset($settings['oauth_access_token_secret'])
89 | || !isset($settings['consumer_key'])
90 | || !isset($settings['consumer_secret']))
91 | {
92 | throw new InvalidArgumentException('Incomplete settings passed to TwitterAPIExchange');
93 | }
94 |
95 | $this->oauth_access_token = $settings['oauth_access_token'];
96 | $this->oauth_access_token_secret = $settings['oauth_access_token_secret'];
97 | $this->consumer_key = $settings['consumer_key'];
98 | $this->consumer_secret = $settings['consumer_secret'];
99 | }
100 |
101 | /**
102 | * Set postfields array, example: array('screen_name' => 'J7mbo')
103 | *
104 | * @param array $array Array of parameters to send to API
105 | *
106 | * @throws \Exception When you are trying to set both get and post fields
107 | *
108 | * @return TwitterAPIExchange Instance of self for method chaining
109 | */
110 | public function setPostfields(array $array)
111 | {
112 | if (!is_null($this->getGetfield()))
113 | {
114 | throw new Exception('You can only choose get OR post fields (post fields include put).');
115 | }
116 |
117 | if (isset($array['status']) && substr($array['status'], 0, 1) === '@')
118 | {
119 | $array['status'] = sprintf("\0%s", $array['status']);
120 | }
121 |
122 | foreach ($array as $key => &$value)
123 | {
124 | if (is_bool($value))
125 | {
126 | $value = ($value === true) ? 'true' : 'false';
127 | }
128 | }
129 |
130 | $this->postfields = $array;
131 |
132 | // rebuild oAuth
133 | if (isset($this->oauth['oauth_signature']))
134 | {
135 | $this->buildOauth($this->url, $this->requestMethod);
136 | }
137 |
138 | return $this;
139 | }
140 |
141 | /**
142 | * Set getfield string, example: '?screen_name=J7mbo'
143 | *
144 | * @param string $string Get key and value pairs as string
145 | *
146 | * @throws \Exception
147 | *
148 | * @return \TwitterAPIExchange Instance of self for method chaining
149 | */
150 | public function setGetfield($string)
151 | {
152 | if (!is_null($this->getPostfields()))
153 | {
154 | throw new Exception('You can only choose get OR post / post fields.');
155 | }
156 |
157 | $getfields = preg_replace('/^\?/', '', explode('&', $string));
158 | $params = array();
159 |
160 | foreach ($getfields as $field)
161 | {
162 | if ($field !== '')
163 | {
164 | list($key, $value) = explode('=', $field);
165 | $params[$key] = $value;
166 | }
167 | }
168 |
169 | $this->getfield = '?' . http_build_query($params, '', '&');
170 |
171 | return $this;
172 | }
173 |
174 | /**
175 | * Get getfield string (simple getter)
176 | *
177 | * @return string $this->getfields
178 | */
179 | public function getGetfield()
180 | {
181 | return $this->getfield;
182 | }
183 |
184 | /**
185 | * Get postfields array (simple getter)
186 | *
187 | * @return array $this->postfields
188 | */
189 | public function getPostfields()
190 | {
191 | return $this->postfields;
192 | }
193 |
194 | /**
195 | * Build the Oauth object using params set in construct and additionals
196 | * passed to this method. For v1.1, see: https://dev.twitter.com/docs/api/1.1
197 | *
198 | * @param string $url The API url to use. Example: https://api.twitter.com/1.1/search/tweets.json
199 | * @param string $requestMethod Either POST or GET
200 | *
201 | * @throws \Exception
202 | *
203 | * @return \TwitterAPIExchange Instance of self for method chaining
204 | */
205 | public function buildOauth($url, $requestMethod)
206 | {
207 | if (!in_array(strtolower($requestMethod), array('post', 'get', 'put', 'delete')))
208 | {
209 | throw new Exception('Request method must be either POST, GET or PUT or DELETE');
210 | }
211 |
212 | $consumer_key = $this->consumer_key;
213 | $consumer_secret = $this->consumer_secret;
214 | $oauth_access_token = $this->oauth_access_token;
215 | $oauth_access_token_secret = $this->oauth_access_token_secret;
216 |
217 | $oauth = array(
218 | 'oauth_consumer_key' => $consumer_key,
219 | 'oauth_nonce' => time(),
220 | 'oauth_signature_method' => 'HMAC-SHA1',
221 | 'oauth_token' => $oauth_access_token,
222 | 'oauth_timestamp' => time(),
223 | 'oauth_version' => '1.0'
224 | );
225 |
226 | $getfield = $this->getGetfield();
227 |
228 | if (!is_null($getfield))
229 | {
230 | $getfields = str_replace('?', '', explode('&', $getfield));
231 |
232 | foreach ($getfields as $g)
233 | {
234 | $split = explode('=', $g);
235 |
236 | /** In case a null is passed through **/
237 | if (isset($split[1]))
238 | {
239 | $oauth[$split[0]] = urldecode($split[1]);
240 | }
241 | }
242 | }
243 |
244 | $postfields = $this->getPostfields();
245 |
246 | if (!is_null($postfields)) {
247 | foreach ($postfields as $key => $value) {
248 | $oauth[$key] = $value;
249 | }
250 | }
251 |
252 | $base_info = $this->buildBaseString($url, $requestMethod, $oauth);
253 | $composite_key = rawurlencode($consumer_secret) . '&' . rawurlencode($oauth_access_token_secret);
254 | $oauth_signature = base64_encode(hash_hmac('sha1', $base_info, $composite_key, true));
255 | $oauth['oauth_signature'] = $oauth_signature;
256 |
257 | $this->url = $url;
258 | $this->requestMethod = $requestMethod;
259 | $this->oauth = $oauth;
260 |
261 | return $this;
262 | }
263 |
264 | /**
265 | * Perform the actual data retrieval from the API
266 | *
267 | * @param boolean $return If true, returns data. This is left in for backward compatibility reasons
268 | * @param array $curlOptions Additional Curl options for this request
269 | *
270 | * @throws \Exception
271 | *
272 | * @return string json If $return param is true, returns json data.
273 | */
274 | public function performRequest($return = true, $curlOptions = array())
275 | {
276 | if (!is_bool($return))
277 | {
278 | throw new Exception('performRequest parameter must be true or false');
279 | }
280 |
281 | $header = array($this->buildAuthorizationHeader($this->oauth), 'Expect:');
282 |
283 | $getfield = $this->getGetfield();
284 | $postfields = $this->getPostfields();
285 |
286 | if (in_array(strtolower($this->requestMethod), array('put', 'delete')))
287 | {
288 | $curlOptions[CURLOPT_CUSTOMREQUEST] = $this->requestMethod;
289 | }
290 |
291 | $options = $curlOptions + array(
292 | CURLOPT_HTTPHEADER => $header,
293 | CURLOPT_HEADER => false,
294 | CURLOPT_URL => $this->url,
295 | CURLOPT_RETURNTRANSFER => true,
296 | CURLOPT_TIMEOUT => 10,
297 | );
298 |
299 | if (!is_null($postfields))
300 | {
301 | $options[CURLOPT_POSTFIELDS] = http_build_query($postfields, '', '&');
302 | }
303 | else
304 | {
305 | if ($getfield !== '')
306 | {
307 | $options[CURLOPT_URL] .= $getfield;
308 | }
309 | }
310 |
311 | $feed = curl_init();
312 | curl_setopt_array($feed, $options);
313 | $json = curl_exec($feed);
314 |
315 | $this->httpStatusCode = curl_getinfo($feed, CURLINFO_HTTP_CODE);
316 |
317 | if (($error = curl_error($feed)) !== '')
318 | {
319 | curl_close($feed);
320 |
321 | throw new \Exception($error);
322 | }
323 |
324 | curl_close($feed);
325 |
326 | return $json;
327 | }
328 |
329 | /**
330 | * Private method to generate the base string used by cURL
331 | *
332 | * @param string $baseURI
333 | * @param string $method
334 | * @param array $params
335 | *
336 | * @return string Built base string
337 | */
338 | private function buildBaseString($baseURI, $method, $params)
339 | {
340 | $return = array();
341 | ksort($params);
342 |
343 | foreach($params as $key => $value)
344 | {
345 | $return[] = rawurlencode($key) . '=' . rawurlencode($value);
346 | }
347 |
348 | return $method . "&" . rawurlencode($baseURI) . '&' . rawurlencode(implode('&', $return));
349 | }
350 |
351 | /**
352 | * Private method to generate authorization header used by cURL
353 | *
354 | * @param array $oauth Array of oauth data generated by buildOauth()
355 | *
356 | * @return string $return Header used by cURL for request
357 | */
358 | private function buildAuthorizationHeader(array $oauth)
359 | {
360 | $return = 'Authorization: OAuth ';
361 | $values = array();
362 |
363 | foreach($oauth as $key => $value)
364 | {
365 | if (in_array($key, array('oauth_consumer_key', 'oauth_nonce', 'oauth_signature',
366 | 'oauth_signature_method', 'oauth_timestamp', 'oauth_token', 'oauth_version'))) {
367 | $values[] = "$key=\"" . rawurlencode($value) . "\"";
368 | }
369 | }
370 |
371 | $return .= implode(', ', $values);
372 | return $return;
373 | }
374 |
375 | /**
376 | * Helper method to perform our request
377 | *
378 | * @param string $url
379 | * @param string $method
380 | * @param string $data
381 | * @param array $curlOptions
382 | *
383 | * @throws \Exception
384 | *
385 | * @return string The json response from the server
386 | */
387 | public function request($url, $method = 'get', $data = null, $curlOptions = array())
388 | {
389 | if (strtolower($method) === 'get')
390 | {
391 | $this->setGetfield($data);
392 | }
393 | else
394 | {
395 | $this->setPostfields($data);
396 | }
397 |
398 | return $this->buildOauth($url, $method)->performRequest(true, $curlOptions);
399 | }
400 |
401 | /**
402 | * Get the HTTP status code for the previous request
403 | *
404 | * @return integer
405 | */
406 | public function getHttpStatusCode()
407 | {
408 | return $this->httpStatusCode;
409 | }
410 | }
411 |
--------------------------------------------------------------------------------
/php/queryTwitter.php:
--------------------------------------------------------------------------------
1 | "YOUR_ACCESS_TOKEN",
8 | 'oauth_access_token_secret' => "YOUR_ACCESS_TOKEN_SECRET",
9 | 'consumer_key' => "YOUR_CONSUMER_KEY",
10 | 'consumer_secret' => "YOUR_CONSUMER_SECRET"
11 | );
12 |
13 | $url = 'https://api.twitter.com/1.1/search/tweets.json';
14 | $getfield = '?q=#'.$hashtag.' AND -filter:retweets AND -filter:replies&lang=en&count=20&tweet_mode=extended';
15 | $requestMethod = 'GET';
16 |
17 | $twitter = new TwitterAPIExchange($settings);
18 | $response = $twitter->setGetfield($getfield)
19 | ->buildOauth($url, $requestMethod)
20 | ->performRequest();
21 |
22 | echo $response;
23 | ?>
--------------------------------------------------------------------------------
/style/sentiment-analysis.css:
--------------------------------------------------------------------------------
1 | .info-text {
2 | font-size: 20px;
3 | margin: 20px auto
4 | }
5 |
6 | .input-field .prefix {
7 | position: absolute;
8 | width: 2rem;
9 | font-size: 3rem;
10 | transition: color .2s;
11 | }
12 |
13 | .material-icons {
14 | font-family: 'Material Icons';
15 | font-weight: normal;
16 | font-style: normal;
17 | font-size: 24px;
18 | line-height: 1;
19 | letter-spacing: normal;
20 | text-transform: none;
21 | display: inline-block;
22 | white-space: nowrap;
23 | word-wrap: normal;
24 | direction: ltr;
25 | -webkit-font-feature-settings: 'liga';
26 | -webkit-font-smoothing: antialiased;
27 | }
28 |
29 | #tag-input {
30 | font-size: 2em;
31 | background-color: transparent;
32 | border: none;
33 | border-bottom: 1px solid #9e9e9e;
34 | border-radius: 0;
35 | outline: none;
36 | height: 3rem;
37 | width: 66%;
38 | width: calc(80% - 3rem);
39 | margin: 0 0 20px 2rem;
40 | padding: 0;
41 | box-shadow: none;
42 | box-sizing: content-box;
43 | transition: all 0.3s;
44 | }
45 |
46 | @media screen and (min-width: 992px) {
47 | .btn-search {
48 | margin-bottom: 5px;
49 | }
50 | }
51 |
52 |
53 | @media screen and (max-width: 992px) {
54 | #tag-input {
55 | font-size: 1.3em;
56 | }
57 | }
58 |
59 | .tweet-table,
60 | .tweet-table td {
61 | border: 1px solid #dee2e6;
62 | border-top: 0px;
63 | }
64 |
65 | .tweet-table td {
66 | padding: 5px;
67 | }
68 | .tweet-table td:first-child {
69 | text-align: center;
70 | width: 20%;
71 | }
72 |
73 | .tweet-table td:nth-child(2) {
74 | text-align: left;
75 | width: 80%;
76 | }
77 |
78 | .spinner-border {
79 | width: 100px;
80 | height: 100px;
81 | margin: 30px auto;
82 | }
83 |
84 | #chartContainer{
85 | height: 400px;
86 | margin: 15px;
87 | }
88 |
89 | #positive-tab.nav-link{
90 | color: #6878AD;
91 | }
92 | #positive-tab.nav-link.active{
93 | color: #fff;
94 | background-color: #6878AD;
95 | }
96 | #neutral-tab.nav-link.active{
97 | color: #fff;
98 | background-color: #00D0A4;
99 | }
100 | #neutral-tab.nav-link{
101 | color: #00D0A4;
102 | }
103 | #negative-tab.nav-link.active{
104 | color: #fff;
105 | background-color: #F2736A;
106 | }
107 | #negative-tab.nav-link{
108 | color: #F2736A;
109 | }
110 | .nav-link {
111 | padding: .5rem .8rem !important;
112 | }
--------------------------------------------------------------------------------