Merge pull request #89 from jwdeveloper/develop-1.8.2

Develop 1.8.2
This commit is contained in:
Jacek W
2024-07-06 17:12:01 +02:00
committed by GitHub
45 changed files with 455 additions and 249 deletions

View File

@@ -0,0 +1,18 @@
package io.github.jwdeveloper.tiktok.annotations;
/**
* HIGHEST 1
* HIGH 2
* NORMAL 3
* LOW 4
* LOWEST 5
*/
public enum Priority {
LOWEST(2), LOW(1), NORMAL(0), HIGH(-1), HIGHEST(-2);
public final int value;
Priority(int value) {
this.value = value;
}
}

View File

@@ -28,5 +28,14 @@ import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface TikTokEventObserver public @interface TikTokEventObserver
{ {
/**
* When more than one method listen for certain Event, you can specify the method priority
* @see Priority
*/
Priority priority() default Priority.NORMAL;
/**
* When true, action is invoked on a thread, from the threads pool
*/
boolean async() default false;
} }

View File

@@ -43,8 +43,6 @@ public class TikTokLinkMicBattleEvent extends TikTokHeaderEvent
true if battle is finished otherwise false true if battle is finished otherwise false
*/ */
private final boolean finished; private final boolean finished;
@Getter(AccessLevel.NONE)
private final boolean oneVsOne;
private final List<Team> teams; private final List<Team> teams;
public TikTokLinkMicBattleEvent(WebcastLinkMicBattle msg) { public TikTokLinkMicBattleEvent(WebcastLinkMicBattle msg) {
@@ -55,7 +53,6 @@ public class TikTokLinkMicBattleEvent extends TikTokHeaderEvent
if (msg.getHostTeamCount() == 2) { // 1v1 battle if (msg.getHostTeamCount() == 2) { // 1v1 battle
teams.add(new Team1v1(msg.getHostTeam(0), msg)); teams.add(new Team1v1(msg.getHostTeam(0), msg));
teams.add(new Team1v1(msg.getHostTeam(1), msg)); teams.add(new Team1v1(msg.getHostTeam(1), msg));
oneVsOne = true;
} else { // 2v2 battle } else { // 2v2 battle
if (isFinished()) { if (isFinished()) {
teams.add(new Team2v2(msg.getHostData2V2List().stream().filter(data -> data.getTeamNumber() == 1).findFirst().orElse(null), msg)); teams.add(new Team2v2(msg.getHostData2V2List().stream().filter(data -> data.getTeamNumber() == 1).findFirst().orElse(null), msg));
@@ -64,7 +61,6 @@ public class TikTokLinkMicBattleEvent extends TikTokHeaderEvent
teams.add(new Team2v2(msg.getHostTeam(0), msg.getHostTeam(1), msg)); teams.add(new Team2v2(msg.getHostTeam(0), msg.getHostTeam(1), msg));
teams.add(new Team2v2(msg.getHostTeam(2), msg.getHostTeam(3), msg)); teams.add(new Team2v2(msg.getHostTeam(2), msg.getHostTeam(3), msg));
} }
oneVsOne = false;
} }
// Info: // Info:
@@ -74,10 +70,14 @@ public class TikTokLinkMicBattleEvent extends TikTokHeaderEvent
} }
public boolean is1v1() { public boolean is1v1() {
return oneVsOne; return teams.get(0) instanceof Team1v1;
} }
public boolean is2v2() { public boolean is2v2() {
return !oneVsOne; return teams.get(0) instanceof Team2v2;
}
public boolean isTie() {
return isFinished() && teams.get(0).getTotalPoints() == teams.get(1).getTotalPoints();
} }
} }

View File

