mirror of
https://github.com/jwdeveloper/TikTokLiveJava.git
synced 2026-02-27 08:49:40 -05:00
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:
19
API/pom.xml
19
API/pom.xml
@@ -58,6 +58,25 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</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>
|
||||
</build>
|
||||
</project>
|
||||
@@ -116,6 +116,7 @@ public class Constants {
|
||||
public static Map<String, String> DefaultRequestHeaders() {
|
||||
var headers = new HashMap<String, String>();
|
||||
|
||||
headers.put("authority","www.tiktok.com");
|
||||
headers.put("Connection", "keep-alive");
|
||||
headers.put("Cache-Control", "max-age=0");
|
||||
headers.put("Accept", "text/html,application/json,application/protobuf");
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package io.github.jwdeveloper.tiktok.data.events.common;
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public class TikTokEmptyEvent extends TikTokEvent
|
||||
{
|
||||
|
||||
}
|
||||
@@ -26,8 +26,8 @@ package io.github.jwdeveloper.tiktok.data.events.common;
|
||||
import io.github.jwdeveloper.tiktok.utils.JsonUtil;
|
||||
import lombok.Getter;
|
||||
|
||||
/*
|
||||
Base class for all events
|
||||
/**
|
||||
* Base class for all events
|
||||
*/
|
||||
@Getter
|
||||
public abstract class TikTokEvent {
|
||||
|
||||
@@ -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.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 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)
|
||||
@Getter
|
||||
public class TikTokGiftComboEvent extends TikTokGiftEvent
|
||||
{
|
||||
public TikTokGiftComboEvent(Gift gift, WebcastGiftMessage msg) {
|
||||
public class TikTokGiftComboEvent extends TikTokGiftEvent {
|
||||
private final GiftSendType comboState;
|
||||
|
||||
public TikTokGiftComboEvent(Gift gift, WebcastGiftMessage msg, GiftSendType comboState) {
|
||||
super(gift, msg);
|
||||
this.comboState = comboState;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.EventType;
|
||||
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.messages.webcast.WebcastGiftMessage;
|
||||
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)
|
||||
@Getter
|
||||
@@ -41,15 +44,10 @@ public class TikTokGiftEvent extends TikTokHeaderEvent {
|
||||
private final Gift gift;
|
||||
private final User user;
|
||||
private final int combo;
|
||||
private final boolean comboFinished;
|
||||
private final int comboIndex;
|
||||
|
||||
public TikTokGiftEvent(Gift gift, WebcastGiftMessage msg) {
|
||||
super(msg.getCommon());
|
||||
this.gift = gift;
|
||||
user = User.map(msg.getUser(),msg.getUserIdentity());
|
||||
user = User.map(msg.getUser(), msg.getUserIdentity());
|
||||
combo = msg.getComboCount();
|
||||
comboFinished = msg.getRepeatEnd() > 0;
|
||||
comboIndex = msg.getRepeatCount();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,9 @@ import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
|
||||
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Value;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
|
||||
/**
|
||||
@@ -41,4 +44,12 @@ public class TikTokWebsocketMessageEvent extends TikTokEvent
|
||||
private TikTokEvent event;
|
||||
|
||||
private WebcastResponse.Message message;
|
||||
|
||||
private MetaData metaData;
|
||||
|
||||
@Value
|
||||
public static class MetaData
|
||||
{
|
||||
Duration handlingTime;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,9 +69,14 @@ public class Text {
|
||||
var format = matcher.replaceAll("%s");
|
||||
|
||||
var output = new ArrayList<String>();
|
||||
for (var piece : textPieces) {
|
||||
for (var piece : textPieces)
|
||||
{
|
||||
output.add(piece.getText());
|
||||
}
|
||||
if(matcher.groupCount() != output.size())
|
||||
{
|
||||
return format;
|
||||
}
|
||||
return String.format(format, output.toArray());
|
||||
}
|
||||
|
||||
|
||||
@@ -21,8 +21,9 @@
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
// 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;
|
||||
|
||||
@Getter
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -46,9 +46,6 @@ public class User {
|
||||
@Getter(AccessLevel.NONE)
|
||||
private Set<UserAttribute> attributes;
|
||||
|
||||
@Setter
|
||||
private boolean tracked;
|
||||
|
||||
public List<UserAttribute> getAttributes() {
|
||||
return attributes.stream().toList();
|
||||
}
|
||||
|
||||
@@ -25,8 +25,10 @@ package io.github.jwdeveloper.tiktok.handler;
|
||||
|
||||
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public interface TikTokMessageHandler
|
||||
{
|
||||
TikTokEvent handle(byte[] messagePayload) throws Exception;
|
||||
List<TikTokEvent> handle(byte[] messagePayload) throws Exception;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
*/
|
||||
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 java.util.List;
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
package io.github.jwdeveloper.tiktok.live;
|
||||
|
||||
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 java.util.List;
|
||||
|
||||
@@ -52,7 +52,6 @@ public interface EventsBuilder<T> {
|
||||
T onWebsocketUnhandledMessage(EventConsumer<TikTokWebsocketUnhandledMessageEvent> event);
|
||||
|
||||
T onGiftCombo(EventConsumer<TikTokGiftComboEvent> event);
|
||||
|
||||
T onGift(EventConsumer<TikTokGiftEvent> event);
|
||||
|
||||
T onQuestion(EventConsumer<TikTokQuestionEvent> event);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -80,7 +80,7 @@
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>**/tiktokSchema.proto/**</exclude>
|
||||
<exclude>**/*.proto</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
|
||||
@@ -41,6 +41,7 @@ import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
|
||||
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager;
|
||||
import io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver;
|
||||
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.TikTokCookieJar;
|
||||
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.builder.EventConsumer;
|
||||
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.websocket.TikTokWebSocketClient;
|
||||
|
||||
@@ -147,10 +149,14 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
|
||||
var apiClient = new TikTokHttpClient(cookieJar, requestFactory);
|
||||
var apiService = new TikTokApiService(apiClient, logger, clientSettings);
|
||||
var giftManager = new TikTokGiftManager();
|
||||
var eventMapper = new TikTokGenericEventMapper();
|
||||
var giftHandler = new TikTokGiftEventHandler(giftManager);
|
||||
|
||||
var webResponseHandler = new TikTokMessageHandlerRegistration(tikTokEventHandler,
|
||||
giftManager,
|
||||
tiktokRoomInfo);
|
||||
tiktokRoomInfo,
|
||||
eventMapper,
|
||||
giftHandler
|
||||
);
|
||||
|
||||
var webSocketClient = new TikTokWebSocketClient(logger,
|
||||
cookieJar,
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
*/
|
||||
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.exceptions.TikTokLiveException;
|
||||
import io.github.jwdeveloper.tiktok.live.GiftManager;
|
||||
|
||||
@@ -23,18 +23,20 @@
|
||||
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.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.exceptions.TikTokMessageMappingException;
|
||||
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.utils.Stopwatch;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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 TikTokEventObserver tikTokEventHandler;
|
||||
protected final TikTokGenericEventMapper mapper;
|
||||
|
||||
public TikTokMessageHandler(TikTokEventObserver tikTokEventHandler) {
|
||||
public TikTokMessageHandler(TikTokEventObserver tikTokEventHandler, TikTokGenericEventMapper mapper) {
|
||||
handlers = new HashMap<>();
|
||||
this.tikTokEventHandler = tikTokEventHandler;
|
||||
init();
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
public abstract void init();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -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 {
|
||||
var messageClassName = message.getMethod();
|
||||
@@ -91,29 +84,16 @@ public abstract class TikTokMessageHandler {
|
||||
return;
|
||||
}
|
||||
var handler = handlers.get(messageClassName);
|
||||
var tiktokEvent = handler.handle(message.getPayload().toByteArray());
|
||||
tikTokEventHandler.publish(client, new TikTokWebsocketMessageEvent(tiktokEvent, message));
|
||||
tikTokEventHandler.publish(client, tiktokEvent);
|
||||
}
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.start();
|
||||
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) {
|
||||
try {
|
||||
var parseMethod = inputClazz.getDeclaredMethod("parseFrom", byte[].class);
|
||||
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);
|
||||
for (var event : events) {
|
||||
tikTokEventHandler.publish(client, new TikTokWebsocketMessageEvent(event, message, metadata));
|
||||
tikTokEventHandler.publish(client, event);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,11 +23,8 @@
|
||||
package io.github.jwdeveloper.tiktok.handlers;
|
||||
|
||||
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.TikTokBarrageEvent;
|
||||
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.common.TikTokEvent;
|
||||
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;
|
||||
@@ -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.TikTokLikeEvent;
|
||||
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.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.models.SocialTypes;
|
||||
import lombok.SneakyThrows;
|
||||
@@ -50,19 +46,21 @@ import lombok.SneakyThrows;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class TikTokMessageHandlerRegistration extends TikTokMessageHandler {
|
||||
private final TikTokGiftManager giftManager;
|
||||
|
||||
private final TikTokRoomInfo roomInfo;
|
||||
private final TikTokGiftEventHandler giftHandler;
|
||||
private final Pattern socialMediaPattern = Pattern.compile("pm_mt_guidance_viewer_([0-9]+)_share");
|
||||
|
||||
public TikTokMessageHandlerRegistration(TikTokEventObserver tikTokEventHandler,
|
||||
TikTokGiftManager giftManager,
|
||||
TikTokRoomInfo roomInfo) {
|
||||
super(tikTokEventHandler);
|
||||
this.giftManager = giftManager;
|
||||
TikTokRoomInfo roomInfo,
|
||||
TikTokGenericEventMapper genericTikTokEventMapper,
|
||||
TikTokGiftEventHandler tikTokGiftEventHandler) {
|
||||
super(tikTokEventHandler, genericTikTokEventMapper);
|
||||
this.giftHandler = tikTokGiftEventHandler;
|
||||
this.roomInfo = roomInfo;
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
|
||||
//ConnectionEvents events
|
||||
@@ -80,7 +78,7 @@ public class TikTokMessageHandlerRegistration extends TikTokMessageHandler {
|
||||
//User Interactions events
|
||||
registerMapping(WebcastChatMessage.class, TikTokCommentEvent.class);
|
||||
registerMapping(WebcastLikeMessage.class, this::handleLike);
|
||||
registerMapping(WebcastGiftMessage.class, this::handleGift);
|
||||
registerMappings(WebcastGiftMessage.class, giftHandler::handleGift);
|
||||
registerMapping(WebcastSocialMessage.class, this::handleSocialMedia);
|
||||
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
|
||||
private TikTokEvent handleSocialMedia(byte[] msg) {
|
||||
@@ -181,13 +156,13 @@ public class TikTokMessageHandlerRegistration extends TikTokMessageHandler {
|
||||
}
|
||||
|
||||
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());
|
||||
return event;
|
||||
}
|
||||
|
||||
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());
|
||||
return event;
|
||||
}
|
||||
@@ -200,7 +175,7 @@ public class TikTokMessageHandlerRegistration extends TikTokMessageHandler {
|
||||
return new TikTokRoomPinEvent(pinMessage, chatEvent);
|
||||
}
|
||||
|
||||
//TODO check
|
||||
//TODO Probably not working
|
||||
@SneakyThrows
|
||||
private TikTokEvent handlePollEvent(byte[] msg) {
|
||||
var poolMessage = WebcastPollMessage.parseFrom(msg);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -133,12 +133,12 @@ public class TikTokHttpClient {
|
||||
private String getSignedUrl(String url, Map<String, Object> parameters) {
|
||||
|
||||
var fullUrl = HttpUtils.parseParameters(url,parameters);
|
||||
var singHeaders = new TreeMap<String,Object>();
|
||||
singHeaders.put("client", "ttlive-java");
|
||||
singHeaders.put("uuc", 1);
|
||||
singHeaders.put("url", fullUrl);
|
||||
var signParams = new TreeMap<String,Object>();
|
||||
signParams.put("client", "ttlive-java");
|
||||
signParams.put("uuc", 1);
|
||||
signParams.put("url", fullUrl);
|
||||
|
||||
var request = requestFactory.setQueries(singHeaders);
|
||||
var request = requestFactory.setQueries(signParams);
|
||||
var content = request.get(Constants.TIKTOK_SIGN_API);
|
||||
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -125,7 +125,7 @@ public class TikTokWebSocketListener extends WebSocketClient {
|
||||
}
|
||||
return Optional.of(websocketMessage);
|
||||
} catch (Exception e) {
|
||||
throw new TikTokProtocolBufferException("Unable to parse WebcastWebsocketMessage", buffer, e);
|
||||
throw new TikTokProtocolBufferException("Unable to parse WebcastPushFrame", buffer, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ import java.util.Random;
|
||||
public class TikTokWebSocketPingingTask
|
||||
{
|
||||
private Thread thread;
|
||||
|
||||
private boolean isRunning = false;
|
||||
private final int MIN_TIMEOUT = 5;
|
||||
private final int MAX_TIMEOUT = 100;
|
||||
@@ -37,7 +36,7 @@ public class TikTokWebSocketPingingTask
|
||||
public void run(WebSocket webSocket)
|
||||
{
|
||||
stop();
|
||||
var thread = new Thread(() ->
|
||||
thread = new Thread(() ->
|
||||
{
|
||||
pingTask(webSocket);
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
*/
|
||||
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 org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>TestApplication</artifactId>
|
||||
<artifactId>Examples</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.github.jwdeveloper.tiktok</groupId>
|
||||
|
||||
Binary file not shown.
@@ -22,8 +22,10 @@
|
||||
*/
|
||||
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 java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
|
||||
public class RunCollector {
|
||||
@@ -33,15 +35,48 @@ public class RunCollector {
|
||||
|
||||
//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")
|
||||
.addUser("bangbetmenygy")
|
||||
.addUser("mr_cios")
|
||||
.addUser("sleepstreamxxx")
|
||||
.addUser("psychotropnazywo")
|
||||
.addUser("accordionistka")
|
||||
TikTokMessageCollectorClient.create("giftsCollector")
|
||||
.addUser("cbcgod")
|
||||
// .addUser("mr_cios")
|
||||
// .addUser("cbcgod")
|
||||
// .addUser("psychotropnazywo")
|
||||
// .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();
|
||||
|
||||
System.in.read();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ public class MessageCollector {
|
||||
|
||||
var queue = messages.get(name);
|
||||
if (queue.size() > limit) {
|
||||
queue.poll();
|
||||
queue.remove();
|
||||
}
|
||||
|
||||
queue.add(new MessageData(base64, host, LocalDateTime.now().toString()));
|
||||
|
||||
@@ -34,6 +34,7 @@ import io.github.jwdeveloper.tiktok.tools.collector.tables.TikTokMessageModel;
|
||||
import io.github.jwdeveloper.tiktok.tools.collector.tables.TikTokResponseModel;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -46,8 +47,9 @@ public class TikTokClientFactory {
|
||||
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 msgFilter = filters.stream().map(Class::getSimpleName).toList();
|
||||
onBuilder.accept(builder);
|
||||
return builder.onConnected((liveClient, event) ->
|
||||
{
|
||||
@@ -62,7 +64,12 @@ public class TikTokClientFactory {
|
||||
responseModel.setHostName(liveClient.getRoomInfo().getHostName());
|
||||
tikTokDatabase.insertResponse(responseModel);
|
||||
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);
|
||||
}
|
||||
})
|
||||
@@ -71,7 +78,7 @@ public class TikTokClientFactory {
|
||||
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;
|
||||
}*/
|
||||
|
||||
@@ -83,7 +90,7 @@ public class TikTokClientFactory {
|
||||
model.setMessage(messageBinary);
|
||||
|
||||
// tikTokDatabase.insertMessage(model);
|
||||
liveClient.getLogger().info("EVENT: [" + tiktokUser + "] " + eventName);
|
||||
// liveClient.getLogger().info("EVENT: [" + tiktokUser + "] " + eventName);
|
||||
})
|
||||
.onError((liveClient, event) ->
|
||||
{
|
||||
|
||||
@@ -82,7 +82,7 @@ public class TikTokMessagessCollectorBuilder {
|
||||
db.init();
|
||||
var factory = new TikTokClientFactory(messageCollector, db);
|
||||
for (var user : users) {
|
||||
var client = factory.runClientAsync(user, onBuilder);
|
||||
var client = factory.runClientAsync(user,filters, onBuilder);
|
||||
client.thenAccept(liveClient ->
|
||||
{
|
||||
tiktokclients.add(liveClient);
|
||||
|
||||
@@ -47,7 +47,15 @@ public class RunJsonTester {
|
||||
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) ->
|
||||
{
|
||||
|
||||
Binary file not shown.
@@ -45,6 +45,7 @@ public class Main {
|
||||
app.get("/tiktok/connect", handler::connect);
|
||||
app.get("/tiktok/disconnect", handler::disconnect);
|
||||
app.get("/tiktok/events", handler::events);
|
||||
app.get("/tiktok/events/pages", handler::eventPages);
|
||||
app.get("/tiktok/events/message", handler::eventMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
package io.github.jwdeveloper.tiktok.webviewer;
|
||||
|
||||
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.TikTokMessageCollectorClient;
|
||||
import io.github.jwdeveloper.tiktok.tools.collector.client.TikTokMessagessCollectorBuilder;
|
||||
@@ -34,6 +33,7 @@ import java.sql.SQLException;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public class TikTokManager {
|
||||
TikTokMessagessCollectorBuilder client;
|
||||
@@ -45,7 +45,17 @@ public class TikTokManager {
|
||||
|
||||
public void connect(String name) throws SQLException {
|
||||
disconnect();
|
||||
client = TikTokMessageCollectorClient.create(msgCollector, "web").addUser(name);
|
||||
client = TikTokMessageCollectorClient.create(msgCollector, "web")
|
||||
.addOnBuilder(liveClientBuilder ->
|
||||
{
|
||||
|
||||
|
||||
liveClientBuilder.onGift((liveClient, event) ->
|
||||
{
|
||||
|
||||
});
|
||||
})
|
||||
.addUser(name);
|
||||
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 messages = eventData.stream().toList();
|
||||
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 content = MessageUtil.getContent(event,bytes);
|
||||
var content = MessageUtil.getContent(event, bytes);
|
||||
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
|
||||
public class MessageDto {
|
||||
String content;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
package io.github.jwdeveloper.tiktok.webviewer.handlers;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import io.github.jwdeveloper.tiktok.webviewer.TikTokManager;
|
||||
import io.javalin.http.Context;
|
||||
@@ -55,7 +56,7 @@ public class TikTokHandler {
|
||||
|
||||
public void events(Context context) throws SQLException {
|
||||
var events = tikTokManager.getEventsNames();
|
||||
var gson = new Gson();
|
||||
var gson = getGson();
|
||||
var result = gson.toJson(events);
|
||||
context.result(result);
|
||||
context.status(200);
|
||||
@@ -63,8 +64,22 @@ public class TikTokHandler {
|
||||
|
||||
public void eventMessage(Context context) throws InvalidProtocolBufferException {
|
||||
String name = context.queryParam("eventName");
|
||||
var result = tikTokManager.getMessage(name);
|
||||
var gson = new Gson();
|
||||
String page = context.queryParam("page");
|
||||
|
||||
var result = tikTokManager.getMessage(name, page);
|
||||
var gson = getGson();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +103,10 @@
|
||||
language: 'json',
|
||||
theme: 'vs-dark'
|
||||
});
|
||||
editor.onDidChangeModelContent(function () {
|
||||
console.log("hello")
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -130,11 +134,8 @@
|
||||
</div>
|
||||
<div class="col-md-10 editor-container ">
|
||||
<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"><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>
|
||||
</ul>
|
||||
</nav>
|
||||
@@ -156,10 +157,9 @@
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
async function connect() {
|
||||
let name = document.getElementById('name').value;
|
||||
name = "bangbetmenygy"
|
||||
// name = "bangbetmenygy"
|
||||
let response = await fetch(`http://localhost:8001/tiktok/connect?name=${name}`);
|
||||
let greeting = await response.text();
|
||||
console.log("connect",greeting);
|
||||
@@ -176,13 +176,39 @@
|
||||
{
|
||||
let response = await fetch(`http://localhost:8001/tiktok/events/message?eventName=${event}`);
|
||||
let json = await response.text();
|
||||
// json = json.replace(/\/n/g, "\n");
|
||||
let root= JSON.parse(json);
|
||||
console.log(root)
|
||||
|
||||
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() {
|
||||
let response = await fetch(`http://localhost:8001/tiktok/events`);
|
||||
let json = await response.text();
|
||||
@@ -197,6 +223,7 @@
|
||||
}).click(async function()
|
||||
{
|
||||
await loadMessage(event);
|
||||
await loadPagination(event);
|
||||
});
|
||||
$("#eventList").append(listItem);
|
||||
});
|
||||
@@ -213,6 +240,9 @@
|
||||
}
|
||||
|
||||
var connected = false;
|
||||
var paginationIndex = 0;
|
||||
var maxPages = 10;
|
||||
var pages = [];
|
||||
setInterval(loop, 1000)
|
||||
showEvents()
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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}}
|
||||
|
||||
|
||||
@@ -1,8 +1,35 @@
|
||||
[](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 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.
|
||||
<a href="https://discord.gg/e2XwPNTBBr" target="blank" >
|
||||
<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.
|
||||
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>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.10.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
@@ -59,7 +81,6 @@ Do you prefer other programming languages?
|
||||
|
||||
{{events-content}}
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
## Listener Example
|
||||
|
||||
@@ -27,11 +27,12 @@ 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.http.TikTokApiService;
|
||||
import io.github.jwdeveloper.tiktok.handlers.events.TikTokGiftEventHandler;
|
||||
import io.github.jwdeveloper.tiktok.http.TikTokCookieJar;
|
||||
import io.github.jwdeveloper.tiktok.http.TikTokHttpClient;
|
||||
import io.github.jwdeveloper.tiktok.http.TikTokHttpRequestFactory;
|
||||
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.mockClient.mocks.ApiServiceMock;
|
||||
import io.github.jwdeveloper.tiktok.mockClient.mocks.LiveClientMock;
|
||||
@@ -97,8 +98,9 @@ public class TikTokMockBuilder extends TikTokLiveClientBuilder {
|
||||
var apiClient = new TikTokHttpClient(cookie, requestFactory);
|
||||
var apiService = new ApiServiceMock(apiClient, logger, clientSettings);
|
||||
var webResponseHandler = new TikTokMessageHandlerRegistration(tikTokEventHandler,
|
||||
giftManager,
|
||||
tiktokRoomInfo);
|
||||
tiktokRoomInfo,
|
||||
new TikTokGenericEventMapper(),
|
||||
new TikTokGiftEventHandler(giftManager));
|
||||
var webSocketClient = new WebsocketClientMock(logger, responses, webResponseHandler);
|
||||
|
||||
return new LiveClientMock(tiktokRoomInfo,
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
*/
|
||||
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.TikTokLiveMessageException;
|
||||
import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandler;
|
||||
@@ -102,7 +103,11 @@ public class WebsocketClientMock implements SocketClient {
|
||||
if (!messages.isEmpty()) {
|
||||
var messageStr = messages.pop();
|
||||
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) {
|
||||
logger.info("Unable to parse message for response " + messageStr.getMessageType());
|
||||
throw new TikTokLiveException(e);
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
15
pom.xml
15
pom.xml
@@ -69,20 +69,7 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</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>
|
||||
</build>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user