Compare commits

..

4 Commits

Author SHA1 Message Date
JW
46d229869e - refactor of the Http client
Changes:

Http-client settings in configure method

```
    TikTokLive.newClient("X")
                .configure(liveClientSettings ->
                {
                   var httpSetting = liveClientSettings.getHttpSettings();
                    httpSetting.setTimeout(Duration.ofSeconds(12));
                });
```

`TikTokLive.requests()` Easy and quick way of making
http request to tiktok
```
    var giftsResponse =TikTokLive.request.fetchGiftsData();
 ```

 Removed:
     TikTokLive.isLiveOnline(String hostName);
     TikTokLive.isHostNameValidAsync(String hostName);

     instead you can use
     ```
     TikTokLive.requests().fetchLiveUserData("Mike").getUserStatus()
     ```
2024-01-05 17:12:37 +01:00
JW
dd417df0ff - refactor of the Http client
Changes:

Http-client settings in configure method

```
    TikTokLive.newClient("X")
                .configure(liveClientSettings ->
                {
                   var httpSetting = liveClientSettings.getHttpSettings();
                    httpSetting.setTimeout(Duration.ofSeconds(12));
                });
```

`TikTokLive.requests()` Easy and quick way of making
http request to tiktok
```
    var giftsResponse =TikTokLive.request.fetchGiftsData();
 ```

 Removed:
     TikTokLive.isLiveOnline(String hostName);
     TikTokLive.isHostNameValidAsync(String hostName);

     instead you can use
     ```
     TikTokLive.requests().fetchLiveUserData("Mike").getUserStatus()
     ```
2024-01-05 17:09:02 +01:00
JW
bc24436269 Merge branch 'master' into develop-1.0.15 2024-01-05 17:07:20 +01:00
JW
2d260dd3f9 - refactor of the Http client
Changes:

Http-client settings in configure method

```
    TikTokLive.newClient("X")
                .configure(liveClientSettings ->
                {
                   var httpSetting = liveClientSettings.getHttpSettings();
                    httpSetting.setTimeout(Duration.ofSeconds(12));
                });
```

`TikTokLive.requests()` Easy and quick way of making
http request to tiktok
```
    var giftsResponse =TikTokLive.request.fetchGiftsData();
 ```

 Removed:
     TikTokLive.isLiveOnline(String hostName);
     TikTokLive.isHostNameValidAsync(String hostName);

     instead you can use
     ```
     TikTokLive.requests().fetchLiveUserData("Mike").getUserStatus()
     ```
2024-01-05 17:04:32 +01:00
57 changed files with 623 additions and 2628 deletions

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<version>1.1.0-Release</version>
<version>1.0.14-Release</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>API</artifactId>

View File

@@ -1,66 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.data.dto;
import lombok.*;
import java.net.*;
@Data
@AllArgsConstructor
public class ProxyData
{
private final String address;
private final int port;
public static ProxyData map(String string) {
if (string == null || string.isBlank())
throw new IllegalArgumentException("Provided address cannot be null or empty!");
int portIndex = string.lastIndexOf(':');
try {
String address = string.substring(0, portIndex);
int port = Integer.parseInt(string.substring(portIndex+1));
// Port validation
if (port < 0 || port > 65535)
throw new IndexOutOfBoundsException("Port out of range: "+port);
// IP Validation
InetAddress res = InetAddress.getByName(address);
return new ProxyData(address, port);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Port must be a valid integer!");
} catch (UnknownHostException e) {
throw new IllegalArgumentException("Address must be valid IPv4, IPv6, or domain name!");
}
}
public ProxyData clone() {
return new ProxyData(address, port);
}
public InetSocketAddress toSocketAddress() {
return new InetSocketAddress(address, port);
}
}

View File

@@ -24,7 +24,7 @@ package io.github.jwdeveloper.tiktok.data.events;
import io.github.jwdeveloper.tiktok.annotations.EventMeta;
import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokLiveClientEvent;
import lombok.Getter;
/**
* Triggered when the connection gets disconnected. In that case you can call connect() again to have a reconnect logic.
@@ -32,12 +32,4 @@ import lombok.Getter;
*/
@EventMeta(eventType = EventType.Control)
public class TikTokDisconnectedEvent extends TikTokLiveClientEvent {
@Getter private final String reason;
public TikTokDisconnectedEvent(String reason) {
this.reason = reason.isBlank() ? "None" : reason;
}
public TikTokDisconnectedEvent() {
this("None");
}
}
}

View File

@@ -1,37 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.data.events.control;
import io.github.jwdeveloper.tiktok.annotations.EventMeta;
import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokLiveClientEvent;
/**
* Triggered when client is connecting to live is successfully established.
*/
@EventMeta(eventType = EventType.Control)
public class TikTokConnectingEvent extends TikTokLiveClientEvent
{
}

View File

@@ -1,38 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.data.events.http;
import io.github.jwdeveloper.tiktok.annotations.EventMeta;
import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.requests.LiveData;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
@EventMeta(eventType = EventType.Debug)
public class TikTokRoomDataResponseEvent extends TikTokEvent
{
private final LiveData.Response liveData;
}

View File

@@ -22,7 +22,10 @@
*/
package io.github.jwdeveloper.tiktok.data.requests;
import lombok.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
public class LiveUserData {
@@ -35,18 +38,15 @@ public class LiveUserData {
@Getter
@AllArgsConstructor
public static class Response {
private String json;
private UserStatus userStatus;
private String roomId;
private long startedAtTimeStamp;
public boolean isLiveOnline() {
return userStatus == LiveUserData.UserStatus.Live || userStatus == LiveUserData.UserStatus.LivePaused;
}
public boolean isHostNameValid() {
return userStatus != LiveUserData.UserStatus.NotFound;
}
}
public enum UserStatus {
@@ -55,4 +55,6 @@ public class LiveUserData {
LivePaused,
Live,
}
}
}

View File

@@ -46,7 +46,6 @@ public class HttpClientSettings {
final Map<String, String> cookies;
@Getter
@Setter
ProxyClientSettings proxyClientSettings;
@Getter
@@ -75,7 +74,7 @@ public class HttpClientSettings {
* @param consumer Use to configure proxy settings for http client
*/
public void configureProxy(Consumer<ProxyClientSettings> consumer) {
proxyClientSettings.setEnabled(true);
proxyClientSettings.setUseProxy(true);
consumer.accept(proxyClientSettings);
}
@@ -105,8 +104,8 @@ public class HttpClientSettings {
newSettings.getHeaders().putAll(new TreeMap<>(this.headers));
newSettings.getCookies().putAll(new TreeMap<>(this.cookies));
newSettings.getParams().putAll(new TreeMap<>(this.params));
newSettings.proxyClientSettings = this.proxyClientSettings;
newSettings.proxyClientSettings = this.proxyClientSettings.clone();
return newSettings;
}
}
}

View File

@@ -36,6 +36,7 @@ public class LiveClientSettings {
/**
* ISO-Language for Client
*/
private String clientLanguage;
/**
@@ -43,6 +44,7 @@ public class LiveClientSettings {
*/
private boolean retryOnConnectionFailure;
/**
* Before retrying connect, wait for select amount of time
*/
@@ -51,35 +53,42 @@ public class LiveClientSettings {
/**
* Whether to print Logs to Console
*/
private boolean printToConsole = true;
private boolean printToConsole = true;
/**
* LoggingLevel for Logs
*/
private Level logLevel;
/**
* Optional: Use it if you need to change TikTok live hostname in builder
*/
private String hostName;
/**
* Parameters used in requests to TikTok api
*/
private HttpClientSettings httpSettings;
/**
/*
* Optional: Sometimes not every messages 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
*/
private String sessionId;
/**
/*
* Optional: By default roomID is fetched before connect to live, but you can set it manually
*
*/
private String roomId;
public static LiveClientSettings createDefault()
{
var httpSettings = new HttpClientSettings();
@@ -94,10 +103,12 @@ public class LiveClientSettings {
clientSettings.setPrintToConsole(false);
clientSettings.setLogLevel(Level.ALL);
clientSettings.setHttpSettings(httpSettings);
return clientSettings;
}
/**
* Default Parameters for HTTP-Request
*/
@@ -136,9 +147,11 @@ public class LiveClientSettings {
clientParams.put("webcast_sdk_version", "1.3.0");
clientParams.put("update_version_code", "1.3.0");
return clientParams;
}
/**
* Default Headers for HTTP-Request
*/
@@ -154,4 +167,6 @@ public class LiveClientSettings {
headers.put("Accept-Language", "en-US,en; q=0.9");
return headers;
}
}
}

View File

@@ -22,102 +22,19 @@
*/
package io.github.jwdeveloper.tiktok.data.settings;
import io.github.jwdeveloper.tiktok.data.dto.ProxyData;
import lombok.*;
import java.net.*;
import java.util.*;
import java.util.function.Consumer;
import lombok.Getter;
import lombok.Setter;
//TODO proxy implementation
@Getter
@Setter
public class ProxyClientSettings implements Iterator<ProxyData>
public class ProxyClientSettings
{
private boolean enabled, lastSuccess;
private Rotation rotation = Rotation.CONSECUTIVE;
private final List<ProxyData> proxyList = new ArrayList<>();
private int index = 0;
private boolean autoDiscard = true;
private Proxy.Type type = Proxy.Type.DIRECT;
private Consumer<ProxyData> onProxyUpdated = x -> {};
@Setter
private boolean useProxy;
public boolean addProxy(String addressPort) {
return proxyList.add(ProxyData.map(addressPort));
}
public boolean addProxy(String address, int port) {
return addProxy(new InetSocketAddress(address, port));
}
public boolean addProxy(InetSocketAddress inetAddress) {
return proxyList.add(new ProxyData(inetAddress.getHostString(), inetAddress.getPort()));
}
public void addProxies(List<String> list) {
list.forEach(this::addProxy);
}
@Override
public boolean hasNext() {
return !proxyList.isEmpty();
}
@Override
public ProxyData next() {
return lastSuccess ? proxyList.get(index) : rotate();
}
public ProxyData rotate() {
var nextProxy = switch (rotation)
{
case CONSECUTIVE -> {
index = (index+1) % proxyList.size();
yield proxyList.get(index).clone();
}
case RANDOM -> {
index = new Random().nextInt(proxyList.size());
yield proxyList.get(index).clone();
}
case NONE -> proxyList.get(index).clone();
};
onProxyUpdated.accept(nextProxy);
return nextProxy;
}
@Override
public void remove() {
proxyList.remove(index);
lastSuccess = false; // index is no longer valid and lastSuccess needs falsified
}
public void setIndex(int index) {
if (index == 0 && proxyList.isEmpty())
this.index = 0;
else {
if (index < 0 || index >= proxyList.size())
throw new IndexOutOfBoundsException("Index " + index + " exceeds list of size: " + proxyList.size());
this.index = index;
}
}
public ProxyClientSettings clone()
{
ProxyClientSettings settings = new ProxyClientSettings();
settings.setEnabled(enabled);
settings.setRotation(rotation);
settings.setIndex(index);
settings.setType(type);
settings.setOnProxyUpdated(onProxyUpdated);
proxyList.forEach(proxyData -> settings.addProxy(proxyData.getAddress(), proxyData.getPort()));
return settings;
return new ProxyClientSettings();
}
public enum Rotation
{
/** Rotate addresses consecutively, from proxy 0 -> 1 -> 2 -> ...etc. */
CONSECUTIVE,
/** Rotate addresses randomly, from proxy 0 -> 69 -> 420 -> 1 -> ...etc. */
RANDOM,
/** Don't rotate addresses at all, pin to the indexed address. */
NONE
}
}
}

View File

@@ -1,49 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.exceptions;
/*
* Happens while bad response from http proxy request to TikTok
*/
public class TikTokProxyRequestException extends TikTokLiveException
{
public TikTokProxyRequestException() {
}
public TikTokProxyRequestException(String message) {
super(message);
}
public TikTokProxyRequestException(String message, Throwable cause) {
super(message, cause);
}
public TikTokProxyRequestException(Throwable cause) {
super(cause);
}
public TikTokProxyRequestException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<version>1.1.0-Release</version>
<version>1.0.14-Release</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -39,7 +39,7 @@
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.5.5</version>
<version>1.5.4</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>

View File

@@ -26,8 +26,6 @@ package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.http.LiveHttpClient;
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
import java.util.concurrent.CompletableFuture;
public class TikTokLive {
/**
@@ -39,49 +37,6 @@ public class TikTokLive {
return new TikTokLiveClientBuilder(hostName);
}
/**
*
* @param hostName profile name of Tiktok user could be found in profile link
* example: https://www.tiktok.com/@dostawcavideo hostName would be dostawcavideo
* @return true if live is Online, false if is offline
*/
public static boolean isLiveOnline(String hostName)
{
return requests().fetchLiveUserData(hostName).isLiveOnline();
}
/**
*
* @param hostName profile name of Tiktok user could be found in profile link
* example: https://www.tiktok.com/@dostawcavideo hostName would be dostawcavideo
* @return true if live is Online, false if is offline
*/
public static CompletableFuture<Boolean> isLiveOnlineAsync(String hostName)
{
return CompletableFuture.supplyAsync(()-> isLiveOnline(hostName));
}
/**
*
* @param hostName profile name of Tiktok user could be found in profile link
* example: https://www.tiktok.com/@dostawcavideo hostName would be dostawcavideo
* @return true is hostName name is valid and exists, false if not
*/
public static boolean isHostNameValid(String hostName)
{
return requests().fetchLiveUserData(hostName).isHostNameValid();
}
/**
*
* @param hostName profile name of Tiktok user could be found in profile link
* example: https://www.tiktok.com/@dostawcavideo hostName would be dostawcavideo
* @return true is hostName name is valid and exists, false if not
*/
public static CompletableFuture<Boolean> isHostNameValidAsync(String hostName)
{
return CompletableFuture.supplyAsync(()-> isHostNameValid(hostName));
}
/**
* Use to get some data from TikTok about users are lives
@@ -89,6 +44,9 @@ public class TikTokLive {
* @return LiveHttpClient
*/
public static LiveHttpClient requests() {
return new TikTokLiveHttpClient();
}
}
}

View File

@@ -26,8 +26,6 @@ import io.github.jwdeveloper.tiktok.data.events.TikTokDisconnectedEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokReconnectingEvent;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.control.TikTokConnectingEvent;
import io.github.jwdeveloper.tiktok.data.events.http.TikTokRoomDataResponseEvent;
import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomInfoEvent;
import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData;
import io.github.jwdeveloper.tiktok.data.requests.LiveData;
@@ -78,15 +76,19 @@ public class TikTokLiveClient implements LiveClient {
public void connectAsync(Consumer<LiveClient> onConnection) {
CompletableFuture.runAsync(() -> {
CompletableFuture.supplyAsync(() ->
{
connect();
onConnection.accept(this);
return this;
});
}
public CompletableFuture<LiveClient> connectAsync() {
return CompletableFuture.supplyAsync(() -> {
return CompletableFuture.supplyAsync(() ->
{
connect();
return this;
});
@@ -103,7 +105,8 @@ public class TikTokLiveClient implements LiveClient {
if (e instanceof TikTokLiveOfflineHostException && clientSettings.isRetryOnConnectionFailure()) {
try {
Thread.sleep(clientSettings.getRetryConnectionTimeout().toMillis());
} catch (Exception ignored) {}
} catch (Exception ignored) {
}
logger.info("Reconnecting");
tikTokEventHandler.publish(this, new TikTokReconnectingEvent());
this.connect();
@@ -117,12 +120,14 @@ public class TikTokLiveClient implements LiveClient {
}
public void tryConnect() {
if (!liveRoomInfo.hasConnectionState(ConnectionState.DISCONNECTED)) {
if (!liveRoomInfo.hasConnectionState(ConnectionState.DISCONNECTED))
{
throw new TikTokLiveException("Already connected");
}
setState(ConnectionState.CONNECTING);
tikTokEventHandler.publish(this,new TikTokConnectingEvent());
var userDataRequest = new LiveUserData.Request(liveRoomInfo.getHostName());
var userData = httpClient.fetchLiveUserData(userDataRequest);
liveRoomInfo.setStartTime(userData.getStartedAtTimeStamp());
@@ -134,9 +139,9 @@ public class TikTokLiveClient implements LiveClient {
throw new TikTokLiveOfflineHostException("User not found: "+liveRoomInfo.getHostUser());
}
var liveDataRequest = new LiveData.Request(userData.getRoomId());
var liveData = httpClient.fetchLiveData(liveDataRequest);
tikTokEventHandler.publish(this, new TikTokRoomDataResponseEvent(liveData));
if (liveData.getLiveStatus() == LiveData.LiveStatus.HostNotFound) {
throw new TikTokLiveOfflineHostException("LiveStream for Host name could not be found.");
}
@@ -150,6 +155,7 @@ public class TikTokLiveClient implements LiveClient {
liveRoomInfo.setAgeRestricted(liveData.isAgeRestricted());
liveRoomInfo.setHost(liveData.getHost());
var liveConnectionRequest =new LiveConnectionData.Request(userData.getRoomId());
var liveConnectionData = httpClient.fetchLiveConnectionData(liveConnectionRequest);
webSocketClient.start(liveConnectionData, this);
@@ -162,8 +168,8 @@ public class TikTokLiveClient implements LiveClient {
if (liveRoomInfo.hasConnectionState(ConnectionState.DISCONNECTED)) {
return;
}
setState(ConnectionState.DISCONNECTED);
webSocketClient.stop();
setState(ConnectionState.DISCONNECTED);
}
private void setState(ConnectionState connectionState) {
@@ -175,6 +181,7 @@ public class TikTokLiveClient implements LiveClient {
tikTokEventHandler.publish(this, event);
}
public LiveRoomInfo getRoomInfo() {
return liveRoomInfo;
}
@@ -193,4 +200,6 @@ public class TikTokLiveClient implements LiveClient {
public GiftManager getGiftManager() {
return tikTokGiftManager;
}
}

View File

@@ -156,7 +156,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
var httpClientFactory = new HttpClientFactory(clientSettings);
var tikTokLiveHttpClient = new TikTokLiveHttpClient(httpClientFactory, clientSettings);
var tikTokLiveHttpClient = new TikTokLiveHttpClient(httpClientFactory);
var webSocketClient = new TikTokWebSocketClient(
clientSettings,
@@ -346,7 +346,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
@Override
public TikTokLiveClientBuilder onRoomInfo(EventConsumer<TikTokRoomInfoEvent> event) {
public LiveClientBuilder onRoomInfo(EventConsumer<TikTokRoomInfoEvent> event) {
tikTokEventHandler.subscribe(TikTokRoomInfoEvent.class, event);
return this;
}
@@ -358,7 +358,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
}
@Override
public TikTokLiveClientBuilder onLiveUnpaused(EventConsumer<TikTokLiveUnpausedEvent> event) {
public LiveClientBuilder onLiveUnpaused(EventConsumer<TikTokLiveUnpausedEvent> event) {
tikTokEventHandler.subscribe(TikTokLiveUnpausedEvent.class, event);
return this;
}

View File

@@ -24,14 +24,18 @@ package io.github.jwdeveloper.tiktok;
import com.google.protobuf.InvalidProtocolBufferException;
import io.github.jwdeveloper.tiktok.data.requests.*;
import io.github.jwdeveloper.tiktok.data.settings.*;
import io.github.jwdeveloper.tiktok.exceptions.*;
import io.github.jwdeveloper.tiktok.http.*;
import io.github.jwdeveloper.tiktok.http.mappers.*;
import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokSignServerException;
import io.github.jwdeveloper.tiktok.http.HttpClientFactory;
import io.github.jwdeveloper.tiktok.http.LiveHttpClient;
import io.github.jwdeveloper.tiktok.http.mappers.GiftsDataMapper;
import io.github.jwdeveloper.tiktok.http.mappers.LiveDataMapper;
import io.github.jwdeveloper.tiktok.http.mappers.LiveUserDataMapper;
import io.github.jwdeveloper.tiktok.http.mappers.SignServerResponseMapper;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import java.net.http.HttpResponse;
import java.util.Optional;
public class TikTokLiveHttpClient implements LiveHttpClient {
@@ -44,23 +48,21 @@ public class TikTokLiveHttpClient implements LiveHttpClient {
private static final String TIKTOK_URL_WEBCAST = "https://webcast.tiktok.com/webcast/";
private final HttpClientFactory httpFactory;
private final LiveClientSettings clientSettings;
private final LiveUserDataMapper liveUserDataMapper;
private final LiveDataMapper liveDataMapper;
private final SignServerResponseMapper signServerResponseMapper;
private final SignServerResponseMapper singServerResponseMapper;
private final GiftsDataMapper giftsDataMapper;
public TikTokLiveHttpClient(HttpClientFactory factory, LiveClientSettings settings) {
public TikTokLiveHttpClient(HttpClientFactory factory) {
this.httpFactory = factory;
clientSettings = settings;
liveUserDataMapper = new LiveUserDataMapper();
liveDataMapper = new LiveDataMapper();
signServerResponseMapper = new SignServerResponseMapper();
singServerResponseMapper = new SignServerResponseMapper();
giftsDataMapper = new GiftsDataMapper();
}
public TikTokLiveHttpClient() {
this(new HttpClientFactory(LiveClientSettings.createDefault()), LiveClientSettings.createDefault());
this(new HttpClientFactory(LiveClientSettings.createDefault()));
}
@@ -85,26 +87,7 @@ public class TikTokLiveHttpClient implements LiveHttpClient {
@Override
public LiveUserData.Response fetchLiveUserData(LiveUserData.Request request) {
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) {
try {
var url = TIKTOK_URL_WEB + "api-live/user/room";
var optional = httpFactory.client(url)
.withParam("uniqueId", request.getUserName())
.withParam("sourceType", "54")
.build()
.toJsonResponse();
if (optional.isEmpty()) {
throw new TikTokLiveRequestException("Unable to get information's about user");
}
var json = optional.get();
return liveUserDataMapper.map(json);
} catch (TikTokProxyRequestException ignored) {}
}
}
var url = TIKTOK_URL_WEB + "api-live/user/room";
var optional = httpFactory.client(url)
.withParam("uniqueId", request.getUserName())
@@ -127,25 +110,7 @@ public class TikTokLiveHttpClient implements LiveHttpClient {
@Override
public LiveData.Response fetchLiveData(LiveData.Request request) {
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) {
try {
var url = TIKTOK_URL_WEBCAST + "room/info";
var optional = httpFactory.client(url)
.withParam("room_id", request.getRoomId())
.build()
.toJsonResponse();
if (optional.isEmpty()) {
throw new TikTokLiveRequestException("Unable to get info about live room");
}
var json = optional.get();
return liveDataMapper.map(json);
} catch (TikTokProxyRequestException ignored) {}
}
}
var url = TIKTOK_URL_WEBCAST + "room/info";
var optional = httpFactory.client(url)
.withParam("room_id", request.getRoomId())
@@ -167,15 +132,14 @@ public class TikTokLiveHttpClient implements LiveHttpClient {
@Override
public LiveConnectionData.Response fetchLiveConnectionData(LiveConnectionData.Request request) {
HttpResponse<byte[]> credentialsResponse = getOptionalProxyResponse(request).orElseGet(()-> {
SignServerResponse signServerResponse = getSignedUrl(request.getRoomId());
return getWebsocketCredentialsResponse(signServerResponse.getSignedUrl());
});
var signServerResponse = getSignedUrl(request.getRoomId());
var credentialsResponse = getWebsocketCredentialsResponse(signServerResponse.getSignedUrl());
try {
var optionalHeader = credentialsResponse.headers().firstValue("set-cookie");
if (optionalHeader.isEmpty()) {
throw new TikTokSignServerException("Sign server did not return the set-cookie header");
throw new TikTokSignServerException("Sign server does not returned set-cookie header");
}
var websocketCookie = optionalHeader.get();
var webcastResponse = WebcastResponse.parseFrom(credentialsResponse.body());
@@ -216,32 +180,18 @@ public class TikTokLiveHttpClient implements LiveHttpClient {
}
var json = optional.get();
return signServerResponseMapper.map(json);
return singServerResponseMapper.map(json);
}
HttpResponse<byte[]> getWebsocketCredentialsResponse(String signedUrl) {
var optionalResponse = httpFactory
.clientEmpty(signedUrl)
.build()
.toResponse();
.toResponse(HttpResponse.BodyHandlers.ofByteArray());
if (optionalResponse.isEmpty()) {
throw new TikTokSignServerException("Unable to get websocket connection credentials");
}
return optionalResponse.get();
}
Optional<HttpResponse<byte[]>> getOptionalProxyResponse(LiveConnectionData.Request request) {
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) {
try {
SignServerResponse signServerResponse = getSignedUrl(request.getRoomId());
HttpResponse<byte[]> credentialsResponse = getWebsocketCredentialsResponse(signServerResponse.getSignedUrl());
clientSettings.getHttpSettings().getProxyClientSettings().rotate();
return Optional.of(credentialsResponse);
} catch (TikTokProxyRequestException | TikTokSignServerException ignored) {}
}
}
return Optional.empty();
}
}
}

View File

@@ -26,62 +26,57 @@ import io.github.jwdeveloper.tiktok.data.settings.HttpClientSettings;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import lombok.AllArgsConstructor;
import java.net.*;
import java.net.http.*;
import java.nio.charset.*;
import java.util.*;
import java.util.regex.*;
import java.net.CookieManager;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
@AllArgsConstructor
public class HttpClient {
protected final HttpClientSettings httpClientSettings;
protected final String url;
private final Pattern pattern = Pattern.compile("charset=(.*?)(?=&|$)");
private final HttpClientSettings httpClientSettings;
private final String url;
public Optional<HttpResponse<byte[]>> toResponse() {
public <T> Optional<HttpResponse<T>> toResponse(HttpResponse.BodyHandler<T> bodyHandler) {
var client = prepareClient();
var request = prepareGetRequest();
try {
var response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
if (response.statusCode() != 200) {
try
{
var response = client.send(request, bodyHandler);
if(response.statusCode() != 200)
{
return Optional.empty();
}
return Optional.of(response);
} catch (Exception e) {
throw new TikTokLiveRequestException(e);
}
}
public Optional<String> toJsonResponse() {
var optional = toResponse();
var optional = toResponse(HttpResponse.BodyHandlers.ofString());
if (optional.isEmpty()) {
return Optional.empty();
}
var response = optional.get();
var body = response.body();
var charset = charsetFrom(response.headers());
return Optional.of(new String(body,charset));
}
private Charset charsetFrom(HttpHeaders headers) {
String type = headers.firstValue("Content-type").orElse("text/html; charset=utf-8");
int i = type.indexOf(";");
if (i >= 0) type = type.substring(i+1);
try {
Matcher matcher = pattern.matcher(type);
if (!matcher.find())
return StandardCharsets.UTF_8;
return Charset.forName(matcher.group(1));
} catch (Throwable x) {
return StandardCharsets.UTF_8;
}
var body = response.body();
return Optional.of(body);
}
public Optional<byte[]> toBinaryResponse() {
var optional = toResponse();
var optional = toResponse(HttpResponse.BodyHandlers.ofByteArray());
if (optional.isEmpty()) {
return Optional.empty();
}
@@ -89,12 +84,13 @@ public class HttpClient {
return Optional.of(body);
}
public URI toUrl() {
var stringUrl = prepareUrlWithParameters(url, httpClientSettings.getParams());
return URI.create(stringUrl);
}
protected HttpRequest prepareGetRequest() {
private HttpRequest prepareGetRequest() {
var requestBuilder = HttpRequest.newBuilder().GET();
requestBuilder.uri(toUrl());
requestBuilder.timeout(httpClientSettings.getTimeout());
@@ -104,17 +100,18 @@ public class HttpClient {
return requestBuilder.build();
}
protected java.net.http.HttpClient prepareClient() {
private java.net.http.HttpClient prepareClient() {
var builder = java.net.http.HttpClient.newBuilder()
.followRedirects(java.net.http.HttpClient.Redirect.NORMAL)
.cookieHandler(new CookieManager())
.connectTimeout(httpClientSettings.getTimeout());
.followRedirects(java.net.http.HttpClient.Redirect.NORMAL)
.cookieHandler(new CookieManager())
.connectTimeout(httpClientSettings.getTimeout());
httpClientSettings.getOnClientCreating().accept(builder);
return builder.build();
}
protected String prepareUrlWithParameters(String url, Map<String, Object> parameters) {
private String prepareUrlWithParameters(String url, Map<String, Object> parameters) {
if (parameters.isEmpty()) {
return url;
}
@@ -126,4 +123,4 @@ public class HttpClient {
return encodedKey + "=" + encodedValue;
}).collect(Collectors.joining("&"));
}
}
}

View File

@@ -77,9 +77,11 @@ public class HttpClientBuilder {
return this;
}
public HttpClient build() {
if (httpClientSettings.getProxyClientSettings().isEnabled())
return new HttpProxyClient(httpClientSettings, url);
return new HttpClient(httpClientSettings, url);
}
}
}

View File

@@ -22,7 +22,7 @@
*/
package io.github.jwdeveloper.tiktok.http;
import io.github.jwdeveloper.tiktok.data.settings.*;
import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
public class HttpClientFactory {
private final LiveClientSettings liveClientSettings;
@@ -37,8 +37,6 @@ public class HttpClientFactory {
//Does not contains default httpClientSettings, Params, headers, etd
public HttpClientBuilder clientEmpty(String url) {
var settings = new HttpClientSettings();
settings.setProxyClientSettings(liveClientSettings.getHttpSettings().getProxyClientSettings());
return new HttpClientBuilder(url, settings);
return new HttpClientBuilder(url);
}
}
}

View File

@@ -1,207 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.http;
import io.github.jwdeveloper.tiktok.data.settings.*;
import io.github.jwdeveloper.tiktok.exceptions.*;
import javax.net.ssl.*;
import java.io.IOException;
import java.net.*;
import java.net.http.*;
import java.net.http.HttpResponse.ResponseInfo;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.stream.Collectors;
public class HttpProxyClient extends HttpClient
{
private final ProxyClientSettings proxySettings;
public HttpProxyClient(HttpClientSettings httpClientSettings, String url) {
super(httpClientSettings, url);
this.proxySettings = httpClientSettings.getProxyClientSettings();
}
public Optional<HttpResponse<byte[]>> toResponse() {
return switch (proxySettings.getType()) {
case HTTP, DIRECT -> handleHttpProxyRequest();
default -> handleSocksProxyRequest();
};
}
public Optional<HttpResponse<byte[]>> handleHttpProxyRequest() {
var builder = java.net.http.HttpClient.newBuilder()
.followRedirects(java.net.http.HttpClient.Redirect.NORMAL)
.cookieHandler(new CookieManager())
.connectTimeout(httpClientSettings.getTimeout());
while (proxySettings.hasNext()) {
try {
InetSocketAddress address = proxySettings.next().toSocketAddress();
builder.proxy(ProxySelector.of(address));
httpClientSettings.getOnClientCreating().accept(builder);
var client = builder.build();
var request = prepareGetRequest();
var response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
if (response.statusCode() != 200) {
proxySettings.setLastSuccess(false);
continue;
}
proxySettings.setLastSuccess(true);
return Optional.of(response);
} catch (HttpConnectTimeoutException | ConnectException e) {
if (proxySettings.isAutoDiscard())
proxySettings.remove();
proxySettings.setLastSuccess(false);
} catch (Exception e) {
throw new TikTokLiveRequestException(e);
}
}
throw new TikTokLiveRequestException("No more proxies available!");
}
private Optional<HttpResponse<byte[]>> handleSocksProxyRequest() {
try {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[]{ new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {}
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {}
public X509Certificate[] getAcceptedIssuers() { return null; }
}}, null);
URL url = toUrl().toURL();
if (proxySettings.hasNext()) {
try {
Proxy proxy = new Proxy(Proxy.Type.SOCKS, proxySettings.next().toSocketAddress());
HttpsURLConnection socksConnection = (HttpsURLConnection) url.openConnection(proxy);
socksConnection.setSSLSocketFactory(sc.getSocketFactory());
socksConnection.setConnectTimeout(httpClientSettings.getTimeout().toMillisPart());
socksConnection.setReadTimeout(httpClientSettings.getTimeout().toMillisPart());
byte[] body = socksConnection.getInputStream().readAllBytes();
Map<String, List<String>> headers = socksConnection.getHeaderFields()
.entrySet()
.stream()
.filter(entry -> entry.getKey() != null)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
var responseInfo = createResponseInfo(socksConnection.getResponseCode(), headers);
var response = createHttpResponse(body, toUrl(), responseInfo);
proxySettings.setLastSuccess(true);
return Optional.of(response);
} catch (IOException e) {
if (proxySettings.isAutoDiscard())
proxySettings.remove();
proxySettings.setLastSuccess(false);
throw new TikTokProxyRequestException(e);
} catch (Exception e) {
throw new TikTokLiveRequestException(e);
}
}
throw new TikTokLiveRequestException("No more proxies available!");
} catch (NoSuchAlgorithmException | MalformedURLException | KeyManagementException e) {
// Should never be reached!
System.out.println("handleSocksProxyRequest()! If you see this message, reach us on discord!");
e.printStackTrace();
return Optional.empty();
} catch (TikTokLiveRequestException e) {
e.printStackTrace();
return Optional.empty();
}
}
private ResponseInfo createResponseInfo(int code, Map<String, List<String>> headers) {
return new ResponseInfo() {
@Override
public int statusCode() {
return code;
}
@Override
public HttpHeaders headers() {
return HttpHeaders.of(headers, (s, s1) -> s != null);
}
@Override
public java.net.http.HttpClient.Version version() {
return java.net.http.HttpClient.Version.HTTP_2;
}
};
}
private HttpResponse<byte[]> createHttpResponse(byte[] body,
URI uri,
ResponseInfo info) {
return new HttpResponse<>()
{
@Override
public int statusCode() {
return info.statusCode();
}
@Override
public HttpRequest request() {
throw new UnsupportedOperationException("TODO");
}
@Override
public Optional<HttpResponse<byte[]>> previousResponse() {
return Optional.empty();
}
@Override
public HttpHeaders headers() {
return info.headers();
}
@Override
public byte[] body() {
return body;
}
@Override
public Optional<SSLSession> sslSession() {
throw new UnsupportedOperationException("TODO");
}
@Override
public URI uri() {
return uri;
}
@Override
public java.net.http.HttpClient.Version version() {
return info.version();
}
};
}
}

View File

@@ -43,7 +43,6 @@ public class LiveDataMapper {
public LiveData.Response map(String json) {
var response = new LiveData.Response();
response.setJson(json);
var parsedJson = JsonParser.parseString(json);
var jsonObject = parsedJson.getAsJsonObject();

View File

@@ -26,10 +26,12 @@ import com.google.gson.JsonParser;
import io.github.jwdeveloper.tiktok.data.requests.LiveUserData;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
public class LiveUserDataMapper
{
public class LiveUserDataMapper {
public LiveUserData.Response map(String json) {
var jsonObject = JsonParser.parseString(json).getAsJsonObject();
var parsedJson = JsonParser.parseString(json);
var jsonObject = parsedJson.getAsJsonObject();
var message = jsonObject.get("message").getAsString();
@@ -62,5 +64,6 @@ public class LiveUserDataMapper
};
return new LiveUserData.Response(json, statusEnum, roomId, startTime);
}
}
}

View File

@@ -111,7 +111,6 @@ public class TikTokListenersManager implements ListenersManager {
EventConsumer eventMethodRef = (liveClient, event) ->
{
try {
method.setAccessible(true);
method.invoke(listener, liveClient, event);
} catch (Exception e) {
throw new TikTokEventListenerMethodException(e);

View File

@@ -22,17 +22,15 @@
*/
package io.github.jwdeveloper.tiktok.websocket;
import io.github.jwdeveloper.tiktok.*;
import io.github.jwdeveloper.tiktok.data.dto.ProxyData;
import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.TikTokLiveEventHandler;
import io.github.jwdeveloper.tiktok.TikTokLiveMessageHandler;
import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData;
import io.github.jwdeveloper.tiktok.data.settings.*;
import io.github.jwdeveloper.tiktok.exceptions.*;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import org.java_websocket.client.WebSocketClient;
import javax.net.ssl.*;
import java.net.Proxy;
import java.security.cert.X509Certificate;
import java.util.HashMap;
public class TikTokWebSocketClient implements SocketClient {
@@ -40,8 +38,6 @@ public class TikTokWebSocketClient implements SocketClient {
private final TikTokLiveMessageHandler messageHandler;
private final TikTokLiveEventHandler tikTokEventHandler;
private WebSocketClient webSocketClient;
private final TikTokWebSocketPingingTask pingingTask;
private boolean isConnected;
public TikTokWebSocketClient(
@@ -52,11 +48,11 @@ public class TikTokWebSocketClient implements SocketClient {
this.messageHandler = messageHandler;
this.tikTokEventHandler = tikTokEventHandler;
isConnected = false;
pingingTask = new TikTokWebSocketPingingTask();
}
@Override
public void start(LiveConnectionData.Response connectionData, LiveClient liveClient) {
public void start(LiveConnectionData.Response connectionData, LiveClient liveClient)
{
if (isConnected) {
stop();
}
@@ -72,70 +68,26 @@ public class TikTokWebSocketClient implements SocketClient {
tikTokEventHandler,
liveClient);
// ProxyClientSettings proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
// if (proxyClientSettings.isEnabled())
// connectProxy(proxyClientSettings);
// else
connectDefault();
}
private void connectDefault() {
try {
try
{
webSocketClient.connect();
pingingTask.run(webSocketClient);
isConnected = true;
} catch (Exception e) {
} catch (Exception e)
{
isConnected = false;
throw new TikTokLiveException("Failed to connect to the websocket", e);
}
}
public void connectProxy(ProxyClientSettings proxySettings) {
try {
if (proxySettings.getType() == Proxy.Type.SOCKS) {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[]{new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {}
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {}
public X509Certificate[] getAcceptedIssuers() { return null; }
}}, null);
webSocketClient.setSocketFactory(sc.getSocketFactory());
}
} catch (Exception e) {
// This will never be thrown.
throw new TikTokProxyRequestException("Unable to set Socks proxy SSL instance");
}
while (proxySettings.hasNext()) {
ProxyData proxyData = proxySettings.next();
if (!tryProxyConnection(proxySettings, proxyData)) {
if (proxySettings.isAutoDiscard())
proxySettings.remove();
continue;
}
pingingTask.run(webSocketClient);
isConnected = true;
break;
}
if (!isConnected)
throw new TikTokLiveException("Failed to connect to the websocket");
}
public boolean tryProxyConnection(ProxyClientSettings proxySettings, ProxyData proxyData) {
try {
webSocketClient.setProxy(new Proxy(proxySettings.getType(), proxyData.toSocketAddress()));
webSocketClient.connect();
return true;
} catch (Exception e) {
return false;
}
}
public void stop() {
if (isConnected && webSocketClient != null && webSocketClient.isOpen()) {
if (isConnected && webSocketClient != null) {
webSocketClient.closeConnection(0, "");
pingingTask.stop();
}
webSocketClient = null;
isConnected = false;
}
}
}

View File

@@ -23,18 +23,23 @@
package io.github.jwdeveloper.tiktok.websocket;
import com.google.protobuf.ByteString;
import io.github.jwdeveloper.tiktok.*;
import io.github.jwdeveloper.tiktok.data.events.*;
import io.github.jwdeveloper.tiktok.data.events.TikTokConnectedEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokDisconnectedEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokProtocolBufferException;
import io.github.jwdeveloper.tiktok.TikTokLiveEventHandler;
import io.github.jwdeveloper.tiktok.TikTokLiveMessageHandler;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.messages.webcast.*;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastPushFrame;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft_6455;
import org.java_websocket.handshake.ServerHandshake;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.Map;
import java.util.Optional;
public class TikTokWebSocketListener extends WebSocketClient {
@@ -81,12 +86,13 @@ public class TikTokWebSocketListener extends WebSocketClient {
pushFrameBuilder.setPayload(webcastResponse.getInternalExtBytes());
if (isNotClosing())
{
this.send(pushFrameBuilder.build().toByteArray());
this.send(pushFrameBuilder.build().toByteArray());
}
}
messageHandler.handle(tikTokLiveClient, webcastResponse);
}
@Override
public void onOpen(ServerHandshake serverHandshake) {
tikTokEventHandler.publish(tikTokLiveClient, new TikTokConnectedEvent());
@@ -95,10 +101,10 @@ public class TikTokWebSocketListener extends WebSocketClient {
}
}
@Override
public void onClose(int code, String reason, boolean remote) {
tikTokEventHandler.publish(tikTokLiveClient, new TikTokDisconnectedEvent(reason));
tikTokLiveClient.disconnect();
public void onClose(int i, String s, boolean b) {
tikTokEventHandler.publish(tikTokLiveClient, new TikTokDisconnectedEvent());
}
@Override
@@ -109,6 +115,8 @@ public class TikTokWebSocketListener extends WebSocketClient {
}
}
private Optional<WebcastPushFrame> getWebcastPushFrame(byte[] buffer) {
try {
var websocketMessage = WebcastPushFrame.parseFrom(buffer);
@@ -133,8 +141,9 @@ public class TikTokWebSocketListener extends WebSocketClient {
return !isClosed() && !isClosing();
}
@Override
public void onMessage(String s) {
// System.err.println(s);
}
}
}

View File

@@ -1,50 +0,0 @@
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 = 250;
private final int MAX_TIMEOUT = 500;
public void run(WebSocket webSocket)
{
stop();
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

@@ -41,7 +41,7 @@
<parent>
<artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<version>1.1.0-Release</version>
<version>1.0.14-Release</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -60,24 +60,6 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>extension-collector</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>extension-recorder</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>extension-collector</artifactId>
<version>1.1.0-Release</version>
<scope>compile</scope>
</dependency>
</dependencies>
<properties>

View File

@@ -1,76 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.extension.collector.TikTokLiveCollector;
import java.io.IOException;
import java.util.List;
import java.util.Map;
public class CollectorExample {
private static String mongoUser;
private static String mongoPassword;
private static String mongoDatabase;
public static void main(String[] args) throws IOException {
var collector = TikTokLiveCollector.use(settings ->
{
settings.setConnectionUrl("mongodb+srv://" + mongoUser + ":" + mongoPassword + "@" + mongoDatabase + "/?retryWrites=true&w=majority");
settings.setDatabaseName("tiktok");
});
collector.connectDatabase();
var users = List.of("tehila_723", "dino123597", "domaxyzx", "dash4214", "obserwacje_live");
var sessionTag = "Tag1";
for (var user : users) {
TikTokLive.newClient(user)
.configure(liveClientSettings ->
{
liveClientSettings.setPrintToConsole(true);
})
.onError((liveClient, event) ->
{
event.getException().printStackTrace();
})
.addListener(collector.newListener(Map.of("sessionTag", sessionTag), document ->
{
if (document.get("dataType") == "message") {
return false;
}
return true;
}))
.buildAndConnectAsync();
}
System.in.read();
collector.disconnectDatabase();
}
}

View File

@@ -57,7 +57,7 @@ public class ListenerExample
/**
*
* Method in TikTokEventListener should meet 4 requirements to be detected
* - must have @TikTokEventObserver annotation
* - must have @TikTokEventHandler annotation
* - must have 2 parameters
* - first parameter must be LiveClient
* - second must be class that extending TikTokEvent

View File

@@ -1,52 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok;
import java.net.Proxy;
public class ProxyExample {
public static void main(String[] args) throws Exception {
TikTokLive.newClient(SimpleExample.TIKTOK_HOSTNAME)
.configure(clientSettings -> {
clientSettings.setPrintToConsole(true);
clientSettings.getHttpSettings().configureProxy(proxySettings -> {
proxySettings.setOnProxyUpdated(proxyData -> System.err.println("Next proxy: " + proxyData.toString()));
proxySettings.setType(Proxy.Type.SOCKS);
proxySettings.addProxy("localhost", 8080);
});
})
.onConnected((liveClient, event) ->
liveClient.getLogger().info("Connected "+liveClient.getRoomInfo().getHostName()))
.onComment((liveClient, event) -> liveClient.getLogger().info(event.getUser().getName()+": "+event.getText()))
.onLike((liveClient, event) -> liveClient.getLogger().info(event.getUser().getName()+" sent "+event.getLikes()+"x likes!"))
.onDisconnected((liveClient, event) ->
liveClient.getLogger().info("Disconnect reason: "+event.getReason()))
.onLiveEnded((liveClient, event) ->
liveClient.getLogger().info("Live Ended: "+liveClient.getRoomInfo().getHostName()))
.onError((liveClient, event) ->
event.getException().printStackTrace())
.buildAndConnect();
System.in.read();
}
}

View File

@@ -1,55 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.extension.recorder.TikTokLiveRecorder;
import io.github.jwdeveloper.tiktok.extension.recorder.impl.event.TikTokLiveRecorderStartedEvent;
public class RecorderExample {
public static void main(String[] args) {
TikTokLive.newClient("bangbetmenygy")
.configure(liveClientSettings ->
{
liveClientSettings.setPrintToConsole(true);
})
.onError((liveClient, event) ->
{
event.getException().printStackTrace();
})
.addListener(TikTokLiveRecorder.use(recorderSettings ->
{
recorderSettings.setFfmpegPath("C:\\Users\\ja\\IdeaProjects\\TikTokLiveJava\\extension-recorder\\libs\\ffmpeg.exe");
recorderSettings.setOutputPath("C:\\Users\\ja\\IdeaProjects\\TikTokLiveJava\\extension-recorder\\out");
recorderSettings.setOutputFileName("test.flv");
}))
.onEvent(TikTokLiveRecorderStartedEvent.class, (liveClient, event) ->
{
System.out.println(event.getDownloadData().getFullUrl());
})
.buildAndConnect();
}
}

View File

@@ -23,18 +23,27 @@
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.data.events.TikTokSubNotifyEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokSubscribeEvent;
import io.github.jwdeveloper.tiktok.data.events.envelop.TikTokChestEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveOfflineHostException;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage;
import io.github.jwdeveloper.tiktok.utils.ConsoleColors;
import io.github.jwdeveloper.tiktok.utils.JsonUtil;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.HashMap;
import java.util.logging.Level;
public class SimpleExample {
public static String TIKTOK_HOSTNAME = "dash4114";
public static String TIKTOK_HOSTNAME = "dash4214";
public static void main(String[] args) throws IOException {
public static void main(String[] args) throws IOException, InterruptedException {
showLogo();
@@ -151,4 +160,4 @@ public class SimpleExample {
""");
}
}
}

501
README.md
View File

@@ -29,7 +29,8 @@ A Java library inspired by [TikTokLive](https://github.com/isaackogan/TikTokLive
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.
# Contributors
[Library documentation for contributors](https://github.com/jwdeveloper/TikTokLiveJava/wiki)
<div align="center">
<a href="https://www.youtube.com/watch?v=eerWGgUKc6c" align="right" target="blank"><img src="https://img.youtube.com/vi/eerWGgUKc6c/hqdefault.jpg" alt="IMAGE ALT TEXT" width="38%" align="right"></a>
@@ -39,7 +40,6 @@ Join the support [discord](https://discord.gg/e2XwPNTBBr) and visit the `#java-s
Do you prefer other programming languages?
- **Node** orginal: [TikTok-Live-Connector](https://github.com/isaackogan/TikTok-Live-Connector) by [@zerodytrash](https://github.com/zerodytrash)
- **Rust** rewrite: [TikTokLiveRust](https://github.com/jwdeveloper/TikTokLiveRust)
- **Python** rewrite: [TikTokLive](https://github.com/isaackogan/TikTokLive) by [@isaackogan](https://github.com/isaackogan)
- **Go** rewrite: [GoTikTokLive](https://github.com/Davincible/gotiktoklive) by [@Davincible](https://github.com/Davincible)
- **C#** rewrite: [TikTokLiveSharp](https://github.com/frankvHoof93/TikTokLiveSharp) by [@frankvHoof93](https://github.com/frankvHoof93)
@@ -49,7 +49,6 @@ Do you prefer other programming languages?
#### Overview
- [Getting started](#getting-started)
- [Events](#events)
- [Extensions](#extensions)
- [Listeners](#listeners)
- [Contributing](#contributing)
@@ -70,7 +69,7 @@ Maven
<dependency>
<groupId>com.github.jwdeveloper.TikTok-Live-Java</groupId>
<artifactId>Client</artifactId>
<version>1.1.0-Release</version>
<version>1.0.15-Release</version>
<scope>compile</scope>
</dependency>
</dependencies>
@@ -87,7 +86,7 @@ dependencyResolutionManagement {
}
dependencies {
implementation 'com.github.jwdeveloper.TikTok-Live-Java:Client:1.1.0-Release'
implementation 'com.github.jwdeveloper.TikTok-Live-Java:Client:1.0.15-Release'
}
```
@@ -166,79 +165,37 @@ TikTokLive.newClient("bangbetmenygy")
## Events
**Control**:
- [onReconnecting](#onreconnecting-tiktokreconnectingevent)
- [onError](#onerror-tiktokerrorevent)
- [onConnected](#onconnected-tiktokconnectedevent)
- [onDisconnected](#ondisconnected-tiktokdisconnectedevent)
- [onReconnecting](#onreconnecting-tiktokreconnectingevent)
- [onError](#onerror-tiktokerrorevent)
**Message**:
- [onEvent](#onevent-tiktokevent)
- [onEvent](#onevent-tiktokevent)
- [onComment](#oncomment-tiktokcommentevent)
- [onRoomInfo](#onroominfo-tiktokroominfoevent)
- [onGift](#ongift-tiktokgiftevent)
- [onSubscribe](#onsubscribe-tiktoksubscribeevent)
- [onFollow](#onfollow-tiktokfollowevent)
- [onGiftCombo](#ongiftcombo-tiktokgiftcomboevent)
- [onLiveEnded](#onliveended-tiktokliveendedevent)
- [onQuestion](#onquestion-tiktokquestionevent)
- [onShare](#onshare-tiktokshareevent)
- [onLiveUnpaused](#onliveunpaused-tiktokliveunpausedevent)
- [onEmote](#onemote-tiktokemoteevent)
- [onJoin](#onjoin-tiktokjoinevent)
- [onFollow](#onfollow-tiktokfollowevent)
- [onLike](#onlike-tiktoklikeevent)
- [onLiveEnded](#onliveended-tiktokliveendedevent)
- [onRoomInfo](#onroominfo-tiktokroominfoevent)
- [onShare](#onshare-tiktokshareevent)
- [onGiftCombo](#ongiftcombo-tiktokgiftcomboevent)
- [onEmote](#onemote-tiktokemoteevent)
- [onGift](#ongift-tiktokgiftevent)
- [onComment](#oncomment-tiktokcommentevent)
- [onLivePaused](#onlivepaused-tiktoklivepausedevent)
- [onLiveUnpaused](#onliveunpaused-tiktokliveunpausedevent)
- [onJoin](#onjoin-tiktokjoinevent)
**Debug**:
- [onWebsocketResponse](#onwebsocketresponse-tiktokwebsocketresponseevent)
- [onWebsocketUnhandledMessage](#onwebsocketunhandledmessage-tiktokwebsocketunhandledmessageevent)
- [onHttpResponse](#onhttpresponse-tiktokhttpresponseevent)
- [onWebsocketResponse](#onwebsocketresponse-tiktokwebsocketresponseevent)
- [onWebsocketMessage](#onwebsocketmessage-tiktokwebsocketmessageevent)
# Examples
<br>
## onReconnecting [TikTokReconnectingEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokReconnectingEvent.java)
```java
TikTokLive.newClient("host-name")
.onReconnecting((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onError [TikTokErrorEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokErrorEvent.java)
General error event. You should handle this.
```java
TikTokLive.newClient("host-name")
.onError((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onConnected [TikTokConnectedEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokConnectedEvent.java)
@@ -280,15 +237,32 @@ TikTokLive.newClient("host-name")
<br>
## onEvent [TikTokEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/common/TikTokEvent.java)
## onReconnecting [TikTokReconnectingEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokReconnectingEvent.java)
Base class for all events
```java
TikTokLive.newClient("host-name")
.onEvent((liveClient, event) ->
.onReconnecting((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onError [TikTokErrorEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokErrorEvent.java)
General error event. You should handle this.
```java
TikTokLive.newClient("host-name")
.onError((liveClient, event) ->
{
})
@@ -318,15 +292,91 @@ TikTokLive.newClient("host-name")
<br>
## onComment [TikTokCommentEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokCommentEvent.java)
## onSubscribe [TikTokSubscribeEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokSubscribeEvent.java)
Triggered every time a new chat comment arrives.
Triggers when a user creates a subscription.
```java
TikTokLive.newClient("host-name")
.onComment((liveClient, event) ->
.onSubscribe((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onQuestion [TikTokQuestionEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokQuestionEvent.java)
Triggered every time someone asks a new question via the question feature.
```java
TikTokLive.newClient("host-name")
.onQuestion((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onFollow [TikTokFollowEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/social/TikTokFollowEvent.java)
Triggers when a user follows the streamer. Based on social event.
```java
TikTokLive.newClient("host-name")
.onFollow((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onLike [TikTokLikeEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/social/TikTokLikeEvent.java)
Triggered when a viewer sends likes to the streamer. For streams with many viewers, this event is not always triggered by TikTok.
```java
TikTokLive.newClient("host-name")
.onLike((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onLiveEnded [TikTokLiveEndedEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokLiveEndedEvent.java)
Triggered when the live stream gets terminated by the host. Will also trigger the TikTokDisconnectedEvent event.
```java
TikTokLive.newClient("host-name")
.onLiveEnded((liveClient, event) ->
{
})
@@ -340,8 +390,6 @@ TikTokLive.newClient("host-name")
## onRoomInfo [TikTokRoomInfoEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/room/TikTokRoomInfoEvent.java)
Triggered when LiveRoomInfo got updated such as likes, viewers, ranking ....
```java
TikTokLive.newClient("host-name")
@@ -354,6 +402,73 @@ TikTokLive.newClient("host-name")
<br>
## onShare [TikTokShareEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/social/TikTokShareEvent.java)
Triggers when a user shares the stream. Based on social event.
```java
TikTokLive.newClient("host-name")
.onShare((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onGiftCombo [TikTokGiftComboEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/gift/TikTokGiftComboEvent.java)
Triggered every time gift is sent
@see GiftSendType it has 3 states
<p>Example when user sends gift with combo</p>
<p>>Combo: 1 -> comboState = GiftSendType.Begin</p>
<p>Combo: 4 -> comboState = GiftSendType.Active</p>
<p>Combo: 8 -> comboState = GiftSendType.Active</p>
<p>Combo: 12 -> comboState = GiftSendType.Finished</p>
Remember if comboState is Finished both TikTokGiftComboEvent and TikTokGiftEvent event gets triggered
```java
TikTokLive.newClient("host-name")
.onGiftCombo((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onEmote [TikTokEmoteEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokEmoteEvent.java)
Triggered every time a subscriber sends an emote (sticker).
```java
TikTokLive.newClient("host-name")
.onEmote((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onGift [TikTokGiftEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/gift/TikTokGiftEvent.java)
@@ -377,192 +492,15 @@ TikTokLive.newClient("host-name")
<br>
## onSubscribe [TikTokSubscribeEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokSubscribeEvent.java)
## onComment [TikTokCommentEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokCommentEvent.java)
Triggers when a user creates a subscription.
Triggered every time a new chat comment arrives.
```java
TikTokLive.newClient("host-name")
.onSubscribe((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onFollow [TikTokFollowEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/social/TikTokFollowEvent.java)
Triggers when a user follows the streamer. Based on social event.
```java
TikTokLive.newClient("host-name")
.onFollow((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onGiftCombo [TikTokGiftComboEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/gift/TikTokGiftComboEvent.java)
Triggered every time gift is sent
@see GiftSendType it has 3 states
<p>Example when user sends gift with combo</p>
<p>>Combo: 1 -> comboState = GiftSendType.Begin</p>
<p>Combo: 4 -> comboState = GiftSendType.Active</p>
<p>Combo: 8 -> comboState = GiftSendType.Active</p>
<p>Combo: 12 -> comboState = GiftSendType.Finsihed</p>
<p>
Remember if comboState is Finsihed both TikTokGiftComboEvent and TikTokGiftEvent event gets triggered
```java
TikTokLive.newClient("host-name")
.onGiftCombo((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onLiveEnded [TikTokLiveEndedEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokLiveEndedEvent.java)
Triggered when the live stream gets terminated by the host. Will also trigger the TikTokDisconnectedEvent event.
```java
TikTokLive.newClient("host-name")
.onLiveEnded((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onQuestion [TikTokQuestionEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokQuestionEvent.java)
Triggered every time someone asks a new question via the question feature.
```java
TikTokLive.newClient("host-name")
.onQuestion((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onShare [TikTokShareEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/social/TikTokShareEvent.java)
Triggers when a user shares the stream. Based on social event.
```java
TikTokLive.newClient("host-name")
.onShare((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onLiveUnpaused [TikTokLiveUnpausedEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokLiveUnpausedEvent.java)
```java
TikTokLive.newClient("host-name")
.onLiveUnpaused((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onEmote [TikTokEmoteEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokEmoteEvent.java)
Triggered every time a subscriber sends an emote (sticker).
```java
TikTokLive.newClient("host-name")
.onEmote((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onJoin [TikTokJoinEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/social/TikTokJoinEvent.java)
```java
TikTokLive.newClient("host-name")
.onJoin((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onLike [TikTokLikeEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/social/TikTokLikeEvent.java)
Triggered when a viewer sends likes to the streamer. For streams with many viewers, this event is not always triggered by TikTok.
```java
TikTokLive.newClient("host-name")
.onLike((liveClient, event) ->
.onComment((liveClient, event) ->
{
})
@@ -590,13 +528,30 @@ TikTokLive.newClient("host-name")
<br>
## onWebsocketResponse [TikTokWebsocketResponseEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/websocket/TikTokWebsocketResponseEvent.java)
## onLiveUnpaused [TikTokLiveUnpausedEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokLiveUnpausedEvent.java)
```java
TikTokLive.newClient("host-name")
.onWebsocketResponse((liveClient, event) ->
.onLiveUnpaused((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onJoin [TikTokJoinEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/social/TikTokJoinEvent.java)
```java
TikTokLive.newClient("host-name")
.onJoin((liveClient, event) ->
{
})
@@ -626,17 +581,17 @@ TikTokLive.newClient("host-name")
<br>
## onHttpResponse [TikTokHttpResponseEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/http/TikTokHttpResponseEvent.java)
## onWebsocketResponse [TikTokWebsocketResponseEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/websocket/TikTokWebsocketResponseEvent.java)
```java
TikTokLive.newClient("host-name")
.onHttpResponse((liveClient, event) ->
{
.onWebsocketResponse((liveClient, event) ->
{
})
.buildAndConnect();
})
.buildAndConnect();
```
@@ -646,37 +601,24 @@ TikTokLive.newClient("host-name")
## onWebsocketMessage [TikTokWebsocketMessageEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/websocket/TikTokWebsocketMessageEvent.java)
Triggered every time TikTok sends data. Data incoming as protobuf message.
You can deserialize the binary object depending on the use case.
Triggered every time a protobuf encoded webcast message arrives. You can deserialize the binary object depending on the use case.
```java
TikTokLive.newClient("host-name")
.onWebsocketMessage((liveClient, event) ->
{
.onWebsocketMessage((liveClient, event) ->
{
})
.buildAndConnect();
})
.buildAndConnect();
```
<br>
## Extensions
List of extensions (addons) to TiktokLiveJava
that will save your time
- [Video Recorder](https://github.com/jwdeveloper/TikTokLiveJava/tree/master/extension-recorder)
- [Live data collector to database](https://github.com/jwdeveloper/TikTokLiveJava/tree/master/extension-collector)
## Listeners
```java
@@ -709,24 +651,24 @@ public static void main(String[] args) throws IOException {
public static class CustomListener implements TikTokEventListener {
@TikTokEventObserver
@TikTokEventHandler
public void onLike(LiveClient liveClient, TikTokLikeEvent event) {
System.out.println(event.toString());
}
@TikTokEventObserver
@TikTokEventHandler
public void onError(LiveClient liveClient, TikTokErrorEvent event) {
// event.getException().printStackTrace();
}
@TikTokEventObserver
@TikTokEventHandler
public void onComment(LiveClient liveClient, TikTokCommentEvent event) {
var userName = event.getUser().getName();
var text = event.getText();
liveClient.getLogger().info(userName + ": " + text);
}
@TikTokEventObserver
@TikTokEventHandler
public void onGift(LiveClient liveClient, TikTokGiftEvent event) {
var message = switch (event.getGift()) {
case ROSE -> "Thanks :)";
@@ -751,7 +693,4 @@ public static class CustomListener implements TikTokEventListener {
## Contributing
[Library documentation for contributors](https://github.com/jwdeveloper/TikTokLiveJava/wiki)
Your improvements are welcome! Feel free to open an <a href="https://github.com/jwdeveloper/TikTok-Live-Java/issues">issue</a> or <a href="https://github.com/jwdeveloper/TikTok-Live-Java/pulls">pull request</a>.

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<version>1.1.0-Release</version>
<version>1.0.14-Release</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<version>1.1.0-Release</version>
<version>1.0.14-Release</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>Tools-EventsWebViewer</artifactId>

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<version>1.1.0-Release</version>
<version>1.0.14-Release</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -126,6 +126,7 @@ TikTokLive.newClient("bangbetmenygy")
{
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.setRetryOnConnectionFailure(true); // Reconnecting if TikTok user is offline
@@ -151,73 +152,35 @@ TikTokLive.newClient("bangbetmenygy")
**Control**:
- [onReconnecting](#onreconnecting-tiktokreconnectingevent)
- [onError](#onerror-tiktokerrorevent)
- [onConnected](#onconnected-tiktokconnectedevent)
- [onDisconnected](#ondisconnected-tiktokdisconnectedevent)
- [onReconnecting](#onreconnecting-tiktokreconnectingevent)
- [onError](#onerror-tiktokerrorevent)
**Message**:
- [onEvent](#onevent-tiktokevent)
- [onEvent](#onevent-tiktokevent)
- [onComment](#oncomment-tiktokcommentevent)
- [onRoomInfo](#onroominfo-tiktokroominfoevent)
- [onGift](#ongift-tiktokgiftevent)
- [onSubscribe](#onsubscribe-tiktoksubscribeevent)
- [onFollow](#onfollow-tiktokfollowevent)
- [onGiftCombo](#ongiftcombo-tiktokgiftcomboevent)
- [onLiveEnded](#onliveended-tiktokliveendedevent)
- [onQuestion](#onquestion-tiktokquestionevent)
- [onShare](#onshare-tiktokshareevent)
- [onLiveUnpaused](#onliveunpaused-tiktokliveunpausedevent)
- [onEmote](#onemote-tiktokemoteevent)
- [onJoin](#onjoin-tiktokjoinevent)
- [onFollow](#onfollow-tiktokfollowevent)
- [onLike](#onlike-tiktoklikeevent)
- [onLiveEnded](#onliveended-tiktokliveendedevent)
- [onRoomInfo](#onroominfo-tiktokroominfoevent)
- [onShare](#onshare-tiktokshareevent)
- [onGiftCombo](#ongiftcombo-tiktokgiftcomboevent)
- [onEmote](#onemote-tiktokemoteevent)
- [onGift](#ongift-tiktokgiftevent)
- [onComment](#oncomment-tiktokcommentevent)
- [onLivePaused](#onlivepaused-tiktoklivepausedevent)
- [onLiveUnpaused](#onliveunpaused-tiktokliveunpausedevent)
- [onJoin](#onjoin-tiktokjoinevent)
**Debug**:
- [onWebsocketResponse](#onwebsocketresponse-tiktokwebsocketresponseevent)
- [onWebsocketUnhandledMessage](#onwebsocketunhandledmessage-tiktokwebsocketunhandledmessageevent)
- [onHttpResponse](#onhttpresponse-tiktokhttpresponseevent)
- [onWebsocketResponse](#onwebsocketresponse-tiktokwebsocketresponseevent)
- [onWebsocketMessage](#onwebsocketmessage-tiktokwebsocketmessageevent)
# Examples
<br>
## onReconnecting [TikTokReconnectingEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokReconnectingEvent.java)
```java
TikTokLive.newClient("host-name")
.onReconnecting((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onError [TikTokErrorEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokErrorEvent.java)
General error event. You should handle this.
```java
TikTokLive.newClient("host-name")
.onError((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onConnected [TikTokConnectedEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokConnectedEvent.java)
@@ -259,15 +222,32 @@ TikTokLive.newClient("host-name")
<br>
## onEvent [TikTokEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/common/TikTokEvent.java)
## onReconnecting [TikTokReconnectingEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokReconnectingEvent.java)
Base class for all events
```java
TikTokLive.newClient("host-name")
.onReconnecting((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onError [TikTokErrorEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokErrorEvent.java)
General error event. You should handle this.
```java
TikTokLive.newClient("host-name")
.onEvent((liveClient, event) ->
.onError((liveClient, event) ->
{
})
@@ -295,65 +275,6 @@ TikTokLive.newClient("host-name")
<br>
## onComment [TikTokCommentEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokCommentEvent.java)
Triggered every time a new chat comment arrives.
```java
TikTokLive.newClient("host-name")
.onComment((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onRoomInfo [TikTokRoomInfoEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/room/TikTokRoomInfoEvent.java)
Triggered when LiveRoomInfo got updated such as likes, viewers, ranking ....
```java
TikTokLive.newClient("host-name")
.onRoomInfo((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onGift [TikTokGiftEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/gift/TikTokGiftEvent.java)
Triggered when user sends gifts that has
no combo (most of expensive gifts)
or if combo has finished
```java
TikTokLive.newClient("host-name")
.onGift((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onSubscribe [TikTokSubscribeEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokSubscribeEvent.java)
@@ -373,73 +294,6 @@ TikTokLive.newClient("host-name")
<br>
## onFollow [TikTokFollowEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/social/TikTokFollowEvent.java)
Triggers when a user follows the streamer. Based on social event.
```java
TikTokLive.newClient("host-name")
.onFollow((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onGiftCombo [TikTokGiftComboEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/gift/TikTokGiftComboEvent.java)
Triggered every time gift is sent
@see GiftSendType it has 3 states
<p>Example when user sends gift with combo</p>
<p>>Combo: 1 -> comboState = GiftSendType.Begin</p>
<p>Combo: 4 -> comboState = GiftSendType.Active</p>
<p>Combo: 8 -> comboState = GiftSendType.Active</p>
<p>Combo: 12 -> comboState = GiftSendType.Finsihed</p>
<p>
Remember if comboState is Finsihed both TikTokGiftComboEvent and TikTokGiftEvent event gets triggered
```java
TikTokLive.newClient("host-name")
.onGiftCombo((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onLiveEnded [TikTokLiveEndedEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokLiveEndedEvent.java)
Triggered when the live stream gets terminated by the host. Will also trigger the TikTokDisconnectedEvent event.
```java
TikTokLive.newClient("host-name")
.onLiveEnded((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onQuestion [TikTokQuestionEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokQuestionEvent.java)
@@ -461,68 +315,15 @@ TikTokLive.newClient("host-name")
<br>
## onShare [TikTokShareEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/social/TikTokShareEvent.java)
## onFollow [TikTokFollowEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/social/TikTokFollowEvent.java)
Triggers when a user shares the stream. Based on social event.
Triggers when a user follows the streamer. Based on social event.
```java
TikTokLive.newClient("host-name")
.onShare((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onLiveUnpaused [TikTokLiveUnpausedEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokLiveUnpausedEvent.java)
```java
TikTokLive.newClient("host-name")
.onLiveUnpaused((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onEmote [TikTokEmoteEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokEmoteEvent.java)
Triggered every time a subscriber sends an emote (sticker).
```java
TikTokLive.newClient("host-name")
.onEmote((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onJoin [TikTokJoinEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/social/TikTokJoinEvent.java)
```java
TikTokLive.newClient("host-name")
.onJoin((liveClient, event) ->
.onFollow((liveClient, event) ->
{
})
@@ -550,6 +351,149 @@ TikTokLive.newClient("host-name")
<br>
## onLiveEnded [TikTokLiveEndedEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokLiveEndedEvent.java)
Triggered when the live stream gets terminated by the host. Will also trigger the TikTokDisconnectedEvent event.
```java
TikTokLive.newClient("host-name")
.onLiveEnded((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onRoomInfo [TikTokRoomInfoEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/room/TikTokRoomInfoEvent.java)
```java
TikTokLive.newClient("host-name")
.onRoomInfo((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onShare [TikTokShareEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/social/TikTokShareEvent.java)
Triggers when a user shares the stream. Based on social event.
```java
TikTokLive.newClient("host-name")
.onShare((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onGiftCombo [TikTokGiftComboEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/gift/TikTokGiftComboEvent.java)
Triggered every time gift is sent
@see GiftSendType it has 3 states
<p>Example when user sends gift with combo</p>
<p>>Combo: 1 -> comboState = GiftSendType.Begin</p>
<p>Combo: 4 -> comboState = GiftSendType.Active</p>
<p>Combo: 8 -> comboState = GiftSendType.Active</p>
<p>Combo: 12 -> comboState = GiftSendType.Finsihed</p>
Remember if comboState is Finsihed both TikTokGiftComboEvent and TikTokGiftEvent event gets triggered
```java
TikTokLive.newClient("host-name")
.onGiftCombo((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onEmote [TikTokEmoteEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokEmoteEvent.java)
Triggered every time a subscriber sends an emote (sticker).
```java
TikTokLive.newClient("host-name")
.onEmote((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onGift [TikTokGiftEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/gift/TikTokGiftEvent.java)
Triggered when user sends gifts that has
no combo (most of expensive gifts)
or if combo has finished
```java
TikTokLive.newClient("host-name")
.onGift((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onComment [TikTokCommentEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokCommentEvent.java)
Triggered every time a new chat comment arrives.
```java
TikTokLive.newClient("host-name")
.onComment((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onLivePaused [TikTokLivePausedEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokLivePausedEvent.java)
@@ -569,13 +513,30 @@ TikTokLive.newClient("host-name")
<br>
## onWebsocketResponse [TikTokWebsocketResponseEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/websocket/TikTokWebsocketResponseEvent.java)
## onLiveUnpaused [TikTokLiveUnpausedEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/TikTokLiveUnpausedEvent.java)
```java
TikTokLive.newClient("host-name")
.onWebsocketResponse((liveClient, event) ->
.onLiveUnpaused((liveClient, event) ->
{
})
.buildAndConnect();
```
<br>
## onJoin [TikTokJoinEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/social/TikTokJoinEvent.java)
```java
TikTokLive.newClient("host-name")
.onJoin((liveClient, event) ->
{
})
@@ -605,13 +566,13 @@ TikTokLive.newClient("host-name")
<br>
## onHttpResponse [TikTokHttpResponseEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/http/TikTokHttpResponseEvent.java)
## onWebsocketResponse [TikTokWebsocketResponseEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/websocket/TikTokWebsocketResponseEvent.java)
```java
TikTokLive.newClient("host-name")
.onHttpResponse((liveClient, event) ->
.onWebsocketResponse((liveClient, event) ->
{
})
@@ -625,8 +586,7 @@ TikTokLive.newClient("host-name")
## onWebsocketMessage [TikTokWebsocketMessageEvent](https://github.com/jwdeveloper/TikTokLiveJava/blob/master/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/websocket/TikTokWebsocketMessageEvent.java)
Triggered every time TikTok sends data. Data incoming as protobuf message.
You can deserialize the binary object depending on the use case.
Triggered every time a protobuf encoded webcast message arrives. You can deserialize the binary object depending on the use case.
```java
@@ -642,17 +602,6 @@ TikTokLive.newClient("host-name")
{{for item of data }}
{{if item is 2}}
my name is {{item.name}}
{{else}}
{{end}}
{{end}}
<br>
## Listeners
@@ -679,7 +628,7 @@ my name is {{item.name}}
/**
*
* Method in TikTokEventListener should meet 4 requirements to be detected
* - must have @TikTokEventObserver annotation
* - must have @TikTokEventHandler annotation
* - must have 2 parameters
* - first parameter must be LiveClient
* - second must be class that extending TikTokEvent
@@ -687,24 +636,24 @@ my name is {{item.name}}
public static class CustomListener implements TikTokEventListener {
@TikTokEventObserver
@TikTokEventHandler
public void onLike(LiveClient liveClient, TikTokLikeEvent event) {
System.out.println(event.toString());
}
@TikTokEventObserver
@TikTokEventHandler
public void onError(LiveClient liveClient, TikTokErrorEvent event) {
// event.getException().printStackTrace();
}
@TikTokEventObserver
@TikTokEventHandler
public void onComment(LiveClient liveClient, TikTokCommentEvent event) {
var userName = event.getUser().getName();
var text = event.getText();
liveClient.getLogger().info(userName + ": " + text);
}
@TikTokEventObserver
@TikTokEventHandler
public void onGift(LiveClient liveClient, TikTokGiftEvent event) {
var message = switch (event.getGift()) {
case ROSE -> "Thanks :)";
@@ -717,7 +666,7 @@ my name is {{item.name}}
liveClient.getLogger().info(message);
}
@TikTokEventObserver
@TikTokEventHandler
public void onAnyEvent(LiveClient liveClient, TikTokEvent event) {
liveClient.getLogger().info(event.getClass().getSimpleName());
}

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<version>1.1.0-Release</version>
<version>1.0.14-Release</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -1,64 +0,0 @@
Collects live data to mongodb database
```xml
<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>1.1.0-Release</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.github.jwdeveloper.TikTok-Live-Java</groupId>
<artifactId>extension-collector</artifactId>
<version>1.1.0-Release</version>
</dependency>
</dependencies>
```
Usage
```java
public static void main(String[] args) throws IOException {
var collector = TikTokLiveCollector.use(settings ->
{
settings.setConnectionUrl("mongodb+srv://" + mongoUser + ":" + mongoPassword + "@" + mongoDatabase + "/?retryWrites=true&w=majority");
settings.setDatabaseName("tiktok");
});
collector.connectDatabase();
var users = List.of("tehila_723", "dino123597", "domaxyzx", "dash4214", "obserwacje_live");
var sessionTag = "Tag1";
for (var user : users) {
TikTokLive.newClient(user)
.configure(liveClientSettings ->
{
liveClientSettings.setPrintToConsole(true);
})
.onError((liveClient, event) ->
{
event.getException().printStackTrace();
})
.addListener(collector.newListener(Map.of("sessionTag", sessionTag), document ->
{
if (document.get("dataType") == "message") {
return false;
}
return true;
}))
.buildAndConnectAsync();
}
System.in.read();
collector.disconnectDatabase();
}
```

View File

@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>TikTokLiveJava</artifactId>
<version>1.1.0-Release</version>
</parent>
<artifactId>extension-collector</artifactId>
<dependencies>
<dependency>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>API</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.24.1</version>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-sync</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>API</artifactId>
<version>1.1.0-Release</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@@ -1,40 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.extension.collector;
import io.github.jwdeveloper.tiktok.extension.collector.api.LiveDataCollector;
import io.github.jwdeveloper.tiktok.extension.collector.api.data.LiveDataCollectorSettings;
import io.github.jwdeveloper.tiktok.extension.collector.impl.TikTokLiveDataCollector;
import java.util.function.Consumer;
public class TikTokLiveCollector
{
public static TikTokLiveDataCollector use(Consumer<LiveDataCollectorSettings> consumer)
{
var settings = new LiveDataCollectorSettings();
consumer.accept(settings);
return new TikTokLiveDataCollector(settings);
}
}

View File

@@ -1,30 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.extension.collector.api;
import io.github.jwdeveloper.tiktok.listener.TikTokEventListener;
public interface LiveDataCollector extends TikTokEventListener
{
}

View File

@@ -1,13 +0,0 @@
package io.github.jwdeveloper.tiktok.extension.collector.api.data;
import lombok.Data;
import org.bson.Document;
import java.util.Map;
import java.util.function.Function;
@Data
public class CollectorListenerSettings {
private Map<String, Object> extraFields;
private Function<Document, Boolean> filter;
}

View File

@@ -1,48 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.extension.collector.api.data;
import lombok.Data;
import java.util.function.Consumer;
@Data
public class LiveDataCollectorSettings {
private String connectionUrl;
private String databaseName;
private String sessionTag;
public void setConnectionUrl(String connectionUrl) {
this.connectionUrl = connectionUrl;
}
public void setConnectionUrl(Consumer<MongoDBConnectionStringBuilder> consumer) {
var builder = new MongoDBConnectionStringBuilder();
consumer.accept(builder);
connectionUrl = builder.build();
}
}

View File

@@ -1,55 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.extension.collector.api.data;
public class MongoDBConnectionStringBuilder {
private String username;
private String password;
private String database;
private String cluster;
public MongoDBConnectionStringBuilder setUsername(String username) {
this.username = username;
return this;
}
public MongoDBConnectionStringBuilder setPassword(String password) {
this.password = password;
return this;
}
public MongoDBConnectionStringBuilder setDatabase(String database) {
this.database = database;
return this;
}
public MongoDBConnectionStringBuilder setCluster(String cluster) {
this.cluster = cluster;
return this;
}
public String build() {
return String.format("mongodb+srv://%s:%s@%s/%s?retryWrites=true&w=majority",
username, password, cluster, database);
}
}

View File

@@ -1,90 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.extension.collector.impl;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.ServerApi;
import com.mongodb.ServerApiVersion;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.Indexes;
import io.github.jwdeveloper.tiktok.extension.collector.api.data.CollectorListenerSettings;
import io.github.jwdeveloper.tiktok.extension.collector.api.data.LiveDataCollectorSettings;
import org.bson.Document;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Function;
public class TikTokLiveDataCollector {
private final LiveDataCollectorSettings settings;
private MongoClient mongoClient;
private MongoDatabase database;
private MongoCollection<Document> collection;
public TikTokLiveDataCollector(LiveDataCollectorSettings settings) {
this.settings = settings;
}
public void connectDatabase() {
var serverApi = ServerApi.builder()
.version(ServerApiVersion.V1)
.build();
var mongoSettings = MongoClientSettings.builder()
.applyConnectionString(new ConnectionString(settings.getConnectionUrl()))
.serverApi(serverApi)
.build();
mongoClient = MongoClients.create(mongoSettings);
database = mongoClient.getDatabase(settings.getDatabaseName());
collection = database.getCollection("data");
collection.createIndex(Indexes.hashed("session"));
collection.createIndex(Indexes.hashed("dataType"));
}
public void disconnectDatabase() {
mongoClient.close();
}
public TikTokLiveDataCollectorListener newListener() {
return newListener(Map.of());
}
public TikTokLiveDataCollectorListener newListener(Map<String, Object> additionalFields) {
return newListener(additionalFields, (e)->true);
}
public TikTokLiveDataCollectorListener newListener(Map<String, Object> additionalFields,
Function<Document, Boolean> filter) {
var settings = new CollectorListenerSettings();
settings.setExtraFields(additionalFields);
settings.setFilter(filter);
return new TikTokLiveDataCollectorListener(collection, settings);
}
}

View File

@@ -1,122 +0,0 @@
package io.github.jwdeveloper.tiktok.extension.collector.impl;
import com.mongodb.client.MongoCollection;
import io.github.jwdeveloper.tiktok.annotations.TikTokEventObserver;
import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.control.TikTokConnectingEvent;
import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketResponseEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveMessageException;
import io.github.jwdeveloper.tiktok.extension.collector.api.LiveDataCollector;
import io.github.jwdeveloper.tiktok.extension.collector.api.data.CollectorListenerSettings;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import io.github.jwdeveloper.tiktok.utils.JsonUtil;
import org.bson.Document;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Base64;
import java.util.UUID;
public class TikTokLiveDataCollectorListener implements LiveDataCollector {
private final MongoCollection<Document> collection;
private final CollectorListenerSettings settings;
private String sessionId;
private String userName;
public TikTokLiveDataCollectorListener(MongoCollection<Document> collection, CollectorListenerSettings settings) {
this.collection = collection;
this.settings = settings;
}
@TikTokEventObserver
private void onResponse(LiveClient liveClient, TikTokWebsocketResponseEvent event) {
includeResponse(liveClient, event.getResponse());
event.getResponse().getMessagesList().forEach(message ->
{
includeMessage(liveClient, message);
});
}
@TikTokEventObserver
private void onEvent(LiveClient liveClient, TikTokEvent event) {
if (event instanceof TikTokConnectingEvent) {
sessionId = UUID.randomUUID().toString();
userName = liveClient.getRoomInfo().getHostName();
}
if (event instanceof TikTokErrorEvent) {
return;
}
includeEvent(event);
}
@TikTokEventObserver
private void onError(LiveClient liveClient, TikTokErrorEvent event) {
event.getException().printStackTrace();
includeError(event);
}
private void includeResponse(LiveClient liveClient, WebcastResponse message) {
var messageContent = Base64.getEncoder().encodeToString(message.toByteArray());
insertDocument(createDocument("response", "webcast", messageContent));
}
private void includeMessage(LiveClient liveClient, WebcastResponse.Message message) {
var method = message.getMethod();
var messageContent = Base64.getEncoder().encodeToString(message.getPayload().toByteArray());
insertDocument(createDocument("message", method, messageContent));
}
private void includeEvent(TikTokEvent event) {
var json = JsonUtil.toJson(event);
var content = Base64.getEncoder().encodeToString(json.getBytes());
var name = event.getClass().getSimpleName();
insertDocument(createDocument("event", name, content));
}
private void includeError(TikTokErrorEvent event) {
var exception = event.getException();
var exceptionName = event.getException().getClass().getSimpleName();
var sw = new StringWriter();
var pw = new PrintWriter(sw);
event.getException().printStackTrace(pw);
var content = sw.toString();
var doc = createDocument("error", exceptionName, content);
if (exception instanceof TikTokLiveMessageException ex) {
doc.append("message", ex.messageToBase64())
.append("response", ex.webcastResponseToBase64());
}
insertDocument(doc);
}
private void insertDocument(Document document) {
if (!settings.getFilter().apply(document)) {
return;
}
collection.insertOne(document);
}
private Document createDocument(String dataType, String dataTypeName, String content) {
var doc = new Document();
doc.append("session", sessionId);
for (var entry : settings.getExtraFields().entrySet()) {
doc.append(entry.getKey(), entry.getValue());
}
doc.append("tiktokUser", userName);
doc.append("dataType", dataType);
doc.append("dataTypeName", dataTypeName);
doc.append("content", content);
return doc;
}
}

View File

@@ -1,56 +0,0 @@
Records stream to flv file
```xml
<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>1.1.0-Release</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.github.jwdeveloper.TikTok-Live-Java</groupId>
<artifactId>extension-recorder</artifactId>
<version>1.1.0-Release</version>
</dependency>
</dependencies>
```
Usage
```java
public static void main(String[] args) {
TikTokLive.newClient("bangbetmenygy")
.configure(liveClientSettings ->
{
liveClientSettings.setPrintToConsole(true);
})
.onError((liveClient, event) ->
{
event.getException().printStackTrace();
})
.addListener(TikTokLiveRecorder.use(recorderSettings ->
{
recorderSettings.setFfmpegPath("C:\\Users\\ja\\IdeaProjects\\TikTokLiveJava\\extension-recorder\\libs\\ffmpeg.exe");
recorderSettings.setOutputPath("C:\\Users\\ja\\IdeaProjects\\TikTokLiveJava\\extension-recorder\\out");
recorderSettings.setOutputFileName("test.flv");
}))
.onEvent(TikTokLiveRecorderStartedEvent.class, (liveClient, event) ->
{
System.out.println(event.getDownloadData().getFullUrl());
})
.buildAndConnect();
}
```

View File

@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<parent>
<artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<version>1.1.0-Release</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>extension-recorder</artifactId>
<dependencies>
<dependency>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>Client</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>API</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>16</maven.compiler.source>
<maven.compiler.target>16</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>

View File

@@ -1,43 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.extension.recorder;
import io.github.jwdeveloper.tiktok.extension.recorder.api.LiveRecorder;
import io.github.jwdeveloper.tiktok.extension.recorder.impl.RecorderListener;
import io.github.jwdeveloper.tiktok.extension.recorder.impl.data.RecorderSettings;
import java.util.function.Consumer;
public class TikTokLiveRecorder
{
public static LiveRecorder use(Consumer<RecorderSettings> consumer)
{
return new RecorderListener(consumer);
}
public static LiveRecorder use()
{
return use(x ->{});
}
}

View File

@@ -1,31 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.extension.recorder.api;
import io.github.jwdeveloper.tiktok.listener.TikTokEventListener;
public interface LiveRecorder extends TikTokEventListener {
void pause();
void unpause();
}

View File

@@ -1,203 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.extension.recorder.impl;
import com.google.gson.JsonParser;
import io.github.jwdeveloper.tiktok.annotations.TikTokEventObserver;
import io.github.jwdeveloper.tiktok.data.events.*;
import io.github.jwdeveloper.tiktok.data.events.http.TikTokRoomDataResponseEvent;
import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.extension.recorder.api.LiveRecorder;
import io.github.jwdeveloper.tiktok.extension.recorder.impl.data.*;
import io.github.jwdeveloper.tiktok.extension.recorder.impl.enums.LiveQuality;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import javax.net.ssl.HttpsURLConnection;
import java.io.*;
import java.net.URL;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import static java.nio.charset.StandardCharsets.UTF_8;
public class RecorderListener implements LiveRecorder {
private final Consumer<RecorderSettings> consumer;
private RecorderSettings settings;
private DownloadData downloadData;
private Thread liveDownloadThread;
public RecorderListener(Consumer<RecorderSettings> consumer) {
this.consumer = consumer;
}
@Override
public void pause() {
}
@Override
public void unpause() {
}
@TikTokEventObserver
private void onResponse(LiveClient liveClient, TikTokRoomDataResponseEvent event) {
settings = RecorderSettings.DEFAULT();
consumer.accept(settings);
var json = event.getLiveData().getJson();
liveClient.getLogger().info("Searching for live download url");
if (settings.getPrepareDownloadData() != null) {
downloadData = settings.getPrepareDownloadData().apply(json);
} else {
downloadData = mapToDownloadData(json);
}
if (downloadData.getDownloadLiveUrl().isEmpty()) {
throw new TikTokLiveException("Unable to find download live url!");
}
liveClient.getLogger().info("Live download url found!");
}
@TikTokEventObserver
private void onConnected(LiveClient liveClient, TikTokConnectedEvent event) {
liveDownloadThread = new Thread(() -> {
try {
var bufferSize = 1024;
var url = new URL(downloadData.getFullUrl());
HttpsURLConnection socksConnection = (HttpsURLConnection) url.openConnection();
var headers = LiveClientSettings.DefaultRequestHeaders();
for (var entry : headers.entrySet()) {
socksConnection.setRequestProperty(entry.getKey(), entry.getValue());
}
var in = new BufferedInputStream(socksConnection.getInputStream());
var path = settings.getOutputPath() + File.separator + settings.getOutputFileName();
var file = new File(path);
file.getParentFile().mkdirs();
var fileOutputStream = new FileOutputStream(file);
byte[] dataBuffer = new byte[bufferSize];
int bytesRead;
while ((bytesRead = in.read(dataBuffer, 0, bufferSize)) != -1) {
fileOutputStream.write(dataBuffer, 0, bytesRead);
}
in.close();
} catch (Exception e) {
e.printStackTrace();
}
});
liveDownloadThread.start();
}
private static void downloadUsingStream(String urlStr, String file) throws IOException {
URL url = new URL(urlStr);
BufferedInputStream bis = new BufferedInputStream(url.openStream());
FileOutputStream fis = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int count;
while ((count = bis.read(buffer, 0, 1024)) != -1)
fis.write(buffer, 0, count);
fis.close();
bis.close();
}
@TikTokEventObserver
private void onDisconnected(LiveClient liveClient, TikTokDisconnectedEvent event) {
if (isConnected())
liveDownloadThread.interrupt();
}
@TikTokEventObserver
private void onDisconnected(LiveClient liveClient, TikTokLiveEndedEvent event) {
if (isConnected())
liveDownloadThread.interrupt();
}
private int terminateFfmpeg(final Process process) {
if (!process.isAlive()) {
// ffmpeg -version, do nothing
return process.exitValue();
}
// ffmpeg -f x11grab
System.out.println("About to destroy the child process...");
try (final OutputStreamWriter out = new OutputStreamWriter(process.getOutputStream(), UTF_8)) {
out.write('q');
} catch (final IOException ioe) {
ioe.printStackTrace();
}
try {
if (!process.waitFor(5L, TimeUnit.SECONDS)) {
process.destroy();
process.waitFor();
}
return process.exitValue();
} catch (InterruptedException ie) {
System.out.println("Interrupted");
ie.printStackTrace();
Thread.currentThread().interrupt();
return -1;
}
}
private DownloadData mapToDownloadData(String json) {
var parsedJson = JsonParser.parseString(json);
var jsonObject = parsedJson.getAsJsonObject();
var streamDataJson = jsonObject.getAsJsonObject("data")
.getAsJsonObject("stream_url")
.getAsJsonObject("live_core_sdk_data")
.getAsJsonObject("pull_data")
.get("stream_data")
.getAsString();
var streamDataJsonObject = JsonParser.parseString(streamDataJson).getAsJsonObject();
var urlLink = streamDataJsonObject.getAsJsonObject("data")
.getAsJsonObject(LiveQuality.origin.name())
.getAsJsonObject("main")
.get("flv")
.getAsString();
var sessionId = streamDataJsonObject.getAsJsonObject("common")
.get("session_id")
.getAsString();
//main
//https://pull-f5-tt03.fcdn.eu.tiktokcdn.com/stage/stream-3284937501738533765.flv?session_id=136-20240109000954BF818F1B3A8E5E39E238&_webnoredir=1
//Working
//https://pull-f5-tt03.fcdn.eu.tiktokcdn.com/game/stream-3284937501738533765_sd5.flv?_session_id=136-20240109001052D91FDBC00143211020C8.1704759052997&_webnoredir=1
//https://pull-f5-tt02.fcdn.eu.tiktokcdn.com/stage/stream-3861399216374940610_uhd5.flv?_session_id=136-20240109000223D0BAA1A83974490EE630.1704758544391&_webnoredir=1
return new DownloadData(urlLink, sessionId);
}
private boolean isConnected() {
return liveDownloadThread != null && liveDownloadThread.isAlive();
}
}

View File

@@ -1,39 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.extension.recorder.impl.data;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class DownloadData {
private String downloadLiveUrl;
private String sessionId;
public String getFullUrl() {
return downloadLiveUrl + "&_webnoredir=1&session_id=" + sessionId;
}
}

View File

@@ -1,54 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.extension.recorder.impl.data;
import io.github.jwdeveloper.tiktok.extension.recorder.impl.enums.LiveQuality;
import io.github.jwdeveloper.tiktok.extension.recorder.impl.enums.LiveFormat;
import lombok.Getter;
import lombok.Setter;
import java.util.function.Function;
@Getter
@Setter
public class RecorderSettings {
private String ffmpegPath;
private String quality;
private String format;
private String outputPath;
private String outputFileName;
private Function<String,DownloadData> prepareDownloadData;
private boolean startOnConnected;
public static RecorderSettings DEFAULT() {
return new RecorderSettings();
}
public void setQuality(LiveQuality quality) {
this.quality = quality.name();
}
public void setFormat(LiveFormat format) {
this.format = format.name();
}
}

View File

@@ -1,28 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.extension.recorder.impl.enums;
public enum LiveFormat
{
MP4
}

View File

@@ -1,27 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.extension.recorder.impl.enums;
public enum LiveQuality {
origin, hd_60, ao, hd, sd, ld,uhd_60
}

View File

@@ -1,35 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.extension.recorder.impl.event;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.extension.recorder.impl.data.DownloadData;
import lombok.AllArgsConstructor;
import lombok.Data;
@AllArgsConstructor
@Data
public class TikTokLiveRecorderStartedEvent extends TikTokEvent
{
DownloadData downloadData;
}

View File

@@ -7,7 +7,7 @@
<groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>TikTokLiveJava</artifactId>
<packaging>pom</packaging>
<version>1.1.0-Release</version>
<version>1.0.14-Release</version>
<modules>
<module>API</module>
<module>Client</module>
@@ -16,9 +16,6 @@
<module>Tools-EventsCollector</module>
<module>Tools-ReadmeGenerator</module>
<module>Tools-EventsWebViewer</module>
<module>extension-recorder</module>
<module>extension-collector</module>
</modules>
<properties>