@@ -22,7 +22,12 @@
*/ */
package io.github.jwdeveloper.tiktok.data.models.battles; package io.github.jwdeveloper.tiktok.data.models.battles;
import lombok.Getter;
public abstract class Team { public abstract class Team {
/** Value >= 0 when finished otherwise -1 */
@Getter protected int totalPoints;
/** /**
* Provides a check for verifying if this team represents a 1v1 Team. * Provides a check for verifying if this team represents a 1v1 Team.
* @return true if this team is of type {@link Team1v1}, false otherwise. * @return true if this team is of type {@link Team1v1}, false otherwise.

View File

@@ -29,10 +29,7 @@ import lombok.Getter;
import java.util.*; import java.util.*;
@Getter @Getter
public class Team1v1 extends Team public class Team1v1 extends Team {
{
/** Value >= 0 when finished otherwise -1 */
private final int totalPoints;
private final int winStreak; private final int winStreak;
private final User host; private final User host;
private final List<Viewer> viewers; private final List<Viewer> viewers;

View File

@@ -30,9 +30,6 @@ import java.util.*;
@Getter @Getter
public class Team2v2 extends Team { public class Team2v2 extends Team {
/** Value >= 0 when finished otherwise -1 */
private final int totalPoints;
private final List<User> hosts; private final List<User> hosts;
private final List<Viewer> viewers; private final List<Viewer> viewers;

View File

@@ -25,16 +25,13 @@ package io.github.jwdeveloper.tiktok.listener;
import java.util.List; import java.util.List;
/** /**
* You can dynamically add or removing TikTokEventListener * Manage events listeners objects
*
* @see TikTokEventListener
*
*/ */
public interface ListenersManager public interface ListenersManager
{ {
List<TikTokEventListener> getListeners(); List<Object> getListeners();
void addListener(TikTokEventListener listener); void addListener(Object listener);
void removeListener(TikTokEventListener listener); void removeListener(Object listener);
} }

View File

@@ -40,7 +40,7 @@ import io.github.jwdeveloper.tiktok.live.LiveClient;
* {@code * {@code
* public static class CustomListener implements TikTokEventListener * public static class CustomListener implements TikTokEventListener
* { * {
* @TikTokEventObserver * @TikTokEventObserver
* public void onError(LiveClient liveClient, TikTokErrorEvent event) * public void onError(LiveClient liveClient, TikTokErrorEvent event)
* { * {
* System.out.println(event.getException().getMessage()); * System.out.println(event.getException().getMessage());
@@ -67,7 +67,9 @@ import io.github.jwdeveloper.tiktok.live.LiveClient;
* } * }
* </pre> * </pre>
*/ */
public interface TikTokEventListener //TODO I think this interface can be removed, since we are using,
{ //annotation @TikTokEventHandler to check methods that are events
@Deprecated(forRemoval = true, since = "1.8.1 (This interface is not longer needed, please remove it from your class)")
public interface TikTokEventListener {
} }

View File

@@ -24,7 +24,6 @@ package io.github.jwdeveloper.tiktok.live.builder;
import io.github.jwdeveloper.dependance.implementation.DependanceContainerBuilder; import io.github.jwdeveloper.dependance.implementation.DependanceContainerBuilder;
import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings; import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
import io.github.jwdeveloper.tiktok.listener.TikTokEventListener;
import io.github.jwdeveloper.tiktok.live.LiveClient; import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.mappers.LiveMapper; import io.github.jwdeveloper.tiktok.mappers.LiveMapper;
@@ -33,40 +32,43 @@ import java.util.function.Consumer;
public interface LiveClientBuilder extends EventsBuilder<LiveClientBuilder> { public interface LiveClientBuilder extends EventsBuilder<LiveClientBuilder> {
/** /**
* This method is triggered after default mappings are registered * This method is triggered after default mappings are registered
* It could be used to OVERRIDE behaviour of mappings and implement custom behaviour * It could be used to OVERRIDE behaviour of mappings and implement custom behaviour
* * <p>
* Be aware if for example you override WebcastGiftEvent, onGiftEvent() will not be working * Be aware if for example you override WebcastGiftEvent, onGiftEvent() will not be working
* *
* @param onCustomMappings lambda method * @param onCustomMappings lambda method
* @return * @return
*/ */
LiveClientBuilder onMapping(Consumer<LiveMapper> onCustomMappings); LiveClientBuilder mappings(Consumer<LiveMapper> onCustomMappings);
@Deprecated(forRemoval = true, since = "1.8.2 use `mappings` method instead")
LiveClientBuilder onMappings(Consumer<LiveMapper> onCustomMappings);
/** /**
* Configuration of client settings * Configuration of client settings
* @see LiveClientSettings *
* @param onConfigure * @param onConfigure
* @return * @return
* @see LiveClientSettings
*/ */
LiveClientBuilder configure(Consumer<LiveClientSettings> onConfigure); LiveClientBuilder configure(Consumer<LiveClientSettings> onConfigure);
/** /**
* @see TikTokEventListener
* Adding events listener class, its fancy way to register events without using lamda method * Adding events listener class, its fancy way to register events without using lamda method
* but actual method in class that implements TikTokEventListener * but actual method in class that implements TikTokEventListener
*
* @return * @return
*/ */
LiveClientBuilder addListener(TikTokEventListener listener); LiveClientBuilder addListener(Object listener);
/** /**
* Allows you to use own implementation of internal TikTokLive dependencies, * Allows you to use own implementation of internal TikTokLive dependencies,
* when the default implementation does not meet your needs * when the default implementation does not meet your needs
* *
*
* @param onCustomizeDependencies access to dependency container * @param onCustomizeDependencies access to dependency container
* @return * @return
*/ */
@@ -74,18 +76,19 @@ public interface LiveClientBuilder extends EventsBuilder<LiveClientBuilder> {
/** /**
* Builds new instance of the LiveClient * Builds new instance of the LiveClient
*
* @return LiveClient object * @return LiveClient object
*/ */
LiveClient build(); LiveClient build();
/** /**
* Builds new instance of the LiveClient and connects to live * Builds new instance of the LiveClient and connects to live
*
* @return LiveClient object * @return LiveClient object
*/ */
LiveClient buildAndConnect(); LiveClient buildAndConnect();
/** /**
*
* @return LiveClient object and connects to TikTok live asynchronously * @return LiveClient object and connects to TikTok live asynchronously
*/ */
CompletableFuture<LiveClient> buildAndConnectAsync(); CompletableFuture<LiveClient> buildAndConnectAsync();

View File

@@ -24,9 +24,8 @@ package io.github.jwdeveloper.tiktok.websocket;
import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData; import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData;
import io.github.jwdeveloper.tiktok.live.LiveClient; import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
public interface SocketClient { public interface LiveSocketClient {
void start(LiveConnectionData.Response webcastResponse, LiveClient tikTokLiveClient); void start(LiveConnectionData.Response webcastResponse, LiveClient tikTokLiveClient);
void stop(); void stop();
} }

View File

@@ -36,7 +36,7 @@ import io.github.jwdeveloper.tiktok.listener.*;
import io.github.jwdeveloper.tiktok.live.*; import io.github.jwdeveloper.tiktok.live.*;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import io.github.jwdeveloper.tiktok.models.ConnectionState; import io.github.jwdeveloper.tiktok.models.ConnectionState;
import io.github.jwdeveloper.tiktok.websocket.SocketClient; import io.github.jwdeveloper.tiktok.websocket.LiveSocketClient;
import lombok.Getter; import lombok.Getter;
import java.util.Base64; import java.util.Base64;
@@ -49,7 +49,7 @@ public class TikTokLiveClient implements LiveClient
{ {
private final TikTokRoomInfo roomInfo; private final TikTokRoomInfo roomInfo;
private final LiveHttpClient httpClient; private final LiveHttpClient httpClient;
private final SocketClient webSocketClient; private final LiveSocketClient webSocketClient;
private final LiveEventsHandler tikTokEventHandler; private final LiveEventsHandler tikTokEventHandler;
private final LiveClientSettings clientSettings; private final LiveClientSettings clientSettings;
private final ListenersManager listenersManager; private final ListenersManager listenersManager;
@@ -62,7 +62,7 @@ public class TikTokLiveClient implements LiveClient
GiftsManager giftsManager, GiftsManager giftsManager,
TikTokRoomInfo tikTokLiveMeta, TikTokRoomInfo tikTokLiveMeta,
LiveHttpClient tiktokHttpClient, LiveHttpClient tiktokHttpClient,
SocketClient webSocketClient, LiveSocketClient webSocketClient,
LiveEventsHandler tikTokEventHandler, LiveEventsHandler tikTokEventHandler,
LiveClientSettings clientSettings, LiveClientSettings clientSettings,
ListenersManager listenersManager, ListenersManager listenersManager,
@@ -78,17 +78,6 @@ public class TikTokLiveClient implements LiveClient
this.logger = logger; this.logger = logger;
} }
public void connectAsync(Consumer<LiveClient> onConnection) {
connectAsync().thenAccept(onConnection);
}
public CompletableFuture<LiveClient> connectAsync() {
return CompletableFuture.supplyAsync(() -> {
connect();
return this;
});
}
public void connect() { public void connect() {
try { try {
tryConnect(); tryConnect();
@@ -199,4 +188,15 @@ public class TikTokLiveClient implements LiveClient
messageHandler.handleSingleMessage(this, message); messageHandler.handleSingleMessage(this, message);
} }
public void connectAsync(Consumer<LiveClient> onConnection) {
connectAsync().thenAccept(onConnection);
}
public CompletableFuture<LiveClient> connectAsync() {
return CompletableFuture.supplyAsync(() -> {
connect();
return this;
});
}
} }

View File

@@ -52,7 +52,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
protected final LiveClientSettings clientSettings; protected final LiveClientSettings clientSettings;
protected final LiveEventsHandler eventHandler; protected final LiveEventsHandler eventHandler;
protected final List<TikTokEventListener> listeners; protected final List<Object> listeners;
protected final List<Consumer<LiveMapper>> onCustomMappings; protected final List<Consumer<LiveMapper>> onCustomMappings;
protected final List<Consumer<DependanceContainerBuilder>> onCustomDependencies; protected final List<Consumer<DependanceContainerBuilder>> onCustomDependencies;
@@ -65,17 +65,23 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
this.onCustomDependencies = new ArrayList<>(); this.onCustomDependencies = new ArrayList<>();
} }
public LiveClientBuilder onMapping(Consumer<LiveMapper> consumer) { public LiveClientBuilder mappings(Consumer<LiveMapper> consumer) {
this.onCustomMappings.add(consumer); this.onCustomMappings.add(consumer);
return this; return this;
} }
@Override
public LiveClientBuilder onMappings(Consumer<LiveMapper> onCustomMappings) {
mappings(onCustomMappings);
return this;
}
public TikTokLiveClientBuilder configure(Consumer<LiveClientSettings> onConfigure) { public TikTokLiveClientBuilder configure(Consumer<LiveClientSettings> onConfigure) {
onConfigure.accept(clientSettings); onConfigure.accept(clientSettings);
return this; return this;
} }
public TikTokLiveClientBuilder addListener(TikTokEventListener listener) { public TikTokLiveClientBuilder addListener(Object listener) {
if (listener != null) if (listener != null)
listeners.add(listener); listeners.add(listener);
return this; return this;
@@ -97,8 +103,9 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
if (clientSettings.getHostName().startsWith("@")) if (clientSettings.getHostName().startsWith("@"))
clientSettings.setHostName(clientSettings.getHostName().substring(1)); clientSettings.setHostName(clientSettings.getHostName().substring(1));
//TODO 250 Magic number
if (clientSettings.getPingInterval() < 250) if (clientSettings.getPingInterval() < 250)
throw new TikTokLiveException("Minimum allowed ping interval is 250 millseconds"); throw new TikTokLiveException("Minimum allowed ping interval is 250 milliseconds");
var httpSettings = clientSettings.getHttpSettings(); var httpSettings = clientSettings.getHttpSettings();
httpSettings.getParams().put("app_language", clientSettings.getClientLanguage()); httpSettings.getParams().put("app_language", clientSettings.getClientLanguage());
@@ -124,20 +131,19 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
//messages //messages
dependance.registerSingleton(LiveEventsHandler.class, eventHandler); dependance.registerSingleton(LiveEventsHandler.class, eventHandler);
dependance.registerSingleton(LiveMessagesHandler.class,TikTokLiveMessageHandler.class); dependance.registerSingleton(LiveMessagesHandler.class, TikTokLiveMessageHandler.class);
//listeners //listeners
dependance.registerSingletonList(TikTokEventListener.class, (e) -> listeners);
dependance.registerSingleton(ListenersManager.class, TikTokListenersManager.class); dependance.registerSingleton(ListenersManager.class, TikTokListenersManager.class);
//networking //networking
dependance.registerSingleton(HttpClientFactory.class); dependance.registerSingleton(HttpClientFactory.class);
dependance.registerSingleton(TikTokWebSocketPingingTask.class); dependance.registerSingleton(TikTokWebSocketPingingTask.class);
if (clientSettings.isOffline()) { if (clientSettings.isOffline()) {
dependance.registerSingleton(SocketClient.class, TikTokWebSocketOfflineClient.class); dependance.registerSingleton(LiveSocketClient.class, TikTokWebSocketOfflineClient.class);
dependance.registerSingleton(LiveHttpClient.class, TikTokLiveHttpOfflineClient.class); dependance.registerSingleton(LiveHttpClient.class, TikTokLiveHttpOfflineClient.class);
} else { } else {
dependance.registerSingleton(SocketClient.class, TikTokWebSocketClient.class); dependance.registerSingleton(LiveSocketClient.class, TikTokWebSocketClient.class);
dependance.registerSingleton(LiveHttpClient.class, TikTokLiveHttpClient.class); dependance.registerSingleton(LiveHttpClient.class, TikTokLiveHttpClient.class);
} }
@@ -176,8 +182,10 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
dependance.registerSingleton(LiveClient.class, TikTokLiveClient.class); dependance.registerSingleton(LiveClient.class, TikTokLiveClient.class);
onCustomDependencies.forEach(action -> action.accept(dependance)); onCustomDependencies.forEach(action -> action.accept(dependance));
var container = dependance.build(); var container = dependance.build();
var listenerManager = container.find(ListenersManager.class);
listeners.forEach(listenerManager::addListener);
return container.find(LiveClient.class); return container.find(LiveClient.class);
} }

View File

@@ -136,7 +136,7 @@ public class TikTokLiveHttpClient implements LiveHttpClient
var url = TIKTOK_URL_WEB + "api-live/user/room"; var url = TIKTOK_URL_WEB + "api-live/user/room";
var result = httpFactory.client(url) var result = httpFactory.client(url)
.withParam("uniqueId", request.getUserName()) .withParam("uniqueId", request.getUserName())
.withParam("sourceType", "54") .withParam("sourceType", "54") //MAGIC NUMBER, WHAT 54 means?
.build() .build()
.toJsonResponse(); .toJsonResponse();
@@ -218,7 +218,7 @@ public class TikTokLiveHttpClient implements LiveHttpClient
private ActionResult<HttpResponse<byte[]>> getByteResponse(String room_id) { private ActionResult<HttpResponse<byte[]>> getByteResponse(String room_id) {
HttpClientBuilder builder = httpFactory.client(TIKTOK_SIGN_API) HttpClientBuilder builder = httpFactory.client(TIKTOK_SIGN_API)
.withParam("client", "ttlive-java") .withParam("client", "ttlive-java")
.withParam("uuc", "1") .withParam("uuc", "1") //MAGIC NUMBER!
.withParam("room_id", room_id); .withParam("room_id", room_id);
if (clientSettings.getApiKey() != null) if (clientSettings.getApiKey() != null)

View File

@@ -22,19 +22,26 @@
*/ */
package io.github.jwdeveloper.tiktok.listener; package io.github.jwdeveloper.tiktok.listener;
import io.github.jwdeveloper.tiktok.annotations.Priority;
import io.github.jwdeveloper.tiktok.live.builder.EventConsumer; import io.github.jwdeveloper.tiktok.live.builder.EventConsumer;
import lombok.Data;
import lombok.Value; import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
@Value @Data
public class ListenerBindingModel public class ListenerMethodInfo {
{ private Object listener;
TikTokEventListener listener; private Class eventType;
Map<Class<?>, List<EventConsumer<?>>> events; private Method method;
private Priority priority;
private boolean async;
private EventConsumer action = (a, b) -> {
};
} }

View File

@@ -23,104 +23,135 @@
package io.github.jwdeveloper.tiktok.listener; package io.github.jwdeveloper.tiktok.listener;
import io.github.jwdeveloper.tiktok.TikTokLiveEventHandler; import io.github.jwdeveloper.dependance.api.DependanceContainer;
import io.github.jwdeveloper.tiktok.annotations.TikTokEventObserver; 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.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokEventListenerMethodException; import io.github.jwdeveloper.tiktok.exceptions.TikTokEventListenerMethodException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException; import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.live.LiveEventsHandler; import io.github.jwdeveloper.tiktok.live.LiveEventsHandler;
import io.github.jwdeveloper.tiktok.live.builder.EventConsumer; import io.github.jwdeveloper.tiktok.live.builder.EventConsumer;
import java.util.ArrayList; import java.lang.reflect.Method;
import java.util.Arrays; import java.util.*;
import java.util.HashMap; import java.util.concurrent.ExecutorService;
import java.util.List; import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
public class TikTokListenersManager implements ListenersManager { public class TikTokListenersManager implements ListenersManager {
private final LiveEventsHandler eventObserver;
private final List<ListenerBindingModel> bindingModels;
public TikTokListenersManager(List<TikTokEventListener> listeners, LiveEventsHandler tikTokEventHandler) { private final Map<Object, List<ListenerMethodInfo>> listeners;
this.eventObserver = tikTokEventHandler; private final LiveEventsHandler eventsHandler;
this.bindingModels = new ArrayList<>(listeners.size()); private final ExecutorService executorService;
for (var listener : listeners) { private final DependanceContainer dependanceContainer;
addListener(listener);
}
public TikTokListenersManager(LiveEventsHandler tikTokEventHandler,
DependanceContainer dependanceContainer) {
this.eventsHandler = tikTokEventHandler;
this.dependanceContainer = dependanceContainer;
this.listeners = new HashMap<>();
executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
} }
@Override @Override
public List<TikTokEventListener> getListeners() { public List<Object> getListeners() {
return bindingModels.stream().map(ListenerBindingModel::getListener).toList(); return listeners.keySet().stream().toList();
} }
@Override @Override
public void addListener(TikTokEventListener listener) { public void addListener(Object listener) {
var alreadyExists = bindingModels.stream().filter(e -> e.getListener() == listener).findAny(); if (listeners.containsKey(listener)) {
if (alreadyExists.isPresent()) {
throw new TikTokLiveException("Listener " + listener.getClass() + " has already been registered"); throw new TikTokLiveException("Listener " + listener.getClass() + " has already been registered");
} }
var bindingModel = bindToEvents(listener);
for (var eventEntrySet : bindingModel.getEvents().entrySet()) { var methodsInfo = getMethodsInfo(listener);
var eventType = eventEntrySet.getKey(); for (var methodInfo : methodsInfo) {
for (var methods : eventEntrySet.getValue()) { eventsHandler.subscribe(methodInfo.getEventType(), methodInfo.getAction());
eventObserver.subscribe(eventType, methods);
}
} }
bindingModels.add(bindingModel); listeners.put(listener, methodsInfo);
} }
@Override @Override
public void removeListener(TikTokEventListener listener) { public void removeListener(Object listener) {
var optional = bindingModels.stream().filter(e -> e.getListener() == listener).findAny(); if (!listeners.containsKey(listener)) {
if (optional.isEmpty()) {
return; return;
} }
var methodsInfo = listeners.get(listener);
var bindingModel = optional.get(); for (var methodInfo : methodsInfo) {
eventsHandler.unsubscribe(methodInfo.getEventType(), methodInfo.getAction());
for (var eventEntrySet : bindingModel.getEvents().entrySet()) {
var eventType = eventEntrySet.getKey();
for (var methods : eventEntrySet.getValue()) {
eventObserver.unsubscribe(eventType, methods);
}
} }
bindingModels.remove(optional.get()); listeners.remove(listener);
} }
private ListenerBindingModel bindToEvents(TikTokEventListener listener) { private List<ListenerMethodInfo> getMethodsInfo(Object listener) {
return Arrays.stream(listener.getClass().getDeclaredMethods())
.filter(e -> e.isAnnotationPresent(TikTokEventObserver.class))
.filter(e -> e.getParameterCount() >= 1)
.map(method -> getSingleMethodInfo(listener, method))
.sorted(Comparator.comparingInt(a -> a.getPriority().value))
.toList();
}
var clazz = listener.getClass(); private ListenerMethodInfo getSingleMethodInfo(Object listener, Method method) {
var methods = Arrays.stream(clazz.getDeclaredMethods()).filter(m ->
m.getParameterCount() == 2 &&
m.isAnnotationPresent(TikTokEventObserver.class)).toList();
var eventsMap = new HashMap<Class<?>, List<EventConsumer<?>>>();
for (var method : methods) {
var liveclientClass = method.getParameterTypes()[0];
var eventClass = method.getParameterTypes()[1];
if (!LiveClient.class.isAssignableFrom(liveclientClass) && !liveclientClass.equals(LiveClient.class)) { method.setAccessible(true);
throw new TikTokEventListenerMethodException("Method " + method.getName() + "() 1st parameter must be instance of " + LiveClient.class.getName() var annotation = method.getAnnotation(TikTokEventObserver.class);
+ " | Invalid parameter class: "+liveclientClass.getName()); var tiktokEventType = Arrays.stream(method.getParameterTypes())
} .filter(TikTokEvent.class::isAssignableFrom)
.findFirst()
.orElseThrow(() -> new TikTokEventListenerMethodException("Method " + method.getName() + "() must have only one parameter that inherits from class " + TikTokEvent.class.getName()));
if (!TikTokEvent.class.isAssignableFrom(eventClass) && !eventClass.equals(TikTokEvent.class)) { var info = new ListenerMethodInfo();
throw new TikTokEventListenerMethodException("Method " + method.getName() + "() 2nd parameter must be instance of " + TikTokEvent.class.getName() info.setListener(listener);
+ " | Invalid parameter class: "+eventClass.getName()); info.setAsync(annotation.async());
} info.setPriority(annotation.priority());
info.setEventType(tiktokEventType);
info.setAction(createAction(listener, method, tiktokEventType));
EventConsumer eventMethodRef = (liveClient, event) -> if (info.isAsync()) {
var action = info.getAction();
info.setAction((liveClient, event) ->
{ {
try { executorService.submit(() ->
method.setAccessible(true); {
method.invoke(listener, liveClient, event); action.onEvent(liveClient, event);
} catch (Exception e) { });
throw new TikTokEventListenerMethodException(e); });
}
};
eventsMap.computeIfAbsent(eventClass, (a) -> new ArrayList<>()).add(eventMethodRef);
} }
return new ListenerBindingModel(listener, eventsMap); return info;
}
//I know, implementation of this might look complicated
private EventConsumer createAction(Object listener, Method method, Class tiktokEventType) {
AtomicReference<Object> eventObjectRef = new AtomicReference<>();
var methodContainer = dependanceContainer.createChildContainer()
.configure(configuration ->
{
//Modifying container, so it returns TikTokEvent object instance,
//when TikTokEvent type is encountered in the methods parameters
configuration.onInjection(injectionEvent ->
{
if (injectionEvent.input().isAssignableFrom(tiktokEventType)) {
return eventObjectRef.get();
}
return injectionEvent.output();
});
})
.build();
return (liveClient, event) ->
{
try {
eventObjectRef.set(event);
//Creating list of input objects based on method parameters
//Objects are received from container
var parameters = methodContainer.resolveParameters(method);
method.invoke(listener, parameters);
} catch (Exception e) {
eventsHandler.publish(liveClient, new TikTokErrorEvent(new TikTokEventListenerMethodException(e)));
}
};
} }
} }

View File

@@ -53,7 +53,7 @@ public class TikTokGenericEventMapper {
private final Map<Class<?>, Method> methodCache; private final Map<Class<?>, Method> methodCache;
private final Map<TypePair, Constructor<?>> constructorCache; private final Map<TypePair, Constructor<?>> constructorCache;
private static final String PARSE_FIELD = "parseFrom";
public TikTokGenericEventMapper() { public TikTokGenericEventMapper() {
this.methodCache = new HashMap<>(); this.methodCache = new HashMap<>();
this.constructorCache = new HashMap<>(); this.constructorCache = new HashMap<>();
@@ -75,7 +75,7 @@ public class TikTokGenericEventMapper {
public Method getParsingMethod(Class<?> input) throws RuntimeException { public Method getParsingMethod(Class<?> input) throws RuntimeException {
return methodCache.computeIfAbsent(input, aClass -> { return methodCache.computeIfAbsent(input, aClass -> {
try { try {
return aClass.getDeclaredMethod("parseFrom", byte[].class); return aClass.getDeclaredMethod(PARSE_FIELD, byte[].class);
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@@ -34,11 +34,12 @@ public class TikTokLiveMapper implements LiveMapper {
private final Map<String, TikTokLiveMapperModel> mappers; private final Map<String, TikTokLiveMapperModel> mappers;
private final LiveMapperHelper mapperUtils; private final LiveMapperHelper mapperUtils;
private final TikTokLiveMapperModel globalMapperModel; private final TikTokLiveMapperModel globalMapperModel;
private static final String GLOBAL_MESSAGE = "GLOBAL MESSAGE";
public TikTokLiveMapper(LiveMapperHelper mapperUtils) { public TikTokLiveMapper(LiveMapperHelper mapperUtils) {
this.mappers = new HashMap<>(); this.mappers = new HashMap<>();
this.mapperUtils = mapperUtils; this.mapperUtils = mapperUtils;
this.globalMapperModel = new TikTokLiveMapperModel("any message"); this.globalMapperModel = new TikTokLiveMapperModel(GLOBAL_MESSAGE);
} }
@Override @Override

View File

@@ -29,6 +29,9 @@ import io.github.jwdeveloper.tiktok.utils.ProtoBufferObject;
import io.github.jwdeveloper.tiktok.utils.ProtocolUtils; import io.github.jwdeveloper.tiktok.utils.ProtocolUtils;
public class TikTokLiveMapperHelper implements LiveMapperHelper { public class TikTokLiveMapperHelper implements LiveMapperHelper {
private static final String PACKAGE_PREFIX = "io.github.jwdeveloper.tiktok.messages.webcast.";
private final TikTokGenericEventMapper genericMapper; private final TikTokGenericEventMapper genericMapper;
public TikTokLiveMapperHelper(TikTokGenericEventMapper genericMapper) { public TikTokLiveMapperHelper(TikTokGenericEventMapper genericMapper) {
@@ -39,6 +42,7 @@ public class TikTokLiveMapperHelper implements LiveMapperHelper {
public <T extends GeneratedMessageV3> T bytesToWebcastObject(byte[] bytes, Class<T> messageClass) { public <T extends GeneratedMessageV3> T bytesToWebcastObject(byte[] bytes, Class<T> messageClass) {
try { try {
var parsingMethod = genericMapper.getParsingMethod(messageClass); var parsingMethod = genericMapper.getParsingMethod(messageClass);
//NULL is passed, since Parsing method is Static
var sourceObject = parsingMethod.invoke(null, bytes); var sourceObject = parsingMethod.invoke(null, bytes);
return (T) sourceObject; return (T) sourceObject;
} catch (Exception e) { } catch (Exception e) {
@@ -49,7 +53,7 @@ public class TikTokLiveMapperHelper implements LiveMapperHelper {
@Override @Override
public Object bytesToWebcastObject(byte[] bytes, String messageName) { public Object bytesToWebcastObject(byte[] bytes, String messageName) {
try { try {
var packageName = "io.github.jwdeveloper.tiktok.messages.webcast." + messageName; var packageName = PACKAGE_PREFIX + messageName;
var clazz = Class.forName(packageName); var clazz = Class.forName(packageName);
return bytesToWebcastObject(bytes, (Class<? extends GeneratedMessageV3>) clazz); return bytesToWebcastObject(bytes, (Class<? extends GeneratedMessageV3>) clazz);
} catch (Exception e) { } catch (Exception e) {
@@ -60,7 +64,7 @@ public class TikTokLiveMapperHelper implements LiveMapperHelper {
@Override @Override
public boolean isMessageHasProtoClass(String messageName) { public boolean isMessageHasProtoClass(String messageName) {
try { try {
var packageName = "io.github.jwdeveloper.tiktok.messages.webcast." + messageName; var packageName = PACKAGE_PREFIX + messageName;
Class.forName(packageName); Class.forName(packageName);
return true; return true;
} catch (Exception e) { } catch (Exception e) {

View File

@@ -38,7 +38,6 @@ import java.util.*;
public class TikTokGiftEventHandler { public class TikTokGiftEventHandler {
private final Map<Long, WebcastGiftMessage> giftsMessages; private final Map<Long, WebcastGiftMessage> giftsMessages;
private final TikTokRoomInfo tikTokRoomInfo; private final TikTokRoomInfo tikTokRoomInfo;
private final GiftsManager giftsManager; private final GiftsManager giftsManager;
public TikTokGiftEventHandler(GiftsManager giftsManager, TikTokRoomInfo tikTokRoomInfo) { public TikTokGiftEventHandler(GiftsManager giftsManager, TikTokRoomInfo tikTokRoomInfo) {

View File

@@ -22,8 +22,6 @@
*/ */
package io.github.jwdeveloper.tiktok.websocket; package io.github.jwdeveloper.tiktok.websocket;
import io.github.jwdeveloper.dependance.injector.api.containers.Container;
import io.github.jwdeveloper.tiktok.*;
import io.github.jwdeveloper.tiktok.data.dto.ProxyData; import io.github.jwdeveloper.tiktok.data.dto.ProxyData;
import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData; import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData;
import io.github.jwdeveloper.tiktok.data.settings.*; import io.github.jwdeveloper.tiktok.data.settings.*;
@@ -38,7 +36,7 @@ import java.net.Proxy;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.HashMap; import java.util.HashMap;
public class TikTokWebSocketClient implements SocketClient { public class TikTokWebSocketClient implements LiveSocketClient {
private final LiveClientSettings clientSettings; private final LiveClientSettings clientSettings;
private final LiveMessagesHandler messageHandler; private final LiveMessagesHandler messageHandler;
private final LiveEventsHandler tikTokEventHandler; private final LiveEventsHandler tikTokEventHandler;

View File

@@ -23,7 +23,6 @@
package io.github.jwdeveloper.tiktok.websocket; package io.github.jwdeveloper.tiktok.websocket;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import io.github.jwdeveloper.tiktok.*;
import io.github.jwdeveloper.tiktok.data.events.*; import io.github.jwdeveloper.tiktok.data.events.*;
import io.github.jwdeveloper.tiktok.exceptions.TikTokProtocolBufferException; import io.github.jwdeveloper.tiktok.exceptions.TikTokProtocolBufferException;
import io.github.jwdeveloper.tiktok.live.LiveClient; import io.github.jwdeveloper.tiktok.live.LiveClient;
@@ -40,9 +39,9 @@ import java.util.*;
public class TikTokWebSocketListener extends WebSocketClient { public class TikTokWebSocketListener extends WebSocketClient {
private final LiveMessagesHandler messageHandler; private final LiveMessagesHandler messagesHandler;
private final LiveEventsHandler tikTokEventHandler; private final LiveEventsHandler eventHandler;
private final LiveClient tikTokLiveClient; private final LiveClient liveClient;
public TikTokWebSocketListener(URI serverUri, public TikTokWebSocketListener(URI serverUri,
Map<String, String> httpHeaders, Map<String, String> httpHeaders,
@@ -51,9 +50,9 @@ public class TikTokWebSocketListener extends WebSocketClient {
LiveEventsHandler tikTokEventHandler, LiveEventsHandler tikTokEventHandler,
LiveClient tikTokLiveClient) { LiveClient tikTokLiveClient) {
super(serverUri, new Draft_6455(), httpHeaders, connectTimeout); super(serverUri, new Draft_6455(), httpHeaders, connectTimeout);
this.messageHandler = messageHandler; this.messagesHandler = messageHandler;
this.tikTokEventHandler = tikTokEventHandler; this.eventHandler = tikTokEventHandler;
this.tikTokLiveClient = tikTokLiveClient; this.liveClient = tikTokLiveClient;
} }
@Override @Override
@@ -61,7 +60,7 @@ public class TikTokWebSocketListener extends WebSocketClient {
try { try {
handleBinary(bytes.array()); handleBinary(bytes.array());
} catch (Exception e) { } catch (Exception e) {
tikTokEventHandler.publish(tikTokLiveClient, new TikTokErrorEvent(e)); eventHandler.publish(liveClient, new TikTokErrorEvent(e));
} }
if (isOpen()) { if (isOpen()) {
sendPing(); sendPing();
@@ -85,12 +84,12 @@ public class TikTokWebSocketListener extends WebSocketClient {
this.send(pushFrameBuilder.build().toByteArray()); this.send(pushFrameBuilder.build().toByteArray());
} }
} }
messageHandler.handle(tikTokLiveClient, webcastResponse); messagesHandler.handle(liveClient, webcastResponse);
} }
@Override @Override
public void onOpen(ServerHandshake serverHandshake) { public void onOpen(ServerHandshake serverHandshake) {
tikTokEventHandler.publish(tikTokLiveClient, new TikTokConnectedEvent()); eventHandler.publish(liveClient, new TikTokConnectedEvent());
if (isOpen()) { if (isOpen()) {
sendPing(); sendPing();
} }
@@ -98,13 +97,13 @@ public class TikTokWebSocketListener extends WebSocketClient {
@Override @Override
public void onClose(int code, String reason, boolean remote) { public void onClose(int code, String reason, boolean remote) {
tikTokEventHandler.publish(tikTokLiveClient, new TikTokDisconnectedEvent(reason)); eventHandler.publish(liveClient, new TikTokDisconnectedEvent(reason));
tikTokLiveClient.disconnect(); liveClient.disconnect();
} }
@Override @Override
public void onError(Exception error) { public void onError(Exception error) {
tikTokEventHandler.publish(tikTokLiveClient, new TikTokErrorEvent(error)); eventHandler.publish(liveClient, new TikTokErrorEvent(error));
if (isOpen()) { if (isOpen()) {
sendPing(); sendPing();
} }
@@ -132,6 +131,7 @@ public class TikTokWebSocketListener extends WebSocketClient {
@Override @Override
public void onMessage(String s) { public void onMessage(String s) {
// System.err.println(s); //TODO we are not using this method, however I wounder if there might be
//so messages that are send as String from TikTok, for example some Jsons
} }
} }

View File

@@ -22,14 +22,13 @@
*/ */
package io.github.jwdeveloper.tiktok.websocket; package io.github.jwdeveloper.tiktok.websocket;
import io.github.jwdeveloper.tiktok.TikTokLiveEventHandler;
import io.github.jwdeveloper.tiktok.data.events.TikTokConnectedEvent; import io.github.jwdeveloper.tiktok.data.events.TikTokConnectedEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokDisconnectedEvent; import io.github.jwdeveloper.tiktok.data.events.TikTokDisconnectedEvent;
import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData; import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData;
import io.github.jwdeveloper.tiktok.live.LiveClient; import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.live.LiveEventsHandler; import io.github.jwdeveloper.tiktok.live.LiveEventsHandler;
public class TikTokWebSocketOfflineClient implements SocketClient { public class TikTokWebSocketOfflineClient implements LiveSocketClient {
private final LiveEventsHandler handler; private final LiveEventsHandler handler;
private LiveClient liveClient; private LiveClient liveClient;

View File

@@ -0,0 +1,51 @@
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.dependance.implementation.DependanceContainerBuilder;
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.data.events.control.TikTokConnectingEvent;
import io.github.jwdeveloper.tiktok.data.events.control.TikTokPreConnectionEvent;
import io.github.jwdeveloper.tiktok.data.events.http.TikTokRoomDataResponseEvent;
import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomInfoEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
import io.github.jwdeveloper.tiktok.models.ConnectionState;
import org.junit.Assert;
import org.junit.Test;
public class TikTokLiveClientTests extends TikTokTestBase {
@Override
public void onBeforeEachTest(LiveClientBuilder liveClientBuilder,
DependanceContainerBuilder containerBuilder) {
}
@Test
public void shouldThrownWhenAlreadyConnected() {
roomInfoMock().setConnectionState(ConnectionState.CONNECTED);
Assert.assertThrows(TikTokLiveException.class, () ->
{
liveClient().connect();
});
Assert.assertEquals(ConnectionState.DISCONNECTED, roomInfoMock().getConnectionState());
AssertEvents(
TikTokErrorEvent.class,
TikTokDisconnectedEvent.class
);
}
@Test
public void shouldConnect() {
liveClient().connect();
Assert.assertEquals(ConnectionState.CONNECTED, roomInfoMock().getConnectionState());
AssertEvents(
TikTokConnectingEvent.class,
TikTokRoomDataResponseEvent.class,
TikTokPreConnectionEvent.class,
TikTokConnectedEvent.class,
TikTokRoomInfoEvent.class);
}
}

View File

@@ -0,0 +1,53 @@
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.dependance.implementation.DependanceContainerBuilder;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.live.LiveEventsHandler;
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
import io.github.jwdeveloper.tiktok.mocks.EventsHandlerMock;
import lombok.Getter;
import lombok.experimental.Accessors;
import org.junit.Before;
/**
* Base class for the unit tests
*/
@Getter
@Accessors(fluent = true)
public abstract class TikTokTestBase {
private LiveClient liveClient;
private EventsHandlerMock eventsHandlerMock;
private TikTokRoomInfo roomInfoMock;
public void AssertEvents(Class<? extends TikTokEvent>... events) {
eventsHandlerMock.assertEvents(events);
}
@Before
public void setup() {
var builder = TikTokLive.newClient("test");
eventsHandlerMock = new EventsHandlerMock();
roomInfoMock = new TikTokRoomInfo();
roomInfoMock.setHostName("test");
liveClient = builder
.configure(liveClientSettings ->
{
liveClientSettings.setOffline(true);
liveClientSettings.setFetchGifts(false);
})
.customize(containerBuilder ->
{
containerBuilder.registerSingleton(LiveEventsHandler.class, eventsHandlerMock);
containerBuilder.registerSingleton(TikTokRoomInfo.class, roomInfoMock);
onBeforeEachTest(builder, containerBuilder);
}).build();
}
public abstract void onBeforeEachTest(LiveClientBuilder liveClientBuilder, DependanceContainerBuilder containerBuilder);
}

View File

@@ -22,7 +22,10 @@
*/ */
package io.github.jwdeveloper.tiktok.listener; package io.github.jwdeveloper.tiktok.listener;
import io.github.jwdeveloper.dependance.Dependance;
import io.github.jwdeveloper.dependance.api.DependanceContainer;
import io.github.jwdeveloper.tiktok.TikTokLiveEventHandler; import io.github.jwdeveloper.tiktok.TikTokLiveEventHandler;
import io.github.jwdeveloper.tiktok.annotations.Priority;
import io.github.jwdeveloper.tiktok.annotations.TikTokEventObserver; import io.github.jwdeveloper.tiktok.annotations.TikTokEventObserver;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent; import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent;
@@ -44,27 +47,34 @@ class TikTokListenersManagerTest {
private TikTokLiveEventHandler eventObserver; private TikTokLiveEventHandler eventObserver;
private TikTokListenersManager tikTokListenersManager; private TikTokListenersManager tikTokListenersManager;
private DependanceContainer dependanceContainer;
private LiveClient liveClient;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
eventObserver = Mockito.mock(TikTokLiveEventHandler.class);
List<TikTokEventListener> listeners = new ArrayList<>(); liveClient = Mockito.mock(LiveClient.class);
tikTokListenersManager = new TikTokListenersManager(listeners, eventObserver); eventObserver = new TikTokLiveEventHandler();
dependanceContainer = Dependance.newContainer()
.registerSingleton(LiveClient.class, liveClient)
.build();
tikTokListenersManager = new TikTokListenersManager(eventObserver, dependanceContainer);
} }
@Test @Test
void addListener() { void addListener() {
TikTokEventListener listener =new TikTokEventListenerTest(); Object listener = new TikTokEventListenerTest();
tikTokListenersManager.addListener(listener); tikTokListenersManager.addListener(listener);
List<TikTokEventListener> listeners = tikTokListenersManager.getListeners(); List<Object> listeners = tikTokListenersManager.getListeners();
assertEquals(1, listeners.size()); assertEquals(1, listeners.size());
assertSame(listener, listeners.get(0)); assertSame(listener, listeners.get(0));
} }
@Test @Test
void addListener_alreadyRegistered_throwsException() { void addListener_alreadyRegistered_throwsException() {
TikTokEventListener listener = new TikTokEventListenerTest(); Object listener = new TikTokEventListenerTest();
tikTokListenersManager.addListener(listener); tikTokListenersManager.addListener(listener);
Exception exception = assertThrows(TikTokLiveException.class, () -> { Exception exception = assertThrows(TikTokLiveException.class, () -> {
@@ -76,39 +86,56 @@ class TikTokListenersManagerTest {
@Test @Test
void removeListener() { void removeListener() {
TikTokEventListener listener = new TikTokEventListenerTest(); Object listener = new TikTokEventListenerTest();
tikTokListenersManager.addListener(listener); tikTokListenersManager.addListener(listener);
tikTokListenersManager.removeListener(listener); tikTokListenersManager.removeListener(listener);
List<TikTokEventListener> listeners = tikTokListenersManager.getListeners(); List<Object> listeners = tikTokListenersManager.getListeners();
assertTrue(listeners.isEmpty()); assertTrue(listeners.isEmpty());
} }
@Test
public void shouldTriggerEvents() {
Object listener = new TikTokEventListenerTest();
tikTokListenersManager.addListener(listener);
var fakeGiftEvent = TikTokGiftEvent.of("TestRosa", 1, 1);
eventObserver.publish(liveClient, fakeGiftEvent);
}
@Test @Test
void removeListener_notRegistered_doesNotThrow() { void removeListener_notRegistered_doesNotThrow() {
TikTokEventListener listener = new TikTokEventListenerTest(); Object listener = new TikTokEventListenerTest();
assertDoesNotThrow(() -> tikTokListenersManager.removeListener(listener)); assertDoesNotThrow(() -> tikTokListenersManager.removeListener(listener));
} }
public static class TikTokEventListenerTest implements TikTokEventListener public static class TikTokEventListenerTest {
{
@TikTokEventObserver @TikTokEventObserver
public void onJoin(LiveClient client, TikTokJoinEvent joinEvent) public void onJoin(LiveClient client, TikTokJoinEvent joinEvent) {
{ System.out.println("Hello from on join" + client + " " + joinEvent);
} }
@TikTokEventObserver @TikTokEventObserver(priority = Priority.LOWEST)
public void onGift(LiveClient client, TikTokGiftEvent giftMessageEvent) public void onGift(LiveClient client, TikTokGiftEvent giftMessageEvent) {
{ System.out.println("Hello from onGift lowest priority" + client + " " + giftMessageEvent);
} }
@TikTokEventObserver @TikTokEventObserver(priority = Priority.NORMAL)
public void onEvent(LiveClient client, TikTokEvent event) public void onGift2(LiveClient client, TikTokGiftEvent giftMessageEvent) {
{ System.out.println("Hello from onGift normal priority " + client + " " + giftMessageEvent);
}
@TikTokEventObserver(priority = Priority.HIGHEST)
public void onGift3(LiveClient client, TikTokGiftEvent giftMessageEvent) {
System.out.println("Hello from onGift highest priority " + client + " " + giftMessageEvent);
}
@TikTokEventObserver(async = true)
public void onEvent(LiveClient client, TikTokEvent event) {
System.out.println("Hello from onEvent im running on the thread " + Thread.currentThread().getName());
} }
} }
} }

View File

@@ -0,0 +1,46 @@
package io.github.jwdeveloper.tiktok.mocks;
import io.github.jwdeveloper.tiktok.TikTokLiveEventHandler;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import java.util.ArrayList;
import java.util.List;
/**
* Cache published events,
*/
public class EventsHandlerMock extends TikTokLiveEventHandler {
private final List<TikTokEvent> publishedEvents = new ArrayList<TikTokEvent>();
@Override
public void publish(LiveClient tikTokLiveClient, TikTokEvent tikTokEvent) {
super.publish(tikTokLiveClient, tikTokEvent);
publishedEvents.add(tikTokEvent);
}
@SafeVarargs
public final void assertEvents(Class<? extends TikTokEvent>... events) {
if (events.length == 0 && !publishedEvents.isEmpty()) {
var classNames = publishedEvents.stream()
.map(e -> e.getClass().getSimpleName())
.toList();
var invokedEvents = String.join("\n", classNames);
throw new IllegalArgumentException("Not events should be invoked but there was: \n" + invokedEvents);
}
for (var i = 0; i < events.length; i++) {
var expectedEvent = events[i];
var invokedEvent = publishedEvents.get(i);
if (expectedEvent.equals(invokedEvent.getClass())) {
continue;
}
throw new RuntimeException("Expected event was " + expectedEvent + " but acctuall was " + invokedEvent.getClass());
}
}
}

View File

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

View File

@@ -37,9 +37,6 @@ public class ConnectionExample {
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
showLogo(); showLogo();
var gifts = TikTokLive.gifts();
TikTokLive.newClient(ConnectionExample.TIKTOK_HOSTNAME) TikTokLive.newClient(ConnectionExample.TIKTOK_HOSTNAME)
.configure(clientSettings -> .configure(clientSettings ->
{ {

View File

@@ -30,7 +30,7 @@ public class CustomMappingExample {
public static void main(String[] args) { public static void main(String[] args) {
TikTokLive.newClient("saszareznikow") TikTokLive.newClient("saszareznikow")
.onMapping(mapper -> .mappings(mapper ->
{ {
mapper.forMessage(WebcastChatMessage.class) mapper.forMessage(WebcastChatMessage.class)
.onBeforeMapping((inputBytes, messageName, mapperHelper) -> .onBeforeMapping((inputBytes, messageName, mapperHelper) ->
@@ -56,7 +56,6 @@ public class CustomMappingExample {
System.out.println("onAfter mapping, " + source.getClass().getSimpleName() + " was mapped to " + events.size() + " events"); System.out.println("onAfter mapping, " + source.getClass().getSimpleName() + " was mapped to " + events.size() + " events");
return events; return events;
}); });
/* /*
There might be cast that we don't have Webcast class for incoming message from TikTok There might be cast that we don't have Webcast class for incoming message from TikTok
`mapperHelper.bytesToProtoBufferStructure` but you can still investigate message structure `mapperHelper.bytesToProtoBufferStructure` but you can still investigate message structure

View File

@@ -29,8 +29,6 @@ public class GiftsExample {
public static void main(String[] args) { public static void main(String[] args) {
var giftsManager = TikTokLive.gifts(); var giftsManager = TikTokLive.gifts();
var giftsList = giftsManager.toList(); var giftsList = giftsManager.toList();
for (var gift : giftsList) { for (var gift : giftsList) {
System.out.println("Gift: " + gift); System.out.println("Gift: " + gift);

View File

@@ -28,21 +28,18 @@ import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent; import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokLikeEvent; import io.github.jwdeveloper.tiktok.data.events.social.TikTokLikeEvent;
import io.github.jwdeveloper.tiktok.listener.TikTokEventListener;
import io.github.jwdeveloper.tiktok.live.LiveClient; import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.utils.ConsoleColors; import io.github.jwdeveloper.tiktok.utils.ConsoleColors;
import java.io.IOException; import java.io.IOException;
public class ListenerExample public class ListenerExample {
{
// <code> // <code>
/** /**
* * Listeners are an alternative way of handling events.
* Listeners are an alternative way of handling events. * I would to suggest to use then when logic of handing event
* I would to suggest to use then when logic of handing event * is more complex
* is more complex
*
*/ */
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
showLogo(); showLogo();
@@ -55,24 +52,21 @@ public class ListenerExample
} }
/** /**
* * Method must meet 2 requirements to be detected
* Method in TikTokEventListener should meet 4 requirements to be detected * - must have @TikTokEventObserver annotation
* - must have @TikTokEventObserver annotation * - must have 1 parameter of type that extending TikTokEvent
* - must have 2 parameters
* - first parameter must be LiveClient
* - second must be class that extending TikTokEvent
*/ */
public static class CustomListener implements TikTokEventListener { public static class CustomListener {
@TikTokEventObserver @TikTokEventObserver
public void onLike(LiveClient liveClient, TikTokLikeEvent event) { public void onLike(TikTokLikeEvent event) {
System.out.println(event.toString()); System.out.println(event.toString());
} }
@TikTokEventObserver @TikTokEventObserver
public void onError(LiveClient liveClient, TikTokErrorEvent event) { public void onError(TikTokErrorEvent event, LiveClient liveClient) {
// event.getException().printStackTrace(); // event.getException().printStackTrace();
} }
@TikTokEventObserver @TikTokEventObserver
@@ -103,9 +97,8 @@ public class ListenerExample
} }
// </code> // </code>
private static void showLogo() private static void showLogo() {
{ System.out.println(ConsoleColors.GREEN + """
System.out.println(ConsoleColors.GREEN+"""
_____ _ _ _____ _ _ _ \s _____ _ _ _____ _ _ _ \s
|_ _(_) | _|_ _|__ | | _| | (_)_ _____\s |_ _(_) | _|_ _|__ | | _| | (_)_ _____\s

View File

@@ -32,6 +32,7 @@ public class RecorderExample {
public static void main(String[] args) { public static void main(String[] args) {
TikTokLive.newClient("bangbetmenygy") TikTokLive.newClient("bangbetmenygy")
.configure(liveClientSettings -> .configure(liveClientSettings ->
{ {

7
future.md Normal file
View File

@@ -0,0 +1,7 @@
Future plans:
[ ] Publish project to Maven repository instead of JITPACK
[ ] Dynamically generates readme.md when new version is released
[ ] Covers all possible classes from Client module with unit test
[ ] Add tests check to GitHub publish action
[ ] Generates documentation based on API module and publish it with GitHub pages

View File

@@ -11,7 +11,7 @@
<modules> <modules>
<module>API</module> <module>API</module>
<module>Client</module> <module>Client</module>
<module>Examples</module> <module>examples</module>
<module>tools-readme</module> <module>tools-readme</module>
<module>extension-recorder</module> <module>extension-recorder</module>

View File

@@ -34,12 +34,6 @@
<version>2.13.0</version> <version>2.13.0</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>com.github.jwdeveloper.Descrabble</groupId>
<artifactId>Descrabble-Full</artifactId>
<version>0.0.11-Release</version>
<scope>compile</scope>
</dependency>
<dependency> <dependency>
<groupId>org.reflections</groupId> <groupId>org.reflections</groupId>
<artifactId>reflections</artifactId> <artifactId>reflections</artifactId>

View File

@@ -1,17 +0,0 @@
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.descrabble.api.DescriptionDecorator;
import io.github.jwdeveloper.descrabble.api.elements.Element;
import io.github.jwdeveloper.descrabble.api.elements.ElementFactory;
public class EventsDecorator implements DescriptionDecorator {
@Override
public void decorate(Element root, ElementFactory factory)
{
}
}

View File

@@ -41,7 +41,6 @@ public class EventsInfoGenerator {
System.out.println(res); System.out.println(res);
} }
public String run() { public String run() {
var events = getEventsDtos(); var events = getEventsDtos();
var builder = new StringBuilder(); var builder = new StringBuilder();

View File

@@ -1,8 +1,6 @@
package io.github.jwdeveloper.tiktok; package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.descrabble.api.DescriptionGenerator;
import io.github.jwdeveloper.descrabble.framework.Descrabble;
import io.github.jwdeveloper.descrabble.plugin.github.DescrabbleGithub;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import java.io.File; import java.io.File;
@@ -23,16 +21,6 @@ public class Main
var output = System.getProperty("user.dir"); var output = System.getProperty("user.dir");
DescriptionGenerator generator = Descrabble.create()
.withTemplate(targetFile)
.withVariable("version", version)
.withDecorator(new EventsDecorator())
.withPlugin(DescrabbleGithub.plugin("README.md"))
.build();
generator.generate(output);
targetFile.delete();
inputStream.close();
} }
} }

View File

@@ -27,7 +27,6 @@ public class ReadmeGenerator {
public static void main(String[] args) { public static void main(String[] args) {
var generator = new ReadmeGenerator(); var generator = new ReadmeGenerator();
generator.generate(); generator.generate();
} }
public void generate() { public void generate() {

View File

@@ -88,8 +88,6 @@ Do you prefer other programming languages?
{{if item is 2}} {{if item is 2}}
my name is {{item.name}}
{{else}} {{else}}
{{end}} {{end}}
@@ -98,7 +96,6 @@ my name is {{item.name}}
<br> <br>
## Listeners ## Listeners
```java ```java
{{listener-content}} {{listener-content}}
``` ```