Initial commit

This commit is contained in:
JW
2023-08-05 18:15:37 +02:00
commit a58612d4c4
44 changed files with 22359 additions and 0 deletions

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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};";
}*/
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}