Merge pull request #34 from jwdeveloper/develop-1-0-10

Develop 1 0 10
This commit is contained in:
Jacek W
2023-12-19 13:48:47 +01:00
committed by GitHub
95 changed files with 3200 additions and 1653 deletions

View File

@@ -56,7 +56,7 @@ public class ClientSettings {
* Whether to print Logs to Console
*/
private boolean printToConsole;
private boolean printToConsole = true;
/**
* LoggingLevel for Logs
*/

View File

@@ -26,7 +26,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface TikTokEventHandler
public @interface TikTokEventObserver
{
}

View File

@@ -47,11 +47,11 @@ public class TikTokCommentEvent extends TikTokHeaderEvent {
public TikTokCommentEvent(WebcastChatMessage msg) {
super(msg.getCommon());
user = User.map(msg.getUser());
user = User.map(msg.getUser(),msg.getUserIdentity());
text = msg.getContent();
visibleToSender = msg.getVisibleToSender();
getUserLanguage = msg.getContentLanguage();
mentionedUser = User.map(msg.getAtUser(),msg.getUserIdentity());
mentionedUser = User.map(msg.getAtUser());
pictures = msg.getEmotesListList().stream().map(e -> Picture.map(e.getEmote().getImage())).toList();
}
}

View File

@@ -0,0 +1,20 @@
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.models.http.HttpData;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
@EventMeta(eventType = EventType.Debug)
public class TikTokHttpResponseEvent extends TikTokEvent
{
String url;
HttpData response;
HttpData request;
}

View File

@@ -0,0 +1,60 @@
package io.github.jwdeveloper.tiktok.data.models.http;
import lombok.Data;
import java.net.URI;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.*;
@Data
public class HttpData {
String url;
String method;
Map<String, List<String>> headers = new TreeMap<>();
Map<String, String> parameters = new TreeMap<>();
int status;
String body = "";
public static HttpData map(HttpRequest request) {
var data = new HttpData();
data.setUrl(request.uri().getPath());
data.setMethod(request.method());
data.setParameters(extractQueryParams(request.uri()));
data.setStatus(200);
if (request.bodyPublisher().isPresent()) {
data.setBody(request.bodyPublisher().get().toString());
}
data.setHeaders(Collections.unmodifiableMap(request.headers().map()));
return data;
}
public static HttpData map(HttpResponse<String> response) {
var data = new HttpData();
data.setUrl(response.uri().getPath());
data.setMethod(response.request().method());
data.setParameters(extractQueryParams(response.uri()));
data.setStatus(200);
data.setBody(response.body());
data.setHeaders(Collections.unmodifiableMap(response.headers().map()));
return data;
}
private static Map<String, String> extractQueryParams(URI uri) {
Map<String, String> params = new HashMap<>();
String query = uri.getQuery();
if (query != null && !query.isEmpty()) {
for (String param : query.split("&")) {
String[] keyValue = param.split("=");
if (keyValue.length > 1) {
params.put(keyValue[0], keyValue[1]);
} else {
params.put(keyValue[0], ""); // Empty value for parameter without explicit value
}
}
}
return params;
}
}

View File

@@ -22,6 +22,7 @@
*/
package io.github.jwdeveloper.tiktok.live;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.listener.ListenersManager;
import io.github.jwdeveloper.tiktok.listener.TikTokEventListener;
@@ -55,6 +56,11 @@ public interface LiveClient {
void disconnect();
/**
* You to manually trigger event
*/
void publishEvent(TikTokEvent event);
/**
* Get information about gifts
*/

View File

@@ -26,6 +26,7 @@ import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.*;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftComboEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent;
import io.github.jwdeveloper.tiktok.data.events.http.TikTokHttpResponseEvent;
import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomInfoEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokFollowEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokJoinEvent;
@@ -39,61 +40,140 @@ import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketUnhandl
public interface EventsBuilder<T> {
/**
* Method used to register own custom events
* @param eventClazz event class
* @param event action
* Invoked whenever specified event is triggered
*
* @param eventClass event class
* @param action action
*/
<E extends TikTokEvent> T onCustomEvent(Class<E> eventClazz, EventConsumer<E> event);
T onRoomInfo(EventConsumer<TikTokRoomInfoEvent> event);
T onComment(EventConsumer<TikTokCommentEvent> event);
T onWebsocketMessage(EventConsumer<TikTokWebsocketMessageEvent> event);
T onWebsocketResponse(EventConsumer<TikTokWebsocketResponseEvent> event);
T onWebsocketUnhandledMessage(EventConsumer<TikTokWebsocketUnhandledMessageEvent> event);
<E extends TikTokEvent> T onEvent(Class<E> eventClass, EventConsumer<E> action);
T onGiftCombo(EventConsumer<TikTokGiftComboEvent> event);
/**
* Invoked whenever any event is triggered
*
* @param action
* @return
*/
T onEvent(EventConsumer<TikTokEvent> action);
T onGift(EventConsumer<TikTokGiftEvent> event);
/**
* Invoked when information about room (live) got updated such as viewer count, etc..
*
* @param action
* @return
*/
T onRoomInfo(EventConsumer<TikTokRoomInfoEvent> action);
T onQuestion(EventConsumer<TikTokQuestionEvent> event);
/**
* Invoked when someone send message to chat
*
* @param action
* @return
*/
T onComment(EventConsumer<TikTokCommentEvent> action);
T onSubscribe(EventConsumer<TikTokSubscribeEvent> event);
T onFollow(EventConsumer<TikTokFollowEvent> event);
/**
* Invoked when TikTokLiveJava makes http request and getting response
*
* @param action
* @return
*/
T onHttpResponse(EventConsumer<TikTokHttpResponseEvent> action);
T onLike(EventConsumer<TikTokLikeEvent> event);
/**
* Invoked when TikTok protocolBuffer data "message" was successfully mapped to event
* events contains protocol-buffer "Message" and TikTokLiveJava "Event"
*
* @param action
* @return
*/
T onWebsocketMessage(EventConsumer<TikTokWebsocketMessageEvent> action);
T onEmote(EventConsumer<TikTokEmoteEvent> event);
/**
* Invoked when there was not found event mapper for TikTok protocolBuffer data "message"
*
* @param action
* @return
*/
T onWebsocketUnhandledMessage(EventConsumer<TikTokWebsocketUnhandledMessageEvent> action);
T onJoin(EventConsumer<TikTokJoinEvent> event);
/**
* Invoked every time TikTok sends protocolBuffer data to websocket
* Response contains list of messages that are later mapped to events
* @param action
* @return
*/
T onWebsocketResponse(EventConsumer<TikTokWebsocketResponseEvent> action);
T onShare(EventConsumer<TikTokShareEvent> event);
// T onChest(EventConsumer<TikTokChestEvent> event);
/**
* Invoked for gifts that has no combo, or when combo finishes
* @param action
* @return
*/
T onGift(EventConsumer<TikTokGiftEvent> action);
T onLivePaused(EventConsumer<TikTokLivePausedEvent> event);
/**
* Invoked for gifts that has combo options such as roses
* @param action
* @return
*/
T onGiftCombo(EventConsumer<TikTokGiftComboEvent> action);
T onLiveUnpaused(EventConsumer<TikTokLiveUnpausedEvent> event);
T onLiveEnded(EventConsumer<TikTokLiveEndedEvent> event);
T onQuestion(EventConsumer<TikTokQuestionEvent> action);
T onConnected(EventConsumer<TikTokConnectedEvent> event);
T onSubscribe(EventConsumer<TikTokSubscribeEvent> action);
T onReconnecting(EventConsumer<TikTokReconnectingEvent> event);
T onFollow(EventConsumer<TikTokFollowEvent> action);
T onDisconnected(EventConsumer<TikTokDisconnectedEvent> event);
T onLike(EventConsumer<TikTokLikeEvent> action);
T onError(EventConsumer<TikTokErrorEvent> event);
T onEmote(EventConsumer<TikTokEmoteEvent> action);
T onEvent(EventConsumer<TikTokEvent> event);
T onJoin(EventConsumer<TikTokJoinEvent> action);
T onShare(EventConsumer<TikTokShareEvent> action);
T onLivePaused(EventConsumer<TikTokLivePausedEvent> action);
T onLiveUnpaused(EventConsumer<TikTokLiveUnpausedEvent> action);
T onLiveEnded(EventConsumer<TikTokLiveEndedEvent> action);
/**
* Invoked when client has been successfully connected to live
* @param action
* @return
*/
T onConnected(EventConsumer<TikTokConnectedEvent> action);
/**
* Invoked when client tries to reconnect
* @param action
* @return
*/
T onReconnecting(EventConsumer<TikTokReconnectingEvent> action);
/**
* Invoked when client disconnected
* @param action
* @return
*/
T onDisconnected(EventConsumer<TikTokDisconnectedEvent> action);
/**
* Invoked when exception was throed inside client or event handler
* @param action
* @return
*/
T onError(EventConsumer<TikTokErrorEvent> action);
// TODO Figure out how those events works
// T onChest(EventConsumer<TikTokChestEvent> event);
//T onLinkMicFanTicket(TikTokEventConsumer<TikTokLinkMicFanTicketEvent> event);
//T onEnvelope(TikTokEventConsumer<TikTokEnvelopeEvent> event);

View File

@@ -24,72 +24,32 @@ package io.github.jwdeveloper.tiktok.mappers;
import com.google.protobuf.GeneratedMessageV3;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.mappers.data.MappingAction;
import io.github.jwdeveloper.tiktok.mappers.data.MappingResult;
import java.util.List;
import java.util.function.Function;
public interface TikTokMapper {
/**
* Triggered when `sourceClass` is mapped,
* input is bytes that are coming from TikTok in `sourceClass` packet
* output is TikTok event we want to create
* <p>
* bytesToEvent(WebcastGiftMessage.class, bytes ->
* {
* var giftMessage = WebcastGiftMessage.parseFrom(bytes);
* var giftName = giftMessage.getGift().getName();
* return new TikTokEvent(Gift.ROSE, giftMessage);
* })
* * if mapper is not found for messageName, TikTokLiveException is thrown
*
* @param sourceClass protocol buffer webcast class
* @param onMapping lambda function that is triggered on mapping. takes as input ProtocolBuffer object and as output TikTokEvent
* @param messageName
* @return TikTokMapperModel
*/
void bytesToEvent(Class<? extends GeneratedMessageV3> sourceClass, Function<byte[], TikTokEvent> onMapping);
TikTokMapperModel forMessage(String messageName);
void bytesToEvents(Class<? extends GeneratedMessageV3> sourceClass, Function<byte[], List<TikTokEvent>> onMapping);
TikTokMapperModel forMessage(Class<? extends GeneratedMessageV3> mapperName);
TikTokMapperModel forMessage(String mapperName, MappingAction<MappingResult> onMapping);
/**
* In case you found some TikTok message that has not Webcast class use this method
*
* @param messageName Name of TikTok data event
* @param onMapping lambda function that is triggered on mapping. takes as input ProtocolBuffer object and as output TikTokEvent
*/
void bytesToEvent(String messageName, Function<byte[], TikTokEvent> onMapping);
TikTokMapperModel forMessage(Class<? extends GeneratedMessageV3> mapperName, MappingAction<MappingResult> onMapping);
void bytesToEvents(String messageName, Function<byte[], List<TikTokEvent>> onMapping);
TikTokMapperModel forMessage(Class<? extends GeneratedMessageV3> mapperName, Function<byte[], TikTokEvent> onMapping);
boolean isRegistered(String mapperName);
/**
* This method can be used to override default mapping for
* certain TikTok incoming data message. For this example
* we are overriding WebcastGiftMessage and retuning CustomGiftEvent
* instead of TikTokGiftEvent
* <p>
* webcastObjectToEvent(WebcastGiftMessage.class, webcastGiftMessage ->
* {
* var giftName = webcastGiftMessage.getGift().getName();
* var user = webcastGiftMessage.getUser().getNickname();
* return new CustomGiftEvent(giftName, user);
* })
*
* @param sourceClass ProtocolBuffer class that represent incoming custom data, hint class should starts with Webcast prefix
* @param onMapping lambda function that is triggered on mapping. takes as input ProtocolBuffer object and as output TikTokEvent
*/
<T extends GeneratedMessageV3> void webcastObjectToEvent(Class<T> sourceClass, Function<T, TikTokEvent> onMapping);
<T extends GeneratedMessageV3> boolean isRegistered(Class<T> mapperName);
<T extends GeneratedMessageV3> void webcastObjectToEvents(Class<T> sourceClass, Function<T, List<TikTokEvent>> onMapping);
/**
* Triggered when `sourceClass` is mapped,
* looking for constructor in `outputClass` with one parameter that is of type `sourceClass`
* and created instance of object from this constructor
*
* @param sourceClass protocol buffer webcast class
* @param outputClass TikTok event class
*/
void webcastObjectToConstructor(Class<? extends GeneratedMessageV3> sourceClass, Class<? extends TikTokEvent> outputClass);
}

View File

@@ -0,0 +1,46 @@
package io.github.jwdeveloper.tiktok.mappers;
import com.google.protobuf.GeneratedMessageV3;
import io.github.jwdeveloper.tiktok.exceptions.TikTokMessageMappingException;
import io.github.jwdeveloper.tiktok.utils.ProtoBufferObject;
public interface TikTokMapperHelper {
/**
* @param bytes protocol buffer data bytes
* @param messageClass class that we want to serialize bytes to
* @param <T> @messageClass must be class that is made by protocol buffer
* @return object of type @messageClass
*/
<T extends GeneratedMessageV3> T bytesToWebcastObject(byte[] bytes, Class<T> messageClass);
/**
* @param bytes protocol buffer data bytes
* @param messageName class that we want to serialize bytes to
* @return protocol buffer objects if class for @messageName has been found
* @throws TikTokMessageMappingException if there is no suitable class for messageName
*/
Object bytesToWebcastObject(byte[] bytes, String messageName);
/**
* @param messageName checks wheaten TikTokLiveJava has class representation for certain protocol-buffer message name
* @return false if class is not found
*/
boolean isMessageHasProtoClass(String messageName);
/**
* @param bytes protocol buffer data bytes
* @return tree structure of protocol buffer object
* @see ProtoBufferObject
*/
ProtoBufferObject bytesToProtoBufferStructure(byte[] bytes);
/**
* Converts object to json
*
* @param obj any object
* @return pretty formatted json
*/
String toJson(Object obj);
}

View File

@@ -0,0 +1,36 @@
package io.github.jwdeveloper.tiktok.mappers;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.mappers.data.AfterMappingAction;
import io.github.jwdeveloper.tiktok.mappers.data.MappingAction;
import io.github.jwdeveloper.tiktok.mappers.data.MappingResult;
import java.util.List;
import java.util.function.Function;
public interface TikTokMapperModel {
/**
* @return name of websocket message that this mapper is mapping from
*/
String getSourceMessageName();
/**
* @param action Input bytes from websocket, you can modify it and returns different bytes
*/
TikTokMapperModel onBeforeMapping(MappingAction<byte[]> action);
/**
* @param action Input bytes from websocket. As output returns list of tiktok live events
*/
TikTokMapperModel onMapping(MappingAction<MappingResult> action);
/**
* @param action You can modify output list of TikTokLive events
* @see AfterMappingAction
*/
TikTokMapperModel onAfterMapping(Function<MappingResult, List<TikTokEvent>> action);
}

View File

@@ -0,0 +1,15 @@
package io.github.jwdeveloper.tiktok.mappers.data;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import java.util.List;
@FunctionalInterface
public interface AfterMappingAction {
/**
* @param source object that was used as source to create events
* @param events list of events prepared before, could be modified or changed
* @return list of events that will be invoked
*/
List<TikTokEvent> onAfterMapping(Object source, List<TikTokEvent> events);
}

View File

@@ -0,0 +1,16 @@
package io.github.jwdeveloper.tiktok.mappers.data;
import io.github.jwdeveloper.tiktok.mappers.TikTokMapperHelper;
@FunctionalInterface
public interface MappingAction<T> {
/**
* @param inputBytes incoming bytes from TikTok server. The represents protocol buffer message that was send to client
* @param messageName name of protocol buffer message
* @param mapperHelper utils and helper methods that can be use to debbug/display/deserialize protocol buffer data
* @return
*/
T onMapping(byte[] inputBytes, String messageName, TikTokMapperHelper mapperHelper);
}

View File

@@ -0,0 +1,31 @@
package io.github.jwdeveloper.tiktok.mappers.data;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.List;
@AllArgsConstructor
@Getter
public class MappingResult
{
Object source;
List<TikTokEvent> events;
String message;
public static MappingResult of(Object source) {
return new MappingResult(source, List.of(),"");
}
public static MappingResult of(Object source, List<TikTokEvent> events) {
return new MappingResult(source, events,"");
}
public static MappingResult of(Object source,TikTokEvent events) {
return new MappingResult(source, List.of(events),"");
}
}

View File

@@ -1018,7 +1018,7 @@ message LinkerInviteContent {
//Data.LinkmicMultiLiveEnum AnchorMultiLiveEnum = 11;
//Data.LinkmicUserSettingInfo AnchorSettingInfo = 12;
string InviterLinkmicIdStr = 13;
// InviteTopHostInfo FromTopHostInfo = 16;
// InviteTopHostInfo FromTopHostInfo = 16;
int64 ActionId = 17;
// repeated LinkmicUserInfo LinkedUsersList = 18;
// Data.PerceptionDialogInfo Dialog = 19;
@@ -1046,9 +1046,9 @@ message LinkerLinkedListChangeContent {
}
message LinkerListChangeContent {
repeated User LinkedUsersList = 1;
repeated User AppliedUsersList = 2;
repeated User ConnectingUsersList = 3;
repeated LinkLayerListUser LinkedUsersList = 1;
repeated LinkLayerListUser AppliedUsersList = 2;
repeated LinkLayerListUser ConnectingUsersList = 3;
}
message LinkerMediaChangeContent {
@@ -1087,7 +1087,7 @@ message LinkerReplyContent {
int64 FromRoomId = 2;
// LinkmicInfo FromUserLinkmicInfo = 3;
int64 ToUserId = 4;
// LinkmicInfo ToUserLinkmicInfo = 5;
// LinkmicInfo ToUserLinkmicInfo = 5;
int64 LinkType = 6;
int64 ReplyStatus = 7;
LinkerSetting LinkerSetting = 8;
@@ -1096,8 +1096,8 @@ message LinkerReplyContent {
map<int64, string> RtcExtInfoMap = 11;
LinkerMicIdxUpdateInfo InviteeMicIdxUpdateInfo = 12;
map<int64, int64> ApplierMicIdxInfoMap = 13;
// Data.LinkmicMultiLiveEnum AnchorMultiLiveEnum = 14;
// Data.LinkmicUserSettingInfo AnchorSettingInfo = 15;
// Data.LinkmicMultiLiveEnum AnchorMultiLiveEnum = 14;
// Data.LinkmicUserSettingInfo AnchorSettingInfo = 15;
int64 ActionId = 16;
// repeated LinkmicUserInfo LinkedUsersList = 17;
int64 SourceType = 18;
@@ -1156,10 +1156,11 @@ message AllListUser {
message LinkLayerListUser {
User user = 1;
string linkmicId = 2;
int64 linkmicId = 2;
Position pos = 3;
int64 linkedTimeNano = 4;
string appVersion = 5;
int64 magicNumber1 = 7;
}
message Position {

View File

@@ -68,7 +68,6 @@ message WebcastGiftMessage {
int32 repeatCount = 5;
int32 comboCount = 6;
User user = 7;
User toUser = 8;
int32 repeatEnd = 9;
int64 groupId = 11;
int64 incomeTaskgifts = 12;
@@ -81,6 +80,13 @@ message WebcastGiftMessage {
bool isFirstSent = 25;
string orderId = 28;
UserIdentity userIdentity = 32;
UserGiftReciever userGiftReciever = 23;
message UserGiftReciever
{
int64 userId =1;
string deviceName = 10;
}
message GiftIMPriority {
repeated int64 queueSizesList = 1;
@@ -507,6 +513,9 @@ message WebcastHourlyRankMessage {
}
}
//<Battles>
//@WebcastLinkMicArmies
message WebcastLinkMicArmies {
Common common = 1;
@@ -523,6 +532,45 @@ message WebcastLinkMicArmies {
uint32 data4 = 12;
uint32 data5 = 13;
}
//@WebcastLinkMicBattlePunishFinish
message WebcastLinkMicBattlePunishFinish {
Common Header = 1;
uint64 Id1 = 2;
uint64 Timestamp = 3;
uint32 Data4 = 4;
uint64 Id2 = 5;
LinkMicBattlePunishFinishData Data6 = 6;
message LinkMicBattlePunishFinishData {
uint64 Id2 = 1; // Same as Id2 in outer object (loser?)
uint64 Timestamp = 2;
uint32 Data3 = 3;
uint64 Id1 = 4; // Same as Id1 in outer object (winner?)
uint32 Data5 = 5;
uint32 Data6 = 6;
uint32 Data8 = 8;
}
}
//@WebcastLinkmicBattleTaskMessage
message WebcastLinkmicBattleTaskMessage {
Common Header = 1;
uint32 Data2 = 2;
LinkmicBattleTaskData Data3 = 3;
LinkmicBattleTaskData2 Data5 = 5;
message LinkmicBattleTaskData {
BattleTaskData Data1 = 1;
}
message BattleTaskData {
uint32 Data1 = 1;
}
message LinkmicBattleTaskData2 {
uint32 Data1 = 1;
uint32 Data2 = 2;
}
}
//@WebcastLinkMicBattle
message WebcastLinkMicBattle {
@@ -572,7 +620,6 @@ message WebcastLinkMicFanTicketMethod {
Common common = 1;
FanTicketRoomNoticeContent FanTicketRoomNotice = 2;
}
//@WebcastLinkMicMethod
message WebcastLinkMicMethod {
Common common = 1;
@@ -590,6 +637,8 @@ message WebcastLinkMicMethod {
int64 inviteUid = 13;
}
//<Battles>
//@WebcastLiveIntroMessage
message WebcastLiveIntroMessage {
Common common = 1;

View File

@@ -87,4 +87,5 @@ public class TikTokLive
{
return new TikTokDataChecker().isHostNameValidAsync(hostName);
}
}

View File

@@ -25,6 +25,7 @@ package io.github.jwdeveloper.tiktok;
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.room.TikTokRoomInfoEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveOfflineHostException;
@@ -189,4 +190,8 @@ public class TikTokLiveClient implements LiveClient {
liveRoomInfo.setConnectionState(connectionState);
}
public void publishEvent(TikTokEvent event)
{
tikTokEventHandler.publish(this, event);
}
}

View File

@@ -27,6 +27,7 @@ import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.envelop.TikTokChestEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftComboEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent;
import io.github.jwdeveloper.tiktok.data.events.http.TikTokHttpResponseEvent;
import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollEvent;
import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomInfoEvent;
import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomPinEvent;
@@ -41,23 +42,25 @@ import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver;
import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandler;
import io.github.jwdeveloper.tiktok.live.GiftManager;
import io.github.jwdeveloper.tiktok.mappers.TikTokLiveMapper;
import io.github.jwdeveloper.tiktok.mappers.TikTokMapper;
import io.github.jwdeveloper.tiktok.mappers.events.TikTokCommonEventHandler;
import io.github.jwdeveloper.tiktok.mappers.events.TikTokGiftEventHandler;
import io.github.jwdeveloper.tiktok.mappers.events.TikTokRoomInfoEventHandler;
import io.github.jwdeveloper.tiktok.mappers.events.TikTokSocialMediaEventHandler;
import io.github.jwdeveloper.tiktok.http.TikTokApiService;
import io.github.jwdeveloper.tiktok.http.TikTokCookieJar;
import io.github.jwdeveloper.tiktok.http.TikTokHttpClient;
import io.github.jwdeveloper.tiktok.http.TikTokHttpRequestFactory;
import io.github.jwdeveloper.tiktok.listener.TikTokEventListener;
import io.github.jwdeveloper.tiktok.listener.TikTokListenersManager;
import io.github.jwdeveloper.tiktok.live.GiftManager;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.live.builder.EventConsumer;
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
import io.github.jwdeveloper.tiktok.mappers.TikTokGenericEventMapper;
import io.github.jwdeveloper.tiktok.mappers.TikTokLiveMapper;
import io.github.jwdeveloper.tiktok.mappers.TikTokLiveMapperHelper;
import io.github.jwdeveloper.tiktok.mappers.TikTokMapper;
import io.github.jwdeveloper.tiktok.mappers.data.MappingResult;
import io.github.jwdeveloper.tiktok.mappers.handlers.TikTokCommonEventHandler;
import io.github.jwdeveloper.tiktok.mappers.handlers.TikTokGiftEventHandler;
import io.github.jwdeveloper.tiktok.mappers.handlers.TikTokRoomInfoEventHandler;
import io.github.jwdeveloper.tiktok.mappers.handlers.TikTokSocialMediaEventHandler;
import io.github.jwdeveloper.tiktok.messages.webcast.*;
import io.github.jwdeveloper.tiktok.utils.ConsoleColors;
import io.github.jwdeveloper.tiktok.websocket.TikTokWebSocketClient;
@@ -88,12 +91,12 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
};
}
public LiveClientBuilder onMapping(Consumer<TikTokMapper> onCustomMappings) {
this.onCustomMappings = onCustomMappings;
return this;
}
public TikTokLiveClientBuilder configure(Consumer<ClientSettings> onConfigure) {
onConfigure.accept(clientSettings);
return this;
@@ -159,7 +162,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
var listenerManager = new TikTokListenersManager(listeners, tikTokEventHandler);
var cookieJar = new TikTokCookieJar();
var requestFactory = new TikTokHttpRequestFactory(cookieJar);
var requestFactory = new TikTokHttpRequestFactory(cookieJar, tikTokEventHandler);
var apiClient = new TikTokHttpClient(cookieJar, requestFactory);
var apiService = new TikTokApiService(apiClient, logger, clientSettings);
var giftManager = new TikTokGiftManager(logger);
@@ -167,7 +170,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
var messageHandler = new TikTokMessageHandler(tikTokEventHandler, eventsMapper);
var webSocketClient = new TikTokWebSocketClient(logger,
var webSocketClient = new TikTokWebSocketClient(
cookieJar,
clientSettings,
messageHandler,
@@ -185,7 +188,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
public TikTokLiveMapper createMapper(GiftManager giftManager, TikTokRoomInfo roomInfo) {
var eventMapper = new TikTokGenericEventMapper();
var mapper = new TikTokLiveMapper(eventMapper);
var mapper = new TikTokLiveMapper(new TikTokLiveMapperHelper(eventMapper));
//ConnectionEvents events
var commonHandler = new TikTokCommonEventHandler();
@@ -193,48 +196,77 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
var roomInfoHandler = new TikTokRoomInfoEventHandler(roomInfo);
var socialHandler = new TikTokSocialMediaEventHandler(roomInfo);
mapper.bytesToEvent(WebcastControlMessage.class, commonHandler::handleWebcastControlMessage);
mapper.forMessage(WebcastControlMessage.class, commonHandler::handleWebcastControlMessage);
//Room status events
mapper.bytesToEvent(WebcastLiveIntroMessage.class, roomInfoHandler::handleIntro);
mapper.bytesToEvent(WebcastRoomUserSeqMessage.class, roomInfoHandler::handleUserRanking);
mapper.forMessage(WebcastLiveIntroMessage.class, roomInfoHandler::handleIntro);
mapper.forMessage(WebcastRoomUserSeqMessage.class, roomInfoHandler::handleUserRanking);
mapper.forMessage(WebcastCaptionMessage.class, (inputBytes, messageName, mapperHelper) ->
{
var messageObject = mapperHelper.bytesToWebcastObject(inputBytes, WebcastCaptionMessage.class);
return MappingResult.of(messageObject, new TikTokCaptionEvent(messageObject));
});
mapper.webcastObjectToConstructor(WebcastCaptionMessage.class, TikTokCaptionEvent.class);
//User Interactions events
mapper.webcastObjectToConstructor(WebcastChatMessage.class, TikTokCommentEvent.class);
mapper.bytesToEvents(WebcastLikeMessage.class, roomInfoHandler::handleLike);
mapper.bytesToEvents(WebcastGiftMessage.class, giftHandler::handleGift);
mapper.bytesToEvent(WebcastSocialMessage.class, socialHandler::handle);
mapper.bytesToEvents(WebcastMemberMessage.class, roomInfoHandler::handleMemberMessage);
mapper.forMessage(WebcastChatMessage.class, (inputBytes, messageName, mapperHelper) ->
{
var messageObject = mapperHelper.bytesToWebcastObject(inputBytes, WebcastChatMessage.class);
return MappingResult.of(messageObject, new TikTokCommentEvent(messageObject));
});
mapper.forMessage(WebcastSubNotifyMessage.class, (inputBytes, messageName, mapperHelper) ->
{
var messageObject = mapperHelper.bytesToWebcastObject(inputBytes, WebcastSubNotifyMessage.class);
return MappingResult.of(messageObject, new TikTokSubscribeEvent(messageObject));
});
mapper.forMessage(WebcastEmoteChatMessage.class, (inputBytes, messageName, mapperHelper) ->
{
var messageObject = mapperHelper.bytesToWebcastObject(inputBytes, WebcastEmoteChatMessage.class);
return MappingResult.of(messageObject, new TikTokEmoteEvent(messageObject));
});
mapper.forMessage(WebcastQuestionNewMessage.class, (inputBytes, messageName, mapperHelper) ->
{
var messageObject = mapperHelper.bytesToWebcastObject(inputBytes, WebcastQuestionNewMessage.class);
return MappingResult.of(messageObject, new TikTokQuestionEvent(messageObject));
});
mapper.forMessage(WebcastLikeMessage.class, roomInfoHandler::handleLike);
mapper.forMessage(WebcastGiftMessage.class, giftHandler::handleGifts);
mapper.forMessage(WebcastSocialMessage.class, socialHandler::handle);
mapper.forMessage(WebcastMemberMessage.class, roomInfoHandler::handleMemberMessage);
//Host Interaction events
mapper.bytesToEvent(WebcastPollMessage.class, commonHandler::handlePollEvent);
mapper.bytesToEvent(WebcastRoomPinMessage.class, commonHandler::handlePinMessage);
mapper.webcastObjectToConstructor(WebcastGoalUpdateMessage.class, TikTokGoalUpdateEvent.class);
mapper.forMessage(WebcastPollMessage.class, commonHandler::handlePollEvent);
mapper.forMessage(WebcastRoomPinMessage.class, commonHandler::handlePinMessage);
mapper.forMessage(WebcastChatMessage.class, (inputBytes, messageName, mapperHelper) ->
{
var messageObject = mapperHelper.bytesToWebcastObject(inputBytes, WebcastChatMessage.class);
return MappingResult.of(messageObject, new TikTokCommentEvent(messageObject));
});
//LinkMic events
mapper.webcastObjectToConstructor(WebcastLinkMicBattle.class, TikTokLinkMicBattleEvent.class);
mapper.webcastObjectToConstructor(WebcastLinkMicArmies.class, TikTokLinkMicArmiesEvent.class);
mapper.webcastObjectToConstructor(WebcastLinkMicMethod.class, TikTokLinkMicMethodEvent.class);
mapper.webcastObjectToConstructor(WebcastLinkMicFanTicketMethod.class, TikTokLinkMicFanTicketEvent.class);
// mapper.webcastObjectToConstructor(WebcastLinkMicBattle.class, TikTokLinkMicBattleEvent.class);
// mapper.webcastObjectToConstructor(WebcastLinkMicArmies.class, TikTokLinkMicArmiesEvent.class);
// mapper.webcastObjectToConstructor(WebcastLinkMicMethod.class, TikTokLinkMicMethodEvent.class);
// mapper.webcastObjectToConstructor(WebcastLinkMicFanTicketMethod.class, TikTokLinkMicFanTicketEvent.class);
//Rank events
mapper.webcastObjectToConstructor(WebcastRankTextMessage.class, TikTokRankTextEvent.class);
mapper.webcastObjectToConstructor(WebcastRankUpdateMessage.class, TikTokRankUpdateEvent.class);
mapper.webcastObjectToConstructor(WebcastHourlyRankMessage.class, TikTokRankUpdateEvent.class);
// mapper.webcastObjectToConstructor(WebcastRankTextMessage.class, TikTokRankTextEvent.class);
// mapper.webcastObjectToConstructor(WebcastRankUpdateMessage.class, TikTokRankUpdateEvent.class);
// mapper.webcastObjectToConstructor(WebcastHourlyRankMessage.class, TikTokRankUpdateEvent.class);
//Others events
mapper.webcastObjectToConstructor(WebcastInRoomBannerMessage.class, TikTokInRoomBannerEvent.class);
mapper.webcastObjectToConstructor(WebcastMsgDetectMessage.class, TikTokDetectEvent.class);
mapper.webcastObjectToConstructor(WebcastBarrageMessage.class, TikTokBarrageEvent.class);
mapper.webcastObjectToConstructor(WebcastUnauthorizedMemberMessage.class, TikTokUnauthorizedMemberEvent.class);
mapper.webcastObjectToConstructor(WebcastOecLiveShoppingMessage.class, TikTokShopEvent.class);
mapper.webcastObjectToConstructor(WebcastImDeleteMessage.class, TikTokIMDeleteEvent.class);
mapper.webcastObjectToConstructor(WebcastQuestionNewMessage.class, TikTokQuestionEvent.class);
mapper.bytesToEvents(WebcastEnvelopeMessage.class, commonHandler::handleEnvelop);
mapper.webcastObjectToConstructor(WebcastSubNotifyMessage.class, TikTokSubscribeEvent.class);
mapper.webcastObjectToConstructor(WebcastEmoteChatMessage.class, TikTokEmoteEvent.class);
// mapper.webcastObjectToConstructor(WebcastInRoomBannerMessage.class, TikTokInRoomBannerEvent.class);
// mapper.webcastObjectToConstructor(WebcastMsgDetectMessage.class, TikTokDetectEvent.class);
// mapper.webcastObjectToConstructor(WebcastBarrageMessage.class, TikTokBarrageEvent.class);
// mapper.webcastObjectToConstructor(WebcastUnauthorizedMemberMessage.class, TikTokUnauthorizedMemberEvent.class);
// mapper.webcastObjectToConstructor(WebcastOecLiveShoppingMessage.class, TikTokShopEvent.class);
// mapper.webcastObjectToConstructor(WebcastImDeleteMessage.class, TikTokIMDeleteEvent.class);
// mapper.bytesToEvents(WebcastEnvelopeMessage.class, commonHandler::handleEnvelop);
onCustomMappings.accept(mapper);
return mapper;
@@ -315,8 +347,8 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
}
@Override
public <E extends TikTokEvent> LiveClientBuilder onCustomEvent(Class<E> eventClazz, EventConsumer<E> event) {
tikTokEventHandler.subscribe(eventClazz, event);
public <E extends TikTokEvent> LiveClientBuilder onEvent(Class<E> eventClass, EventConsumer<E> event) {
tikTokEventHandler.subscribe(eventClass, event);
return this;
}
@@ -416,6 +448,12 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
return this;
}
@Override
public LiveClientBuilder onHttpResponse(EventConsumer<TikTokHttpResponseEvent> action) {
tikTokEventHandler.subscribe(TikTokHttpResponseEvent.class, action);
return this;
}
public TikTokLiveClientBuilder onGoalUpdate(EventConsumer<TikTokGoalUpdateEvent> event) {
tikTokEventHandler.subscribe(TikTokGoalUpdateEvent.class, event);
return this;

View File

@@ -62,7 +62,7 @@ public class TikTokRoomInfo implements LiveRoomInfo {
@Override
public User getHostUser() {
return null;
return host;
}
public void updateRanking(List<RankingUser> rankingUsers) {

View File

@@ -26,6 +26,7 @@ import io.github.jwdeveloper.tiktok.ClientSettings;
import io.github.jwdeveloper.tiktok.Constants;
import io.github.jwdeveloper.tiktok.data.dto.TikTokUserInfo;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver;
import java.util.concurrent.CompletableFuture;
import java.util.logging.Logger;
@@ -54,7 +55,7 @@ public class TikTokDataChecker {
public TikTokApiService getApiService() {
var jar = new TikTokCookieJar();
var factory = new TikTokHttpRequestFactory(jar);
var factory = new TikTokHttpRequestFactory(jar,new TikTokEventObserver());
var client = new TikTokHttpClient(jar, factory);
var settings = new ClientSettings();
settings.setClientParameters(Constants.DefaultClientParams());

View File

@@ -91,7 +91,6 @@ public class TikTokHttpClient {
if (parameters == null) {
parameters = new HashMap<>();
}
System.out.println("RomMID: "+parameters.get("room_id"));
var request = requestFactory.setQueries(parameters);
return request.post(url);
}

View File

@@ -24,7 +24,10 @@ package io.github.jwdeveloper.tiktok.http;
import io.github.jwdeveloper.tiktok.Constants;
import io.github.jwdeveloper.tiktok.data.events.http.TikTokHttpResponseEvent;
import io.github.jwdeveloper.tiktok.data.models.http.HttpData;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver;
import lombok.SneakyThrows;
import java.net.CookieManager;
@@ -45,26 +48,27 @@ public class TikTokHttpRequestFactory implements TikTokHttpRequest {
private final Map<String, String> defaultHeaders;
private final TikTokCookieJar tikTokCookieJar;
private final HttpClient client;
private final TikTokEventObserver eventHandler;
private String query;
public TikTokHttpRequestFactory(TikTokCookieJar tikTokCookieJar) {
public TikTokHttpRequestFactory(TikTokCookieJar tikTokCookieJar, TikTokEventObserver eventHandler) {
this.tikTokCookieJar = tikTokCookieJar;
this.cookieManager = new CookieManager();
this.eventHandler = eventHandler;
defaultHeaders = Constants.DefaultRequestHeaders();
client = HttpClient.newBuilder()
.cookieHandler(cookieManager)
.connectTimeout(Duration.ofSeconds(2))
.build();
}
@SneakyThrows
public String get(String url) {
var uri = URI.create(url);
var requestBuilder = HttpRequest.newBuilder().GET();
for (var header : defaultHeaders.entrySet())
{
if(header.getKey().equals("Connection") || header.getKey().equals("Accept-Encoding"))
{
for (var header : defaultHeaders.entrySet()) {
if (header.getKey().equals("Connection") || header.getKey().equals("Accept-Encoding")) {
continue;
}
requestBuilder.setHeader(header.getKey(), header.getValue());
@@ -73,9 +77,7 @@ public class TikTokHttpRequestFactory implements TikTokHttpRequest {
var baseUri = uri.toString();
var requestUri = URI.create(baseUri + "?" + query);
requestBuilder.uri(requestUri);
}
else
{
} else {
requestBuilder.uri(uri);
}
@@ -88,15 +90,13 @@ public class TikTokHttpRequestFactory implements TikTokHttpRequest {
public String post(String url) {
var uri = URI.create(url);
var request = HttpRequest.newBuilder().POST(HttpRequest.BodyPublishers.ofString(""));
for (var header : defaultHeaders.entrySet())
{
if(header.getKey().equals("Connection"))
{
for (var header : defaultHeaders.entrySet()) {
if (header.getKey().equals("Connection")) {
continue;
}
request.setHeader(header.getKey(), header.getValue());
}
request.setHeader("Content-type","application/x-www-form-urlencoded; charset=UTF-8");
request.setHeader("Content-type", "application/x-www-form-urlencoded; charset=UTF-8");
request.setHeader("Cookie", tikTokCookieJar.parseCookies());
@@ -104,11 +104,8 @@ public class TikTokHttpRequestFactory implements TikTokHttpRequest {
var baseUri = uri.toString();
var requestUri = URI.create(baseUri + "?" + query);
request.uri(requestUri);
System.out.println(requestUri.toString());
}
return getContent(request.build());
}
@@ -125,7 +122,7 @@ public class TikTokHttpRequestFactory implements TikTokHttpRequest {
public TikTokHttpRequest setQueries(Map<String, Object> queries) {
if (queries == null)
return this;
var testMap = new TreeMap<String,Object>(queries);
var testMap = new TreeMap<String, Object>(queries);
query = String.join("&", testMap.entrySet().stream().map(x ->
{
var key = x.getKey();
@@ -143,6 +140,9 @@ public class TikTokHttpRequestFactory implements TikTokHttpRequest {
private String getContent(HttpRequest request) throws Exception {
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
var event = new TikTokHttpResponseEvent(response.uri().toString(), HttpData.map(request), HttpData.map(response));
eventHandler.publish(null, event);
if (response.statusCode() == 404) {
throw new TikTokLiveRequestException("Request responded with 404 NOT_FOUND");
}
@@ -155,8 +155,6 @@ public class TikTokHttpRequestFactory implements TikTokHttpRequest {
for (var cookie : cookies) {
var split = cookie.split(";")[0].split("=");
var uri = request.uri();
var key = split[0];
var value = split[1];
tikTokCookieJar.set(key, value);

View File

@@ -23,11 +23,10 @@
package io.github.jwdeveloper.tiktok.listener;
import io.github.jwdeveloper.tiktok.annotations.TikTokEventHandler;
import io.github.jwdeveloper.tiktok.annotations.TikTokEventObserver;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokEventListenerMethodException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.live.builder.EventConsumer;
@@ -37,10 +36,10 @@ import java.util.HashMap;
import java.util.List;
public class TikTokListenersManager implements ListenersManager {
private final TikTokEventObserver eventObserver;
private final io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver eventObserver;
private final List<ListenerBindingModel> bindingModels;
public TikTokListenersManager(List<TikTokEventListener> listeners, TikTokEventObserver tikTokEventHandler) {
public TikTokListenersManager(List<TikTokEventListener> listeners, io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver tikTokEventHandler) {
this.eventObserver = tikTokEventHandler;
this.bindingModels = new ArrayList<>(listeners.size());
for (var listener : listeners) {
@@ -93,7 +92,7 @@ public class TikTokListenersManager implements ListenersManager {
var clazz = listener.getClass();
var methods = Arrays.stream(clazz.getDeclaredMethods()).filter(m ->
m.getParameterCount() == 2 &&
m.isAnnotationPresent(TikTokEventHandler.class)).toList();
m.isAnnotationPresent(TikTokEventObserver.class)).toList();
var eventsMap = new HashMap<Class<?>, List<EventConsumer<?>>>();
for (var method : methods) {
var eventClazz = method.getParameterTypes()[1];

View File

@@ -24,7 +24,8 @@ package io.github.jwdeveloper.tiktok.mappers;
import com.google.protobuf.GeneratedMessageV3;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokMessageMappingException;
import io.github.jwdeveloper.tiktok.mappers.data.MappingAction;
import io.github.jwdeveloper.tiktok.mappers.data.MappingResult;
import java.util.HashMap;
import java.util.List;
@@ -32,79 +33,68 @@ import java.util.Map;
import java.util.function.Function;
public class TikTokLiveMapper implements TikTokMapper {
private final Map<String, Function<byte[], List<TikTokEvent>>> mappers;
private final TikTokGenericEventMapper genericMapper;
public TikTokLiveMapper(TikTokGenericEventMapper genericMapper) {
private final Map<String, TikTokLiveMapperModel> mappers;
private final TikTokMapperHelper mapperUtils;
public TikTokLiveMapper(TikTokMapperHelper mapperUtils) {
this.mappers = new HashMap<>();
this.genericMapper = genericMapper;
this.mapperUtils = mapperUtils;
}
@Override
public void bytesToEvent(String messageName, Function<byte[], TikTokEvent> onMapping) {
mappers.put(messageName, messagePayload -> List.of(onMapping.apply(messagePayload)));
public TikTokMapperModel forMessage(String messageName) {
if (!isRegistered(messageName)) {
var model = new TikTokLiveMapperModel(messageName);
mappers.put(messageName, model);
}
return mappers.get(messageName);
}
@Override
public void bytesToEvents(String messageName, Function<byte[], List<TikTokEvent>> onMapping) {
mappers.put(messageName, onMapping::apply);
}
public void bytesToEvent(Class<? extends GeneratedMessageV3> clazz, Function<byte[], TikTokEvent> onMapping) {
mappers.put(clazz.getSimpleName(), messagePayload -> List.of(onMapping.apply(messagePayload)));
}
public void bytesToEvents(Class<? extends GeneratedMessageV3> clazz, Function<byte[], List<TikTokEvent>> onMapping) {
mappers.put(clazz.getSimpleName(), onMapping::apply);
public TikTokMapperModel forMessage(Class<? extends GeneratedMessageV3> mapperName) {
return forMessage(mapperName.getSimpleName());
}
@Override
public void webcastObjectToConstructor(Class<? extends GeneratedMessageV3> sourceClass, Class<? extends TikTokEvent> outputClass) {
bytesToEvent(sourceClass, (e) -> genericMapper.mapToEvent(sourceClass, outputClass, e));
public TikTokMapperModel forMessage(String mapperName, MappingAction<MappingResult> onMapping) {
var model = forMessage(mapperName);
model.onMapping(onMapping);
return model;
}
@Override
public TikTokMapperModel forMessage(Class<? extends GeneratedMessageV3> mapperName, MappingAction<MappingResult> onMapping) {
var model = forMessage(mapperName);
model.onMapping(onMapping);
return model;
}
@Override
public <T extends GeneratedMessageV3> void webcastObjectToEvent(Class<T> source, Function<T, TikTokEvent> onMapping) {
bytesToEvent(source, (bytes) ->
{
try {
var parsingMethod = genericMapper.getParsingMethod(source);
var sourceObject = parsingMethod.invoke(null, bytes);
var event = onMapping.apply((T) sourceObject);
return event;
} catch (Exception e) {
throw new TikTokMessageMappingException(source, "can't find parsing method", e);
}
});
public TikTokMapperModel forMessage(Class<? extends GeneratedMessageV3> mapperName, Function<byte[], TikTokEvent> onMapping) {
return forMessage(mapperName, (inputBytes, messageName, mapperHelper) -> MappingResult.of(inputBytes, onMapping.apply(inputBytes)));
}
@Override
public <T extends GeneratedMessageV3> void webcastObjectToEvents(Class<T> source, Function<T, List<TikTokEvent>> onMapping) {
bytesToEvents(source, (bytes) ->
{
try {
var parsingMethod = genericMapper.getParsingMethod(source);
var sourceObject = parsingMethod.invoke(null, bytes);
var event = onMapping.apply((T) sourceObject);
return event;
} catch (Exception e) {
throw new TikTokMessageMappingException(source, "can't find parsing method", e);
}
});
public boolean isRegistered(String mapperName) {
return mappers.containsKey(mapperName);
}
public boolean isRegistered(String input) {
return mappers.containsKey(input);
public <T extends GeneratedMessageV3> boolean isRegistered(Class<T> mapperName) {
return mappers.containsKey(mapperName.getSimpleName());
}
public List<TikTokEvent> handleMapping(String input, byte[] bytes) {
if (!isRegistered(input)) {
public List<TikTokEvent> handleMapping(String messageName, byte[] bytes) {
if (!isRegistered(messageName)) {
return List.of();
}
var mapper = mappers.get(input);
var events = mapper.apply(bytes);
return events;
var mapperModel = mappers.get(messageName);
var inputBytes = mapperModel.getOnBeforeMapping().onMapping(bytes, messageName, mapperUtils);
var mappingResult = mapperModel.getOnMapping().onMapping(inputBytes, messageName, mapperUtils);
var afterMappingResult = mapperModel.getOnAfterMapping().apply(mappingResult);
return afterMappingResult;
}
}

View File

@@ -0,0 +1,60 @@
package io.github.jwdeveloper.tiktok.mappers;
import com.google.protobuf.GeneratedMessageV3;
import io.github.jwdeveloper.tiktok.exceptions.TikTokMessageMappingException;
import io.github.jwdeveloper.tiktok.utils.JsonUtil;
import io.github.jwdeveloper.tiktok.utils.ProtoBufferObject;
import io.github.jwdeveloper.tiktok.utils.ProtocolUtils;
public class TikTokLiveMapperHelper implements TikTokMapperHelper {
private final TikTokGenericEventMapper genericMapper;
public TikTokLiveMapperHelper(TikTokGenericEventMapper genericMapper) {
this.genericMapper = genericMapper;
}
@Override
public <T extends GeneratedMessageV3> T bytesToWebcastObject(byte[] bytes, Class<T> messageClass) {
try {
var parsingMethod = genericMapper.getParsingMethod(messageClass);
var sourceObject = parsingMethod.invoke(null, bytes);
return (T) sourceObject;
} catch (Exception e) {
throw new TikTokMessageMappingException(messageClass, "can't find parsing method", e);
}
}
@Override
public Object bytesToWebcastObject(byte[] bytes, String messageName) {
try {
var packageName = "io.github.jwdeveloper.tiktok.messages.webcast." + messageName;
var clazz = Class.forName(packageName);
return bytesToWebcastObject(bytes, (Class<? extends GeneratedMessageV3>) clazz);
} catch (Exception e) {
throw new TikTokMessageMappingException(messageName, e);
}
}
@Override
public boolean isMessageHasProtoClass(String messageName) {
try {
var packageName = "io.github.jwdeveloper.tiktok.messages.webcast." + messageName;
Class.forName(packageName);
return true;
} catch (Exception e) {
return false;
}
}
@Override
public ProtoBufferObject bytesToProtoBufferStructure(byte[] bytes) {
return ProtocolUtils.getProtocolBufferStructure(bytes);
}
@Override
public String toJson(Object obj) {
return JsonUtil.toJson(obj);
}
}

View File

@@ -0,0 +1,57 @@
package io.github.jwdeveloper.tiktok.mappers;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.mappers.data.MappingAction;
import io.github.jwdeveloper.tiktok.mappers.data.MappingResult;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
import java.util.function.Function;
@Getter
@Setter
public class TikTokLiveMapperModel implements TikTokMapperModel {
@Getter
private String sourceMessageName;
private MappingAction<byte[]> onBeforeMapping;
private MappingAction<MappingResult> onMapping;
private Function<MappingResult, List<TikTokEvent>> onAfterMapping;
public TikTokLiveMapperModel(String sourceMessageName, MappingAction onMapping) {
this.sourceMessageName = sourceMessageName;
this.onBeforeMapping = (inputBytes, mesasgeName, mapperHelper) -> inputBytes;
this.onMapping = onMapping;
this.onAfterMapping = MappingResult::getEvents;
}
public TikTokLiveMapperModel(String sourceMessageName) {
this.sourceMessageName = sourceMessageName;
this.onBeforeMapping = (inputBytes, mesasgeName, mapperHelper) -> inputBytes;
this.onMapping = (inputBytes, mesasgeName, mapperHelper) -> MappingResult.of(inputBytes, List.of());
this.onAfterMapping = MappingResult::getEvents;
}
@Override
public TikTokMapperModel onBeforeMapping(MappingAction<byte[]> action) {
this.onBeforeMapping = action;
return this;
}
@Override
public TikTokMapperModel onMapping(MappingAction<MappingResult> action) {
this.onMapping = action;
return this;
}
@Override
public TikTokMapperModel onAfterMapping(Function<MappingResult, List<TikTokEvent>> action) {
this.onAfterMapping = action;
return this;
}
}

View File

@@ -20,7 +20,7 @@
* 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.mappers.events;
package io.github.jwdeveloper.tiktok.mappers.handlers;
public class TikTokChestEventHandler {
}

View File

@@ -20,7 +20,7 @@
* 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.mappers.events;
package io.github.jwdeveloper.tiktok.mappers.handlers;
import io.github.jwdeveloper.tiktok.data.events.*;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;

View File

@@ -20,7 +20,7 @@
* 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.mappers.events;
package io.github.jwdeveloper.tiktok.mappers.handlers;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftComboEvent;
@@ -30,6 +30,8 @@ import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.data.models.gifts.GiftSendType;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.live.GiftManager;
import io.github.jwdeveloper.tiktok.mappers.TikTokMapperHelper;
import io.github.jwdeveloper.tiktok.mappers.data.MappingResult;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage;
import lombok.SneakyThrows;
import sun.misc.Unsafe;
@@ -48,9 +50,10 @@ public class TikTokGiftEventHandler {
}
@SneakyThrows
public List<TikTokEvent> handleGift(byte[] msg) {
public MappingResult handleGifts(byte[] msg, String name, TikTokMapperHelper helper) {
var currentMessage = WebcastGiftMessage.parseFrom(msg);
return handleGift(currentMessage);
var gifts = handleGift(currentMessage);
return MappingResult.of(currentMessage,gifts);
}
public List<TikTokEvent> handleGift(WebcastGiftMessage currentMessage) {

View File

@@ -20,7 +20,7 @@
* 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.mappers.events;
package io.github.jwdeveloper.tiktok.mappers.handlers;
import io.github.jwdeveloper.tiktok.TikTokRoomInfo;
import io.github.jwdeveloper.tiktok.data.events.TikTokSubscribeEvent;
@@ -31,6 +31,8 @@ import io.github.jwdeveloper.tiktok.data.events.social.TikTokJoinEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokLikeEvent;
import io.github.jwdeveloper.tiktok.data.models.RankingUser;
import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.mappers.TikTokMapperHelper;
import io.github.jwdeveloper.tiktok.mappers.data.MappingResult;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLikeMessage;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLiveIntroMessage;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastMemberMessage;
@@ -76,8 +78,7 @@ public class TikTokRoomInfoEventHandler {
return handleRoomInfo(tikTokRoomInfo ->
{
if(tikTokRoomInfo.getHost() == null)
{
if (tikTokRoomInfo.getHost() == null) {
tikTokRoomInfo.setHost(hostUser);
}
tikTokRoomInfo.setLanguage(language);
@@ -85,7 +86,7 @@ public class TikTokRoomInfoEventHandler {
}
@SneakyThrows
public List<TikTokEvent> handleMemberMessage(byte[] msg) {
public MappingResult handleMemberMessage(byte[] msg, String name, TikTokMapperHelper helper) {
var message = WebcastMemberMessage.parseFrom(msg);
var event = switch (message.getAction()) {
@@ -98,18 +99,17 @@ public class TikTokRoomInfoEventHandler {
{
tikTokRoomInfo.setViewersCount(message.getMemberCount());
});
return List.of(event, roomInfoEvent);
return MappingResult.of(message, List.of(event, roomInfoEvent));
}
@SneakyThrows
public List<TikTokEvent> handleLike(byte[] msg)
{
public MappingResult handleLike(byte[] msg, String name, TikTokMapperHelper helper) {
var message = WebcastLikeMessage.parseFrom(msg);
var event = new TikTokLikeEvent(message);
var roomInfoEvent = this.handleRoomInfo(tikTokRoomInfo ->
{
tikTokRoomInfo.setLikesCount(event.getTotalLikes());
});
return List.of(event, roomInfoEvent);
return MappingResult.of(message, List.of(event, roomInfoEvent));
}
}

View File

@@ -20,7 +20,7 @@
* 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.mappers.events;
package io.github.jwdeveloper.tiktok.mappers.handlers;
import io.github.jwdeveloper.tiktok.TikTokRoomInfo;
import io.github.jwdeveloper.tiktok.data.events.TikTokUnhandledSocialEvent;

View File

@@ -39,7 +39,6 @@ import java.util.TreeMap;
import java.util.logging.Logger;
public class TikTokWebSocketClient implements SocketClient {
private final Logger logger;
private final ClientSettings clientSettings;
private final TikTokCookieJar tikTokCookieJar;
private final TikTokMessageHandler messageHandler;
@@ -48,12 +47,11 @@ public class TikTokWebSocketClient implements SocketClient {
private TikTokWebSocketPingingTask pingingTask;
private boolean isConnected;
public TikTokWebSocketClient(Logger logger,
public TikTokWebSocketClient(
TikTokCookieJar tikTokCookieJar,
ClientSettings clientSettings,
TikTokMessageHandler messageHandler,
TikTokEventObserver tikTokEventHandler) {
this.logger = logger;
this.tikTokCookieJar = tikTokCookieJar;
this.clientSettings = clientSettings;
this.messageHandler = messageHandler;

View File

@@ -27,7 +27,7 @@ import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.gifts.GiftSendType;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager;
import io.github.jwdeveloper.tiktok.mappers.events.TikTokGiftEventHandler;
import io.github.jwdeveloper.tiktok.mappers.handlers.TikTokGiftEventHandler;
import io.github.jwdeveloper.tiktok.messages.data.GiftStruct;
import io.github.jwdeveloper.tiktok.messages.data.Image;
import io.github.jwdeveloper.tiktok.messages.data.User;

View File

@@ -22,12 +22,11 @@
*/
package io.github.jwdeveloper.tiktok.listener;
import io.github.jwdeveloper.tiktok.annotations.TikTokEventHandler;
import io.github.jwdeveloper.tiktok.annotations.TikTokEventObserver;
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.social.TikTokJoinEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -42,12 +41,12 @@ import static org.mockito.Mockito.verify;
class TikTokListenersManagerTest {
private TikTokEventObserver eventObserver;
private io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver eventObserver;
private TikTokListenersManager tikTokListenersManager;
@BeforeEach
void setUp() {
eventObserver = Mockito.mock(TikTokEventObserver.class);
eventObserver = Mockito.mock(io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver.class);
List<TikTokEventListener> listeners = new ArrayList<>();
tikTokListenersManager = new TikTokListenersManager(listeners, eventObserver);
}
@@ -93,19 +92,19 @@ class TikTokListenersManagerTest {
public static class TikTokEventListenerTest implements TikTokEventListener
{
@TikTokEventHandler
@TikTokEventObserver
public void onJoin(LiveClient client, TikTokJoinEvent joinEvent)
{
}
@TikTokEventHandler
@TikTokEventObserver
public void onGift(LiveClient client, TikTokGiftEvent giftMessageEvent)
{
}
@TikTokEventHandler
@TikTokEventObserver
public void onEvent(LiveClient client, TikTokEvent event)
{

View File

@@ -22,55 +22,64 @@
*/
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.exceptions.TikTokMessageMappingException;
import io.github.jwdeveloper.tiktok.mappers.data.MappingResult;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastChatMessage;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage;
import io.github.jwdeveloper.tiktok.utils.ProtocolUtils;
public class CustomMappingExample {
public static void main(String[] args) {
TikTokLive.newClient("vadimpyrography")
.onCustomEvent(CustomChatEvent.class, (liveClient, event) ->
TikTokLive.newClient("saszareznikow")
.onMapping(mapper ->
{
System.out.println("hello world!");
mapper.forMessage(WebcastChatMessage.class)
.onBeforeMapping((inputBytes, messageName, mapperHelper) ->
{
System.out.println("===============================");
System.out.println("OnBefore mapping: " + messageName);
return inputBytes;
})
.onMapping((inputBytes, messageName, mapperHelper) ->
{
System.out.println("onMapping mapping: " + messageName);
var message = mapperHelper.bytesToWebcastObject(inputBytes, WebcastChatMessage.class);
var language = message.getContentLanguage();
var userName = message.getUser().getNickname();
var content = message.getContent();
var event = new CustomChatEvent(language, userName, content);
return MappingResult.of(message, event);
})
.onAfterMapping(mappingResult ->
{
var source = mappingResult.getSource();
var events = mappingResult.getEvents();
System.out.println("onAfter mapping, " + source.getClass().getSimpleName() + " was mapped to " + events.size() + " events");
return events;
});
/*
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
by using helper methods
*/
mapper.forMessage("WebcastMemberMessage")
.onBeforeMapping((inputBytes, messageName, mapperHelper) ->
{
if (mapperHelper.isMessageHasProtoClass(messageName)) {
var messageObject = mapperHelper.bytesToWebcastObject(inputBytes, messageName);
// System.out.println(mapperHelper.toJson(messageObject));
} else {
var structure = mapperHelper.bytesToProtoBufferStructure(inputBytes);
// System.out.println(structure.toJson());
}
return inputBytes;
});
})
.onError((liveClient, event) ->
{
event.getException().printStackTrace();
})
.onMapping(mapper ->
{
mapper.webcastObjectToEvent(WebcastChatMessage.class, chatMessage ->
{
var language = chatMessage.getContentLanguage();
var userName = chatMessage.getUser().getNickname();
var message = chatMessage.getContent();
return new CustomChatEvent(language, userName, message);
});
mapper.bytesToEvent("WebcastGiftMessage", bytes ->
{
try
{
var event = WebcastGiftMessage.parseFrom(bytes);
return new TikTokGiftEvent(Gift.ROSA, event);
} catch (Exception e) {
throw new TikTokMessageMappingException("unable to map gift message!", e);
}
});
mapper.bytesToEvent("WebcastMemberMessage",bytes ->
{
//displaying unknown messages from tiktok
var structure = ProtocolUtils.getProtocolBufferStructure(bytes);
System.out.println(structure.toJson());
return new TikTokErrorEvent(new RuntimeException("Message not implemented"));
});
}).buildAndConnect();
.buildAndConnect();
}

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;
public class Examplee
{
public static void main(String[] args)
{
TikTokLive.newClient("skullchefasmr")
.onGift((liveClient, event) ->
{
System.out.println("Dzięki za gifta "+event.getGift().getName());
}).buildAndConnect();
}
}

View File

@@ -22,7 +22,7 @@
*/
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.annotations.TikTokEventHandler;
import io.github.jwdeveloper.tiktok.annotations.TikTokEventObserver;
import io.github.jwdeveloper.tiktok.data.events.TikTokCommentEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
@@ -65,24 +65,24 @@ public class ListenerExample
public static class CustomListener implements TikTokEventListener {
@TikTokEventHandler
@TikTokEventObserver
public void onLike(LiveClient liveClient, TikTokLikeEvent event) {
System.out.println(event.toString());
}
@TikTokEventHandler
@TikTokEventObserver
public void onError(LiveClient liveClient, TikTokErrorEvent event) {
// event.getException().printStackTrace();
}
@TikTokEventHandler
@TikTokEventObserver
public void onComment(LiveClient liveClient, TikTokCommentEvent event) {
var userName = event.getUser().getName();
var text = event.getText();
liveClient.getLogger().info(userName + ": " + text);
}
@TikTokEventHandler
@TikTokEventObserver
public void onGift(LiveClient liveClient, TikTokGiftEvent event) {
var message = switch (event.getGift()) {
case ROSE -> "Thanks :)";
@@ -95,7 +95,7 @@ public class ListenerExample
liveClient.getLogger().info(message);
}
@TikTokEventHandler
@TikTokEventObserver
public void onAnyEvent(LiveClient liveClient, TikTokEvent event) {
liveClient.getLogger().info(event.getClass().getSimpleName());
}

View File

@@ -0,0 +1,65 @@
/*
* 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.tools;
import io.github.jwdeveloper.tiktok.tools.collector.client.TikTokDataCollectorBuilder;
import io.github.jwdeveloper.tiktok.tools.collector.api.DataCollectorBuilder;
import io.github.jwdeveloper.tiktok.tools.tester.TikTokDataTesterBuilder;
import io.github.jwdeveloper.tiktok.tools.tester.api.DataTesterBuilder;
public class TikTokLiveTools
{
/**
*
* @param databaseName dataCollector use sql-lite database to store message
* if database not exits it creates new one
* @return
*/
public static DataCollectorBuilder createCollector(String databaseName)
{
return new TikTokDataCollectorBuilder(databaseName);
}
/**
*
* @param databaseName dataTester will read messages for database
* before using dataTester, use dataCollector to create database
* if database not exits exception will be thrown
* @return
*/
public static DataTesterBuilder createTester(String databaseName)
{
return new TikTokDataTesterBuilder(databaseName);
}
/**
*
* Returns browser application that collects and display Events, Messages, WebcastResponses
* in online web editor so it's easier to read and analyze data structures
* @return
*/
public static void createWebViewer()
{
}
}

View File

@@ -1,93 +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.tools.collector;
import io.github.jwdeveloper.tiktok.TikTokLive;
import io.github.jwdeveloper.tiktok.TikTokLiveClient;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage;
import io.github.jwdeveloper.tiktok.tools.collector.client.TikTokMessageCollectorClient;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.logging.Level;
public class RunCollector {
//https://protobuf-decoder.netlify.app/
//https://streamdps.com/tiktok-widgets/gifts/
//WebcastLinkMicBattleItemCard does streamer win battle?
public static void main(String[] args) throws SQLException, IOException {
TikTokMessageCollectorClient.create("giftsCollector")
//.addUser("crece.sara")
//.addUser("moniczkka")
.addUser("valeria.viral")
// .addUser("cbcgod")
// .addUser("psychotropnazywo")
// .addUser("accordionistka")
//.addEventFilter(WebcastGiftMessage.class)
.addOnBuilder(liveClientBuilder ->
{
liveClientBuilder.onGiftCombo((liveClient, event) ->
{
liveClient.getLogger().setLevel(Level.OFF);
var gifts = liveClient.getGiftManager().getGifts();
var sb = new StringBuilder();
sb.append("GIFT COMBO User: " + event.getUser().getProfileName()+" ");
sb.append("Name: " + event.getGift().getName() + " ");
sb.append("Combo: " + event.getCombo() + " ");
sb.append("COST: " + event.getGift().getDiamondCost() + " ");
sb.append("STATE: " + event.getComboState() + " ");
System.out.println(sb.toString());
});
liveClientBuilder.onGift((liveClient, event) ->
{
liveClient.getLogger().setLevel(Level.OFF);
var gifts = liveClient.getGiftManager().getGifts();
var sb = new StringBuilder();
sb.append("GIFT User: " + event.getUser().getProfileName()+" ");
sb.append("Name: " + event.getGift().getName() + " ");
sb.append("COST: " + event.getGift().getDiamondCost() + " ");
sb.append("Combo: " + event.getCombo() + " ");
System.out.println(sb.toString());
});
})
.buildAndRun();
System.in.read();
}
}

View File

@@ -20,18 +20,14 @@
* 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.tools.collector.tables;
package io.github.jwdeveloper.tiktok.tools.collector.api;
import lombok.Data;
public interface DataCollector {
@Data
public class TikTokResponseModel
{
private Integer id;
void connect();
private String hostName;
private String response;
void disconnect();
private String createdAt;
void disconnect(boolean keepDatabase);
}

View File

@@ -0,0 +1,44 @@
/*
* 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.tools.collector.api;
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
import io.github.jwdeveloper.tiktok.tools.db.TikTokDatabase;
import java.util.function.Consumer;
public interface DataCollectorBuilder extends DataFilters<DataCollectorBuilder> {
DataCollectorBuilder setOutputPath(String path);
DataCollectorBuilder setSessionTag(String sessionTimestamp);
DataCollectorBuilder setDatabase(TikTokDatabase database);
DataCollectorBuilder configureLiveClient(Consumer<LiveClientBuilder> consumer);
DataCollectorBuilder addUser(String user);
DataCollector buildAndRun();
DataCollector build();
}

View File

@@ -0,0 +1,35 @@
/*
* 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.tools.collector.api;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
public interface DataFilters<T> {
T addMessageFilter(Class<? extends com.google.protobuf.GeneratedMessageV3> message);
T addMessageFilter(String message);
T addEventFilter(Class<? extends TikTokEvent> event);
T addEventFilter(String event);
}

View File

@@ -20,17 +20,22 @@
* 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.tools.collector.client;
package io.github.jwdeveloper.tiktok.tools.collector.api;
public class TikTokMessageCollectorClient
{
public static TikTokMessagessCollectorBuilder create(String outputName)
{
return new TikTokMessagessCollectorBuilder(outputName);
}
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
import lombok.Data;
public static TikTokMessagessCollectorBuilder create(MessageCollector messageCollector, String outputName)
{
return new TikTokMessagessCollectorBuilder(messageCollector,outputName);
}
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
@Data
public class TikTokDataCollectorModel {
private List<String> users;
private String outputPath;
private String outputName;
private Set<String> eventsFilter;
private Set<String> messagesFilter;
private String sessionTag ="";
private Consumer<LiveClientBuilder> onConfigureLiveClient;
}

View File

@@ -37,14 +37,14 @@ import java.time.LocalDateTime;
import java.util.*;
import java.util.logging.Logger;
public class MessageCollector {
public class MessagesManager {
@Getter
Map<String, Queue<MessageData>> messages;
String outputName;
int limit = 20;
public MessageCollector(String outputName) {
public MessagesManager(String outputName) {
this.messages = new TreeMap<>();
this.outputName = outputName;
load();

View File

@@ -22,104 +22,16 @@
*/
package io.github.jwdeveloper.tiktok.tools.collector.client;
import io.github.jwdeveloper.tiktok.TikTokLive;
import io.github.jwdeveloper.tiktok.TikTokLiveClientBuilder;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveMessageException;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
import io.github.jwdeveloper.tiktok.tools.collector.db.TikTokDatabase;
import io.github.jwdeveloper.tiktok.tools.collector.tables.ExceptionInfoModel;
import io.github.jwdeveloper.tiktok.tools.collector.tables.TikTokErrorModel;
import io.github.jwdeveloper.tiktok.tools.collector.tables.TikTokMessageModel;
import io.github.jwdeveloper.tiktok.tools.collector.tables.TikTokResponseModel;
import java.util.Base64;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import io.github.jwdeveloper.tiktok.tools.db.TikTokDatabase;
public class TikTokClientFactory {
private final MessageCollector messageCollector;
private final MessagesManager messageCollector;
private final TikTokDatabase tikTokDatabase;
public TikTokClientFactory(MessageCollector messageCollector, TikTokDatabase tikTokDatabase) {
public TikTokClientFactory(MessagesManager messageCollector, TikTokDatabase tikTokDatabase) {
this.messageCollector = messageCollector;
this.tikTokDatabase = tikTokDatabase;
}
public CompletableFuture<LiveClient> runClientAsync(String tiktokUser, List<Class<?>> filters, Consumer<LiveClientBuilder> onBuilder) {
var builder = TikTokLive.newClient(tiktokUser);
var msgFilter = filters.stream().map(Class::getSimpleName).toList();
onBuilder.accept(builder);
return builder.onConnected((liveClient, event) ->
{
liveClient.getLogger().info("CONNECTED TO " + liveClient.getRoomInfo().getHostName());
})
.onWebsocketResponse((liveClient, event) ->
{
var response = Base64.getEncoder().encodeToString(event.getResponse().toByteArray());
var responseModel = new TikTokResponseModel();
responseModel.setResponse(response);
responseModel.setHostName(liveClient.getRoomInfo().getHostName());
tikTokDatabase.insertResponse(responseModel);
liveClient.getLogger().info("Response");
for (var message : event.getResponse().getMessagesList())
{
if(msgFilter.size() > 0 && !msgFilter.contains(message.getMethod()))
{
continue;
}
messageCollector.addMessage(liveClient.getLogger(), liveClient.getRoomInfo().getHostName(), message);
}
})
.onWebsocketMessage((liveClient, event) ->
{
var eventName = event.getEvent().getClass().getSimpleName();
/*
if (msgFilter.size() != 0 && !msgFilter.contains(event.getEvent().getClass())) {
return;
}*/
var messageBinary = Base64.getEncoder().encodeToString(event.getMessage().toByteArray());
var model = new TikTokMessageModel();
model.setType("messsage");
model.setHostName(tiktokUser);
model.setEventName(eventName);
model.setMessage(messageBinary);
// tikTokDatabase.insertMessage(model);
// liveClient.getLogger().info("EVENT: [" + tiktokUser + "] " + eventName);
})
.onError((liveClient, event) ->
{
event.getException().printStackTrace();
var exception = event.getException();
var exceptionContent = ExceptionInfoModel.getStackTraceAsString(exception);
var errorModel = new TikTokErrorModel();
if (exception instanceof TikTokLiveMessageException ex) {
errorModel.setHostName(tiktokUser);
errorModel.setErrorName(ex.messageMethod());
errorModel.setErrorType("error-message");
errorModel.setExceptionContent(exceptionContent);
errorModel.setMessage(ex.messageToBase64());
errorModel.setResponse(ex.webcastResponseToBase64());
} else {
errorModel.setHostName(tiktokUser);
errorModel.setErrorName(exception.getClass().getSimpleName());
errorModel.setErrorType("error-system");
errorModel.setExceptionContent(exceptionContent);
errorModel.setMessage("");
errorModel.setResponse("");
}
tikTokDatabase.insertError(errorModel);
liveClient.getLogger().info("ERROR: " + errorModel.getErrorName());
exception.printStackTrace();
})
.buildAndConnectAsync();
}
}

View File

@@ -0,0 +1,223 @@
/*
* 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.tools.collector.client;
import io.github.jwdeveloper.tiktok.TikTokLive;
import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.http.TikTokHttpResponseEvent;
import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketMessageEvent;
import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketResponseEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveMessageException;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import io.github.jwdeveloper.tiktok.tools.collector.api.DataCollector;
import io.github.jwdeveloper.tiktok.tools.collector.api.TikTokDataCollectorModel;
import io.github.jwdeveloper.tiktok.tools.db.TikTokDatabase;
import io.github.jwdeveloper.tiktok.tools.db.tables.ExceptionInfoModel;
import io.github.jwdeveloper.tiktok.tools.db.tables.TikTokDataTable;
import io.github.jwdeveloper.tiktok.tools.db.tables.TikTokErrorModel;
import io.github.jwdeveloper.tiktok.utils.JsonUtil;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
public class TikTokDataCollector implements DataCollector {
private final TikTokDataCollectorModel dataCollectorModel;
private final TikTokDatabase tikTokDatabase;
private final List<LiveClient> tiktokClients;
public TikTokDataCollector(TikTokDataCollectorModel dataCollectorModel, TikTokDatabase tikTokDatabase) {
this.dataCollectorModel = dataCollectorModel;
this.tikTokDatabase = tikTokDatabase;
this.tiktokClients = new ArrayList<>();
}
public void connect() {
try {
if (!tikTokDatabase.isConnected()) {
tikTokDatabase.connect();
}
for (var user : dataCollectorModel.getUsers()) {
var client = createLiveClient(user);
tiktokClients.add(client);
client.connectAsync();
}
} catch (Exception e) {
throw new RuntimeException("Unable to start tiktok connector", e);
}
}
public void disconnect() {
disconnect(false);
}
@Override
public void disconnect(boolean keepDatabase) {
try {
for (var client : tiktokClients) {
client.disconnect();
}
if (!keepDatabase) {
tikTokDatabase.close();
}
} catch (Exception e) {
throw new RuntimeException("Unable to stop tiktok connector", e);
}
}
public LiveClient createLiveClient(String tiktokUser) {
var builder = TikTokLive.newClient(tiktokUser);
builder.onConnected((liveClient, event) ->
{
liveClient.getLogger().info("Connected to " + liveClient.getRoomInfo().getHostName());
})
.onDisconnected((liveClient, event) ->
{
liveClient.getLogger().info("Disconnected " + liveClient.getRoomInfo().getHostName());
})
.onWebsocketResponse(this::handleResponseAndMessages)
.onWebsocketMessage(this::handleMappedEvent)
.onHttpResponse((liveClient, event) ->
{
var data = createHttpResponseData(event, tiktokUser);
tikTokDatabase.insertData(data);
})
.onError(this::handleError);
dataCollectorModel.getOnConfigureLiveClient().accept(builder);
return builder.build();
}
private void handleResponseAndMessages(LiveClient client, TikTokWebsocketResponseEvent event) {
var responseData = createResponseData(event.getResponse(), client.getRoomInfo().getHostName());
tikTokDatabase.insertData(responseData);
var filter = dataCollectorModel.getMessagesFilter();
for (var message : event.getResponse().getMessagesList()) {
if (filter.isEmpty()) {
var data = createMessageData(message, client.getRoomInfo().getHostName());
tikTokDatabase.insertData(data);
continue;
}
if (!filter.contains(message.getMethod())) {
continue;
}
var data = createMessageData(message, client.getRoomInfo().getHostName());
tikTokDatabase.insertData(data);
}
}
private void handleMappedEvent(LiveClient client, TikTokWebsocketMessageEvent messageEvent) {
var event = messageEvent.getEvent();
var eventName = event.getClass().getSimpleName();
var filter = dataCollectorModel.getEventsFilter();
if (filter.isEmpty()) {
var data = createEventData(event, client.getRoomInfo().getHostName());
tikTokDatabase.insertData(data);
return;
}
if (!filter.contains(eventName)) {
return;
}
var data = createEventData(event, client.getRoomInfo().getHostName());
tikTokDatabase.insertData(data);
}
private void handleError(LiveClient client, TikTokErrorEvent event) {
var exception = event.getException();
var userName = client.getRoomInfo().getHostName();
var exceptionContent = ExceptionInfoModel.getStackTraceAsString(exception);
var errorModel = new TikTokErrorModel();
if (exception instanceof TikTokLiveMessageException ex) {
errorModel.setHostName(userName);
errorModel.setErrorName(ex.messageMethod());
errorModel.setErrorType("error-message");
errorModel.setExceptionContent(exceptionContent);
errorModel.setMessage(ex.messageToBase64());
errorModel.setResponse(ex.webcastResponseToBase64());
} else {
errorModel.setHostName(userName);
errorModel.setErrorName(exception.getClass().getSimpleName());
errorModel.setErrorType("error-system");
errorModel.setExceptionContent(exceptionContent);
errorModel.setMessage("");
errorModel.setResponse("");
}
tikTokDatabase.insertError(errorModel);
client.getLogger().info("ERROR: " + errorModel.getErrorName());
exception.printStackTrace();
}
private TikTokDataTable createHttpResponseData(TikTokHttpResponseEvent response, String tiktokUser) {
var base64 = JsonUtil.toJson(response);
var data = new TikTokDataTable();
data.setSessionTag(dataCollectorModel.getSessionTag());
data.setTiktokUser(tiktokUser);
data.setDataType("response");
data.setDataTypeName("Http");
data.setContent(base64);
return data;
}
private TikTokDataTable createResponseData(WebcastResponse response, String tiktokUser) {
var base64 = Base64.getEncoder().encodeToString(response.toByteArray());
var data = new TikTokDataTable();
data.setSessionTag(dataCollectorModel.getSessionTag());
data.setTiktokUser(tiktokUser);
data.setDataType("response");
data.setDataTypeName("WebcastResponse");
data.setContent(base64);
return data;
}
private TikTokDataTable createMessageData(WebcastResponse.Message message, String tiktokUser) {
var base64 = Base64.getEncoder().encodeToString(message.getPayload().toByteArray());
var data = new TikTokDataTable();
data.setSessionTag(dataCollectorModel.getSessionTag());
data.setTiktokUser(tiktokUser);
data.setDataType("message");
data.setDataTypeName(message.getMethod());
data.setContent(base64);
return data;
}
private TikTokDataTable createEventData(TikTokEvent event, String tiktokUser) {
var base64 = JsonUtil.toJson(event);
var data = new TikTokDataTable();
data.setSessionTag(dataCollectorModel.getSessionTag());
data.setTiktokUser(tiktokUser);
data.setDataType("event");
data.setDataTypeName(event.getClass().getSimpleName());
data.setContent(base64);
return data;
}
}

View File

@@ -0,0 +1,138 @@
/*
* 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.tools.collector.client;
import com.google.protobuf.GeneratedMessageV3;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
import io.github.jwdeveloper.tiktok.tools.collector.api.DataCollectorBuilder;
import io.github.jwdeveloper.tiktok.tools.collector.api.DataCollector;
import io.github.jwdeveloper.tiktok.tools.collector.api.TikTokDataCollectorModel;
import io.github.jwdeveloper.tiktok.tools.db.TikTokDatabase;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.UUID;
import java.util.function.Consumer;
public class TikTokDataCollectorBuilder implements DataCollectorBuilder {
TikTokDataCollectorModel dataModel;
TikTokDatabase database;
public TikTokDataCollectorBuilder(String outputName) {
dataModel = new TikTokDataCollectorModel();
dataModel.setOutputName(outputName);
dataModel.setUsers(new ArrayList<>());
dataModel.setEventsFilter(new HashSet<>());
dataModel.setMessagesFilter(new HashSet<>());
dataModel.setOutputPath("...");
dataModel.setOnConfigureLiveClient((e) -> {
});
}
@Override
public DataCollectorBuilder addUser(String user) {
dataModel.getUsers().add(user);
return this;
}
@Override
public TikTokDataCollectorBuilder addMessageFilter(Class<? extends GeneratedMessageV3> message) {
dataModel.getMessagesFilter().add(message.getSimpleName());
return this;
}
@Override
public TikTokDataCollectorBuilder addMessageFilter(String message) {
dataModel.getMessagesFilter().add(message);
return this;
}
@Override
public TikTokDataCollectorBuilder addEventFilter(Class<? extends TikTokEvent> event) {
dataModel.getEventsFilter().add(event.getSimpleName());
return this;
}
@Override
public TikTokDataCollectorBuilder addEventFilter(String event) {
dataModel.getEventsFilter().add(event);
return this;
}
@Override
public DataCollectorBuilder setOutputPath(String path) {
dataModel.setOutputPath(path);
return this;
}
@Override
public DataCollectorBuilder setSessionTag(String sessionTimestamp) {
dataModel.setSessionTag(sessionTimestamp);
return this;
}
@Override
public DataCollectorBuilder setDatabase(TikTokDatabase database)
{
this.database =database;
return this;
}
@Override
public DataCollectorBuilder configureLiveClient(Consumer<LiveClientBuilder> consumer) {
dataModel.setOnConfigureLiveClient(consumer);
return this;
}
@Override
public DataCollector buildAndRun() {
var collector = build();
collector.connect();
return collector;
}
@Override
public DataCollector build() {
if (dataModel.getSessionTag().isEmpty()) {
dataModel.setSessionTag(UUID.randomUUID().toString());
}
if(database == null)
{
database = new TikTokDatabase(dataModel.getOutputName());
}
var dataCollector = new TikTokDataCollector(dataModel, database);
return dataCollector;
}
}

View File

@@ -1,99 +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.tools.collector.client;
import io.github.jwdeveloper.tiktok.TikTokLiveClientBuilder;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
import io.github.jwdeveloper.tiktok.tools.collector.db.TikTokDatabase;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class TikTokMessagessCollectorBuilder {
List<String> users;
String outputFileName;
List<Class<?>> filters;
Consumer<LiveClientBuilder> onBuilder;
List<LiveClient> tiktokclients;
MessageCollector messageCollector;
public TikTokMessagessCollectorBuilder(String outputName) {
users = new ArrayList<>();
outputFileName = outputName;
filters = new ArrayList<>();
onBuilder = (e) -> {
};
tiktokclients = new ArrayList<>();
messageCollector = new MessageCollector(outputName);
}
public TikTokMessagessCollectorBuilder(MessageCollector messageCollector, String outputFileName) {
this(outputFileName);
this.messageCollector = messageCollector;
}
public TikTokMessagessCollectorBuilder setOutputName(String name) {
outputFileName = name;
return this;
}
public TikTokMessagessCollectorBuilder addOnBuilder(Consumer<LiveClientBuilder> consumer) {
onBuilder = consumer;
return this;
}
public TikTokMessagessCollectorBuilder addUser(String user) {
users.add(user);
return this;
}
public TikTokMessagessCollectorBuilder addEventFilter(Class<?> event) {
filters.add(event);
return this;
}
public MessageCollector buildAndRun() throws SQLException {
var db = new TikTokDatabase(outputFileName);
db.init();
var factory = new TikTokClientFactory(messageCollector, db);
for (var user : users) {
var client = factory.runClientAsync(user,filters, onBuilder);
client.thenAccept(liveClient ->
{
tiktokclients.add(liveClient);
});
}
return messageCollector;
}
public void stop() {
for (var client : tiktokclients) {
client.disconnect();
}
}
}

View File

@@ -1,92 +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.tools.collector.db;
import io.github.jwdeveloper.tiktok.tools.collector.tables.TikTokErrorModel;
import io.github.jwdeveloper.tiktok.tools.collector.tables.TikTokMessageModel;
import io.github.jwdeveloper.tiktok.tools.collector.tables.TikTokResponseModel;
import org.jdbi.v3.core.Jdbi;
import org.jdbi.v3.sqlobject.SqlObjectPlugin;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
public class TikTokDatabase {
private final String database;
private TikTokMessageModelDAO messagesTable;
private TikTokErrorModelDAO errorTable;
private TikTokResponseModelDAO responseTable;
public TikTokDatabase(String database) {
this.database = database;
}
public void init() throws SQLException {
var jdbcUrl = "jdbc:sqlite:" + database + ".db";
DriverManager.getConnection(jdbcUrl);
var jdbi = Jdbi.create(jdbcUrl)
.installPlugin(new SqlObjectPlugin());
jdbi.useHandle(handle -> {
handle.execute(SqlConsts.CREATE_MESSAGES_TABLE);
handle.execute(SqlConsts.CREATE_ERROR_TABLE);
handle.execute(SqlConsts.CREATE_RESPONSE_MODEL);
});
messagesTable = jdbi.onDemand(TikTokMessageModelDAO.class);
errorTable = jdbi.onDemand(TikTokErrorModelDAO.class);
responseTable = jdbi.onDemand(TikTokResponseModelDAO.class);
}
public void insertMessage(TikTokMessageModel message) {
message.setCreatedAt(getTime());
messagesTable.insertTikTokMessage(message);
}
public void insertError(TikTokErrorModel message) {
message.setCreatedAt(getTime());
errorTable.insertTikTokMessage(message);
}
public void insertResponse(TikTokResponseModel message) {
message.setCreatedAt(getTime());
responseTable.insert(message);
}
public List<TikTokErrorModel> selectErrors() {
return errorTable.selectErrors();
}
public List<TikTokMessageModel> selectMessages() {
return messagesTable.select();
}
public List<TikTokResponseModel> selectResponces() {
return responseTable.select();
}
private String getTime() {
return new SimpleDateFormat("dd:MM:yyyy HH:mm:ss.SSS").format(new Date());
}
}

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.tools.collector.db;
import io.github.jwdeveloper.tiktok.tools.collector.tables.TikTokErrorModel;
import io.github.jwdeveloper.tiktok.tools.collector.tables.TikTokMessageModel;
import org.jdbi.v3.sqlobject.config.RegisterBeanMapper;
import org.jdbi.v3.sqlobject.customizer.BindBean;
import org.jdbi.v3.sqlobject.statement.SqlQuery;
import org.jdbi.v3.sqlobject.statement.SqlUpdate;
import java.util.List;
@RegisterBeanMapper(TikTokMessageModel.class)
public interface TikTokMessageModelDAO
{
@SqlUpdate("INSERT INTO TikTokMessageModel (hostName, eventName,type, eventContent, createdAt) " +
"VALUES (:hostName, :eventName, :type, :eventContent, :createdAt)")
void insertTikTokMessage(@BindBean TikTokMessageModel message);
@SqlQuery("SELECT * FROM TikTokMessageModel")
List<TikTokMessageModel> select();
}

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.tools.collector.db;
import io.github.jwdeveloper.tiktok.tools.collector.tables.TikTokMessageModel;
import io.github.jwdeveloper.tiktok.tools.collector.tables.TikTokResponseModel;
import org.jdbi.v3.sqlobject.config.RegisterBeanMapper;
import org.jdbi.v3.sqlobject.customizer.BindBean;
import org.jdbi.v3.sqlobject.statement.SqlQuery;
import org.jdbi.v3.sqlobject.statement.SqlUpdate;
import java.util.List;
@RegisterBeanMapper(TikTokResponseModel.class)
public interface TikTokResponseModelDAO
{
@SqlUpdate("INSERT INTO TikTokResponseModel (hostName, response, createdAt) " +
"VALUES (:hostName, :response, :createdAt)")
void insert(@BindBean TikTokResponseModel message);
@SqlQuery("SELECT * FROM TikTokResponseModel")
List<TikTokResponseModel> select();
}

View File

@@ -20,17 +20,19 @@
* 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.tools.collector.db;
package io.github.jwdeveloper.tiktok.tools.db;
public class SqlConsts
{
public static String CREATE_MESSAGES_TABLE = """
CREATE TABLE IF NOT EXISTS TikTokMessageModel (
public static String CREATE_DATA_TABLE = """
CREATE TABLE IF NOT EXISTS TikTokData (
id INTEGER PRIMARY KEY AUTOINCREMENT,
hostName TEXT,
type TEXT,
eventName TEXT,
eventContent TEXT,
sessionTag TEXT,
tiktokUser TEXT,
dataType TEXT,
dataTypeName TEXT,
content TEXT,
createdAt TEXT
);
""";

View File

@@ -0,0 +1,77 @@
/*
* 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.tools.db;
import io.github.jwdeveloper.tiktok.tools.db.tables.TikTokDataTable;
import org.jdbi.v3.sqlobject.config.RegisterBeanMapper;
import org.jdbi.v3.sqlobject.customizer.Bind;
import org.jdbi.v3.sqlobject.customizer.BindBean;
import org.jdbi.v3.sqlobject.statement.SqlQuery;
import org.jdbi.v3.sqlobject.statement.SqlUpdate;
import java.util.List;
import java.util.Map;
@RegisterBeanMapper(TikTokDataTable.class)
public interface TikTokDataTableDAO {
String query = """
INSERT INTO TikTokData (sessionTag, tiktokUser, dataType, dataTypeName, content, createdAt) VALUES (:sessionTag, :tiktokUser, :dataType, :dataTypeName, :content, :createdAt)
""";
@SqlUpdate(query)
void insertData(@BindBean TikTokDataTable data);
@SqlQuery("SELECT * FROM TikTokData WHERE sessionTag = :sessionTag")
List<TikTokDataTable> selectBySession(@Bind("sessionTag") String sessionTag);
@SqlQuery("SELECT * FROM TikTokData WHERE dataType = :dataType AND sessionTag = :sessionTag AND tiktokUser = :tiktokUser")
List<TikTokDataTable> selectSessionData(@Bind("dataType") String dataType,
@Bind("sessionTag") String sessionTag,
@Bind("tiktokUser") String user);
@SqlQuery("SELECT * FROM TikTokData WHERE sessionTag = :sessionTag AND tiktokUser = :tiktokUser AND dataType = \"response\"")
List<TikTokDataTable> selectResponces(@Bind("sessionTag") String sessionTag, @Bind("tiktokUser") String user);
@SqlQuery("SELECT * FROM TikTokData WHERE sessionTag = :sessionTag AND tiktokUser = :tiktokUser AND dataType = \"event\"")
List<TikTokDataTable> selectBySessionEvents(@Bind("sessionTag") String sessionTag, @Bind("tiktokUser") String userName);
@SqlQuery("SELECT * FROM TikTokData WHERE sessionTag = :sessionTag AND tiktokUser = :tiktokUser AND dataType = \"message\"")
List<TikTokDataTable> selectBySessionMessages(@Bind("sessionTag") String sessionTag, @Bind("tiktokUser") String userName);
@SqlQuery("SELECT tiktokUser FROM TikTokData GROUP BY tiktokUser")
List<String> getUsers();
@SqlQuery("SELECT sessionTag FROM TikTokData WHERE tiktokUser = :tiktokUser GROUP BY sessionTag")
List<String> getSessionTagByUser(@Bind("tiktokUser") String tiktokUser);
String groupByDataTypeNameQuery = """
SELECT dataTypeName, COUNT(*) as count
FROM TikTokData
WHERE dataType = 'message' AND sessionTag = :sessionTag AND tiktokUser = :userName
GROUP BY dataTypeName
""";
}

View File

@@ -0,0 +1,138 @@
/*
* 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.tools.db;
import io.github.jwdeveloper.tiktok.tools.db.tables.TikTokDataTable;
import io.github.jwdeveloper.tiktok.tools.db.tables.TikTokErrorModel;
import lombok.Getter;
import org.jdbi.v3.core.Jdbi;
import org.jdbi.v3.sqlobject.SqlObjectPlugin;
import org.sqlite.SQLiteConfig;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class TikTokDatabase {
private final String database;
private TikTokErrorModelDAO errorTable;
@Getter
private TikTokDataTableDAO dataTableDAO;
private Connection connection;
public TikTokDatabase(String database) {
this.database = database;
}
public boolean isConnected()
{
return connection != null;
}
public void connect() throws SQLException {
var jdbcUrl = "jdbc:sqlite:" + database + ".db";
var config = new SQLiteConfig();
config.setEncoding(SQLiteConfig.Encoding.UTF8);
connection = DriverManager.getConnection(jdbcUrl, config.toProperties());
var jdbi = Jdbi.create(jdbcUrl).installPlugin(new SqlObjectPlugin());
jdbi.useHandle(handle -> {
handle.execute(SqlConsts.CREATE_DATA_TABLE);
handle.execute(SqlConsts.CREATE_ERROR_TABLE);
});
dataTableDAO = jdbi.onDemand(TikTokDataTableDAO.class);
errorTable = jdbi.onDemand(TikTokErrorModelDAO.class);
}
public void close() throws SQLException {
connection.close();
}
public void insertData(TikTokDataTable tikTokDataTable) {
tikTokDataTable.setCreatedAt(getTime());
dataTableDAO.insertData(tikTokDataTable);
}
public List<TikTokDataTable> getSessionResponces(String sessionTag, String userName) {
return dataTableDAO.selectResponces(sessionTag, userName);
}
public List<String> getDataNames(String dataType, String sessionTag, String userName) {
try {
var sb = new StringBuilder();
sb.append("""
SELECT dataTypeName, COUNT(*) as count
FROM TikTokData
""");
sb.append(" WHERE dataType = \""+dataType+"\" ");
sb.append(" AND tiktokUser = \"" + userName + "\" ");
sb.append(" AND sessionTag = \"" + sessionTag + "\" ");
sb.append("GROUP BY dataTypeName");
var statement = connection.prepareStatement(sb.toString());
var resultSet = statement.executeQuery();
List<String> dataTypeCounts = new ArrayList<>();
while (resultSet.next()) {
var dataTypeName = resultSet.getString("dataTypeName");
dataTypeCounts.add(dataTypeName);
}
resultSet.close();
statement.close();
return dataTypeCounts;
} catch (Exception e) {
e.printStackTrace();
return List.of("error");
}
}
public List<TikTokDataTable> getSessionMessages(String sessionTag, String userName, int count) {
return dataTableDAO.selectBySessionMessages(sessionTag, userName);
}
public void insertError(TikTokErrorModel message) {
message.setCreatedAt(getTime());
errorTable.insertTikTokMessage(message);
}
public List<TikTokErrorModel> selectErrors() {
return errorTable.selectErrors();
}
private String getTime() {
return new SimpleDateFormat("dd:MM:yyyy HH:mm:ss.SSS").format(new Date());
}
}

View File

@@ -20,9 +20,9 @@
* 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.tools.collector.db;
package io.github.jwdeveloper.tiktok.tools.db;
import io.github.jwdeveloper.tiktok.tools.collector.tables.TikTokErrorModel;
import io.github.jwdeveloper.tiktok.tools.db.tables.TikTokErrorModel;
import org.jdbi.v3.sqlobject.config.RegisterBeanMapper;
import org.jdbi.v3.sqlobject.customizer.BindBean;

View File

@@ -20,7 +20,7 @@
* 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.tools.collector.tables;
package io.github.jwdeveloper.tiktok.tools.db.tables;
import java.io.PrintWriter;
import java.io.StringWriter;

View File

@@ -20,23 +20,24 @@
* 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.tools.collector.tables;
package io.github.jwdeveloper.tiktok.tools.db.tables;
import lombok.Data;
@Data
public class TikTokMessageModel
public class TikTokDataTable
{
private Integer id;
private String hostName;
private String sessionTag;
private String eventName;
private String tiktokUser;
private String type;
private String dataType;
private String message;
private String dataTypeName;
private String content;
private String createdAt;
}

View File

@@ -20,7 +20,7 @@
* 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.tools.collector.tables;
package io.github.jwdeveloper.tiktok.tools.db.tables;
import lombok.Data;

View File

@@ -1,77 +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.tools.tester;
import com.google.protobuf.ByteString;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import io.github.jwdeveloper.tiktok.mockClient.TikTokClientMock;
import io.github.jwdeveloper.tiktok.tools.collector.db.TikTokDatabase;
import io.github.jwdeveloper.tiktok.tools.collector.tables.TikTokResponseModel;
import io.github.jwdeveloper.tiktok.tools.util.MessageUtil;
import io.github.jwdeveloper.tiktok.utils.ConsoleColors;
import io.github.jwdeveloper.tiktok.utils.JsonUtil;
import java.util.logging.ConsoleHandler;
import java.util.logging.Formatter;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
public class RunDbTester {
public static void main(String[] args) throws Exception {
var db = new TikTokDatabase("test");
db.init();
var responses = db.selectResponces().stream().map(TikTokResponseModel::getResponse).toList();
var client = TikTokClientMock
.create()
.addResponses(responses)
.onWebsocketUnhandledMessage((liveClient, event) ->
{
var sb = new StringBuilder();
sb.append("Unhandled Message! " );
sb.append(event.getData().getMethod());
sb.append(MessageUtil.getContent(event.getData()));
liveClient.getLogger().info(sb.toString());
})
.onWebsocketMessage((liveClient, event) ->
{
var sb = new StringBuilder();
sb.append(event.getEvent().getClass().getSimpleName());
sb.append(event.getEvent().toJson());
liveClient.getLogger().fine(sb.toString());
})
.build();
client.connect();
}
}

View File

@@ -23,8 +23,8 @@
package io.github.jwdeveloper.tiktok.tools.tester;
import com.google.gson.JsonParser;
import io.github.jwdeveloper.tiktok.mockClient.TikTokClientMock;
import io.github.jwdeveloper.tiktok.mockClient.mocks.LiveClientMock;
import io.github.jwdeveloper.tiktok.tools.tester.mockClient.TikTokLiveMock;
import io.github.jwdeveloper.tiktok.tools.tester.mockClient.mocks.LiveClientMock;
import io.github.jwdeveloper.tiktok.tools.util.MessageUtil;
import java.io.FileReader;
@@ -37,7 +37,7 @@ public class RunJsonTester {
public static void main(String[] args) throws IOException {
var messages = getMessages();
var client =(LiveClientMock) TikTokClientMock.create()
var client =(LiveClientMock) TikTokLiveMock.create()
.onWebsocketUnhandledMessage((liveClient, event) ->
{
var sb = new StringBuilder();

View File

@@ -0,0 +1,90 @@
/*
* 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.tools.tester;
import io.github.jwdeveloper.tiktok.tools.db.TikTokDatabase;
import io.github.jwdeveloper.tiktok.tools.db.tables.TikTokDataTable;
import io.github.jwdeveloper.tiktok.tools.tester.api.DataTester;
import io.github.jwdeveloper.tiktok.tools.tester.api.DataTesterModel;
import io.github.jwdeveloper.tiktok.tools.tester.mockClient.TikTokLiveMock;
import io.github.jwdeveloper.tiktok.tools.tester.mockClient.mocks.LiveClientMock;
import java.util.LinkedList;
import java.util.Queue;
public class TikTokDataTester implements DataTester {
private DataTesterModel model;
private LiveClientMock client;
private Queue<TikTokDataTable> data;
private TikTokDatabase database;
public TikTokDataTester(DataTesterModel model) {
this.model = model;
}
@Override
public void connect() {
try {
database = new TikTokDatabase(model.getDatabaseName());
database.connect();
var mockBuilder = TikTokLiveMock.create();
model.getBuilderConsumer().accept(mockBuilder);
client = mockBuilder.build();
var respocnes = database.getSessionResponces(model.getSessionTag(), model.getUser());
data = new LinkedList<>(respocnes);
client.connect();
while (!data.isEmpty()) {
nextResponse();
}
} catch (Exception e) {
throw new RuntimeException("Error while running tester", e);
}
}
@Override
public void nextResponse() {
try {
var responce = data.poll();
client.publishResponse(responce.getContent());
Thread.sleep(1);
} catch (Exception e) {
throw new RuntimeException("Unable to run response!");
}
}
@Override
public void disconnect() {
try {
client.disconnect();
database.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* 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.tools.tester;
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
import io.github.jwdeveloper.tiktok.tools.tester.api.DataTester;
import io.github.jwdeveloper.tiktok.tools.tester.api.DataTesterBuilder;
import io.github.jwdeveloper.tiktok.tools.tester.api.DataTesterModel;
import java.util.function.Consumer;
public class TikTokDataTesterBuilder implements DataTesterBuilder {
private final DataTesterModel model;
public TikTokDataTesterBuilder(String databaseName) {
this.model = new DataTesterModel();
this.model.setDatabaseName(databaseName);
}
@Override
public DataTesterBuilder setSessionTag(String sessionTag) {
model.setSessionTag(sessionTag);
return this;
}
@Override
public DataTesterBuilder setUser(String user) {
model.setUser(user);
return this;
}
@Override
public DataTesterBuilder configureLiveClient(Consumer<LiveClientBuilder> builderConsumer) {
model.setBuilderConsumer(builderConsumer);
return this;
}
@Override
public DataTester build() {
return new TikTokDataTester(model);
}
@Override
public DataTester buildAndRun()
{
var tester = build();
tester.connect();
return tester;
}
}

View File

@@ -0,0 +1,32 @@
/*
* 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.tools.tester.api;
public interface DataTester
{
void connect();
void nextResponse();
void disconnect();
}

View File

@@ -0,0 +1,41 @@
/*
* 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.tools.tester.api;
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
import java.util.function.Consumer;
public interface DataTesterBuilder {
DataTesterBuilder setSessionTag(String sessionTag);
DataTesterBuilder setUser(String user);
DataTesterBuilder configureLiveClient(Consumer<LiveClientBuilder> builderConsumer);
DataTester build();
DataTester buildAndRun();
}

View File

@@ -0,0 +1,39 @@
/*
* 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.tools.tester.api;
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
import lombok.Data;
import java.util.function.Consumer;
@Data
public class DataTesterModel {
String databaseName;
String sessionTag;
String user;
Consumer<LiveClientBuilder> builderConsumer = (a) -> {
};
}

View File

@@ -20,9 +20,9 @@
* 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.mockClient;
package io.github.jwdeveloper.tiktok.tools.tester.mockClient;
public class TikTokClientMock
public class TikTokLiveMock
{
public static TikTokMockBuilder create(String host)
{

View File

@@ -20,25 +20,22 @@
* 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.mockClient;
package io.github.jwdeveloper.tiktok.tools.tester.mockClient;
import io.github.jwdeveloper.tiktok.TikTokLiveClientBuilder;
import io.github.jwdeveloper.tiktok.TikTokRoomInfo;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver;
import io.github.jwdeveloper.tiktok.handlers.TikTokMessageHandler;
import io.github.jwdeveloper.tiktok.mappers.events.TikTokGiftEventHandler;
import io.github.jwdeveloper.tiktok.mappers.events.TikTokRoomInfoEventHandler;
import io.github.jwdeveloper.tiktok.mappers.events.TikTokSocialMediaEventHandler;
import io.github.jwdeveloper.tiktok.http.TikTokCookieJar;
import io.github.jwdeveloper.tiktok.http.TikTokHttpClient;
import io.github.jwdeveloper.tiktok.http.TikTokHttpRequestFactory;
import io.github.jwdeveloper.tiktok.listener.TikTokListenersManager;
import io.github.jwdeveloper.tiktok.mappers.TikTokGenericEventMapper;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import io.github.jwdeveloper.tiktok.mockClient.mocks.ApiServiceMock;
import io.github.jwdeveloper.tiktok.mockClient.mocks.LiveClientMock;
import io.github.jwdeveloper.tiktok.mockClient.mocks.WebsocketClientMock;
import io.github.jwdeveloper.tiktok.tools.tester.mockClient.mocks.ApiServiceMock;
import io.github.jwdeveloper.tiktok.tools.tester.mockClient.mocks.LiveClientMock;
import io.github.jwdeveloper.tiktok.tools.tester.mockClient.mocks.WebsocketClientMock;
import java.util.Base64;
import java.util.List;
@@ -96,7 +93,7 @@ public class TikTokMockBuilder extends TikTokLiveClientBuilder {
var listenerManager = new TikTokListenersManager(listeners, tikTokEventHandler);
var giftManager = new TikTokGiftManager(logger);
var requestFactory = new TikTokHttpRequestFactory(cookie);
var requestFactory = new TikTokHttpRequestFactory(cookie, new TikTokEventObserver());
var apiClient = new TikTokHttpClient(cookie, requestFactory);
var apiService = new ApiServiceMock(apiClient, logger, clientSettings);
var mapper = createMapper(giftManager, tiktokRoomInfo);

View File

@@ -20,7 +20,7 @@
* 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.mockClient.mocks;
package io.github.jwdeveloper.tiktok.tools.tester.mockClient.mocks;
import io.github.jwdeveloper.tiktok.ClientSettings;
import io.github.jwdeveloper.tiktok.http.TikTokApiService;

View File

@@ -20,7 +20,7 @@
* 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.mockClient.mocks;
package io.github.jwdeveloper.tiktok.tools.tester.mockClient.mocks;
import io.github.jwdeveloper.tiktok.ClientSettings;
import io.github.jwdeveloper.tiktok.TikTokLiveClient;
@@ -54,8 +54,11 @@ public class LiveClientMock extends TikTokLiveClient {
listenersManager,
logger);
this.websocketClientMock = webSocketClient;
websocketClientMock.setClient(this);
}
public void publishMessage(String type, String base64) {
websocketClientMock.addMessage(type, base64);
}
@@ -75,5 +78,4 @@ public class LiveClientMock extends TikTokLiveClient {
public void publishResponse(WebcastResponse message) {
websocketClientMock.addResponse(message);
}
}

View File

@@ -20,7 +20,7 @@
* 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.mockClient.mocks;
package io.github.jwdeveloper.tiktok.tools.tester.mockClient.mocks;
import com.google.protobuf.ByteString;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
@@ -39,12 +39,19 @@ import java.util.logging.Logger;
public class WebsocketClientMock implements SocketClient {
Logger logger;
Stack<WebcastResponse> responses;
Stack<MsgStruct> messages;
TikTokMessageHandler messageHandler;
LiveClient client;
Thread thread;
private boolean isRunning;
public void setClient(LiveClientMock liveClientMock) {
this.client = liveClientMock;
}
@Value
public static class MsgStruct {
String messageType;
@@ -87,38 +94,28 @@ public class WebsocketClientMock implements SocketClient {
@Override
public void start(WebcastResponse webcastResponse, LiveClient tikTokLiveClient) {
logger.info("Running message: " + responses.size());
isRunning = true;
while (!responses.isEmpty() || !messages.isEmpty()) {
if (!responses.isEmpty()) {
thread = new Thread(() ->
{
while (isRunning)
{
while (!responses.isEmpty()) {
var response = responses.pop();
for (var message : response.getMessagesList()) {
try {
messageHandler.handleSingleMessage(tikTokLiveClient, message);
} catch (Exception e) {
logger.info("Unable to parse message for response " + response.getCursor());
throw new TikTokLiveMessageException(message, response, e);
}
}
}
if (!messages.isEmpty()) {
var messageStr = messages.pop();
try {
var msg = WebcastResponse.Message.newBuilder()
.setMethod(messageStr.messageType)
.setPayload(ByteString.copyFrom(messageStr.getMessageValue()))
.build();
messageHandler.handleSingleMessage(tikTokLiveClient, msg);
} catch (Exception e) {
logger.info("Unable to parse message for response " + messageStr.getMessageType());
throw new TikTokLiveException(e);
}
messageHandler.handle(client, response);
}
}
});
isRunning = true;
thread.start();
}
@Override
public void stop() {
isRunning = true;
isRunning = false;
thread.interrupt();
}
}

View File

@@ -42,16 +42,16 @@ public class MessageUtil {
}
}
public static String getContent(String methodName, byte[] bytes) {
public static String getContent(String messageName, byte[] bytes) {
try {
var inputClazz = Class.forName("io.github.jwdeveloper.tiktok.messages.webcast." + methodName);
var inputClazz = Class.forName("io.github.jwdeveloper.tiktok.messages.webcast." + messageName);
var parseMethod = inputClazz.getDeclaredMethod("parseFrom", byte[].class);
var deserialized = parseMethod.invoke(null, bytes);
return JsonUtil.messageToJson(deserialized);
} catch (Exception ex) {
var sb = new StringBuilder();
sb.append("Can not find protocolbuffer file message representation for " + methodName);
sb.append("Can not find protocol-buffer file message representation for " + messageName);
sb.append("\n");
var structure = ProtocolUtils.getProtocolBufferStructure(bytes);
var json =structure.toJson();

File diff suppressed because one or more lines are too long

View File

@@ -22,13 +22,35 @@
*/
package io.github.jwdeveloper.tiktok.webviewer;
import io.github.jwdeveloper.tiktok.tools.db.TikTokDatabase;
import io.github.jwdeveloper.tiktok.webviewer.handlers.TikTokHandler;
import io.github.jwdeveloper.tiktok.webviewer.services.TikTokCollectorService;
import io.github.jwdeveloper.tiktok.webviewer.services.TikTokDatabaseService;
import io.javalin.Javalin;
public class Main {
public static void main(String[] args) {
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.sql.SQLException;
import java.util.concurrent.ExecutionException;
var manager = new TikTokManager();
public class Main {
public static void main(String[] args) throws SQLException, ExecutionException, InterruptedException, IOException {
var settings = new Settings();
settings.setUserName("szalonamoniaxx");
settings.setSessionTag("battle");
settings.setDbName("db-battle");
settings.setPort(8002);
var db = new TikTokDatabase(settings.getDbName());
db.connect();
var service = new TikTokDatabaseService(db);
var collectorService = new TikTokCollectorService(settings, db);
var handler = new TikTokHandler(service, settings, collectorService);
// var manager = new TikTokManager(service);
var app = Javalin.create(config ->
{
config.plugins.enableCors(corsContainer ->
@@ -39,13 +61,19 @@ public class Main {
});
});
config.staticFiles.add("/public");
}).start(8001);
}).start(settings.getPort());
var handler = new TikTokHandler(manager);
app.get("/tiktok/status", handler::connectionStatus);
app.get("/tiktok/connect", handler::connect);
app.get("/tiktok/disconnect", handler::disconnect);
app.get("/tiktok/events", handler::events);
app.get("/tiktok/events/pages", handler::eventPages);
app.get("/tiktok/events/message", handler::eventMessage);
app.get("/tiktok/data/pages", handler::getDataPages);
app.get("/tiktok/data/names", handler::getDataNames);
app.get("/tiktok/data", handler::getData);
app.get("/tiktok/update", handler::updateSearch);
app.get("/tiktok/sessions", handler::getUserSessionTags);
app.get("/tiktok/users", handler::getUsers);
app.get("/tiktok/data-types", handler::getDataTypes);
}
}

View File

@@ -0,0 +1,14 @@
package io.github.jwdeveloper.tiktok.webviewer;
import lombok.Data;
@Data
public class Settings
{
private int port;
private String dbName;
private String userName;
private String sessionTag;
}

View File

@@ -1,137 +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.webviewer;
import com.google.protobuf.InvalidProtocolBufferException;
import io.github.jwdeveloper.tiktok.tools.collector.client.MessageCollector;
import io.github.jwdeveloper.tiktok.tools.collector.client.TikTokMessageCollectorClient;
import io.github.jwdeveloper.tiktok.tools.collector.client.TikTokMessagessCollectorBuilder;
import io.github.jwdeveloper.tiktok.tools.util.MessageUtil;
import lombok.Value;
import java.sql.SQLException;
import java.util.Base64;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
public class TikTokManager {
TikTokMessagessCollectorBuilder client;
MessageCollector msgCollector;
public static String dbName= "log";
public TikTokManager() {
msgCollector = new MessageCollector(dbName);
}
public void connect(String name) throws SQLException {
disconnect();
client = TikTokMessageCollectorClient.create(msgCollector, dbName)
.addOnBuilder(liveClientBuilder ->
{
liveClientBuilder.onRoomInfo((liveClient, event) ->
{
});
liveClientBuilder.onGift((liveClient, event) ->
{
});
liveClientBuilder.onWebsocketUnhandledMessage((liveClient, event) ->
{
System.out.println("UNHANDLED MESSAGE! "+event.getMessage().getMethod());
});
})
.addUser(name);
client.buildAndRun();
}
public List<String> getEventsNames() {
return msgCollector.getMessages().keySet().stream().toList();
}
public List<String> getEventMessages(String eventName) {
return msgCollector.getMessages().get(eventName).stream().map(MessageCollector.MessageData::getEventData).toList();
}
public MessageDto getMessage(String event, String index) throws InvalidProtocolBufferException {
var eventData = msgCollector.getMessages().get(event);
var messages = eventData.stream().toList();
var random = new Random();
var msgIndex = 0;
if (index != null && !index.isEmpty()) {
msgIndex = Integer.parseInt(index);
msgIndex = Math.min(msgIndex, messages.size() - 1);
msgIndex = Math.max(msgIndex, 0);
}
var msg = messages.get(msgIndex);
var bytes = Base64.getDecoder().decode(msg.getEventData());
var content = MessageUtil.getContent(event, bytes);
return new MessageDto(content, msg.getEventData(), event);
}
public PagesDto getPages(String event) throws InvalidProtocolBufferException {
var eventData = msgCollector.getMessages().get(event);
var messages = eventData.stream().toList();
var counter = new AtomicInteger(-1);
var pages = messages.stream().map(e ->
{
return "http://localhost:8001/tiktok/events/message?eventName=" + event + "&page=" + counter.incrementAndGet();
}).toList();
return new PagesDto(event, messages.size(), pages);
}
@Value
public class PagesDto {
String eventName;
int pages;
List<String> links;
}
@Value
public class MessageDto {
String content;
String base64;
String eventName;
}
public void disconnect() {
if (client == null) {
return;
}
client.stop();
}
}

View File

@@ -0,0 +1,105 @@
/*
* 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.webviewer;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkLayerMessage;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkMessage;
import io.github.jwdeveloper.tiktok.tools.TikTokLiveTools;
import java.io.IOException;
public class ToolsExamples {
private static final String tiktokUser = "debb.cl";
private static final String db = "db-battle";
private static final String sessionTag = "gifts";
public static void main(String[] args) throws IOException {
// runCollector();
runCollector();
//runTester();
System.in.read();
}
/*
WebcastLinkMicArmies
WebcastLinkMicBattle
*/
//WebcastLinkMicArmies battle data?
//WebcastLinkMicBattlePunishFinish end of battle?
//WebcastLinkLayerMessage send after end of battle
// send after LinkLayer -> WebcastLinkMessage
private static void runCollector() {
TikTokLiveTools.createCollector(db)
.addUser(tiktokUser)
.setSessionTag(sessionTag)
.configureLiveClient(liveClientBuilder ->
{
liveClientBuilder.configure(clientSettings ->
{
clientSettings.setPrintToConsole(true);
});
liveClientBuilder.onWebsocketResponse((liveClient, event) ->
{
for (var msg : event.getResponse().getMessagesList()) {
System.out.println(msg.getMethod());
}
});
liveClientBuilder.onConnected((liveClient, event) ->
{
liveClient.getLogger().info("Connected");
});
}).buildAndRun();
}
private static void runTester() {
TikTokLiveTools.createTester(db)
.setSessionTag(sessionTag)
.setUser(tiktokUser)
.configureLiveClient(liveClientBuilder ->
{
liveClientBuilder.onError((liveClient, event) ->
{
event.getException().printStackTrace();
;
});
liveClientBuilder.onWebsocketResponse((liveClient, event) ->
{
System.out.println("Response =====================================");
for (var msg : event.getResponse().getMessagesList()) {
System.out.println("Message -> " + msg.getMethod());
}
});
liveClientBuilder.onEvent((liveClient, event) ->
{
System.out.println("Event -> " + event.getClass().getSimpleName());
});
})
.buildAndRun();
}
}

View File

@@ -25,61 +25,153 @@ package io.github.jwdeveloper.tiktok.webviewer.handlers;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.protobuf.InvalidProtocolBufferException;
import io.github.jwdeveloper.tiktok.webviewer.TikTokManager;
import io.github.jwdeveloper.tiktok.webviewer.Settings;
import io.github.jwdeveloper.tiktok.webviewer.services.TikTokCollectorService;
import io.github.jwdeveloper.tiktok.webviewer.services.TikTokDatabaseService;
import io.javalin.http.Context;
import lombok.Value;
import java.sql.SQLException;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class TikTokHandler {
private final TikTokManager tikTokManager;
private final TikTokDatabaseService databaseService;
private final Settings settings;
private final TikTokCollectorService collectorService;
public TikTokHandler(TikTokManager tikTokManager) {
this.tikTokManager = tikTokManager;
public TikTokHandler(TikTokDatabaseService databaseService,
Settings settings,
TikTokCollectorService collectorService) {
this.databaseService = databaseService;
this.settings = settings;
this.collectorService = collectorService;
}
public void connect(Context context) throws SQLException {
String name = context.queryParam("name");
if (name.equals(" ")) {
context.result("Name can not be empty");
context.status(400);
return;
}
tikTokManager.connect(name);
public void connect(Context context) {
var name = context.queryParam("name");
var sessionTag = context.queryParam("session");
System.out.println("Session tag" + sessionTag);
collectorService.start(name, sessionTag);
settings.setUserName(name);
context.status(200);
}
public void disconnect(Context context){
tikTokManager.disconnect();
context.status(200);
}
public void events(Context context) {
var events = tikTokManager.getEventsNames();
var gson = getGson();
var result = gson.toJson(events);
public void connectionStatus(Context context) {
var isWorking = collectorService.isRunning();
var result = getGson().toJson(isWorking);
context.result(result);
context.status(200);
}
public void eventMessage(Context context) throws InvalidProtocolBufferException {
String name = context.queryParam("eventName");
String page = context.queryParam("page");
public void disconnect(Context context) {
var result = tikTokManager.getMessage(name, page);
var gson = getGson();
context.result(gson.toJson(result));
collectorService.stop();
context.status(200);
}
public void eventPages(Context context) throws InvalidProtocolBufferException {
String name = context.queryParam("eventName");
var result = tikTokManager.getPages(name);
public void getUsers(Context context) {
var users = databaseService.getUsers();
var result = getGson().toJson(users);
context.result(result);
context.status(200);
}
public void getUserSessionTags(Context context) {
var dataType = context.queryParam("user");
var sessionsTags = databaseService.getSessionTag(dataType);
var result = getGson().toJson(sessionsTags);
context.result(result);
context.status(200);
}
public void getDataTypes(Context context) {
var result = getGson().toJson(List.of("event", "message", "response"));
context.result(result);
context.status(200);
}
public void updateSearch(Context context) {
var userName = context.queryParam("user");
var sessionTag = context.queryParam("session");
settings.setUserName(userName);
settings.setSessionTag(sessionTag);
context.status(200);
}
public void getDataNames(Context context) {
var dataType = context.queryParam("type");
var dataNames = databaseService.getDataNames(dataType, settings.getUserName(), settings.getSessionTag());
var gson = getGson();
context.result(gson.toJson(result));
var result = gson.toJson(dataNames);
context.result(result);
context.status(200);
}
public void getData(Context context) {
var page = context.queryParam("page");
var dataType = context.queryParam("type");
var dataName = context.queryParam("name");
if (page == null) {
page = "0";
}
var asProto = context.queryParam("asProto");
var asJson = asProto == null;
var dto = new TikTokDatabaseService.DatabaseDataDto(dataType, dataName, settings.getUserName(), settings.getSessionTag(), asJson);
var result = databaseService.getData(dto);
var content = result.get(Integer.parseInt(page));
var response = new MessageDto(content, "", dataName);
var gson = getGson();
context.result(gson.toJson(response));
}
public void getDataPages(Context context) throws InvalidProtocolBufferException {
var dataType = context.queryParam("type");
var dataName = context.queryParam("name");
var asJson = true;
var dto = new TikTokDatabaseService.DatabaseDataDto(dataType, dataName, settings.getUserName(), settings.getSessionTag(), asJson);
var result = databaseService.getData(dto);
var counter = new AtomicInteger(-1);
var pages = result.stream().map(e ->
{
return "http://localhost:" + settings.getPort() + "/tiktok/data?type=" + dataType + "&page=" + counter.incrementAndGet() + "&name=" + dataName;
}).toList();
var response = new PagesDto(dataName, counter.get(), pages);
var gson = getGson();
context.result(gson.toJson(response));
}
public Gson getGson() {
return new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create();
}
@Value
public class PagesDto {
String eventName;
int pages;
List<String> links;
}
@Value
public class MessageDto {
String content;
String base64;
String eventName;
}
}

View File

@@ -0,0 +1,89 @@
package io.github.jwdeveloper.tiktok.webviewer.services;
import io.github.jwdeveloper.tiktok.tools.TikTokLiveTools;
import io.github.jwdeveloper.tiktok.tools.collector.api.DataCollector;
import io.github.jwdeveloper.tiktok.tools.db.TikTokDatabase;
import io.github.jwdeveloper.tiktok.webviewer.Settings;
public class TikTokCollectorService {
private final Settings settings;
private final TikTokDatabase database;
private boolean isConnected = false;
private DataCollector collector;
public TikTokCollectorService(Settings settings, TikTokDatabase database) {
this.settings = settings;
this.database = database;
}
public void start(String user, String sessionTag) {
stop();
collector = createClient(user, sessionTag);
collector.connect();
}
public boolean isRunning()
{
if(collector == null)
{
return false;
}
return isConnected;
}
public void stop() {
if (collector != null) {
collector.disconnect(true);
}
isConnected =false;
collector = null;
try {
database.close();
database.connect();
}
catch (Exception e)
{
e.printStackTrace();
}
}
private DataCollector createClient(String user, String sessionTag) {
return TikTokLiveTools.createCollector("dupa")
.addUser(user)
.setSessionTag(sessionTag)
.setDatabase(database)
.configureLiveClient(liveClientBuilder ->
{
liveClientBuilder.configure(clientSettings ->
{
clientSettings.setPrintToConsole(true);
});
liveClientBuilder.onWebsocketResponse((liveClient, event) ->
{
for (var msg : event.getResponse().getMessagesList()) {
System.out.println(msg.getMethod());
}
});
liveClientBuilder.onDisconnected((liveClient, event) ->
{
liveClient.getLogger().info("Disconnected");
isConnected = false;
});
liveClientBuilder.onError((liveClient, event) ->
{
event.getException().printStackTrace();
});
liveClientBuilder.onConnected((liveClient, event) ->
{
liveClient.getLogger().info("Connected");
isConnected = true;
});
}).build();
}
}

View File

@@ -0,0 +1,118 @@
package io.github.jwdeveloper.tiktok.webviewer.services;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import io.github.jwdeveloper.tiktok.tools.db.TikTokDataTableDAO;
import io.github.jwdeveloper.tiktok.tools.db.TikTokDatabase;
import io.github.jwdeveloper.tiktok.tools.db.tables.TikTokDataTable;
import io.github.jwdeveloper.tiktok.tools.util.MessageUtil;
import io.github.jwdeveloper.tiktok.utils.JsonUtil;
import io.github.jwdeveloper.tiktok.utils.ProtocolUtils;
import java.util.Base64;
import java.util.List;
public class TikTokDatabaseService {
public TikTokDatabase tikTokDatabase;
private TikTokDataTableDAO table;
public record DatabaseDataDto(String dataType, String dataName, String user, String sessionTag, boolean asJson) {
}
public TikTokDatabaseService(TikTokDatabase databaseName) {
tikTokDatabase = databaseName;
table = tikTokDatabase.getDataTableDAO();
}
public List<String> getUsers() {
return table.getUsers();
}
public List<String> getSessionTag(String user) {
return table.getSessionTagByUser(user);
}
public List<String> getDataNames(String dataType, String user, String sessionTag) {
return tikTokDatabase.getDataNames(dataType, sessionTag, user);
}
public List<String> getData(DatabaseDataDto dataDto) {
var data = tikTokDatabase.getDataTableDAO().selectSessionData(dataDto.dataType(), dataDto.sessionTag(), dataDto.user());
switch (dataDto.dataType()) {
case "message" -> {
return getMessages(dataDto.dataName(), data, dataDto.asJson);
}
case "event" -> {
return getEvents(dataDto.dataName(), data);
}
case "response" -> {
return getResponse(data,dataDto.dataName());
}
}
return List.of("unknown dataType");
}
private List<String> getMessages(String messageName, List<TikTokDataTable> data, boolean asJson) {
var messages = data.stream()
.filter(e -> e.getDataTypeName().equals(messageName))
.map(e ->
{
try {
var bytes = Base64.getDecoder().decode(e.getContent());
if (asJson == false) {
return ProtocolUtils.getProtocolBufferStructure(bytes).toJson();
}
var parsedMessage = MessageUtil.getContent(messageName, bytes);
return parsedMessage;
} catch (Exception ex) {
ex.printStackTrace();
return "";
}
})
.toList();
return messages;
}
private List<String> getResponse(List<TikTokDataTable> data,String dataTypeName)
{
var messages = data.stream()
.filter(e -> e.getDataTypeName().equals(dataTypeName))
.map(e ->
{
if(e.getDataTypeName().equals("Http"))
{
return e.getContent();
}
try
{
var bytes = Base64.getDecoder().decode(e.getContent());
var parsedMessage = WebcastResponse.parseFrom(bytes);
var json = JsonUtil.toJson(parsedMessage);
return json;
} catch (Exception ex) {
ex.printStackTrace();
return "error";
}
})
.toList();
return messages;
}
private List<String> getEvents(String dataName, List<TikTokDataTable> data) {
var messages = data.stream()
.filter(e -> e.getDataTypeName().equals(dataName))
.map(TikTokDataTable::getContent)
.toList();
return messages;
}
}

View File

@@ -0,0 +1,135 @@
body, html {
height: 100%;
margin: 0;
display: flex;
flex-direction: column;
color: whitesmoke;
}
.header {
margin-bottom: 20px;
}
.connect-btn
{
width: 100%;
}
.dropdown
{
width: 100%;
}
.dropdown-toggle
{
background-color: #2c2c2c;
border-color: #2c2c2c;
width: 100%;
}
.btn-primary
{
background-color: #2c2c2c;
border-color: #2c2c2c;
}
.btn-primary:hover
{
background-color: #474747;
border-color: #474747;
}
.margin
{
margin-top: 2em;
}
.form
{
padding: 1em;
border-bottom: 2px solid #676767
}
.form-label
{
color: whitesmoke;
}
.editor-parent
{
height: 2000px;
}
.editor-container
{
resize: vertical;
overflow: auto;
}
.list-group-item:hover
{
background-color: #474747;
color: azure;
cursor: pointer;
}
.list-group-item
{
background-color: #2c2c2c;
color: azure;
border-color: #252424;
}
.margin-left
{
margin-right: 0.2em;
}
.content {
flex-grow: 1; /* Takes up the remaining space */
display: flex;
}
.col-md-10, .col-md-2 {
padding: 0; /* Remove default padding */
height: 100%;
}
.scrollable-element {
overflow-y: scroll; /* Enable vertical scrollbar */
/* Other styles as needed */
scrollbar-width: thin;
height: 800px;
scrollbar-color: #275c9c #000000; /* thumb and track color */
}
.dropdown-menu
{
width: 100%;
background-color: #414040;
color: whitesmoke;
}
.dropdown-item
{
-webkit-user-select: none; /* For webkit browsers */
-moz-user-select: none; /* For Firefox */
-ms-user-select: none; /* For Microsoft browsers */
user-select: none; /* Standard syntax */
color: whitesmoke;
}
.dropdown-item:hover
{
background-color: #696868;
color: whitesmoke;
}
.dropdown-item:hover
{
cursor: pointer;
}
::-webkit-scrollbar {
width: 10px; /* Set width of the scrollbar */
}
::-webkit-scrollbar-track {
background: #1e1e23; /* Track color */
}
::-webkit-scrollbar-thumb {
background: #123054; /* Thumb color */
border-radius: 5px; /* Rounded corners */
}

View File

@@ -3,252 +3,132 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.12.9/dist/umd/popper.min.js"></script>
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"
integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.12.9/dist/umd/popper.min.js"
integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
crossorigin="anonymous"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
<title>Bootstrap Styled Javalin App</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/ace.js" type="text/javascript" charset="utf-8"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/js/bootstrap.min.js"
integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/ace.js" type="text/javascript"
charset="utf-8"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.30.1/min/vs/editor/editor.main.css">
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.30.1/min/vs/editor/editor.main.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.30.1/min/vs/loader.min.js"></script>
<style>
body, html {
height: 100%;
margin: 0;
display: flex;
flex-direction: column;
background-color: #121212; /* Dark background */
}
#app {
height: 100vh; /* 100% of the viewport height */
display: flex;
flex-direction: column;
}
.header {
margin-bottom: 20px;
}
.btn-primary
{
background-color: #2c2c2c;
border-color: #2c2c2c;
}
.btn-primary:hover
{
background-color: #474747;
border-color: #474747;
}
#editor {
overflow-y: auto;
white-space: pre-wrap;
height: 100%;
overflow-y: auto;
}
.editor-container
{
padding: 10em;
}
.list-group-item:hover
{
background-color: #474747;
color: azure;
cursor: pointer;
}
.list-group-item
{
background-color: #2c2c2c;
color: azure;
border-color: #252424;
}
.content {
flex-grow: 1; /* Takes up the remaining space */
display: flex;
}
.col-md-10, .col-md-2 {
padding: 0; /* Remove default padding */
height: 100%;
}
</style>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.30.1/min/vs/editor/editor.main.css">
<!-- Load Monaco Editor's main loader script -->
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.30.1/min/vs/editor/editor.main.css">
<link rel="stylesheet" , href="index.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.30.1/min/vs/loader.min.js"></script>
<!-- Configure the loader -->
<script>
require.config({
paths: {'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.30.1/min/vs'}
});
</script>
<!-- Load the editor -->
<script>
var editor;
require(['vs/editor/editor.main'], function() {
var editor2;
require(['vs/editor/editor.main'], function () {
editor = monaco.editor.create(document.getElementById('editor'), {
value: [
'function x() {',
'\tconsole.log("Hello world!");',
'}'
'Hello world!',
].join('\n'),
language: 'json',
theme: 'vs-dark'
});
editor.onDidChangeModelContent(function () {
console.log("hello")
theme: 'vs-dark',
automaticLayout: true
});
editor2 = monaco.editor.create(document.getElementById('editor2'), {
value: [
'Hello world!',
].join('\n'),
language: 'json',
theme: 'vs-dark',
automaticLayout: true
});
});
</script>
</head>
<body class="bg-dark">
<body class="container-fluid bg-dark">
<div id="app" class="container-fluid mt-5">
<div >
<div class="mb-3">
<label for="name" class="form-label">Name:</label>
<input type="text" id="name" name="name" class="form-control">
<div class="row ">
<div class="col-2 ">
<div class="form margin">
<h5 >Data collector</h5>
<div class="form-group ">
<label for="name" class="form-label">TikTok username</label>
<input type="text" id="name" class="form-control">
</div>
<button type="button" onclick="connect()" class="btn btn-primary">Connect</button>
<button type="button" onclick="disconnect()" class="btn btn-primary">Disconnect</button>
<button onclick="showEvents()" class="btn btn-primary">Show Events</button>
<div class="form-group ">
<label class="form-label">SessionTag</label>
<input type="text" id="sessionTag" class="form-control">
</div>
<button id="connectionButton" type="button" class="btn btn-primary mt-3 connect-btn">Connect </button>
</div>
<div class="content row mt-5">
<div>
<div class="dropdown mt-3" id="usersDropDown">
<button class="btn btn-secondary dropdown-toggle" type="button" id="usersDropDownButton"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
User
</button>
<div id="userDropdownContent" class="dropdown-menu" aria-labelledby="usersDropDownButton">
<div class="col-md-2 ">
<ul id="eventList" class="list-group" style="max-height: 100%; overflow-y: auto;">
<!-- List items will be added dynamically -->
</div>
</div>
<div class="dropdown mt-3" id="sessionTagDropDown">
<button class="btn btn-secondary dropdown-toggle" type="button" id="sessionTagDropDownButton"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
SessionTag
</button>
<div class="dropdown-menu" aria-labelledby="sessionTagDropDownButton">
</div>
</div>
<div class="dropdown mt-3" id="dataTypeDropDown">
<button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Data Type
</button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
</div>
</div>
</div>
<div class="mt-5">
<ul id="dataList" class="list-group scrollable-element">
</ul>
</div>
<div class="col-md-10 editor-container ">
</div>
<div class="col-10 margin">
<nav aria-label="Page navigation example">
<ul id="pages" class="pagination">
<li class="page-item btn-primary"><a class="page-link" href="#">Previous</a></li>
<li class="page-item"><a class="page-link" href="#">Next</a></li>
</ul>
</nav>
<div id="editor"></div>
<div class="row editor-parent">
<div class="col-6 editor-container" id="editor"></div>
<div class="col-6 editor-container" id="editor2"></div>
</div>
</div>
</div>
</div>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
</div>
</div>
</div>
<script>
async function connect() {
let name = document.getElementById('name').value;
// name = "bangbetmenygy"
let response = await fetch(`http://localhost:8001/tiktok/connect?name=${name}`);
let greeting = await response.text();
console.log("connect",greeting);
connected = true;
}
async function disconnect() {
let response = await fetch(`http://localhost:8001/tiktok/disconnect`);
let greeting = await response.text();
console.log("disconnect",greeting);
connected = false;
}
async function loadMessage(event)
{
let response = await fetch(`http://localhost:8001/tiktok/events/message?eventName=${event}`);
let json = await response.text();
let root= JSON.parse(json);
editor.setValue(root.content);
}
async function loadMessageLink(link)
{
let response = await fetch(link);
let json = await response.text();
let root= JSON.parse(json);
editor.setValue(root.content);
}
async function loadPagination(event)
{
let response = await fetch(`http://localhost:8001/tiktok/events/pages?eventName=${event}`);
let json = await response.text();
let object = JSON.parse(json);
let pages = object.links;
console.log("PAGES: ",pages)
$("#pages").empty();
$.each(pages, function(index, element) {
let content = $('<button>',{
class: 'btn btn-primary',
text: index
}).click(async function()
{
await loadMessageLink(element);
console.log(editor)
});
$("#pages").append(content);
});
}
async function showEvents() {
let response = await fetch(`http://localhost:8001/tiktok/events`);
let json = await response.text();
console.log("events:",json);
let events= JSON.parse(json);
$("#eventList").empty();
$.each(events, function(index, event) {
let listItem = $('<li>', {
class: 'list-group-item',
text: event
}).click(async function()
{
await loadMessage(event);
await loadPagination(event);
});
$("#eventList").append(listItem);
});
}
async function loop()
{
if(!connected)
{
return;
}
console.log("updating");
await showEvents();
}
var connected = false;
var paginationIndex = 0;
var maxPages = 10;
var pages = [];
setInterval(loop, 1000)
showEvents()
</script>
<script src="index.js"></script>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.3/dist/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>

View File

@@ -0,0 +1,210 @@
var dataType = "messages"
var connected = false;
var paginationIndex = 0;
var maxPages = 10;
var pages = [];
var userName = "";
var port = 8002;
var baseUrl = `http://localhost:${port}`
var data =
{
dataType: "event",
dataName: "",
user: "",
sessionTag: "",
page: 0,
collector:
{
connected: false,
user: "",
sessionTag: "",
}
}
dropDown("usersDropDown", () => `${baseUrl}/tiktok/users`, async (e) => {
data.user = e;
update();
})
dropDown("dataTypeDropDown", () => `${baseUrl}/tiktok/data-types`, async (e) => {
data.dataType = e;
update();
})
dropDown("sessionTagDropDown", () => `${baseUrl}/tiktok/sessions?user=${data.user}`, async (e) => {
data.sessionTag = e;
update();
})
function update() {
new Promise(async function (resolve, reject) {
await updateAsync();
resolve(true);
});
}
async function updateAsync() {
console.log(data);
await updateConnectionButton()
await updateDataNamesList(async (dataName) => {
data.dataName = dataName;
data.page = 0;
await updateContent();
await updatePagination(async (page, link) => {
data.page = page;
let response = await fetch(link);
let json = await response.text();
console.log(link)
let root = JSON.parse(json);
editor.setValue(root.content);
if(data.dataType === 'message')
{
console.log("sending proto version")
let response2 = await fetch(`${link}&asProto=true`);
let json2 = await response2.text();
let root2 = JSON.parse(json2);
editor2.setValue(root2.content);
}
});
});
await fetch(`${baseUrl}/tiktok/update?user=${data.user}&session=${data.sessionTag}`);
}
async function updatePagination(onSelect) {
let response = await fetch(`${baseUrl}/tiktok/data/pages?name=${data.dataName}&type=${data.dataType}`);
let json = await response.text();
let object = JSON.parse(json);
let pages = object.links;
$("#pages").empty();
let page = 0;
$.each(pages, function (index, element) {
let currentPage = page;
page++;
let content = $('<button>', {
class: 'btn btn-primary margin-left',
text: index
}).click(async function () {
await onSelect(currentPage, element);
});
$("#pages").append(content);
});
}
async function updateContent() {
console.log("updating content", data)
let response = await fetch(`${baseUrl}/tiktok/data?name=${data.dataName}&type=${data.dataType}&page=${data.page}`);
let json = await response.text();
let root = JSON.parse(json);
editor.setValue(root.content);
if(data.dataType === 'message')
{
console.log("sending proto version")
let response2 = await fetch(`${baseUrl}/tiktok/data?name=${data.dataName}&type=${data.dataType}&page=${data.page}&asProto=true`);
let json2 = await response2.text();
let root2 = JSON.parse(json2);
editor2.setValue(root2.content);
}
}
async function updateDataNamesList(onSelect) {
let response = await fetch(`${baseUrl}/tiktok/data/names?type=${data.dataType}`);
let json = await response.text();
let responce = JSON.parse(json);
let element = $("#dataList");
console.log(responce)
element.empty();
$.each(responce, function (index, dataName) {
let listItem = $('<li>', {
class: 'list-group-item',
text: dataName
}).click(async function () {
onSelect(dataName)
});
element.append(listItem);
});
}
function dropDown(elementId, onUrl, onSelect) {
let dropDown = $("#" + elementId);
dropDown.on('show.bs.dropdown', async function (e, b) {
let response = await fetch(onUrl());
let json = await response.text();
let values = JSON.parse(json);
let optionsElement = dropDown.find("div")
optionsElement.empty();
let displayElement = dropDown.find("button")
for (let value of values) {
let dropDownItem = $('<p>', {
class: 'dropdown-item',
text: value
}).click(async function () {
displayElement.text(value)
onSelect(value)
});
optionsElement.append(dropDownItem)
}
})
}
setInterval(() => {
new Promise(async (a, b) => {
await updateConnectionButton()
});
}, 1000)
$("#connectionButton").on('click', async (a) => {
if (!data.collector.connected) {
console.log("connecting")
await connect()
} else {
console.log("disconnecting")
await disconnect()
}
});
async function updateConnectionButton() {
let button = $("#connectionButton");
let response = await fetch(`${baseUrl}/tiktok/status`);
let json = await response.text();
let values = JSON.parse(json);
console.log(values)
data.collector.connected = values;
if (data.collector.connected) {
button.text("disconnect");
} else {
button.text("connect");
}
}
async function connect() {
let name = document.getElementById('name').value;
let session = document.getElementById('sessionTag').value;
data.collector.name = name
data.collector.sessionTag =session
let response = await fetch(`${baseUrl}/tiktok/connect?name=${data.collector.name}&session=${data.collector.sessionTag}`);
let greeting = await response.text();
}
async function disconnect() {
let response = await fetch(`${baseUrl}/tiktok/disconnect`);
let greeting = await response.text();
}
update()

View File

@@ -27,6 +27,7 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import io.github.jwdeveloper.tiktok.Constants;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import io.github.jwdeveloper.tiktok.handlers.TikTokEventObserver;
import io.github.jwdeveloper.tiktok.http.TikTokCookieJar;
import io.github.jwdeveloper.tiktok.http.TikTokHttpClient;
import io.github.jwdeveloper.tiktok.http.TikTokHttpRequestFactory;
@@ -78,7 +79,7 @@ public class GiftOfficialJson {
public static JsonArray getJsonGifts() {
var jar = new TikTokCookieJar();
var tiktokHttpClient = new TikTokHttpClient(jar, new TikTokHttpRequestFactory(jar));
var tiktokHttpClient = new TikTokHttpClient(jar, new TikTokHttpRequestFactory(jar, new TikTokEventObserver()));
var settings = Constants.DefaultClientSettings();

BIN
db-battle.db Normal file

Binary file not shown.

BIN
dupa.db

Binary file not shown.