`onWebsocketMessage()` TikTokWebsocketMessageEvent new event that is
   triggered when new ProtocolBuffer message come from TikTok server.
   Should be mainly use for debuging purpose

Bugs:
 - Fixed bug: WebcastSocialMessage was always triggering `TikTokShareEvent` events such as `TikTokLikeEvent`, `TikTokFollowEvent`, `TikTokShareEvent`, `TikTokJoinEvent` was ignored

 - Fixed bug: Websocket was disconnecting when there was no incoming events for the while. Fixed by implementing background loop that pinging TikTok server every few ms.

 - Fixed bug: Disconnect method was not working
This commit is contained in:
JW
2023-09-04 12:05:13 +02:00
parent b059afd621
commit 1aff710523
18 changed files with 279 additions and 38 deletions

View File

@@ -88,7 +88,7 @@ public interface TikTokEventBuilder<T> {
T onEvent(TikTokEventConsumer<TikTokEvent> event);
T onSuccessResponseMapping(TikTokEventConsumer<TikTokSuccessResponseMappingEvent> event);
T onWebsocketMessage(TikTokEventConsumer<TikTokWebsocketMessageEvent> event);
}

View File

@@ -5,9 +5,13 @@ import io.github.jwdeveloper.tiktok.messages.WebcastResponse;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* Happens when TikTok websocket receive message from server
*/
@Data
@AllArgsConstructor
public class TikTokSuccessResponseMappingEvent extends TikTokEvent
public class TikTokWebsocketMessageEvent extends TikTokEvent
{
private TikTokEvent event;

View File

@@ -3,7 +3,6 @@ package io.github.jwdeveloper.tiktok.live;
public interface LiveRoomInfo
{
int getViewersCount();
String getRoomId();
String getUserName();
}

View File

@@ -0,0 +1,38 @@
package io.github.jwdeveloper.tiktok.utils;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
public class CancelationToken
{
private boolean isCanceled =false;
public void cancel()
{
isCanceled =true;
}
public boolean isCancel()
{
return isCanceled;
}
public void throwIfCancel()
{
if(!isCanceled)
{
return;
}
throw new TikTokLiveException("Token requested cancelation");
}
public boolean isNotCancel()
{
return !isCancel();
}
public static CancelationToken create()
{
return new CancelationToken();
}
}

View File

@@ -414,13 +414,16 @@ message User {
Picture profilePicture = 9; // Avatar
Picture picture720 = 10; // 720p
Picture picture1080 = 11; // 1080p
uint32 data2 = 15;
uint64 data3 = 16;
int32 status = 15;
int64 createTime = 16;
int64 modifyTime = 17;
int32 secret = 18;
string shareQrcodeUri = 19;
repeated Picture additionalPictures = 21;
FollowerData followerData = 22;
string userString1 = 23;
// string userString1 = 23;
UserRanking userRank1 = 25;
string userString2 = 32;
// string userString2 = 32;
uint64 data4 = 37;
string uniqueId = 38; // @-ID for user
string data5 = 46;

View File

@@ -12,6 +12,7 @@ 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.utils.CancelationToken;
import io.github.jwdeveloper.tiktok.websocket.TikTokWebSocketClient;
import java.time.Duration;
@@ -319,8 +320,8 @@ public class TikTokLiveClientBuilder implements TikTokEventBuilder<TikTokLiveCli
}
@Override
public TikTokLiveClientBuilder onSuccessResponseMapping(TikTokEventConsumer<TikTokSuccessResponseMappingEvent> event) {
tikTokEventHandler.subscribe(TikTokSuccessResponseMappingEvent.class, event);
public TikTokLiveClientBuilder onWebsocketMessage(TikTokEventConsumer<TikTokWebsocketMessageEvent> event) {
tikTokEventHandler.subscribe(TikTokWebsocketMessageEvent.class, event);
return this;
}
}

View File

@@ -6,7 +6,7 @@ import io.github.jwdeveloper.tiktok.ClientSettings;
import io.github.jwdeveloper.tiktok.TikTokLiveClient;
import io.github.jwdeveloper.tiktok.events.TikTokEvent;
import io.github.jwdeveloper.tiktok.events.messages.TikTokErrorEvent;
import io.github.jwdeveloper.tiktok.events.messages.TikTokSuccessResponseMappingEvent;
import io.github.jwdeveloper.tiktok.events.messages.TikTokWebsocketMessageEvent;
import io.github.jwdeveloper.tiktok.events.messages.TikTokUnhandledEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveMessageException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokMessageMappingException;
@@ -70,7 +70,7 @@ public abstract class TikTokMessageHandler {
}
var handler = handlers.get(message.getType());
var tiktokEvent = handler.handle(message);
tikTokEventHandler.publish(client, new TikTokSuccessResponseMappingEvent(tiktokEvent, message));
tikTokEventHandler.publish(client, new TikTokWebsocketMessageEvent(tiktokEvent, message));
tikTokEventHandler.publish(client, tiktokEvent);
}

