From 54433af65f0a0c7800268a063e4f1d08f88deec2 Mon Sep 17 00:00:00 2001 From: kohlerpop1 Date: Mon, 15 Sep 2025 23:12:43 -0400 Subject: [PATCH 1/4] Quick string alteration for proper state! --- .../data/events/TikTokDisconnectedEvent.java | 10 +- .../tiktok/data/requests/LiveUserData.java | 6 +- .../data/settings/LiveClientSettings.java | 5 + .../jwdeveloper/tiktok/live/LiveRoomInfo.java | 2 + .../jwdeveloper/tiktok/TikTokLiveClient.java | 27 ++++-- .../tiktok/TikTokLiveClientBuilder.java | 3 +- .../tiktok/TikTokLiveHttpClient.java | 18 ++-- .../tiktok/TikTokLiveHttpOfflineClient.java | 2 +- .../jwdeveloper/tiktok/TikTokRoomInfo.java | 72 +++++++------- .../tiktok/http/mappers/GiftsDataMapper.java | 8 +- .../tiktok/http/mappers/LiveDataMapper.java | 4 +- .../http/mappers/LiveUserDataMapper.java | 25 +++-- .../websocket/TikTokWebSocketClient.java | 2 +- .../websocket/TikTokWebSocketListener.java | 18 ++-- .../TikTokWebSocketOfflineClient.java | 2 +- .../euler/TikTokWebSocketEulerClient.java | 97 +++++++++++++++++++ .../euler/TikTokWebSocketEulerListener.java | 76 +++++++++++++++ 17 files changed, 288 insertions(+), 89 deletions(-) create mode 100644 Client/src/main/java/io/github/jwdeveloper/tiktok/websocket/euler/TikTokWebSocketEulerClient.java create mode 100644 Client/src/main/java/io/github/jwdeveloper/tiktok/websocket/euler/TikTokWebSocketEulerListener.java 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 b5a3ddd2..2f573581 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,12 @@ @Getter @EventMeta(eventType = EventType.Control) public class TikTokDisconnectedEvent extends TikTokLiveClientEvent { + /** 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); - } } \ 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 a48d5968..1da30452 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 Request(String userName) { 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 201bee4d..fbe81786 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,11 @@ 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; + /** * 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/LiveRoomInfo.java b/API/src/main/java/io/github/jwdeveloper/tiktok/live/LiveRoomInfo.java index e1dcd032..a3911fcf 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/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClient.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClient.java index 08d65a45..193d4517 100644 --- a/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClient.java +++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClient.java @@ -79,11 +79,14 @@ public TikTokLiveClient( 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(-1, "Exception: " + e.getMessage())); if (e instanceof TikTokLiveOfflineHostException && clientSettings.isRetryOnConnectionFailure()) { try { @@ -101,6 +104,17 @@ public void connect() { } } + 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 void tryConnect() { 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 void tryConnect() { 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 void tryConnect() { 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); 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 9370b11a..ff1316cf 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.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 LiveClient build() { 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 11759c0e..590afd0f 100644 --- a/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveHttpClient.java +++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveHttpClient.java @@ -53,9 +53,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 +60,6 @@ public TikTokLiveHttpClient(HttpClientFactory factory) { 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 +89,7 @@ public GiftsData.Response getRoomGiftsData(String room_id) { 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 +119,7 @@ public LiveUserData.Response getLiveUserData(LiveUserData.Request request) { 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 +147,7 @@ public LiveData.Response getLiveData(LiveData.Request request) { throw new TikTokLiveRequestException("Unable to get info about live room - "+result); var json = result.getContent(); - return liveDataMapper.map(json); + return LiveDataMapper.map(json); } @Override @@ -207,7 +201,7 @@ public boolean requestSendChat(LiveRoomInfo roomInfo, String content) { HttpClientBuilder builder = httpFactory.client(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(); } @@ -232,7 +226,7 @@ protected ActionResult> getByteResponse(String 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 +235,4 @@ protected ActionResult> getByteResponse(String room_id) { 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 a9dd5beb..dbcdda1e 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 GiftsData.Response fetchRoomGiftsData(String room_id) { @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 a57dc0d6..2420047c 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.List; @Data -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; - - public boolean hasConnectionState(ConnectionState state) { - return connectionState == state; - } - - public void updateRanking(List rankingUsers) { - usersRanking.clear(); - usersRanking.addAll(rankingUsers); - } +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; + + 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 4a14fd9b..e4d6b98f 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 @@ 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 GiftsData.Response map(String json) { 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 @@ private Gift mapSingleGift(JsonElement jsonElement) { 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 GiftsData.Response mapRoom(String json) { 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 b51d5c72..d5c9dbaa 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 @@ else if (rival_id != 0) 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 c60d0567..b4ecb628 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 @@ 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 LiveUserData.Response map(String json, Logger logger) { 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 LiveUserData.Response map(String json, Logger logger) { 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 LiveUserData.Response map(String json, Logger logger) { 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 aa5a630b..dc1c35e0 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 @@ -77,7 +77,7 @@ public void start(LiveConnectionData.Response connectionData, LiveClient liveCli connectDefault(); } - private void connectDefault() { + public void connectDefault() { try { webSocketClient.connect(); heartbeatTask.run(webSocketClient, clientSettings.getPingInterval()); 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 23b2a8d2..1f124e2a 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 @@ 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 void onMessage(ByteBuffer bytes) { } } - private void handleBinary(byte[] buffer) { + protected void handleBinary(byte[] buffer) { var websocketPushFrameOptional = getWebcastPushFrame(buffer); if (websocketPushFrameOptional.isEmpty()) { return; @@ -97,7 +97,7 @@ public void onOpen(ServerHandshake serverHandshake) { @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 void onError(Exception error) { 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 f7cbdc8a..b9c96d32 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 @@ -46,7 +46,7 @@ public void start(LiveConnectionData.Response webcastResponse, LiveClient tikTok @Override public void stop(int type) { if (liveClient != null) - handler.publish(liveClient, new TikTokDisconnectedEvent("Stopping")); + handler.publish(liveClient, new TikTokDisconnectedEvent(-1, "Stopping")); } @Override 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 00000000..115e5663 --- /dev/null +++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/websocket/euler/TikTokWebSocketEulerClient.java @@ -0,0 +1,97 @@ +/* + * 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.*; +import io.github.jwdeveloper.tiktok.exceptions.*; +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.*; +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(0); + + webSocketClient = new TikTokWebSocketEulerListener( + URI.create("wss://ws.eulerstream.com?uniqueId=%s&apiKey=%s&features.rawMessages=true".formatted(liveClient.getRoomInfo().getHostName(), clientSettings.getApiKey())), + 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(int type) { + if (isConnected()) { + switch (type) { + case 1 -> { + try { + webSocketClient.closeBlocking(); + } catch (InterruptedException e) { + throw new TikTokLiveException("Failed to stop the websocket"); + } + } + case 2 -> 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 00000000..f7f4a8c1 --- /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 From 19ed45e6de48de88ce62664011ba896fa42b5ba5 Mon Sep 17 00:00:00 2001 From: kohlerpop1 Date: Tue, 16 Sep 2025 20:53:54 -0400 Subject: [PATCH 2/4] Remove -1 close code and convert to standard public static value with reference! --- .../tiktok/data/events/TikTokDisconnectedEvent.java | 10 ++++++++++ .../io/github/jwdeveloper/tiktok/TikTokLiveClient.java | 2 +- .../tiktok/websocket/TikTokWebSocketOfflineClient.java | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) 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 2f573581..715ca046 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,6 +30,8 @@ @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; @@ -38,4 +40,12 @@ public TikTokDisconnectedEvent(int code, String reason) { this.code = code; this.reason = reason.isBlank() ? "None" : 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/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClient.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClient.java index 193d4517..e06102f7 100644 --- a/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClient.java +++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClient.java @@ -86,7 +86,7 @@ public void connect() { } catch (TikTokLiveException e) { setState(ConnectionState.DISCONNECTED); tikTokEventHandler.publish(this, new TikTokErrorEvent(e)); - tikTokEventHandler.publish(this, new TikTokDisconnectedEvent(-1, "Exception: " + e.getMessage())); + tikTokEventHandler.publish(this, new TikTokDisconnectedEvent("Exception: " + e.getMessage())); if (e instanceof TikTokLiveOfflineHostException && clientSettings.isRetryOnConnectionFailure()) { try { 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 b9c96d32..f7cbdc8a 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 @@ -46,7 +46,7 @@ public void start(LiveConnectionData.Response webcastResponse, LiveClient tikTok @Override public void stop(int type) { if (liveClient != null) - handler.publish(liveClient, new TikTokDisconnectedEvent(-1, "Stopping")); + handler.publish(liveClient, new TikTokDisconnectedEvent("Stopping")); } @Override From 03180d6a1cc333ab8892f454a30cca63a3d5974a Mon Sep 17 00:00:00 2001 From: kohlerpop1 Date: Tue, 16 Sep 2025 21:19:44 -0400 Subject: [PATCH 3/4] Convert to and use LiveClientStopType enum for disconnecting websocket from magic numbers! --- .../jwdeveloper/tiktok/live/LiveClient.java | 15 +++---- .../tiktok/websocket/LiveClientStopType.java | 42 +++++++++++++++++++ .../tiktok/websocket/LiveSocketClient.java | 2 +- .../jwdeveloper/tiktok/TikTokLiveClient.java | 4 +- .../websocket/TikTokWebSocketClient.java | 8 ++-- .../TikTokWebSocketOfflineClient.java | 2 +- .../euler/TikTokWebSocketEulerClient.java | 14 +++---- 7 files changed, 65 insertions(+), 22 deletions(-) create mode 100644 API/src/main/java/io/github/jwdeveloper/tiktok/websocket/LiveClientStopType.java 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 c0ce8a40..a30ff85c 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 @@ 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/websocket/LiveClientStopType.java b/API/src/main/java/io/github/jwdeveloper/tiktok/websocket/LiveClientStopType.java new file mode 100644 index 00000000..0cb78b9e --- /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 f04e8b52..9f09edfb 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 @@ 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 e06102f7..01b0f09c 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.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; @@ -166,7 +166,7 @@ public void tryConnect() { 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/websocket/TikTokWebSocketClient.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/websocket/TikTokWebSocketClient.java index dc1c35e0..6a2fc601 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 TikTokWebSocketClient( @Override public void start(LiveConnectionData.Response connectionData, LiveClient liveClient) { if (isConnected()) - stop(0); + stop(LiveClientStopType.NORMAL); messageHandler.handle(liveClient, connectionData.getWebcastResponse()); @@ -129,17 +129,17 @@ public boolean tryProxyConnection(ProxyClientSettings proxySettings, ProxyData p } } - 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/TikTokWebSocketOfflineClient.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/websocket/TikTokWebSocketOfflineClient.java index f7cbdc8a..3a737608 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 void start(LiveConnectionData.Response webcastResponse, LiveClient tikTok } @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 index 115e5663..667dbf58 100644 --- 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 @@ -23,14 +23,14 @@ package io.github.jwdeveloper.tiktok.websocket.euler; import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData; -import io.github.jwdeveloper.tiktok.data.settings.*; -import io.github.jwdeveloper.tiktok.exceptions.*; +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.*; +import java.net.URI; import java.util.HashMap; public class TikTokWebSocketEulerClient implements LiveSocketClient { @@ -53,7 +53,7 @@ public TikTokWebSocketEulerClient( @Override public void start(LiveConnectionData.Response connectionData, LiveClient liveClient) { if (isConnected()) - stop(0); + stop(LiveClientStopType.NORMAL); webSocketClient = new TikTokWebSocketEulerListener( URI.create("wss://ws.eulerstream.com?uniqueId=%s&apiKey=%s&features.rawMessages=true".formatted(liveClient.getRoomInfo().getHostName(), clientSettings.getApiKey())), @@ -74,17 +74,17 @@ public void connect() { } } - 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(); } } From 763d470793235612ce878bcfd174bff53c3c9a36 Mon Sep 17 00:00:00 2001 From: kohlerpop1 Date: Tue, 16 Sep 2025 21:35:09 -0400 Subject: [PATCH 4/4] Add capability to use Eulerstream Enterprise server and websocket! --- .../tiktok/data/settings/LiveClientSettings.java | 5 +++++ .../io/github/jwdeveloper/tiktok/TikTokLiveHttpClient.java | 6 ++++-- .../tiktok/websocket/euler/TikTokWebSocketEulerClient.java | 5 ++++- 3 files changed, 13 insertions(+), 3 deletions(-) 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 fbe81786..725b2a2e 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 @@ -94,6 +94,11 @@ public class LiveClientSettings { */ 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/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveHttpClient.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveHttpClient.java index 590afd0f..cba22090 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/"; @@ -198,7 +200,7 @@ public boolean requestSendChat(LiveRoomInfo roomInfo, String content) { 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("x-api-key", clientSettings.getApiKey()); @@ -219,7 +221,7 @@ protected ActionResult> getStartingPayload(LiveConnectionDa } 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); 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 index 667dbf58..6d0fcd40 100644 --- 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 @@ -55,8 +55,11 @@ public void start(LiveConnectionData.Response connectionData, LiveClient liveCli 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("wss://ws.eulerstream.com?uniqueId=%s&apiKey=%s&features.rawMessages=true".formatted(liveClient.getRoomInfo().getHostName(), clientSettings.getApiKey())), + URI.create(url), new HashMap<>(clientSettings.getHttpSettings().getHeaders()), clientSettings.getHttpSettings().getTimeout().toMillisPart(), messageHandler,