de.jetwick.tw.TwitterSearch.java Source code

Java tutorial

Introduction

Here is the source code for de.jetwick.tw.TwitterSearch.java

Source

/**
 * Copyright (C) 2010 Peter Karich <jetwick_@_pannous_._info>
 *
 * 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 de.jetwick.tw;

import de.jetwick.util.AnyExecutor;
import de.jetwick.data.JTweet;
import de.jetwick.data.JUser;
import de.jetwick.util.Helper;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import twitter4j.FilterQuery;
import twitter4j.IDs;
import twitter4j.Paging;
import twitter4j.Query;
import twitter4j.QueryResult;
import twitter4j.RateLimitStatus;
import twitter4j.ResponseList;
import twitter4j.Status;
import twitter4j.StatusDeletionNotice;
import twitter4j.StatusListener;
import twitter4j.Trend;
import twitter4j.Tweet;
import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.TwitterFactory;
import twitter4j.User;
import twitter4j.auth.AccessToken;
import twitter4j.auth.RequestToken;
import twitter4j.TwitterStream;
import twitter4j.TwitterStreamFactory;

/**
 * @author Peter Karich, peat_hal 'at' users 'dot' sourceforge 'dot' net
 */
public class TwitterSearch implements Serializable {

    private static final long serialVersionUID = 1L;
    public final static String COOKIE = "jetslide";
    /**
     * Do not use less than this limit of 20 api points for queueing searches of
     * unloggedin users
     */
    public final static int LIMIT = 50;
    public final static String LINK_FILTER = "filter:links";
    private Twitter twitter;
    protected Logger logger = LoggerFactory.getLogger(TwitterSearch.class);
    private String consumerKey;
    private String consumerSecret;
    private int rateLimit = -1;

    public TwitterSearch() {
    }

    public TwitterSearch setConsumer(String consumerKey, String consumerSecrect) {
        this.consumerKey = consumerKey;
        this.consumerSecret = consumerSecrect;
        return this;
    }

    public String getConsumerKey() {
        return consumerKey;
    }

    public String getConsumerSecret() {
        return consumerSecret;
    }

    /**
     * Connect with twitter to get a new personalized twitter4j instance.
     *
     * @throws RuntimeException if verification or connecting failed
     */
    public TwitterSearch initTwitter4JInstance(String token, String tokenSecret, boolean verify) {
        if (consumerKey == null)
            throw new NullPointerException("Please use init consumer settings!");

        setupProperties();
        AccessToken aToken = new AccessToken(token, tokenSecret);
        twitter = new TwitterFactory().getInstance();
        twitter.setOAuthConsumer(consumerKey, consumerSecret);
        twitter.setOAuthAccessToken(aToken);
        try {
            //            RequestToken requestToken = t.getOAuthRequestToken();
            //            System.out.println("TW-URL:" + requestToken.getAuthorizationURL());
            if (verify)
                twitter.verifyCredentials();

            String str = "<user>";
            try {
                str = twitter.getScreenName();
            } catch (Exception ex) {
            }
            logger.info("create new TwitterSearch for " + str + " with verifification:" + verify);
        } catch (TwitterException ex) {
            // rate limit only exceeded
            if (ex.getStatusCode() == 400)
                return this;

            throw new RuntimeException(ex);
        }
        return this;
    }

    /**
     * Set an already 'connected' twitter4j instance. No exception can be
     * thrown.
     */
    public TwitterSearch setTwitter4JInstance(Twitter tw) {
        twitter = tw;
        return this;
    }

    public Twitter getTwitter4JInstance() {
        return twitter;
    }

    private void setupProperties() {
        // this issue should now be resolved:
        // http://groups.google.com/group/twitter4j/browse_thread/thread/6f6d5b35149e2faa
        //        System.setProperty("twitter4j.http.useSSL", "false");

        // friends makes problems
        // http://groups.google.com/group/twitter4j/browse_thread/thread/f696de22d4554143
        // http://groups.google.com/group/twitter-development-talk/browse_thread/thread/cd76f954957f6fb0
        // http://groups.google.com/group/twitter-development-talk/browse_thread/thread/9e9bfec2f076e4f9
        //System.setProperty("twitter4j.http.useSSL", "true");

        // changing some properties to be applied on HttpURLConnection
        // default read timeout 120000 see twitter4j.internal.http.HttpClientImpl
        System.setProperty("twitter4j.http.readTimeout", "10000");

        // default connection time out 20000
        System.setProperty("twitter4j.http.connectionTimeout", "10000");
    }

