001/*
002 * Copyright (c) 2010-2024 Mark Allen, Norbert Bartels.
003 *
004 * Permission is hereby granted, free of charge, to any person obtaining a copy
005 * of this software and associated documentation files (the "Software"), to deal
006 * in the Software without restriction, including without limitation the rights
007 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
008 * copies of the Software, and to permit persons to whom the Software is
009 * furnished to do so, subject to the following conditions:
010 *
011 * The above copyright notice and this permission notice shall be included in
012 * all copies or substantial portions of the Software.
013 *
014 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
015 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
016 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
017 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
018 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
019 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
020 * THE SOFTWARE.
021 */
022package com.restfb;
023
024import static com.restfb.util.ObjectUtil.verifyParameterPresence;
025
026import java.util.ArrayList;
027import java.util.Collections;
028import java.util.List;
029
030import com.restfb.exception.FacebookResponseContentException;
031import com.restfb.scope.ScopeBuilder;
032
033/**
034 * The default implementation to work with the Instagram Basic Display API.
035 * <p>
036 * it is used for this Instagram API and for the Threads API. This API is accordingly to the reference based on the
037 * Instagram Basic Display API.
038 *
039 * @deprecated on December 2024 see blog post: <a href=
040 *             "https://developers.facebook.com/blog/post/2024/09/04/update-on-instagram-basic-display-api/">https://developers.facebook.com/blog/post/2024/09/04/update-on-instagram-basic-display-api/</a>
041 */
042@Deprecated
043public class DefaultInstagramClient extends DefaultFacebookClient {
044
045  public DefaultInstagramClient(Version version) {
046    super(version);
047  }
048
049  public DefaultInstagramClient(String accessToken, Version apiVersion) {
050    super(accessToken, apiVersion);
051  }
052
053  public DefaultInstagramClient(String accessToken, String appSecret, Version apiVersion) {
054    super(accessToken, appSecret, apiVersion);
055  }
056
057  public DefaultInstagramClient(String accessToken, WebRequestor webRequestor, JsonMapper jsonMapper,
058      Version apiVersion) {
059    super(accessToken, webRequestor, jsonMapper, apiVersion);
060  }
061
062  public DefaultInstagramClient(String accessToken, String appSecret, WebRequestor webRequestor, JsonMapper jsonMapper,
063      Version apiVersion) {
064    super(accessToken, appSecret, webRequestor, jsonMapper, apiVersion);
065  }
066
067  @Override
068  public String getLoginDialogUrl(String appId, String redirectUri, ScopeBuilder scope, String state,
069      Parameter... parameters) {
070    List<Parameter> parameterList = new ArrayList<>();
071    Collections.addAll(parameterList, parameters);
072    parameterList.add(Parameter.with("response_type", CODE));
073    return getGenericLoginDialogUrl(appId, redirectUri, scope,
074      () -> getFacebookEndpointUrls().getInstagramApiEndpoint() + "/oauth/authorize", state, parameterList);
075  }
076
077  @Override
078  public String getLoginDialogUrl(String appId, String redirectUri, ScopeBuilder scope, Parameter... parameters) {
079    return getLoginDialogUrl(appId, redirectUri, scope, null, parameters);
080  }
081
082  @Override
083  public AccessToken obtainUserAccessToken(String clientId, String clientSecret, String redirectUri, String code) {
084    verifyParameterPresence(CLIENT_ID, clientId);
085    verifyParameterPresence(PARAM_CLIENT_SECRET, clientSecret);
086    verifyParameterPresence(CODE, code);
087    verifyParameterPresence(REDIRECT_URI, redirectUri);
088
089    return publish(PATH_OAUTH_ACCESS_TOKEN, AccessToken.class, //
090      Parameter.with(CLIENT_ID, clientId), //
091      Parameter.with(PARAM_CLIENT_SECRET, clientSecret), //
092      Parameter.with(CODE, code), //
093      Parameter.with(GRANT_TYPE, "authorization_code"), //
094      Parameter.with(REDIRECT_URI, redirectUri));
095  }
096
097  @Override
098  public AccessToken obtainExtendedAccessToken(String appId, String appSecret, String accessToken) {
099    throw new UnsupportedOperationException("Not supported, use the other obtainExtendedAccessToken instead");
100  }
101
102  @Override
103  public AccessToken obtainExtendedAccessToken(String appId, String appSecret) {
104    verifyParameterPresence(APP_SECRET, appSecret);
105    verifyParameterPresence("accessToken", accessToken);
106
107    String response = makeRequest("access_token", false, false, null, //
108      Parameter.with(PARAM_CLIENT_SECRET, appSecret), //
109      Parameter.with(GRANT_TYPE, "ig_exchange_token"), //
110      Parameter.withFields("access_token,expires_in,token_type"));
111
112    try {
113      return getAccessTokenFromResponse(response);
114    } catch (Exception t) {
115      throw new FacebookResponseContentException(CANNOT_EXTRACT_ACCESS_TOKEN_MESSAGE, t);
116    }
117  }
118
119  /**
120   * Obtain a refreshed Instagram extended access token.
121   *
122   * <p>
123   * This method is used to refresh an existing Instagram extended access token. Extended access tokens expire after a
124   * certain period of time, and this method allows you to obtain a new one using the refresh token provided with the
125   * original extended access token.
126   *
127   * @return A new {@link AccessToken} object containing the refreshed access token, expiration time, and token type.
128   * @throws FacebookResponseContentException
129   *           If the response from the Facebook API cannot be parsed or if the access token cannot be extracted from
130   *           the response.
131   */
132  @Override
133  public AccessToken obtainRefreshedExtendedAccessToken() {
134    String response = makeRequest("refresh_access_token", false, false, null, //
135      Parameter.with(GRANT_TYPE, "ig_refresh_token"), //
136      Parameter.withFields("access_token,expires_in,token_type"));
137    try {
138      return getAccessTokenFromResponse(response);
139    } catch (Exception t) {
140      throw new FacebookResponseContentException(CANNOT_EXTRACT_ACCESS_TOKEN_MESSAGE, t);
141    }
142  }
143
144  @Override
145  public FacebookClient createClientWithAccessToken(String accessToken) {
146    return new DefaultInstagramClient(accessToken, this.appSecret, getWebRequestor(), getJsonMapper(), this.apiVersion);
147  }
148
149  @Override
150  protected String createBaseUrlForEndpoint(String apiCall, boolean hasAttachment, boolean hasReel) {
151    String baseUrl = getInstagramGraphEndpointUrl();
152    if (apiCall.startsWith("oauth")) {
153      baseUrl = getFacebookEndpointUrls().getInstagramApiEndpoint();
154    }
155
156    return baseUrl;
157  }
158
159  private String getInstagramGraphEndpointUrl() {
160    if (apiVersion.isUrlElementRequired()) {
161      return getFacebookEndpointUrls().getInstagramEndpoint() + '/' + apiVersion.getUrlElement();
162    } else {
163      return getFacebookEndpointUrls().getInstagramEndpoint();
164    }
165  }
166}