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