package mining; import io.BearerRequester; import io.OAuthRequester; import io.RateLimitException; import io.Requester; import io.Response; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; import org.apache.commons.codec.Charsets; import org.apache.http.NameValuePair; import org.apache.http.client.utils.URLEncodedUtils; import org.apache.http.message.BasicNameValuePair; import support.ConsumerKeySecret; import support.OAuthAccessTokenSecret; import utils.Configuration; /** * Accessors to the Twitter API. */ public class TwitterApi { private static final String CFG_BEARER_TOKEN = "bearer-token"; private static final String CFG_OAUTH_TOKEN = "oauth-token"; private static final String CFG_OAUTH_SECRET = "oauth-secret"; private final Requester requester; public TwitterApi(Requester requester) { this.requester = requester; } /** * Establishes an instance using application-only authentication. * * @return An API context usable for requests using app-only auth. * @throws IOException */ public static TwitterApi getAppOnly() throws IOException { Configuration cfg = Configuration.getConfig(); String token; BearerRequester breq = null; token = cfg.getProperty(CFG_BEARER_TOKEN); if (token != null) { breq = new BearerRequester(token); if (!breq.isValid()) { Logger.getLogger(TwitterApi.class.getName()) .info("Bearer token invalid, trying a new one..."); breq = null; } } if (breq == null) { ConsumerKeySecret cks = getConsumerKeySecret(); breq = new BearerRequester(cks); cfg.setProperty(CFG_BEARER_TOKEN, breq.getAccessToken()); cfg.save(); } return new TwitterApi(breq); } /** * Establishes an instance using a user context (OAuth signing). * * @param ps A supplier of the PIN for a given URL. May be null if not * supported. * @return An API context usable for requests using OAuth. * @throws IOException if no usable context can be instantiated. */ public static TwitterApi getOAuth(PinSupplier ps) throws IOException { Configuration cfg = Configuration.getConfig(); OAuthAccessTokenSecret secrets = null; ConsumerKeySecret cks = getConsumerKeySecret(); OAuthRequester oreq = new OAuthRequester(cks); /* check if the stored access tokens are still valid */ { String token, secret; token = cfg.getProperty(CFG_OAUTH_TOKEN); secret = cfg.getProperty(CFG_OAUTH_SECRET); if (token != null && secret != null) { secrets = new OAuthAccessTokenSecret(token, secret); oreq.setAccessToken(secrets); if (!oreq.isValid()) { Logger.getLogger(TwitterApi.class.getName()) .info("OAuth access tokens invalid"); secrets = null; } } } /* if no valid secrets are available, request a new access token */ if (secrets == null) { if (ps == null) { throw new IOException("Unable to retrieve an access token"); } String authUrl = oreq.getAuthURL(); for (int i = 0; i < 5; ++i) { try { String pin = ps.requestPin(authUrl); // stop asking if an empty PIN was supplied if (!pin.isEmpty()) { oreq.supplyPINForTokens(pin); } break; } catch (IOException ex) { System.err.println("Trying again, PIN rejected: " + ex); } } secrets = oreq.getSecrets(); if (secrets == null) { throw new IOException("Unable to get access tokens"); } cfg.setProperty(CFG_OAUTH_TOKEN, secrets.getToken()); cfg.setProperty(CFG_OAUTH_SECRET, secrets.getSecret()); cfg.save(); } return new TwitterApi(oreq); } public interface PinSupplier { /** * Given a URL, the pin supplier must synchronously request a PIN. * * @param url The URL to be presented to the user. * @return A non-null string that can be exchanged for access tokens. * @throws IOException if the PIN could not be retrieved. */ public String requestPin(String url) throws IOException; } private static ConsumerKeySecret getConsumerKeySecret() { // static consumer keys retrieved from dev.twitter.com return new ConsumerKeySecret(Configuration.CONSUMER_KEY, Configuration.CONSUMER_SECRET); } /** * @param resource The resource to be requested. * @return A builder for an API request. */ public Builder build(String resource) { return new Builder(resource); } /** * @return The requester instance associated with this. */ public Requester getRequester() { return requester; } public class Builder { private final String resource; private final List params; public Builder(String resource) { this.resource = resource; this.params = new ArrayList<>(); } public Builder param(String key, String val) { params.add(new BasicNameValuePair(key, val)); return this; } @Override public String toString() { if (params.isEmpty()) { return this.resource; } return this.resource + "?" + URLEncodedUtils.format(params, Charsets.UTF_8); } public Response request() throws IOException, RateLimitException { return TwitterApi.this.requester.getJSON(toString()); } } }