Events handling

This commit is contained in:
JW
2023-08-16 17:36:40 +02:00
parent 59befcd6f9
commit 4b9e531c95
121 changed files with 4368 additions and 613 deletions

View File

@@ -4,7 +4,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>TikTokLiveJava</artifactId>
<groupId>org.example</groupId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -17,43 +17,6 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<sourceDirectory>target/generated-sources/protobuf/java</sourceDirectory>
<plugins>
<plugin>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.0</version>
<executions>
<execution>
<phase>initialize</phase>
<goals>
<goal>detect</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.17.3:exe:${os.detected.classifier}</protocArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Add other plugins as needed -->
</plugins>
</build>
<dependencies>
<dependency>
@@ -61,11 +24,13 @@
<artifactId>Java-WebSocket</artifactId>
<version>1.5.4</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.23.0</version>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
@@ -73,7 +38,6 @@
<scope>compile</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
@@ -87,9 +51,6 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
@@ -103,7 +64,7 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.example</groupId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>API</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>

View File

@@ -1,11 +0,0 @@
package io.github.jwdeveloper.tiktok;
public class Main
{
public static void main(String[] args)
{
System.out.println("Hello world!");
}
}

View File

@@ -1,97 +0,0 @@
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.http.TikTokApiService;
import io.github.jwdeveloper.tiktok.http.TikTokCookieJar;
import io.github.jwdeveloper.tiktok.http.TikTokHttpApiClient;
import io.github.jwdeveloper.tiktok.http.TikTokHttpRequestFactory;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.live.TikTokLiveMeta;
import io.github.jwdeveloper.tiktok.websocket.TikTokWebsocketClient;
import java.time.Duration;
import java.util.Map;
import java.util.function.Consumer;
import java.util.logging.Logger;
public class TikTokClientBuilder {
private String userName;
private final ClientSettings clientSettings;
private Map<String, Object> clientParameters;
private Logger logger;
public TikTokClientBuilder(String userName) {
this.userName = userName;
this.clientSettings = Constants.DefaultClientSettings();
this.clientParameters = Constants.DefaultClientParams();
this.logger = Logger.getLogger(TikTokLive.class.getName());
}
public TikTokClientBuilder clientSettings(Consumer<ClientSettings> consumer) {
consumer.accept(clientSettings);
return this;
}
public TikTokClientBuilder hostUserName(String userName) {
this.userName = userName;
return this;
}
public TikTokClientBuilder clientParameters(Map<String, Object> clientParameters) {
this.clientParameters = clientParameters;
return this;
}
public TikTokClientBuilder addClientParameters(String key, Object value) {
this.clientParameters.put(key, value);
return this;
}
private void validate() {
if (clientSettings.getTimeout() == null) {
clientSettings.setTimeout(Duration.ofSeconds(Constants.DEFAULT_TIMEOUT));
}
if (clientSettings.getPollingInterval() == null) {
clientSettings.setPollingInterval(Duration.ofSeconds(Constants.DEFAULT_POLLTIME));
}
if (clientSettings.getClientLanguage() == null || clientSettings.getClientLanguage().equals("")) {
clientSettings.setClientLanguage(Constants.DefaultClientSettings().getClientLanguage());
}
if (clientSettings.getSocketBufferSize() < 500_000) {
clientSettings.setSocketBufferSize(Constants.DefaultClientSettings().getSocketBufferSize());
}
if (userName == null || userName.equals("")) {
throw new RuntimeException("UserName can not be null");
}
if (clientParameters == null) {
clientParameters = Constants.DefaultClientParams();
}
clientParameters.put("app_language", clientSettings.getClientLanguage());
clientParameters.put("webcast_language", clientSettings.getClientLanguage());
}
public LiveClient build() {
validate();
var meta = new TikTokLiveMeta();
meta.setUserName(userName);
var cookieJar = new TikTokCookieJar();
var requestFactory = new TikTokHttpRequestFactory(cookieJar);
var apiClient = new TikTokHttpApiClient(cookieJar,clientSettings, requestFactory);
var apiService = new TikTokApiService(apiClient, logger,clientParameters);
var webSocketClient = new TikTokWebsocketClient(logger, cookieJar,clientParameters, requestFactory,clientSettings);
var giftManager =new TikTokGiftManager(logger, apiService, clientSettings);
return new TikTokLiveClient(meta,apiService, webSocketClient, giftManager, logger);
}
}

View File

@@ -1,7 +1,10 @@
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.events.objects.TikTokGift;
import io.github.jwdeveloper.tiktok.http.TikTokApiService;
import io.github.jwdeveloper.tiktok.live.models.gift.TikTokGift;
import io.github.jwdeveloper.tiktok.models.GiftId;
import lombok.Getter;
import java.util.HashMap;
import java.util.List;
@@ -14,11 +17,15 @@ public class TikTokGiftManager {
private TikTokApiService apiService;
private Map<Integer, TikTokGift> gifts;
@Getter
private Map<GiftId, TikTokGift> activeGifts;
public TikTokGiftManager(Logger logger, TikTokApiService apiService, ClientSettings clientSettings) {
this.logger = logger;
this.clientSettings = clientSettings;
this.apiService = apiService;
this.gifts = new HashMap<>();
activeGifts = new HashMap<>();
}
public void loadGifts() {
@@ -26,11 +33,13 @@ public class TikTokGiftManager {
return;
}
logger.info("Fetching gifts");
gifts =apiService.fetchAvailableGifts();
//TODO gifts =apiService.fetchAvailableGifts();
}
public List<TikTokGift> getGifts()
{
return gifts.values().stream().toList();
}
}

View File

@@ -4,8 +4,10 @@ package io.github.jwdeveloper.tiktok;
public class TikTokLive
{
public static TikTokClientBuilder newClient(String userName)
public static TikTokLiveClientBuilder newClient(String userName)
{
return new TikTokClientBuilder(userName);
return new TikTokLiveClientBuilder(userName);
}
}

View File

