All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.echobox.api.tiktok.client.DefaultTikTokClient Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.echobox.api.tiktok.client;

import com.echobox.api.tiktok.client.DefaultWebRequestor.HttpMethod;
import com.echobox.api.tiktok.client.WebRequestor.Response;
import com.echobox.api.tiktok.connection.Connection;
import com.echobox.api.tiktok.exception.TikTokAPIException;
import com.echobox.api.tiktok.exception.TikTokException;
import com.echobox.api.tiktok.exception.TikTokJsonMappingException;
import com.echobox.api.tiktok.exception.TikTokNetworkException;
import com.echobox.api.tiktok.model.HasCursor;
import com.echobox.api.tiktok.model.response.TokenResponse;
import com.echobox.api.tiktok.version.Version;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.google.gson.annotations.SerializedName;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.function.Function;

/**
 * Default Java TikTok client for communicating with the API, specific endpoints will be
 * configured in separate 'connection' classes.
 *
 * @author eddspencer
 */
public class DefaultTikTokClient implements TikTokClient {
  
  private static final String TIKTOK_ENDPOINT_URL = "https://business-api.tiktok.com/open_api";
  
  /**
   * Endpoint path for access token creation
   */
  protected static final String ENDPOINT_TOKEN = "/tt_user/oauth2/token/";
  
  /**
   * Endpoint path for refresh token creation
   */
  protected static final String ENDPOINT_REFRESH_TOKEN = "/tt_user/oauth2/refresh_token/";
  
  private static final Gson JSON_FACTORY = new Gson();
  
  private final Version version;
  private WebRequestor webRequestor;
  
  /**
   * Constructor with no access token for use when obtaining an access token
   *
   * @param version the version
   * @throws GeneralSecurityException the general security exception
   * @throws IOException the io exception
   */
  public DefaultTikTokClient(Version version) throws GeneralSecurityException, IOException {
    this(version, (String) null);
  }
  
  /**
   * Constructor with access token for use when calling the endpoints to get and update data
   *
   * @param version the version
   * @param accessToken the access token
   * @throws GeneralSecurityException the general security exception
   * @throws IOException the IO exception
   */
  public DefaultTikTokClient(Version version, String accessToken)
      throws GeneralSecurityException, IOException {
    this(version, new DefaultWebRequestor(accessToken));
  }
  
  /**
   * Constructor with web requestor for use when calling the endpoints to get and update data
   *
   * @param version the version
   * @param webRequestor the web requestor
   */
  public DefaultTikTokClient(Version version, WebRequestor webRequestor) {
    this.version = version;
    this.webRequestor = webRequestor;
  }
  
  @Override
  public Version getVersion() {
    return version;
  }
  
  @Override
  public WebRequestor getWebRequestor() {
    return webRequestor;
  }
  
  protected void setWebRequestor(WebRequestor webRequestor) {
    this.webRequestor = webRequestor;
  }
  
  @Override
  public  T fetchObject(String endpoint, Class responseType, Parameter... parameters) {
    return makeRequest(endpoint, HttpMethod.GET, responseType, null, parameters);
  }
  
  @Override
  public > Connection fetchConnection(String endpoint,
      Class responseType, Parameter... parameters) {
    final Function> getNextPage = cursor -> {
      final ArrayList parameterList = new ArrayList<>(Arrays.asList(parameters));
      if (cursor != null) {
        int index = parameterList.indexOf(Parameter.with("cursor", cursor.toString()));
        if (index == -1) {
          parameterList.add(Parameter.with("cursor", cursor.toString()));
        } else {
          parameterList.set(index, Parameter.with("cursor", cursor.toString()));
        }
      }
      return makeRequest(endpoint, HttpMethod.GET, responseType, null,
          parameterList.toArray(new Parameter[0]));
    };
    return new Connection<>(getNextPage);
  }
  
  @Override
  public  T publish(String endpoint, Class responseType, Object data,
      Parameter... parameters) {
    return makeRequest(endpoint, HttpMethod.POST, responseType, data, parameters);
  }
  
  @Override
  public AuthTokensV2 obtainAuthTokensFromAuthCodeV2(String appId, String appSecret,
      String authCode, String redirectURI) {
    final TokenRequestBody requestBody =
        new TokenRequestBody(appId, appSecret, authCode, redirectURI);
    
    return makeRequest(ENDPOINT_TOKEN, HttpMethod.POST, TokenResponse.class, requestBody)
        .getAuthTokens();
  }
  
