Compare commits

...

39 Commits

Author SHA1 Message Date
kohlerpop1
1308b86567 Made User#attributes final as its only set once and moved each constructor assignment to field declaration! 2024-01-05 17:06:50 +01:00
kohlerpop1
20ba88c0ac Removed useless lines, made Picture#toString, and optimized TikTokGiftManager#findById & TikTokGiftManager#findByName! 2024-01-05 17:06:50 +01:00
GitHub Action
77533ea4be Update version in pom.xml 2023-12-22 21:11:06 +00:00
Jacek W
3231924f8f Merge pull request #40 from kohlerpop1/kohlerpop1-fixes-updates
Optimized a few methods when calling and using the Api!
2023-12-22 18:46:56 +01:00
kohlerpop1
ea525470e2 Revert to StringBuilder and stacking stream line! 2023-12-22 12:22:50 -05:00
kohlerpop1
b0bf4ac606 Added @Getter to Badge classes and toString methods! 2023-12-22 10:32:49 -05:00
kohlerpop1
0b9f1570d0 Optimized a few methods when calling and using the Api! 2023-12-21 21:40:11 -05:00
kohlerpop1
7a4c7fecbd Merge remote-tracking branch 'origin/kohlerpop1-fixes-updates' into kohlerpop1-fixes-updates 2023-12-20 14:27:46 -05:00
GitHub Action
0ae9068858 Update version in pom.xml 2023-12-20 19:27:06 +00:00
kohlerpop1
8905958207 Merge remote-tracking branch 'origin/kohlerpop1-fixes-updates' into kohlerpop1-fixes-updates
# Conflicts:
#	Tools-EventsCollector/src/main/java/io/github/jwdeveloper/tiktok/tools/tester/mockClient/mocks/ApiServiceMock.java
2023-12-20 14:26:01 -05:00
JW
c12f3cc4dc . 2023-12-20 20:24:33 +01:00
David Kohler
7402899f52 Merge branch 'jwdeveloper:master' into kohlerpop1-fixes-updates 2023-12-20 14:19:37 -05:00
kohlerpop1
1b8b150d61 Removed not needed @Override in ApiServiceMock! 2023-12-20 14:18:55 -05:00
Jacek W
b2305b7bed Merge pull request #39 from kohlerpop1/kohlerpop1-fixes-updates
Added startTime to LiveRoomInfo to determine when the stream started!
2023-12-20 20:17:00 +01:00
kohlerpop1
7b911838a2 Added startTime to LiveRoomInfo to determine when the stream started! 2023-12-20 14:07:53 -05:00
Jacek W
e44cb71869 Update README.md 2023-12-19 21:57:24 +01:00
GitHub Action
af8c689417 Update version in pom.xml 2023-12-19 20:53:26 +00:00
Jacek W
81ac92fb33 Merge pull request #37 from jwdeveloper/develop-1-0-12
Including toUser in TikTokGiftEvent
2023-12-19 21:51:06 +01:00
JW
34a78b5435 Including toUser in TikTokGiftEvent 2023-12-19 21:49:27 +01:00
Jacek W
534cb7906d Merge pull request #36 from jwdeveloper/develop-1-0-12
Develop 1 0 12
2023-12-19 21:43:07 +01:00
JW
0bb8edfe5c Including toUser in TikTokGiftEvent 2023-12-19 21:31:58 +01:00
JW
4979c1b27a Merge remote-tracking branch 'origin/master' 2023-12-19 21:30:31 +01:00
Jacek W
f7c8ffdaa5 Update README.md 2023-12-19 17:34:07 +01:00
GitHub Action
c1fda687d3 Update version in pom.xml 2023-12-19 16:31:06 +00:00
JW
05c49c4545 . 2023-12-19 17:29:16 +01:00
Jacek W
7d36f36cee Update README.md 2023-12-19 14:01:48 +01:00
GitHub Action
f8a716429d Update version in pom.xml 2023-12-19 12:58:59 +00:00
Jacek W
fee805f0ea Merge pull request #35 from jwdeveloper/develop-1-0-10
Develop 1 0 10
2023-12-19 13:57:08 +01:00
JW
1733102aff Changes:
- TikTokHttpResponseEvent
  - Fixed User attributes in CommentEvent
  - Redesign .onMapper method
2023-12-19 13:56:25 +01:00
JW
89f54d5976 Merge branch 'master' into develop-1-0-10 2023-12-19 13:56:19 +01:00
JW
39055b5f3a Merge branch 'master' into develop-1-0-10 2023-12-19 13:54:50 +01:00
Jacek W
cd9e9ead85 Merge pull request #34 from jwdeveloper/develop-1-0-10
Develop 1 0 10
2023-12-19 13:48:47 +01:00
JW
5a2d2d23f0 Changes:
- TikTokHttpResponseEvent
  - Fixed User attributes in CommentEvent
  - Redesign .onMapper method
2023-12-19 13:46:11 +01:00
JW
3eed982d6b Changes:
- TikTokHttpResponseEvent
  - Fixed User attributes in CommentEvent
  - Redesign .onMapper method
2023-12-19 04:01:06 +01:00
GitHub Action
385560de3a Update version in pom.xml 2023-12-18 21:58:15 +00:00
Jacek W
2e37b6627b Merge pull request #33 from kohlerpop1/kohlerpop1-fixes/updates
Updates to TikTokGiftEvent & grammar fixes!
2023-12-18 22:56:33 +01:00
kohlerpop1
c4f0d63b43 Add toUser to TikTokGiftEvent to determine the correct user receiving a gift!
Made grammar fix to collaboration.md and README.md!
2023-12-18 16:52:40 -05:00
Jacek W
15550ed703 Update README.md 2023-12-12 19:49:52 +01:00
GitHub Action
c7d84218f2 Update version in pom.xml 2023-12-12 18:49:13 +00:00
125 changed files with 3725 additions and 1858 deletions

4
.gitignore vendored
View File

@@ -1,10 +1,10 @@
backend-infrastructure/.aws-sam
# Created by https://www.gitignore.io/api/osx,linux,python,windows,pycharm,visualstudiocode
*.db
### Linux ###
*~
.db
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*

View File

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

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
*/
@@ -64,7 +64,7 @@ public class ClientSettings {
/**
* Optional: Use it if you need to change TikTok live hostname in builder
* Optional: Use it if you need to change TikTok live hostname in builder
*/
private String hostName;

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

@@ -33,6 +33,8 @@ public class TikTokUserInfo
String roomId;
long startTime;
public enum UserStatus
{
NotFound,
@@ -40,4 +42,4 @@ public class TikTokUserInfo
LivePaused,
Live
}
}
}

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

