Fix Message parsing for

- LikeMessage
- MessageWebcastGiftMessage
- MessageWebcastChatMessage
This commit is contained in:
JW
2023-08-22 19:53:33 +02:00
parent a9e347b8da
commit 2391b12598
20 changed files with 203 additions and 152 deletions

View File

@@ -7,7 +7,7 @@ import io.github.jwdeveloper.tiktok.live.ConnectionState;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.live.LiveRoomInfo;
import io.github.jwdeveloper.tiktok.live.TikTokRoomInfo;
import io.github.jwdeveloper.tiktok.websocket.TikTokWebsocketClient;
import io.github.jwdeveloper.tiktok.websocket.TikTokWebSocketClient;
import java.util.logging.Logger;
@@ -15,14 +15,14 @@ public class TikTokLiveClient implements LiveClient {
private final TikTokRoomInfo meta;
private final TikTokGiftManager giftManager;
private final TikTokApiService apiClient;
private final TikTokWebsocketClient webSocketClient;
private final TikTokWebSocketClient webSocketClient;
private final TikTokEventHandler tikTokEventHandler;
private final Logger logger;
public TikTokLiveClient(TikTokRoomInfo tikTokLiveMeta,
TikTokApiService tikTokApiService,
TikTokWebsocketClient webSocketClient,
TikTokWebSocketClient webSocketClient,
TikTokGiftManager tikTokGiftManager,
TikTokEventHandler tikTokEventHandler,
Logger logger) {
@@ -57,9 +57,9 @@ public class TikTokLiveClient implements LiveClient {
public void tryConnect() {
if (meta.hasConnectionState(ConnectionState.CONNECTED))
throw new RuntimeException("Already connected");
throw new TikTokLiveException("Already connected");
if (meta.hasConnectionState(ConnectionState.CONNECTING))
throw new RuntimeException("Already connecting");
throw new TikTokLiveException("Already connecting");
logger.info("Connecting");
setState(ConnectionState.CONNECTING);

View File

@@ -11,51 +11,29 @@ import io.github.jwdeveloper.tiktok.http.TikTokHttpApiClient;
import io.github.jwdeveloper.tiktok.http.TikTokHttpRequestFactory;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.live.TikTokRoomInfo;
import io.github.jwdeveloper.tiktok.websocket.TikTokWebsocketClient;
import io.github.jwdeveloper.tiktok.websocket.TikTokWebSocketClient;
import java.time.Duration;
import java.util.Map;
import java.util.function.Consumer;
import java.util.logging.Logger;
public class TikTokLiveClientBuilder implements TikTokEventBuilder<TikTokLiveClientBuilder> {
private String userName;
private final ClientSettings clientSettings;
private Map<String, Object> clientParameters;
private final Logger logger;
private final TikTokEventHandler tikTokEventHandler;
public TikTokLiveClientBuilder(String userName) {
this.tikTokEventHandler = new TikTokEventHandler();
this.userName = userName;
this.clientSettings = Constants.DefaultClientSettings();
this.clientParameters = Constants.DefaultClientParams();
this.clientSettings.setHostName(userName);
this.logger = Logger.getLogger(TikTokLive.class.getName());
}
public TikTokLiveClientBuilder clientSettings(Consumer<ClientSettings> consumer) {
public TikTokLiveClientBuilder configure(Consumer<ClientSettings> consumer) {
consumer.accept(clientSettings);
return this;
}
public TikTokLiveClientBuilder hostUserName(String userName) {
this.userName = userName;
return this;
}
public TikTokLiveClientBuilder clientParameters(Map<String, Object> clientParameters) {
this.clientParameters = clientParameters;
return this;
}
public TikTokLiveClientBuilder addClientParameters(String key, Object value) {
this.clientParameters.put(key, value);
return this;
}
private void validate() {
if (clientSettings.getTimeout() == null) {
@@ -75,16 +53,13 @@ public class TikTokLiveClientBuilder implements TikTokEventBuilder<TikTokLiveCli
}
if (userName == null || userName.equals("")) {
throw new RuntimeException("UserName can not be null");
if (clientSettings.getHostName() == null || clientSettings.getHostName() .equals("")) {
throw new RuntimeException("HostName can not be null");
}
if (clientParameters == null) {
clientParameters = Constants.DefaultClientParams();
}
clientParameters.put("app_language", clientSettings.getClientLanguage());
clientParameters.put("webcast_language", clientSettings.getClientLanguage());
var params = clientSettings.getClientParameters();
params.put("app_language", clientSettings.getClientLanguage());
params.put("webcast_language", clientSettings.getClientLanguage());
}
public LiveClient build() {
@@ -92,18 +67,17 @@ public class TikTokLiveClientBuilder implements TikTokEventBuilder<TikTokLiveCli
var meta = new TikTokRoomInfo();
meta.setUserName(userName);
meta.setUserName(clientSettings.getHostName());
var cookieJar = new TikTokCookieJar();
var requestFactory = new TikTokHttpRequestFactory(cookieJar);
var apiClient = new TikTokHttpApiClient(cookieJar, clientSettings, requestFactory);
var apiService = new TikTokApiService(apiClient, logger, clientParameters);
var apiClient = new TikTokHttpApiClient(cookieJar, requestFactory);
var apiService = new TikTokApiService(apiClient, logger, clientSettings);
var giftManager = new TikTokGiftManager(logger, apiService, clientSettings);
var webResponseHandler = new WebResponseHandler(tikTokEventHandler,giftManager);
var webSocketClient = new TikTokWebsocketClient(logger,
var webSocketClient = new TikTokWebSocketClient(logger,
cookieJar,
clientParameters,
requestFactory,
clientSettings,
webResponseHandler,

View File

@@ -69,9 +69,10 @@ public abstract class WebResponseHandlerBase {
handleSingleMessage(message);
} catch (Exception e)
{
var decoded = Base64.getEncoder().encodeToString(message.getBinary().toByteArray());
var exception = new TikTokLiveException("Error whilst Handling Message. Stopping Client. Final Message: \n"+decoded, e);
var exception = new TikTokLiveException("Error whilst Handling Message"+message.getType()+": \n"+decoded, e);
tikTokEventHandler.publish(new TikTokErrorEvent(exception));
}
}

View File

@@ -44,8 +44,8 @@ public class HttpUtils
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());
final String encodedKey = URLEncoder.encode(param.getKey(), StandardCharsets.UTF_8);
final String encodedValue = URLEncoder.encode(param.getValue().toString(), StandardCharsets.UTF_8);
builder.append(encodedKey).append("=").append(encodedValue);
first = true;
}

View File

@@ -1,7 +1,9 @@
package io.github.jwdeveloper.tiktok.http;
import com.google.gson.Gson;
import io.github.jwdeveloper.tiktok.ClientSettings;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import io.github.jwdeveloper.tiktok.live.LiveRoomMeta;
import io.github.jwdeveloper.tiktok.models.gifts.TikTokGift;
import io.github.jwdeveloper.tiktok.messages.WebcastResponse;
@@ -15,12 +17,12 @@ import java.util.regex.Pattern;
public class TikTokApiService {
private final TikTokHttpApiClient apiClient;
private final Logger logger;
private final Map<String, Object> clientParams;
private final ClientSettings clientSettings;
public TikTokApiService(TikTokHttpApiClient apiClient, Logger logger, Map<String, Object> clientParams) {
public TikTokApiService(TikTokHttpApiClient apiClient, Logger logger, ClientSettings clientSettings) {
this.apiClient = apiClient;
this.logger = logger;
this.clientParams = clientParams;
this.clientSettings = clientSettings;
}
public String fetchRoomId(String userName) {
@@ -29,7 +31,7 @@ public class TikTokApiService {
try {
html = apiClient.GetLivestreamPage(userName);
} catch (Exception e) {
throw new RuntimeException("Failed to fetch room id from WebCast, see stacktrace for more info.", e);
throw new TikTokLiveRequestException("Failed to fetch room id from WebCast, see stacktrace for more info.", e);
}
Pattern firstPattern = Pattern.compile("room_id=([0-9]*)");
@@ -51,7 +53,7 @@ public class TikTokApiService {
throw new TikTokLiveException("Unable to fetch room ID");
}
clientParams.put("room_id", id);
clientSettings.getClientParameters().put("room_id", id);
logger.info("RoomID -> "+id);
return id;
}
@@ -60,7 +62,7 @@ public class TikTokApiService {
public LiveRoomMeta fetchRoomInfo() {
logger.info("Fetch RoomInfo");
try {
var response = apiClient.GetJObjectFromWebcastAPI("room/info/", clientParams);
var response = apiClient.GetJObjectFromWebcastAPI("room/info/", clientSettings.getClientParameters());
if (!response.has("data")) {
return new LiveRoomMeta();
}
@@ -78,7 +80,7 @@ public class TikTokApiService {
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);
throw new TikTokLiveRequestException("Failed to fetch room info from WebCast, see stacktrace for more info.", e);
}
}
@@ -86,20 +88,20 @@ public class TikTokApiService {
{
logger.info("Fetch ClientData");
try {
var response = apiClient.GetDeserializedMessage("im/fetch/", clientParams);
clientParams.put("cursor",response.getCursor());
clientParams.put("internal_ext", response.getAckIds());
var response = apiClient.GetDeserializedMessage("im/fetch/", clientSettings.getClientParameters());
clientSettings.getClientParameters().put("cursor",response.getCursor());
clientSettings.getClientParameters().put("internal_ext", response.getAckIds());
return response;
}
catch (Exception e)
{
throw new TikTokLiveException("Failed to fetch client data", e);
throw new TikTokLiveRequestException("Failed to fetch client data", e);
}
}
public Map<Integer, TikTokGift> fetchAvailableGifts() {
try {
var response = apiClient.GetJObjectFromWebcastAPI("gift/list/", clientParams);
var response = apiClient.GetJObjectFromWebcastAPI("gift/list/", clientSettings.getClientParameters());
if(!response.has("data"))
{
return new HashMap<>();
@@ -120,7 +122,7 @@ public class TikTokApiService {
}
return gifts;
} catch (Exception e) {
throw new TikTokLiveException("Failed to fetch giftTokens from WebCast, see stacktrace for more info.", e);
throw new TikTokLiveRequestException("Failed to fetch giftTokens from WebCast, see stacktrace for more info.", e);
}
}
}

View File

@@ -4,7 +4,7 @@ import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.github.jwdeveloper.tiktok.ClientSettings;
import io.github.jwdeveloper.tiktok.Constants;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import io.github.jwdeveloper.tiktok.messages.WebcastResponse;
import java.net.URI;
@@ -16,27 +16,23 @@ import java.util.Map;
import java.util.TreeMap;
public class TikTokHttpApiClient {
private final ClientSettings clientSettings;
private final TikTokHttpRequestFactory requestFactory;
private final TikTokCookieJar tikTokCookieJar;
public TikTokHttpApiClient(TikTokCookieJar tikTokCookieJar, ClientSettings clientSettings, TikTokHttpRequestFactory requestFactory) {
this.clientSettings = clientSettings;
public TikTokHttpApiClient(TikTokCookieJar tikTokCookieJar, TikTokHttpRequestFactory requestFactory) {
this.requestFactory = requestFactory;
this.tikTokCookieJar = tikTokCookieJar;
}
public String GetLivestreamPage(String userName) {
var url = Constants.TIKTOK_URL_WEB + "@" + userName + "/live/";
var get = getRequest(url, null, false);
var get = getRequest(url, null);
return get;
}
public JsonObject GetJObjectFromWebcastAPI(String path, Map<String, Object> parameters) {
var get = getRequest(Constants.TIKTOK_URL_WEBCAST + path, parameters, false);
var get = getRequest(Constants.TIKTOK_URL_WEBCAST + path, parameters);
var json = JsonParser.parseString(get);
var jsonObject = json.getAsJsonObject();
return jsonObject;
@@ -49,12 +45,11 @@ public class TikTokHttpApiClient {
}
catch (Exception e)
{
throw new TikTokLiveException("Unable to deserialize message: "+path,e);
throw new TikTokLiveRequestException("Unable to deserialize message: "+path,e);
}
}
private String getRequest(String url, Map<String, Object> parameters, boolean signURL) {
private String getRequest(String url, Map<String, Object> parameters) {
if (parameters == null) {
parameters = new HashMap<>();
}
@@ -86,7 +81,7 @@ public class TikTokHttpApiClient {
}
catch (Exception e)
{
throw new TikTokLiveException("unabel to send signature");
throw new TikTokLiveRequestException("Unable to send signature");
}
}
@@ -112,7 +107,7 @@ public class TikTokHttpApiClient {
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);
throw new TikTokLiveRequestException("Insufficient values have been supplied for signing. Likely due to an update. Post an issue on GitHub.", e);
}
}

View File

@@ -2,10 +2,10 @@ package io.github.jwdeveloper.tiktok.http;
import io.github.jwdeveloper.tiktok.Constants;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import lombok.SneakyThrows;
import java.net.CookieManager;
import java.net.ProxySelector;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
@@ -18,24 +18,16 @@ 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;
public class TikTokHttpRequestFactory implements TikTokHttpRequest {
private final CookieManager cookieManager;
private final Map<String, String> defaultHeaders;
private final TikTokCookieJar tikTokCookieJar;
private final HttpClient client;
private String query;
private Boolean sent;
private Map<String, String> defaultHeaders;
private TikTokCookieJar tikTokCookieJar;
public TikTokHttpRequestFactory(TikTokCookieJar tikTokCookieJar) {
cookieManager = new CookieManager();
this.tikTokCookieJar = tikTokCookieJar;
this.cookieManager = new CookieManager();
defaultHeaders = Constants.DefaultRequestHeaders();
client = HttpClient.newBuilder()
.cookieHandler(cookieManager)
@@ -43,8 +35,7 @@ public class TikTokHttpRequestFactory implements TikTokHttpRequest
.build();
}
public WebSocket.Builder openSocket()
{
public WebSocket.Builder openSocket() {
return client.newWebSocketBuilder();
}
@@ -52,26 +43,21 @@ public class TikTokHttpRequestFactory implements TikTokHttpRequest
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());
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());
for (var header : defaultHeaders.entrySet()) {
request.setHeader(header.getKey(), header.getValue());
}
if (query != null) {
var baseUri = uri.toString();
@@ -81,14 +67,12 @@ public class TikTokHttpRequestFactory implements TikTokHttpRequest
return GetContent(request.build());
}
public TikTokHttpRequest setHeader(String key, String value)
{
defaultHeaders.put(key,value);
public TikTokHttpRequest setHeader(String key, String value) {
defaultHeaders.put(key, value);
return this;
}
public TikTokHttpRequest setAgent( String value)
{
public TikTokHttpRequest setAgent(String value) {
defaultHeaders.put("User-Agent", value);
return this;
}
@@ -111,26 +95,18 @@ public class TikTokHttpRequestFactory implements TikTokHttpRequest
}
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() == 404) {
throw new TikTokLiveRequestException("Request responded with 404 NOT_FOUND");
}
if(response.statusCode() != 200)
{
throw new RuntimeException("Request was unsuccessful "+response.statusCode());
if (response.statusCode() != 200) {
throw new TikTokLiveRequestException("Request was unsuccessful " + response.statusCode());
}
var cookies = response.headers().allValues("Set-Cookie");
for(var cookie : cookies)
{
for (var cookie : cookies) {
var split = cookie.split(";")[0].split("=");
var uri = request.uri();
@@ -139,9 +115,9 @@ public class TikTokHttpRequestFactory implements TikTokHttpRequest
var value = split[1];
tikTokCookieJar.set(key, value);
var map = new HashMap<String,List<String>>();
map.put(key,List.of(value));
cookieManager.put(uri,map);
var map = new HashMap<String, List<String>>();
map.put(key, List.of(value));
cookieManager.put(uri, map);
}
return response.body();

View File

@@ -13,16 +13,13 @@ import io.github.jwdeveloper.tiktok.messages.WebcastResponse;
import java.net.URI;
import java.net.http.WebSocket;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Logger;
public class TikTokWebsocketClient {
public class TikTokWebSocketClient {
private final Logger logger;
private final Map<String, Object> clientParams;
private final ClientSettings clientSettings;
private final TikTokCookieJar tikTokCookieJar;
private final TikTokHttpRequestFactory factory;
@@ -33,15 +30,13 @@ public class TikTokWebsocketClient {
private boolean isConnected;
public TikTokWebsocketClient(Logger logger,
public TikTokWebSocketClient(Logger logger,
TikTokCookieJar tikTokCookieJar,
Map<String, Object> clientParams,
TikTokHttpRequestFactory factory,
ClientSettings clientSettings,
WebResponseHandler webResponseHandler,
TikTokEventHandler tikTokEventHandler) {
this.logger = logger;
this.clientParams = clientParams;
this.tikTokCookieJar = tikTokCookieJar;
this.clientSettings = clientSettings;
this.factory = factory;
@@ -78,7 +73,7 @@ public class TikTokWebsocketClient {
var headers = Constants.DefaultRequestHeaders();
var clone = new TreeMap<>(clientParams);
var clone = new TreeMap<>(clientSettings.getClientParameters());
clone.putAll(headers);
clone.put(name, value);
var url = webcastResponse.getSocketUrl();

View File

@@ -0,0 +1,41 @@
package io.github.jwdeveloper.tiktok;
import com.google.protobuf.InvalidProtocolBufferException;
import io.github.jwdeveloper.tiktok.messages.WebcastChatMessage;
import io.github.jwdeveloper.tiktok.messages.WebcastGiftMessage;
import io.github.jwdeveloper.tiktok.messages.WebcastLikeMessage;
import io.github.jwdeveloper.tiktok.messages.WebcastWebsocketMessage;
import org.junit.Test;
public class ParseMessagesTests extends TikTokBaseTest
{
// @Test
public void ShouldParseWebcastWebsocketMessage() throws InvalidProtocolBufferException {
var bytes = getFileBytesUtf("WebcastWebsocketMessage.bin");
var message = WebcastWebsocketMessage.parseFrom(bytes);
System.out.println("id: " + message.getId());
System.out.println("type: " + message.getType());
System.out.println("binary: " + message.getBinary().size());
}
@Test
public void ShouldParseLikeMessage() throws InvalidProtocolBufferException {
var bytes = getFileBytesUtf("LikeMessage.bin");
var message = WebcastLikeMessage.parseFrom(bytes);
}
@Test
public void ShouldParseGiftMessage() throws InvalidProtocolBufferException {
var bytes = getFileBytesUtf("MessageWebcastGiftMessage.bin");
var message = WebcastGiftMessage.parseFrom(bytes);
}
@Test
public void ShouldParseMessageWebcastChatMessage() throws InvalidProtocolBufferException {
var bytes = getFileBytesUtf("MessageWebcastChatMessage.bin");
var message = WebcastChatMessage.parseFrom(bytes);
}
}

View File

@@ -0,0 +1,34 @@
package io.github.jwdeveloper.tiktok;
import com.google.protobuf.InvalidProtocolBufferException;
import io.github.jwdeveloper.tiktok.messages.WebcastWebsocketMessage;
import java.io.IOException;
import java.util.Base64;
public class TikTokBaseTest
{
public byte[] getFileBytes(String path)
{
try {
var stream = getClass().getClassLoader().getResourceAsStream(path);
var bytes= stream.readAllBytes();
stream.close();
return bytes;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public byte[] getFileBytesUtf(String path)
{
try {
var stream = getClass().getClassLoader().getResourceAsStream(path);
var bytes= stream.readAllBytes();
stream.close();
return Base64.getDecoder().decode(bytes);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long