View File

@@ -25,10 +25,10 @@ public class TikTokWebSocketClient {
private final TikTokCookieJar tikTokCookieJar;
private final TikTokMessageHandlerRegistration webResponseHandler;
private final TikTokEventHandler tikTokEventHandler;
private WebSocketClient webSocketClient;
private boolean isConnected;
private TikTokLiveClient tikTokLiveClient;
private TikTokWebSocketPingingTask pingingTask;
private boolean isConnected;
public TikTokWebSocketClient(Logger logger,
TikTokCookieJar tikTokCookieJar,
@@ -59,7 +59,13 @@ public class TikTokWebSocketClient {
}
webSocketClient = startWebSocket(url);
webSocketClient.connect();
} catch (Exception e) {
pingingTask = new TikTokWebSocketPingingTask();
pingingTask.run(webSocketClient);
isConnected = true;
} catch (Exception e)
{
isConnected =false;
throw new TikTokLiveException("Failed to connect to the websocket", e);
}
}
@@ -83,15 +89,23 @@ public class TikTokWebSocketClient {
var cookie = tikTokCookieJar.parseCookies();
var map = new HashMap<String, String>();
map.put("Cookie", cookie);
return new TikTokWebSocketListener(URI.create(url), map, 3000, webResponseHandler, tikTokEventHandler, tikTokLiveClient);
return new TikTokWebSocketListener(URI.create(url),
map,
3000,
webResponseHandler,
tikTokEventHandler,
tikTokLiveClient);
}
public void stop() {
public void stop()
{
if (isConnected && webSocketClient != null) {
webSocketClient.close();
webSocketClient.close(1);
}
webSocketClient = null;
pingingTask = null;
isConnected = false;
}
}

View File

@@ -101,17 +101,7 @@ public class TikTokWebSocketListener extends WebSocketClient {
}
}
private void pingTask(WebSocket webSocket) throws InterruptedException {
while (true) {
byte[] message = new byte[]{58, 2, 104, 98};
ByteBuffer buffer = ByteBuffer.wrap(message);
while (buffer.hasRemaining()) {
webSocket.sendPing(buffer);
}
buffer.clear();
Thread.sleep(10);
}
}
private void sendAckId(long id) {
var serverInfo = WebcastWebsocketAck

View File

@@ -0,0 +1,58 @@
package io.github.jwdeveloper.tiktok.websocket;
import org.java_websocket.WebSocket;
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;
public void run(WebSocket webSocket)
{
var thread = new Thread(() ->
{
pingTask(webSocket);
});
isRunning =true;
thread.start();
}
public void stop()
{
if(thread != null)
{
thread.interrupt();
}
isRunning = false;
}
private void pingTask(WebSocket webSocket)
{
var random = new Random();
while (isRunning)
{
try
{
if(!webSocket.isOpen())
{
Thread.sleep(100);
continue;
}
webSocket.sendPing();
var timeout = random.nextInt(MAX_TIMEOUT)+MIN_TIMEOUT;
Thread.sleep(timeout);
}
catch (Exception e)
{
isRunning = false;
}
}
}
}

View File

@@ -40,4 +40,10 @@ public class ParseMessagesTests extends TikTokBaseTest
}
@Test
public void ShouldParseMessageWebcastMemberMessage() throws InvalidProtocolBufferException {
var bytes = getFileBytesUtf("WebcastMemberMessage.bin");
var message = WebcastMemberMessage.parseFrom(bytes);
}
}

File diff suppressed because one or more lines are too long

View File

@@ -32,9 +32,7 @@ Do you prefer other programming languages?
<url>https://jitpack.io</url>
</repository>
</repositories>
```
```xml
<dependencies>
<dependency>
<groupId>com.github.jwdeveloper.TikTok-Live-Java</groupId>

View File

@@ -2,11 +2,48 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!-- READ ME
This POM file is configured to run only inside TikTokLiveJava project
In case you have copied it follows this stets to fix console errors
1. Remember to remove
<parent>
<artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<version>0.0.13-Release</version>
</parent>
2. Paste
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.github.jwdeveloper.TikTok-Live-Java</groupId>
<artifactId>Client</artifactId>
<version>0.0.14-Release</version> <- Change with latest version of TikTokJava
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
</dependencies>
I hope it will help you :)
-->
<parent>
<artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<version>0.0.14-Release</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>TestApplication</artifactId>

View File

@@ -4,13 +4,19 @@ import io.github.jwdeveloper.tiktok.events.messages.*;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import java.io.IOException;
import java.time.Duration;
public class Main {
public static String TEST_TIKTOK_USER = "polonezgarage";
public static String TEST_TIKTOK_USER = "olchik.m1";
public static void main(String[] args) throws IOException {
var client = TikTokLive.newClient(TEST_TIKTOK_USER)
.configure(clientSettings ->
{
clientSettings.setRetryConnectionTimeout(Duration.ofSeconds(5));
clientSettings.setRetryOnConnectionFailure(true);
})
.onConnected(Main::onConnected)
.onDisconnected(Main::onDisconnected)
.onRoomViewerData(Main::onViewerData)
@@ -26,9 +32,13 @@ public class Main {
{
error.getException().printStackTrace();
})
.onEvent((liveClient, event) ->
{
var viewers = liveClient.getRoomInfo().getViewersCount();
})
.buildAndRun();
var viewers = client.getRoomInfo().getViewersCount();
System.in.read();
}

View File

@@ -8,18 +8,52 @@ import io.github.jwdeveloper.tiktok.tools.collector.tables.TikTokErrorModel;
import io.github.jwdeveloper.tiktok.tools.collector.tables.TikTokMessageModel;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
public class Main {
//https://protobuf-decoder.netlify.app/
/*
mia_tattoo
moniczkka
besin1276
*/
public static List<String> ignoredEvents;
public static void main(String[] args) throws SQLException {
var tiktokUser = "mr_cios";
ignoredEvents = List.of("TikTokJoinEvent","TikTokLikeEvent");
var db = new TikTokDatabase("test");
db.init();
var users = new ArrayList<String>();
users.add("mia_tattoo");
users.add("moniczkka");
users.add("besin1276");
for(var user : users)
{
runTikTokLiveInstance(user, db);
}
}
private static void runTikTokLiveInstance(String tiktokUser, TikTokDatabase tikTokDatabase)
{
TikTokLive.newClient(tiktokUser)
.onSuccessResponseMapping((liveClient, event) ->
.onWebsocketMessage((liveClient, event) ->
{
var eventName = event.getEvent().getClass().getSimpleName();
if(ignoredEvents.contains(eventName))
{
return;
}
var binary = Base64.getEncoder().encodeToString(event.getMessage().getBinary().toByteArray());
var model = TikTokMessageModel.builder()
.type("message")
@@ -28,8 +62,8 @@ public class Main {
.eventContent(binary)
.build();
db.insertMessage(model);
System.out.println("EVENT: " + eventName);
tikTokDatabase.insertMessage(model);
System.out.println("EVENT: ["+tiktokUser+"] " + eventName);
})
.onError((liveClient, event) ->
{
@@ -53,7 +87,7 @@ public class Main {
}
var error = builder.build();
db.insertError(error);
tikTokDatabase.insertError(error);
System.out.println("ERROR: "+error.getErrorName());
exception.printStackTrace();

View File

@@ -33,6 +33,11 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.13.1</version> <!-- Check for the latest version -->
</dependency>
<dependency>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>Client</artifactId>

View File

@@ -0,0 +1,43 @@
package io.github.jwdeveloper.tiktok.protocol;
import org.jsoup.Jsoup;
import java.io.File;
import java.io.IOException;
public class ProtocolGenerator
{
public static void main(String[] args) {
// Path to the HTML file
File htmlFile = new File("C:\\Users\\ja\\IdeaProjects\\TikTokLiveJava\\Tools\\src\\main\\resources\\page.html");
try {
// Parse the HTML file with Jsoup
var doc = Jsoup.parse(htmlFile, "UTF-8");
// Find all script tags
var scriptTags = doc.select("script");
// Display all script tags
int counter = 1;
for (var scriptTag : scriptTags) {
String srcValue = scriptTag.attr("src");
if(!srcValue.contains("tiktok/webapp/main/webapp-live/"))
{
continue;
}
// Only print those script tags which have a 'src' attribute
if (!srcValue.isEmpty()) {
System.out.println("Script Tag " + counter + " src attribute: " + srcValue);
}
counter++;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}