mirror of
https://github.com/jwdeveloper/TikTokLiveJava.git
synced 2026-03-02 02:09:40 -05:00
Initial commit
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
package io.github.jwdeveloper.tiktok.http;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
public class HttpUtils
|
||||
{
|
||||
public static String parseParameters(String url, Map<String,Object> parameters)
|
||||
{
|
||||
var parameterString = "";
|
||||
if (!parameters.isEmpty()) {
|
||||
var builder = new StringBuilder();
|
||||
builder.append("?");
|
||||
var first = false;
|
||||
for (var param : parameters.entrySet()) {
|
||||
|
||||
if (first) {
|
||||
builder.append("&");
|
||||
}
|
||||
builder.append(param.getKey()).append("=").append(param.getValue());
|
||||
first = true;
|
||||
}
|
||||
parameterString = builder.toString();
|
||||
}
|
||||
|
||||
return url+parameterString;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static String parseParametersEncode(String url, Map<String,Object> parameters)
|
||||
{
|
||||
|
||||
var parameterString = "";
|
||||
if (!parameters.isEmpty()) {
|
||||
var builder = new StringBuilder();
|
||||
builder.append("?");
|
||||
var first = false;
|
||||
for (var param : parameters.entrySet()) {
|
||||
|
||||
if (first) {
|
||||
builder.append("&");
|
||||
}
|
||||
|
||||
final String encodedKey = URLEncoder.encode(param.getKey().toString(), StandardCharsets.UTF_8.toString());
|
||||
final String encodedValue = URLEncoder.encode(param.getValue().toString(), StandardCharsets.UTF_8.toString());
|
||||
builder.append(encodedKey).append("=").append(encodedValue);
|
||||
first = true;
|
||||
}
|
||||
parameterString = builder.toString();
|
||||
}
|
||||
|
||||
return url+parameterString;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package io.github.jwdeveloper.tiktok.http;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonParser;
|
||||
import io.github.jwdeveloper.generated.WebcastResponse;
|
||||
import io.github.jwdeveloper.tiktok.TikTokLiveException;
|
||||
import io.github.jwdeveloper.tiktok.live.LiveRoomInfo;
|
||||
import io.github.jwdeveloper.tiktok.live.models.gift.TikTokGift;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class TikTokApiService {
|
||||
private final TikTokHttpApiClient apiClient;
|
||||
private final Logger logger;
|
||||
private final Map<String, Object> clientParams;
|
||||
|
||||
public TikTokApiService(TikTokHttpApiClient apiClient, Logger logger, Map<String, Object> clientParams) {
|
||||
this.apiClient = apiClient;
|
||||
this.logger = logger;
|
||||
this.clientParams = clientParams;
|
||||
}
|
||||
|
||||
public String fetchRoomId(String userName) {
|
||||
logger.info("Fetching room ID");
|
||||
String html;
|
||||
try {
|
||||
html = apiClient.GetLivestreamPage(userName);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to fetch room id from WebCast, see stacktrace for more info.", e);
|
||||
}
|
||||
|
||||
Pattern firstPattern = Pattern.compile("room_id=([0-9]*)");
|
||||
Matcher firstMatcher = firstPattern.matcher(html);
|
||||
String id = "";
|
||||
|
||||
if (firstMatcher.find()) {
|
||||
id = firstMatcher.group(1);
|
||||
} else {
|
||||
Pattern secondPattern = Pattern.compile("\"roomId\":\"([0-9]*)\"");
|
||||
Matcher secondMatcher = secondPattern.matcher(html);
|
||||
|
||||
if (secondMatcher.find()) {
|
||||
id = secondMatcher.group(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (id.isEmpty()) {
|
||||
throw new TikTokLiveException("Unable to fetch room ID");
|
||||
}
|
||||
|
||||
clientParams.put("room_id", id);
|
||||
logger.info("RoomID -> "+id);
|
||||
return id;
|
||||
}
|
||||
|
||||
|
||||
public LiveRoomInfo fetchRoomInfo() {
|
||||
logger.info("Fetch RoomInfo");
|
||||
try {
|
||||
var response = apiClient.GetJObjectFromWebcastAPI("room/info/", clientParams);
|
||||
if (!response.has("data")) {
|
||||
return new LiveRoomInfo();
|
||||
}
|
||||
|
||||
var data = response.getAsJsonObject("data");
|
||||
if (!data.has("status")) {
|
||||
return new LiveRoomInfo();
|
||||
}
|
||||
|
||||
var status = data.get("status");
|
||||
|
||||
var info = new LiveRoomInfo();
|
||||
info.setStatus(status.getAsInt());
|
||||
|
||||
logger.info("RoomInfo status -> "+info.getStatus());
|
||||
return info;
|
||||
} catch (Exception e) {
|
||||
throw new TikTokLiveException("Failed to fetch room info from WebCast, see stacktrace for more info.", e);
|
||||
}
|
||||
}
|
||||
|
||||
public WebcastResponse fetchClientData()
|
||||
{
|
||||
logger.info("Fetch ClientData");
|
||||
try {
|
||||
var response = apiClient.GetDeserializedMessage("im/fetch/", clientParams);
|
||||
clientParams.put("cursor",response.getCursor());
|
||||
clientParams.put("internal_ext", response.getInternalExt());
|
||||
return response;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new TikTokLiveException("Failed to fetch client data", e);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<Integer, TikTokGift> fetchAvailableGifts() {
|
||||
try {
|
||||
var response = apiClient.GetJObjectFromWebcastAPI("gift/list/", clientParams);
|
||||
if(!response.has("data"))
|
||||
{
|
||||
return new HashMap<>();
|
||||
}
|
||||
var dataJson = response.getAsJsonObject("data");
|
||||
if(!dataJson.has("gifts"))
|
||||
{
|
||||
return new HashMap<>();
|
||||
}
|
||||
var giftsJsonList = dataJson.get("gifts").getAsJsonArray();
|
||||
var gifts = new HashMap<Integer, TikTokGift>();
|
||||
var gson = new Gson();
|
||||
for(var jsonGift : giftsJsonList)
|
||||
{
|
||||
var gift = gson.fromJson(jsonGift, TikTokGift.class);
|
||||
logger.info("Found Available Gift "+ gift.getName()+ " with ID "+gift.getId());
|
||||
gifts.put(gift.getId(),gift);
|
||||
}
|
||||
return gifts;
|
||||
} catch (Exception e) {
|
||||
throw new TikTokLiveException("Failed to fetch giftTokens from WebCast, see stacktrace for more info.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package io.github.jwdeveloper.tiktok.http;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class TikTokCookieJar {
|
||||
/// <summary>
|
||||
/// Cookies in Jar
|
||||
/// </summary>
|
||||
private final Map<String, String> cookies;
|
||||
|
||||
/// <summary>
|
||||
/// Create a TikTok cookie jar instance.
|
||||
/// </summary>
|
||||
public TikTokCookieJar() {
|
||||
cookies = new HashMap<>();
|
||||
}
|
||||
|
||||
|
||||
public String get(String key) {
|
||||
return cookies.get(key);
|
||||
}
|
||||
|
||||
public void set(String key, String value) {
|
||||
cookies.put(key, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enumerates Cookies
|
||||
/// </summary>
|
||||
public Set<Map.Entry<String, String>> GetEnumerator() {
|
||||
return cookies.entrySet();
|
||||
}
|
||||
|
||||
/* /// <summary>
|
||||
/// Enumerates Cookies
|
||||
/// </summary>
|
||||
public IEnumerator<string> GetEnumerator()
|
||||
{
|
||||
foreach (var cookie in cookies)
|
||||
yield return $"{cookie.Key}={cookie.Value};";
|
||||
}*/
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package io.github.jwdeveloper.tiktok.http;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import io.github.jwdeveloper.generated.WebcastResponse;
|
||||
import io.github.jwdeveloper.tiktok.ClientSettings;
|
||||
import io.github.jwdeveloper.tiktok.Constants;
|
||||
import io.github.jwdeveloper.tiktok.TikTokLiveException;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class TikTokHttpApiClient {
|
||||
private final ClientSettings clientSettings;
|
||||
private final TikTokHttpRequestFactory requestFactory;
|
||||
|
||||
public TikTokHttpApiClient(ClientSettings clientSettings, TikTokHttpRequestFactory requestFactory) {
|
||||
this.clientSettings = clientSettings;
|
||||
this.requestFactory = requestFactory;
|
||||
}
|
||||
|
||||
|
||||
public String GetLivestreamPage(String userName) {
|
||||
|
||||
var url = Constants.TIKTOK_URL_WEB + "@" + userName + "/live/";
|
||||
var get = getRequest(url, null, false);
|
||||
return get;
|
||||
}
|
||||
|
||||
public JsonObject GetJObjectFromWebcastAPI(String path, Map<String, Object> parameters) {
|
||||
var get = getRequest(Constants.TIKTOK_URL_WEBCAST + path, parameters, false);
|
||||
var json = JsonParser.parseString(get);
|
||||
var jsonObject = json.getAsJsonObject();
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
public WebcastResponse GetDeserializedMessage(String path, Map<String, Object> parameters) {
|
||||
var bytes = getSignRequest(Constants.TIKTOK_URL_WEBCAST + path, parameters);
|
||||
try {
|
||||
return WebcastResponse.parseFrom(bytes);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new TikTokLiveException("Unable to deserialize message: "+path,e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String getRequest(String url, Map<String, Object> parameters, boolean signURL) {
|
||||
if (parameters == null) {
|
||||
parameters = new HashMap<>();
|
||||
}
|
||||
|
||||
var request = requestFactory.SetQueries(parameters);
|
||||
return request.Get(url);
|
||||
}
|
||||
private byte[] getSignRequest(String url, Map<String, Object> parameters) {
|
||||
url = GetSignedUrl(url, parameters);
|
||||
try {
|
||||
var client = HttpClient.newHttpClient();
|
||||
var request = HttpRequest.newBuilder()
|
||||
.uri(new URI(url))
|
||||
.build();
|
||||
var response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
|
||||
|
||||
return response.body();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new TikTokLiveException("unabel to send signature");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private String GetSignedUrl(String url, Map<String, Object> parameters) {
|
||||
|
||||
var fullUrl = HttpUtils.parseParameters(url,parameters);
|
||||
var singHeaders = new HashMap<String, Object>();
|
||||
singHeaders.put("client", "ttlive-net");
|
||||
singHeaders.put("uuc", 1);
|
||||
singHeaders.put("url", fullUrl);
|
||||
|
||||
var request = requestFactory.SetQueries(singHeaders);
|
||||
var content = request.Get(Constants.TIKTOK_SIGN_API);
|
||||
|
||||
|
||||
try {
|
||||
var json = JsonParser.parseString(content);
|
||||
var jsonObject = json.getAsJsonObject();
|
||||
var signedUrl = jsonObject.get("signedUrl").getAsString();
|
||||
var userAgent = jsonObject.get("User-Agent").getAsString();
|
||||
|
||||
//requestFactory.setHeader()
|
||||
requestFactory.setAgent(userAgent);
|
||||
return signedUrl;
|
||||
} catch (Exception e) {
|
||||
throw new TikTokLiveException("Insufficent values have been supplied for signing. Likely due to an update. Post an issue on GitHub.", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package io.github.jwdeveloper.tiktok.http;
|
||||
|
||||
|
||||
import io.github.jwdeveloper.tiktok.Constants;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
import java.net.CookieManager;
|
||||
import java.net.ProxySelector;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class TikTokHttpRequestFactory implements TikTokHttpRequest
|
||||
{
|
||||
private CookieManager cookieManager;
|
||||
private HttpClient client;
|
||||
|
||||
private Duration timeout;
|
||||
|
||||
private ProxySelector webProxy;
|
||||
private String query;
|
||||
private Boolean sent;
|
||||
private Map<String, String> defaultHeaders;
|
||||
|
||||
public TikTokHttpRequestFactory() {
|
||||
|
||||
cookieManager = new CookieManager();
|
||||
defaultHeaders = Constants.DefaultRequestHeaders();
|
||||
client = HttpClient.newBuilder()
|
||||
.cookieHandler(cookieManager)
|
||||
.connectTimeout(Duration.ofSeconds(2))
|
||||
.build();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public String Get(String url) {
|
||||
var uri = URI.create(url);
|
||||
var request = HttpRequest.newBuilder().GET();
|
||||
for(var header : defaultHeaders.entrySet())
|
||||
{
|
||||
//request.setHeader(header.getKey(),header.getValue());
|
||||
}
|
||||
if (query != null) {
|
||||
var baseUri = uri.toString();
|
||||
var requestUri = URI.create(baseUri + "?" + query);
|
||||
request.uri(requestUri);
|
||||
}
|
||||
|
||||
return GetContent(request.build());
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public String Post(String url, HttpRequest.BodyPublisher data) {
|
||||
var uri = URI.create(url);
|
||||
var request = HttpRequest.newBuilder().POST(data);
|
||||
for(var header : defaultHeaders.entrySet())
|
||||
{
|
||||
request.setHeader(header.getKey(),header.getValue());
|
||||
}
|
||||
if (query != null) {
|
||||
var baseUri = uri.toString();
|
||||
var requestUri = URI.create(baseUri + "?" + query);
|
||||
request.uri(requestUri);
|
||||
}
|
||||
return GetContent(request.build());
|
||||
}
|
||||
|
||||
public TikTokHttpRequest setHeader(String key, String value)
|
||||
{
|
||||
defaultHeaders.put(key,value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TikTokHttpRequest setAgent( String value)
|
||||
{
|
||||
defaultHeaders.put("User-Agent", value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TikTokHttpRequest SetQueries(Map<String, Object> queries) {
|
||||
if (queries == null)
|
||||
return this;
|
||||
query = String.join("&", queries.entrySet().stream().map(x ->
|
||||
{
|
||||
var key = x.getKey();
|
||||
var value = "";
|
||||
try {
|
||||
value = URLEncoder.encode(x.getValue().toString(), StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return key + "=" + value;
|
||||
}).toList());
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private String GetContent(HttpRequest request) throws Exception {
|
||||
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
sent = true;
|
||||
if (response.statusCode() == 404)
|
||||
{
|
||||
throw new RuntimeException("Request responded with 404 NOT_FOUND");
|
||||
}
|
||||
|
||||
if(response.statusCode() != 200)
|
||||
{
|
||||
throw new RuntimeException("Request was unsuccessful "+response.statusCode());
|
||||
}
|
||||
|
||||
|
||||
var cookies = response.headers().allValues("Set-Cookie");
|
||||
for(var cookie : cookies)
|
||||
{
|
||||
var split = cookie.split(";")[0].split("=");
|
||||
var uri = request.uri();
|
||||
var map = new HashMap<String,List<String>>();
|
||||
map.put(split[0],List.of(split[1]));
|
||||
cookieManager.put(uri,map);
|
||||
|
||||
}
|
||||
return response.body();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user