diff --git a/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokDisconnectedEvent.java b/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokDisconnectedEvent.java
index b5a3ddd..715ca04 100644
--- a/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokDisconnectedEvent.java
+++ b/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokDisconnectedEvent.java
@@ -30,14 +30,22 @@ import lombok.Getter;
@Getter
@EventMeta(eventType = EventType.Control)
public class TikTokDisconnectedEvent extends TikTokLiveClientEvent {
+ public static int UNKNOWN_CLOSE_CODE = -1;
+
+ /** Valid CloseFrame code or -1 for unknown */
+ private final int code;
private final String reason;
- public TikTokDisconnectedEvent(String reason) {
+ public TikTokDisconnectedEvent(int code, String reason) {
+ this.code = code;
this.reason = reason.isBlank() ? "None" : reason;
}
- public static TikTokDisconnectedEvent of(String reason)
- {
- return new TikTokDisconnectedEvent(reason);
+ public TikTokDisconnectedEvent(String reason) {
+ this(UNKNOWN_CLOSE_CODE, reason);
+ }
+
+ public boolean isUnknownCloseCode() {
+ return this.code == UNKNOWN_CLOSE_CODE;
}
}
\ No newline at end of file
diff --git a/API/src/main/java/io/github/jwdeveloper/tiktok/data/requests/LiveUserData.java b/API/src/main/java/io/github/jwdeveloper/tiktok/data/requests/LiveUserData.java
index a48d596..1da3045 100644
--- a/API/src/main/java/io/github/jwdeveloper/tiktok/data/requests/LiveUserData.java
+++ b/API/src/main/java/io/github/jwdeveloper/tiktok/data/requests/LiveUserData.java
@@ -22,7 +22,7 @@
*/
package io.github.jwdeveloper.tiktok.data.requests;
-import io.github.jwdeveloper.tiktok.data.models.users.User;
+import io.github.jwdeveloper.tiktok.live.LiveRoomInfo;
import lombok.*;
public class LiveUserData {
@@ -43,9 +43,7 @@ public class LiveUserData {
public static class Response {
private final String json;
private final UserStatus userStatus;
- private final String roomId;
- private final long startTime;
- private final User user;
+ private final LiveRoomInfo roomInfo;
public boolean isLiveOnline() {
return userStatus == LiveUserData.UserStatus.Live || userStatus == LiveUserData.UserStatus.LivePaused;
diff --git a/API/src/main/java/io/github/jwdeveloper/tiktok/data/settings/LiveClientSettings.java b/API/src/main/java/io/github/jwdeveloper/tiktok/data/settings/LiveClientSettings.java
index 201bee4..725b2a2 100644
--- a/API/src/main/java/io/github/jwdeveloper/tiktok/data/settings/LiveClientSettings.java
+++ b/API/src/main/java/io/github/jwdeveloper/tiktok/data/settings/LiveClientSettings.java
@@ -89,6 +89,16 @@ public class LiveClientSettings {
/** Throw an exception on 18+ Age Restriction */
private boolean throwOnAgeRestriction;
+ /** Use Eulerstream.com websocket for events
+ * @apiNote Requires API Key
+ */
+ private boolean useEulerstreamWebsocket;
+
+ /** Use Eulerstream.com enterprise endpoints
+ * @apiNote Requires API Key with
+ */
+ private boolean useEulerstreamEnterprise;
+
/**
* Optional: Sometimes not every messages from chat are send to TikTokLiveJava to fix this issue you can set sessionId.
*
This requires {@link #ttTargetIdc} also being set correctly for sessionid to be effective.
diff --git a/API/src/main/java/io/github/jwdeveloper/tiktok/live/LiveClient.java b/API/src/main/java/io/github/jwdeveloper/tiktok/live/LiveClient.java
index c0ce8a4..a30ff85 100644
--- a/API/src/main/java/io/github/jwdeveloper/tiktok/live/LiveClient.java
+++ b/API/src/main/java/io/github/jwdeveloper/tiktok/live/LiveClient.java
@@ -24,6 +24,7 @@ package io.github.jwdeveloper.tiktok.live;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.listener.ListenersManager;
+import io.github.jwdeveloper.tiktok.websocket.LiveClientStopType;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
@@ -49,16 +50,16 @@ public interface LiveClient {
/**
* Disconnects the connection.
- * @param type
- *
0 - Normal - Initiates disconnection and returns
- *
1 - Disconnects blocking and returns after closure
- *
2 - Disconnects and kills connection to websocket
- *
Default {@link #disconnect()} is 0
+ * @param type {@code LiveClientStopType}
+ * @see LiveClientStopType
*/
- void disconnect(int type);
+ void disconnect(LiveClientStopType type);
+ /**
+ * Disconnects with {@link LiveClientStopType#NORMAL}
+ */
default void disconnect() {
- disconnect(0);
+ disconnect(LiveClientStopType.NORMAL);
}
/**
diff --git a/API/src/main/java/io/github/jwdeveloper/tiktok/live/LiveRoomInfo.java b/API/src/main/java/io/github/jwdeveloper/tiktok/live/LiveRoomInfo.java
index e1dcd03..a3911fc 100644
--- a/API/src/main/java/io/github/jwdeveloper/tiktok/live/LiveRoomInfo.java
+++ b/API/src/main/java/io/github/jwdeveloper/tiktok/live/LiveRoomInfo.java
@@ -49,5 +49,7 @@ public interface LiveRoomInfo
String getTitle();
User getHost();
List getUsersRanking();
+ String getLanguage();
ConnectionState getConnectionState();
+ void copy(LiveRoomInfo roomInfo);
}
\ No newline at end of file
diff --git a/API/src/main/java/io/github/jwdeveloper/tiktok/websocket/LiveClientStopType.java b/API/src/main/java/io/github/jwdeveloper/tiktok/websocket/LiveClientStopType.java
new file mode 100644
index 0000000..0cb78b9
--- /dev/null
+++ b/API/src/main/java/io/github/jwdeveloper/tiktok/websocket/LiveClientStopType.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2023-2024 jwdeveloper jacekwoln@gmail.com
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package io.github.jwdeveloper.tiktok.websocket;
+
+public enum LiveClientStopType
+{
+ /**
+ * Initiates the websocket close handshake. This method does not block
In oder to make sure
+ * the connection is closed use {@link LiveClientStopType#CLOSE_BLOCKING}
+ */
+ NORMAL,
+ /**
+ * Same as {@link LiveClientStopType#NORMAL} but blocks until the websocket closed or failed to do so.
+ *
+ * @apiNote Can throw {@link InterruptedException} when/if the threads get interrupted
+ */
+ CLOSE_BLOCKING,
+ /**
+ * This will close the connection immediately without a proper close handshake.
+ * The code and the message therefore won't be transferred over the wire also they will be forwarded to onClose/onWebsocketClose. */
+ DISCONNECT
+}
\ No newline at end of file
diff --git a/API/src/main/java/io/github/jwdeveloper/tiktok/websocket/LiveSocketClient.java b/API/src/main/java/io/github/jwdeveloper/tiktok/websocket/LiveSocketClient.java
index f04e8b5..9f09edf 100644
--- a/API/src/main/java/io/github/jwdeveloper/tiktok/websocket/LiveSocketClient.java
+++ b/API/src/main/java/io/github/jwdeveloper/tiktok/websocket/LiveSocketClient.java
@@ -27,6 +27,6 @@ import io.github.jwdeveloper.tiktok.live.LiveClient;
public interface LiveSocketClient {
void start(LiveConnectionData.Response webcastResponse, LiveClient tikTokLiveClient);
- void stop(int type);
+ void stop(LiveClientStopType type);
boolean isConnected();
}
\ No newline at end of file
diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClient.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClient.java
index 08d65a4..01b0f09 100644
--- a/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClient.java
+++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClient.java
@@ -35,7 +35,7 @@ import io.github.jwdeveloper.tiktok.listener.ListenersManager;
import io.github.jwdeveloper.tiktok.live.*;
import io.github.jwdeveloper.tiktok.messages.webcast.ProtoMessageFetchResult;
import io.github.jwdeveloper.tiktok.models.ConnectionState;
-import io.github.jwdeveloper.tiktok.websocket.LiveSocketClient;
+import io.github.jwdeveloper.tiktok.websocket.*;
import lombok.Getter;
import java.util.Base64;
@@ -79,11 +79,14 @@ public class TikTokLiveClient implements LiveClient
public void connect() {
try {
- tryConnect();
+ if (clientSettings.isUseEulerstreamWebsocket())
+ tryEulerConnect();
+ else
+ tryConnect();
} catch (TikTokLiveException e) {
setState(ConnectionState.DISCONNECTED);
tikTokEventHandler.publish(this, new TikTokErrorEvent(e));
- tikTokEventHandler.publish(this, new TikTokDisconnectedEvent("Exception: "+e.getMessage()));
+ tikTokEventHandler.publish(this, new TikTokDisconnectedEvent("Exception: " + e.getMessage()));
if (e instanceof TikTokLiveOfflineHostException && clientSettings.isRetryOnConnectionFailure()) {
try {
@@ -101,6 +104,17 @@ public class TikTokLiveClient implements LiveClient
}
}
+ private void tryEulerConnect() {
+ if (!roomInfo.hasConnectionState(ConnectionState.DISCONNECTED)) {
+ throw new TikTokLiveException("Already connected");
+ }
+
+ setState(ConnectionState.CONNECTING);
+ tikTokEventHandler.publish(this, new TikTokConnectingEvent());
+ webSocketClient.start(null, this);
+ setState(ConnectionState.CONNECTED);
+ }
+
public void tryConnect() {
if (!roomInfo.hasConnectionState(ConnectionState.DISCONNECTED)) {
throw new TikTokLiveException("Already connected");
@@ -110,8 +124,7 @@ public class TikTokLiveClient implements LiveClient
tikTokEventHandler.publish(this, new TikTokConnectingEvent());
var userDataRequest = new LiveUserData.Request(roomInfo.getHostName());
var userData = httpClient.fetchLiveUserData(userDataRequest);
- roomInfo.setStartTime(userData.getStartTime());
- roomInfo.setRoomId(userData.getRoomId());
+ roomInfo.copy(userData.getRoomInfo());
if (userData.getUserStatus() == LiveUserData.UserStatus.Offline)
throw new TikTokLiveOfflineHostException("User is offline: " + roomInfo.getHostName(), userData, null);
@@ -119,7 +132,7 @@ public class TikTokLiveClient implements LiveClient
if (userData.getUserStatus() == LiveUserData.UserStatus.NotFound)
throw new TikTokLiveUnknownHostException("User not found: " + roomInfo.getHostName(), userData, null);
- var liveDataRequest = new LiveData.Request(userData.getRoomId());
+ var liveDataRequest = new LiveData.Request(userData.getRoomInfo().getRoomId());
var liveData = httpClient.fetchLiveData(liveDataRequest);
if (liveData.isAgeRestricted() && clientSettings.isThrowOnAgeRestriction())
@@ -143,9 +156,9 @@ public class TikTokLiveClient implements LiveClient
throw new TikTokLivePreConnectionException(preconnectEvent);
if (clientSettings.isFetchGifts())
- giftManager.attachGiftsList(httpClient.fetchRoomGiftsData(userData.getRoomId()).getGifts());
+ giftManager.attachGiftsList(httpClient.fetchRoomGiftsData(userData.getRoomInfo().getRoomId()).getGifts());
- var liveConnectionRequest = new LiveConnectionData.Request(userData.getRoomId());
+ var liveConnectionRequest = new LiveConnectionData.Request(userData.getRoomInfo().getRoomId());
var liveConnectionData = httpClient.fetchLiveConnectionData(liveConnectionRequest);
webSocketClient.start(liveConnectionData, this);
@@ -153,7 +166,7 @@ public class TikTokLiveClient implements LiveClient
tikTokEventHandler.publish(this, new TikTokRoomInfoEvent(roomInfo));
}
- public void disconnect(int type) {
+ public void disconnect(LiveClientStopType type) {
if (webSocketClient.isConnected())
webSocketClient.stop(type);
if (!roomInfo.hasConnectionState(ConnectionState.DISCONNECTED))
diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClientBuilder.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClientBuilder.java
index 9370b11..ff1316c 100644
--- a/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClientBuilder.java
+++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClientBuilder.java
@@ -42,6 +42,7 @@ import io.github.jwdeveloper.tiktok.mappers.handlers.TikTokGiftEventHandler;
import io.github.jwdeveloper.tiktok.mappers.handlers.TikTokRoomInfoEventHandler;
import io.github.jwdeveloper.tiktok.mappers.handlers.TikTokSocialMediaEventHandler;
import io.github.jwdeveloper.tiktok.websocket.*;
+import io.github.jwdeveloper.tiktok.websocket.euler.TikTokWebSocketEulerClient;
import java.util.*;
import java.util.concurrent.CompletableFuture;
@@ -137,7 +138,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
dependance.registerSingleton(LiveSocketClient.class, TikTokWebSocketOfflineClient.class);
dependance.registerSingleton(LiveHttpClient.class, TikTokLiveHttpOfflineClient.class);
} else {
- dependance.registerSingleton(LiveSocketClient.class, TikTokWebSocketClient.class);
+ dependance.registerSingleton(LiveSocketClient.class, clientSettings.isUseEulerstreamWebsocket() ? TikTokWebSocketEulerClient.class : TikTokWebSocketClient.class);
dependance.registerSingleton(LiveHttpClient.class, TikTokLiveHttpClient.class);
}
diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveHttpClient.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveHttpClient.java
index 11759c0..cba2209 100644
--- a/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveHttpClient.java
+++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveHttpClient.java
@@ -45,6 +45,8 @@ public class TikTokLiveHttpClient implements LiveHttpClient
*/
private static final String TIKTOK_SIGN_API = "https://tiktok.eulerstream.com/webcast/fetch";
private static final String TIKTOK_CHAT_URL = "https://tiktok.eulerstream.com/webcast/chat";
+ private static final String TIKTOK_SIGN_ENTERPRISE_API = "https://tiktok.enterprise.eulerstream.com/webcast/fetch";
+ private static final String TIKTOK_CHAT_ENTERPRISE_URL = "https://tiktok.enterprise.eulerstream.com/webcast/chat";
private static final String TIKTOK_URL_WEB = "https://www.tiktok.com/";
private static final String TIKTOK_URL_WEBCAST = "https://webcast.tiktok.com/webcast/";
private static final String TIKTOK_ROOM_GIFTS_URL = TIKTOK_URL_WEBCAST+"gift/list/";
@@ -53,9 +55,6 @@ public class TikTokLiveHttpClient implements LiveHttpClient
private final HttpClientFactory httpFactory;
private final LiveClientSettings clientSettings;
- private final LiveUserDataMapper liveUserDataMapper;
- private final LiveDataMapper liveDataMapper;
- private final GiftsDataMapper giftsDataMapper;
private final Logger logger;
@Inject
@@ -63,9 +62,6 @@ public class TikTokLiveHttpClient implements LiveHttpClient
this.httpFactory = factory;
this.clientSettings = factory.getLiveClientSettings();
this.logger = LoggerFactory.create("HttpClient-"+hashCode(), clientSettings);
- liveUserDataMapper = new LiveUserDataMapper();
- liveDataMapper = new LiveDataMapper();
- giftsDataMapper = new GiftsDataMapper();
}
public TikTokLiveHttpClient(Consumer consumer) {
@@ -95,7 +91,7 @@ public class TikTokLiveHttpClient implements LiveHttpClient
throw new TikTokLiveRequestException("Unable to fetch gifts information's - "+result);
var json = result.getContent();
- return giftsDataMapper.mapRoom(json);
+ return GiftsDataMapper.mapRoom(json);
}
@Override
@@ -125,7 +121,7 @@ public class TikTokLiveHttpClient implements LiveHttpClient
throw new TikTokLiveRequestException("Unable to get information's about user - "+result);
var json = result.getContent();
- return liveUserDataMapper.map(json, logger);
+ return LiveUserDataMapper.map(json, logger);
}
@Override
@@ -153,7 +149,7 @@ public class TikTokLiveHttpClient implements LiveHttpClient
throw new TikTokLiveRequestException("Unable to get info about live room - "+result);
var json = result.getContent();
- return liveDataMapper.map(json);
+ return LiveDataMapper.map(json);
}
@Override
@@ -204,10 +200,10 @@ public class TikTokLiveHttpClient implements LiveHttpClient
body.addProperty("sessionId", clientSettings.getSessionId());
body.addProperty("ttTargetIdc", clientSettings.getTtTargetIdc());
body.addProperty("roomId", roomInfo.getRoomId());
- HttpClientBuilder builder = httpFactory.client(TIKTOK_CHAT_URL)
+ HttpClientBuilder builder = httpFactory.client(clientSettings.isUseEulerstreamEnterprise() ? TIKTOK_CHAT_ENTERPRISE_URL : TIKTOK_CHAT_URL)
.withHeader("Content-Type", "application/json");
if (clientSettings.getApiKey() != null)
- builder.withHeader("apiKey", clientSettings.getApiKey());
+ builder.withHeader("x-api-key", clientSettings.getApiKey());
var result = builder.withBody(HttpRequest.BodyPublishers.ofString(body.toString())).build().toJsonResponse();
return result.isSuccess();
}
@@ -225,14 +221,14 @@ public class TikTokLiveHttpClient implements LiveHttpClient
}
protected ActionResult> getByteResponse(String room_id) {
- HttpClientBuilder builder = httpFactory.client(TIKTOK_SIGN_API)
+ HttpClientBuilder builder = httpFactory.client(clientSettings.isUseEulerstreamEnterprise() ? TIKTOK_SIGN_ENTERPRISE_API : TIKTOK_SIGN_API)
.withParam("client", "ttlive-java")
.withParam("room_id", room_id);
if (clientSettings.getSessionId() != null) // Allows receiving of all comments and Subscribe Events
builder.withParam("session_id", clientSettings.getSessionId());
if (clientSettings.getApiKey() != null)
- builder.withParam("apiKey", clientSettings.getApiKey());
+ builder.withHeader("x-api-key", clientSettings.getApiKey());
var result = builder.build().toHttpResponse(HttpResponse.BodyHandlers.ofByteArray());
@@ -241,4 +237,4 @@ public class TikTokLiveHttpClient implements LiveHttpClient
return result;
}
-}
+}
\ No newline at end of file
diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveHttpOfflineClient.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveHttpOfflineClient.java
index a9dd5be..dbcdda1 100644
--- a/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveHttpOfflineClient.java
+++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveHttpOfflineClient.java
@@ -40,7 +40,7 @@ public class TikTokLiveHttpOfflineClient implements LiveHttpClient {
@Override
public LiveUserData.Response fetchLiveUserData(LiveUserData.Request request) {
- return new LiveUserData.Response("", LiveUserData.UserStatus.Live, "offline_room_id", 0, null);
+ return new LiveUserData.Response("", LiveUserData.UserStatus.Live, null);
}
@Override
diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokRoomInfo.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokRoomInfo.java
index a57dc0d..2420047 100644
--- a/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokRoomInfo.java
+++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokRoomInfo.java
@@ -32,37 +32,43 @@ import java.util.LinkedList;
import java.util.List;
@Data
-public class TikTokRoomInfo implements LiveRoomInfo {
- private String roomId;
+public class TikTokRoomInfo implements LiveRoomInfo
+{
+ private String roomId;
+ private int likesCount;
+ private int viewersCount;
+ private int totalViewersCount;
+ private long startTime;
+ private boolean ageRestricted;
+ private User host;
+ private List usersRanking = new LinkedList<>();
+ private String hostName;
+ private String title;
+ private String language = "en";
+ private ConnectionState connectionState = ConnectionState.DISCONNECTED;
- private int likesCount;
+ public boolean hasConnectionState(ConnectionState state) {
+ return connectionState == state;
+ }
- private int viewersCount;
+ public void updateRanking(List rankingUsers) {
+ usersRanking.clear();
+ usersRanking.addAll(rankingUsers);
+ }
- private int totalViewersCount;
-
- private long startTime;
-
- private boolean ageRestricted;
-
- private User host;
-
- private List usersRanking = new LinkedList<>();
-
- private String hostName;
-
- private String title;
-
- private String language = "en";
-
- private ConnectionState connectionState = ConnectionState.DISCONNECTED;
-
- public boolean hasConnectionState(ConnectionState state) {
- return connectionState == state;
- }
-
- public void updateRanking(List rankingUsers) {
- usersRanking.clear();
- usersRanking.addAll(rankingUsers);
- }
+ @Override
+ public void copy(LiveRoomInfo roomInfo) {
+ this.roomId = roomInfo.getRoomId();
+ this.likesCount = roomInfo.getLikesCount();
+ this.viewersCount = roomInfo.getViewersCount();
+ this.totalViewersCount = roomInfo.getTotalViewersCount();
+ this.startTime = roomInfo.getStartTime();
+ this.ageRestricted = roomInfo.isAgeRestricted();
+ this.host = roomInfo.getHost();
+ this.usersRanking = roomInfo.getUsersRanking();
+ this.hostName = roomInfo.getHostName();
+ this.title = roomInfo.getTitle();
+ this.language = roomInfo.getLanguage();
+ this.connectionState = roomInfo.getConnectionState();
+ }
}
\ No newline at end of file
diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/http/mappers/GiftsDataMapper.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/http/mappers/GiftsDataMapper.java
index 4a14fd9..e4d6b98 100644
--- a/Client/src/main/java/io/github/jwdeveloper/tiktok/http/mappers/GiftsDataMapper.java
+++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/http/mappers/GiftsDataMapper.java
@@ -32,7 +32,7 @@ import java.util.List;
public class GiftsDataMapper {
- public GiftsData.Response map(String json) {
+ public static GiftsData.Response map(String json) {
var parsedJson = JsonParser.parseString(json);
var jsonObject = parsedJson.getAsJsonObject();
var gifts = jsonObject.entrySet()
@@ -43,7 +43,7 @@ public class GiftsDataMapper {
return new GiftsData.Response(json, gifts);
}
- private Gift mapSingleGift(JsonElement jsonElement) {
+ private static Gift mapSingleGift(JsonElement jsonElement) {
var jsonObject = jsonElement.getAsJsonObject();
var id = jsonObject.get("id").getAsInt();
@@ -53,7 +53,7 @@ public class GiftsDataMapper {
return new Gift(id, name, diamondCost, new Picture(image), jsonObject);
}
- public GiftsData.Response mapRoom(String json) {
+ public static GiftsData.Response mapRoom(String json) {
var parsedJson = JsonParser.parseString(json);
var jsonObject = parsedJson.getAsJsonObject();
if (jsonObject.get("data") instanceof JsonObject data && data.get("gifts") instanceof JsonArray giftArray) {
@@ -69,7 +69,7 @@ public class GiftsDataMapper {
return new GiftsData.Response("", List.of());
}
- private Gift mapSingleRoomGift(JsonElement jsonElement) {
+ private static Gift mapSingleRoomGift(JsonElement jsonElement) {
var jsonObject = jsonElement.getAsJsonObject();
var id = jsonObject.get("id").getAsInt();
diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/http/mappers/LiveDataMapper.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/http/mappers/LiveDataMapper.java
index b51d5c7..d5c9dba 100644
--- a/Client/src/main/java/io/github/jwdeveloper/tiktok/http/mappers/LiveDataMapper.java
+++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/http/mappers/LiveDataMapper.java
@@ -41,7 +41,7 @@ public class LiveDataMapper {
* 3 - ?
* 4 - Offline
*/
- public LiveData.Response map(String json) {
+ public static LiveData.Response map(String json) {
var response = new LiveData.Response();
response.setJson(json);
@@ -128,7 +128,7 @@ public class LiveDataMapper {
return response;
}
- public User getUser(JsonObject jsonElement) {
+ public static User getUser(JsonObject jsonElement) {
var id = jsonElement.get("id").getAsLong();
var name = jsonElement.get("display_id").getAsString();
var profileName = jsonElement.get("nickname").getAsString();
diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/http/mappers/LiveUserDataMapper.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/http/mappers/LiveUserDataMapper.java
index c60d056..b4ecb62 100644
--- a/Client/src/main/java/io/github/jwdeveloper/tiktok/http/mappers/LiveUserDataMapper.java
+++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/http/mappers/LiveUserDataMapper.java
@@ -23,6 +23,7 @@
package io.github.jwdeveloper.tiktok.http.mappers;
import com.google.gson.*;
+import io.github.jwdeveloper.tiktok.*;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.data.requests.LiveUserData;
@@ -33,7 +34,7 @@ import java.util.logging.Logger;
public class LiveUserDataMapper
{
- public LiveUserData.Response map(String json, Logger logger) {
+ public static LiveUserData.Response map(String json, Logger logger) {
try {
var jsonObject = JsonParser.parseString(json).getAsJsonObject();
@@ -43,14 +44,14 @@ public class LiveUserDataMapper
throw new TikTokLiveRequestException("fetchRoomIdFromTiktokApi -> Unable to fetch roomID, contact the developer");
}
if (message.equals("user_not_found")) {
- return new LiveUserData.Response(json, LiveUserData.UserStatus.NotFound, "", -1, null);
+ return new LiveUserData.Response(json, LiveUserData.UserStatus.NotFound, null);
}
//live -> status 2
//live paused -> 3
//not live -> status 4
var element = jsonObject.get("data");
if (element.isJsonNull()) {
- return new LiveUserData.Response(json, LiveUserData.UserStatus.NotFound, "", -1, null);
+ return new LiveUserData.Response(json, LiveUserData.UserStatus.NotFound, null);
}
var data = element.getAsJsonObject();
var user = data.getAsJsonObject("user");
@@ -58,8 +59,17 @@ public class LiveUserDataMapper
var roomId = user.get("roomId").getAsString();
var status = user.get("status").getAsInt();
+ TikTokRoomInfo roomInfo = new TikTokRoomInfo();
+ roomInfo.setRoomId(roomId);
+
var liveRoom = data.getAsJsonObject("liveRoom");
- long startTime = liveRoom.get("startTime").getAsLong();
+
+ roomInfo.setTitle(liveRoom.get("title").getAsString());
+ roomInfo.setStartTime(liveRoom.get("startTime").getAsLong());
+ roomInfo.setTitle(liveRoom.get("title").getAsString());
+ roomInfo.setViewersCount(liveRoom.getAsJsonObject("liveRoomStats").get("userCount").getAsInt());
+ roomInfo.setTotalViewersCount(liveRoom.getAsJsonObject("liveRoomStats").get("enterCount").getAsInt());
+ roomInfo.setAgeRestricted(jsonObject.get("statusCode").getAsInt() == TikTokLiveHttpClient.TIKTOK_AGE_RESTRICTED_CODE);
var statusEnum = switch (status) {
case 2 -> LiveUserData.UserStatus.Live;
@@ -78,10 +88,13 @@ public class LiveUserDataMapper
stats.get("followerCount").getAsLong(),
List.of());
- return new LiveUserData.Response(json, statusEnum, roomId, startTime, foundUser);
+ roomInfo.setHost(foundUser);
+ roomInfo.setHostName(foundUser.getName());
+
+ return new LiveUserData.Response(json, statusEnum, roomInfo);
} catch (JsonSyntaxException | IllegalStateException e) {
logger.warning("Malformed Json: '"+json+"' - Error Message: "+e.getMessage());
- return new LiveUserData.Response(json, LiveUserData.UserStatus.NotFound, "", -1, null);
+ return new LiveUserData.Response(json, LiveUserData.UserStatus.NotFound, null);
}
}
}
\ No newline at end of file
diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/websocket/TikTokWebSocketClient.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/websocket/TikTokWebSocketClient.java
index aa5a630..6a2fc60 100644
--- a/Client/src/main/java/io/github/jwdeveloper/tiktok/websocket/TikTokWebSocketClient.java
+++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/websocket/TikTokWebSocketClient.java
@@ -57,7 +57,7 @@ public class TikTokWebSocketClient implements LiveSocketClient {
@Override
public void start(LiveConnectionData.Response connectionData, LiveClient liveClient) {
if (isConnected())
- stop(0);
+ stop(LiveClientStopType.NORMAL);
messageHandler.handle(liveClient, connectionData.getWebcastResponse());
@@ -77,7 +77,7 @@ public class TikTokWebSocketClient implements LiveSocketClient {
connectDefault();
}
- private void connectDefault() {
+ public void connectDefault() {
try {
webSocketClient.connect();
heartbeatTask.run(webSocketClient, clientSettings.getPingInterval());
@@ -129,17 +129,17 @@ public class TikTokWebSocketClient implements LiveSocketClient {
}
}
- public void stop(int type) {
+ public void stop(LiveClientStopType type) {
if (isConnected()) {
switch (type) {
- case 1 -> {
+ case CLOSE_BLOCKING -> {
try {
webSocketClient.closeBlocking();
} catch (InterruptedException e) {
throw new TikTokLiveException("Failed to stop the websocket");
}
}
- case 2 -> webSocketClient.closeConnection(CloseFrame.NORMAL, "");
+ case DISCONNECT -> webSocketClient.closeConnection(CloseFrame.NORMAL, "");
default -> webSocketClient.close();
}
heartbeatTask.stop(webSocketClient);
diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/websocket/TikTokWebSocketListener.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/websocket/TikTokWebSocketListener.java
index 23b2a8d..1f124e2 100644
--- a/Client/src/main/java/io/github/jwdeveloper/tiktok/websocket/TikTokWebSocketListener.java
+++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/websocket/TikTokWebSocketListener.java
@@ -39,9 +39,9 @@ import java.util.*;
public class TikTokWebSocketListener extends WebSocketClient {
- private final LiveMessagesHandler messagesHandler;
- private final LiveEventsHandler eventHandler;
- private final LiveClient liveClient;
+ protected final LiveMessagesHandler messagesHandler;
+ protected final LiveEventsHandler eventHandler;
+ protected final LiveClient liveClient;
public TikTokWebSocketListener(URI serverUri,
Map httpHeaders,
@@ -67,7 +67,7 @@ public class TikTokWebSocketListener extends WebSocketClient {
}
}
- private void handleBinary(byte[] buffer) {
+ protected void handleBinary(byte[] buffer) {
var websocketPushFrameOptional = getWebcastPushFrame(buffer);
if (websocketPushFrameOptional.isEmpty()) {
return;
@@ -97,7 +97,7 @@ public class TikTokWebSocketListener extends WebSocketClient {
@Override
public void onClose(int code, String reason, boolean remote) {
- eventHandler.publish(liveClient, new TikTokDisconnectedEvent(reason));
+ eventHandler.publish(liveClient, new TikTokDisconnectedEvent(code, reason));
liveClient.disconnect();
}
@@ -111,12 +111,8 @@ public class TikTokWebSocketListener extends WebSocketClient {
private Optional getWebcastPushFrame(byte[] buffer) {
try {
- var websocketMessage = WebcastPushFrame.parseFrom(buffer);
- if (websocketMessage.getPayload().isEmpty()) {
- return Optional.empty();
- }
- return Optional.of(websocketMessage);
- } catch (Exception e) {
+ return Optional.of(WebcastPushFrame.parseFrom(buffer)).filter(msg -> !msg.getPayload().isEmpty());
+ } catch (Exception e) {
throw new TikTokProtocolBufferException("Unable to parse WebcastPushFrame", buffer, e);
}
}
diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/websocket/TikTokWebSocketOfflineClient.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/websocket/TikTokWebSocketOfflineClient.java
index f7cbdc8..3a73760 100644
--- a/Client/src/main/java/io/github/jwdeveloper/tiktok/websocket/TikTokWebSocketOfflineClient.java
+++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/websocket/TikTokWebSocketOfflineClient.java
@@ -44,7 +44,7 @@ public class TikTokWebSocketOfflineClient implements LiveSocketClient {
}
@Override
- public void stop(int type) {
+ public void stop(LiveClientStopType type) {
if (liveClient != null)
handler.publish(liveClient, new TikTokDisconnectedEvent("Stopping"));
}
diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/websocket/euler/TikTokWebSocketEulerClient.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/websocket/euler/TikTokWebSocketEulerClient.java
new file mode 100644
index 0000000..6d0fcd4
--- /dev/null
+++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/websocket/euler/TikTokWebSocketEulerClient.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2023-2024 jwdeveloper jacekwoln@gmail.com
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package io.github.jwdeveloper.tiktok.websocket.euler;
+
+import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData;
+import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
+import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
+import io.github.jwdeveloper.tiktok.live.*;
+import io.github.jwdeveloper.tiktok.websocket.*;
+import org.java_websocket.client.WebSocketClient;
+import org.java_websocket.framing.CloseFrame;
+
+import java.net.URI;
+import java.util.HashMap;
+
+public class TikTokWebSocketEulerClient implements LiveSocketClient {
+
+ private final LiveClientSettings clientSettings;
+ private final LiveMessagesHandler messageHandler;
+ private final LiveEventsHandler tikTokEventHandler;
+ private WebSocketClient webSocketClient;
+
+ public TikTokWebSocketEulerClient(
+ LiveClientSettings clientSettings,
+ LiveMessagesHandler messageHandler,
+ LiveEventsHandler tikTokEventHandler)
+ {
+ this.clientSettings = clientSettings;
+ this.messageHandler = messageHandler;
+ this.tikTokEventHandler = tikTokEventHandler;
+ }
+
+ @Override
+ public void start(LiveConnectionData.Response connectionData, LiveClient liveClient) {
+ if (isConnected())
+ stop(LiveClientStopType.NORMAL);
+
+ String url = "wss://ws.eulerstream.com?uniqueId=%s&apiKey=%s&features.rawMessages=true".formatted(liveClient.getRoomInfo().getHostName(), clientSettings.getApiKey())
+ + (clientSettings.isUseEulerstreamWebsocket() ? "&features.useEnterpriseApi=true" : "");
+
+ webSocketClient = new TikTokWebSocketEulerListener(
+ URI.create(url),
+ new HashMap<>(clientSettings.getHttpSettings().getHeaders()),
+ clientSettings.getHttpSettings().getTimeout().toMillisPart(),
+ messageHandler,
+ tikTokEventHandler,
+ liveClient);
+
+ connect();
+ }
+
+ public void connect() {
+ try {
+ webSocketClient.connect();
+ } catch (Exception e) {
+ throw new TikTokLiveException("Failed to connect to the websocket", e);
+ }
+ }
+
+ public void stop(LiveClientStopType type) {
+ if (isConnected()) {
+ switch (type) {
+ case CLOSE_BLOCKING -> {
+ try {
+ webSocketClient.closeBlocking();
+ } catch (InterruptedException e) {
+ throw new TikTokLiveException("Failed to stop the websocket");
+ }
+ }
+ case DISCONNECT -> webSocketClient.closeConnection(CloseFrame.NORMAL, "");
+ default -> webSocketClient.close();
+ }
+ }
+ webSocketClient = null;
+ }
+
+ public boolean isConnected() {
+ return webSocketClient != null && webSocketClient.isOpen();
+ }
+}
\ No newline at end of file
diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/websocket/euler/TikTokWebSocketEulerListener.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/websocket/euler/TikTokWebSocketEulerListener.java
new file mode 100644
index 0000000..f7f4a8c
--- /dev/null
+++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/websocket/euler/TikTokWebSocketEulerListener.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2023-2024 jwdeveloper jacekwoln@gmail.com
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+package io.github.jwdeveloper.tiktok.websocket.euler;
+
+import com.google.gson.*;
+import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent;
+import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomInfoEvent;
+import io.github.jwdeveloper.tiktok.data.requests.LiveUserData;
+import io.github.jwdeveloper.tiktok.http.mappers.LiveUserDataMapper;
+import io.github.jwdeveloper.tiktok.live.*;
+import io.github.jwdeveloper.tiktok.websocket.TikTokWebSocketListener;
+
+import java.net.URI;
+import java.util.Map;
+
+public class TikTokWebSocketEulerListener extends TikTokWebSocketListener
+{
+
+ public TikTokWebSocketEulerListener(URI serverUri,
+ Map httpHeaders,
+ int connectTimeout,
+ LiveMessagesHandler messageHandler,
+ LiveEventsHandler tikTokEventHandler,
+ LiveClient tikTokLiveClient) {
+ super(serverUri, httpHeaders, connectTimeout, messageHandler, tikTokEventHandler, tikTokLiveClient);
+ }
+
+ @Override
+ public void onMessage(String raw) {
+ try {
+ JsonElement element = JsonParser.parseString(raw);
+ if (element instanceof JsonObject o) {
+ if (o.get("messages") instanceof JsonArray msgs) {
+ for (JsonElement msg : msgs) {
+ if (msg instanceof JsonObject oMsg) {
+ switch (oMsg.get("type").getAsString()) { // Should only receive these 2 types ever
+ case "workerInfo" -> liveClient.getLogger().info(oMsg.toString()); // Always 1st message
+ case "roomInfo" -> { // Always 2nd message
+ LiveUserData.Response data = LiveUserDataMapper.map(oMsg.getAsJsonObject("data").getAsJsonObject("data").getAsJsonObject("raw").toString(), liveClient.getLogger());
+ liveClient.getRoomInfo().copy(data.getRoomInfo());
+ eventHandler.publish(liveClient, new TikTokRoomInfoEvent(liveClient.getRoomInfo()));
+ }
+ }
+ }
+ }
+ }
+ } else
+ throw new IllegalArgumentException("Invalid JsonObject: "+element);
+ } catch (Exception e) {
+ eventHandler.publish(liveClient, new TikTokErrorEvent(e));
+ }
+ if (isOpen()) {
+ sendPing();
+ }
+ }
+}
\ No newline at end of file