/* * Copyright 2018 IBM Corp. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package com.ibm.watson.personality_insights.twitter; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; import java.util.Properties; import java.util.Set; import twitter4j.Paging; import twitter4j.RateLimitStatus; import twitter4j.RateLimitStatusEvent; import twitter4j.RateLimitStatusListener; import twitter4j.ResponseList; import twitter4j.Status; import twitter4j.Twitter; import twitter4j.TwitterFactory; import twitter4j.User; import twitter4j.conf.ConfigurationBuilder; /** * The Class Twitter4JHelper. */ public class Twitter4JHelper implements RateLimitStatusListener { private static final String CONSUMER_KEY = "twitter.consumerKey"; private static final String CONSUMER_SECRET = "twitter.consumerSecret"; private static final String ACCESS_TOKEN = "twitter.accessToken"; private static final String ACCESS_SECRET = "twitter.accessSecret"; private Twitter client = null; private boolean rateLimited = false; private long rateLimitResetTime = -1; /** * Instantiates a new twitter 4 J helper. * * @param properties the properties * @throws Exception the exception */ public Twitter4JHelper(Properties properties) throws Exception { String consumerKey = properties.getProperty(CONSUMER_KEY); String consumerSecret = properties.getProperty(CONSUMER_SECRET); String accessToken = properties.getProperty(ACCESS_TOKEN); String accessSecret = properties.getProperty(ACCESS_SECRET); // Validate that these are set and throw an error if they are not if (consumerKey == null || consumerKey.isEmpty()) { throw new Exception(CONSUMER_KEY + " cannot be null"); } if (consumerSecret == null || consumerSecret.isEmpty()) { throw new Exception(CONSUMER_SECRET + " cannot be null"); } if (accessToken == null || accessToken.isEmpty()) { throw new Exception(ACCESS_TOKEN + " cannot be null"); } if (accessSecret == null || accessSecret.isEmpty()) { throw new Exception(ACCESS_SECRET + " cannot be null"); } ConfigurationBuilder cb = new ConfigurationBuilder(); cb.setDebugEnabled(true) .setOAuthConsumerKey(consumerKey) .setOAuthConsumerSecret(consumerSecret) .setOAuthAccessToken(accessToken) .setOAuthAccessTokenSecret(accessSecret); TwitterFactory twitterFactory = new TwitterFactory(cb.build()); client = twitterFactory.getInstance(); client.addRateLimitStatusListener(this); } /** * Gets the user image. * * @param status the status * @return the user image */ public String getUserImage(Status status) { return status.getUser().getProfileImageURL(); } /** * Gets the tweets. * * @param idOrHandle the id or handle * @param langs the langs * @param numberOfNonRetweets the number of non retweets * @return the tweets * @throws Exception the exception */ public List<Status> getTweets(String idOrHandle, Set<String> langs, int numberOfNonRetweets) throws Exception { List<Status> retval = new ArrayList<Status>(); long userId = -1; if (idOrHandle.startsWith("@")) { // Check rate limit checkRateLimitAndThrow(); User user = client.showUser(idOrHandle.substring(1)); if (user == null) throw new Exception("Handle " + idOrHandle + " is not a valid twitter handle."); userId = user.getId(); } else { userId = Long.valueOf(idOrHandle); } long cursor = -1; Paging page = new Paging(1, 200); do { checkRateLimitAndThrow(); ResponseList<Status> tweets = client.getUserTimeline(userId, page); if (tweets == null || tweets.size() == 0) break; for (int i = 0; i < tweets.size(); i++) { Status status = tweets.get(i); cursor = status.getId() - 1; // Ignore retweets if (status.isRetweet()) continue; // Language if (!langs.contains(status.getLang())) continue; retval.add(status); if (retval.size() >= numberOfNonRetweets) return retval; } page.maxId(cursor); } while (true); return retval; } private synchronized void setRateLimitStatus(boolean rateLimitReached, long resetTime) { rateLimited = rateLimitReached; rateLimitResetTime = resetTime; } private synchronized boolean isRateLimited() { if (rateLimited && System.currentTimeMillis() > rateLimitResetTime) { rateLimited = false; rateLimitResetTime = -1; } return rateLimited; } private void checkRateLimitAndThrow() throws Exception { if (isRateLimited()) { SimpleDateFormat df2 = new SimpleDateFormat("HH:mm:ss"); String dateText = df2.format(rateLimitResetTime); throw new Exception("The twitter api rate limit has been hit. " + "No more requests will be sent until the rate limit resets at " + dateText); } } /** * On rate limit reached. * * @param rlStatusEvent the rl status event */ @Override public void onRateLimitReached(RateLimitStatusEvent rlStatusEvent) { RateLimitStatus rls = rlStatusEvent.getRateLimitStatus(); setRateLimitStatus(true, ((long) rls.getResetTimeInSeconds()) * 1000L); System.err .println("Twitter rate limit reached, stopping all requests for " + rls.getSecondsUntilReset() + " seconds"); } /** * On rate limit status. * * @param rlStatusEvent the rl status event */ @Override public void onRateLimitStatus(RateLimitStatusEvent rlStatusEvent) { @SuppressWarnings("unused") RateLimitStatus rls = rlStatusEvent.getRateLimitStatus(); } }