    /**
     * Opening the url will show you a PIN
     *
     * @throws TwitterException
     */
    public RequestToken doDesktopLogin() throws TwitterException {
        twitter = new TwitterFactory().getInstance();
        twitter.setOAuthConsumer(consumerKey, consumerSecret);
        RequestToken requestToken = twitter.getOAuthRequestToken("");
        System.out.println("Open the following URL and grant access to your account:");
        System.out.println(requestToken.getAuthorizationURL());
        return requestToken;

    }

    public AccessToken getToken4Desktop(RequestToken requestToken, String pin) throws TwitterException {
        AccessToken at = twitter.getOAuthAccessToken(requestToken, pin);
        System.out.println("token:" + at.getToken() + " secret:" + at.getTokenSecret());
        return at;
    }

    private RequestToken tmpRequestToken;

    /**
     * @return the url where the user should be redirected to
     */
    public String oAuthLogin(String callbackUrl) throws Exception {
        twitter = new TwitterFactory().getInstance();
        twitter.setOAuthConsumer(consumerKey, consumerSecret);
        tmpRequestToken = twitter.getOAuthRequestToken(callbackUrl);
        return tmpRequestToken.getAuthenticationURL();
    }

    /**
     * grab oauth_verifier from request of callback site
     *
     * @return screenname or null
     */
    public AccessToken oAuthOnCallBack(String oauth_verifierParameter) throws TwitterException {
        if (tmpRequestToken == null)
            throw new IllegalStateException("RequestToken is empty. Call oAuthLogin before!");

        AccessToken aToken = twitter.getOAuthAccessToken(tmpRequestToken, oauth_verifierParameter);
        twitter.verifyCredentials();
        tmpRequestToken = null;
        return aToken;
    }

    public String getScreenName() {
        try {
            return twitter.getScreenName();
        } catch (Exception ex) {
            return null;
        }
    }

    public JUser getUser() throws TwitterException {
        return getUser(twitter.getScreenName());
    }

    public JUser getUser(String screenName) throws TwitterException {
        JUser user = new JUser(screenName);
        updateUserInfo(Arrays.asList(user));
        return user;
    }

    public User getTwitterUser() throws TwitterException {
        ResponseList list = twitter.lookupUsers(new String[] { twitter.getScreenName() });
        rateLimit--;
        if (list.size() == 0)
            return null;
        else if (list.size() == 1)
            return (User) list.get(0);
        else
            throw new IllegalStateException(
                    "returned more than one user for screen name:" + twitter.getScreenName());
    }

    public int getSecondsUntilReset() {
        try {
            RateLimitStatus rls = twitter.getRateLimitStatus();
            rateLimit = rls.getRemainingHits();
            return rls.getSecondsUntilReset();
        } catch (TwitterException ex) {
            logger.error("Cannot determine rate limit:" + ex.getMessage());
            return -1;
        }
    }

    /**
     * Check with this method otherwise you'll get TwitterException
     */
    public int getRateLimit() {
        try {
            rateLimit = twitter.getRateLimitStatus().getRemainingHits();
            return rateLimit;
        } catch (TwitterException ex) {
            logger.error("Cannot determine rate limit", ex);
            return -1;
        }
    }

    public int getRateLimitFromCache() {
        if (twitter == null)
            return -1;

        try {
            if (rateLimit < 0)
                rateLimit = twitter.getRateLimitStatus().getRemainingHits();
        } catch (TwitterException ex) {
            rateLimit = -1;
        }

        return rateLimit;
    }

    /**
     * forces correct rate limit for next getRateLimitFromCache
     */
    public void resetRateLimitCache() {
        rateLimit = -1;
    }

    public Status getTweet(long id) throws TwitterException {
        Status st = twitter.showStatus(id);
        rateLimit--;
        return st;
    }

    public void getTest() throws TwitterException {
        System.out.println(twitter.getFollowersIDs("dzone", 0).getIDs());
        System.out.println(twitter.getFriendsStatuses("dzone", 0));
        rateLimit--;
        rateLimit--;
    }

