From 54433af65f0a0c7800268a063e4f1d08f88deec2 Mon Sep 17 00:00:00 2001 From: kohlerpop1 Date: Mon, 15 Sep 2025 23:12:43 -0400 Subject: [PATCH] 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 | 66 +++++++------ .../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, 285 insertions(+), 86 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 b5a3ddd..2f57358 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 @@ import lombok.Getter; @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 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..fbe8178 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 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/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClient.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClient.java index 08d65a4..193d451 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 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(-1, "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); 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..590afd0 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 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 +89,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 +119,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 +147,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 @@ -207,7 +201,7 @@ public class TikTokLiveHttpClient implements LiveHttpClient 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 @@ public class TikTokLiveHttpClient implements LiveHttpClient 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 @@ 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..dc1c35e 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 class TikTokWebSocketClient implements LiveSocketClient { 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 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..b9c96d3 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 class TikTokWebSocketOfflineClient implements LiveSocketClient { @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 0000000..115e566 --- /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 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