@@ -1,5 +1,7 @@
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventHandler;
import io.github.jwdeveloper.tiktok.http.TikTokApiService;
import io.github.jwdeveloper.tiktok.live.ConnectionState;
import io.github.jwdeveloper.tiktok.live.LiveClient;
@@ -14,6 +16,8 @@ public class TikTokLiveClient implements LiveClient {
private final TikTokGiftManager giftManager;
private final TikTokApiService apiClient;
private final TikTokWebsocketClient webSocketClient;
private final TikTokEventHandler tikTokEventHandler;
private final Logger logger;
@@ -21,15 +25,19 @@ public class TikTokLiveClient implements LiveClient {
TikTokApiService tikTokApiService,
TikTokWebsocketClient webSocketClient,
TikTokGiftManager tikTokGiftManager,
TikTokEventHandler tikTokEventHandler,
Logger logger) {
this.meta = tikTokLiveMeta;
this.giftManager = tikTokGiftManager;
this.apiClient = tikTokApiService;
this.webSocketClient = webSocketClient;
this.logger = logger;
this.tikTokEventHandler = tikTokEventHandler;
}
public void run() {
tryConnect();
}

View File

@@ -0,0 +1,295 @@
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.events.TikTokEvent;
import io.github.jwdeveloper.tiktok.events.TikTokEventBuilder;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventHandler;
import io.github.jwdeveloper.tiktok.events.messages.*;
import io.github.jwdeveloper.tiktok.handlers.WebResponseHandler;
import io.github.jwdeveloper.tiktok.http.TikTokApiService;
import io.github.jwdeveloper.tiktok.http.TikTokCookieJar;
import io.github.jwdeveloper.tiktok.http.TikTokHttpApiClient;
import io.github.jwdeveloper.tiktok.http.TikTokHttpRequestFactory;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.live.TikTokLiveMeta;
import io.github.jwdeveloper.tiktok.websocket.TikTokWebsocketClient;
import io.github.jwdeveloper.tiktok.handlers.WebResponseHandlerBase;
import java.time.Duration;
import java.util.Map;
import java.util.function.Consumer;
import java.util.logging.Logger;
public class TikTokLiveClientBuilder implements TikTokEventBuilder<TikTokLiveClientBuilder> {
private String userName;
private final ClientSettings clientSettings;
private Map<String, Object> clientParameters;
private Logger logger;
private TikTokEventHandler tikTokEventHandler;
public TikTokLiveClientBuilder(String userName) {
this.tikTokEventHandler = new TikTokEventHandler();
this.userName = userName;
this.clientSettings = Constants.DefaultClientSettings();
this.clientParameters = Constants.DefaultClientParams();
this.logger = Logger.getLogger(TikTokLive.class.getName());
}
public TikTokLiveClientBuilder clientSettings(Consumer<ClientSettings> consumer) {
consumer.accept(clientSettings);
return this;
}
public TikTokLiveClientBuilder hostUserName(String userName) {
this.userName = userName;
return this;
}
public TikTokLiveClientBuilder clientParameters(Map<String, Object> clientParameters) {
this.clientParameters = clientParameters;
return this;
}
public TikTokLiveClientBuilder addClientParameters(String key, Object value) {
this.clientParameters.put(key, value);
return this;
}
private void validate() {
if (clientSettings.getTimeout() == null) {
clientSettings.setTimeout(Duration.ofSeconds(Constants.DEFAULT_TIMEOUT));
}
if (clientSettings.getPollingInterval() == null) {
clientSettings.setPollingInterval(Duration.ofSeconds(Constants.DEFAULT_POLLTIME));
}
if (clientSettings.getClientLanguage() == null || clientSettings.getClientLanguage().equals("")) {
clientSettings.setClientLanguage(Constants.DefaultClientSettings().getClientLanguage());
}
if (clientSettings.getSocketBufferSize() < 500_000) {
clientSettings.setSocketBufferSize(Constants.DefaultClientSettings().getSocketBufferSize());
}
if (userName == null || userName.equals("")) {
throw new RuntimeException("UserName can not be null");
}
if (clientParameters == null) {
clientParameters = Constants.DefaultClientParams();
}
clientParameters.put("app_language", clientSettings.getClientLanguage());
clientParameters.put("webcast_language", clientSettings.getClientLanguage());
}
public LiveClient build() {
validate();
var meta = new TikTokLiveMeta();
meta.setUserName(userName);
var cookieJar = new TikTokCookieJar();
var requestFactory = new TikTokHttpRequestFactory(cookieJar);
var apiClient = new TikTokHttpApiClient(cookieJar, clientSettings, requestFactory);
var apiService = new TikTokApiService(apiClient, logger, clientParameters);
var giftManager = new TikTokGiftManager(logger, apiService, clientSettings);
var webResponseHandler = new WebResponseHandler(tikTokEventHandler,giftManager);
var webSocketClient = new TikTokWebsocketClient(logger, cookieJar, clientParameters, requestFactory, clientSettings, webResponseHandler);
return new TikTokLiveClient(meta, apiService, webSocketClient, giftManager, tikTokEventHandler, logger);
}
public LiveClient buildAndRun() {
var client = build();
client.run();
return client;
}
public TikTokLiveClientBuilder onLinkMicFanTicket(Consumer<TikTokLinkMicFanTicketEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkMicFanTicketEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onEnvelope(Consumer<TikTokEnvelopeEvent> event) {
tikTokEventHandler.subscribe(TikTokEnvelopeEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onShopMessage(Consumer<TikTokShopMessageEvent> event) {
tikTokEventHandler.subscribe(TikTokShopMessageEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onDetectMessage(Consumer<TikTokDetectMessageEvent> event) {
tikTokEventHandler.subscribe(TikTokDetectMessageEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLinkLayerMessage(Consumer<TikTokLinkLayerMessageEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkLayerMessageEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onDisconnected(Consumer<TikTokDisconnectedEvent> event) {
tikTokEventHandler.subscribe(TikTokDisconnectedEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onConnected(Consumer<TikTokConnectedEvent> event) {
tikTokEventHandler.subscribe(TikTokConnectedEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onCaption(Consumer<TikTokCaptionEvent> event) {
tikTokEventHandler.subscribe(TikTokCaptionEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onQuestion(Consumer<TikTokQuestionEvent> event) {
tikTokEventHandler.subscribe(TikTokQuestionEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onRoomPinMessage(Consumer<TikTokRoomPinMessageEvent> event) {
tikTokEventHandler.subscribe(TikTokRoomPinMessageEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onRoomMessage(Consumer<TikTokRoomMessageEvent> event) {
tikTokEventHandler.subscribe(TikTokRoomMessageEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLike(Consumer<TikTokLikeEvent> event) {
tikTokEventHandler.subscribe(TikTokLikeEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLinkMessage(Consumer<TikTokLinkMessageEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkMessageEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onBarrageMessage(Consumer<TikTokBarrageMessageEvent> event) {
tikTokEventHandler.subscribe(TikTokBarrageMessageEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onGiftMessage(Consumer<TikTokGiftMessageEvent> event) {
tikTokEventHandler.subscribe(TikTokGiftMessageEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLinkMicArmies(Consumer<TikTokLinkMicArmiesEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkMicArmiesEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onEmote(Consumer<TikTokEmoteEvent> event) {
tikTokEventHandler.subscribe(TikTokEmoteEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onUnauthorizedMember(Consumer<TikTokUnauthorizedMemberEvent> event) {
tikTokEventHandler.subscribe(TikTokUnauthorizedMemberEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onInRoomBanner(Consumer<TikTokInRoomBannerEvent> event) {
tikTokEventHandler.subscribe(TikTokInRoomBannerEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLinkMicMethod(Consumer<TikTokLinkMicMethodEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkMicMethodEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onSubscribe(Consumer<TikTokSubscribeEvent> event) {
tikTokEventHandler.subscribe(TikTokSubscribeEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onPollMessage(Consumer<TikTokPollMessageEvent> event) {
tikTokEventHandler.subscribe(TikTokPollMessageEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onFollow(Consumer<TikTokFollowEvent> event) {
tikTokEventHandler.subscribe(TikTokFollowEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onRoomViewerData(Consumer<TikTokRoomViewerDataEvent> event) {
tikTokEventHandler.subscribe(TikTokRoomViewerDataEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onGoalUpdate(Consumer<TikTokGoalUpdateEvent> event) {
tikTokEventHandler.subscribe(TikTokGoalUpdateEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onComment(Consumer<TikTokCommentEvent> event) {
tikTokEventHandler.subscribe(TikTokCommentEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onRankUpdate(Consumer<TikTokRankUpdateEvent> event) {
tikTokEventHandler.subscribe(TikTokRankUpdateEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onIMDelete(Consumer<TikTokIMDeleteEvent> event) {
tikTokEventHandler.subscribe(TikTokIMDeleteEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onJoin(Consumer<TikTokJoinEvent> event) {
tikTokEventHandler.subscribe(TikTokJoinEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onRankText(Consumer<TikTokRankTextEvent> event) {
tikTokEventHandler.subscribe(TikTokRankTextEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onShare(Consumer<TikTokShareEvent> event) {
tikTokEventHandler.subscribe(TikTokShareEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onSubNotify(Consumer<TikTokSubNotifyEvent> event) {
tikTokEventHandler.subscribe(TikTokSubNotifyEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLinkMicBattle(Consumer<TikTokLinkMicBattleEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkMicBattleEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onGiftBroadcast(Consumer<TikTokGiftBroadcastEvent> event) {
tikTokEventHandler.subscribe(TikTokGiftBroadcastEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onEvent(Consumer<TikTokEvent> event)
{
tikTokEventHandler.subscribe(TikTokEvent.class,event);
return this;
}
}

View File

@@ -1,23 +0,0 @@
package io.github.jwdeveloper.tiktok;
public class TikTokLiveException extends RuntimeException
{
public TikTokLiveException() {
}
public TikTokLiveException(String message) {
super(message);
}
public TikTokLiveException(String message, Throwable cause) {
super(message, cause);
}
public TikTokLiveException(Throwable cause) {
super(cause);
}
public TikTokLiveException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -1,4 +0,0 @@
package io.github.jwdeveloper.tiktok.events;
public class TikTokChatEvent {
}

View File

@@ -1,6 +0,0 @@
package io.github.jwdeveloper.tiktok.events;
public class TikTokEvent
{
}

View File

@@ -1,4 +0,0 @@
package io.github.jwdeveloper.tiktok.events;
public class TikTokGiftEvent {
}

View File

@@ -0,0 +1,40 @@
package io.github.jwdeveloper.tiktok.handlers;
import io.github.jwdeveloper.tiktok.events.TikTokEvent;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
public class TikTokEventHandler {
private final Map<String, Consumer> events;
public TikTokEventHandler()
{
events = new HashMap<>();
}
public void publish(TikTokEvent tikTokEvent)
{
if(events.containsKey(TikTokEvent.class.getSimpleName()))
{
var handler = events.get(TikTokEvent.class.getSimpleName());
handler.accept(tikTokEvent);
}
var name = tikTokEvent.getClass().getSimpleName();
if(!events.containsKey(name))
{
return;
}
var handler = events.get(name);
handler.accept(tikTokEvent);
}
public <T extends TikTokEvent> void subscribe(Class<?> clazz, Consumer<T> event)
{
events.put(clazz.getSimpleName(),event);
}
}

View File

@@ -0,0 +1,12 @@
package io.github.jwdeveloper.tiktok.handlers;
import io.github.jwdeveloper.tiktok.events.TikTokEvent;
import io.github.jwdeveloper.tiktok.messages.WebcastResponse;
public interface TikTokMessageHandler<T>
{
Class<T> getHandleClazz();
TikTokEvent handle(WebcastResponse.Message message) throws Exception;
}

View File

@@ -0,0 +1,149 @@
package io.github.jwdeveloper.tiktok.handlers;
import io.github.jwdeveloper.tiktok.TikTokGiftManager;
import io.github.jwdeveloper.tiktok.events.TikTokEvent;
import io.github.jwdeveloper.tiktok.events.messages.*;
import io.github.jwdeveloper.tiktok.events.objects.TikTokGift;
import io.github.jwdeveloper.tiktok.messages.*;
import io.github.jwdeveloper.tiktok.models.GiftId;
import io.github.jwdeveloper.tiktok.models.SocialTypes;
import lombok.SneakyThrows;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class WebResponseHandler extends WebResponseHandlerBase {
private final TikTokGiftManager giftManager;
public WebResponseHandler(TikTokEventHandler tikTokEventHandler, TikTokGiftManager giftManager) {
super(tikTokEventHandler);
this.giftManager = giftManager;
}
@Override
public void init() {
//ConnectionEvents events
register(WebcastControlMessage.class, TikTokRoomMessageEvent.class);
register(SystemMessage.class, this::handleWebcastControlMessage);
//Room status events
register(WebcastLiveIntroMessage.class, TikTokRoomMessageEvent.class);
register(WebcastRoomUserSeqMessage.class, TikTokRoomViewerDataEvent.class); //TODO update viewer count ViewerCount = userSeqMessage.ViewerCount;
register(RoomMessage.class, TikTokRoomMessageEvent.class);
register(WebcastRoomMessage.class, TikTokRoomMessageEvent.class);
register(WebcastCaptionMessage.class, TikTokCaptionEvent.class);
//User Interactions events
register(WebcastChatMessage.class, TikTokCommentEvent.class);
register(WebcastLikeMessage.class, TikTokLikeEvent.class);
register(WebcastGiftMessage.class, this::handleGift);
register(WebcastSocialMessage.class, this::handleSocialMedia);
register(WebcastMemberMessage.class, this::handleMemberMessage);
//Host Interaction events
register(WebcastPollMessage.class, TikTokPollMessageEvent.class);
register(WebcastRoomPinMessage.class, TikTokRoomPinMessageEvent.class);
register(WebcastGoalUpdateMessage.class, TikTokGoalUpdateEvent.class);
//LinkMic events
register(WebcastLinkMicBattle.class, TikTokLinkMicBattleEvent.class);
register(WebcastLinkMicArmies.class, TikTokLinkMicArmiesEvent.class);
register(LinkMicMethod.class, TikTokLinkMicMethodEvent.class);
register(WebcastLinkMicMethod.class, TikTokLinkMicMethodEvent.class);
register(WebcastLinkMicFanTicketMethod.class, TikTokLinkMicFanTicketEvent.class);
//Rank events
register(WebcastRankTextMessage.class, TikTokRankTextEvent.class);
register(WebcastRankUpdateMessage.class, TikTokRankUpdateEvent.class);
register(WebcastHourlyRankMessage.class, TikTokRankUpdateEvent.class);
//Others events
register(WebcastInRoomBannerMessage.class, TikTokInRoomBannerEvent.class);
register(WebcastMsgDetectMessage.class, TikTokDetectMessageEvent.class);
register(WebcastBarrageMessage.class, TikTokBarrageMessageEvent.class);
register(WebcastUnauthorizedMemberMessage.class, TikTokUnauthorizedMemberEvent.class);
register(WebcastGiftBroadcastMessage.class, TikTokGiftBroadcastEvent.class);
register(WebcastOecLiveShoppingMessage.class, TikTokShopMessageEvent.class);
register(WebcastImDeleteMessage.class, TikTokIMDeleteEvent.class);
register(WebcastQuestionNewMessage.class, TikTokQuestionEvent.class);
register(WebcastEnvelopeMessage.class, TikTokEnvelopeEvent.class);
register(WebcastSubNotifyMessage.class, TikTokSubNotifyEvent.class);
register(WebcastEmoteChatMessage.class, TikTokEmoteEvent.class);
}
@SneakyThrows
private TikTokEvent handleWebcastControlMessage(WebcastResponse.Message msg)
{
var message = WebcastControlMessage.parseFrom(msg.getBinary());
return switch (message.getAction()) {
case STREAM_PAUSED -> new TikTokLivePausedEvent();
case STREAM_ENDED -> new TikTokLiveEndedEvent();
default -> new TikTokUnhandledControlEvent(message);
};
}
@SneakyThrows
private TikTokEvent handleGift(WebcastResponse.Message msg) {
var giftMessage = WebcastGiftMessage.parseFrom(msg.getBinary());
var giftId = new GiftId(giftMessage.getGiftId(), giftMessage.getSender().getUniqueId());
var activeGifts = giftManager.getActiveGifts();
if (activeGifts.containsKey(giftId)) {
// Debug.Log($"Updating Gift[{giftId.Gift}]Amount[{message.Amount}]");
var gift = activeGifts.get(giftId);
gift.setAmount(giftMessage.getAmount());
} else {
TikTokGift newGift = new TikTokGift(giftMessage);
activeGifts.put(giftId, newGift);
// Debug.Log($"New Gift[{giftId.Gift}]Amount[{message.Amount}]");
// RunEvent(OnGift, newGift);
}
if (giftMessage.getRepeatEnd()) {
//if (ShouldLog(LogLevel.Verbose))
// Debug.Log($"GiftStreak Ended: [{giftId.Gift}] Amount[{message.Amount}]")
var gift = activeGifts.get(giftId);
gift.setStreakFinished(true);
activeGifts.remove(gift);
}
// Debug.Log($"Handling GiftMessage");
return new TikTokGiftMessageEvent(giftMessage);
}
@SneakyThrows
private TikTokEvent handleSocialMedia(WebcastResponse.Message msg) {
var message = WebcastSocialMessage.parseFrom(msg.getBinary());
String type = message.getHeader().getSocialData().getType();
Pattern pattern = Pattern.compile("pm_mt_guidance_viewer_([0-9]+)_share");
Matcher matcher = pattern.matcher(type);
if (matcher.find()) {
var value = matcher.group(0);
var number = Integer.parseInt(value);
return new TikTokShareEvent(message, number);
}
var socialType = message.getHeader().getSocialData().getType();
return switch (socialType) {
case SocialTypes.LikeType -> new TikTokLikeEvent(message);
case SocialTypes.FollowType -> new TikTokFollowEvent(message);
case SocialTypes.ShareType -> new TikTokShareEvent(message);
case SocialTypes.JoinType -> new TikTokJoinEvent(message);
default -> new TikTokUnhandledSocialEvent(message);
};
}
@SneakyThrows
private TikTokEvent handleMemberMessage(WebcastResponse.Message msg) {
var message = WebcastMemberMessage.parseFrom(msg.getBinary());
return switch (message.getAction()) {
case JOINED -> new TikTokJoinEvent(message);
case SUBSCRIBED -> new TikTokSubscribeEvent(message);
default -> new TikTokUnhandledMemberEvent(message);
};
}
}

View File

@@ -0,0 +1,89 @@
package io.github.jwdeveloper.tiktok.handlers;
import com.google.protobuf.ByteString;
import io.github.jwdeveloper.tiktok.events.messages.*;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.events.TikTokEvent;
import io.github.jwdeveloper.tiktok.messages.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
public abstract class WebResponseHandlerBase {
private final Map<String, TikTokMessageHandler> handlers;
private final TikTokEventHandler tikTokEventHandler;
public WebResponseHandlerBase(TikTokEventHandler tikTokEventHandler)
{
handlers = new HashMap<>();
this.tikTokEventHandler =tikTokEventHandler;
}
public abstract void init();
public void register(Class<?> input, Class<?> output)
{
register(input,(e)->
{
try
{
var parseMethod = input.getDeclaredMethod("parseFrom", ByteString.class);
var deserialized = parseMethod.invoke(null,e.getBinary());
var constructors = Arrays.stream(output.getConstructors()).filter(ea -> Arrays.stream(ea.getParameterTypes()).toList().contains(input)).findFirst();
var tiktokEvent = constructors.get().newInstance(deserialized);
return (TikTokEvent)tiktokEvent;
}
catch (Exception ex)
{
throw new TikTokLiveException("Unable to handle parsing from class: "+input.getSimpleName()+" to class "+output.getSimpleName(),ex);
}
});
}
public <T> void register(Class clazz, Function<WebcastResponse.Message,TikTokEvent> func)
{
var haandler = new TikTokMessageHandler<T>() {
@Override
public Class<T> getHandleClazz() {
return clazz;
}
@Override
public TikTokEvent handle(WebcastResponse.Message message) throws Exception {
return func.apply(message);
}
};
handlers.put(haandler.getHandleClazz().getSimpleName(), haandler);
}
public void handle(WebcastResponse webcastResponse) {
// System.out.println("==============================================================");
// System.out.println("Getting messages: " + webcastResponse.getMessagesList().size());
for (var message : webcastResponse.getMessagesList()) {
try {
handleSingleMessage(message);
} catch (Exception e) {
throw new TikTokLiveException("Error whilst Handling Message. Stopping Client.{Environment.NewLine}Final Message: {Convert.ToBase64String(message.Binary)}", e);
}
}
// System.out.println("==============================================================");
}
private void handleSingleMessage(WebcastResponse.Message message) throws Exception {
if(!handlers.containsKey(message.getType()))
{
tikTokEventHandler.publish(new TikTokUnhandledEvent(message));
return;
}
var handler = handlers.get(message.getType());
var tiktokEvent = handler.handle(message);
tikTokEventHandler.publish(tiktokEvent);
}
}

View File

@@ -1,11 +1,10 @@
package io.github.jwdeveloper.tiktok.http;
import com.google.gson.Gson;
import com.google.gson.JsonParser;
import io.github.jwdeveloper.generated.WebcastResponse;
import io.github.jwdeveloper.tiktok.TikTokLiveException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.live.LiveRoomInfo;
import io.github.jwdeveloper.tiktok.live.models.gift.TikTokGift;
import io.github.jwdeveloper.tiktok.models.gifts.TikTokGift;
import io.github.jwdeveloper.tiktok.messages.WebcastResponse;
import java.util.HashMap;
import java.util.Map;
@@ -89,7 +88,7 @@ public class TikTokApiService {
try {
var response = apiClient.GetDeserializedMessage("im/fetch/", clientParams);
clientParams.put("cursor",response.getCursor());
clientParams.put("internal_ext", response.getInternalExt());
clientParams.put("internal_ext", response.getAckIds());
return response;
}
catch (Exception e)

View File

@@ -18,10 +18,6 @@ public class TikTokCookieJar {
cookies.put(key, value);
}
public Set<Map.Entry<String, String>> GetEnumerator() {
return cookies.entrySet();
}
public String parseCookies()
{
var sb = new StringBuilder();

View File

@@ -2,17 +2,16 @@ package io.github.jwdeveloper.tiktok.http;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.github.jwdeveloper.generated.WebcastResponse;
import io.github.jwdeveloper.tiktok.ClientSettings;
import io.github.jwdeveloper.tiktok.Constants;
import io.github.jwdeveloper.tiktok.TikTokLiveException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.messages.WebcastResponse;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
@@ -81,7 +80,6 @@ public class TikTokHttpApiClient {
var key = split[0];
var value = split[1];
tikTokCookieJar.set(key, value);
var i =0;
}
return response.body();

View File

@@ -1,101 +1,77 @@
package io.github.jwdeveloper.tiktok.websocket;
import com.google.protobuf.ByteString;
import io.github.jwdeveloper.generated.WebcastMessageEvent;
import io.github.jwdeveloper.generated.WebcastResponse;
import io.github.jwdeveloper.generated.WebcastWebsocketAck;
import io.github.jwdeveloper.generated.WebcastWebsocketMessage;
import io.github.jwdeveloper.tiktok.TikTokLiveException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.messages.WebcastResponse;
import io.github.jwdeveloper.tiktok.messages.WebcastWebsocketAck;
import io.github.jwdeveloper.tiktok.messages.WebcastWebsocketMessage;
import java.io.ByteArrayOutputStream;
import java.net.http.WebSocket;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
public class TikTokWebSocketListener implements java.net.http.WebSocket.Listener {
List<ByteBuffer> parts = new ArrayList<>();
CompletableFuture<?> accumulatedMessage = new CompletableFuture<>();
private ByteArrayOutputStream accumulatedData = new ByteArrayOutputStream();
@Override
public CompletionStage<?> onBinary(WebSocket webSocket, ByteBuffer data, boolean last) {
System.out.println("Received onBinary: " + data + " " + last);
parts.add(data);
webSocket.request(1);
if (last) {
var buffer = combineBuffer(parts);
accumulatedMessage.complete(null);
CompletionStage<?> cf = accumulatedMessage;
accumulatedMessage = new CompletableFuture<>();
handleBinary(webSocket, buffer);
parts = new ArrayList<>();
return cf;
}
return accumulatedMessage;
}
public ByteBuffer combineBuffer(List<ByteBuffer> buffers) {
int totalCapacity = buffers.stream().mapToInt(ByteBuffer::remaining).sum();
ByteBuffer combined = ByteBuffer.allocate(totalCapacity);
for (ByteBuffer buffer : buffers) {
combined.put(buffer);
}
combined.flip();
return combined;
}
private void handleBinary(WebSocket webSocket, ByteBuffer buffer) {
try {
// buffer= Base64.getEncoder().encode(buffer);
ByteString byteString = ByteString.copyFrom(buffer);
WebcastWebsocketMessage websocketMessage = WebcastWebsocketMessage.parseFrom(byteString);
var decoded = Base64.getEncoder().encodeToString(data.array());
System.out.println(decoded);
var bytes = new byte[data.remaining()];
data.get(bytes);
accumulatedData.write(bytes);
if (last) {
if(websocketMessage.getBinary().isEmpty())
{
return;
}
try {
var response = WebcastResponse.parseFrom(websocketMessage.getBinary());
var serverInfo = WebcastWebsocketAck
.newBuilder()
.setType("ack")
.setId(websocketMessage.getId())
.build();
webSocket.sendBinary(serverInfo.toByteString().asReadOnlyByteBuffer(), true);
System.out.println("Works");
// handleResponse(response);
} catch (Exception e) {
throw new TikTokLiveException("Unabel to read WebcastResponse",e);
// System.out.println("Unable to read WebcastResponse");
//handleBinary(webSocket, accumulatedData.toByteArray());
accumulatedData.reset();
accumulatedData = new ByteArrayOutputStream();
}
} catch (Exception e) {
// System.out.println("Unable to read WebcastWebsocketMessage");
e.printStackTrace();
throw new TikTokLiveException("Unabel to read WebcastWebsocketMessage",e);
}
webSocket.request(1);
return null;
}
private void handleBinary(WebSocket webSocket, byte[] buffer) {
try {
var websocketMessage = WebcastWebsocketMessage.parseFrom(buffer);
if (websocketMessage.getBinary().isEmpty()) {
return;
}
sendAckId(webSocket, websocketMessage.getId());
try {
//error here
var response = WebcastResponse.parseFrom(websocketMessage.getBinary());
System.out.println("Works");
// handleResponse(response);
} catch (Exception e) {
throw new TikTokLiveException("Unabel to read WebcastResponse", e);
}
} catch (Exception e) {
throw new TikTokLiveException("Unabel to read WebcastWebsocketMessage", e);
}
}
public void handleResponse(WebcastResponse webcastResponse) {
System.out.println("Handling response: Messages"+webcastResponse.getMessagesList().size());
for(var message : webcastResponse.getMessagesList())
{
}
private void sendAckId(WebSocket webSocket, long id) {
var serverInfo = WebcastWebsocketAck
.newBuilder()
.setType("ack")
.setId(id)
.build();
webSocket.sendBinary(serverInfo.toByteString().asReadOnlyByteBuffer(), true);
}
@Override
public void onOpen(java.net.http.WebSocket webSocket) {
System.out.println("WebSocket opened");

View File

@@ -1,15 +1,17 @@
package io.github.jwdeveloper.tiktok.websocket;
import io.github.jwdeveloper.generated.WebcastResponse;
import io.github.jwdeveloper.tiktok.ClientSettings;
import io.github.jwdeveloper.tiktok.Constants;
import io.github.jwdeveloper.tiktok.TikTokLiveException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.handlers.WebResponseHandlerBase;
import io.github.jwdeveloper.tiktok.http.HttpUtils;
import io.github.jwdeveloper.tiktok.http.TikTokCookieJar;
import io.github.jwdeveloper.tiktok.http.TikTokHttpRequestFactory;
import io.github.jwdeveloper.tiktok.messages.WebcastResponse;
import org.java_websocket.drafts.Draft_6455;
import java.net.URI;
import java.net.http.HttpClient;
import java.nio.ByteBuffer;
import java.time.Duration;
import java.util.HashMap;
@@ -23,47 +25,47 @@ public class TikTokWebsocketClient {
private final ClientSettings clientSettings;
private final TikTokCookieJar tikTokCookieJar;
private final TikTokHttpRequestFactory factory;
private final WebResponseHandlerBase webResponseHandler;
public TikTokWebsocketClient(Logger logger,
TikTokCookieJar tikTokCookieJar,
Map<String, Object> clientParams,
TikTokHttpRequestFactory factory,
ClientSettings clientSettings) {
ClientSettings clientSettings,
WebResponseHandlerBase webResponseHandler) {
this.logger = logger;
this.clientParams = clientParams;
this.tikTokCookieJar = tikTokCookieJar;
this.clientSettings = clientSettings;
this.factory = factory;
this.webResponseHandler = webResponseHandler;
}
public void start(WebcastResponse webcastResponse) {
if (webcastResponse.getWsUrl().isEmpty() || webcastResponse.getWsParam().getAllFields().isEmpty()) {
if (webcastResponse.getSocketUrl().isEmpty() || webcastResponse.getSocketParamsList().isEmpty()) {
throw new TikTokLiveException("Could not find Room");
}
try {
var params = webcastResponse.getWsParam();
var params = webcastResponse.getSocketParamsList().get(0);
var name = params.getName();
var value = params.getValue();
System.out.println("KEY: " + name + " value: " + value);
// System.out.println("KEY: " + name + " value: " + value);
var headers = Constants.DefaultRequestHeaders();
var clone = new TreeMap<>(clientParams);
// clone.putAll(headers);
clone.putAll(headers);
clone.put(name, value);
clone.put("compress", "gzip");
var url = webcastResponse.getWsUrl();
//clone.put("compress", "gzip");
var url = webcastResponse.getSocketUrl();
var wsUrl = HttpUtils.parseParametersEncode(url, clone);
logger.info("Starting Socket-Threads");
//runningTask = Task.Run(WebSocketLoop, token);
//pollingTask = Task.Run(PingLoop, token);
startWS(wsUrl);
startWS2(wsUrl);
} catch (Exception e) {
throw new TikTokLiveException("Failed to connect to the websocket", e);
}
@@ -85,14 +87,13 @@ public class TikTokWebsocketClient {
var map = new HashMap<String, String>();
map.put("Cookie", cookie);
var ws = factory.openSocket()
var ws = factory.openSocket()
.subprotocols("echo-protocol")
.connectTimeout(Duration.ofSeconds(15))
.header("Cookie", cookie)
.buildAsync(URI.create(url), new TikTokWebSocketListener()).get();
while (true) {
byte[] message = new byte[]{58, 2, 104, 98};
ByteBuffer buffer = ByteBuffer.wrap(message);
@@ -108,6 +109,34 @@ public class TikTokWebsocketClient {
}
}
public void startWS2(String url) {
try {
var cookie = tikTokCookieJar.parseCookies();
System.out.println("WssIP: " + url);
System.out.println("Cookie: " + cookie);
var map = new HashMap<String, String>();
map.put("Cookie", cookie);
var client = new WebSocketClientTest(URI.create(url), new Draft_6455(), map, 1500,webResponseHandler);
client.connect();
/*
while (true) {
byte[] message = new byte[]{58, 2, 104, 98};
ByteBuffer buffer = ByteBuffer.wrap(message);
while (buffer.hasRemaining()) {
// client.send(buffer);
client.sendPing();
}
buffer.clear();
Thread.sleep(10);
}*/
} catch (Exception e) {
e.printStackTrace();
}
}
public void stop() {
}

View File

@@ -0,0 +1,120 @@
package io.github.jwdeveloper.tiktok.websocket;
import com.google.protobuf.ByteString;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.handlers.WebResponseHandlerBase;
import io.github.jwdeveloper.tiktok.messages.WebcastResponse;
import io.github.jwdeveloper.tiktok.messages.WebcastWebsocketAck;
import io.github.jwdeveloper.tiktok.messages.WebcastWebsocketMessage;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft;
import org.java_websocket.handshake.ServerHandshake;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.Base64;
import java.util.Map;
import java.util.zip.GZIPInputStream;
public class WebSocketClientTest extends WebSocketClient {
private boolean debbug = false;
private final WebResponseHandlerBase webResponseHandler;
public WebSocketClientTest(URI serverUri,
Draft protocolDraft,
Map<String, String> httpHeaders,
int connectTimeout,
WebResponseHandlerBase webResponseHandler) {
super(serverUri, protocolDraft, httpHeaders, connectTimeout);
this.webResponseHandler = webResponseHandler;
}
@Override
public void onMessage(ByteBuffer data) {
sendPing();
//System.out.println("onMessage Binary");
var bytes = new byte[data.remaining()];
data.get(bytes);
if(debbug)
{
var decoded = Base64.getEncoder().encodeToString(bytes);
// System.out.println(decoded);
}
handleBinary(bytes);
}
private void handleBinary(byte[] buffer) {
try {
var websocketMessage = WebcastWebsocketMessage.parseFrom(buffer);
if (websocketMessage.getBinary().isEmpty()) {
return;
}
try {
var response = WebcastResponse.parseFrom(websocketMessage.getBinary());
sendAckId(websocketMessage.getId());
webResponseHandler.handle(response);
} catch (Exception e) {
throw new TikTokLiveException("Unabel to read WebcastResponse", e);
}
} catch (Exception e) {
throw new TikTokLiveException("Unabel to read WebcastWebsocketMessage", e);
}
}
public byte[] unGunzipFile(ByteString byteString) {
try {
GZIPInputStream gZIPInputStream = new GZIPInputStream(byteString.newInput());
var bytes = gZIPInputStream.readAllBytes();
gZIPInputStream.close();
return bytes;
} catch (Exception ex) {
ex.printStackTrace();
}
return new byte[0];
}
private void sendAckId(long id) {
var serverInfo = WebcastWebsocketAck
.newBuilder()
.setType("ack")
.setId(id)
.build();
send(serverInfo.toByteString().asReadOnlyByteBuffer());
}
@Override
public void onOpen(ServerHandshake serverHandshake) {
System.out.println("onOpen");
sendPing();
}
@Override
public void onMessage(String s) {
sendPing();
}
@Override
public void onClose(int i, String s, boolean b) {
System.out.println("onClose");
}
@Override
public void onError(Exception e) {
System.out.println("error");
e.printStackTrace();
}
}

View File

@@ -1,310 +0,0 @@
syntax = "proto3";
package TikTok;
option java_package = "io.github.jwdeveloper.generated";
option java_multiple_files = true;
// Data structure from im/fetch/ response
message WebcastResponse {
repeated Message messages = 1;
string cursor = 2;
int32 fetchInterval = 3;
int64 serverTimestamp = 4;
string internalExt = 5;
int32 fetchType = 6; // ws (1) or polling (2)
WebsocketParam wsParam = 7;
int32 heartbeatDuration = 8;
bool needAck = 9;
string wsUrl = 10;
}
message Message {
string type = 1;
bytes binary = 2;
}
message WebsocketParam {
string name = 1;
string value = 2;
}
// Message types depending on Message.tyoe
message WebcastControlMessage {
int32 action = 2;
}
// Statistics like viewer count
message WebcastRoomUserSeqMessage {
repeated TopUser topViewers = 2;
int32 viewerCount = 3;
}
message TopUser {
uint64 coinCount = 1;
User user = 2;
}
message WebcastChatMessage {
WebcastMessageEvent event = 1;
User user = 2;
string comment = 3;
}
message WebcastMemberMessage {
WebcastMessageEvent event = 1;
User user = 2;
int32 actionId = 10;
}
message WebcastGiftMessage {
WebcastMessageEvent event = 1;
int32 giftId = 2;
int32 repeatCount = 5;
User user = 7;
int32 repeatEnd = 9;
uint64 groupId = 11;
WebcastGiftMessageGiftDetails giftDetails = 15;
string monitorExtra = 22;
WebcastGiftMessageGiftExtra giftExtra = 23;
}
message WebcastGiftMessageGiftDetails {
WebcastGiftMessageGiftImage giftImage = 1;
string giftName = 16;
string describe = 2;
int32 giftType = 11;
int32 diamondCount = 12;
}
// Taken from https://github.com/Davincible/gotiktoklive/blob/da4630622bc586629a53faae64e8c53509af29de/proto/tiktok.proto#L57
message WebcastGiftMessageGiftExtra {
uint64 timestamp = 6;
uint64 receiverUserId = 8;
}
message WebcastGiftMessageGiftImage {
string giftPictureUrl = 1;
}
// Battle start
message WebcastLinkMicBattle {
repeated WebcastLinkMicBattleItems battleUsers = 10;
}
message WebcastLinkMicBattleItems {
WebcastLinkMicBattleGroup battleGroup = 2;
}
message WebcastLinkMicBattleGroup {
LinkUser user = 1;
}
// Battle status
message WebcastLinkMicArmies {
repeated WebcastLinkMicArmiesItems battleItems = 3;
int32 battleStatus = 7;
}
message WebcastLinkMicArmiesItems {
uint64 hostUserId = 1;
repeated WebcastLinkMicArmiesGroup battleGroups = 2;
}
message WebcastLinkMicArmiesGroup {
repeated User users = 1;
int32 points = 2;
}
// Follow & share event
message WebcastSocialMessage {
WebcastMessageEvent event = 1;
User user = 2;
}
// Like event (is only sent from time to time, not with every like)
message WebcastLikeMessage {
WebcastMessageEvent event = 1;
User user = 5;
int32 likeCount = 2;
int32 totalLikeCount = 3;
}
// New question event
message WebcastQuestionNewMessage {
QuestionDetails questionDetails = 2;
}
message QuestionDetails {
string questionText = 2;
User user = 5;
}
message WebcastMessageEvent {
uint64 msgId = 2;
uint64 createTime = 4;
WebcastMessageEventDetails eventDetails = 8;
}
// Contains UI information
message WebcastMessageEventDetails {
string displayType = 1;
string label = 2;
}
// Source: Co-opted https://github.com/zerodytrash/TikTok-Livestream-Chat-Connector/issues/19#issuecomment-1074150342
message WebcastLiveIntroMessage {
uint64 id = 2;
string description = 4;
User user = 5;
}
message SystemMessage {
string description = 2;
}
message WebcastInRoomBannerMessage {
string data = 2;
}
message RankItem {
string colour = 1;
uint64 id = 4;
}
message WeeklyRanking {
string type = 1;
string label = 2;
RankItem rank = 3;
}
message RankContainer {
WeeklyRanking rankings = 4;
}
message WebcastHourlyRankMessage {
RankContainer data = 2;
}
// Chat Emotes (Subscriber)
message WebcastEmoteChatMessage {
User user = 2;
EmoteDetails emote = 3;
}
message EmoteDetails {
string emoteId = 1;
EmoteImage image = 2;
}
message EmoteImage {
string imageUrl = 1;
}
// Envelope (treasure boxes)
// Taken from https://github.com/ThanoFish/TikTok-Live-Connector/blob/9b215b96792adfddfb638344b152fa9efa581b4c/src/proto/tiktokSchema.proto
message WebcastEnvelopeMessage {
TreasureBoxData treasureBoxData = 2;
TreasureBoxUser treasureBoxUser = 1;
}
message TreasureBoxUser {
TreasureBoxUser2 user2 = 8;
}
message TreasureBoxUser2 {
repeated TreasureBoxUser3 user3 = 4;
}
message TreasureBoxUser3 {
TreasureBoxUser4 user4 = 21;
}
message TreasureBoxUser4 {
User user = 1;
}
message TreasureBoxData {
uint32 coins = 5;
uint32 canOpen = 6;
uint64 timestamp = 7;
}
// New Subscriber message
message WebcastSubNotifyMessage {
WebcastMessageEvent event = 1;
User user = 2;
int32 exhibitionType = 3;
int32 subMonth = 4;
int32 subscribeType = 5;
int32 oldSubscribeStatus = 6;
int32 subscribingStatus = 8;
}
// ==================================
// Generic stuff
message User {
uint64 userId = 1;
string nickname = 3;
ProfilePicture profilePicture = 9;
string uniqueId = 38;
string secUid = 46;
repeated UserBadgesAttributes badges = 64;
uint64 createTime = 16;
string bioDescription = 5;
FollowInfo followInfo = 22;
}
message FollowInfo {
int32 followingCount = 1;
int32 followerCount = 2;
int32 followStatus = 3;
int32 pushStatus = 4;
}
message LinkUser {
uint64 userId = 1;
string nickname = 2;
ProfilePicture profilePicture = 3;
string uniqueId = 4;
}
message ProfilePicture {
repeated string urls = 1;
}
message UserBadgesAttributes {
int32 badgeSceneType = 3;
repeated UserImageBadge imageBadges = 20;
repeated UserBadge badges = 21;
}
message UserBadge {
string type = 2;
string name = 3;
}
message UserImageBadge {
int32 displayType = 1;
UserImageBadgeImage image = 2;
}
message UserImageBadgeImage {
string url = 1;
}
// Websocket incoming message structure
message WebcastWebsocketMessage {
uint64 id = 2;
string type = 7;
bytes binary = 8;
}
// Websocket acknowledgment message
message WebcastWebsocketAck {
uint64 id = 2;
string type = 7;
}

View File

@@ -0,0 +1,29 @@
package io.github.jwdeveloper.tiktok;
import com.google.protobuf.InvalidProtocolBufferException;
import io.github.jwdeveloper.tiktok.messages.WebcastWebsocketMessage;
import org.junit.Test;
import java.io.IOException;
public class SerializeWebMessageTest
{
@Test
public void WebcastWebsocketMessage()
{
try (var str = getClass().getClassLoader().getResourceAsStream("WebcastWebsocketMessage.bin"))
{
var bytes = str.readAllBytes();
var person = WebcastWebsocketMessage.parseFrom(bytes);
System.out.println("id: " + person.getId());
System.out.println("type: " + person.getType());
System.out.println("binary: " + person.getBinary().size());
// System.out.println("Email: " + person.getEmail());
} catch (InvalidProtocolBufferException e) {
System.out.println("Error parsing the protobuf message: " + e.getMessage());
e.printStackTrace();
} catch (IOException e) {
System.out.println("Error reading the file: " + e.getMessage());
}
}
}

View File

@@ -1,23 +1,84 @@
package io.github.jwdeveloper.tiktok;
import org.junit.jupiter.api.Test;
import io.github.jwdeveloper.tiktok.events.messages.*;
import org.junit.Test;
import java.io.IOException;
public class TikTokLiveTest
{
public static String TEST_USER_SUBJECT = "live_interactive_";
public class TikTokLiveTest {
public static String TEST_USER_SUBJECT = "tv_asahi_news";
@Test
public void ShouldConnect() throws IOException {
var client = TikTokLive.newClient(TEST_USER_SUBJECT).build();
client.run();
var client = TikTokLive.newClient(TEST_USER_SUBJECT)
.onConnected(this::onConnected)
.onDisconnected(this::onDisconnected)
.onRoomViewerData(this::onViewerData)
.onJoin(this::onJoin)
.onComment(this::onComment)
.onFollow(this::onFollow)
.onShare(this::onShare)
.onSubscribe(this::onSubscribe)
.onLike(this::onLike)
.onGiftMessage(this::onGiftMessage)
.onEmote(this::onEmote)
.buildAndRun();
System.in.read();
}
private void onConnected(TikTokConnectedEvent e) {
print("Connected");
}
private void onDisconnected(TikTokDisconnectedEvent e) {
print("Disconnected");
}
private void onViewerData(TikTokRoomViewerDataEvent e) {
print("Viewer count is:", e.getViewerCount());
}
private void onJoin(TikTokJoinEvent e) {
print(e.getUser().getUniqueId(), "joined!");
}
private void onComment(TikTokCommentEvent e) {
print(e.getUser().getUniqueId(), e.getText());
}
private void onFollow(TikTokFollowEvent e) {
print(e.getNewFollower().getUniqueId(), "followed!");
}
private void onShare(TikTokShareEvent e) {
print(e.getUser().getUniqueId(), "shared!");
}
private void onSubscribe(TikTokSubscribeEvent e) {
print(e.getNewSubscriber().getUniqueId(), "subscribed!");
}
private void onLike(TikTokLikeEvent e) {
print(e.getSender().getUniqueId(), "liked!");
}
private void onGiftMessage(TikTokGiftMessageEvent e) {
print(e.getSender().getUniqueId(), "sent", e.getAmount(), "x", e.getGift().getName());
}
private void onEmote(TikTokEmoteEvent e) {
print(e.getUser().getUniqueId(), "sent", e.getEmoteId());
}
private static void print(Object... messages) {
var sb = new StringBuilder();
for (var message : messages) {
sb.append(message).append(" ");
}
System.out.println(sb.toString());
}
}

View File

@@ -0,0 +1,32 @@
 ŔĆł»„ÖF¸E *
compress_typenone2pb:msgBš
Ű
WebcastLikeMessageş

WebcastLikeMessageˇ¤ŚöÍííd żÚřęëíd0
pm_mt_msg_viewer{0:user} liked the LIVE
#ffffffff "
Ş}
{ ˆ•ÉÔëdhulajnoga czek˛hulajnoga.czek5ňLMS4wLjABAAAAx4-lIrRjH0pMT0D3laROVsnU-4u6P1tN4td82AQTCHjKZ-CHaF_DluNSFQQp1s4DHP°¸Ŕ ĆĐŇź1†q*Ü ˆ•ÉÔëdhulajnoga czek˛ş ˛hulajnoga.czek5J˙
Čhttps://p16-sign-useast2a.tiktokcdn.com/tos-useast2a-avt-0068-euttp/26aa68e29831ff0c9d4fb37875efffdd~tplv-tiktok-shrink:72:72.webp?x-expires=1692291600&x-signature=Z2ZM2T%2FckIxKSj%2BJY%2BONoypyMwo%3D
´https://p16-sign-useast2a.tiktokcdn.com/tos-useast2a-avt-0068-euttp/26aa68e29831ff0c9d4fb37875efffdd~c5_100x100.webp?x-expires=1692291600&x-signature=gJgrVHIZWnVCif4Z6gXuhnl1pn8%3D
´https://p16-sign-useast2a.tiktokcdn.com/tos-useast2a-avt-0068-euttp/26aa68e29831ff0c9d4fb37875efffdd~c5_100x100.jpeg?x-expires=1692291600&x-signature=ITqTLj6v7UVl12HeeGa6a6FYNpc%3DD100x100/tos-useast2a-avt-0068-euttp/26aa68e29831ff0c9d4fb37875efffddŞ0 ňLMS4wLjABAAAAx4-lIrRjH0pMT0D3laROVsnU-4u6P1tN4td82AQTCHjKZ-CHaF_DluNSFQQp1s4DîR„sslocal://webcast_webview_popup?gravity=bottom&show_mask=1&url=https%3A%2F%2Flf16-web.tiktokcdn.com%2Fobj%2Fies-hotsoon-draft-sg%2Ftiktok-live-faq%2Ftiktok_live_revenue_new_gifter_details.html&web_bg_color=FFFFFF&height=60%25&mask_bg_color=000000b3&use_spark=1b:"10000037267585271263611681*0
27154120369731914523 pm_mt_live_ng_im
New gifter( Ü sslocal://webcast_lynxview_popup?use_spark=1&url=https%3A%2F%2Flf16-gecko-source.tiktokcdn.com%2Fobj%2Fbyte-gurd-source-sg%2Ftiktok%2Ffe%2Flive%2Ftiktok_live_revenue_user_level_main%2Fsrc%2Fpages%2Fprivilege%2Fpanel%2Ftemplate.js&hide_status_bar=0&hide_nav_bar=1&container_bg_color=00000000&height=90%25&bdhm_bid=tiktok_live_revenue_user_level_main&use_forest=1XbN
271383811767874578280".mock_fix_width_transparent_7138381176787457828*Ő+webcast-va/grade_badge_icon_lite_lv1_v1.png:ésslocal://webcast_lynxview_popup?use_spark=1&url=https%3A%2F%2Flf16-gecko-source.tiktokcdn.com%2Fobj%2Fbyte-gurd-source-sg%2Ftiktok%2Ffe%2Flive%2Ftiktok_live_revenue_user_level_main%2Fsrc%2Fpages%2Fprivilege%2Fpanel%2Ftemplate.js&hide_status_bar=0&hide_nav_bar=1&container_bg_color=00000000&height=90%25&bdhm_bid=tiktok_live_revenue_user_level_main&use_forest=1
\https://p16-webcast.tiktokcdn.com/webcast-va/grade_badge_icon_lite_lv1_v1.png~tplv-obj.image
\https://p19-webcast.tiktokcdn.com/webcast-va/grade_badge_icon_lite_lv1_v1.png~tplv-obj.image"2* b
 #99789EE7x2 : " Z
 #99789EE7€÷( R„sslocal://webcast_webview_popup?gravity=bottom&show_mask=1&url=https%3A%2F%2Flf16-web.tiktokcdn.com%2Fobj%2Fies-hotsoon-draft-sg%2Ftiktok-live-faq%2Ftiktok_live_revenue_new_gifter_details.html&web_bg_color=FFFFFF&height=60%25&mask_bg_color=000000b3&use_spark=1Xb:
271541203697319145231"10000037267585271263611681*¦x ˆŰ 0:„sslocal://webcast_webview_popup?gravity=bottom&show_mask=1&url=https%3A%2F%2Flf16-web.tiktokcdn.com%2Fobj%2Fies-hotsoon-draft-sg%2Ftiktok-live-faq%2Ftiktok_live_revenue_new_gifter_details.html&web_bg_color=FFFFFF&height=60%25&mask_bg_color=000000b3&use_spark=1
Shttps://p16-webcast.tiktokcdn.com/webcast-va/new_gifter_badge_v3.png~tplv-obj.image
Shttps://p19-webcast.tiktokcdn.com/webcast-va/new_gifter_badge_v3.png~tplv-obj.image"webcast-va/new_gifter_badge_v3.png
pm_mt_live_ng_im
New gifterZ
 #803F3F3Fb
 #803F3F3FˆË”ʍÁcŔ
pm_mt_msg_viewer{0:user} liked the LIVE
#ffffffff "
Ş}
{˛hulajnoga.czek5ňLMS4wLjABAAAAx4-lIrRjH0pMT0D3laROVsnU-4u6P1tN4td82AQTCHjKZ-CHaF_DluNSFQQp1s4D ˆ•ÉÔëdhulajnoga czekHˇ¤ŚöÍííd)1692120689751_7267603020139989890_1_1_0_0č ×ŘŇź1*-@H