    // this works:
    // curl -u user:pw http://api.twitter.com/1/statuses/13221113653/retweeted_by.xml => Peter
    // curl -u user:pw http://api.twitter.com/1/statuses/13221113653/retweeted_by/ids.xml => 51798603 (my user id)
    List<Status> getRetweets(long id) {
        return Collections.EMPTY_LIST;
        //        try {
        //            return twitter.getRetweets(id);
        //        } catch (TwitterException ex) {
        //            throw new RuntimeException(ex);
        //        }
    }

    private long lastAccess = 0;

    public List<Tweet> getSomeTweets() {
        if ((System.currentTimeMillis() - lastAccess) < 50 * 1000) {
            logger.info("skipping public timeline");
            return Collections.emptyList();
        }

        lastAccess = System.currentTimeMillis();
        List<Tweet> res = new ArrayList<Tweet>();
        try {
            ResponseList statusList = twitter.getPublicTimeline();
            rateLimit--;
            for (Object st : statusList) {
                res.add(toTweet((Status) st));
            }
            return res;
        } catch (TwitterException ex) {
            logger.error("Cannot get trends!", ex);
            return res;
        }
    }

    public static Twitter4JTweet toTweet(Status st) {
        return toTweet(st, st.getUser());
    }

    public static Twitter4JTweet toTweet(Status st, User user) {
        if (user == null)
            throw new IllegalArgumentException("User mustn't be null!");
        if (st == null)
            throw new IllegalArgumentException("Status mustn't be null!");

        Twitter4JTweet tw = new Twitter4JTweet(st.getId(), st.getText(), user.getScreenName());
        tw.setCreatedAt(st.getCreatedAt());
        tw.setFromUser(user.getScreenName());

        if (user.getProfileImageURL() != null)
            tw.setProfileImageUrl(user.getProfileImageURL().toString());

        tw.setSource(st.getSource());
        tw.setToUser(st.getInReplyToUserId(), st.getInReplyToScreenName());
        tw.setInReplyToStatusId(st.getInReplyToStatusId());

        if (st.getGeoLocation() != null) {
            tw.setGeoLocation(st.getGeoLocation());
            tw.setLocation(st.getGeoLocation().getLatitude() + ", " + st.getGeoLocation().getLongitude());
        } else if (st.getPlace() != null)
            tw.setLocation(st.getPlace().getCountryCode());
        else if (user.getLocation() != null)
            tw.setLocation(toStandardLocation(user.getLocation()));

        return tw;
    }

    public static String toStandardLocation(String loc) {
        if (loc == null || loc.trim().length() == 0)
            return null;

        String[] locs;
        if (loc.contains("/"))
            locs = loc.split("/", 2);
        else if (loc.contains(","))
            locs = loc.split(",", 2);
        else
            locs = new String[] { loc };

        if (locs.length == 2)
            return locs[0].replaceAll("[,/]", " ").replaceAll("  ", " ").trim() + ", "
                    + locs[1].replaceAll("[,/]", " ").replaceAll("  ", " ").trim();
        else
            return locs[0].replaceAll("[,/]", " ").replaceAll("  ", " ").trim() + ", -";
    }

    Query createQuery(String str) {
        Query q = new Query(str);
        q.setResultType(Query.RECENT);
        return q;
    }

    // Twitter Search API:
    // Returns up to a max of roughly 1500 results
    // Rate limited by IP address.
    // The specific number of requests a client is able to make to the Search API for a given hour is not released.
    // The number is quite a bit higher and we feel it is both liberal and sufficient for most applications.
    // The since_id parameter will be removed from the next_page element as it is not supported for pagination.
    public long search(String term, Collection<JTweet> result, int tweets, long lastMaxCreateTime)
            throws TwitterException {
        Map<String, JUser> userMap = new LinkedHashMap<String, JUser>();
        return search(term, result, userMap, tweets, lastMaxCreateTime);
    }

