summaryrefslogtreecommitdiff
path: root/src/mining/TwitterApi.java
blob: d145a86eddffa514b2ed0d54c8a11cecf706420a (plain)
1
2
3
4
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
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<NameValuePair> 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());
        }
    }
}