From 3832db111e887cf43bba6d0a30f108689e184a2a Mon Sep 17 00:00:00 2001 From: JW Date: Mon, 11 Dec 2023 23:18:40 +0100 Subject: [PATCH] Changes: onCustomEvent() <- registering custom events onMapping() <- custom mappings check out 'CustomMappingExample' more gifs has been added exceptions are more explicit --- .../TikTokMessageMappingException.java | 21 +- .../tiktok/live/builder/EventsBuilder.java | 13 +- .../live/builder/LiveClientBuilder.java | 42 +++- .../tiktok/mappers/TikTokMapper.java | 73 +++++++ .../tiktok/utils/CancelationToken.java | 60 ----- .../utils/ProtoBufferFileGenerator.java | 56 +++++ .../utils/ProtoBufferJsonGenerator.java | 31 +++ .../tiktok/utils/ProtoBufferObject.java | 76 +++++++ .../tiktok/utils/ProtocolUtils.java | 94 ++++++++ API/src/main/proto/webcast.proto | 2 + .../tiktok/TikTokLiveClientBuilder.java | 121 +++++++--- .../tiktok/handlers/TikTokMessageHandler.java | 31 +-- .../TikTokMessageHandlerRegistration.java | 206 ------------------ .../mappers/TikTokGenericEventMapper.java | 2 +- .../tiktok/mappers/TikTokLiveMapper.java | 88 ++++++++ .../events/TikTokChestEventHandler.java | 2 +- .../events/TikTokCommonEventHandler.java | 66 ++++++ .../events/TikTokGiftEventHandler.java | 2 +- .../events/TikTokRoomInfoEventHandler.java | 38 +++- .../events/TikTokSocialMediaEventHandler.java | 2 +- .../websocket/TikTokWebSocketClient.java | 14 +- .../websocket/TikTokWebSocketListener.java | 34 +-- .../events/TikTokGiftEventHandlerTest.java | 1 + .../tiktok/CustomMappingExample.java | 88 ++++++++ .../jwdeveloper/tiktok/SimpleExample.java | 36 ++- .../jwdeveloper/tiktok/SimpleExample$1.class | Bin 879 -> 879 bytes .../tiktok/tools/util/MessageUtil.java | 37 +--- .../tiktok/mockClient/TikTokMockBuilder.java | 17 +- 28 files changed, 841 insertions(+), 412 deletions(-) create mode 100644 API/src/main/java/io/github/jwdeveloper/tiktok/mappers/TikTokMapper.java delete mode 100644 API/src/main/java/io/github/jwdeveloper/tiktok/utils/CancelationToken.java create mode 100644 API/src/main/java/io/github/jwdeveloper/tiktok/utils/ProtoBufferFileGenerator.java create mode 100644 API/src/main/java/io/github/jwdeveloper/tiktok/utils/ProtoBufferJsonGenerator.java create mode 100644 API/src/main/java/io/github/jwdeveloper/tiktok/utils/ProtoBufferObject.java create mode 100644 API/src/main/java/io/github/jwdeveloper/tiktok/utils/ProtocolUtils.java delete mode 100644 Client/src/main/java/io/github/jwdeveloper/tiktok/handlers/TikTokMessageHandlerRegistration.java create mode 100644 Client/src/main/java/io/github/jwdeveloper/tiktok/mappers/TikTokLiveMapper.java rename Client/src/main/java/io/github/jwdeveloper/tiktok/{handlers => mappers}/events/TikTokChestEventHandler.java (95%) create mode 100644 Client/src/main/java/io/github/jwdeveloper/tiktok/mappers/events/TikTokCommonEventHandler.java rename Client/src/main/java/io/github/jwdeveloper/tiktok/{handlers => mappers}/events/TikTokGiftEventHandler.java (99%) rename Client/src/main/java/io/github/jwdeveloper/tiktok/{handlers => mappers}/events/TikTokRoomInfoEventHandler.java (67%) rename Client/src/main/java/io/github/jwdeveloper/tiktok/{handlers => mappers}/events/TikTokSocialMediaEventHandler.java (98%) create mode 100644 Examples/src/main/java/io/github/jwdeveloper/tiktok/CustomMappingExample.java diff --git a/API/src/main/java/io/github/jwdeveloper/tiktok/exceptions/TikTokMessageMappingException.java b/API/src/main/java/io/github/jwdeveloper/tiktok/exceptions/TikTokMessageMappingException.java index e3671bd..ad3c1ec 100644 --- a/API/src/main/java/io/github/jwdeveloper/tiktok/exceptions/TikTokMessageMappingException.java +++ b/API/src/main/java/io/github/jwdeveloper/tiktok/exceptions/TikTokMessageMappingException.java @@ -26,15 +26,20 @@ package io.github.jwdeveloper.tiktok.exceptions; /** * Happens when incoming data from TikTok can not be mapped to TikTokEvent's */ -public class TikTokMessageMappingException extends TikTokLiveException -{ - public TikTokMessageMappingException(Class inputClazz, Class outputClass, Throwable throwable) - { - super("Unable to handle mapping from class: " + inputClazz.getSimpleName() + " to class " + outputClass.getSimpleName(),throwable); +public class TikTokMessageMappingException extends TikTokLiveException { + public TikTokMessageMappingException(Class inputClazz, Class outputClass, Throwable throwable) { + super("Unable to handle mapping from class: " + inputClazz.getSimpleName() + " to class " + outputClass.getSimpleName(), throwable); } - public TikTokMessageMappingException(Class inputClazz, Class outputClass, String message) - { - super("Unable to handle mapping from class: " + inputClazz.getSimpleName() + " to class " + outputClass.getSimpleName()+": "+message); + public TikTokMessageMappingException(Class inputClazz, Class outputClass, String message) { + super("Unable to handle mapping from class: " + inputClazz.getSimpleName() + " to class " + outputClass.getSimpleName() + ": " + message); + } + + public TikTokMessageMappingException(Class inputClazz, String message, Throwable throwable) { + super("Unable to handle mapping from class: " + inputClazz.getSimpleName() + ": " + message, throwable); + } + + public TikTokMessageMappingException(String message, Throwable throwable) { + super( message, throwable); } } diff --git a/API/src/main/java/io/github/jwdeveloper/tiktok/live/builder/EventsBuilder.java b/API/src/main/java/io/github/jwdeveloper/tiktok/live/builder/EventsBuilder.java index 1885ae9..671b587 100644 --- a/API/src/main/java/io/github/jwdeveloper/tiktok/live/builder/EventsBuilder.java +++ b/API/src/main/java/io/github/jwdeveloper/tiktok/live/builder/EventsBuilder.java @@ -38,7 +38,12 @@ import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketUnhandl public interface EventsBuilder { - T onCustomEvent(EventConsumer event); + /** + * Method used to register own custom events + * @param eventClazz event class + * @param event action + */ + T onCustomEvent(Class eventClazz, EventConsumer event); T onRoomInfo(EventConsumer event); @@ -52,6 +57,7 @@ public interface EventsBuilder { T onGiftCombo(EventConsumer event); + T onGift(EventConsumer event); T onQuestion(EventConsumer event); @@ -68,7 +74,7 @@ public interface EventsBuilder { T onShare(EventConsumer event); - // T onChest(EventConsumer event); + // T onChest(EventConsumer event); T onLivePaused(EventConsumer event); @@ -83,11 +89,10 @@ public interface EventsBuilder { T onDisconnected(EventConsumer event); T onError(EventConsumer event); + T onEvent(EventConsumer event); - - // TODO Figure out how those events works //T onLinkMicFanTicket(TikTokEventConsumer event); diff --git a/API/src/main/java/io/github/jwdeveloper/tiktok/live/builder/LiveClientBuilder.java b/API/src/main/java/io/github/jwdeveloper/tiktok/live/builder/LiveClientBuilder.java index 46610c9..d460617 100644 --- a/API/src/main/java/io/github/jwdeveloper/tiktok/live/builder/LiveClientBuilder.java +++ b/API/src/main/java/io/github/jwdeveloper/tiktok/live/builder/LiveClientBuilder.java @@ -23,20 +23,60 @@ package io.github.jwdeveloper.tiktok.live.builder; import io.github.jwdeveloper.tiktok.ClientSettings; +import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; import io.github.jwdeveloper.tiktok.listener.TikTokEventListener; import io.github.jwdeveloper.tiktok.live.LiveClient; +import io.github.jwdeveloper.tiktok.mappers.TikTokMapper; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; +import java.util.function.Function; public interface LiveClientBuilder extends EventsBuilder { - LiveClientBuilder configure(Consumer consumer); + /** + * This method is triggered after default mappings are registered + * It could be used to OVERRIDE behaviour of mappings and implement custom behaviour + * + * Be aware if for example you override WebcastGiftEvent, onGiftEvent() will not be working + * + * @param onCustomMappings lambda method + * @return + */ + LiveClientBuilder onMapping(Consumer onCustomMappings); + + + /** + * Configuration of client settings + * @see ClientSettings + * @param onConfigure + * @return + */ + LiveClientBuilder configure(Consumer onConfigure); + + /** + * @see TikTokEventListener + * Adding events listener class, its fancy way to register events without using lamda method + * but actual method in class that implements TikTokEventListener + * @return + */ LiveClientBuilder addListener(TikTokEventListener listener); + /** + * + * @return LiveClient object + */ LiveClient build(); + /** + * + * @return LiveClient object and connects to TikTok live + */ LiveClient buildAndConnect(); + /** + * + * @return LiveClient object and connects to TikTok live asynchronously + */ CompletableFuture buildAndConnectAsync(); } diff --git a/API/src/main/java/io/github/jwdeveloper/tiktok/mappers/TikTokMapper.java b/API/src/main/java/io/github/jwdeveloper/tiktok/mappers/TikTokMapper.java new file mode 100644 index 0000000..d7db822 --- /dev/null +++ b/API/src/main/java/io/github/jwdeveloper/tiktok/mappers/TikTokMapper.java @@ -0,0 +1,73 @@ +package io.github.jwdeveloper.tiktok.mappers; + +import com.google.protobuf.GeneratedMessageV3; +import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; + +import java.util.List; +import java.util.function.Function; + +public interface TikTokMapper { + + + /** + * Triggered when `sourceClass` is mapped, + * input is bytes that are coming from TikTok in `sourceClass` packet + * output is TikTok event we want to create + *

+ * bytesToEvent(WebcastGiftMessage.class, bytes -> + * { + * var giftMessage = WebcastGiftMessage.parseFrom(bytes); + * var giftName = giftMessage.getGift().getName(); + * return new TikTokEvent(Gift.ROSE, giftMessage); + * }) + * + * @param sourceClass protocol buffer webcast class + * @param onMapping lambda function that is triggered on mapping. takes as input ProtocolBuffer object and as output TikTokEvent + */ + void bytesToEvent(Class sourceClass, Function onMapping); + + void bytesToEvents(Class sourceClass, Function> onMapping); + + + /** + * In case you found some TikTok message that has not Webcast class use this method + * + * @param messageName Name of TikTok data event + * @param onMapping lambda function that is triggered on mapping. takes as input ProtocolBuffer object and as output TikTokEvent + */ + void bytesToEvent(String messageName, Function onMapping); + + void bytesToEvents(String messageName, Function> onMapping); + + + /** + * This method can be used to override default mapping for + * certain TikTok incoming data message. For this example + * we are overriding WebcastGiftMessage and retuning CustomGiftEvent + * instead of TikTokGiftEvent + *

+ * webcastObjectToEvent(WebcastGiftMessage.class, webcastGiftMessage -> + * { + * var giftName = webcastGiftMessage.getGift().getName(); + * var user = webcastGiftMessage.getUser().getNickname(); + * return new CustomGiftEvent(giftName, user); + * }) + * + * @param sourceClass ProtocolBuffer class that represent incoming custom data, hint class should starts with Webcast prefix + * @param onMapping lambda function that is triggered on mapping. takes as input ProtocolBuffer object and as output TikTokEvent + */ + void webcastObjectToEvent(Class sourceClass, Function onMapping); + + void webcastObjectToEvents(Class sourceClass, Function> onMapping); + + /** + * Triggered when `sourceClass` is mapped, + * looking for constructor in `outputClass` with one parameter that is of type `sourceClass` + * and created instance of object from this constructor + * + * @param sourceClass protocol buffer webcast class + * @param outputClass TikTok event class + */ + void webcastObjectToConstructor(Class sourceClass, Class outputClass); + +} diff --git a/API/src/main/java/io/github/jwdeveloper/tiktok/utils/CancelationToken.java b/API/src/main/java/io/github/jwdeveloper/tiktok/utils/CancelationToken.java deleted file mode 100644 index 8b76854..0000000 --- a/API/src/main/java/io/github/jwdeveloper/tiktok/utils/CancelationToken.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2023-2023 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.utils; - -import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException; - -public class CancelationToken -{ - private boolean isCanceled =false; - - public void cancel() - { - isCanceled =true; - } - - public boolean isCancel() - { - - return isCanceled; - } - - public void throwIfCancel() - { - if(!isCanceled) - { - return; - } - throw new TikTokLiveException("Token requested cancelation"); - } - - public boolean isNotCancel() - { - return !isCancel(); - } - - public static CancelationToken create() - { - return new CancelationToken(); - } -} diff --git a/API/src/main/java/io/github/jwdeveloper/tiktok/utils/ProtoBufferFileGenerator.java b/API/src/main/java/io/github/jwdeveloper/tiktok/utils/ProtoBufferFileGenerator.java new file mode 100644 index 0000000..2f2cdff --- /dev/null +++ b/API/src/main/java/io/github/jwdeveloper/tiktok/utils/ProtoBufferFileGenerator.java @@ -0,0 +1,56 @@ +package io.github.jwdeveloper.tiktok.utils; + +import java.nio.file.Path; +import java.util.Map; +import java.util.TreeMap; + +public class ProtoBufferFileGenerator { + + + public static String generate(ProtoBufferObject protoBuffObj, String name) { + + var sb = new StringBuilder(); + var offset = 2; + sb.append("message ").append(name).append(" { \n"); + + var objects = new TreeMap(); + var objectCounter = 0; + for (var entry : protoBuffObj.getFields().entrySet()) { + var index = entry.getKey(); + var field = entry.getValue(); + var fieldName = field.type.toLowerCase() + "Value"; + var value = field.value; + if (field.value instanceof ProtoBufferObject object) { + fieldName += objectCounter; + value = ""; + objects.put(fieldName,object); + objectCounter++; + + } + for (var i = 0; i < offset; i++) { + sb.append(" "); + } + sb.append(field.type).append(" ").append(fieldName) + .append(" ") + .append("=") + .append(" ") + .append(index) + .append(";") + .append(" //") + .append(value) + .append("\n"); + } + sb.append(" \n"); + for(var object : objects.entrySet()) + { + var structure = generate(object.getValue(),object.getKey()); + sb.append(structure); + } + + + sb.append(" \n"); + sb.append("} \n"); + return sb.toString(); + } + +} diff --git a/API/src/main/java/io/github/jwdeveloper/tiktok/utils/ProtoBufferJsonGenerator.java b/API/src/main/java/io/github/jwdeveloper/tiktok/utils/ProtoBufferJsonGenerator.java new file mode 100644 index 0000000..aac6609 --- /dev/null +++ b/API/src/main/java/io/github/jwdeveloper/tiktok/utils/ProtoBufferJsonGenerator.java @@ -0,0 +1,31 @@ +package io.github.jwdeveloper.tiktok.utils; + +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; + +public class ProtoBufferJsonGenerator { + public static JsonObject genratejson(ProtoBufferObject protoBuffObj) { + + JsonObject jsonObject = new JsonObject(); + for (var entry : protoBuffObj.getFields().entrySet()) { + var fieldName = entry.getKey() + "_" + entry.getValue().type; + if (entry.getValue().value instanceof ProtoBufferObject protoObj) + { + JsonObject childJson = genratejson(protoObj); + jsonObject.add(fieldName, childJson); + continue; + } + + var value = entry.getValue().value.toString(); + jsonObject.addProperty(fieldName, value); + } + + return jsonObject; + } + + public static String generate(ProtoBufferObject protoBufferObject) { + var json = genratejson(protoBufferObject); + var gson = new GsonBuilder().setPrettyPrinting().create(); + return gson.toJson(json); + } +} diff --git a/API/src/main/java/io/github/jwdeveloper/tiktok/utils/ProtoBufferObject.java b/API/src/main/java/io/github/jwdeveloper/tiktok/utils/ProtoBufferObject.java new file mode 100644 index 0000000..98fd757 --- /dev/null +++ b/API/src/main/java/io/github/jwdeveloper/tiktok/utils/ProtoBufferObject.java @@ -0,0 +1,76 @@ +package io.github.jwdeveloper.tiktok.utils; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Map; +import java.util.TreeMap; + +public class ProtoBufferObject { + @Getter + private final Map fields; + + public ProtoBufferObject() { + this.fields = new TreeMap<>(); + } + + public void addField(int index, String type, Object value) { + fields.put(index, new ProtoBufferField(type, value)); + } + + public void addField(int index, ProtoBufferField value) { + fields.put(index, value); + } + + + public String toProtoFile() + { + return ProtoBufferFileGenerator.generate(this,"UnknownMessage"); + } + + public String toJson() + { + return ProtoBufferJsonGenerator.generate(this); + } + @Override + public String toString() { + return toString(0, true); + } + + public String toString(int offset ,boolean nested) { + + var sb = new StringBuilder(); + sb.append("\n"); + for (var entry : fields.entrySet()) { + var index = entry.getKey(); + var field = entry.getValue(); + + for(var i =0;i listeners; + protected Consumer onCustomMappings; public TikTokLiveClientBuilder(String userName) { this.tikTokEventHandler = new TikTokEventObserver(); @@ -77,10 +84,18 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder { this.clientSettings.setHostName(userName); this.logger = Logger.getLogger(TikTokLive.class.getSimpleName() + " " + userName); this.listeners = new ArrayList<>(); + this.onCustomMappings = (e) -> { + }; } - public TikTokLiveClientBuilder configure(Consumer consumer) { - consumer.accept(clientSettings); + + public LiveClientBuilder onMapping(Consumer onCustomMappings) { + this.onCustomMappings = onCustomMappings; + return this; + } + + public TikTokLiveClientBuilder configure(Consumer onConfigure) { + onConfigure.accept(clientSettings); return this; } @@ -104,8 +119,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder { throw new TikTokLiveException("HostName can not be null"); } - if (clientSettings.getHostName().startsWith("@")) - { + if (clientSettings.getHostName().startsWith("@")) { clientSettings.setHostName(clientSettings.getHostName().substring(1)); } @@ -135,9 +149,6 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder { if (!clientSettings.isPrintToConsole()) { logger.setLevel(Level.OFF); } - - - } public LiveClient build() { @@ -152,23 +163,14 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder { var apiClient = new TikTokHttpClient(cookieJar, requestFactory); var apiService = new TikTokApiService(apiClient, logger, clientSettings); var giftManager = new TikTokGiftManager(logger); - var eventMapper = new TikTokGenericEventMapper(); + var eventsMapper = createMapper(giftManager, tiktokRoomInfo); + var messageHandler = new TikTokMessageHandler(tikTokEventHandler, eventsMapper); - var giftHandler = new TikTokGiftEventHandler(giftManager); - var roomInfoHandler = new TikTokRoomInfoEventHandler(tiktokRoomInfo); - var socialHandler = new TikTokSocialMediaEventHandler(tiktokRoomInfo); - - var webResponseHandler = new TikTokMessageHandlerRegistration(tikTokEventHandler, - roomInfoHandler, - eventMapper, - giftHandler, - socialHandler - ); var webSocketClient = new TikTokWebSocketClient(logger, cookieJar, clientSettings, - webResponseHandler, + messageHandler, tikTokEventHandler); return new TikTokLiveClient(tiktokRoomInfo, @@ -181,6 +183,64 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder { logger); } + public TikTokLiveMapper createMapper(GiftManager giftManager, TikTokRoomInfo roomInfo) { + var eventMapper = new TikTokGenericEventMapper(); + var mapper = new TikTokLiveMapper(eventMapper); + + //ConnectionEvents events + var commonHandler = new TikTokCommonEventHandler(); + var giftHandler = new TikTokGiftEventHandler(giftManager); + var roomInfoHandler = new TikTokRoomInfoEventHandler(roomInfo); + var socialHandler = new TikTokSocialMediaEventHandler(roomInfo); + + mapper.bytesToEvent(WebcastControlMessage.class, commonHandler::handleWebcastControlMessage); + + //Room status events + mapper.bytesToEvent(WebcastLiveIntroMessage.class, roomInfoHandler::handleIntro); + mapper.bytesToEvent(WebcastRoomUserSeqMessage.class, roomInfoHandler::handleUserRanking); + + mapper.webcastObjectToConstructor(WebcastCaptionMessage.class, TikTokCaptionEvent.class); + + //User Interactions events + mapper.webcastObjectToConstructor(WebcastChatMessage.class, TikTokCommentEvent.class); + mapper.bytesToEvents(WebcastLikeMessage.class, roomInfoHandler::handleLike); + mapper.bytesToEvents(WebcastGiftMessage.class, giftHandler::handleGift); + mapper.bytesToEvent(WebcastSocialMessage.class, socialHandler::handle); + mapper.bytesToEvents(WebcastMemberMessage.class, roomInfoHandler::handleMemberMessage); + + //Host Interaction events + mapper.bytesToEvent(WebcastPollMessage.class, commonHandler::handlePollEvent); + mapper.bytesToEvent(WebcastRoomPinMessage.class, commonHandler::handlePinMessage); + mapper.webcastObjectToConstructor(WebcastGoalUpdateMessage.class, TikTokGoalUpdateEvent.class); + + //LinkMic events + mapper.webcastObjectToConstructor(WebcastLinkMicBattle.class, TikTokLinkMicBattleEvent.class); + mapper.webcastObjectToConstructor(WebcastLinkMicArmies.class, TikTokLinkMicArmiesEvent.class); + mapper.webcastObjectToConstructor(WebcastLinkMicMethod.class, TikTokLinkMicMethodEvent.class); + mapper.webcastObjectToConstructor(WebcastLinkMicFanTicketMethod.class, TikTokLinkMicFanTicketEvent.class); + + //Rank events + mapper.webcastObjectToConstructor(WebcastRankTextMessage.class, TikTokRankTextEvent.class); + mapper.webcastObjectToConstructor(WebcastRankUpdateMessage.class, TikTokRankUpdateEvent.class); + mapper.webcastObjectToConstructor(WebcastHourlyRankMessage.class, TikTokRankUpdateEvent.class); + + //Others events + mapper.webcastObjectToConstructor(WebcastInRoomBannerMessage.class, TikTokInRoomBannerEvent.class); + mapper.webcastObjectToConstructor(WebcastMsgDetectMessage.class, TikTokDetectEvent.class); + mapper.webcastObjectToConstructor(WebcastBarrageMessage.class, TikTokBarrageEvent.class); + mapper.webcastObjectToConstructor(WebcastUnauthorizedMemberMessage.class, TikTokUnauthorizedMemberEvent.class); + mapper.webcastObjectToConstructor(WebcastOecLiveShoppingMessage.class, TikTokShopEvent.class); + mapper.webcastObjectToConstructor(WebcastImDeleteMessage.class, TikTokIMDeleteEvent.class); + mapper.webcastObjectToConstructor(WebcastQuestionNewMessage.class, TikTokQuestionEvent.class); + mapper.bytesToEvents(WebcastEnvelopeMessage.class, commonHandler::handleEnvelop); + mapper.webcastObjectToConstructor(WebcastSubNotifyMessage.class, TikTokSubscribeEvent.class); + mapper.webcastObjectToConstructor(WebcastEmoteChatMessage.class, TikTokEmoteEvent.class); + + onCustomMappings.accept(mapper); + return mapper; + } + + public LiveClient buildAndConnect() { var client = build(); client.connect(); @@ -191,11 +251,6 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder { return build().connectAsync(); } - public LiveClientBuilder onCustomEvent(EventConsumer event) { - tikTokEventHandler.subscribe(CustomEvent.class, event); - return this; - } - public TikTokLiveClientBuilder onUnhandledSocial( EventConsumer event) { @@ -203,7 +258,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder { return this; } - // @Override + // @Override public LiveClientBuilder onChest(EventConsumer event) { tikTokEventHandler.subscribe(TikTokChestEvent.class, event); return this; @@ -259,6 +314,13 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder { return this; } + @Override + public LiveClientBuilder onCustomEvent(Class eventClazz, EventConsumer event) { + tikTokEventHandler.subscribe(eventClazz, event); + return this; + } + + @Override public LiveClientBuilder onRoomInfo(EventConsumer event) { tikTokEventHandler.subscribe(TikTokRoomInfoEvent.class, event); @@ -266,7 +328,6 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder { } - public TikTokLiveClientBuilder onLivePaused(EventConsumer event) { tikTokEventHandler.subscribe(TikTokLivePausedEvent.class, event); return this; diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/handlers/TikTokMessageHandler.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/handlers/TikTokMessageHandler.java index 2bf76d9..ef72527 100644 --- a/Client/src/main/java/io/github/jwdeveloper/tiktok/handlers/TikTokMessageHandler.java +++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/handlers/TikTokMessageHandler.java @@ -25,46 +25,28 @@ package io.github.jwdeveloper.tiktok.handlers; import io.github.jwdeveloper.tiktok.data.dto.MessageMetaData; import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent; -import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketMessageEvent; import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketResponseEvent; import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketUnhandledMessageEvent; import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveMessageException; import io.github.jwdeveloper.tiktok.live.LiveClient; -import io.github.jwdeveloper.tiktok.mappers.TikTokGenericEventMapper; +import io.github.jwdeveloper.tiktok.mappers.TikTokLiveMapper; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse; import io.github.jwdeveloper.tiktok.utils.Stopwatch; import java.time.Duration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -public abstract class TikTokMessageHandler { +public class TikTokMessageHandler { - private final Map handlers; private final TikTokEventObserver tikTokEventHandler; - protected final TikTokGenericEventMapper mapper; + private final TikTokLiveMapper mapper; - public TikTokMessageHandler(TikTokEventObserver tikTokEventHandler, TikTokGenericEventMapper mapper) { - handlers = new HashMap<>(); + public TikTokMessageHandler(TikTokEventObserver tikTokEventHandler, TikTokLiveMapper mapper) { this.tikTokEventHandler = tikTokEventHandler; this.mapper = mapper; } - public void registerMapping(Class clazz, Function func) { - handlers.put(clazz.getSimpleName(), messagePayload -> List.of(func.apply(messagePayload))); - } - - public void registerMappings(Class clazz, Function> func) { - handlers.put(clazz.getSimpleName(), func::apply); - } - - public void registerMapping(Class input, Class output) { - registerMapping(input, (e) -> mapper.mapToEvent(input, output, e)); - } public void handle(LiveClient client, WebcastResponse webcastResponse) { tikTokEventHandler.publish(client, new TikTokWebsocketResponseEvent(webcastResponse)); @@ -80,14 +62,13 @@ public abstract class TikTokMessageHandler { public void handleSingleMessage(LiveClient client, WebcastResponse.Message message) throws Exception { var messageClassName = message.getMethod(); - if (!handlers.containsKey(messageClassName)) { + if (!mapper.isRegistered(messageClassName)) { tikTokEventHandler.publish(client, new TikTokWebsocketUnhandledMessageEvent(message)); return; } - var handler = handlers.get(messageClassName); var stopwatch = new Stopwatch(); stopwatch.start(); - var events = handler.handle(message.getPayload().toByteArray()); + var events = mapper.handleMapping(messageClassName, message.getPayload().toByteArray()); var handlingTimeInMs = stopwatch.stop(); var metadata = new MessageMetaData(Duration.ofNanos(handlingTimeInMs)); diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/handlers/TikTokMessageHandlerRegistration.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/handlers/TikTokMessageHandlerRegistration.java deleted file mode 100644 index f0ce172..0000000 --- a/Client/src/main/java/io/github/jwdeveloper/tiktok/handlers/TikTokMessageHandlerRegistration.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (c) 2023-2023 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.handlers; - -import io.github.jwdeveloper.tiktok.data.events.*; -import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; -import io.github.jwdeveloper.tiktok.data.events.envelop.TikTokChestEvent; -import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollEndEvent; -import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollEvent; -import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollStartEvent; -import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollUpdateEvent; -import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomPinEvent; -import io.github.jwdeveloper.tiktok.data.events.social.TikTokJoinEvent; -import io.github.jwdeveloper.tiktok.data.events.social.TikTokLikeEvent; -import io.github.jwdeveloper.tiktok.data.models.chest.Chest; -import io.github.jwdeveloper.tiktok.data.models.users.User; -import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException; -import io.github.jwdeveloper.tiktok.handlers.events.TikTokGiftEventHandler; -import io.github.jwdeveloper.tiktok.handlers.events.TikTokRoomInfoEventHandler; -import io.github.jwdeveloper.tiktok.handlers.events.TikTokSocialMediaEventHandler; -import io.github.jwdeveloper.tiktok.mappers.TikTokGenericEventMapper; -import io.github.jwdeveloper.tiktok.messages.enums.EnvelopeDisplay; -import io.github.jwdeveloper.tiktok.messages.webcast.*; -import lombok.SneakyThrows; - -import java.util.Collections; -import java.util.List; - -public class TikTokMessageHandlerRegistration extends TikTokMessageHandler { - - private final TikTokGiftEventHandler giftHandler; - private final TikTokRoomInfoEventHandler roomInfoHandler; - private final TikTokSocialMediaEventHandler socialHandler; - - public TikTokMessageHandlerRegistration(TikTokEventObserver tikTokEventHandler, - TikTokRoomInfoEventHandler roomInfoHandler, - TikTokGenericEventMapper genericTikTokEventMapper, - TikTokGiftEventHandler tikTokGiftEventHandler, - TikTokSocialMediaEventHandler tikTokSocialMediaEventHandler) { - super(tikTokEventHandler, genericTikTokEventMapper); - this.giftHandler = tikTokGiftEventHandler; - this.roomInfoHandler = roomInfoHandler; - this.socialHandler = tikTokSocialMediaEventHandler; - init(); - } - - public void init() { - - - registerMapping(WebcastGiftMessage.class, bytes -> - { - try { - WebcastGiftMessage tiktokData = WebcastGiftMessage.parseFrom(bytes); - - io.github.jwdeveloper.tiktok.messages.data.User tiktokProtocolBufferUser = tiktokData.getUser(); - io.github.jwdeveloper.tiktok.data.models.users.User tiktokLiveJavaUser = User.map(tiktokProtocolBufferUser); - - return new CustomEvent(tiktokLiveJavaUser, "hello word"); - } catch (Exception e) { - throw new TikTokLiveException("Unable to parse our custom event", e); - } - }); - - //ConnectionEvents events - registerMapping(WebcastControlMessage.class, this::handleWebcastControlMessage); - - //Room status events - registerMapping(WebcastLiveIntroMessage.class, roomInfoHandler::handleIntro); - registerMapping(WebcastRoomUserSeqMessage.class, roomInfoHandler::handleUserRanking); - - registerMapping(WebcastCaptionMessage.class, TikTokCaptionEvent.class); - - //User Interactions events - registerMapping(WebcastChatMessage.class, TikTokCommentEvent.class); - registerMappings(WebcastLikeMessage.class, this::handleLike); - registerMappings(WebcastGiftMessage.class, giftHandler::handleGift); - registerMapping(WebcastSocialMessage.class, socialHandler::handle); - registerMappings(WebcastMemberMessage.class, this::handleMemberMessage); - - //Host Interaction events - registerMapping(WebcastPollMessage.class, this::handlePollEvent); - registerMapping(WebcastRoomPinMessage.class, this::handlePinMessage); - registerMapping(WebcastGoalUpdateMessage.class, TikTokGoalUpdateEvent.class); - - //LinkMic events - registerMapping(WebcastLinkMicBattle.class, TikTokLinkMicBattleEvent.class); - registerMapping(WebcastLinkMicArmies.class, TikTokLinkMicArmiesEvent.class); - registerMapping(WebcastLinkMicMethod.class, TikTokLinkMicMethodEvent.class); - registerMapping(WebcastLinkMicFanTicketMethod.class, TikTokLinkMicFanTicketEvent.class); - - //Rank events - registerMapping(WebcastRankTextMessage.class, TikTokRankTextEvent.class); - registerMapping(WebcastRankUpdateMessage.class, TikTokRankUpdateEvent.class); - registerMapping(WebcastHourlyRankMessage.class, TikTokRankUpdateEvent.class); - - //Others events - registerMapping(WebcastInRoomBannerMessage.class, TikTokInRoomBannerEvent.class); - registerMapping(WebcastMsgDetectMessage.class, TikTokDetectEvent.class); - registerMapping(WebcastBarrageMessage.class, TikTokBarrageEvent.class); - registerMapping(WebcastUnauthorizedMemberMessage.class, TikTokUnauthorizedMemberEvent.class); - registerMapping(WebcastOecLiveShoppingMessage.class, TikTokShopEvent.class); - registerMapping(WebcastImDeleteMessage.class, TikTokIMDeleteEvent.class); - registerMapping(WebcastQuestionNewMessage.class, TikTokQuestionEvent.class); - registerMappings(WebcastEnvelopeMessage.class, this::handleEnvelop); - registerMapping(WebcastSubNotifyMessage.class, TikTokSubscribeEvent.class); - registerMapping(WebcastEmoteChatMessage.class, TikTokEmoteEvent.class); - } - - - @SneakyThrows - private TikTokEvent handleWebcastControlMessage(byte[] msg) { - var message = WebcastControlMessage.parseFrom(msg); - return switch (message.getAction()) { - case STREAM_PAUSED -> new TikTokLivePausedEvent(); - case STREAM_ENDED -> new TikTokLiveEndedEvent(); - case STREAM_UNPAUSED -> new TikTokLiveUnpausedEvent(); - default -> new TikTokUnhandledControlEvent(message); - }; - } - - - - - - @SneakyThrows - private List handleMemberMessage(byte[] msg) { - var message = WebcastMemberMessage.parseFrom(msg); - - var event = switch (message.getAction()) { - case JOINED -> new TikTokJoinEvent(message); - case SUBSCRIBED -> new TikTokSubscribeEvent(message); - default -> new TikTokUnhandledMemberEvent(message); - }; - - var roomInfoEvent = roomInfoHandler.handleRoomInfo(tikTokRoomInfo -> - { - tikTokRoomInfo.setViewersCount(message.getMemberCount()); - }); - - return List.of(event, roomInfoEvent); - } - - private List handleLike(byte[] msg) { - var event = (TikTokLikeEvent) mapper.mapToEvent(WebcastLikeMessage.class, TikTokLikeEvent.class, msg); - var roomInfoEvent = roomInfoHandler.handleRoomInfo(tikTokRoomInfo -> - { - tikTokRoomInfo.setLikesCount(event.getTotalLikes()); - }); - return List.of(event, roomInfoEvent); - } - - @SneakyThrows - private TikTokEvent handlePinMessage(byte[] msg) { - var pinMessage = WebcastRoomPinMessage.parseFrom(msg); - var chatMessage = WebcastChatMessage.parseFrom(pinMessage.getPinnedMessage()); - var chatEvent = new TikTokCommentEvent(chatMessage); - return new TikTokRoomPinEvent(pinMessage, chatEvent); - } - - //TODO Probably not working - @SneakyThrows - private TikTokEvent handlePollEvent(byte[] msg) { - var poolMessage = WebcastPollMessage.parseFrom(msg); - return switch (poolMessage.getMessageType()) { - case 0 -> new TikTokPollStartEvent(poolMessage); - case 1 -> new TikTokPollEndEvent(poolMessage); - case 2 -> new TikTokPollUpdateEvent(poolMessage); - default -> new TikTokPollEvent(poolMessage); - }; - } - - @SneakyThrows - private List handleEnvelop(byte[] data) { - var msg = WebcastEnvelopeMessage.parseFrom(data); - if (msg.getDisplay() != EnvelopeDisplay.EnvelopeDisplayNew) { - return Collections.emptyList(); - } - var totalDiamonds = msg.getEnvelopeInfo().getDiamondCount(); - var totalUsers = msg.getEnvelopeInfo().getPeopleCount(); - var chest = new Chest(totalDiamonds, totalUsers); - - return List.of(new TikTokChestEvent(chest, msg)); - } - - -} diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/mappers/TikTokGenericEventMapper.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/mappers/TikTokGenericEventMapper.java index bf564b2..c2c1cda 100644 --- a/Client/src/main/java/io/github/jwdeveloper/tiktok/mappers/TikTokGenericEventMapper.java +++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/mappers/TikTokGenericEventMapper.java @@ -74,7 +74,7 @@ public class TikTokGenericEventMapper { } } - private Method getParsingMethod(Class input) throws NoSuchMethodException { + public Method getParsingMethod(Class input) throws NoSuchMethodException { if (methodCache.containsKey(input)) { return methodCache.get(input); } diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/mappers/TikTokLiveMapper.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/mappers/TikTokLiveMapper.java new file mode 100644 index 0000000..a6df11a --- /dev/null +++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/mappers/TikTokLiveMapper.java @@ -0,0 +1,88 @@ +package io.github.jwdeveloper.tiktok.mappers; + +import com.google.protobuf.GeneratedMessageV3; +import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; +import io.github.jwdeveloper.tiktok.exceptions.TikTokMessageMappingException; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +public class TikTokLiveMapper implements TikTokMapper { + private final Map>> mappers; + private final TikTokGenericEventMapper genericMapper; + + public TikTokLiveMapper(TikTokGenericEventMapper genericMapper) { + this.mappers = new HashMap<>(); + this.genericMapper = genericMapper; + } + + @Override + public void bytesToEvent(String messageName, Function onMapping) { + mappers.put(messageName, messagePayload -> List.of(onMapping.apply(messagePayload))); + } + + @Override + public void bytesToEvents(String messageName, Function> onMapping) { + mappers.put(messageName, onMapping::apply); + } + + public void bytesToEvent(Class clazz, Function onMapping) { + mappers.put(clazz.getSimpleName(), messagePayload -> List.of(onMapping.apply(messagePayload))); + } + + + + public void bytesToEvents(Class clazz, Function> onMapping) { + mappers.put(clazz.getSimpleName(), onMapping::apply); + } + + @Override + public void webcastObjectToConstructor(Class sourceClass, Class outputClass) { + bytesToEvent(sourceClass, (e) -> genericMapper.mapToEvent(sourceClass, outputClass, e)); + } + + @Override + public void webcastObjectToEvent(Class source, Function onMapping) { + bytesToEvent(source, (bytes) -> + { + try { + var parsingMethod = genericMapper.getParsingMethod(source); + var sourceObject = parsingMethod.invoke(null, bytes); + var event = onMapping.apply((T) sourceObject); + return event; + } catch (Exception e) { + throw new TikTokMessageMappingException(source, "can't find parsing method", e); + } + }); + } + + @Override + public void webcastObjectToEvents(Class source, Function> onMapping) { + bytesToEvents(source, (bytes) -> + { + try { + var parsingMethod = genericMapper.getParsingMethod(source); + var sourceObject = parsingMethod.invoke(null, bytes); + var event = onMapping.apply((T) sourceObject); + return event; + } catch (Exception e) { + throw new TikTokMessageMappingException(source, "can't find parsing method", e); + } + }); + } + + public boolean isRegistered(String input) { + return mappers.containsKey(input); + } + + public List handleMapping(String input, byte[] bytes) { + if (!isRegistered(input)) { + return List.of(); + } + var mapper = mappers.get(input); + var events = mapper.apply(bytes); + return events; + } +} diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/handlers/events/TikTokChestEventHandler.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/mappers/events/TikTokChestEventHandler.java similarity index 95% rename from Client/src/main/java/io/github/jwdeveloper/tiktok/handlers/events/TikTokChestEventHandler.java rename to Client/src/main/java/io/github/jwdeveloper/tiktok/mappers/events/TikTokChestEventHandler.java index 846c0c0..9e5794e 100644 --- a/Client/src/main/java/io/github/jwdeveloper/tiktok/handlers/events/TikTokChestEventHandler.java +++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/mappers/events/TikTokChestEventHandler.java @@ -20,7 +20,7 @@ * 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.handlers.events; +package io.github.jwdeveloper.tiktok.mappers.events; public class TikTokChestEventHandler { } diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/mappers/events/TikTokCommonEventHandler.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/mappers/events/TikTokCommonEventHandler.java new file mode 100644 index 0000000..fa40c26 --- /dev/null +++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/mappers/events/TikTokCommonEventHandler.java @@ -0,0 +1,66 @@ +package io.github.jwdeveloper.tiktok.mappers.events; + +import io.github.jwdeveloper.tiktok.data.events.*; +import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; +import io.github.jwdeveloper.tiktok.data.events.envelop.TikTokChestEvent; +import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollEndEvent; +import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollEvent; +import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollStartEvent; +import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollUpdateEvent; +import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomPinEvent; +import io.github.jwdeveloper.tiktok.data.models.chest.Chest; +import io.github.jwdeveloper.tiktok.messages.enums.EnvelopeDisplay; +import io.github.jwdeveloper.tiktok.messages.webcast.*; +import lombok.SneakyThrows; + +import java.util.Collections; +import java.util.List; + +public class TikTokCommonEventHandler +{ + + @SneakyThrows + public TikTokEvent handleWebcastControlMessage(byte[] msg) { + var message = WebcastControlMessage.parseFrom(msg); + return switch (message.getAction()) { + case STREAM_PAUSED -> new TikTokLivePausedEvent(); + case STREAM_ENDED -> new TikTokLiveEndedEvent(); + case STREAM_UNPAUSED -> new TikTokLiveUnpausedEvent(); + default -> new TikTokUnhandledControlEvent(message); + }; + } + + @SneakyThrows + public TikTokEvent handlePinMessage(byte[] msg) { + var pinMessage = WebcastRoomPinMessage.parseFrom(msg); + var chatMessage = WebcastChatMessage.parseFrom(pinMessage.getPinnedMessage()); + var chatEvent = new TikTokCommentEvent(chatMessage); + return new TikTokRoomPinEvent(pinMessage, chatEvent); + } + + //TODO Probably not working + @SneakyThrows + public TikTokEvent handlePollEvent(byte[] msg) { + var poolMessage = WebcastPollMessage.parseFrom(msg); + return switch (poolMessage.getMessageType()) { + case 0 -> new TikTokPollStartEvent(poolMessage); + case 1 -> new TikTokPollEndEvent(poolMessage); + case 2 -> new TikTokPollUpdateEvent(poolMessage); + default -> new TikTokPollEvent(poolMessage); + }; + } + + @SneakyThrows + public List handleEnvelop(byte[] data) { + var msg = WebcastEnvelopeMessage.parseFrom(data); + if (msg.getDisplay() != EnvelopeDisplay.EnvelopeDisplayNew) { + return Collections.emptyList(); + } + var totalDiamonds = msg.getEnvelopeInfo().getDiamondCount(); + var totalUsers = msg.getEnvelopeInfo().getPeopleCount(); + var chest = new Chest(totalDiamonds, totalUsers); + + return List.of(new TikTokChestEvent(chest, msg)); + } + +} diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/handlers/events/TikTokGiftEventHandler.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/mappers/events/TikTokGiftEventHandler.java similarity index 99% rename from Client/src/main/java/io/github/jwdeveloper/tiktok/handlers/events/TikTokGiftEventHandler.java rename to Client/src/main/java/io/github/jwdeveloper/tiktok/mappers/events/TikTokGiftEventHandler.java index 42b3f44..4819a14 100644 --- a/Client/src/main/java/io/github/jwdeveloper/tiktok/handlers/events/TikTokGiftEventHandler.java +++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/mappers/events/TikTokGiftEventHandler.java @@ -20,7 +20,7 @@ * 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.handlers.events; +package io.github.jwdeveloper.tiktok.mappers.events; import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftComboEvent; diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/handlers/events/TikTokRoomInfoEventHandler.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/mappers/events/TikTokRoomInfoEventHandler.java similarity index 67% rename from Client/src/main/java/io/github/jwdeveloper/tiktok/handlers/events/TikTokRoomInfoEventHandler.java rename to Client/src/main/java/io/github/jwdeveloper/tiktok/mappers/events/TikTokRoomInfoEventHandler.java index 61d8791..0ccbc7b 100644 --- a/Client/src/main/java/io/github/jwdeveloper/tiktok/handlers/events/TikTokRoomInfoEventHandler.java +++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/mappers/events/TikTokRoomInfoEventHandler.java @@ -20,17 +20,24 @@ * 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.handlers.events; +package io.github.jwdeveloper.tiktok.mappers.events; import io.github.jwdeveloper.tiktok.TikTokRoomInfo; +import io.github.jwdeveloper.tiktok.data.events.TikTokSubscribeEvent; +import io.github.jwdeveloper.tiktok.data.events.TikTokUnhandledMemberEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomInfoEvent; +import io.github.jwdeveloper.tiktok.data.events.social.TikTokJoinEvent; +import io.github.jwdeveloper.tiktok.data.events.social.TikTokLikeEvent; import io.github.jwdeveloper.tiktok.data.models.RankingUser; import io.github.jwdeveloper.tiktok.data.models.users.User; +import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLikeMessage; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLiveIntroMessage; +import io.github.jwdeveloper.tiktok.messages.webcast.WebcastMemberMessage; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastRoomUserSeqMessage; import lombok.SneakyThrows; +import java.util.List; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -76,4 +83,33 @@ public class TikTokRoomInfoEventHandler { tikTokRoomInfo.setLanguage(language); }); } + + @SneakyThrows + public List handleMemberMessage(byte[] msg) { + var message = WebcastMemberMessage.parseFrom(msg); + + var event = switch (message.getAction()) { + case JOINED -> new TikTokJoinEvent(message); + case SUBSCRIBED -> new TikTokSubscribeEvent(message); + default -> new TikTokUnhandledMemberEvent(message); + }; + + var roomInfoEvent = this.handleRoomInfo(tikTokRoomInfo -> + { + tikTokRoomInfo.setViewersCount(message.getMemberCount()); + }); + + return List.of(event, roomInfoEvent); + } + @SneakyThrows + public List handleLike(byte[] msg) + { + var message = WebcastLikeMessage.parseFrom(msg); + var event = new TikTokLikeEvent(message); + var roomInfoEvent = this.handleRoomInfo(tikTokRoomInfo -> + { + tikTokRoomInfo.setLikesCount(event.getTotalLikes()); + }); + return List.of(event, roomInfoEvent); + } } diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/handlers/events/TikTokSocialMediaEventHandler.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/mappers/events/TikTokSocialMediaEventHandler.java similarity index 98% rename from Client/src/main/java/io/github/jwdeveloper/tiktok/handlers/events/TikTokSocialMediaEventHandler.java rename to Client/src/main/java/io/github/jwdeveloper/tiktok/mappers/events/TikTokSocialMediaEventHandler.java index 577e0ad..e09fed3 100644 --- a/Client/src/main/java/io/github/jwdeveloper/tiktok/handlers/events/TikTokSocialMediaEventHandler.java +++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/mappers/events/TikTokSocialMediaEventHandler.java @@ -20,7 +20,7 @@ * 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.handlers.events; +package io.github.jwdeveloper.tiktok.mappers.events; import io.github.jwdeveloper.tiktok.TikTokRoomInfo; import io.github.jwdeveloper.tiktok.data.events.TikTokUnhandledSocialEvent; 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 a7ef317..12ef0e9 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 @@ -24,11 +24,9 @@ package io.github.jwdeveloper.tiktok.websocket; import io.github.jwdeveloper.tiktok.ClientSettings; -import io.github.jwdeveloper.tiktok.Constants; -import io.github.jwdeveloper.tiktok.TikTokLiveClient; import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException; import io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver; -import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandlerRegistration; +import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandler; import io.github.jwdeveloper.tiktok.http.HttpUtils; import io.github.jwdeveloper.tiktok.http.TikTokCookieJar; import io.github.jwdeveloper.tiktok.live.LiveClient; @@ -44,7 +42,7 @@ public class TikTokWebSocketClient implements SocketClient { private final Logger logger; private final ClientSettings clientSettings; private final TikTokCookieJar tikTokCookieJar; - private final TikTokMessageHandlerRegistration webResponseHandler; + private final TikTokMessageHandler messageHandler; private final TikTokEventObserver tikTokEventHandler; private WebSocketClient webSocketClient; private TikTokWebSocketPingingTask pingingTask; @@ -53,12 +51,12 @@ public class TikTokWebSocketClient implements SocketClient { public TikTokWebSocketClient(Logger logger, TikTokCookieJar tikTokCookieJar, ClientSettings clientSettings, - TikTokMessageHandlerRegistration webResponseHandler, + TikTokMessageHandler messageHandler, TikTokEventObserver tikTokEventHandler) { this.logger = logger; this.tikTokCookieJar = tikTokCookieJar; this.clientSettings = clientSettings; - this.webResponseHandler = webResponseHandler; + this.messageHandler = messageHandler; this.tikTokEventHandler = tikTokEventHandler; isConnected = false; } @@ -74,7 +72,7 @@ public class TikTokWebSocketClient implements SocketClient { } try { - webResponseHandler.handle(tikTokLiveClient, webcastResponse); + messageHandler.handle(tikTokLiveClient, webcastResponse); var url = getWebSocketUrl(webcastResponse); webSocketClient = startWebSocket(url, tikTokLiveClient); webSocketClient.connect(); @@ -107,7 +105,7 @@ public class TikTokWebSocketClient implements SocketClient { return new TikTokWebSocketListener(url, headers, 3000, - webResponseHandler, + messageHandler, tikTokEventHandler, liveClient); } 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 ecd87aa..6a21f74 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 @@ -28,7 +28,7 @@ import io.github.jwdeveloper.tiktok.data.events.TikTokDisconnectedEvent; import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent; import io.github.jwdeveloper.tiktok.exceptions.TikTokProtocolBufferException; import io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver; -import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandlerRegistration; +import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandler; import io.github.jwdeveloper.tiktok.live.LiveClient; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastPushFrame; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse; @@ -44,33 +44,22 @@ import java.util.Optional; public class TikTokWebSocketListener extends WebSocketClient { - private final TikTokMessageHandlerRegistration webResponseHandler; + private final TikTokMessageHandler messageHandler; private final TikTokEventObserver tikTokEventHandler; private final LiveClient tikTokLiveClient; public TikTokWebSocketListener(URI serverUri, Map httpHeaders, int connectTimeout, - TikTokMessageHandlerRegistration webResponseHandler, + TikTokMessageHandler messageHandler, TikTokEventObserver tikTokEventHandler, LiveClient tikTokLiveClient) { super(serverUri, new Draft_6455(), httpHeaders,connectTimeout); - this.webResponseHandler = webResponseHandler; + this.messageHandler = messageHandler; this.tikTokEventHandler = tikTokEventHandler; this.tikTokLiveClient = tikTokLiveClient; } - - @Override - public void onOpen(ServerHandshake serverHandshake) { - tikTokEventHandler.publish(tikTokLiveClient,new TikTokConnectedEvent()); - if(isNotClosing()) - { - sendPing(); - } - } - - @Override public void onMessage(ByteBuffer bytes) { @@ -85,6 +74,19 @@ public class TikTokWebSocketListener extends WebSocketClient { } } + + @Override + public void onOpen(ServerHandshake serverHandshake) { + tikTokEventHandler.publish(tikTokLiveClient,new TikTokConnectedEvent()); + if(isNotClosing()) + { + sendPing(); + } + } + + + + @Override public void onClose(int i, String s, boolean b) { tikTokEventHandler.publish(tikTokLiveClient,new TikTokDisconnectedEvent()); @@ -114,7 +116,7 @@ public class TikTokWebSocketListener extends WebSocketClient { // sendAckId(webResponse.getFetchInterval()); } - webResponseHandler.handle(tikTokLiveClient, webResponse); + messageHandler.handle(tikTokLiveClient, webResponse); } private Optional getWebcastWebsocketMessage(byte[] buffer) { diff --git a/Client/src/test/java/io/github/jwdeveloper/tiktok/handlers/events/TikTokGiftEventHandlerTest.java b/Client/src/test/java/io/github/jwdeveloper/tiktok/handlers/events/TikTokGiftEventHandlerTest.java index 146d47c..db40d86 100644 --- a/Client/src/test/java/io/github/jwdeveloper/tiktok/handlers/events/TikTokGiftEventHandlerTest.java +++ b/Client/src/test/java/io/github/jwdeveloper/tiktok/handlers/events/TikTokGiftEventHandlerTest.java @@ -27,6 +27,7 @@ import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent; import io.github.jwdeveloper.tiktok.data.models.Picture; import io.github.jwdeveloper.tiktok.data.models.gifts.GiftSendType; import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager; +import io.github.jwdeveloper.tiktok.mappers.events.TikTokGiftEventHandler; import io.github.jwdeveloper.tiktok.messages.data.GiftStruct; import io.github.jwdeveloper.tiktok.messages.data.Image; import io.github.jwdeveloper.tiktok.messages.data.User; diff --git a/Examples/src/main/java/io/github/jwdeveloper/tiktok/CustomMappingExample.java b/Examples/src/main/java/io/github/jwdeveloper/tiktok/CustomMappingExample.java new file mode 100644 index 0000000..7ac34d9 --- /dev/null +++ b/Examples/src/main/java/io/github/jwdeveloper/tiktok/CustomMappingExample.java @@ -0,0 +1,88 @@ +package io.github.jwdeveloper.tiktok; + +import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent; +import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; +import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent; +import io.github.jwdeveloper.tiktok.data.models.gifts.Gift; +import io.github.jwdeveloper.tiktok.exceptions.TikTokMessageMappingException; +import io.github.jwdeveloper.tiktok.messages.webcast.WebcastChatMessage; +import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage; +import io.github.jwdeveloper.tiktok.utils.ProtocolUtils; + +public class CustomMappingExample { + + public static void main(String[] args) { + TikTokLive.newClient("vadimpyrography") + .onCustomEvent(CustomChatEvent.class, (liveClient, event) -> + { + System.out.println("hello world!"); + }) + .onError((liveClient, event) -> + { + event.getException().printStackTrace(); + }) + .onMapping(mapper -> + { + mapper.webcastObjectToEvent(WebcastChatMessage.class, chatMessage -> + { + var language = chatMessage.getContentLanguage(); + var userName = chatMessage.getUser().getNickname(); + var message = chatMessage.getContent(); + return new CustomChatEvent(language, userName, message); + }); + mapper.bytesToEvent("WebcastGiftMessage", bytes -> + { + try + { + var event = WebcastGiftMessage.parseFrom(bytes); + return new TikTokGiftEvent(Gift.ROSA, event); + } catch (Exception e) { + throw new TikTokMessageMappingException("unable to map gift message!", e); + } + }); + + mapper.bytesToEvent("WebcastMemberMessage",bytes -> + { + //displaying unknown messages from tiktok + var structure = ProtocolUtils.getProtocolBufferStructure(bytes); + System.out.println(structure.toJson()); + return new TikTokErrorEvent(new RuntimeException("Message not implemented")); + }); + }).buildAndConnect(); + + } + + public static class CustomChatEvent extends TikTokEvent { + private final String langauge; + private final String userName; + private final String message; + + public CustomChatEvent(String language, String userName, String message) { + this.langauge = language; + this.userName = userName; + this.message = message; + } + + public String getLangauge() { + return langauge; + } + + public String getUserName() { + return userName; + } + + public String getMessage() { + return message; + } + + @Override + public String toString() { + return "CustomChatEvent{" + + "language='" + langauge + '\'' + + ", userName='" + userName + '\'' + + ", message='" + message + '\'' + + '}'; + } + + } +} diff --git a/Examples/src/main/java/io/github/jwdeveloper/tiktok/SimpleExample.java b/Examples/src/main/java/io/github/jwdeveloper/tiktok/SimpleExample.java index a6e81f6..9528da7 100644 --- a/Examples/src/main/java/io/github/jwdeveloper/tiktok/SimpleExample.java +++ b/Examples/src/main/java/io/github/jwdeveloper/tiktok/SimpleExample.java @@ -26,7 +26,9 @@ import io.github.jwdeveloper.tiktok.data.events.TikTokSubNotifyEvent; import io.github.jwdeveloper.tiktok.data.events.TikTokSubscribeEvent; import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent; import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveOfflineHostException; +import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage; import io.github.jwdeveloper.tiktok.utils.ConsoleColors; +import io.github.jwdeveloper.tiktok.utils.JsonUtil; import java.io.IOException; import java.time.Duration; @@ -42,6 +44,8 @@ public class SimpleExample { // set tiktok username + + /* //Optional checking if host name is correct if(TikTokLive.isHostNameValid(TIKTOK_HOSTNAME)) @@ -50,14 +54,33 @@ public class SimpleExample { } */ - // Optional checking if live is online - if(TikTokLive.isLiveOnline(TIKTOK_HOSTNAME)) - { + // Optional checking if live is online + if (TikTokLive.isLiveOnline(TIKTOK_HOSTNAME)) { System.out.println("Live is online!"); } - + TikTokLive.newClient("test") + .onWebsocketResponse((liveClient, event) -> + { + var response = event.getResponse(); + for (var message : response.getMessagesList()) { + var name = message.getMethod(); + var binaryData = message.getPayload(); + System.out.println("Event name: " + name); + if (name.equals("WebcastGiftEvent")) { + try { + WebcastGiftMessage giftMessage = WebcastGiftMessage.parseFrom(binaryData); + var giftName = giftMessage.getGift().getName(); + var giftId = giftMessage.getGiftId(); + var userName = giftMessage.getUser().getNickname(); + System.out.println("Gift: "+giftName+" id: "+giftId+" from user: "+userName); + } catch (Exception e) { + throw new RuntimeException("Mapping error", e); + } + } + } + }).buildAndConnect(); TikTokLive.newClient(SimpleExample.TIKTOK_HOSTNAME) @@ -96,9 +119,8 @@ public class SimpleExample { var tiktokLiveEvent = event.getEvent(); - if(tiktokLiveEvent instanceof TikTokSubNotifyEvent e) - { - System.out.println("it was subscrible event"); + if (tiktokLiveEvent instanceof TikTokSubNotifyEvent e) { + System.out.println("it was subscrible event"); } }) diff --git a/Examples/target/classes/io/github/jwdeveloper/tiktok/SimpleExample$1.class b/Examples/target/classes/io/github/jwdeveloper/tiktok/SimpleExample$1.class index d3153404e0008a8eaf578c02b99648638a3056a5..2cc864eac89921c07f442efadeedb9fbd8d6891f 100644 GIT binary patch delta 13 UcmaFQ_MUBnGBacAWEEy_03-ecOaK4? delta 13 UcmaFQ_MUBnGBacTWEEy_03(nDHUIzs diff --git a/Tools-EventsCollector/src/main/java/io/github/jwdeveloper/tiktok/tools/util/MessageUtil.java b/Tools-EventsCollector/src/main/java/io/github/jwdeveloper/tiktok/tools/util/MessageUtil.java index 8a2979c..bde6104 100644 --- a/Tools-EventsCollector/src/main/java/io/github/jwdeveloper/tiktok/tools/util/MessageUtil.java +++ b/Tools-EventsCollector/src/main/java/io/github/jwdeveloper/tiktok/tools/util/MessageUtil.java @@ -26,6 +26,7 @@ import com.google.protobuf.ByteString; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse; import io.github.jwdeveloper.tiktok.utils.ConsoleColors; import io.github.jwdeveloper.tiktok.utils.JsonUtil; +import io.github.jwdeveloper.tiktok.utils.ProtocolUtils; public class MessageUtil { public static String getContent(WebcastResponse.Message message) { @@ -50,39 +51,11 @@ public class MessageUtil { return JsonUtil.messageToJson(deserialized); } catch (Exception ex) { var sb = new StringBuilder(); - sb.append("Can not find mapper for " + methodName); + sb.append("Can not find protocolbuffer file message representation for " + methodName); sb.append("\n"); - try { - var files = com.google.protobuf.UnknownFieldSet.parseFrom(bytes); - for (var field : files.asMap().entrySet()) - { - var value = field.getValue(); - if(!value.getLengthDelimitedList().isEmpty()) - { - sb.append(field.getKey()+" LengthDelimited "+value.getLengthDelimitedList()+" \n"); - } - if(!value.getFixed32List().isEmpty()) - { - sb.append(field.getKey()+" fixed32 "+value.getFixed32List()+" \n"); - } - if(!value.getFixed64List().isEmpty()) - { - sb.append(field.getKey()+" fixed64 "+value.getFixed64List()+" \n"); - } - if(!value.getGroupList().isEmpty()) - { - sb.append(field.getKey()+" group "+value.getGroupList()+" \n"); - } - if(!value.getVarintList().isEmpty()) - { - sb.append(field.getKey()+" varint "+value.getVarintList()+" \n"); - } - } - } catch (Exception e) { - sb.append("Error while getting unknown fileds "+e.getMessage()); - } - - + var structure = ProtocolUtils.getProtocolBufferStructure(bytes); + var json =structure.toJson(); + sb.append(json); //String jsonString = JsonFormat.printToString(protobufData); return sb.toString(); } diff --git a/Tools/src/main/java/io/github/jwdeveloper/tiktok/mockClient/TikTokMockBuilder.java b/Tools/src/main/java/io/github/jwdeveloper/tiktok/mockClient/TikTokMockBuilder.java index c733da1..f5ef3d8 100644 --- a/Tools/src/main/java/io/github/jwdeveloper/tiktok/mockClient/TikTokMockBuilder.java +++ b/Tools/src/main/java/io/github/jwdeveloper/tiktok/mockClient/TikTokMockBuilder.java @@ -26,10 +26,10 @@ import io.github.jwdeveloper.tiktok.TikTokLiveClientBuilder; import io.github.jwdeveloper.tiktok.TikTokRoomInfo; import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException; import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager; -import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandlerRegistration; -import io.github.jwdeveloper.tiktok.handlers.events.TikTokGiftEventHandler; -import io.github.jwdeveloper.tiktok.handlers.events.TikTokRoomInfoEventHandler; -import io.github.jwdeveloper.tiktok.handlers.events.TikTokSocialMediaEventHandler; +import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandler; +import io.github.jwdeveloper.tiktok.mappers.events.TikTokGiftEventHandler; +import io.github.jwdeveloper.tiktok.mappers.events.TikTokRoomInfoEventHandler; +import io.github.jwdeveloper.tiktok.mappers.events.TikTokSocialMediaEventHandler; import io.github.jwdeveloper.tiktok.http.TikTokCookieJar; import io.github.jwdeveloper.tiktok.http.TikTokHttpClient; import io.github.jwdeveloper.tiktok.http.TikTokHttpRequestFactory; @@ -99,12 +99,9 @@ public class TikTokMockBuilder extends TikTokLiveClientBuilder { var requestFactory = new TikTokHttpRequestFactory(cookie); var apiClient = new TikTokHttpClient(cookie, requestFactory); var apiService = new ApiServiceMock(apiClient, logger, clientSettings); - var webResponseHandler = new TikTokMessageHandlerRegistration(tikTokEventHandler, - new TikTokRoomInfoEventHandler(tiktokRoomInfo), - new TikTokGenericEventMapper(), - new TikTokGiftEventHandler(giftManager), - new TikTokSocialMediaEventHandler(tiktokRoomInfo)); - var webSocketClient = new WebsocketClientMock(logger, responses, webResponseHandler); + var mapper = createMapper(giftManager, tiktokRoomInfo); + var handler = new TikTokMessageHandler(tikTokEventHandler, mapper); + var webSocketClient = new WebsocketClientMock(logger, responses, handler); return new LiveClientMock(tiktokRoomInfo, apiService,