    long search(String term, Collection<JTweet> result, Map<String, JUser> userMap, int tweets,
            long lastMaxCreateTime) throws TwitterException {
        long maxId = 0L;
        long maxMillis = 0L;
        int hitsPerPage;
        int maxPages;
        if (tweets < 100) {
            hitsPerPage = tweets;
            maxPages = 1;
        } else {
            hitsPerPage = 100;
            maxPages = tweets / hitsPerPage;
            if (tweets % hitsPerPage > 0)
                maxPages++;
        }

        boolean breakPaging = false;
        for (int page = 0; page < maxPages; page++) {
            Query query = new Query(term);
            // RECENT or POPULAR
            query.setResultType(Query.MIXED);

            // avoid that more recent results disturb our paging!
            if (page > 0)
                query.setMaxId(maxId);

            query.setPage(page + 1);
            query.setRpp(hitsPerPage);
            QueryResult res = twitter.search(query);

            // is res.getTweets() sorted?
            for (Object o : res.getTweets()) {
                Tweet twe = (Tweet) o;
                // determine maxId in the first page
                if (page == 0 && maxId < twe.getId())
                    maxId = twe.getId();

                if (maxMillis < twe.getCreatedAt().getTime())
                    maxMillis = twe.getCreatedAt().getTime();

                if (twe.getCreatedAt().getTime() + 1000 < lastMaxCreateTime)
                    breakPaging = true;
                else {
                    String userName = twe.getFromUser().toLowerCase();
                    JUser user = userMap.get(userName);
                    if (user == null) {
                        user = new JUser(userName).init(twe);
                        userMap.put(userName, user);
                    }

                    result.add(new JTweet(twe, user));
                }
            }

            // minMillis could force us to leave earlier than defined by maxPages
            // or if resulting tweets are less then request (but -10 because of twitter strangeness)
            if (breakPaging || res.getTweets().size() < hitsPerPage - 10)
                break;
        }

        return maxMillis;
    }

    /**
     * @deprecated use the search method
     */
    public Collection<JTweet> searchAndGetUsers(String term, Collection<JUser> result, int tweets, int maxPage)
            throws TwitterException {
        Set<JTweet> solrTweets = new LinkedHashSet<JTweet>();
        Map<String, JUser> userMap = new LinkedHashMap<String, JUser>();
        result.addAll(userMap.values());
        return solrTweets;
    }

    /**
     * API COSTS: 1
     *
     * @param users should be maximal 100 users
     * @return the latest tweets of the users
     */
    public Collection<? extends Tweet> updateUserInfo(List<? extends JUser> users) {
        int counter = 0;
        String arr[] = new String[users.size()];

        // responseList of twitter.lookup has not the same order as arr has!!
        Map<String, JUser> userMap = new LinkedHashMap<String, JUser>();

        for (JUser u : users) {
            arr[counter++] = u.getScreenName();
            userMap.put(u.getScreenName(), u);
        }

        int maxRetries = 5;
        for (int retry = 0; retry < maxRetries; retry++) {
            try {
                ResponseList res = twitter.lookupUsers(arr);
                rateLimit--;
                List<Tweet> tweets = new ArrayList<Tweet>();
                for (int ii = 0; ii < res.size(); ii++) {
                    User user = (User) res.get(ii);
                    JUser yUser = userMap.get(user.getScreenName().toLowerCase());
                    if (yUser == null)
                        continue;

                    Status stat = yUser.updateFieldsBy(user);
                    if (stat == null)
                        continue;

                    Twitter4JTweet tw = toTweet(stat, user);
                    tweets.add(tw);
                }
                return tweets;
            } catch (TwitterException ex) {
                logger.warn("Couldn't lookup users. Retry:" + retry + " of " + maxRetries, ex);
                if (retry < 1)
                    continue;
                else
                    break;
            }
        }

        return Collections.EMPTY_LIST;
    }

    public List<JTweet> getTweets(JUser user, Collection<JUser> users, int twPerPage) throws TwitterException {
        Map<String, JUser> map = new LinkedHashMap<String, JUser>();
        List<JTweet> userTweets = getTweets(user, twPerPage);
        users.addAll(map.values());
        return userTweets;
    }

    // http://apiwiki.twitter.com/Twitter-REST-API-Method:-statuses-user_timeline
    // -> without RETWEETS!? => count can be smaller than the requested!
    //    public List<SolrTweet> getTweets(String userScreenName) throws TwitterException {
    //        if (getRateLimit() == 0) {
    //            logger.error("No API calls available");
    //            return Collections.EMPTY_LIST;
    //        }
    //        return getTweets(userScreenName, 100);
    //    }
    /**
     * You will only be able to access the latest 3200 statuses from a user's
     * timeline
     */
    List<JTweet> getTweets(JUser user, int tweets) throws TwitterException {
        List<JTweet> res = new ArrayList<JTweet>();
        int p = 0;
        int pages = 1;

        for (; p < pages; p++) {
            res.addAll(getTweets(user, p * tweets, tweets));
        }

        return res;
    }