  @Override
  public AuthTokensV2 obtainAuthTokensFromRefreshTokenV2(String appId, String appSecret,
      String refreshToken) {
    final RefreshTokenRequestBody requestBody =
        new RefreshTokenRequestBody(appId, appSecret, refreshToken);
    
    return makeRequest(ENDPOINT_REFRESH_TOKEN, HttpMethod.POST, TokenResponse.class, requestBody)
        .getAuthTokens();
  }
  
  protected  T makeRequest(String endpoint, HttpMethod httpMethod, Class responseType,
      Object data, Parameter... parameters) {
    final Response response = executeRequest(endpoint, httpMethod, data, parameters);
    validateResponse(response);
    try {
      return JSON_FACTORY.fromJson(response.getBody(), responseType);
    } catch (JsonSyntaxException ex) {
      throw new TikTokJsonMappingException("Error calling endpoint: " + endpoint, ex);
    }
  }
  
  protected void validateResponse(Response response) {
    final Status tikTokStatus = getTikTokStatus(response.getBody());
    
    final TikTokException ex = TikTokAPIException
        .fromStatusCode(response.getStatusCode(), tikTokStatus.getCode(),
            tikTokStatus.getMessage());
    
    if (ex != null) {
      throw ex;
    }
  }
  
  protected Status getTikTokStatus(String body) {
    if (StringUtils.isEmpty(body)) {
      return Status.EMPTY;
    }
    
    try {
      return JSON_FACTORY.fromJson(body, Status.class);
    } catch (JsonSyntaxException ex) {
      // Treat body like message if it is not JSON
      return new Status(null, body);
    }
  }
  
  protected Response executeRequest(String endpoint, HttpMethod httpMethod, Object data,
      Parameter... parameters) {
    final String fullEndpoint = createFullEndpoint(endpoint);
    final String parameterString = Parameter.toURLEncodedString(parameters);
    
    try {
      switch (httpMethod) {
        case GET:
          return webRequestor.executeGet(fullEndpoint + parameterString);
        case POST:
          final String jsonBody = data == null ? null : JSON_FACTORY.toJson(data);
          return webRequestor.executePost(fullEndpoint, jsonBody, parameterString);
        default:
          throw new TikTokException("Unrecognised HTTP method: " + httpMethod);
      }
    } catch (IOException ex) {
      throw new TikTokNetworkException("Error calling endpoint: " + endpoint, ex);
    }
  }
  
  protected String createFullEndpoint(String endpoint) {
    while (endpoint.startsWith("/")) {
      endpoint = endpoint.substring(1);
    }
    return String.format("%s/%s/%s", TIKTOK_ENDPOINT_URL, version.getUrlElement(), endpoint);
  }
  
  /**
   * Wrapper class for TikTok status code and message
   * @author eddspencer
   */
  @AllArgsConstructor
  @NoArgsConstructor
  @Getter
  static class Status {
    
    private static final Status EMPTY = new Status(null, null);
    
    private Integer code;
    private String message;
  }
  
  /**
   * Request POST body for the oauth2 token endpoint
   *
   * @author paulpopa
   */
  @AllArgsConstructor
  @NoArgsConstructor
  @Getter
  protected static class TokenRequestBody {
    @SerializedName("client_id")
    private String clientId;
    @SerializedName("client_secret")
    private String clientSecret;
    @SerializedName("grant_type")
    private String grantType;
    @SerializedName("auth_code")
    private String authCode;
    @SerializedName("redirect_uri")
    private String redirectURI;
    
    public TokenRequestBody(String clientId, String clientSecret, String authCode,
        String redirectURI) {
      this.clientId = clientId;
      this.clientSecret = clientSecret;
      this.grantType = "authorization_code";
      this.authCode = authCode;
      this.redirectURI = redirectURI;
    }
  }
  
  /**
   * Request POST body for the oauth2 refresh token endpoint
   *
   * @author paulpopa
   */
  @AllArgsConstructor
  @NoArgsConstructor
  @Getter
  protected static class RefreshTokenRequestBody {
    @SerializedName("client_id")
    private String clientId;
    @SerializedName("client_secret")
    private String clientSecret;
    @SerializedName("grant_type")
    private String grantType;
    @SerializedName("refresh_token")
    private String refreshToken;
    
    public RefreshTokenRequestBody(String clientId, String clientSecret, String refreshToken) {
      this.clientId = clientId;
      this.clientSecret = clientSecret;
      this.grantType = "refresh_token";
      this.refreshToken = refreshToken;
    }
  }
  
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy