├── .github └── workflows │ └── heroku.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── demo.gif ├── heroku.yml ├── pom.xml └── src └── main ├── java └── com │ └── rawsanj │ └── tweet │ ├── Application.java │ ├── config │ ├── TwitterConfig.java │ └── WebSocketConfig.java │ ├── controller │ ├── SearchController.java │ └── WebSocketEventController.java │ ├── service │ └── StreamTweetEventService.java │ └── util │ ├── Description.java │ └── Entities.java └── resources ├── application.properties ├── static ├── app.js └── main.css └── templates ├── events.html └── search.html /.github/workflows/heroku.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to Heroku 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | env: 14 | HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Set up JDK 8 19 | uses: actions/setup-java@v1 20 | with: 21 | java-version: 8 22 | - name: Build with Maven 23 | run: mvn -B package --file pom.xml 24 | - name: Install Heroku Java Plugin 25 | run: heroku plugins:install java 26 | - name: Deploy to Heroku 27 | run: heroku deploy:jar target/*.jar --app spring-tweets 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | pom.xml.tag 3 | pom.xml.releaseBackup 4 | pom.xml.versionsBackup 5 | pom.xml.next 6 | release.properties 7 | dependency-reduced-pom.xml 8 | buildNumber.properties 9 | .mvn/timing.properties 10 | 11 | # Avoid ignoring Maven wrapper jar file (.jar files are usually ignored) 12 | !/.mvn/wrapper/maven-wrapper.jar 13 | 14 | ### STS ### 15 | .apt_generated 16 | .classpath 17 | .factorypath 18 | .project 19 | .settings 20 | .springBeans 21 | 22 | ### IntelliJ IDEA ### 23 | .idea 24 | *.iws 25 | *.iml 26 | *.ipr -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | install: mvn install 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Sanjay Rawat 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 | # Spring-Twitter-Stream [![Build Status](https://travis-ci.org/RawSanj/spring-twitter-stream.svg?branch=master)](https://travis-ci.org/RawSanj/spring-twitter-stream) 2 | 3 | Spring Boot - Spring Social Twitter - D3.Js webapp for Streaming Live #HashTags and source location of Tweets. 4 | 5 | ## Demo - Live Demo @ [Heroku](https://spring-tweets.herokuapp.com/) 6 | ![Spring Twitter Stream Demo](/demo.gif?raw=true "Spring Twitter Stream Demo") 7 | ## Installation 8 | 9 | #### Clone the Github repository 10 | ```sh 11 | $ git clone https://github.com/RawSanj/Spring-Twitter-Stream.git 12 | ``` 13 | 14 | #### Twitter App and Configuration 15 | 1. Login to https://apps.twitter.com 16 | 2. Create a New Application and note down the *Consumer Key, Consumer Secret, Access Token and Access Token Secret*. 17 | 3. Edit the `/src/main/resources/application.properties` and add above noted keys. 18 | 19 | #### Run the application 20 | ```sh 21 | $ mvn spring-boot:run 22 | ``` 23 | Then navigate to [http://localhost:8080](http://localhost:8080) in your browser. 24 | 25 | ## Deploy to Cloud Foundry 26 | 27 | #### Package the application (creates spring-twitter-stream-0.1.0.war file) 28 | ```sh 29 | $ mvn clean package 30 | ``` 31 | 32 | #### Pre-requisite: 33 | 34 | 1. Account @ http://run.pivotal.io. $87 Credit Free Account. 35 | 2. cf cli is installed - http://docs.cloudfoundry.org/cf-cli 36 | 37 | #### Login to Pivotal Cloud Foundry 38 | ```sh 39 | $ cf login -a https://api.run.pivotal.io 40 | ``` 41 | #### Deploy the application 42 | ```sh 43 | $ cf push spring-twitter-app -p target/spring-twitter-stream-0.1.0.war --random-route 44 | ``` 45 | 46 | ## Deploy to Heroku 47 | 48 | #### Package the application (creates spring-twitter-stream-0.1.0.war file) 49 | ```sh 50 | $ mvn clean package 51 | ``` 52 | 53 | #### Pre-requisite: 54 | 55 | 1. Account @ https://www.heroku.com. Free Account. 56 | 2. heroku cli is installed - https://devcenter.heroku.com/articles/heroku-cli 57 | 58 | #### Login to Heroku 59 | ```sh 60 | $ heroku login 61 | ``` 62 | #### Install Heroku cli deploy plugin 63 | ```sh 64 | $ heroku plugins:install heroku-cli-deploy 65 | ``` 66 | #### Create the application in Heroku 67 | ```sh 68 | $ heroku create spring-tweets-app 69 | ``` 70 | #### Deploy the application 71 | ```sh 72 | $ heroku war:deploy target/spring-twitter-stream-0.1.0.war --app spring-tweets-app 73 | ``` 74 | 75 | 76 | ## Tools and Tech 77 | 78 | The following tools, technologies and libraries are used to create this project : 79 | 80 | * [Spring Boot] - (Spring Social Twitter, Spring SseEmitter) 81 | * [Thymeleaf] - (Thymeleaf is a template engine capable of processing and generating HTML, XML, JavaScript, CSS and text.) 82 | * [D3Js] - D3.js is a JavaScript library for manipulating documents based on data. 83 | * [Spring Tool Suite] 84 | * [Git] 85 | 86 | ## License 87 | ---- 88 | 89 | The MIT License (MIT) 90 | 91 | Copyright (c) 2017. Sanjay Rawat 92 | 93 | [Thymeleaf]: http://www.thymeleaf.org/ 94 | [Spring Boot]: http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/ 95 | [Spring Tool Suite]: https://spring.io/tools 96 | [Git]: https://git-scm.com/ 97 | [D3Js]: https://d3js.org/ 98 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RawSanj/spring-twitter-stream/bc94073e56a9c30caffa47d487ea9a9b79ee6559/demo.gif -------------------------------------------------------------------------------- /heroku.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to Heroku 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | env: 14 | HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - name: Set up JDK 8 19 | uses: actions/setup-java@v1 20 | with: 21 | java-version: 8 22 | - name: Build with Maven 23 | run: mvn -B package --file pom.xml 24 | - name: Install Heroku Java Plugin 25 | run: heroku plugins:install java 26 | - name: Deploy to Heroku 27 | run: heroku war:deploy target/spring-twitter-stream-0.1.0.war --app spring-tweets-app 28 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework 7 | spring-twitter-stream 8 | jar 9 | 0.1.0 10 | 11 | 12 | org.springframework.boot 13 | spring-boot-starter-parent 14 | 1.5.9.RELEASE 15 | 16 | 17 | 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-starter-web 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-thymeleaf 26 | 27 | 28 | org.springframework.social 29 | spring-social-twitter 30 | 31 | 32 | 33 | org.springframework.boot 34 | spring-boot-starter-websocket 35 | 36 | 37 | 38 | org.webjars 39 | webjars-locator 40 | 41 | 42 | 43 | org.webjars 44 | sockjs-client 45 | 1.0.2 46 | 47 | 48 | 49 | org.webjars 50 | stomp-websocket 51 | 2.3.3 52 | 53 | 54 | 55 | org.webjars 56 | bootstrap 57 | 3.3.7 58 | 59 | 60 | 61 | org.webjars 62 | jquery 63 | 3.1.0 64 | 65 | 66 | 67 | org.springframework.boot 68 | spring-boot-devtools 69 | 70 | 71 | 72 | org.springframework.boot 73 | spring-boot-starter-test 74 | test 75 | 76 | 77 | 78 | 79 | 80 | UTF-8 81 | 1.8 82 | 83 | 84 | 85 | 86 | 87 | org.springframework.boot 88 | spring-boot-maven-plugin 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/main/java/com/rawsanj/tweet/Application.java: -------------------------------------------------------------------------------- 1 | package com.rawsanj.tweet; 2 | 3 | 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @SpringBootApplication 8 | public class Application{ 9 | 10 | public static void main(String[] args) { 11 | SpringApplication.run(Application.class, args); 12 | } 13 | } -------------------------------------------------------------------------------- /src/main/java/com/rawsanj/tweet/config/TwitterConfig.java: -------------------------------------------------------------------------------- 1 | package com.rawsanj.tweet.config; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.social.twitter.api.impl.TwitterTemplate; 7 | 8 | @Configuration 9 | public class TwitterConfig { 10 | 11 | @Value("${spring.social.twitter.appId}") 12 | private String consumerKey; 13 | 14 | @Value("${spring.social.twitter.appSecret}") 15 | private String consumerSecret; 16 | 17 | @Value("${twitter.access.token}") 18 | private String accessToken; 19 | 20 | @Value("${twitter.access.token.secret}") 21 | private String accessTokenSecret; 22 | 23 | @Bean 24 | TwitterTemplate getTwtTemplate(){ 25 | return new TwitterTemplate(consumerKey, consumerSecret, accessToken, accessTokenSecret); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/rawsanj/tweet/config/WebSocketConfig.java: -------------------------------------------------------------------------------- 1 | package com.rawsanj.tweet.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.messaging.simp.config.MessageBrokerRegistry; 5 | import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer; 6 | import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; 7 | import org.springframework.web.socket.config.annotation.StompEndpointRegistry; 8 | 9 | @Configuration 10 | @EnableWebSocketMessageBroker 11 | public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer { 12 | 13 | @Override 14 | public void configureMessageBroker(MessageBrokerRegistry config) { 15 | config.enableSimpleBroker("/topic"); 16 | config.setApplicationDestinationPrefixes("/app"); 17 | } 18 | 19 | @Override 20 | public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) { 21 | stompEndpointRegistry.addEndpoint("/twitter-websocket").withSockJS(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/rawsanj/tweet/controller/SearchController.java: -------------------------------------------------------------------------------- 1 | package com.rawsanj.tweet.controller; 2 | 3 | import org.springframework.social.twitter.api.SearchParameters; 4 | import org.springframework.social.twitter.api.SearchResults; 5 | import org.springframework.social.twitter.api.Tweet; 6 | import org.springframework.social.twitter.api.impl.TwitterTemplate; 7 | import org.springframework.stereotype.Controller; 8 | import org.springframework.ui.Model; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RequestParam; 12 | 13 | import java.util.List; 14 | 15 | 16 | @Controller 17 | @RequestMapping("/") 18 | public class SearchController { 19 | 20 | private final TwitterTemplate twitterTemplate; 21 | 22 | public SearchController(TwitterTemplate twitterTemplate) { 23 | this.twitterTemplate=twitterTemplate; 24 | } 25 | 26 | @GetMapping(path = "tweet") 27 | public String searchTwitter(Model model, @RequestParam String search) { 28 | 29 | int count = 200; 30 | 31 | SearchResults results = twitterTemplate.searchOperations().search( 32 | new SearchParameters(search) 33 | .resultType(SearchParameters.ResultType.RECENT) 34 | .count(count)); 35 | 36 | List tweets = results.getTweets(); 37 | model.addAttribute("tweets", tweets); 38 | model.addAttribute("count", count); 39 | model.addAttribute("search", search); 40 | 41 | return "search"; 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /src/main/java/com/rawsanj/tweet/controller/WebSocketEventController.java: -------------------------------------------------------------------------------- 1 | package com.rawsanj.tweet.controller; 2 | 3 | import java.util.List; 4 | import java.util.concurrent.CopyOnWriteArrayList; 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.web.bind.annotation.RequestMapping; 7 | import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; 8 | 9 | import com.rawsanj.tweet.service.StreamTweetEventService; 10 | 11 | @Controller 12 | @RequestMapping("/") 13 | public class WebSocketEventController { 14 | 15 | private StreamTweetEventService streamTweetEventService; 16 | 17 | private List emitters = new CopyOnWriteArrayList<>(); 18 | 19 | public WebSocketEventController(StreamTweetEventService streamTweetEventService) { 20 | this.streamTweetEventService = streamTweetEventService; 21 | } 22 | 23 | @RequestMapping("/") 24 | public String streamTweetAsEvents(){ 25 | return "events"; 26 | } 27 | 28 | @RequestMapping("/tweetLocation") 29 | public SseEmitter streamTweets() throws InterruptedException{ 30 | 31 | SseEmitter sseEmitter = new SseEmitter(); 32 | emitters.add(sseEmitter); 33 | sseEmitter.onCompletion(() -> emitters.remove(sseEmitter)); 34 | 35 | streamTweetEventService.streamTweetEvent(emitters); 36 | 37 | return sseEmitter; 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /src/main/java/com/rawsanj/tweet/service/StreamTweetEventService.java: -------------------------------------------------------------------------------- 1 | package com.rawsanj.tweet.service; 2 | 3 | import java.io.IOException; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.social.twitter.api.HashTagEntity; 11 | import org.springframework.social.twitter.api.Stream; 12 | import org.springframework.social.twitter.api.StreamDeleteEvent; 13 | import org.springframework.social.twitter.api.StreamListener; 14 | import org.springframework.social.twitter.api.StreamWarningEvent; 15 | import org.springframework.social.twitter.api.Tweet; 16 | import org.springframework.social.twitter.api.Twitter; 17 | import org.springframework.stereotype.Service; 18 | import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; 19 | 20 | @Service 21 | public class StreamTweetEventService { 22 | 23 | private final Logger log = LoggerFactory.getLogger(StreamTweetEventService.class); 24 | 25 | @Autowired 26 | private Twitter twitter; 27 | 28 | private Stream userStream; 29 | 30 | public void streamTweetEvent(List emitters) throws InterruptedException{ 31 | 32 | List listeners = new ArrayList(); 33 | 34 | StreamListener streamListener = new StreamListener() { 35 | @Override 36 | public void onWarning(StreamWarningEvent warningEvent) { 37 | } 38 | 39 | @Override 40 | public void onTweet(Tweet tweet) { 41 | //log.info("User '{}', Tweeted : {}, from ; {}", tweet.getUser().getName() , tweet.getText(), tweet.getUser().getLocation()); 42 | Integer connectedUsers = emitters.size(); 43 | 44 | //log.info("Streaming to :" + connectedUsers +" connected Users"); 45 | 46 | if (connectedUsers!=0) { 47 | for (SseEmitter emiter : emitters) { 48 | try { 49 | emiter.send(SseEmitter.event().name("streamLocation").data(tweet.getUser().getLocation())); 50 | 51 | StringBuilder hashTag = new StringBuilder(); 52 | 53 | List hashTags = tweet.getEntities().getHashTags(); 54 | for (HashTagEntity hash : hashTags) { 55 | hashTag.append("#"+hash.getText() + " "); 56 | } 57 | //System.out.println(hashTag); 58 | emiter.send(SseEmitter.event().name("streamHashtags").data(hashTag)); 59 | } catch (IOException e) { 60 | System.out.println("User Disconnected from the Stream"); 61 | //e.printStackTrace(); 62 | } 63 | } 64 | }else{ 65 | //Close Stream when all Users are disconnected. 66 | userStream.close(); 67 | log.info("Zero Connected Users - Closing Stream"); 68 | } 69 | 70 | } 71 | 72 | @Override 73 | public void onLimit(int numberOfLimitedTweets) { 74 | } 75 | 76 | @Override 77 | public void onDelete(StreamDeleteEvent deleteEvent) { 78 | } 79 | }; 80 | //Start Stream when a User is connected 81 | if (emitters.size()==1) { 82 | listeners.add(streamListener); 83 | userStream = twitter.streamingOperations().sample(listeners); 84 | } 85 | 86 | // Stream from a specific Location: 87 | // Float west=-122.75f; 88 | // Float south=36.8f; 89 | // Float east=-121.75f; 90 | // Float north = 37.8f; 91 | // 92 | // FilterStreamParameters filterStreamParameters = new FilterStreamParameters(); 93 | // filterStreamParameters.addLocation(west, south, east, north); 94 | //Stream userStream = twitter.streamingOperations().filter(filterStreamParameters, listeners); 95 | 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/rawsanj/tweet/util/Description.java: -------------------------------------------------------------------------------- 1 | 2 | package com.rawsanj.tweet.util; 3 | 4 | import java.util.ArrayList; 5 | import java.util.HashMap; 6 | import java.util.List; 7 | import java.util.Map; 8 | import javax.annotation.Generated; 9 | import com.fasterxml.jackson.annotation.JsonAnyGetter; 10 | import com.fasterxml.jackson.annotation.JsonAnySetter; 11 | import com.fasterxml.jackson.annotation.JsonIgnore; 12 | import com.fasterxml.jackson.annotation.JsonInclude; 13 | import com.fasterxml.jackson.annotation.JsonProperty; 14 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 15 | 16 | @JsonInclude(JsonInclude.Include.NON_NULL) 17 | @Generated("org.jsonschema2pojo") 18 | @JsonPropertyOrder({ 19 | "urls" 20 | }) 21 | public class Description { 22 | 23 | @JsonProperty("urls") 24 | private List urls = new ArrayList(); 25 | @JsonIgnore 26 | private Map additionalProperties = new HashMap(); 27 | 28 | /** 29 | * 30 | * @return 31 | * The urls 32 | */ 33 | @JsonProperty("urls") 34 | public List getUrls() { 35 | return urls; 36 | } 37 | 38 | /** 39 | * 40 | * @param urls 41 | * The urls 42 | */ 43 | @JsonProperty("urls") 44 | public void setUrls(List urls) { 45 | this.urls = urls; 46 | } 47 | 48 | @JsonAnyGetter 49 | public Map getAdditionalProperties() { 50 | return this.additionalProperties; 51 | } 52 | 53 | @JsonAnySetter 54 | public void setAdditionalProperty(String name, Object value) { 55 | this.additionalProperties.put(name, value); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/com/rawsanj/tweet/util/Entities.java: -------------------------------------------------------------------------------- 1 | 2 | package com.rawsanj.tweet.util; 3 | 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | import javax.annotation.Generated; 7 | import com.fasterxml.jackson.annotation.JsonAnyGetter; 8 | import com.fasterxml.jackson.annotation.JsonAnySetter; 9 | import com.fasterxml.jackson.annotation.JsonIgnore; 10 | import com.fasterxml.jackson.annotation.JsonInclude; 11 | import com.fasterxml.jackson.annotation.JsonProperty; 12 | import com.fasterxml.jackson.annotation.JsonPropertyOrder; 13 | 14 | @JsonInclude(JsonInclude.Include.NON_NULL) 15 | @Generated("org.jsonschema2pojo") 16 | @JsonPropertyOrder({ 17 | "description" 18 | }) 19 | public class Entities { 20 | 21 | @JsonProperty("description") 22 | private Description description; 23 | @JsonIgnore 24 | private Map additionalProperties = new HashMap(); 25 | 26 | /** 27 | * 28 | * @return 29 | * The description 30 | */ 31 | @JsonProperty("description") 32 | public Description getDescription() { 33 | return description; 34 | } 35 | 36 | /** 37 | * 38 | * @param description 39 | * The description 40 | */ 41 | @JsonProperty("description") 42 | public void setDescription(Description description) { 43 | this.description = description; 44 | } 45 | 46 | @JsonAnyGetter 47 | public Map getAdditionalProperties() { 48 | return this.additionalProperties; 49 | } 50 | 51 | @JsonAnySetter 52 | public void setAdditionalProperty(String name, Object value) { 53 | this.additionalProperties.put(name, value); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.social.twitter.appId={{put consumerKey here}} 2 | spring.social.twitter.appSecret={{put consumerSecret here}} 3 | 4 | twitter.access.token={{put accessToken here}} 5 | twitter.access.token.secret={{put accessTokenSecret here}} 6 | -------------------------------------------------------------------------------- /src/main/resources/static/app.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function () { 2 | 3 | var browserNotSupported = false; 4 | 5 | //Array to Store Streaming Tweet Locations 6 | var words = ["Word Cloud", "Live", "Tweets", "From", "Cities/Countries", "Streaming", "Every 5 Secs", "Word Cloud", "Live", "Tweets", "From", "Cities/Countries", "Streaming", "Every 5 Secs", "Project Author", "@RawSanj"]; 7 | 8 | //Array to Store Streaming HashTags 9 | var hashTagsArr = ["Live Streaming #HashTags using SssEmitter", "Streaming in Every 2 Seconds", 10 | "#Hashing from around the Globe", 11 | "Project Author - @RawSanj", 12 | "Fork this Project on Github", 13 | "#Spring-Twitter-Stream", 14 | "EventSource is not Supported in Internet Explorer, Please use Chrome", 15 | "Spring-Boot is #Awesome"]; 16 | 17 | var hashTagsAndProfile = []; 18 | 19 | var height = $(window).height(), width = $("#chart").width(); 20 | 21 | try { 22 | var streamLocation = new EventSource('/tweetLocation'); 23 | 24 | streamLocation.addEventListener('streamLocation', function (event) { 25 | 26 | var location = event.data; 27 | 28 | if (location != "") { 29 | words.push(location); 30 | } 31 | ; 32 | 33 | }); 34 | 35 | streamLocation.addEventListener('streamHashtags', function (event) { 36 | 37 | var hashtags = event.data; 38 | //console.log(hashtags); 39 | if (hashtags !== '' && hashtags !== '""') { 40 | hashTagsArr.push(hashtags); 41 | } 42 | ; 43 | 44 | }); 45 | } 46 | catch (err) { 47 | words = ["Project Author", "@RawSanj", "Project Author", "@RawSanj", "Internet Explorer", "Does not Support EventSource", "Please use Chrome!", "Please use Chrome!", "IE Support coming up", "in WebSocket version"]; 48 | browserNotSupported = true; 49 | } 50 | 51 | $("#hashTags").height(height).width($("#text-container").width()); 52 | 53 | var lastClassindex = 0; 54 | showNewHashTags(); 55 | 56 | function showNewHashTags() { 57 | 58 | //Put Project Info in beetween hashtags 59 | if (Math.floor(Math.random() * 20) === 10) { 60 | var dispInfo = ["Project Author - @RawSanj", 61 | "Fork this Project on Github", 62 | "#Spring-Twitter-Stream"]; 63 | hashTagsArr = hashTagsArr.concat(dispInfo); 64 | } 65 | 66 | for (var i = 0; i < hashTagsArr.length; i++) { 67 | 68 | $("#hashTags").append("

" 69 | + hashTagsArr[i] + "

"); 70 | lastClassindex++; 71 | 72 | } 73 | $('#hashTags').animate({scrollTop: $('#hashTags').prop("scrollHeight")}, 2000); 74 | lastClassindex = hashTagsArr.length; 75 | 76 | hashTagsArr = []; //Empty hashTag Array to free up array 77 | 78 | setTimeout(function () { 79 | showNewHashTags() 80 | }, 2000); 81 | 82 | } 83 | 84 | function returnTextClass(index) { 85 | var cssClass = ["success", "info", "warning", "danger", "primary"]; 86 | var ind = index % 5; 87 | return cssClass[ind]; 88 | } 89 | 90 | 91 | //..........Code for Word Cloud............ 92 | 93 | //Store Compressed Data 94 | var compressedWordArray = compressArray(words); 95 | 96 | // Encapsulate the word cloud functionality 97 | function wordCloud(selector) { 98 | 99 | var fill = d3.scale.category20(); 100 | 101 | //Construct the word cloud's SVG element 102 | var svg = d3.select(selector).append("svg") 103 | .attr("width", width) 104 | .attr("height", height) 105 | .append("g") 106 | .attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")"); 107 | 108 | //Draw the word cloud 109 | function draw(words) { 110 | var cloud = svg.selectAll("g text") 111 | .data(words, function (d) { 112 | return d.text; 113 | }) 114 | 115 | //Entering words 116 | cloud.enter() 117 | .append("text") 118 | .style("font-family", "Impact") 119 | .style("fill", function (d, i) { 120 | return fill(i); 121 | }) 122 | .attr("text-anchor", "middle") 123 | .attr('font-size', 1) 124 | .text(function (d) { 125 | return d.text; 126 | }); 127 | 128 | //Entering and existing words 129 | cloud 130 | .transition() 131 | .duration(600) 132 | .style("font-size", function (d) { 133 | return d.size + "px"; 134 | }) 135 | .attr("transform", function (d) { 136 | return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")"; 137 | }) 138 | .style("fill-opacity", 1); 139 | 140 | //Exiting words 141 | cloud.exit() 142 | .transition() 143 | .duration(200) 144 | .style('fill-opacity', 1e-6) 145 | .attr('font-size', 1) 146 | .remove(); 147 | } 148 | 149 | function returnRotation() { 150 | var angle = [0, -90, -60, -45, -30, 0, 30, 45, 60, 90]; 151 | var index = Math.floor(Math.random() * 10); 152 | return angle[index]; 153 | } 154 | 155 | //Use the module pattern to encapsulate the visualisation code. We'll 156 | // expose only the parts that need to be public. 157 | return { 158 | 159 | //Recompute the word cloud for a new set of words. This method will 160 | // asycnhronously call draw when the layout has been computed. 161 | //The outside world will need to call this function, so make it part 162 | // of the wordCloud return value. 163 | update: function (words) { 164 | 165 | var maxSize = d3.max(compressedWordArray, function (d) { 166 | return d.size 167 | }); 168 | //Define Pixel of Text 169 | var pixScale = d3.scale.linear() 170 | .domain([0, maxSize]) 171 | .range([10, 80]); 172 | 173 | d3.layout.cloud().size([(width - 50), (height - 20)]) 174 | .words(words) 175 | .padding(5) 176 | .rotate(function () { 177 | return ~~(Math.random() * 2) * returnRotation(); 178 | }) 179 | .font("Impact") 180 | .fontSize(function (d) { 181 | return Math.floor(pixScale(d.size)); 182 | }) 183 | .on("end", draw) 184 | .start(); 185 | } 186 | } 187 | 188 | } 189 | 190 | //This method tells the word cloud to redraw with a new set of words. 191 | //In reality the new words would probably come from a server request, 192 | // user input or some other source. 193 | function showNewWords(vis) { 194 | 195 | if (browserNotSupported) { 196 | words = ["Project Author", "@RawSanj", "Project Author", "@RawSanj", "Internet Explorer", "Does not Support EventSource", "Please use Chrome!", "Please use Chrome!", "IE Support coming up", "in WebSocket version"]; 197 | } else if (words.length === 0) { 198 | words = ["Whoops!", "Looks Like", "Nobody", "Tweeted", "In Last 5 Seconds"]; 199 | } 200 | 201 | compressedWordArray = compressArray(words); 202 | 203 | vis.update(compressedWordArray); 204 | words = []; //Empty Word Array to free up array 205 | 206 | setTimeout(function () { 207 | showNewWords(vis) 208 | }, 5000); 209 | 210 | } 211 | 212 | //Create a new instance of the word cloud visualisation. 213 | var myWordCloud = wordCloud('body'); 214 | 215 | //Start cycling through the demo data 216 | showNewWords(myWordCloud); 217 | 218 | function compressArray(original) { 219 | 220 | var compressed = []; 221 | // make a copy of the input array 222 | var copy = original.slice(0); 223 | 224 | // first loop goes over every element 225 | for (var i = 0; i < original.length; i++) { 226 | 227 | var myCount = 0; 228 | // loop over every element in the copy and see if it's the same 229 | for (var w = 0; w < copy.length; w++) { 230 | if (original[i] == copy[w]) { 231 | // increase amount of times duplicate is found 232 | myCount++; 233 | // sets item to undefined 234 | delete copy[w]; 235 | } 236 | } 237 | 238 | if (myCount > 0) { 239 | var a = new Object(); 240 | a.text = original[i]; 241 | a.size = myCount; 242 | compressed.push(a); 243 | } 244 | } 245 | 246 | return compressed; 247 | }; 248 | }); -------------------------------------------------------------------------------- /src/main/resources/static/main.css: -------------------------------------------------------------------------------- 1 | #hashTags{ 2 | overflow-y:scroll; 3 | overflow-x: hidden; 4 | position: absolute; 5 | height: 100%; 6 | width: inherit; 7 | scrollbar-face-color: #367CD2; 8 | scrollbar-shadow-color: #FFFFFF; 9 | scrollbar-highlight-color: #FFFFFF; 10 | scrollbar-3dlight-color: #FFFFFF; 11 | scrollbar-darkshadow-color: #FFFFFF; 12 | scrollbar-track-color: #FFFFFF; 13 | scrollbar-arrow-color: #FFFFFF; 14 | } 15 | 16 | 17 | /* Let's get this party started */ 18 | #hashTags::-webkit-scrollbar { 19 | width: 10px; 20 | } 21 | 22 | /* Track */ 23 | #hashTags::-webkit-scrollbar-track { 24 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); 25 | -webkit-border-radius: 10px; 26 | border-radius: 10px; 27 | } 28 | 29 | /* Handle */ 30 | #hashTags::-webkit-scrollbar-thumb { 31 | -webkit-border-radius: 10px; 32 | border-radius: 10px; 33 | background: rgba(111, 169, 255, 0.8); 34 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.5); 35 | } 36 | 37 | @import url(https://fonts.googleapis.com/css?family=Roboto:300,400,500,700); 38 | @import url(https://raw.github.com/FortAwesome/Font-Awesome/master/docs/assets/css/font-awesome.min.css); 39 | 40 | body { 41 | background: rgba(221, 221, 221, 0.37); 42 | font-size: 15px; 43 | font-family: "Roboto", "Helvetica Neue", Helvetica, Arial, sans-serif; 44 | overflow:hidden; 45 | } 46 | 47 | #wrap { 48 | margin: 20px 40px; 49 | display: inline-block; 50 | position: fixed !important; 51 | height: 60px; 52 | float: left; 53 | padding: 0; 54 | z-index: 100; 55 | } 56 | 57 | ::-webkit-input-placeholder { /* WebKit, Blink, Edge */ 58 | color: #E51C23; 59 | } 60 | 61 | input[type="text"] { 62 | height: 67px; 63 | font-size: 55px; 64 | display: inline-block; 65 | font-weight: 100; 66 | border: none; 67 | outline: none; 68 | color: #E51C23; 69 | padding: 3px; 70 | padding-left: 80px; 71 | width: 0px; 72 | position: absolute; 73 | top: 0; 74 | left: 0; 75 | background: none; 76 | z-index: 3; 77 | transition: width .4s cubic-bezier(0.000, 0.795, 0.000, 1.000); 78 | cursor: pointer; 79 | } 80 | 81 | input[type="text"]:focus:hover { 82 | border-bottom: 1px solid #E51C23; 83 | } 84 | 85 | input[type="text"]:focus { 86 | width: 700px; 87 | z-index: 1; 88 | border-bottom: 1px solid #E51C23; 89 | cursor: text; 90 | background-color: rgba(255, 152, 0, 0.69); 91 | } 92 | input[type="submit"] { 93 | height: 67px; 94 | width: 63px; 95 | display: inline-block; 96 | color:red; 97 | float: left; 98 | background: url(data:image/png;base64,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) center center no-repeat; 99 | text-indent: -10000px; 100 | border: none; 101 | position: absolute; 102 | top: 0; 103 | left: 0; 104 | z-index: 2; 105 | cursor: pointer; 106 | opacity: 0.6; 107 | cursor: pointer; 108 | transition: opacity .4s ease; 109 | } 110 | 111 | input[type="submit"]:hover { 112 | opacity: 0.9; 113 | } 114 | 115 | .container-fluid{ 116 | padding-left: 0px !important; 117 | padding-right: 0px !important; 118 | } -------------------------------------------------------------------------------- /src/main/resources/templates/events.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Twitter Stream 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 | -------------------------------------------------------------------------------- /src/main/resources/templates/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello Twitter 4 | 5 | 6 | 123 | 124 | 125 | 126 |
127 | 128 |
129 | 130 |
131 |
132 |
133 | 134 | 135 |
136 |
137 |
138 |

139 | 140 | Searched Results for: 141 | 142 | keyWord 143 |

144 |
145 | 146 |
147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 |
Sr. NoTweetTweeted ByFromTweeted At
1 Tweet User Name From Tweeted At
163 |
164 |
165 |
166 |
167 | 176 | 177 | --------------------------------------------------------------------------------