    public List<JTweet> getTweets(JUser user, int start, int tweets) throws TwitterException {
        List<JTweet> res = new ArrayList<JTweet>();
        int currentPage = start / tweets;

        if (tweets > 100)
            throw new IllegalStateException("Twitter does not allow more than 100 tweets per page!");

        if (tweets == 0)
            throw new IllegalStateException("tweets should be positive!");

        for (int trial = 0; trial < 2; trial++) {
            try {
                ResponseList rList = twitter.getUserTimeline(user.getScreenName(),
                        new Paging(currentPage + 1, tweets, 1));
                rateLimit--;
                for (Object st : rList) {
                    Tweet tw = toTweet((Status) st);
                    res.add(new JTweet(tw, user.init(tw)));
                }
                break;
            } catch (TwitterException ex) {
                logger.warn("Exception while getTweets. trial:" + trial + " page:" + currentPage + " - "
                        + Helper.getMsg(ex));
                if (ex.exceededRateLimitation())
                    return res;

                continue;
            }
        }

        return res;
    }

    /**
     * The last 200 tweets will be retrieved
     */
    public Collection<Tweet> getHomeTimeline(int tweets) throws TwitterException {
        ArrayList list = new ArrayList<Tweet>();
        getHomeTimeline(list, tweets, 0);
        return list;
    }

    /**
     * This method only returns up to 800 statuses, including retweets.
     */
    public long getHomeTimeline(Collection<JTweet> result, int tweets, long lastId) throws TwitterException {
        if (lastId <= 0)
            lastId = 1;

        Map<String, JUser> userMap = new LinkedHashMap<String, JUser>();
        int hitsPerPage = 100;
        long maxId = lastId;
        long sinceId = lastId;
        int maxPages = tweets / hitsPerPage + 1;

        END_PAGINATION: for (int page = 0; page < maxPages; page++) {
            Paging paging = new Paging(page + 1, tweets, sinceId);
            // avoid that more recent results disturb our paging!
            if (page > 0)
                paging.setMaxId(maxId);

            Collection<Status> tmp = twitter.getHomeTimeline(paging);
            rateLimit--;
            for (Status st : tmp) {
                // determine maxId in the first page
                if (page == 0 && maxId < st.getId())
                    maxId = st.getId();

                if (st.getId() < sinceId)
                    break END_PAGINATION;

                Tweet tw = toTweet(st);
                String userName = tw.getFromUser().toLowerCase();
                JUser user = userMap.get(userName);
                if (user == null) {
                    user = new JUser(st.getUser()).init(tw);
                    userMap.put(userName, user);
                }

                result.add(new JTweet(tw, user));
            }

            // sinceId could force us to leave earlier than defined by maxPages
            if (tmp.size() < hitsPerPage)
                break;
        }

        return maxId;
    }

    public TwitterStream streamingTwitter(Collection<String> track, final Queue<JTweet> queue)
            throws TwitterException {
        String[] trackArray = track.toArray(new String[track.size()]);
        TwitterStream stream = new TwitterStreamFactory().getInstance(twitter.getAuthorization());
        stream.addListener(new StatusListener() {

            @Override
            public void onStatus(Status status) {
                // ugly twitter ...
                if (Helper.isEmpty(status.getUser().getScreenName()))
                    return;

                if (!queue.offer(new JTweet(toTweet(status), new JUser(status.getUser()))))
                    logger.error("Cannot add tweet as input queue for streaming is full:" + queue.size());
            }

            @Override
            public void onDeletionNotice(StatusDeletionNotice statusDeletionNotice) {
                logger.error("We do not support onDeletionNotice at the moment! Tweet id: "
                        + statusDeletionNotice.getStatusId());
            }

            @Override
            public void onTrackLimitationNotice(int numberOfLimitedStatuses) {
                logger.warn("onTrackLimitationNotice:" + numberOfLimitedStatuses);
            }

            @Override
            public void onException(Exception ex) {
                logger.error("onException", ex);
            }

            @Override
            public void onScrubGeo(long userId, long upToStatusId) {
            }
        });
        stream.filter(new FilterQuery(0, new long[0], trackArray));
        return stream;
    }

    public void getFollowers(String user, AnyExecutor<JUser> anyExecutor) {
        getFriendsOrFollowers(user, anyExecutor, false);
    }

    public void getFriends(String userName, AnyExecutor<JUser> executor) {
        getFriendsOrFollowers(userName, executor, true);
    }