@@ -26,6 +26,7 @@ import io.github.jwdeveloper.tiktok.annotations.EventMeta;
import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.data.models.gifts.GiftSendType;
import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage;
import lombok.Getter;
@@ -40,7 +41,7 @@ import lombok.Getter;
* <p>Combo: 4 -> comboState = GiftSendType.Active</p>
* <p>Combo: 8 -> comboState = GiftSendType.Active</p>
* <p>Combo: 12 -> comboState = GiftSendType.Finsihed</p>
*
* <p>
* Remember if comboState is Finsihed both TikTokGiftComboEvent and TikTokGiftEvent event gets triggered
*/
@EventMeta(eventType = EventType.Message)
@@ -48,8 +49,8 @@ import lombok.Getter;
public class TikTokGiftComboEvent extends TikTokGiftEvent {
private final GiftSendType comboState;
public TikTokGiftComboEvent(Gift gift, WebcastGiftMessage msg, GiftSendType comboState) {
super(gift, msg);
public TikTokGiftComboEvent(Gift gift, User host, WebcastGiftMessage msg, GiftSendType comboState) {
super(gift, host, msg);
this.comboState = comboState;
}
}

View File

@@ -26,28 +26,38 @@ package io.github.jwdeveloper.tiktok.data.events.gift;
import io.github.jwdeveloper.tiktok.annotations.EventMeta;
import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokHeaderEvent;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.data.models.gifts.GiftSendType;
import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage;
import lombok.Getter;
import java.util.ArrayList;
/*
/**
* Triggered when user sends gifts that has
* no combo (most of expensive gifts)
* or if combo has finished
* no combo (most of expensive gifts)
* or if combo has finished
*/
@EventMeta(eventType = EventType.Message)
@Getter
public class TikTokGiftEvent extends TikTokHeaderEvent {
private final Gift gift;
private final User user;
private final User toUser;
private final int combo;
public TikTokGiftEvent(Gift gift, WebcastGiftMessage msg) {
public TikTokGiftEvent(Gift gift, User liveHost, WebcastGiftMessage msg) {
super(msg.getCommon());
this.gift = gift;
user = User.map(msg.getUser(), msg.getUserIdentity());
if (msg.getToUser().getNickname().isEmpty()) {
toUser = liveHost;
} else {
toUser = User.map(msg.getToUser());
}
combo = msg.getComboCount();
}
}
}

View File

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

@@ -28,15 +28,10 @@ import lombok.Getter;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.*;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class Picture {
@Getter
@@ -49,7 +44,6 @@ public class Picture {
}
public static Picture map(io.github.jwdeveloper.tiktok.messages.data.Image profilePicture) {
var index = profilePicture.getUrlListCount() - 1;
if (index < 0) {
return new Picture("");
@@ -74,12 +68,11 @@ public class Picture {
return CompletableFuture.supplyAsync(this::downloadImage);
}
private BufferedImage download(String urlString)
{
if(urlString.isEmpty())
{
private BufferedImage download(String urlString) {
if (urlString.isEmpty()) {
return null;
}
var baos = new ByteArrayOutputStream();
try (var is = new URL(urlString).openStream()) {
var byteChunk = new byte[4096];
@@ -103,4 +96,9 @@ public class Picture {
public static Picture Empty() {
return new Picture("");
}
}
@Override
public String toString() {
return "Picture{link='" + link + "', image=" + image + "}";
}
}

View File

@@ -37,6 +37,4 @@ public class Badge {
public static Badge empty() {
return new Badge();
}
}
}

View File

@@ -24,19 +24,22 @@ package io.github.jwdeveloper.tiktok.data.models.badges;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.messages.data.BadgeStruct;
import lombok.Getter;
@Getter
public class CombineBadge extends Badge {
private final Picture picture;
private final String text;
private final String subText;
public CombineBadge(BadgeStruct.CombineBadge combineBadge) {
picture = Picture.map(combineBadge.getIcon());
text = combineBadge.getText().getDefaultPattern();
subText = combineBadge.getStr();
}
}
@Override
public String toString() {
return "CombineBadge{picture=" + picture +", text='" + text + "', subText='" + subText + "'}";
}
}

View File

@@ -24,12 +24,18 @@ package io.github.jwdeveloper.tiktok.data.models.badges;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.messages.data.BadgeStruct;
import lombok.Getter;
@Getter
public class PictureBadge extends Badge {
private final Picture picture;
public PictureBadge(BadgeStruct.ImageBadge imageBadge) {
public PictureBadge(BadgeStruct.ImageBadge imageBadge) {
picture = Picture.map(imageBadge.getImage());
}
}
@Override
public String toString() {
return "PictureBadge{picture=" + picture + "}";
}
}

View File

@@ -23,12 +23,18 @@
package io.github.jwdeveloper.tiktok.data.models.badges;
import io.github.jwdeveloper.tiktok.messages.data.BadgeStruct;
import lombok.Getter;
@Getter
public class StringBadge extends Badge {
private final String text;
public String text;
public StringBadge(BadgeStruct.StringBadge stringBadge) {
this.text = stringBadge.getStr();
}
}
@Override
public String toString() {
return "StringBadge{text='" + text + "'}";
}
}

View File

@@ -23,13 +23,18 @@
package io.github.jwdeveloper.tiktok.data.models.badges;
import io.github.jwdeveloper.tiktok.messages.data.BadgeStruct;
import lombok.Getter;
public class TextBadge extends Badge
{
@Getter
public class TextBadge extends Badge {
private final String text;
public TextBadge(BadgeStruct.TextBadge textBadge)
{
public TextBadge(BadgeStruct.TextBadge textBadge) {
this.text = textBadge.getDefaultPattern();
}
}
@Override
public String toString() {
return "TextBadge{text='" + text + "'}";
}
}

View File

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

@@ -43,13 +43,12 @@ public class User {
private long followers;
private List<Badge> badges;
@Getter(AccessLevel.NONE)
private Set<UserAttribute> attributes;
private final Set<UserAttribute> attributes = new HashSet<>();
public List<UserAttribute> getAttributes() {
return attributes.stream().toList();
}
public boolean hasAttribute(UserAttribute userFlag) {
return attributes.contains(userFlag);
}
@@ -106,7 +105,6 @@ public class User {
this.following = following;
this.followers = followers;
this.badges = badges;
this.attributes = new HashSet<>();
}
public User(Long id,
@@ -123,14 +121,12 @@ public class User {
this.following = following;
this.followers = followers;
this.badges = badges;
this.attributes = new HashSet<>();
}
public User(Long userId,
String nickName) {
this.id = userId;
this.name = nickName;
this.attributes = new HashSet<>();
}
public User(Long userId,
@@ -213,4 +209,4 @@ public class User {
0,
List.of(Badge.empty()));
}
}
}

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();
/**
* Use to manually invoke event
*/
void publishEvent(TikTokEvent event);
/**
* Get information about gifts
*/

View File

@@ -42,6 +42,7 @@ public interface LiveRoomInfo
*/
int getTotalViewersCount();
int getLikesCount();
long getStartTime();
boolean isAgeRestricted();
String getRoomId();
String getHostName();
@@ -49,4 +50,4 @@ public interface LiveRoomInfo
User getHostUser();
List<RankingUser> getUsersRanking();
ConnectionState getConnectionState();
}
}

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,35 @@ 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);
TikTokMapperModel forMessage(Class<? extends GeneratedMessageV3> mapperName, MappingAction<MappingResult> onMapping);
TikTokMapperModel forMessage(Class<? extends GeneratedMessageV3> mapperName, Function<byte[], TikTokEvent> onMapping);
TikTokMapperModel forAnyMessage();
/**
* 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);
boolean isRegistered(String mapperName);
void bytesToEvents(String messageName, Function<byte[], List<TikTokEvent>> onMapping);
<T extends GeneratedMessageV3> boolean isRegistered(Class<T> 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> 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,68 @@
/*
* 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.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

@@ -20,24 +20,39 @@
* 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.mappers;
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 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;
@RegisterBeanMapper(TikTokResponseModel.class)
public interface TikTokResponseModelDAO
{
@SqlUpdate("INSERT INTO TikTokResponseModel (hostName, response, createdAt) " +
"VALUES (:hostName, :response, :createdAt)")
void insert(@BindBean TikTokResponseModel message);
public interface TikTokMapperModel {
@SqlQuery("SELECT * FROM TikTokResponseModel")
List<TikTokResponseModel> select();
/**
* @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

@@ -20,17 +20,18 @@
* 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.mappers.data;
public class TikTokMessageCollectorClient
{
public static TikTokMessagessCollectorBuilder create(String outputName)
{
return new TikTokMessagessCollectorBuilder(outputName);
}
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
public static TikTokMessagessCollectorBuilder create(MessageCollector messageCollector, String outputName)
{
return new TikTokMessagessCollectorBuilder(messageCollector,outputName);
}
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,38 @@
/*
* 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.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

@@ -20,16 +20,34 @@
* 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;
package io.github.jwdeveloper.tiktok.mappers.data;
public class Examplee
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.List;
@AllArgsConstructor
@Getter
public class MappingResult
{
public static void main(String[] args)
{
TikTokLive.newClient("skullchefasmr")
.onGift((liveClient, event) ->
{
System.out.println("Dzięki za gifta "+event.getGift().getName());
}).buildAndConnect();
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

@@ -64,8 +64,8 @@ message Text {
string stringValue = 11;
oneof textPieceType
{
TextPieceUser userValue = 21;
TextPieceGift giftValue = 22;
TextPieceUser userValue = 21;
TextPieceGift giftValue = 22;
}
TextPiecePatternRef patternRefValue = 24;
}
@@ -155,7 +155,7 @@ message BadgeStruct {
message ProfileCardPanel {
bool useNewProfileCardStyle = 1;
// BadgeTextPosition badgeTextPosition = 2; // Enum
// BadgeTextPosition badgeTextPosition = 2; // Enum
ProjectionConfig projectionConfig = 3;
ProfileContent profileContent = 4;
}
@@ -1000,8 +1000,8 @@ message LinkerCreateContent {
message LinkerEnterContent {
repeated User LinkedUsersList = 1;
// LinkmicMultiLiveEnum AnchorMultiLiveEnum = 2;
// Data.LinkmicUserSettingInfo AnchorSettingInfo = 3;
// LinkmicMultiLiveEnum AnchorMultiLiveEnum = 2;
// Data.LinkmicUserSettingInfo AnchorSettingInfo = 3;
}
message LinkerInviteContent {
@@ -1018,15 +1018,15 @@ 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;
// Data.PunishEventInfo PunishInfo = 20;
// repeated LinkmicUserInfo LinkedUsersList = 18;
// Data.PerceptionDialogInfo Dialog = 19;
// Data.PunishEventInfo PunishInfo = 20;
int32 FromRoomAgeRestricted = 21;
// Data.Tag FromTag = 22;
// repeated Data.CohostABTestSetting AbTestSettingList = 23;
// Data.LinkerInviteMessageExtra LinkerInviteMsgExtra = 101;
// Data.Tag FromTag = 22;
// repeated Data.CohostABTestSetting AbTestSettingList = 23;
// Data.LinkerInviteMessageExtra LinkerInviteMsgExtra = 101;
}
message LinkerKickOutContent {
@@ -1046,17 +1046,17 @@ 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 {
// MicIdxOperation Op = 1;
// MicIdxOperation Op = 1;
int64 ToUserId = 2;
int64 AnchorId = 3;
int64 RoomId = 4;
// LinkerSceneType ChangeScene = 5;
// LinkerSceneType ChangeScene = 5;
}
message LinkerMicIdxUpdateContent {
@@ -1064,14 +1064,14 @@ message LinkerMicIdxUpdateContent {
}
message LinkerMicIdxUpdateInfo {
// MicIdxOperation Op = 1;
// MicIdxOperation Op = 1;
int64 UserId = 2;
int64 MicIdx = 3;
}
message LinkerMuteContent {
int64 UserId = 1;
// Data.MuteStatus Status = 2;
// Data.MuteStatus Status = 2;
}
message LinkerRandomMatchContent {
@@ -1085,9 +1085,9 @@ message LinkerRandomMatchContent {
message LinkerReplyContent {
int64 FromUserId = 1;
int64 FromRoomId = 2;
// LinkmicInfo FromUserLinkmicInfo = 3;
// LinkmicInfo FromUserLinkmicInfo = 3;
int64 ToUserId = 4;
// LinkmicInfo ToUserLinkmicInfo = 5;
// LinkmicInfo ToUserLinkmicInfo = 5;
int64 LinkType = 6;
int64 ReplyStatus = 7;
LinkerSetting LinkerSetting = 8;
@@ -1096,10 +1096,10 @@ 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;
// repeated LinkmicUserInfo LinkedUsersList = 17;
int64 SourceType = 18;
}
@@ -1124,7 +1124,7 @@ message LinkerUpdateUserContent {
}
message LinkerUpdateUserSettingContent {
// Data.LinkmicUserSettingInfo UpdateUserSettingInfo = 1;
// Data.LinkmicUserSettingInfo UpdateUserSettingInfo = 1;
}
message LinkerWaitingListChangeContent {
@@ -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

@@ -18,7 +18,7 @@ message WebcastPushFrame {
uint64 LogId = 2;
uint64 Service = 3;
uint64 Method = 4;
map<string,string> headers = 5;
map<string, string> headers = 5;
string PayloadEncoding = 6;
string PayloadType = 7;
bytes Payload = 8;
@@ -81,6 +81,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;
@@ -197,7 +204,7 @@ message WebcastChatMessage {
int32 quickChatScene = 16;
int32 communityFlaggedStatus = 17;
UserIdentity UserIdentity = 18;
map<int32,string> CommentQualityScores = 19;
map<int32, string> CommentQualityScores = 19;
// @EmoteWithIndex
// proto.webcast.im.ChatMessage
@@ -277,14 +284,14 @@ message WebcastGoalUpdateMessage {
int64 contributorId = 4;
Image contributorAvatar = 5;
string contributorDisplayId = 6;
// SubGoal contributeSubgoal = 7;
// SubGoal contributeSubgoal = 7;
int64 contributeCount = 9;
int64 contributeScore = 10;
int64 giftRepeatCount = 11;
string contributorIdStr = 12;
bool pin = 13;
bool unpin = 14;
// GoalPinInfo pinInfo = 15;
// GoalPinInfo pinInfo = 15;
}
// Message related to Chat-moderation?
@@ -348,7 +355,7 @@ message WebcastSocialMessage {
message WebcastSubNotifyMessage {
Common common = 1;
User user = 2;
// ExhibitionType exhibitionType = 3; // Enum
// ExhibitionType exhibitionType = 3; // Enum
int64 subMonth = 4;
SubscribeType subscribeType = 5; // Enum
OldSubscribeStatus oldSubscribeStatus = 6; // Enum
@@ -507,6 +514,9 @@ message WebcastHourlyRankMessage {
}
}
//<Battles>
//@WebcastLinkMicArmies
message WebcastLinkMicArmies {
Common common = 1;
@@ -523,6 +533,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 +621,6 @@ message WebcastLinkMicFanTicketMethod {
Common common = 1;
FanTicketRoomNoticeContent FanTicketRoomNotice = 2;
}
//@WebcastLinkMicMethod
message WebcastLinkMicMethod {
Common common = 1;
@@ -590,6 +638,8 @@ message WebcastLinkMicMethod {
int64 inviteUid = 13;
}
//<Battles>
//@WebcastLiveIntroMessage
message WebcastLiveIntroMessage {
Common common = 1;
@@ -632,7 +682,7 @@ message WebcastMsgDetectMessage {
bool detectP2PMsg = 3;
bool detectRoomMsg = 4;
bool httpOptimize = 5;
}
}
}
//@WebcastOecLiveShoppingMessage
@@ -702,11 +752,11 @@ message WebcastLinkMessage {
LinkerUpdateUserSettingContent UpdateUserSettingContent = 18;
LinkerMicIdxUpdateContent MicIdxUpdateContent = 19;
LinkerListChangeContent ListChangeContent = 20;
// CohostListChangeContent CohostListChangeContent = 21;
// CohostListChangeContent CohostListChangeContent = 21;
LinkerMediaChangeContent MediaChangeContent = 22;
LinkerAcceptNoticeContent ReplyAcceptNoticeContent = 23;
LinkerSysKickOutContent SysKickOutContent = 101;
// LinkmicUserToastContent UserToastContent = 102;
// LinkmicUserToastContent UserToastContent = 102;
string Extra = 200;
int64 ExpireTimestamp = 201;
string TransferExtra = 202;

View File

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

View File

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

View File

@@ -22,9 +22,11 @@
*/
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.data.dto.TikTokUserInfo;
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;
@@ -134,13 +136,15 @@ public class TikTokLiveClient implements LiveClient {
apiService.updateSessionId();
TikTokUserInfo info = apiService.fetchUserInfoFromTikTokApi(liveRoomInfo.getHostName());
liveRoomInfo.setStartTime(info.getStartTime());
if (clientSettings.getRoomId() != null) {
liveRoomInfo.setRoomId(clientSettings.getRoomId());
logger.info("Using roomID from settings: " + clientSettings.getRoomId());
} else {
var roomId = apiService.fetchRoomId(liveRoomInfo.getHostName());
liveRoomInfo.setRoomId(roomId);
liveRoomInfo.setRoomId(info.getRoomId());
}
apiService.updateRoomId(liveRoomInfo.getRoomId());
var liveRoomMeta = apiService.fetchRoomInfo();
@@ -189,4 +193,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;
@@ -110,12 +113,12 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
clientSettings.setTimeout(Duration.ofSeconds(Constants.DEFAULT_TIMEOUT));
}
if (clientSettings.getClientLanguage() == null || clientSettings.getClientLanguage().equals("")) {
if (clientSettings.getClientLanguage() == null || clientSettings.getClientLanguage().isEmpty()) {
clientSettings.setClientLanguage(Constants.DefaultClientSettings().getClientLanguage());
}
if (clientSettings.getHostName() == null || clientSettings.getHostName().equals("")) {
if (clientSettings.getHostName() == null || clientSettings.getHostName().isEmpty()) {
throw new TikTokLiveException("HostName can not be null");
}
@@ -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,56 +188,85 @@ 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();
var giftHandler = new TikTokGiftEventHandler(giftManager);
var giftHandler = new TikTokGiftEventHandler(giftManager, roomInfo);
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;
@@ -514,11 +552,4 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
tikTokEventHandler.subscribe(TikTokReconnectingEvent.class, event);
return this;
}
}
}

View File

@@ -22,11 +22,10 @@
*/
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.RankingUser;
import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.models.ConnectionState;
import io.github.jwdeveloper.tiktok.live.LiveRoomInfo;
import io.github.jwdeveloper.tiktok.models.ConnectionState;
import lombok.Data;
import java.util.LinkedList;
@@ -42,6 +41,8 @@ public class TikTokRoomInfo implements LiveRoomInfo {
private int totalViewersCount;
private long startTime;
private boolean ageRestricted;
private User host;
@@ -62,12 +63,11 @@ public class TikTokRoomInfo implements LiveRoomInfo {
@Override
public User getHostUser() {
return null;
return host;
}
public void updateRanking(List<RankingUser> rankingUsers) {
usersRanking.clear();
usersRanking.addAll(rankingUsers);
}
}
}

View File

@@ -22,14 +22,12 @@
*/
package io.github.jwdeveloper.tiktok.gifts;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.live.GiftManager;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.logging.Logger;
@@ -89,23 +87,17 @@ public class TikTokGiftManager implements GiftManager {
}
public Gift findById(int giftId) {
if (!indexById.containsKey(giftId)) {
return Gift.UNDEFINED;
}
return indexById.get(giftId);
Gift gift = indexById.get(giftId);
return gift == null ? Gift.UNDEFINED : gift;
}
public Gift findByName(String giftName) {
if (!indexByName.containsKey(giftName)) {
return Gift.UNDEFINED;
}
return indexByName.get(giftName);
Gift gift = indexByName.get(giftName);
return gift == null ? Gift.UNDEFINED : gift;
}
@Override
public List<Gift> getGifts()
{
public List<Gift> getGifts() {
return indexById.values().stream().toList();
}
}
}

