Skip to content

Commit 78aac50

Browse files
author
Maria Besfamilnaya
committed
add Instagram API (https://www.instagram.com/)
1 parent 02cf695 commit 78aac50

File tree

7 files changed

+389
-1
lines changed

7 files changed

+389
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ ScribeJava support out-of-box several HTTP clients:
7777
* HeadHunter ХэдХантер (https://hh.ru/) [example](https://github.com/scribejava/scribejava/blob/master/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/HHExample.java)
7878
* HiOrg-Server (https://www.hiorg-server.de/) [example](https://github.com/scribejava/scribejava/blob/master/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/HiOrgServerExample.java)
7979
* Imgur (http://imgur.com/) [example](https://github.com/scribejava/scribejava/blob/master/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/ImgurExample.java)
80+
* Instagram (https://www.instagram.com/) [example](https://github.com/scribejava/scribejava/blob/master/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/InstagramExample.java)
8081
* Kaixin 开心网 (http://www.kaixin001.com/) [example](https://github.com/scribejava/scribejava/blob/master/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/Kaixin20Example.java)
8182
* Kakao (https://kakao.com/) [example](https://github.com/scribejava/scribejava/blob/master/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/KakaoExample.java)
8283
* Keycloak (https://www.keycloak.org/) [example](https://github.com/scribejava/scribejava/blob/master/scribejava-apis/src/test/java/com/github/scribejava/apis/examples/KeycloakExample.java)
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.github.scribejava.apis;
2+
3+
import java.io.OutputStream;
4+
import com.github.scribejava.apis.instagram.InstagramAccessTokenJsonExtractor;
5+
import com.github.scribejava.apis.instagram.InstagramService;
6+
import com.github.scribejava.core.builder.api.DefaultApi20;
7+
import com.github.scribejava.core.extractors.TokenExtractor;
8+
import com.github.scribejava.core.httpclient.HttpClient;
9+
import com.github.scribejava.core.httpclient.HttpClientConfig;
10+
import com.github.scribejava.core.model.OAuth2AccessToken;
11+
import com.github.scribejava.core.model.Verb;
12+
import com.github.scribejava.core.oauth.OAuth20Service;
13+
import com.github.scribejava.core.oauth2.clientauthentication.ClientAuthentication;
14+
import com.github.scribejava.core.oauth2.clientauthentication.RequestBodyAuthenticationScheme;
15+
16+
/**
17+
* Instagram API
18+
*/
19+
public class InstagramApi extends DefaultApi20 {
20+
21+
private static class InstanceHolder {
22+
23+
private static final InstagramApi INSTANCE = new InstagramApi();
24+
}
25+
26+
public static InstagramApi instance() {
27+
return InstanceHolder.INSTANCE;
28+
}
29+
30+
@Override
31+
public Verb getAccessTokenVerb() {
32+
return Verb.POST;
33+
}
34+
35+
@Override
36+
public String getAccessTokenEndpoint() {
37+
return "https://api.instagram.com/oauth/access_token";
38+
}
39+
40+
@Override
41+
public String getRefreshTokenEndpoint() {
42+
return "https://graph.instagram.com/refresh_access_token";
43+
}
44+
45+
@Override
46+
protected String getAuthorizationBaseUrl() {
47+
return "https://api.instagram.com/oauth/authorize";
48+
}
49+
50+
@Override
51+
public TokenExtractor<OAuth2AccessToken> getAccessTokenExtractor() {
52+
return InstagramAccessTokenJsonExtractor.instance();
53+
}
54+
55+
@Override
56+
public ClientAuthentication getClientAuthentication() {
57+
return RequestBodyAuthenticationScheme.instance();
58+
}
59+
60+
@Override
61+
public OAuth20Service createService(String apiKey, String apiSecret, String callback, String defaultScope,
62+
String responseType, OutputStream debugStream, String userAgent, HttpClientConfig httpClientConfig,
63+
HttpClient httpClient) {
64+
return new InstagramService(this, apiKey, apiSecret, callback, defaultScope, responseType,
65+
debugStream, userAgent, httpClientConfig, httpClient);
66+
}
67+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package com.github.scribejava.apis.instagram;
2+
3+
import java.io.IOException;
4+
import java.util.Objects;
5+
import com.github.scribejava.core.exceptions.OAuthException;
6+
import com.github.scribejava.core.model.Response;
7+
8+
/**
9+
* non standard Instagram replace for
10+
* {@link com.github.scribejava.core.model.OAuth2AccessTokenErrorResponse}
11+
*
12+
* examples:<br>
13+
*
14+
* '{"error_type": "OAuthException", "code": 400, "error_message": "Missing required field client_id"}'
15+
*/
16+
public class InstagramAccessTokenErrorResponse extends OAuthException {
17+
18+
private static final long serialVersionUID = -1277129766099856895L;
19+
20+
private final String errorType;
21+
private final int codeInt;
22+
private final Response response;
23+
24+
public InstagramAccessTokenErrorResponse(String errorType, int code,
25+
String errorMessage, Response response) {
26+
super(errorMessage);
27+
this.errorType = errorType;
28+
this.codeInt = code;
29+
this.response = response;
30+
}
31+
32+
public String getErrorType() {
33+
return errorType;
34+
}
35+
36+
public int getCodeInt() {
37+
return codeInt;
38+
}
39+
40+
/**
41+
*
42+
* @return body of response
43+
* @throws IOException IOException
44+
* @deprecated use {@link #getResponse()} and then {@link Response#getBody()}
45+
*/
46+
@Deprecated
47+
public String getRawResponse() throws IOException {
48+
return response.getBody();
49+
}
50+
51+
public Response getResponse() {
52+
return response;
53+
}
54+
55+
@Override
56+
public boolean equals(Object o) {
57+
if (this == o) {
58+
return true;
59+
}
60+
if (o == null || getClass() != o.getClass()) {
61+
return false;
62+
}
63+
InstagramAccessTokenErrorResponse that = (InstagramAccessTokenErrorResponse) o;
64+
return codeInt == that.codeInt && Objects.equals(errorType, that.errorType)
65+
&& Objects.equals(response, that.response);
66+
}
67+
68+
@Override
69+
public int hashCode() {
70+
int hash = 5;
71+
hash = 83 * hash + Objects.hashCode(response);
72+
hash = 83 * hash + Objects.hashCode(getMessage());
73+
hash = 83 * hash + Objects.hashCode(errorType);
74+
hash = 83 * hash + Objects.hashCode(codeInt);
75+
return hash;
76+
}
77+
78+
@Override
79+
public String toString() {
80+
return "InstagramAccessTokenErrorResponse{" +
81+
"errorType='" + errorType + '\'' +
82+
", codeInt=" + codeInt +
83+
", response=" + response +
84+
'}';
85+
}
86+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package com.github.scribejava.apis.instagram;
2+
3+
import java.io.IOException;
4+
import com.fasterxml.jackson.databind.JsonNode;
5+
import com.github.scribejava.apis.facebook.FacebookAccessTokenJsonExtractor;
6+
import com.github.scribejava.core.extractors.OAuth2AccessTokenJsonExtractor;
7+
import com.github.scribejava.core.model.Response;
8+
9+
/**
10+
* non standard Facebook Extractor
11+
*/
12+
public class InstagramAccessTokenJsonExtractor extends OAuth2AccessTokenJsonExtractor {
13+
14+
protected InstagramAccessTokenJsonExtractor() {
15+
}
16+
17+
private static class InstanceHolder {
18+
19+
private static final InstagramAccessTokenJsonExtractor INSTANCE = new InstagramAccessTokenJsonExtractor();
20+
}
21+
22+
public static InstagramAccessTokenJsonExtractor instance() {
23+
return InstanceHolder.INSTANCE;
24+
}
25+
26+
/**
27+
* Non standard error message. Could be Instagram or Facebook specific.
28+
* Usually Instagram type is used for getting access tokens. Facebook type is used for
29+
* refreshing tokens.
30+
*
31+
* examples:<br>
32+
*
33+
* Instagram specific:
34+
* '{"error_type": "OAuthException", "code": 400, "error_message": "Missing required field client_id"}'
35+
*
36+
* Facebook specific:
37+
* '{"error":{"message":"Error validating application. Invalid application
38+
* ID.","type":"OAuthException","code":101,"fbtrace_id":"CvDR+X4WWIx"}}'
39+
*
40+
* @param response response
41+
*/
42+
@Override
43+
public void generateError(Response response) throws IOException {
44+
final JsonNode errorNode = OAuth2AccessTokenJsonExtractor.OBJECT_MAPPER
45+
.readTree(response.getBody());
46+
JsonNode error = errorNode.get("error");
47+
if (error != null) {
48+
FacebookAccessTokenJsonExtractor.instance().generateError(response);
49+
} else {
50+
throw new InstagramAccessTokenErrorResponse(
51+
errorNode.get("error_type").asText(),
52+
errorNode.get("code").asInt(),
53+
errorNode.get("error_message").asText(),
54+
response
55+
);
56+
}
57+
}
58+
59+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package com.github.scribejava.apis.instagram;
2+
3+
import java.io.IOException;
4+
import java.io.OutputStream;
5+
import java.util.concurrent.ExecutionException;
6+
import com.github.scribejava.core.builder.api.DefaultApi20;
7+
import com.github.scribejava.core.httpclient.HttpClient;
8+
import com.github.scribejava.core.httpclient.HttpClientConfig;
9+
import com.github.scribejava.core.model.OAuth2AccessToken;
10+
import com.github.scribejava.core.model.OAuthConstants;
11+
import com.github.scribejava.core.model.OAuthRequest;
12+
import com.github.scribejava.core.model.Verb;
13+
import com.github.scribejava.core.oauth.OAuth20Service;
14+
15+
public class InstagramService extends OAuth20Service {
16+
17+
private static final String LONG_LIVED_ACCESS_TOKEN_ENDPOINT = "https://graph.instagram.com/access_token";
18+
19+
public InstagramService(DefaultApi20 api, String apiKey, String apiSecret, String callback,
20+
String defaultScope, String responseType, OutputStream debugStream, String userAgent,
21+
HttpClientConfig httpClientConfig, HttpClient httpClient) {
22+
super(api, apiKey, apiSecret, callback, defaultScope, responseType, debugStream,
23+
userAgent, httpClientConfig, httpClient);
24+
}
25+
26+
/**
27+
* Refresh a long-lived Instagram User Access Token that is at least 24 hours old but has not expired.
28+
* Refreshed tokens are valid for 60 days from the date at which they are refreshed.
29+
*
30+
* @param accessToken long-lived access token
31+
* @param scope (not used)
32+
* @return refresh token request
33+
*/
34+
@Override
35+
protected OAuthRequest createRefreshTokenRequest(String accessToken, String scope) {
36+
if (accessToken == null || accessToken.isEmpty()) {
37+
throw new IllegalArgumentException("The refreshToken cannot be null or empty");
38+
}
39+
final OAuthRequest request = new OAuthRequest(Verb.GET, getApi().getRefreshTokenEndpoint());
40+
41+
request.addParameter(OAuthConstants.GRANT_TYPE, "ig_refresh_token");
42+
request.addParameter(OAuthConstants.ACCESS_TOKEN, accessToken);
43+
44+
logRequestWithParams("refresh token", request);
45+
46+
return request;
47+
}
48+
49+
/**
50+
* Get long-lived access token.
51+
*
52+
* Initial accessToken is valid for 1 hour so one can get long-lived access token.
53+
* Long-lived access token is valid for 60 days.
54+
*
55+
* @param accessToken short-lived access token
56+
* @return long-lived access token with filled expireIn and refreshToken
57+
*/
58+
public OAuth2AccessToken getLongLivedAccessToken(OAuth2AccessToken accessToken)
59+
throws InterruptedException, ExecutionException, IOException {
60+
String shortLivedAccessToken = accessToken.getAccessToken();
61+
OAuthRequest request = createLongLivedAccessTokenRequest(shortLivedAccessToken);
62+
return sendAccessTokenRequestSync(request);
63+
}
64+
65+
private OAuthRequest createLongLivedAccessTokenRequest(String shortLivedAccessToken) {
66+
final OAuthRequest request = new OAuthRequest(Verb.GET, LONG_LIVED_ACCESS_TOKEN_ENDPOINT);
67+
68+
getApi().getClientAuthentication().addClientAuthentication(request, getApiKey(), getApiSecret());
69+
70+
request.addParameter(OAuthConstants.GRANT_TYPE, "ig_exchange_token");
71+
request.addParameter(OAuthConstants.ACCESS_TOKEN, shortLivedAccessToken);
72+
73+
if (isDebug()) {
74+
log("created long-lived access token request with body params [%s], query string params [%s]",
75+
request.getBodyParams().asFormUrlEncodedString(),
76+
request.getQueryStringParams().asFormUrlEncodedString());
77+
}
78+
return request;
79+
}
80+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package com.github.scribejava.apis.examples;
2+
3+
import java.io.IOException;
4+
import java.util.Random;
5+
import java.util.Scanner;
6+
import java.util.concurrent.ExecutionException;
7+
import com.github.scribejava.apis.InstagramApi;
8+
import com.github.scribejava.apis.instagram.InstagramService;
9+
import com.github.scribejava.core.builder.ServiceBuilder;
10+
import com.github.scribejava.core.model.OAuth2AccessToken;
11+
import com.github.scribejava.core.model.OAuthRequest;
12+
import com.github.scribejava.core.model.Response;
13+
import com.github.scribejava.core.model.Verb;
14+
import com.github.scribejava.core.oauth.OAuth20Service;
15+
16+
public class InstagramExample {
17+
18+
private static final String NETWORK_NAME = "Instagram";
19+
private static final String PROTECTED_RESOURCE_URL =
20+
"https://graph.instagram.com/me/media?fields=id,caption,media_type,media_url,username";
21+
22+
private InstagramExample() {
23+
}
24+
25+
@SuppressWarnings("PMD.SystemPrintln")
26+
public static void main(String... args) throws IOException, InterruptedException, ExecutionException {
27+
// Replace these with your client id and secret
28+
final String clientId = "client-id";
29+
final String clientSecret = "client-secret";
30+
final String secretState = "secret" + new Random().nextInt(999_999);
31+
final OAuth20Service service = new ServiceBuilder(clientId)
32+
.apiSecret(clientSecret)
33+
.defaultScope("user_profile,user_media")
34+
.callback("http://example.com")
35+
.build(InstagramApi.instance());
36+
37+
final Scanner in = new Scanner(System.in, "UTF-8");
38+
39+
System.out.println("=== " + NETWORK_NAME + "'s OAuth Workflow ===");
40+
System.out.println();
41+
42+
// Obtain the Authorization URL
43+
System.out.println("Fetching the Authorization URL...");
44+
final String authorizationUrl = service.getAuthorizationUrl(secretState);
45+
System.out.println("Got the Authorization URL!");
46+
System.out.println("Now go and authorize ScribeJava here:");
47+
System.out.println(authorizationUrl);
48+
System.out.println("And paste the authorization code here");
49+
System.out.print(">>");
50+
final String code = in.nextLine();
51+
System.out.println();
52+
53+
System.out.println("And paste the state from server here. We have set 'secretState'='" + secretState + "'.");
54+
System.out.print(">>");
55+
final String value = in.nextLine();
56+
if (secretState.equals(value)) {
57+
System.out.println("State value does match!");
58+
} else {
59+
System.out.println("Ooops, state value does not match!");
60+
System.out.println("Expected = " + secretState);
61+
System.out.println("Got = " + value);
62+
System.out.println();
63+
}
64+
65+
System.out.println("Trading the Authorization Code for an Access Token...");
66+
final OAuth2AccessToken accessToken = service.getAccessToken(code);
67+
System.out.println("Got the Access Token!");
68+
System.out.println("(The raw response looks like this: " + accessToken.getRawResponse() + "')");
69+
System.out.println();
70+
71+
// Now let's go and ask for a protected resource!
72+
System.out.println("Now we're going to access a protected resource...");
73+
final OAuthRequest request = new OAuthRequest(Verb.GET, PROTECTED_RESOURCE_URL);
74+
service.signRequest(accessToken, request);
75+
try (Response response = service.execute(request)) {
76+
System.out.println("Got it! Lets see what we found...");
77+
System.out.println();
78+
System.out.println(response.getCode());
79+
System.out.println(response.getBody());
80+
}
81+
System.out.println();
82+
System.out.println("Thats it man! Go and build something awesome with ScribeJava! :)");
83+
84+
InstagramService instagramService = (InstagramService) service;
85+
System.out.println("Now let's exchange our short-lived token to long-lived access token...");
86+
OAuth2AccessToken longLivedAccessToken = instagramService.getLongLivedAccessToken(accessToken);
87+
System.out.println("Got the Access Token!");
88+
System.out.println("(The access token raw response looks like this: " + longLivedAccessToken.getRawResponse() + "')");
89+
90+
System.out.println("Now it's time to refresh long-lived token...");
91+
OAuth2AccessToken refreshAccessToken = service.refreshAccessToken(longLivedAccessToken.getAccessToken());
92+
System.out.println("Got the refreshed Access Token!");
93+
System.out.println("(The refreshed access token raw response looks like this: " + refreshAccessToken.getRawResponse() + "')");
94+
}
95+
}

0 commit comments

Comments
 (0)