    public void getFriendsOrFollowers(String userName, AnyExecutor<JUser> executor, boolean friends) {
        long cursor = -1;
        resetRateLimitCache();
        MAIN: while (true) {
            while (getRateLimitFromCache() < LIMIT) {
                int reset = getSecondsUntilReset();
                if (reset != 0) {
                    logger.info("no api points left while getFriendsOrFollowers! Skipping ...");
                    return;
                }
                resetRateLimitCache();
                myWait(0.5f);
            }

            ResponseList res = null;
            IDs ids = null;
            try {
                if (friends)
                    ids = twitter.getFriendsIDs(userName, cursor);
                else
                    ids = twitter.getFollowersIDs(userName, cursor);

                rateLimit--;
            } catch (TwitterException ex) {
                logger.warn(ex.getMessage());
                break;
            }
            if (ids.getIDs().length == 0)
                break;

            long[] intids = ids.getIDs();

            // split into max 100 batch            
            for (int offset = 0; offset < intids.length; offset += 100) {
                long[] limitedIds = new long[100];
                for (int ii = 0; ii + offset < intids.length && ii < limitedIds.length; ii++) {
                    limitedIds[ii] = intids[ii + offset];
                }

                // retry at max N times for every id bunch
                for (int i = 0; i < 5; i++) {
                    try {
                        res = twitter.lookupUsers(limitedIds);
                        rateLimit--;
                        for (Object o : res) {
                            User user = (User) o;
                            // strange that this was necessary for ibood
                            if (user.getScreenName().trim().isEmpty())
                                continue;

                            JUser jUser = new JUser(user);
                            if (executor.execute(jUser) == null)
                                break MAIN;
                        }
                        break;
                    } catch (Exception ex) {
                        ex.printStackTrace();
                        myWait(5);
                        continue;
                    }
                }

                if (res == null) {
                    logger.error("giving up");
                    break;
                }
            }

            if (!ids.hasNext())
                break;

            cursor = ids.getNextCursor();
        }
    }

    public Collection<JUser> getFriendsNotFollowing(String user) {
        final Set<JUser> tmpUsers = new LinkedHashSet<JUser>();
        AnyExecutor exec = new AnyExecutor<JUser>() {

            @Override
            public JUser execute(JUser o) {
                tmpUsers.add(o);
                return o;
            }
        };
        getFriendsOrFollowers(user, exec, true);

        // store friends (people who are followed from specified user)
        Set<JUser> friends = new LinkedHashSet<JUser>(tmpUsers);
        System.out.println("friends:" + friends.size());

        // store followers of specified user into tmpUsers
        tmpUsers.clear();
        getFriendsOrFollowers(user, exec, false);
        System.out.println("followers:" + tmpUsers.size());

        // now remove users from friends which already follow
        for (JUser u : tmpUsers) {
            friends.remove(u);
        }
        return friends;
    }

    public void unfollow(String user) {
        try {
            twitter.destroyFriendship(user);
        } catch (TwitterException ex) {
            throw new RuntimeException(ex);
        }
    }

    public void follow(JUser user) {
        try {
            twitter.createFriendship(user.getScreenName());
        } catch (TwitterException ex) {
            throw new RuntimeException(ex);
        }
    }

    public Status doRetweet(long twitterId) throws TwitterException {
        Status st = twitter.retweetStatus(twitterId);
        rateLimit--;
        return st;
    }

    private void myWait(float seconds) {
        try {
            Thread.sleep(Math.round(seconds * 1000));
        } catch (Exception ex) {
            throw new UnsupportedOperationException(ex);
        }
    }

    /**
     * @return a message describing the problem with twitter or an empty string
     * if nothing related to twitter!
     */
    public static String getMessage(Exception ex) {
        if (ex instanceof TwitterException) {
            TwitterException twExc = (TwitterException) ex;
            if (twExc.exceededRateLimitation())
                return ("Couldn't process your request. You don't have enough twitter API points!"
                        + " Please wait: " + twExc.getRetryAfter() + " seconds and try again!");
            else if (twExc.isCausedByNetworkIssue())
                return ("Couldn't process your request. Network issue.");
            else
                return ("Couldn't process your request. Something went wrong while communicating with Twitter :-/");
        }

        return "";
    }

    public boolean isInitialized() {
        return twitter != null;
    }

    public void sendDMTo(String screenName, String txt) throws TwitterException {
        twitter.sendDirectMessage(screenName, txt);
    }
}