View File

@@ -22,58 +22,30 @@
*/
package io.github.jwdeveloper.tiktok.http;
import lombok.SneakyThrows;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.stream.Collectors;
public class HttpUtils
{
public static String parseParameters(String url, Map<String,Object> parameters)
{
var parameterString = "";
if (!parameters.isEmpty()) {
var builder = new StringBuilder();
builder.append("?");
var first = false;
for (var param : parameters.entrySet()) {
if (parameters.isEmpty())
return url;
if (first) {
builder.append("&");
}
builder.append(param.getKey()).append("=").append(param.getValue());
first = true;
}
parameterString = builder.toString();
}
return url+parameterString;
return url+ "?" + parameters.entrySet().stream().map(entry -> entry.getKey()+"="+entry.getValue()).collect(Collectors.joining("&"));
}
@SneakyThrows
public static String parseParametersEncode(String url, Map<String,Object> parameters)
{
if (parameters.isEmpty())
return url;
var parameterString = "";
if (!parameters.isEmpty()) {
var builder = new StringBuilder();
builder.append("?");
var first = false;
for (var param : parameters.entrySet()) {
if (first) {
builder.append("&");
}
final String encodedKey = URLEncoder.encode(param.getKey(), StandardCharsets.UTF_8);
final String encodedValue = URLEncoder.encode(param.getValue().toString(), StandardCharsets.UTF_8);
builder.append(encodedKey).append("=").append(encodedValue);
first = true;
}
parameterString = builder.toString();
}
return url+parameterString;
return url+ "?" + parameters.entrySet().stream().map(entry -> {
String encodedKey = URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8);
String encodedValue = URLEncoder.encode(entry.getValue().toString(), StandardCharsets.UTF_8);
return encodedKey+"="+encodedValue;
}).collect(Collectors.joining("&"));
}
}
}

