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 */
039public class DefaultInstagramClient extends DefaultFacebookClient {
040
041  public DefaultInstagramClient(Version version) {
042    super(version);
043  }
044
045  public DefaultInstagramClient(String accessToken, Version apiVersion) {
046    super(accessToken, apiVersion);
047  }
048
049  public DefaultInstagramClient(String accessToken, String appSecret, Version apiVersion) {
050    super(accessToken, appSecret, apiVersion);
051  }
052
053  public DefaultInstagramClient(String accessToken, WebRequestor webRequestor, JsonMapper jsonMapper,
054      Version apiVersion) {
055    super(accessToken, webRequestor, jsonMapper, apiVersion);
056  }
057
058  public DefaultInstagramClient(String accessToken, String appSecret, WebRequestor webRequestor, JsonMapper jsonMapper,
059      Version apiVersion) {
060    super(accessToken, appSecret, webRequestor, jsonMapper, apiVersion);
061  }
062
063  @Override
064  public String getLoginDialogUrl(String appId, String redirectUri, ScopeBuilder scope, String state,
065      Parameter... parameters) {
066    List<Parameter> parameterList = new ArrayList<>();
067    Collections.addAll(parameterList, parameters);
068    parameterList.add(Parameter.with("response_type", CODE));
069    return getGenericLoginDialogUrl(appId, redirectUri, scope,
070      () -> getFacebookEndpointUrls().getInstagramApiEndpoint() + "/oauth/authorize", state, parameterList);
071  }
072
073  @Override
074  public String getLoginDialogUrl(String appId, String redirectUri, ScopeBuilder scope, Parameter... parameters) {
075    return getLoginDialogUrl(appId, redirectUri, scope, null, parameters);
076  }
077
078  @Override
079  public AccessToken obtainUserAccessToken(String clientId, String clientSecret, String redirectUri, String code) {
080    verifyParameterPresence(CLIENT_ID, clientId);
081    verifyParameterPresence(PARAM_CLIENT_SECRET, clientSecret);
082    verifyParameterPresence(CODE, code);
083    verifyParameterPresence(REDIRECT_URI, redirectUri);
084
085    return publish(PATH_OAUTH_ACCESS_TOKEN, AccessToken.class, //
086      Parameter.with(CLIENT_ID, clientId), //
087      Parameter.with(PARAM_CLIENT_SECRET, clientSecret), //
088      Parameter.with(CODE, code), //
089      Parameter.with(GRANT_TYPE, "authorization_code"), //
090      Parameter.with(REDIRECT_URI, redirectUri));
091  }
092
093  @Override
094  public AccessToken obtainExtendedAccessToken(String appId, String appSecret, String accessToken) {
095    throw new UnsupportedOperationException("Not supported, use the other obtainExtendedAccessToken instead");
096  }
097
098  @Override
099  public AccessToken obtainExtendedAccessToken(String appId, String appSecret) {
100    verifyParameterPresence(APP_SECRET, appSecret);
101    verifyParameterPresence("accessToken", accessToken);
102
103    String response = makeRequest("access_token", false, false, null, //
104      Parameter.with(PARAM_CLIENT_SECRET, appSecret), //
105      Parameter.with(GRANT_TYPE, "ig_exchange_token"), //
106      Parameter.withFields("access_token,expires_in,token_type"));
107
108    try {
109      return getAccessTokenFromResponse(response);
110    } catch (Exception t) {
111      throw new FacebookResponseContentException(CANNOT_EXTRACT_ACCESS_TOKEN_MESSAGE, t);
112    }
113  }
114
115  /**
116   * Obtain a refreshed Instagram extended access token.
117   *
118   * <p>
119   * This method is used to refresh an existing Instagram extended access token. Extended access tokens expire after a
120   * certain period of time, and this method allows you to obtain a new one using the refresh token provided with the
121   * original extended access token.
122   *
123   * @return A new {@link AccessToken} object containing the refreshed access token, expiration time, and token type.
124   * @throws FacebookResponseContentException
125   *           If the response from the Facebook API cannot be parsed or if the access token cannot be extracted from
126   *           the response.
127   */
128  @Override
129  public AccessToken obtainRefreshedExtendedAccessToken() {
130    String response = makeRequest("refresh_access_token", false, false, null, //
131      Parameter.with(GRANT_TYPE, "ig_refresh_token"), //
132      Parameter.withFields("access_token,expires_in,token_type"));
133    try {
134      return getAccessTokenFromResponse(response);
135    } catch (Exception t) {
136      throw new FacebookResponseContentException(CANNOT_EXTRACT_ACCESS_TOKEN_MESSAGE, t);
137    }
138  }
139
140  @Override
141  public FacebookClient createClientWithAccessToken(String accessToken) {
142    return new DefaultInstagramClient(accessToken, this.appSecret, getWebRequestor(), getJsonMapper(), this.apiVersion);
143  }
144
145  @Override
146  protected String createBaseUrlForEndpoint(String apiCall, boolean hasAttachment, boolean hasReel) {
147    String baseUrl = getInstagramGraphEndpointUrl();
148    if (apiCall.startsWith("oauth")) {
149      baseUrl = getFacebookEndpointUrls().getInstagramApiEndpoint();
150    }
151
152    return baseUrl;
153  }
154
155  private String getInstagramGraphEndpointUrl() {
156    if (apiVersion.isUrlElementRequired()) {
157      return getFacebookEndpointUrls().getInstagramEndpoint() + '/' + apiVersion.getUrlElement();
158    } else {
159      return getFacebookEndpointUrls().getInstagramEndpoint();
160    }
161  }
162}