Breaking changes:

'Gift': changed from class to enum, so now you can handle
incoming gifts in switch

`Events`
- new:
     onGiftComboFinished
- Removed:
      onGiftBrodcast
- Rename:
     onGiftMessage -> onGift
     onRoomPinMessage -> onRoomPin
     onRoomMessage -> onRoom
     onLinkMessage -> onLink
     onBarrageMessage -> onBarrage
     onPollMessage -> onPoll
     onShopMessage -> onShop
     onDetectMessage -> onDetect

`GiftManager`
   added:
      registerGift
      findById
      findByName
      getGifts
   removed:
      getActiveGifts
This commit is contained in:
JW
2023-10-12 03:41:36 +02:00
parent b18ca25865
commit 2d6111ef4d
48 changed files with 657 additions and 180 deletions

View File

@@ -58,6 +58,25 @@
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>attach-javadocs</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<doclint>-html,-syntax,-accessibility,-missing</doclint>
<failOnError>false</failOnError>
<quiet>true</quiet>
</configuration>
</execution>
</executions>
</plugin>
</plugins> </plugins>
</build> </build>
</project> </project>

View File

@@ -116,6 +116,7 @@ public class Constants {
public static Map<String, String> DefaultRequestHeaders() { public static Map<String, String> DefaultRequestHeaders() {
var headers = new HashMap<String, String>(); var headers = new HashMap<String, String>();
headers.put("authority","www.tiktok.com");
headers.put("Connection", "keep-alive"); headers.put("Connection", "keep-alive");
headers.put("Cache-Control", "max-age=0"); headers.put("Cache-Control", "max-age=0");
headers.put("Accept", "text/html,application/json,application/protobuf"); headers.put("Accept", "text/html,application/json,application/protobuf");

View File

@@ -0,0 +1,10 @@
package io.github.jwdeveloper.tiktok.data.events.common;
/**
*
*/
public class TikTokEmptyEvent extends TikTokEvent
{
}

View File

@@ -26,8 +26,8 @@ package io.github.jwdeveloper.tiktok.data.events.common;
import io.github.jwdeveloper.tiktok.utils.JsonUtil; import io.github.jwdeveloper.tiktok.utils.JsonUtil;
import lombok.Getter; import lombok.Getter;
/* /**
Base class for all events * Base class for all events
*/ */
@Getter @Getter
public abstract class TikTokEvent { public abstract class TikTokEvent {

View File

@@ -24,16 +24,32 @@ package io.github.jwdeveloper.tiktok.data.events.gift;
import io.github.jwdeveloper.tiktok.annotations.EventMeta; import io.github.jwdeveloper.tiktok.annotations.EventMeta;
import io.github.jwdeveloper.tiktok.annotations.EventType; import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.data.models.Gift; import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.data.models.gifts.GiftSendType;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage;
import lombok.Getter; import lombok.Getter;
/**
* Triggered every time gift is sent
*
* @see GiftSendType it has 3 states
*
* Example when user sends gift with combo
* Combo: 1 -> comboState = GiftSendType.Begin
* Combo: 4 -> comboState = GiftSendType.Active
* Combo: 8 -> comboState = GiftSendType.Active
* Combo: 12 -> comboState = GiftSendType.Finsihed
*
* Remember if comboState is Finsihed both TikTokGiftComboEvent and TikTokGiftEvent event gets triggered
*/
@EventMeta(eventType = EventType.Message) @EventMeta(eventType = EventType.Message)
@Getter @Getter
public class TikTokGiftComboEvent extends TikTokGiftEvent public class TikTokGiftComboEvent extends TikTokGiftEvent {
{ private final GiftSendType comboState;
public TikTokGiftComboEvent(Gift gift, WebcastGiftMessage msg) {
public TikTokGiftComboEvent(Gift gift, WebcastGiftMessage msg, GiftSendType comboState) {
super(gift, msg); super(gift, msg);
this.comboState = comboState;
} }
} }

View File

@@ -26,14 +26,17 @@ package io.github.jwdeveloper.tiktok.data.events.gift;
import io.github.jwdeveloper.tiktok.annotations.EventMeta; import io.github.jwdeveloper.tiktok.annotations.EventMeta;
import io.github.jwdeveloper.tiktok.annotations.EventType; import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokHeaderEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokHeaderEvent;
import io.github.jwdeveloper.tiktok.data.models.Gift; import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.data.models.gifts.GiftSendType;
import io.github.jwdeveloper.tiktok.data.models.users.User; import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage;
import lombok.Getter; import lombok.Getter;
/* /*
* Triggered every time a gift arrives. * Triggered when user sends gifts that has
* no combo (most of expensive gifts)
* or if combo has finished
*/ */
@EventMeta(eventType = EventType.Message) @EventMeta(eventType = EventType.Message)
@Getter @Getter
@@ -41,15 +44,10 @@ public class TikTokGiftEvent extends TikTokHeaderEvent {
private final Gift gift; private final Gift gift;
private final User user; private final User user;
private final int combo; private final int combo;
private final boolean comboFinished;
private final int comboIndex;
public TikTokGiftEvent(Gift gift, WebcastGiftMessage msg) { public TikTokGiftEvent(Gift gift, WebcastGiftMessage msg) {
super(msg.getCommon()); super(msg.getCommon());
this.gift = gift; this.gift = gift;
user = User.map(msg.getUser(),msg.getUserIdentity()); user = User.map(msg.getUser(), msg.getUserIdentity());
combo = msg.getComboCount(); combo = msg.getComboCount();
comboFinished = msg.getRepeatEnd() > 0;
comboIndex = msg.getRepeatCount();
} }
} }

View File

@@ -28,6 +28,9 @@ import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.Value;
import java.time.Duration;
/** /**
@@ -41,4 +44,12 @@ public class TikTokWebsocketMessageEvent extends TikTokEvent
private TikTokEvent event; private TikTokEvent event;
private WebcastResponse.Message message; private WebcastResponse.Message message;
private MetaData metaData;
@Value
public static class MetaData
{
Duration handlingTime;
}
} }

View File

@@ -69,9 +69,14 @@ public class Text {
var format = matcher.replaceAll("%s"); var format = matcher.replaceAll("%s");
var output = new ArrayList<String>(); var output = new ArrayList<String>();
for (var piece : textPieces) { for (var piece : textPieces)
{
output.add(piece.getText()); output.add(piece.getText());
} }
if(matcher.groupCount() != output.size())
{
return format;
}
return String.format(format, output.toArray()); return String.format(format, output.toArray());
} }

View File

@@ -21,8 +21,9 @@
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
// This enum is generated // This enum is generated
package io.github.jwdeveloper.tiktok.data.models; package io.github.jwdeveloper.tiktok.data.models.gifts;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import lombok.Getter; import lombok.Getter;
@Getter @Getter

View File

@@ -0,0 +1,18 @@
package io.github.jwdeveloper.tiktok.data.models.gifts;
public enum GiftSendType
{
Finished,
Begin,
Active;
public static GiftSendType fromNumber(long number)
{
return switch ((int) number) {
case 0 -> GiftSendType.Finished;
case 1, 2, 4 -> GiftSendType.Active;
default -> GiftSendType.Finished;
};
}
}

View File

@@ -46,9 +46,6 @@ public class User {
@Getter(AccessLevel.NONE) @Getter(AccessLevel.NONE)
private Set<UserAttribute> attributes; private Set<UserAttribute> attributes;
@Setter
private boolean tracked;
public List<UserAttribute> getAttributes() { public List<UserAttribute> getAttributes() {
return attributes.stream().toList(); return attributes.stream().toList();
} }

View File

@@ -25,8 +25,10 @@ package io.github.jwdeveloper.tiktok.handler;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import java.util.List;
public interface TikTokMessageHandler public interface TikTokMessageHandler
{ {
TikTokEvent handle(byte[] messagePayload) throws Exception; List<TikTokEvent> handle(byte[] messagePayload) throws Exception;
} }

View File

@@ -22,7 +22,7 @@
*/ */
package io.github.jwdeveloper.tiktok.live; package io.github.jwdeveloper.tiktok.live;
import io.github.jwdeveloper.tiktok.data.models.Gift; import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.data.models.Picture; import io.github.jwdeveloper.tiktok.data.models.Picture;
import java.util.List; import java.util.List;

View File

@@ -23,7 +23,7 @@
package io.github.jwdeveloper.tiktok.live; package io.github.jwdeveloper.tiktok.live;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.models.Gift; import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.data.models.users.User; import io.github.jwdeveloper.tiktok.data.models.users.User;
import java.util.List; import java.util.List;

View File

@@ -52,7 +52,6 @@ public interface EventsBuilder<T> {
T onWebsocketUnhandledMessage(EventConsumer<TikTokWebsocketUnhandledMessageEvent> event); T onWebsocketUnhandledMessage(EventConsumer<TikTokWebsocketUnhandledMessageEvent> event);
T onGiftCombo(EventConsumer<TikTokGiftComboEvent> event); T onGiftCombo(EventConsumer<TikTokGiftComboEvent> event);
T onGift(EventConsumer<TikTokGiftEvent> event); T onGift(EventConsumer<TikTokGiftEvent> event);
T onQuestion(EventConsumer<TikTokQuestionEvent> event); T onQuestion(EventConsumer<TikTokQuestionEvent> event);

View File

@@ -0,0 +1,20 @@
package io.github.jwdeveloper.tiktok.utils;
public class Stopwatch {
private long startTime;
private long stopTime;
public void start() {
startTime = System.nanoTime();
}
public long stop() {
stopTime = System.nanoTime();
return getElapsedTime();
}
public long getElapsedTime() {
return stopTime - startTime;
}
}

View File

@@ -80,7 +80,7 @@
<filter> <filter>
<artifact>*:*</artifact> <artifact>*:*</artifact>
<excludes> <excludes>
<exclude>**/tiktokSchema.proto/**</exclude> <exclude>**/*.proto</exclude>
</excludes> </excludes>
</filter> </filter>
</filters> </filters>

View File

@@ -41,6 +41,7 @@ import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager; import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver; import io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver;
import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandlerRegistration; import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandlerRegistration;
import io.github.jwdeveloper.tiktok.handlers.events.TikTokGiftEventHandler;
import io.github.jwdeveloper.tiktok.http.TikTokApiService; import io.github.jwdeveloper.tiktok.http.TikTokApiService;
import io.github.jwdeveloper.tiktok.http.TikTokCookieJar; import io.github.jwdeveloper.tiktok.http.TikTokCookieJar;
import io.github.jwdeveloper.tiktok.http.TikTokHttpClient; import io.github.jwdeveloper.tiktok.http.TikTokHttpClient;
@@ -50,6 +51,7 @@ import io.github.jwdeveloper.tiktok.listener.TikTokListenersManager;
import io.github.jwdeveloper.tiktok.live.LiveClient; import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.live.builder.EventConsumer; import io.github.jwdeveloper.tiktok.live.builder.EventConsumer;
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder; import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
import io.github.jwdeveloper.tiktok.mappers.TikTokGenericEventMapper;
import io.github.jwdeveloper.tiktok.utils.ConsoleColors; import io.github.jwdeveloper.tiktok.utils.ConsoleColors;
import io.github.jwdeveloper.tiktok.websocket.TikTokWebSocketClient; import io.github.jwdeveloper.tiktok.websocket.TikTokWebSocketClient;
@@ -147,10 +149,14 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
var apiClient = new TikTokHttpClient(cookieJar, requestFactory); var apiClient = new TikTokHttpClient(cookieJar, requestFactory);
var apiService = new TikTokApiService(apiClient, logger, clientSettings); var apiService = new TikTokApiService(apiClient, logger, clientSettings);
var giftManager = new TikTokGiftManager(); var giftManager = new TikTokGiftManager();
var eventMapper = new TikTokGenericEventMapper();
var giftHandler = new TikTokGiftEventHandler(giftManager);
var webResponseHandler = new TikTokMessageHandlerRegistration(tikTokEventHandler, var webResponseHandler = new TikTokMessageHandlerRegistration(tikTokEventHandler,
giftManager, tiktokRoomInfo,
tiktokRoomInfo); eventMapper,
giftHandler
);
var webSocketClient = new TikTokWebSocketClient(logger, var webSocketClient = new TikTokWebSocketClient(logger,
cookieJar, cookieJar,

View File

@@ -22,7 +22,7 @@
*/ */
package io.github.jwdeveloper.tiktok.gifts; package io.github.jwdeveloper.tiktok.gifts;
import io.github.jwdeveloper.tiktok.data.models.Gift; import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.data.models.Picture; import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException; import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.live.GiftManager; import io.github.jwdeveloper.tiktok.live.GiftManager;

View File

@@ -23,18 +23,20 @@
package io.github.jwdeveloper.tiktok.handlers; package io.github.jwdeveloper.tiktok.handlers;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent; 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.TikTokWebsocketMessageEvent;
import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketResponseEvent; import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketResponseEvent;
import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketUnhandledMessageEvent; import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketUnhandledMessageEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveMessageException; import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveMessageException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokMessageMappingException;
import io.github.jwdeveloper.tiktok.live.LiveClient; import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.mappers.TikTokGenericEventMapper;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import io.github.jwdeveloper.tiktok.utils.Stopwatch;
import java.util.Arrays; import java.time.Duration;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
@@ -43,21 +45,24 @@ public abstract class TikTokMessageHandler {
private final Map<String, io.github.jwdeveloper.tiktok.handler.TikTokMessageHandler> handlers; private final Map<String, io.github.jwdeveloper.tiktok.handler.TikTokMessageHandler> handlers;
private final TikTokEventObserver tikTokEventHandler; private final TikTokEventObserver tikTokEventHandler;
protected final TikTokGenericEventMapper mapper;
public TikTokMessageHandler(TikTokEventObserver tikTokEventHandler) { public TikTokMessageHandler(TikTokEventObserver tikTokEventHandler, TikTokGenericEventMapper mapper) {
handlers = new HashMap<>(); handlers = new HashMap<>();
this.tikTokEventHandler = tikTokEventHandler; this.tikTokEventHandler = tikTokEventHandler;
init(); this.mapper = mapper;
} }
public abstract void init();
public void registerMapping(Class<?> clazz, Function<byte[], TikTokEvent> func) { public void registerMapping(Class<?> clazz, Function<byte[], TikTokEvent> func) {
handlers.put(clazz.getSimpleName(), messagePayload -> List.of(func.apply(messagePayload)));
}
public void registerMappings(Class<?> clazz, Function<byte[], List<TikTokEvent>> func) {
handlers.put(clazz.getSimpleName(), func::apply); handlers.put(clazz.getSimpleName(), func::apply);
} }
public void registerMapping(Class<?> input, Class<?> output) { public void registerMapping(Class<?> input, Class<?> output) {
registerMapping(input, (e) -> mapMessageToEvent(input, output, e)); registerMapping(input, (e) -> mapper.mapToEvent(input, output, e));
} }
public void handle(LiveClient client, WebcastResponse webcastResponse) { public void handle(LiveClient client, WebcastResponse webcastResponse) {
@@ -71,18 +76,6 @@ public abstract class TikTokMessageHandler {
} }
} }
} }
public void handleSingleMessage(LiveClient client, String type, byte[] bytes) throws Exception {
if (!handlers.containsKey(type)) {
tikTokEventHandler.publish(client, new TikTokWebsocketUnhandledMessageEvent(WebcastResponse.Message.newBuilder().setMethod(type).build()));
return;
}
var handler = handlers.get(type);
var tiktokEvent = handler.handle(bytes);
tikTokEventHandler.publish(client, new TikTokWebsocketMessageEvent(tiktokEvent, WebcastResponse.Message.newBuilder().build()));
tikTokEventHandler.publish(client, tiktokEvent);
}
public void handleSingleMessage(LiveClient client, WebcastResponse.Message message) throws Exception { public void handleSingleMessage(LiveClient client, WebcastResponse.Message message) throws Exception {
var messageClassName = message.getMethod(); var messageClassName = message.getMethod();
@@ -91,29 +84,16 @@ public abstract class TikTokMessageHandler {
return; return;
} }
var handler = handlers.get(messageClassName); var handler = handlers.get(messageClassName);
var tiktokEvent = handler.handle(message.getPayload().toByteArray()); var stopwatch = new Stopwatch();
tikTokEventHandler.publish(client, new TikTokWebsocketMessageEvent(tiktokEvent, message)); stopwatch.start();
tikTokEventHandler.publish(client, tiktokEvent); var events = handler.handle(message.getPayload().toByteArray());
} var handlingTimeInMs = stopwatch.stop();
var metadata = new TikTokWebsocketMessageEvent.MetaData(Duration.ofNanos(handlingTimeInMs));
protected TikTokEvent mapMessageToEvent(Class<?> inputClazz, Class<?> outputClass, byte[] payload) { for (var event : events) {
try { tikTokEventHandler.publish(client, new TikTokWebsocketMessageEvent(event, message, metadata));
var parseMethod = inputClazz.getDeclaredMethod("parseFrom", byte[].class); tikTokEventHandler.publish(client, event);
var deserialized = parseMethod.invoke(null,payload);
var constructors = Arrays.stream(outputClass.getConstructors())
.filter(ea -> Arrays.stream(ea.getParameterTypes())
.toList()
.contains(inputClazz))
.findFirst();
if (constructors.isEmpty()) {
throw new TikTokMessageMappingException(inputClazz, outputClass, "Unable to find constructor with input class type");
}
var tiktokEvent = constructors.get().newInstance(deserialized);
return (TikTokEvent) tiktokEvent;
} catch (Exception ex) {
throw new TikTokMessageMappingException(inputClazz, outputClass, ex);
} }
} }
} }

View File

@@ -23,11 +23,8 @@
package io.github.jwdeveloper.tiktok.handlers; package io.github.jwdeveloper.tiktok.handlers;
import io.github.jwdeveloper.tiktok.TikTokRoomInfo; import io.github.jwdeveloper.tiktok.TikTokRoomInfo;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.*; import io.github.jwdeveloper.tiktok.data.events.*;
import io.github.jwdeveloper.tiktok.data.events.TikTokBarrageEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftComboEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent;
import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollEndEvent; 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.TikTokPollEvent;
import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollStartEvent; import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollStartEvent;
@@ -39,10 +36,9 @@ import io.github.jwdeveloper.tiktok.data.events.social.TikTokFollowEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokJoinEvent; import io.github.jwdeveloper.tiktok.data.events.social.TikTokJoinEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokLikeEvent; import io.github.jwdeveloper.tiktok.data.events.social.TikTokLikeEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokShareEvent; import io.github.jwdeveloper.tiktok.data.events.social.TikTokShareEvent;
import io.github.jwdeveloper.tiktok.data.models.Gift;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.Text; import io.github.jwdeveloper.tiktok.data.models.Text;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager; import io.github.jwdeveloper.tiktok.handlers.events.TikTokGiftEventHandler;
import io.github.jwdeveloper.tiktok.mappers.TikTokGenericEventMapper;
import io.github.jwdeveloper.tiktok.messages.webcast.*; import io.github.jwdeveloper.tiktok.messages.webcast.*;
import io.github.jwdeveloper.tiktok.models.SocialTypes; import io.github.jwdeveloper.tiktok.models.SocialTypes;
import lombok.SneakyThrows; import lombok.SneakyThrows;
@@ -50,19 +46,21 @@ import lombok.SneakyThrows;
import java.util.regex.Pattern; import java.util.regex.Pattern;
public class TikTokMessageHandlerRegistration extends TikTokMessageHandler { public class TikTokMessageHandlerRegistration extends TikTokMessageHandler {
private final TikTokGiftManager giftManager;
private final TikTokRoomInfo roomInfo; private final TikTokRoomInfo roomInfo;
private final TikTokGiftEventHandler giftHandler;
private final Pattern socialMediaPattern = Pattern.compile("pm_mt_guidance_viewer_([0-9]+)_share"); private final Pattern socialMediaPattern = Pattern.compile("pm_mt_guidance_viewer_([0-9]+)_share");
public TikTokMessageHandlerRegistration(TikTokEventObserver tikTokEventHandler, public TikTokMessageHandlerRegistration(TikTokEventObserver tikTokEventHandler,
TikTokGiftManager giftManager, TikTokRoomInfo roomInfo,
TikTokRoomInfo roomInfo) { TikTokGenericEventMapper genericTikTokEventMapper,
super(tikTokEventHandler); TikTokGiftEventHandler tikTokGiftEventHandler) {
this.giftManager = giftManager; super(tikTokEventHandler, genericTikTokEventMapper);
this.giftHandler = tikTokGiftEventHandler;
this.roomInfo = roomInfo; this.roomInfo = roomInfo;
init();
} }
@Override
public void init() { public void init() {
//ConnectionEvents events //ConnectionEvents events
@@ -80,7 +78,7 @@ public class TikTokMessageHandlerRegistration extends TikTokMessageHandler {
//User Interactions events //User Interactions events
registerMapping(WebcastChatMessage.class, TikTokCommentEvent.class); registerMapping(WebcastChatMessage.class, TikTokCommentEvent.class);
registerMapping(WebcastLikeMessage.class, this::handleLike); registerMapping(WebcastLikeMessage.class, this::handleLike);
registerMapping(WebcastGiftMessage.class, this::handleGift); registerMappings(WebcastGiftMessage.class, giftHandler::handleGift);
registerMapping(WebcastSocialMessage.class, this::handleSocialMedia); registerMapping(WebcastSocialMessage.class, this::handleSocialMedia);
registerMapping(WebcastMemberMessage.class, this::handleMemberMessage); registerMapping(WebcastMemberMessage.class, this::handleMemberMessage);
@@ -124,29 +122,6 @@ public class TikTokMessageHandlerRegistration extends TikTokMessageHandler {
}; };
} }
@SneakyThrows
private TikTokEvent handleGift(byte[] msg) {
var giftMessage = WebcastGiftMessage.parseFrom(msg);
var gift = giftManager.findById((int) giftMessage.getGiftId());
if (gift == Gift.UNDEFINED) {
gift = giftManager.findByName(giftMessage.getGift().getName());
}
if (gift == Gift.UNDEFINED) {
gift = giftManager.registerGift(
(int) giftMessage.getGift().getId(),
giftMessage.getGift().getName(),
giftMessage.getGift().getDiamondCount(),
Picture.map(giftMessage.getGift().getImage()));
}
if (giftMessage.getRepeatEnd() > 0) {
return new TikTokGiftComboEvent(gift, giftMessage);
}
return new TikTokGiftEvent(gift, giftMessage);
}
@SneakyThrows @SneakyThrows
private TikTokEvent handleSocialMedia(byte[] msg) { private TikTokEvent handleSocialMedia(byte[] msg) {
@@ -181,13 +156,13 @@ public class TikTokMessageHandlerRegistration extends TikTokMessageHandler {
} }
private TikTokEvent handleRoomUserSeqMessage(byte[] msg) { private TikTokEvent handleRoomUserSeqMessage(byte[] msg) {
var event = (TikTokRoomUserInfoEvent) mapMessageToEvent(WebcastRoomUserSeqMessage.class, TikTokRoomUserInfoEvent.class, msg); var event = (TikTokRoomUserInfoEvent) mapper.mapToEvent(WebcastRoomUserSeqMessage.class, TikTokRoomUserInfoEvent.class, msg);
roomInfo.setViewersCount(event.getTotalUsers()); roomInfo.setViewersCount(event.getTotalUsers());
return event; return event;
} }
private TikTokEvent handleLike(byte[] msg) { private TikTokEvent handleLike(byte[] msg) {
var event = (TikTokLikeEvent) mapMessageToEvent(WebcastLikeMessage.class, TikTokLikeEvent.class, msg); var event = (TikTokLikeEvent) mapper.mapToEvent(WebcastLikeMessage.class, TikTokLikeEvent.class, msg);
roomInfo.setLikesCount(event.getTotalLikes()); roomInfo.setLikesCount(event.getTotalLikes());
return event; return event;
} }
@@ -200,7 +175,7 @@ public class TikTokMessageHandlerRegistration extends TikTokMessageHandler {
return new TikTokRoomPinEvent(pinMessage, chatEvent); return new TikTokRoomPinEvent(pinMessage, chatEvent);
} }
//TODO check //TODO Probably not working
@SneakyThrows @SneakyThrows
private TikTokEvent handlePollEvent(byte[] msg) { private TikTokEvent handlePollEvent(byte[] msg) {
var poolMessage = WebcastPollMessage.parseFrom(msg); var poolMessage = WebcastPollMessage.parseFrom(msg);

View File

@@ -0,0 +1,86 @@
package io.github.jwdeveloper.tiktok.handlers.events;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftComboEvent;
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.Gift;
import io.github.jwdeveloper.tiktok.data.models.gifts.GiftSendType;
import io.github.jwdeveloper.tiktok.live.GiftManager;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage;
import lombok.SneakyThrows;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TikTokGiftEventHandler {
private final GiftManager giftManager;
private final Map<Long, WebcastGiftMessage> giftsMessages;
public TikTokGiftEventHandler(GiftManager giftManager) {
this.giftManager = giftManager;
giftsMessages = new HashMap<>();
}
@SneakyThrows
public List<TikTokEvent> handleGift(byte[] msg) {
var currentMessage = WebcastGiftMessage.parseFrom(msg);
var userId = currentMessage.getUser().getId();
var currentType = GiftSendType.fromNumber(currentMessage.getSendType());
var containsPreviousMessage = giftsMessages.containsKey(userId);
if (!containsPreviousMessage) {
if (currentType == GiftSendType.Finished) {
return List.of(getGiftEvent(currentMessage));
} else {
giftsMessages.put(userId, currentMessage);
return List.of(getGiftComboEvent(currentMessage, GiftSendType.Begin));
}
}
var previousMessage = giftsMessages.get(userId);
var previousType = GiftSendType.fromNumber(previousMessage.getSendType());
if (currentType == GiftSendType.Active &&
previousType == GiftSendType.Active) {
giftsMessages.put(userId, currentMessage);
return List.of(getGiftComboEvent(currentMessage, GiftSendType.Active));
}
if (currentType == GiftSendType.Finished &&
previousType == GiftSendType.Active) {
giftsMessages.clear();
return List.of(
getGiftComboEvent(currentMessage, GiftSendType.Finished),
getGiftEvent(currentMessage));
}
return List.of();
}
private TikTokGiftEvent getGiftEvent(WebcastGiftMessage message) {
var gift = getGiftObject(message);
return new TikTokGiftEvent(gift, message);
}
private TikTokGiftEvent getGiftComboEvent(WebcastGiftMessage message, GiftSendType state) {
var gift = getGiftObject(message);
return new TikTokGiftComboEvent(gift, message, state);
}
private Gift getGiftObject(WebcastGiftMessage giftMessage) {
var gift = giftManager.findById((int) giftMessage.getGiftId());
if (gift == Gift.UNDEFINED) {
gift = giftManager.findByName(giftMessage.getGift().getName());
}
if (gift == Gift.UNDEFINED) {
gift = giftManager.registerGift(
(int) giftMessage.getGift().getId(),
giftMessage.getGift().getName(),
giftMessage.getGift().getDiamondCount(),
Picture.map(giftMessage.getGift().getImage()));
}
return gift;
}
}

View File

@@ -133,12 +133,12 @@ public class TikTokHttpClient {
private String getSignedUrl(String url, Map<String, Object> parameters) { private String getSignedUrl(String url, Map<String, Object> parameters) {
var fullUrl = HttpUtils.parseParameters(url,parameters); var fullUrl = HttpUtils.parseParameters(url,parameters);
var singHeaders = new TreeMap<String,Object>(); var signParams = new TreeMap<String,Object>();
singHeaders.put("client", "ttlive-java"); signParams.put("client", "ttlive-java");
singHeaders.put("uuc", 1); signParams.put("uuc", 1);
singHeaders.put("url", fullUrl); signParams.put("url", fullUrl);
var request = requestFactory.setQueries(singHeaders); var request = requestFactory.setQueries(signParams);
var content = request.get(Constants.TIKTOK_SIGN_API); var content = request.get(Constants.TIKTOK_SIGN_API);

View File

@@ -0,0 +1,85 @@
package io.github.jwdeveloper.tiktok.mappers;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokMessageMappingException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* Goal of this class is to map ProtocolBuffer objects to TikTok Event in generic way
*
* First parameter is ProtocolBuffer class type
* Second parameters is TikTokEvent class type
* Third parameters is bytes payload
*
* mapToEvent(WebcastGiftMessage.class, TikTokGiftEvent.class, payload)
*
* How does it work?
* 1. Finds method `parseFrom(byte[] bytes)` inside ProtocolBuffer class
* 2. put payload to the method methods and create new instance of ProtcolBuffer object
* 3. Finds in TikTokEvent constructor that takes ProtocolBuffer type as parameter
* 4. create new Instance in TikTokEvents using object from step 2 and constructor from step 3
*
* methodCache and constructorCache are used to boost performance
*/
public class TikTokGenericEventMapper {
private record TypePair(Class<?> a, Class<?> b) {
}
private final Map<Class<?>, Method> methodCache;
private final Map<TypePair, Constructor<?>> constructorCache;
public TikTokGenericEventMapper() {
this.methodCache = new HashMap<>();
this.constructorCache = new HashMap<>();
}
public TikTokEvent mapToEvent(Class<?> inputClazz, Class<?> outputClass, byte[] payload) {
try {
var method = getParsingMethod(inputClazz);
var deserializedMessage = method.invoke(null, payload);
var constructor = getParsingConstructor(inputClazz, outputClass);
var tiktokEvent = constructor.newInstance(deserializedMessage);
return (TikTokEvent) tiktokEvent;
} catch (Exception ex) {
throw new TikTokMessageMappingException(inputClazz, outputClass, ex);
}
}
private Method getParsingMethod(Class<?> input) throws NoSuchMethodException {
if (methodCache.containsKey(input)) {
return methodCache.get(input);
}
var method = input.getDeclaredMethod("parseFrom", byte[].class);
methodCache.put(input, method);
return method;
}
private Constructor<?> getParsingConstructor(Class<?> input, Class<?> output) {
var pair = new TypePair(input, output);
if (constructorCache.containsKey(pair)) {
return constructorCache.get(pair);
}
var optional = Arrays.stream(output.getConstructors())
.filter(ea -> Arrays.stream(ea.getParameterTypes())
.toList()
.contains(input))
.findFirst();
if (optional.isEmpty()) {
throw new TikTokMessageMappingException(input, output, "Unable to find constructor with input class type");
}
constructorCache.put(pair, optional.get());
return optional.get();
}
}

View File

@@ -125,7 +125,7 @@ public class TikTokWebSocketListener extends WebSocketClient {
} }
return Optional.of(websocketMessage); return Optional.of(websocketMessage);
} catch (Exception e) { } catch (Exception e) {
throw new TikTokProtocolBufferException("Unable to parse WebcastWebsocketMessage", buffer, e); throw new TikTokProtocolBufferException("Unable to parse WebcastPushFrame", buffer, e);
} }
} }

View File

@@ -28,7 +28,6 @@ import java.util.Random;
public class TikTokWebSocketPingingTask public class TikTokWebSocketPingingTask
{ {
private Thread thread; private Thread thread;
private boolean isRunning = false; private boolean isRunning = false;
private final int MIN_TIMEOUT = 5; private final int MIN_TIMEOUT = 5;
private final int MAX_TIMEOUT = 100; private final int MAX_TIMEOUT = 100;
@@ -37,7 +36,7 @@ public class TikTokWebSocketPingingTask
public void run(WebSocket webSocket) public void run(WebSocket webSocket)
{ {
stop(); stop();
var thread = new Thread(() -> thread = new Thread(() ->
{ {
pingTask(webSocket); pingTask(webSocket);
}); });

View File

@@ -22,7 +22,7 @@
*/ */
package io.github.jwdeveloper.tiktok.gifts; package io.github.jwdeveloper.tiktok.gifts;
import io.github.jwdeveloper.tiktok.data.models.Gift; import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.data.models.Picture; import io.github.jwdeveloper.tiktok.data.models.Picture;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;

View File

@@ -46,7 +46,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>TestApplication</artifactId> <artifactId>Examples</artifactId>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>io.github.jwdeveloper.tiktok</groupId> <groupId>io.github.jwdeveloper.tiktok</groupId>

View File

@@ -22,8 +22,10 @@
*/ */
package io.github.jwdeveloper.tiktok.tools.collector; package io.github.jwdeveloper.tiktok.tools.collector;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage;
import io.github.jwdeveloper.tiktok.tools.collector.client.TikTokMessageCollectorClient; import io.github.jwdeveloper.tiktok.tools.collector.client.TikTokMessageCollectorClient;
import java.io.IOException;
import java.sql.SQLException; import java.sql.SQLException;
public class RunCollector { public class RunCollector {
@@ -33,15 +35,48 @@ public class RunCollector {
//WebcastLinkMicBattleItemCard does streamer win battle? //WebcastLinkMicBattleItemCard does streamer win battle?
public static void main(String[] args) throws SQLException { public static void main(String[] args) throws SQLException, IOException {
TikTokMessageCollectorClient.create("messageCollector") TikTokMessageCollectorClient.create("giftsCollector")
.addUser("bangbetmenygy") .addUser("cbcgod")
.addUser("mr_cios") // .addUser("mr_cios")
.addUser("sleepstreamxxx") // .addUser("cbcgod")
.addUser("psychotropnazywo") // .addUser("psychotropnazywo")
.addUser("accordionistka") // .addUser("accordionistka")
.addEventFilter(WebcastGiftMessage.class)
.addOnBuilder(liveClientBuilder ->
{
liveClientBuilder.onGift((liveClient, event) ->
{
});
liveClientBuilder.onGiftCombo((liveClient, event) ->
{
});
liveClientBuilder.onGift((liveClient, event) ->
{
var sb = new StringBuilder();
sb.append("GIFT User: " + event.getUser().getDisplayName()+" ");
sb.append("Name: " + event.getGift().name() + " ");
sb.append("Combo: " + event.getCombo() + " ");
System.out.println(sb.toString());
});
liveClientBuilder.onGiftCombo((liveClient, event) ->
{
var sb = new StringBuilder();
sb.append("COMBO User: " + event.getUser().getDisplayName()+" ");
sb.append("Name: " + event.getGift().name() + " ");
sb.append("Combo: " + event.getCombo() + " ");
sb.append("Type: " + event.getComboState().name());
System.out.println(sb.toString());
});
})
.buildAndRun(); .buildAndRun();
System.in.read();
} }

View File

@@ -62,7 +62,7 @@ public class MessageCollector {
var queue = messages.get(name); var queue = messages.get(name);
if (queue.size() > limit) { if (queue.size() > limit) {
queue.poll(); queue.remove();
} }
queue.add(new MessageData(base64, host, LocalDateTime.now().toString())); queue.add(new MessageData(base64, host, LocalDateTime.now().toString()));

View File

@@ -34,6 +34,7 @@ import io.github.jwdeveloper.tiktok.tools.collector.tables.TikTokMessageModel;
import io.github.jwdeveloper.tiktok.tools.collector.tables.TikTokResponseModel; import io.github.jwdeveloper.tiktok.tools.collector.tables.TikTokResponseModel;
import java.util.Base64; import java.util.Base64;
import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -46,8 +47,9 @@ public class TikTokClientFactory {
this.tikTokDatabase = tikTokDatabase; this.tikTokDatabase = tikTokDatabase;
} }
public CompletableFuture<LiveClient> runClientAsync(String tiktokUser, Consumer<LiveClientBuilder> onBuilder) { public CompletableFuture<LiveClient> runClientAsync(String tiktokUser, List<Class<?>> filters, Consumer<LiveClientBuilder> onBuilder) {
var builder = TikTokLive.newClient(tiktokUser); var builder = TikTokLive.newClient(tiktokUser);
var msgFilter = filters.stream().map(Class::getSimpleName).toList();
onBuilder.accept(builder); onBuilder.accept(builder);
return builder.onConnected((liveClient, event) -> return builder.onConnected((liveClient, event) ->
{ {
@@ -62,7 +64,12 @@ public class TikTokClientFactory {
responseModel.setHostName(liveClient.getRoomInfo().getHostName()); responseModel.setHostName(liveClient.getRoomInfo().getHostName());
tikTokDatabase.insertResponse(responseModel); tikTokDatabase.insertResponse(responseModel);
liveClient.getLogger().info("Response"); liveClient.getLogger().info("Response");
for (var message : event.getResponse().getMessagesList()) { for (var message : event.getResponse().getMessagesList())
{
if(msgFilter.size() > 0 && !msgFilter.contains(message.getMethod()))
{
continue;
}
messageCollector.addMessage(liveClient.getLogger(), liveClient.getRoomInfo().getHostName(), message); messageCollector.addMessage(liveClient.getLogger(), liveClient.getRoomInfo().getHostName(), message);
} }
}) })
@@ -71,7 +78,7 @@ public class TikTokClientFactory {
var eventName = event.getEvent().getClass().getSimpleName(); var eventName = event.getEvent().getClass().getSimpleName();
/* /*
if (filter.size() != 0 && !filter.contains(event.getEvent().getClass())) { if (msgFilter.size() != 0 && !msgFilter.contains(event.getEvent().getClass())) {
return; return;
}*/ }*/
@@ -83,7 +90,7 @@ public class TikTokClientFactory {
model.setMessage(messageBinary); model.setMessage(messageBinary);
// tikTokDatabase.insertMessage(model); // tikTokDatabase.insertMessage(model);
liveClient.getLogger().info("EVENT: [" + tiktokUser + "] " + eventName); // liveClient.getLogger().info("EVENT: [" + tiktokUser + "] " + eventName);
}) })
.onError((liveClient, event) -> .onError((liveClient, event) ->
{ {

View File

@@ -82,7 +82,7 @@ public class TikTokMessagessCollectorBuilder {
db.init(); db.init();
var factory = new TikTokClientFactory(messageCollector, db); var factory = new TikTokClientFactory(messageCollector, db);
for (var user : users) { for (var user : users) {
var client = factory.runClientAsync(user, onBuilder); var client = factory.runClientAsync(user,filters, onBuilder);
client.thenAccept(liveClient -> client.thenAccept(liveClient ->
{ {
tiktokclients.add(liveClient); tiktokclients.add(liveClient);

View File

@@ -47,7 +47,15 @@ public class RunJsonTester {
sb.append(MessageUtil.getContent(event.getData())); sb.append(MessageUtil.getContent(event.getData()));
liveClient.getLogger().info(sb.toString()); // liveClient.getLogger().info(sb.toString());
}).
onGift((liveClient, event) ->
{
liveClient.getLogger().info("Gift event: "+event.toJson());
})
.onGiftCombo((liveClient, event) ->
{
liveClient.getLogger().info("GiftCombo event"+event.toJson());
}) })
.onError((liveClient, event) -> .onError((liveClient, event) ->
{ {

View File

@@ -45,6 +45,7 @@ public class Main {
app.get("/tiktok/connect", handler::connect); app.get("/tiktok/connect", handler::connect);
app.get("/tiktok/disconnect", handler::disconnect); app.get("/tiktok/disconnect", handler::disconnect);
app.get("/tiktok/events", handler::events); app.get("/tiktok/events", handler::events);
app.get("/tiktok/events/pages", handler::eventPages);
app.get("/tiktok/events/message", handler::eventMessage); app.get("/tiktok/events/message", handler::eventMessage);
} }
} }

View File

@@ -23,7 +23,6 @@
package io.github.jwdeveloper.tiktok.webviewer; package io.github.jwdeveloper.tiktok.webviewer;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import io.github.jwdeveloper.tiktok.tools.collector.client.MessageCollector; import io.github.jwdeveloper.tiktok.tools.collector.client.MessageCollector;
import io.github.jwdeveloper.tiktok.tools.collector.client.TikTokMessageCollectorClient; import io.github.jwdeveloper.tiktok.tools.collector.client.TikTokMessageCollectorClient;
import io.github.jwdeveloper.tiktok.tools.collector.client.TikTokMessagessCollectorBuilder; import io.github.jwdeveloper.tiktok.tools.collector.client.TikTokMessagessCollectorBuilder;
@@ -34,6 +33,7 @@ import java.sql.SQLException;
import java.util.Base64; import java.util.Base64;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
public class TikTokManager { public class TikTokManager {
TikTokMessagessCollectorBuilder client; TikTokMessagessCollectorBuilder client;
@@ -45,7 +45,17 @@ public class TikTokManager {
public void connect(String name) throws SQLException { public void connect(String name) throws SQLException {
disconnect(); disconnect();
client = TikTokMessageCollectorClient.create(msgCollector, "web").addUser(name); client = TikTokMessageCollectorClient.create(msgCollector, "web")
.addOnBuilder(liveClientBuilder ->
{
liveClientBuilder.onGift((liveClient, event) ->
{
});
})
.addUser(name);
client.buildAndRun(); client.buildAndRun();
} }
@@ -58,19 +68,49 @@ public class TikTokManager {
} }
public MessageDto getMessage(String event) throws InvalidProtocolBufferException { public MessageDto getMessage(String event, String index) throws InvalidProtocolBufferException {
var eventData = msgCollector.getMessages().get(event); var eventData = msgCollector.getMessages().get(event);
var messages = eventData.stream().toList(); var messages = eventData.stream().toList();
var random = new Random(); var random = new Random();
var index = random.nextInt(messages.size()-1);
var msg = messages.get(index); var msgIndex = 0;
if (index != null && !index.isEmpty()) {
msgIndex = Integer.parseInt(index);
msgIndex = Math.min(msgIndex, messages.size() - 1);
msgIndex = Math.max(msgIndex, 0);
}
var msg = messages.get(msgIndex);
var bytes = Base64.getDecoder().decode(msg.getEventData()); var bytes = Base64.getDecoder().decode(msg.getEventData());
var content = MessageUtil.getContent(event,bytes); var content = MessageUtil.getContent(event, bytes);
return new MessageDto(content, msg.getEventData(), event); return new MessageDto(content, msg.getEventData(), event);
} }
public PagesDto getPages(String event) throws InvalidProtocolBufferException {
var eventData = msgCollector.getMessages().get(event);
var messages = eventData.stream().toList();
var counter = new AtomicInteger(-1);
var pages = messages.stream().map(e ->
{
return "http://localhost:8001/tiktok/events/message?eventName=" + event + "&page=" + counter.incrementAndGet();
}).toList();
return new PagesDto(event, messages.size(), pages);
}
@Value
public class PagesDto {
String eventName;
int pages;
List<String> links;
}
@Value @Value
public class MessageDto { public class MessageDto {
String content; String content;

View File

@@ -23,6 +23,7 @@
package io.github.jwdeveloper.tiktok.webviewer.handlers; package io.github.jwdeveloper.tiktok.webviewer.handlers;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import io.github.jwdeveloper.tiktok.webviewer.TikTokManager; import io.github.jwdeveloper.tiktok.webviewer.TikTokManager;
import io.javalin.http.Context; import io.javalin.http.Context;
@@ -55,7 +56,7 @@ public class TikTokHandler {
public void events(Context context) throws SQLException { public void events(Context context) throws SQLException {
var events = tikTokManager.getEventsNames(); var events = tikTokManager.getEventsNames();
var gson = new Gson(); var gson = getGson();
var result = gson.toJson(events); var result = gson.toJson(events);
context.result(result); context.result(result);
context.status(200); context.status(200);
@@ -63,8 +64,22 @@ public class TikTokHandler {
public void eventMessage(Context context) throws InvalidProtocolBufferException { public void eventMessage(Context context) throws InvalidProtocolBufferException {
String name = context.queryParam("eventName"); String name = context.queryParam("eventName");
var result = tikTokManager.getMessage(name); String page = context.queryParam("page");
var gson = new Gson();
var result = tikTokManager.getMessage(name, page);
var gson = getGson();
context.result(gson.toJson(result)); context.result(gson.toJson(result));
} }
public void eventPages(Context context) throws InvalidProtocolBufferException {
String name = context.queryParam("eventName");
var result = tikTokManager.getPages(name);
var gson = getGson();
context.result(gson.toJson(result));
}
public Gson getGson() {
return new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create();
}
} }

View File

@@ -103,6 +103,10 @@
language: 'json', language: 'json',
theme: 'vs-dark' theme: 'vs-dark'
}); });
editor.onDidChangeModelContent(function () {
console.log("hello")
});
}); });
</script> </script>
@@ -130,11 +134,8 @@
</div> </div>
<div class="col-md-10 editor-container "> <div class="col-md-10 editor-container ">
<nav aria-label="Page navigation example"> <nav aria-label="Page navigation example">
<ul class="pagination"> <ul id="pages" class="pagination">
<li class="page-item btn-primary"><a class="page-link" href="#">Previous</a></li> <li class="page-item btn-primary"><a class="page-link" href="#">Previous</a></li>
<li class="page-item"><a class="page-link" href="#">1</a></li>
<li class="page-item"><a class="page-link" href="#">2</a></li>
<li class="page-item"><a class="page-link" href="#">3</a></li>
<li class="page-item"><a class="page-link" href="#">Next</a></li> <li class="page-item"><a class="page-link" href="#">Next</a></li>
</ul> </ul>
</nav> </nav>
@@ -156,10 +157,9 @@
<script> <script>
async function connect() { async function connect() {
let name = document.getElementById('name').value; let name = document.getElementById('name').value;
name = "bangbetmenygy" // name = "bangbetmenygy"
let response = await fetch(`http://localhost:8001/tiktok/connect?name=${name}`); let response = await fetch(`http://localhost:8001/tiktok/connect?name=${name}`);
let greeting = await response.text(); let greeting = await response.text();
console.log("connect",greeting); console.log("connect",greeting);
@@ -176,13 +176,39 @@
{ {
let response = await fetch(`http://localhost:8001/tiktok/events/message?eventName=${event}`); let response = await fetch(`http://localhost:8001/tiktok/events/message?eventName=${event}`);
let json = await response.text(); let json = await response.text();
// json = json.replace(/\/n/g, "\n");
let root= JSON.parse(json); let root= JSON.parse(json);
console.log(root)
editor.setValue(root.content); editor.setValue(root.content);
} }
async function loadMessageLink(link)
{
let response = await fetch(link);
let json = await response.text();
let root= JSON.parse(json);
editor.setValue(root.content);
}
async function loadPagination(event)
{
let response = await fetch(`http://localhost:8001/tiktok/events/pages?eventName=${event}`);
let json = await response.text();
let object = JSON.parse(json);
let pages = object.links;
console.log("PAGES: ",pages)
$("#pages").empty();
$.each(pages, function(index, element) {
let content = $('<button>',{
class: 'btn btn-primary',
text: index
}).click(async function()
{
await loadMessageLink(element);
console.log(editor)
});
$("#pages").append(content);
});
}
async function showEvents() { async function showEvents() {
let response = await fetch(`http://localhost:8001/tiktok/events`); let response = await fetch(`http://localhost:8001/tiktok/events`);
let json = await response.text(); let json = await response.text();
@@ -197,6 +223,7 @@
}).click(async function() }).click(async function()
{ {
await loadMessage(event); await loadMessage(event);
await loadPagination(event);
}); });
$("#eventList").append(listItem); $("#eventList").append(listItem);
}); });
@@ -213,6 +240,9 @@
} }
var connected = false; var connected = false;
var paginationIndex = 0;
var maxPages = 10;
var pages = [];
setInterval(loop, 1000) setInterval(loop, 1000)
showEvents() showEvents()

View File

@@ -0,0 +1,98 @@
/*
* 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;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import java.time.Duration;
import java.util.logging.Level;
public class CodeExample
{
public static void main(String[] args)
{
TikTokLive.newClient("mrBeast")
.onGift((liveClient, event) ->
{
System.out.println("Thank you for Money!");
})
.buildAndConnect();
}
public static void codeExample()
{
TikTokLive.newClient("bangbetmenygy")
.onGift((liveClient, event) ->
{
if(event.getGift() == Gift.ROSE)
{
liveClient.getLogger().info("Rose from "+event.getUser().getDisplayName());
return;
}
liveClient.getLogger().info("Thank you for "+event.getGift().getName());
})
.onJoin((liveClient, event) ->
{
liveClient.getLogger().info("Hello "+event.getUser().getDisplayName());
})
.onConnected((liveClient, event) ->
{
liveClient.getLogger().info("Connected to live ");
})
.onError((liveClient, event) ->
{
liveClient.getLogger().info("ERROR! "+event.getException().getMessage());
})
.buildAndConnect();
}
public static void configExample()
{
TikTokLive.newClient("bangbetmenygy")
.configure((settings) ->
{
settings.setHostName("bangbetmenygy"); // This method is useful in case you want change hostname later
settings.setClientLanguage("en"); // Language
settings.setTimeout(Duration.ofSeconds(2)); // Connection timeout
settings.setLogLevel(Level.ALL); // Log level
settings.setPrintToConsole(true); // Printing all logs to console even if log level is Level.OFF
settings.setHandleExistingEvents(true); // Invokes all TikTok events that had occurred before connection
settings.setRetryOnConnectionFailure(true); // Reconnecting if TikTok user is offline
settings.setRetryConnectionTimeout(Duration.ofSeconds(1)); // Timeout before next reconnection
//Optional: Sometimes not every message from chat are send to TikTokLiveJava to fix this issue you can set sessionId
// documentation how to obtain sessionId https://github.com/isaackogan/TikTok-Live-Connector#send-chat-messages
settings.setSessionId("86c3c8bf4b17ebb2d74bb7fa66fd0000");
//Optional:
//RoomId can be used as an override if you're having issues with HostId.
//You can find it in the HTML for the livestream-page
settings.setRoomId("XXXXXXXXXXXXXXXXX");
})
.buildAndConnect();
}
}

View File

@@ -86,7 +86,7 @@ public class EventsInfoGenerator {
#### {{method-name}} [{{event-name}}](https://github.com/jwdeveloper/TikTok-Live-Java/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/events/messages.java) ## {{method-name}} [{{event-name}}](https://github.com/jwdeveloper/TikTok-Live-Java/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/events/messages.java)
{{content}} {{content}}

View File

@@ -1,8 +1,35 @@
[![](https://jitpack.io/v/jwdeveloper/TikTok-Live-Java.svg)](https://jitpack.io/#jwdeveloper/TikTok-Live-Java) <div align="center" >
<a target="blank" >
<img src="https://raw.githubusercontent.com/jwdeveloper/TikTokLiveJava/develop-1_0_0/Tools-ReadmeGenerator/src/main/resources/logo.svg" width="15%" >
</img>
</a>
</div>
<div align="center" >
<h1>TikTok Live Java</h1>
❤️❤️🎁 *Connect to TikTok live in 3 lines* 🎁❤️❤️
<div align="center" >
<a href="https://jitpack.io/#jwdeveloper/TikTok-Live-Java" target="blank" >
<img src="https://jitpack.io/v/jwdeveloper/TikTok-Live-Java.svg" width="20%" >
</img>
</a>
# TikTokLive Java <a href="https://discord.gg/e2XwPNTBBr" target="blank" >
A Java library based on [TikTokLive](https://github.com/isaackogan/TikTokLive) and [TikTokLiveSharp](https://github.com/sebheron/TikTokLiveSharp). Use it to receive live stream events such as comments and gifts in realtime from [TikTok LIVE](https://www.tiktok.com/live) by connecting to TikTok's internal WebCast push service. <img src="https://img.shields.io/badge/Discord-%235865F2.svg?style=for-the-badge&logo=discord&logoColor=white" >
</img>
</a>
<a target="blank" >
<img src="https://img.shields.io/badge/java-%23ED8B00.svg?style=for-the-badge&logo=openjdk&logoColor=white" >
</img>
</a>
</div>
</div>
# Introduction
A Java library inspired by [TikTokLive](https://github.com/isaackogan/TikTokLive) and [TikTokLiveSharp](https://github.com/sebheron/TikTokLiveSharp). Use it to receive live stream events such as comments and gifts in realtime from [TikTok LIVE](https://www.tiktok.com/live) by connecting to TikTok's internal WebCast push service.
The library includes a wrapper that connects to the WebCast service using just the username (`uniqueId`). This allows you to connect to your own live chat as well as the live chat of other streamers. The library includes a wrapper that connects to the WebCast service using just the username (`uniqueId`). This allows you to connect to your own live chat as well as the live chat of other streamers.
No credentials are required. Events such as [Members Joining](#member), [Gifts](#gift), [Subscriptions](#subscribe), [Viewers](#roomuser), [Follows](#social), [Shares](#social), [Questions](#questionnew), [Likes](#like) and [Battles](#linkmicbattle) can be tracked. No credentials are required. Events such as [Members Joining](#member), [Gifts](#gift), [Subscriptions](#subscribe), [Viewers](#roomuser), [Follows](#social), [Shares](#social), [Questions](#questionnew), [Likes](#like) and [Battles](#linkmicbattle) can be tracked.
@@ -42,11 +69,6 @@ Do you prefer other programming languages?
<version>{{version}}</version> <version>{{version}}</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
</dependencies> </dependencies>
``` ```
@@ -59,7 +81,6 @@ Do you prefer other programming languages?
{{events-content}} {{events-content}}
<br>
<br> <br>
## Listener Example ## Listener Example

View File

@@ -27,11 +27,12 @@ import io.github.jwdeveloper.tiktok.TikTokRoomInfo;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException; import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager; import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager;
import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandlerRegistration; import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandlerRegistration;
import io.github.jwdeveloper.tiktok.http.TikTokApiService; import io.github.jwdeveloper.tiktok.handlers.events.TikTokGiftEventHandler;
import io.github.jwdeveloper.tiktok.http.TikTokCookieJar; import io.github.jwdeveloper.tiktok.http.TikTokCookieJar;
import io.github.jwdeveloper.tiktok.http.TikTokHttpClient; import io.github.jwdeveloper.tiktok.http.TikTokHttpClient;
import io.github.jwdeveloper.tiktok.http.TikTokHttpRequestFactory; import io.github.jwdeveloper.tiktok.http.TikTokHttpRequestFactory;
import io.github.jwdeveloper.tiktok.listener.TikTokListenersManager; import io.github.jwdeveloper.tiktok.listener.TikTokListenersManager;
import io.github.jwdeveloper.tiktok.mappers.TikTokGenericEventMapper;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import io.github.jwdeveloper.tiktok.mockClient.mocks.ApiServiceMock; import io.github.jwdeveloper.tiktok.mockClient.mocks.ApiServiceMock;
import io.github.jwdeveloper.tiktok.mockClient.mocks.LiveClientMock; import io.github.jwdeveloper.tiktok.mockClient.mocks.LiveClientMock;
@@ -97,8 +98,9 @@ public class TikTokMockBuilder extends TikTokLiveClientBuilder {
var apiClient = new TikTokHttpClient(cookie, requestFactory); var apiClient = new TikTokHttpClient(cookie, requestFactory);
var apiService = new ApiServiceMock(apiClient, logger, clientSettings); var apiService = new ApiServiceMock(apiClient, logger, clientSettings);
var webResponseHandler = new TikTokMessageHandlerRegistration(tikTokEventHandler, var webResponseHandler = new TikTokMessageHandlerRegistration(tikTokEventHandler,
giftManager, tiktokRoomInfo,
tiktokRoomInfo); new TikTokGenericEventMapper(),
new TikTokGiftEventHandler(giftManager));
var webSocketClient = new WebsocketClientMock(logger, responses, webResponseHandler); var webSocketClient = new WebsocketClientMock(logger, responses, webResponseHandler);
return new LiveClientMock(tiktokRoomInfo, return new LiveClientMock(tiktokRoomInfo,

View File

@@ -22,6 +22,7 @@
*/ */
package io.github.jwdeveloper.tiktok.mockClient.mocks; package io.github.jwdeveloper.tiktok.mockClient.mocks;
import com.google.protobuf.ByteString;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException; import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveMessageException; import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveMessageException;
import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandler; import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandler;
@@ -102,7 +103,11 @@ public class WebsocketClientMock implements SocketClient {
if (!messages.isEmpty()) { if (!messages.isEmpty()) {
var messageStr = messages.pop(); var messageStr = messages.pop();
try { try {
messageHandler.handleSingleMessage(tikTokLiveClient, messageStr.getMessageType(), messageStr.getMessageValue()); var msg = WebcastResponse.Message.newBuilder()
.setMethod(messageStr.messageType)
.setPayload(ByteString.copyFrom(messageStr.getMessageValue()))
.build();
messageHandler.handleSingleMessage(tikTokLiveClient, msg);
} catch (Exception e) { } catch (Exception e) {
logger.info("Unable to parse message for response " + messageStr.getMessageType()); logger.info("Unable to parse message for response " + messageStr.getMessageType());
throw new TikTokLiveException(e); throw new TikTokLiveException(e);

15
pom.xml
View File

@@ -69,20 +69,7 @@
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>attach-javadocs</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins> </plugins>
</build> </build>