View File

@@ -22,12 +22,9 @@
*/
package io.github.jwdeveloper.tiktok.http;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.*;
import io.github.jwdeveloper.tiktok.ClientSettings;
import io.github.jwdeveloper.tiktok.data.dto.TikTokUserInfo;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveOfflineHostException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import io.github.jwdeveloper.tiktok.live.LiveRoomMeta;
import io.github.jwdeveloper.tiktok.mappers.LiveRoomMetaMapper;
@@ -35,7 +32,6 @@ import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import java.util.HashMap;
import java.util.logging.Logger;
import java.util.regex.Pattern;
public class TikTokApiService {
private final TikTokHttpClient tiktokHttpClient;
@@ -48,7 +44,6 @@ public class TikTokApiService {
this.clientSettings = clientSettings;
}
public void updateSessionId() {
if (clientSettings.getSessionId() == null) {
return;
@@ -59,43 +54,45 @@ public class TikTokApiService {
tiktokHttpClient.setSessionId(clientSettings.getSessionId());
}
public String fetchRoomId(String userName) {
var userInfo = fetchUserInfoFromTikTokApi(userName);
clientSettings.getClientParameters().put("room_id", userInfo.getRoomId());
logger.info("RoomID -> " + userInfo.getRoomId());
return userInfo.getRoomId();
public void updateRoomId(String roomId)
{
clientSettings.getClientParameters().put("room_id", roomId);
}
public TikTokUserInfo fetchUserInfoFromTikTokApi(String userName) {
var params = new HashMap<>(clientSettings.getClientParameters());
params.put("uniqueId", userName);
params.put("sourceType", 54);
JsonObject roomData = null;
JsonObject roomData;
try {
roomData = tiktokHttpClient.getJsonFromTikTokApi("api-live/user/room/", params);
} catch (Exception e) {
throw new TikTokLiveRequestException("Failed to fetch pre connection room information, it happens when TikTok temporary blocks you. Try to connect again in few minutes");
}
var message = roomData.get("message").getAsString();
if (message.equals("params_error")) {
throw new TikTokLiveRequestException("fetchRoomIdFromTiktokApi -> Unable to fetch roomID, contact with developer");
throw new TikTokLiveRequestException("fetchRoomIdFromTiktokApi -> Unable to fetch roomID, contact the developer");
}
if (message.equals("user_not_found")) {
return new TikTokUserInfo(TikTokUserInfo.UserStatus.NotFound, "");
return new TikTokUserInfo(TikTokUserInfo.UserStatus.NotFound, "", -1);
}
//live -> status 2
//live paused -> 3
//not live -> status 4
var data = roomData.getAsJsonObject("data");
var element = roomData.get("data");
if (element.isJsonNull()) {
return new TikTokUserInfo(TikTokUserInfo.UserStatus.NotFound, "", -1);
}
var data = element.getAsJsonObject();
var user = data.getAsJsonObject("user");
var roomId = user.get("roomId").getAsString();
var status = user.get("status").getAsInt();
var liveRoom = data.getAsJsonObject("liveRoom");
long startTime = liveRoom.get("startTime").getAsLong();
var statusEnum = switch (status) {
case 2 -> TikTokUserInfo.UserStatus.Live;
case 3 -> TikTokUserInfo.UserStatus.LivePaused;
@@ -103,7 +100,7 @@ public class TikTokApiService {
default -> TikTokUserInfo.UserStatus.NotFound;
};
return new TikTokUserInfo(statusEnum, roomId);
return new TikTokUserInfo(statusEnum, roomId, startTime);
}
@@ -138,4 +135,4 @@ public class TikTokApiService {
throw new TikTokLiveRequestException("Failed to fetch live websocket connection data", e);
}
}
}
}

View File

@@ -22,9 +22,8 @@
*/
package io.github.jwdeveloper.tiktok.http;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
public class TikTokCookieJar {
private final Map<String, String> cookies;
@@ -40,13 +39,10 @@ public class TikTokCookieJar {
cookies.put(key, value);
}
public String parseCookies()
{
var sb = new StringBuilder();
for(var entry : cookies.entrySet())
{
sb.append(entry.getKey()).append("=").append(entry.getValue()).append(";");
}
return sb.toString();
public String parseCookies() {
return cookies.entrySet()
.stream()
.map(entry -> entry.getKey()+"="+entry.getValue()+";")
.collect(Collectors.joining());
}
}
}

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

@@ -45,8 +45,7 @@ public class TikTokHttpClient {
this.tikTokCookieJar = tikTokCookieJar;
}
public void setSessionId(String sessionId)
{
public void setSessionId(String sessionId) {
tikTokCookieJar.set("sessionid", sessionId);
tikTokCookieJar.set("sessionid_ss", sessionId);
tikTokCookieJar.set("sid_tt", sessionId);
@@ -54,14 +53,12 @@ public class TikTokHttpClient {
public String getLivestreamPage(String userName) {
var url = Constants.TIKTOK_URL_WEB + "@" + userName + "/live/";
var get = getRequest(url, null);
return get;
}
public JsonObject getJsonFromTikTokApi(String path, Map<String,Object> params)
{
public JsonObject getJsonFromTikTokApi(String path, Map<String,Object> params) {
var get = getRequest(Constants.TIKTOK_URL_WEB + path, params);
var json = JsonParser.parseString(get);
var jsonObject = json.getAsJsonObject();
@@ -91,7 +88,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);
}
@@ -118,7 +114,6 @@ public class TikTokHttpClient {
{
var split = cookie.split(";")[0].split("=");
var key = split[0];
var value = split[1];
tikTokCookieJar.set(key, value);
@@ -134,7 +129,6 @@ public class TikTokHttpClient {
private String getSignedUrl(String url, Map<String, Object> parameters) {
var fullUrl = HttpUtils.parseParameters(url,parameters);
var signParams = new TreeMap<String,Object>();
signParams.put("client", "ttlive-java");
@@ -144,7 +138,6 @@ public class TikTokHttpClient {
var request = requestFactory.setQueries(signParams);
var content = request.get(Constants.TIKTOK_SIGN_API);
try {
var json = JsonParser.parseString(content);
var jsonObject = json.getAsJsonObject();
@@ -158,4 +151,4 @@ public class TikTokHttpClient {
}
}
}
}

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;
@@ -39,32 +42,34 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
public class TikTokHttpRequestFactory implements TikTokHttpRequest {
private final CookieManager cookieManager;
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 +78,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 +91,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 +105,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,24 +123,25 @@ public class TikTokHttpRequestFactory implements TikTokHttpRequest {
public TikTokHttpRequest setQueries(Map<String, Object> queries) {
if (queries == null)
return this;
var testMap = new TreeMap<String,Object>(queries);
query = String.join("&", testMap.entrySet().stream().map(x ->
{
var testMap = new TreeMap<>(queries);
query = testMap.entrySet().stream().map(x -> {
var key = x.getKey();
var value = "";
try {
value = URLEncoder.encode(x.getValue().toString(), StandardCharsets.UTF_8);
return key+"="+URLEncoder.encode(x.getValue().toString(), StandardCharsets.UTF_8);
} catch (Exception e) {
e.printStackTrace();
return key + "=";
}
return key + "=" + value;
}).toList());
}).collect(Collectors.joining("&"));
return this;
}
private String getContent(HttpRequest request) throws Exception {
var response = client.send(request, HttpResponse.BodyHandlers.ofString());
var event = new TikTokHttpResponseEvent(response.uri().toString(), HttpData.map(response), HttpData.map(request));
eventHandler.publish(null, event);
if (response.statusCode() == 404) {
throw new TikTokLiveRequestException("Request responded with 404 NOT_FOUND");
}
@@ -155,8 +154,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);
@@ -164,10 +161,7 @@ public class TikTokHttpRequestFactory implements TikTokHttpRequest {
var map = new HashMap<String, List<String>>();
map.put(key, List.of(value));
cookieManager.put(uri, map);
}
return response.body();
}
}
}

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];
@@ -116,8 +115,8 @@ public class TikTokListenersManager implements ListenersManager {
throw new TikTokEventListenerMethodException(e);
}
};
eventsMap.computeIfAbsent(eventClazz, (a) -> new ArrayList<EventConsumer<?>>()).add(eventMethodRef);
eventsMap.computeIfAbsent(eventClazz, (a) -> new ArrayList<>()).add(eventMethodRef);
}
return new ListenerBindingModel(listener, eventsMap);
}
}
}

View File

@@ -22,7 +22,6 @@
*/
package io.github.jwdeveloper.tiktok.mappers;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.users.User;
@@ -123,4 +122,4 @@ public class LiveRoomMetaMapper {
user.addAttribute(UserAttribute.LiveHost);
return user;
}
}
}

View File

@@ -23,8 +23,10 @@
package io.github.jwdeveloper.tiktok.mappers;
import com.google.protobuf.GeneratedMessageV3;
import io.github.jwdeveloper.tiktok.TikTokLive;
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 +34,83 @@ 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;
private final TikTokLiveMapperModel globalMapperModel;
public TikTokLiveMapper(TikTokMapperHelper mapperUtils) {
this.mappers = new HashMap<>();
this.genericMapper = genericMapper;
this.mapperUtils = mapperUtils;
this.globalMapperModel = new TikTokLiveMapperModel("any message");
}
@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 TikTokMapperModel forAnyMessage() {
return globalMapperModel;
}
public boolean isRegistered(String input) {
return mappers.containsKey(input);
public boolean isRegistered(String mapperName) {
return mappers.containsKey(mapperName);
}
public List<TikTokEvent> handleMapping(String input, byte[] bytes) {
if (!isRegistered(input)) {
public <T extends GeneratedMessageV3> boolean isRegistered(Class<T> mapperName) {
return mappers.containsKey(mapperName.getSimpleName());
}
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 globalInputBytes = globalMapperModel.getOnBeforeMapping().onMapping(inputBytes, messageName, mapperUtils);
var mappingResult = mapperModel.getOnMapping().onMapping(globalInputBytes, messageName, mapperUtils);
if (mappingResult == null) {
mappingResult = globalMapperModel.getOnMapping().onMapping(globalInputBytes, messageName, mapperUtils);
}
var afterMappingResult = mapperModel.getOnAfterMapping().apply(mappingResult);
var globalAfterMappingResult = globalMapperModel.getOnAfterMapping().apply(MappingResult.of(mappingResult.getSource(), afterMappingResult));
return globalAfterMappingResult;
}
}

View File

@@ -0,0 +1,82 @@
/*
* 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.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,79 @@
/*
* 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.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,8 +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.mappers.events;
package io.github.jwdeveloper.tiktok.mappers.handlers;
import io.github.jwdeveloper.tiktok.TikTokRoomInfo;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftComboEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent;
@@ -30,6 +31,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;
@@ -41,16 +44,19 @@ import java.util.Map;
public class TikTokGiftEventHandler {
private final GiftManager giftManager;
private final Map<Long, WebcastGiftMessage> giftsMessages;
private final TikTokRoomInfo tikTokRoomInfo;
public TikTokGiftEventHandler(GiftManager giftManager) {
public TikTokGiftEventHandler(GiftManager giftManager, TikTokRoomInfo tikTokRoomInfo) {
this.giftManager = giftManager;
giftsMessages = new HashMap<>();
this.tikTokRoomInfo = tikTokRoomInfo;
}
@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) {
@@ -98,12 +104,12 @@ public class TikTokGiftEventHandler {
private TikTokGiftEvent getGiftEvent(WebcastGiftMessage message) {
var gift = getGiftObject(message);
return new TikTokGiftEvent(gift, message);
return new TikTokGiftEvent(gift, tikTokRoomInfo.getHost(), message);
}
private TikTokGiftEvent getGiftComboEvent(WebcastGiftMessage message, GiftSendType state) {
var gift = getGiftObject(message);
return new TikTokGiftComboEvent(gift, message, state);
return new TikTokGiftComboEvent(gift, tikTokRoomInfo.getHost(), message, state);
}
private Gift getGiftObject(WebcastGiftMessage giftMessage) {

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

@@ -22,12 +22,13 @@
*/
package io.github.jwdeveloper.tiktok.handlers.events;
import io.github.jwdeveloper.tiktok.TikTokRoomInfo;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftComboEvent;
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;
@@ -49,42 +50,43 @@ class TikTokGiftEventHandlerTest {
@BeforeAll
public void before() {
var manager = new TikTokGiftManager(Logger.getLogger("x"));
var info = new TikTokRoomInfo();
info.setHost(new io.github.jwdeveloper.tiktok.data.models.users.User(123L, "test", new Picture("")));
manager.registerGift(123, "example", 123, new Picture("image.webp"));
handler = new TikTokGiftEventHandler(manager);
handler = new TikTokGiftEventHandler(manager, info);
}
@Test
void shouldHandleGifts() {
var message = getGiftMessage("example-new-name", 123, "image-new.png", 0, 1,false);
var message = getGiftMessage("example-new-name", 123, "image-new.png", 0, 1, false);
var result = handler.handleGift(message);
Assertions.assertEquals(2, result.size());
var event = (TikTokGiftEvent) result.get(0);
var gift = event.getGift();
Assertions.assertEquals("image-new.png",gift.getPicture().getLink());
Assertions.assertEquals(123,gift.getId());
Assertions.assertEquals("image-new.png", gift.getPicture().getLink());
Assertions.assertEquals(123, gift.getId());
}
@Test
void shouldHandleStrakableGift() {
var message = getGiftMessage("example-new-name", 123, "image-new.png", 0, 1,true);
var message = getGiftMessage("example-new-name", 123, "image-new.png", 0, 1, true);
var result = handler.handleGift(message);
Assertions.assertEquals(1, result.size());
var event = (TikTokGiftEvent) result.get(0);
var gift = event.getGift();
Assertions.assertEquals("image-new.png",gift.getPicture().getLink());
Assertions.assertEquals(123,gift.getId());
Assertions.assertEquals("image-new.png", gift.getPicture().getLink());
Assertions.assertEquals(123, gift.getId());
}
@Test
void shouldHandleStrike()
{
var message1 = getGiftMessage("example-new-name", 123, "image-new.png", 1, 1,true);
var message2 = getGiftMessage("example-new-name", 123, "image-new.png", 2, 1,true);
var message3 = getGiftMessage("example-new-name", 123, "image-new.png", 0, 1,true);
void shouldHandleStrike() {
var message1 = getGiftMessage("example-new-name", 123, "image-new.png", 1, 1, true);
var message2 = getGiftMessage("example-new-name", 123, "image-new.png", 2, 1, true);
var message3 = getGiftMessage("example-new-name", 123, "image-new.png", 0, 1, true);
var result1 = handler.handleGift(message1);
var result2 = handler.handleGift(message2);
@@ -96,9 +98,9 @@ class TikTokGiftEventHandlerTest {
Assertions.assertEquals(2, result3.size());
var event3 = (TikTokGiftComboEvent) result3.get(0);
Assertions.assertEquals(GiftSendType.Begin,event1.getComboState());
Assertions.assertEquals(GiftSendType.Active,event2.getComboState());
Assertions.assertEquals(GiftSendType.Finished,event3.getComboState());
Assertions.assertEquals(GiftSendType.Begin, event1.getComboState());
Assertions.assertEquals(GiftSendType.Active, event2.getComboState());
Assertions.assertEquals(GiftSendType.Finished, event3.getComboState());
}
@@ -116,7 +118,7 @@ class TikTokGiftEventHandlerTest {
giftBuilder.setId(giftId);
giftBuilder.setName(giftName);
giftBuilder.setImage(Image.newBuilder().addUrlList(giftImage).build());
giftBuilder.setType(streakable?1:0);
giftBuilder.setType(streakable ? 1 : 0);
userBuilder.setId(userId);
builder.setGiftId(giftId);
@@ -127,5 +129,4 @@ class TikTokGiftEventHandlerTest {
}
}

View File

@@ -0,0 +1,47 @@
package io.github.jwdeveloper.tiktok.http;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import java.util.*;
public class HttpUtilsTest
{
@Test
public void parseParameters_EmptyParameters_ShouldHaveNoParameters()
{
String parsed = HttpUtils.parseParameters("https://webcast.tiktok.com/webcast/im/fetch/", new HashMap<>());
Assertions.assertEquals("https://webcast.tiktok.com/webcast/im/fetch/", parsed);
}
@Test
public void parseParameters_ValidParameters_ShouldConstructValidURL()
{
LinkedHashMap<String, Object> testMap = new LinkedHashMap<>();
testMap.put("room_id", 1);
testMap.put("uniqueId", "randomName");
String parsed = HttpUtils.parseParameters("https://webcast.tiktok.com/webcast/im/fetch/", testMap);
Assertions.assertEquals("https://webcast.tiktok.com/webcast/im/fetch/?room_id=1&uniqueId=randomName", parsed);
}
@Test
public void parseParametersEncode_EmptyParameters_ShouldHaveNoParameters()
{
String parsed = HttpUtils.parseParametersEncode("https://webcast.tiktok.com/webcast/im/fetch/", new HashMap<>());
Assertions.assertEquals("https://webcast.tiktok.com/webcast/im/fetch/", parsed);
}
@Test
public void parseParametersEncode_ValidParameters_ShouldConstructValidURL()
{
LinkedHashMap<String, Object> testMap = new LinkedHashMap<>();
testMap.put("room_id", 1);
testMap.put("root_referer", "https://www.tiktok.com/");
String parsed = HttpUtils.parseParametersEncode("https://webcast.tiktok.com/webcast/im/fetch/", testMap);
Assertions.assertEquals("https://webcast.tiktok.com/webcast/im/fetch/?room_id=1&root_referer=https%3A%2F%2Fwww.tiktok.com%2F", parsed);
}
}

View File

@@ -24,7 +24,6 @@ package io.github.jwdeveloper.tiktok.http;
import com.google.gson.JsonObject;
import io.github.jwdeveloper.tiktok.ClientSettings;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import io.github.jwdeveloper.tiktok.live.LiveRoomMeta;
import io.github.jwdeveloper.tiktok.mappers.LiveRoomMetaMapper;
@@ -84,28 +83,6 @@ public class TikTokApiServiceTest
verify(tiktokHttpClient, times(1)).setSessionId("validSessionId");
}
// @Test
void fetchRoomId_ValidResponse_ReturnsRoomId() throws Exception {
String expectedRoomId = "123456";
String htmlResponse = "room_id=" + expectedRoomId ;
when(tiktokHttpClient.getLivestreamPage(anyString())).thenReturn(htmlResponse);
String roomId = tikTokApiService.fetchRoomId("username");
assertEquals(expectedRoomId, roomId);
verify(clientSettings.getClientParameters()).put("room_id", expectedRoomId);
}
// @Test
void fetchRoomId_ExceptionThrown_ThrowsTikTokLiveRequestException() throws Exception {
when(tiktokHttpClient.getLivestreamPage(anyString())).thenThrow(new Exception("some exception"));
assertThrows(TikTokLiveRequestException.class, () -> {
tikTokApiService.fetchRoomId("username");
});
}
//@Test
void fetchRoomInfo_ValidResponse_ReturnsLiveRoomMeta() throws Exception {
HashMap<String, Object> clientParameters = new HashMap<>();

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

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

View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.live.GiftManager;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import lombok.AllArgsConstructor;
public class CustomEventExample {
@AllArgsConstructor
public static class CheapGiftEvent extends TikTokEvent {
Gift gift;
}
@AllArgsConstructor
public static class ExpensiveGiftEvent extends TikTokEvent {
Gift gift;
}
public static void main(String[] args) {
TikTokLive.newClient(SimpleExample.TIKTOK_HOSTNAME)
.onGift((liveClient, event) ->
{
if (event.getGift().getDiamondCost() > 100)
liveClient.publishEvent(new ExpensiveGiftEvent(event.getGift()));
else
liveClient.publishEvent(new CheapGiftEvent(event.getGift()));
})
.onEvent(CheapGiftEvent.class,(liveClient, event) ->
{
System.out.println("Thanks for cheap gift");
})
.onEvent(ExpensiveGiftEvent.class,(liveClient, event) ->
{
System.out.println("Thanks for expensive gift!");
})
.build();
}
}

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

@@ -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

@@ -1,7 +1,6 @@
<div align="center" >
<a target="blank" >
<img src="https://raw.githubusercontent.com/jwdeveloper/TikTokLiveJava/develop-1_0_0/Tools-ReadmeGenerator/src/main/resources/logo.svg" width="15%" >
</img>
</a>
</div>
<div align="center" >
@@ -12,18 +11,15 @@
<div align="center" >
<a href="https://jitpack.io/#jwdeveloper/TikTok-Live-Java" target="blank" >
<img src="https://jitpack.io/v/jwdeveloper/TikTok-Live-Java.svg" width="20%" >
</img>
</a>
<a href="https://discord.gg/e2XwPNTBBr" target="blank" >
<img src="https://img.shields.io/badge/Discord-%235865F2.svg?style=for-the-badge&logo=discord&logoColor=white" >
</img>
</a>
<a target="blank" >
<img src="https://img.shields.io/badge/java-%23ED8B00.svg?style=for-the-badge&logo=openjdk&logoColor=white" >
</img>
</a>
</div>
</div>
@@ -33,7 +29,7 @@ A Java library inspired by [TikTokLive](https://github.com/isaackogan/TikTokLive
The library includes a wrapper that connects to the WebCast service using just the username (`uniqueId`). This allows you to connect to your own live chat as well as the live chat of other streamers.
No credentials are required. Events such as [Members Joining](#member), [Gifts](#gift), [Subscriptions](#subscribe), [Viewers](#roomuser), [Follows](#social), [Shares](#social), [Questions](#questionnew), [Likes](#like) and [Battles](#linkmicbattle) can be tracked.
# Contributors
# Contributors
[Library documentation for contributors](https://github.com/jwdeveloper/TikTokLiveJava/wiki)
<div align="center">
@@ -58,7 +54,7 @@ Do you prefer other programming languages?
## Getting started
1. Install the package
1. Install the package
Maven
```xml
@@ -73,7 +69,7 @@ Maven
<dependency>
<groupId>com.github.jwdeveloper.TikTok-Live-Java</groupId>
<artifactId>Client</artifactId>
<version>1.0.7-Release</version>
<version>1.0.12-Release</version>
<scope>compile</scope>
</dependency>
</dependencies>
@@ -90,7 +86,7 @@ dependencyResolutionManagement {
}
dependencies {
implementation 'com.github.jwdeveloper.TikTok-Live-Java:Client:1.0.7-Release'
implementation 'com.github.jwdeveloper.TikTok-Live-Java:Client:1.0.12-Release'
}
```
@@ -438,9 +434,9 @@ Triggered every time gift is sent
<p>>Combo: 1 -> comboState = GiftSendType.Begin</p>
<p>Combo: 4 -> comboState = GiftSendType.Active</p>
<p>Combo: 8 -> comboState = GiftSendType.Active</p>
<p>Combo: 12 -> comboState = GiftSendType.Finsihed</p>
<p>Combo: 12 -> comboState = GiftSendType.Finished</p>
Remember if comboState is Finsihed both TikTokGiftComboEvent and TikTokGiftEvent event gets triggered
Remember if comboState is Finished both TikTokGiftComboEvent and TikTokGiftEvent event gets triggered
```java

View File

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

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

@@ -0,0 +1,33 @@
/*
* 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;
public interface DataCollector {
void connect();
void disconnect();
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

@@ -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.collector.api;
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
import lombok.Data;
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

@@ -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;
@@ -55,9 +55,4 @@ public class ApiServiceMock extends TikTokApiService {
return WebcastResponse.newBuilder().build();
}
@Override
public String fetchRoomId(String userName) {
return "mock-room-id";
}
}

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());
thread = new Thread(() ->
{
while (isRunning)
{
while (!responses.isEmpty()) {
var response = responses.pop();
messageHandler.handle(client, response);
}
}
});
isRunning = true;
while (!responses.isEmpty() || !messages.isEmpty()) {
if (!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);
}
}
}
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

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

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);
}
}

Some files were not shown because too many files have changed in this diff Show More