Compare commits

..

4 Commits

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

Http-client settings in configure method

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

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

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

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

Http-client settings in configure method

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

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

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

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

Http-client settings in configure method

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

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

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

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

3
.gitignore vendored
View File

@@ -1,11 +1,10 @@
backend-infrastructure/.aws-sam
.db
# 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.6.0-Release</version>
<version>1.0.14-Release</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>API</artifactId>

View File

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

View File

@@ -47,23 +47,11 @@ public class TikTokCommentEvent extends TikTokHeaderEvent {
public TikTokCommentEvent(WebcastChatMessage msg) {
super(msg.getCommon());
user = User.map(msg.getUser(), msg.getUserIdentity());
user = User.map(msg.getUser(),msg.getUserIdentity());
text = msg.getContent();
visibleToSender = msg.getVisibleToSender();
getUserLanguage = msg.getContentLanguage();
mentionedUser = User.map(msg.getAtUser());
pictures = msg.getEmotesListList().stream().map(e -> Picture.map(e.getEmote().getImage())).toList();
}
public static TikTokCommentEvent of(String userName, String message) {
var builder = WebcastChatMessage.newBuilder();
builder.setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder()
.setNickname(userName)
.build());
builder.setContentLanguage("en");
builder.setVisibleToSender(true);
builder.setContent(message);
return new TikTokCommentEvent(builder.build());
}
}

View File

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

View File

@@ -27,12 +27,12 @@ import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokHeaderEvent;
import io.github.jwdeveloper.tiktok.data.models.LinkMicArmy;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.messages.enums.LinkMicBattleStatus;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkMicArmies;
import lombok.Getter;
import java.util.List;
/**
* Triggered every time a battle participant receives points. Contains the current status of the battle and the army that suported the group.
*/
@@ -40,10 +40,8 @@ import java.util.List;
@EventMeta(eventType = EventType.Message)
public class TikTokLinkMicArmiesEvent extends TikTokHeaderEvent {
private final Long battleId;
/**
true if battle is finished otherwise false
*/
private final boolean finished;
private final Integer battleStatus;
private final Picture picture;
@@ -54,6 +52,6 @@ public class TikTokLinkMicArmiesEvent extends TikTokHeaderEvent {
battleId = msg.getId();
armies = msg.getBattleItemsList().stream().map(LinkMicArmy::new).toList();
picture = Picture.map(msg.getImage());
finished = msg.getBattleStatus() == LinkMicBattleStatus.ARMY_FINISHED;
battleStatus = msg.getBattleStatus();
}
}
}

View File

@@ -22,62 +22,29 @@
*/
package io.github.jwdeveloper.tiktok.data.events;
import io.github.jwdeveloper.tiktok.annotations.*;
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.battles.*;
import io.github.jwdeveloper.tiktok.messages.enums.LinkMicBattleStatus;
import io.github.jwdeveloper.tiktok.data.models.LinkMicBattleTeam;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkMicBattle;
import lombok.*;
import lombok.Getter;
import java.util.*;
import java.util.List;
/**
* Triggered every time a battle starts & ends
* Triggered every time a battle starts.
*/
@Getter
@EventMeta(eventType = EventType.Message)
public class TikTokLinkMicBattleEvent extends TikTokHeaderEvent
{
private final Long battleId;
/**
true if battle is finished otherwise false
*/
private final boolean finished;
@Getter(AccessLevel.NONE)
private final boolean oneVsOne;
private final List<Team> teams;
public class TikTokLinkMicBattleEvent extends TikTokHeaderEvent {
private final Long battleId;
private final List<LinkMicBattleTeam> team1;
private final List<LinkMicBattleTeam> team2;
public TikTokLinkMicBattleEvent(WebcastLinkMicBattle msg) {
super(msg.getCommon());
battleId = msg.getId();
finished = msg.getBattleStatus() == LinkMicBattleStatus.BATTLE_FINISHED;
teams = new ArrayList<>();
if (msg.getHostTeamCount() == 2) { // 1v1 battle
teams.add(new Team1v1(msg.getHostTeam(0), msg));
teams.add(new Team1v1(msg.getHostTeam(1), msg));
oneVsOne = true;
} else { // 2v2 battle
if (isFinished()) {
teams.add(new Team2v2(msg.getHostData2V2List().stream().filter(data -> data.getTeamNumber() == 1).findFirst().orElse(null), msg));
teams.add(new Team2v2(msg.getHostData2V2List().stream().filter(data -> data.getTeamNumber() == 2).findFirst().orElse(null), msg));
} else {
teams.add(new Team2v2(msg.getHostTeam(0), msg.getHostTeam(1), msg));
teams.add(new Team2v2(msg.getHostTeam(2), msg.getHostTeam(3), msg));
}
oneVsOne = false;
}
// Info:
// - msg.getDetailsList() & msg.getViewerTeamList() both only have content when battle is finished
// - msg.getDetailsCount() & msg.getViewerTeamCount() always is 2 only when battle is finished
// - msg.getHostTeamCount() always is 2 for 1v1 or 4 for 2v2
}
public boolean is1v1() {
return oneVsOne;
}
public boolean is2v2() {
return !oneVsOne;
}
}
public TikTokLinkMicBattleEvent(WebcastLinkMicBattle msg) {
super(msg.getCommon());
battleId = msg.getId();
team1 = msg.getTeams1List().stream().map(LinkMicBattleTeam::new).toList();
team2 = msg.getTeams2List().stream().map(LinkMicBattleTeam::new).toList();
}
}

View File

@@ -36,10 +36,10 @@ import lombok.Getter;
*/
@Getter
@EventMeta(eventType = EventType.Message)
public class TikTokSubscribeEvent extends TikTokHeaderEvent {
public class TikTokSubscribeEvent extends TikTokHeaderEvent
{
private final User user;
public TikTokSubscribeEvent(WebcastMemberMessage msg) {
super(msg.getCommon());
user = User.map(msg.getUser());
@@ -52,11 +52,4 @@ public class TikTokSubscribeEvent extends TikTokHeaderEvent {
user.addAttribute(UserAttribute.Subscriber);
}
public static TikTokSubscribeEvent of(String userName) {
return new TikTokSubscribeEvent(WebcastMemberMessage.newBuilder()
.setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder()
.setNickname(userName)
.build())
.build());
}
}

View File

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

View File

@@ -22,9 +22,10 @@
*/
package io.github.jwdeveloper.tiktok.data.events.gift;
import io.github.jwdeveloper.tiktok.annotations.*;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.gifts.*;
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;
@@ -33,7 +34,7 @@ import lombok.Getter;
/**
* Triggered every time gift is sent
*
* @see GiftComboStateType it has 3 states
* @see GiftSendType it has 3 states
*
* <p>Example when user sends gift with combo</p>
* <p>>Combo: 1 -> comboState = GiftSendType.Begin</p>
@@ -46,21 +47,10 @@ import lombok.Getter;
@EventMeta(eventType = EventType.Message)
@Getter
public class TikTokGiftComboEvent extends TikTokGiftEvent {
private final GiftComboStateType comboState;
private final GiftSendType comboState;
public TikTokGiftComboEvent(Gift gift, User host, WebcastGiftMessage msg, GiftComboStateType comboState) {
public TikTokGiftComboEvent(Gift gift, User host, WebcastGiftMessage msg, GiftSendType comboState) {
super(gift, host, msg);
this.comboState = comboState;
}
public static TikTokGiftComboEvent of(Gift gift, int combo, GiftComboStateType comboState) {
return new TikTokGiftComboEvent(
gift,
new User(0L, "Test", new Picture("")),
WebcastGiftMessage
.newBuilder()
.setComboCount(combo)
.build(),
comboState);
}
}
}

View File

@@ -23,14 +23,18 @@
package io.github.jwdeveloper.tiktok.data.events.gift;
import io.github.jwdeveloper.tiktok.annotations.*;
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.*;
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
@@ -56,20 +60,4 @@ public class TikTokGiftEvent extends TikTokHeaderEvent {
}
combo = msg.getComboCount();
}
public TikTokGiftEvent(Gift gift) {
this.gift = gift;
user = new User(0L, "sender", new Picture(""));
toUser = new User(0L, "receiver", new Picture(""));
combo = 1;
}
public static TikTokGiftEvent of(Gift gift) {
return new TikTokGiftEvent(gift);
}
public static TikTokGiftEvent of(String name, int id, int diamonds) {
return TikTokGiftEvent.of(new Gift(id, name, diamonds, ""));
}
}

View File

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

View File

@@ -24,10 +24,8 @@ package io.github.jwdeveloper.tiktok.data.events.social;
import io.github.jwdeveloper.tiktok.annotations.EventMeta;
import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.data.events.TikTokSubscribeEvent;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokHeaderEvent;
import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastMemberMessage;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastSocialMessage;
import lombok.Value;
@@ -47,12 +45,4 @@ public class TikTokFollowEvent extends TikTokHeaderEvent
totalFollowers = msg.getFollowCount();
}
public static TikTokFollowEvent of(String userName)
{
return new TikTokFollowEvent(WebcastSocialMessage.newBuilder()
.setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder()
.setNickname(userName)
.build())
.build());
}
}

View File

@@ -26,7 +26,6 @@ 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.users.User;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLikeMessage;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastMemberMessage;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastSocialMessage;
import lombok.Getter;
@@ -48,13 +47,4 @@ public class TikTokJoinEvent extends TikTokHeaderEvent {
user = User.map(msg.getUser());
totalUsers = msg.getMemberCount();
}
public static TikTokJoinEvent of(String userName)
{
return new TikTokJoinEvent(WebcastMemberMessage.newBuilder()
.setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder()
.setNickname(userName)
.build())
.build());
}
}

View File

@@ -57,15 +57,4 @@ public class TikTokLikeEvent extends TikTokHeaderEvent
likes = msg.getCount();
totalLikes = msg.getTotal();
}
public static TikTokLikeEvent of(String userName, int likes)
{
return new TikTokLikeEvent(WebcastLikeMessage.newBuilder()
.setCount(likes)
.setTotal(likes)
.setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder()
.setNickname(userName)
.build())
.build());
}
}

View File

@@ -46,4 +46,4 @@ public class LinkMicArmy {
List<User> Users;
Integer Points;
}
}
}

View File

@@ -20,21 +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.extension.collector.api.settings.mongo;
package io.github.jwdeveloper.tiktok.data.models;
import lombok.Setter;
import lombok.experimental.Accessors;
@Setter
@Accessors(chain = true)
public class MongoDBConnectionStringBuilder {
private String username;
private String password;
private String database;
private String cluster;
import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkMicBattle;
import lombok.Value;
public String build() {
return String.format("mongodb+srv://%s:%s@%s/%s?retryWrites=true&w=majority",
username, password, cluster, database);
import java.util.List;
@Value
public class LinkMicBattleTeam {
Long teamId;
List<User> users;
public LinkMicBattleTeam(WebcastLinkMicBattle.LinkMicBattleTeam team) {
this.teamId = team.getId();
this.users = team.getUsersList().stream().map(User::new).toList();
}
}
}

View File

@@ -44,11 +44,11 @@ public class Picture {
}
public static Picture map(io.github.jwdeveloper.tiktok.messages.data.Image profilePicture) {
var index = profilePicture.getUrlCount() - 1;
var index = profilePicture.getUrlListCount() - 1;
if (index < 0) {
return new Picture("");
}
var url = profilePicture.getUrl(index);
var url = profilePicture.getUrlList(index);
return new Picture(url);
}

View File

@@ -1,47 +0,0 @@
package io.github.jwdeveloper.tiktok.data.models.battles;
public abstract class Team {
/**
* Provides a check for verifying if this team represents a 1v1 Team.
* @return true if this team is of type {@link Team1v1}, false otherwise.
*/
public boolean is1v1Team() {
return this instanceof Team1v1;
}
/**
* Provides a check for verifying if this team represents a 1v1 Team.
* @return true if this team is of type {@link Team1v1}, false otherwise.
*/
public boolean is2v2Team() {
return this instanceof Team2v2;
}
/**
* Convenience method to get this team as a {@link Team1v1}. If this team is of some
* other type, an {@link IllegalStateException} will result. Hence it is best to use this method
* after ensuring that this element is of the desired type by calling {@link #is1v1Team()} first.
*
* @return this team as a {@link Team1v1}.
* @throws IllegalStateException if this team is of another type.
*/
public Team1v1 getAs1v1Team() {
if (is1v1Team())
return (Team1v1) this;
throw new IllegalStateException("Not a 1v1Team: " + this);
}
/**
* Convenience method to get this team as a {@link Team2v2}. If this team is of some
* other type, an {@link IllegalStateException} will result. Hence it is best to use this method
* after ensuring that this element is of the desired type by calling {@link #is2v2Team()} first.
*
* @return this team as a {@link Team2v2}.
* @throws IllegalStateException if this team is of another type.
*/
public Team2v2 getAs2v2Team() {
if (is2v2Team())
return (Team2v2) this;
throw new IllegalStateException("Not a 2v2Team: " + this);
}
}

View File

@@ -1,26 +0,0 @@
package io.github.jwdeveloper.tiktok.data.models.battles;
import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkMicBattle;
import lombok.Getter;
import java.util.*;
@Getter
public class Team1v1 extends Team
{
/** Value >= 0 when finished otherwise -1 */
private final int totalPoints;
private final int winStreak;
private final User host;
private final List<Viewer> viewers;
public Team1v1(WebcastLinkMicBattle.LinkMicBattleHost hostTeam, WebcastLinkMicBattle msg) {
long hostId = hostTeam.getId();
this.winStreak = msg.getTeamDataList().stream().filter(data -> data.getTeamId() == hostId).map(data -> data.getData().getWinStreak()).findFirst().orElse(-1);
this.totalPoints = msg.getDetailsList().stream().filter(dets -> dets.getId() == hostId).map(dets -> dets.getSummary().getPoints()).findFirst().orElse(-1);
this.host = new User(hostTeam.getHostGroup(0).getHost(0));
this.viewers = msg.getViewerTeamList().stream().filter(team -> team.getId() == hostId).findFirst().map(topViewers ->
topViewers.getViewerGroup(0).getViewerList().stream().map(Viewer::new).toList()).orElseGet(ArrayList::new);
}
}

View File

@@ -1,31 +0,0 @@
package io.github.jwdeveloper.tiktok.data.models.battles;
import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkMicBattle;
import lombok.Getter;
import java.util.*;
@Getter
public class Team2v2 extends Team {
/** Value >= 0 when finished otherwise -1 */
private final int totalPoints;
private final List<User> hosts;
private final List<Viewer> viewers;
public Team2v2(WebcastLinkMicBattle.LinkMicBattleHost hostTeam1, WebcastLinkMicBattle.LinkMicBattleHost hostTeam2, WebcastLinkMicBattle msg) {
this.totalPoints = -1;
this.hosts = List.of(new User(hostTeam1.getHostGroup(0).getHost(0)), new User(hostTeam2.getHostGroup(0).getHost(0)));
this.viewers = new ArrayList<>();
}
public Team2v2(WebcastLinkMicBattle.Host2v2Data hd, WebcastLinkMicBattle msg) {
this.totalPoints = hd.getTotalPoints();
var host = new User(msg.getHostTeamList().stream().filter(data -> data.getId() == hd.getHostdata(0).getHostId()).findFirst().orElseThrow().getHostGroup(0).getHost(0));
var cohost = new User(msg.getHostTeamList().stream().filter(data -> data.getId() == hd.getHostdata(1).getHostId()).findFirst().orElseThrow().getHostGroup(0).getHost(0));
this.hosts = List.of(host, cohost);
this.viewers = msg.getViewerTeamList().stream().filter(team -> team.getId() == host.getId() || team.getId() == cohost.getId()).findFirst().map(topViewers ->
topViewers.getViewerGroup(0).getViewerList().stream().map(Viewer::new).toList()).orElseGet(ArrayList::new);
}
}

View File

@@ -1,17 +0,0 @@
package io.github.jwdeveloper.tiktok.data.models.battles;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkMicBattle;
import lombok.Getter;
@Getter
public class Viewer {
private final User user;
private final int points;
public Viewer(WebcastLinkMicBattle.LinkMicBattleTopViewers.TopViewerGroup.TopViewer topViewer) {
this.user = new User(topViewer.getId(), null, topViewer.getProfileId(), Picture.map(topViewer.getImages(0)));
this.points = topViewer.getPoints();
}
}

View File

@@ -22,16 +22,19 @@
*/
package io.github.jwdeveloper.tiktok.data.models.gifts;
//TODO it should be called GiftComboStateType
public enum GiftComboStateType {
public enum GiftSendType
{
Finished,
Begin,
Active;
public static GiftComboStateType fromNumber(long number) {
public static GiftSendType fromNumber(long number)
{
return switch ((int) number) {
case 1, 2, 4 -> GiftComboStateType.Active;
default -> GiftComboStateType.Finished;
case 0 -> GiftSendType.Finished;
case 1, 2, 4 -> GiftSendType.Active;
default -> GiftSendType.Finished;
};
}
}
}

View File

@@ -24,7 +24,7 @@ package io.github.jwdeveloper.tiktok.data.models.users;
import io.github.jwdeveloper.tiktok.data.models.badges.Badge;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.messages.webcast.*;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastEnvelopeMessage;
import lombok.AccessLevel;
import lombok.Getter;
@@ -136,14 +136,6 @@ public class User {
this.picture = picture;
}
public User(long id, String name, String profileId, Picture picture) {
this(id, name, profileId, picture, 0, 0, List.of(Badge.empty()));
}
public User(WebcastLinkMicBattle.LinkMicBattleHost.HostGroup.Host host) {
this(host.getId(), host.getName(), host.getProfileId(), Picture.map(host.getImages(0)));
}
public User(io.github.jwdeveloper.tiktok.messages.data.User user) {
this(user.getId(), user.getDisplayId(), Picture.map(user.getAvatarThumb()));
profileName = user.getNickname();
@@ -167,6 +159,7 @@ public class User {
}
}
public static User EMPTY = new User(0L,
"",
Picture.Empty(),
@@ -216,18 +209,4 @@ public class User {
0,
List.of(Badge.empty()));
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + "'" +
", profileName='" + profileName + "'" +
", picture=" + picture +
", following=" + following +
", followers=" + followers +
", badges=" + badges +
", attributes=" + attributes +
"}";
}
}

View File

@@ -42,7 +42,16 @@ public class GiftsData
public static final class Response
{
private String json;
private List<Gift> gifts;
private List<GiftModel> gifts;
}
@Data
public static class GiftModel
{
private int id;
private String name;
private int diamondCost;
private String image;
}
}

View File

@@ -35,7 +35,6 @@ public class LiveData {
}
@Data
@AllArgsConstructor
public static class Response {
private String json;
private LiveStatus liveStatus;
@@ -45,12 +44,6 @@ public class LiveData {
private int totalViewers;
private boolean ageRestricted;
private User host;
private LiveType liveType;
public Response() {
}
}
public enum LiveStatus {
@@ -58,11 +51,4 @@ public class LiveData {
HostOnline,
HostOffline,
}
public enum LiveType {
SOLO,
BOX,
BATTLE,
CO_HOST
}
}
}

View File

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

View File

@@ -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.data.requests;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class SignServerResponse
{
private String signedUrl;
private String userAgent;
}

View File

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

View File

@@ -33,26 +33,10 @@ import java.util.logging.Level;
@Data
public class LiveClientSettings {
/**
* TODO: give better description
* <p>
* sets client in the offline mode, so it do not connects to TikTok servers
* it makes sense to use it when you are testing client with your custom events
*/
private boolean offline;
/**
* TODO: give better description
* <p>
* Determines if gifts data is downloaded before TikTokLive starts,
* when `false` then client.giftManager() does not contain initial gifts
*/
private boolean fetchGifts = true;
/**
* ISO-Language for Client
*/
private String clientLanguage;
/**
@@ -60,6 +44,7 @@ public class LiveClientSettings {
*/
private boolean retryOnConnectionFailure;
/**
* Before retrying connect, wait for select amount of time
*/
@@ -68,49 +53,44 @@ public class LiveClientSettings {
/**
* Whether to print Logs to Console
*/
private boolean printToConsole = true;
private boolean printToConsole = true;
/**
* LoggingLevel for Logs
*/
private Level logLevel;
/**
* Optional: Use it if you need to change TikTok live hostname in builder
*/
private String hostName;
/**
* Parameters used in requests to TikTok api
*/
private HttpClientSettings httpSettings;
/**
* Interval of time in milliseconds between pings to TikTok
* @apiNote Min: 250 (0.25 seconds), Default: 5000 (5 seconds)
/*
* Optional: Sometimes not every messages from chat are send to TikTokLiveJava to fix this issue you can set sessionId
* documentation how to obtain sessionId https://github.com/isaackogan/TikTok-Live-Connector#send-chat-messages
*/
private long pingInterval = 5000;
/** Throw an exception on 18+ Age Restriction */
private boolean throwOnAgeRestriction;
/**
* Optional: Sometimes not every messages from chat are send to TikTokLiveJava to fix this issue you can set sessionId
* @see <a href="https://github.com/isaackogan/TikTok-Live-Connector#send-chat-messages">Documentation: How to obtain sessionId</a>
*/
private String sessionId;
/**
/*
* Optional: By default roomID is fetched before connect to live, but you can set it manually
*
*/
private String roomId;
/**
* Optional: API Key for increased limit to signing server
*/
private String apiKey;
public static LiveClientSettings createDefault() {
public static LiveClientSettings createDefault()
{
var httpSettings = new HttpClientSettings();
httpSettings.getParams().putAll(DefaultClientParams());
httpSettings.getHeaders().putAll(DefaultRequestHeaders());
@@ -123,10 +103,12 @@ public class LiveClientSettings {
clientSettings.setPrintToConsole(false);
clientSettings.setLogLevel(Level.ALL);
clientSettings.setHttpSettings(httpSettings);
return clientSettings;
}
/**
* Default Parameters for HTTP-Request
*/
@@ -165,9 +147,11 @@ public class LiveClientSettings {
clientParams.put("webcast_sdk_version", "1.3.0");
clientParams.put("update_version_code", "1.3.0");
return clientParams;
}
/**
* Default Headers for HTTP-Request
*/
@@ -184,5 +168,5 @@ public class LiveClientSettings {
return headers;
}
}
}

View File

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

View File

@@ -23,7 +23,7 @@
package io.github.jwdeveloper.tiktok.exceptions;
/**
/*
* Happens while bad response from Http request to TikTok
*/
public class TikTokLiveRequestException extends TikTokLiveException
@@ -46,4 +46,4 @@ public class TikTokLiveRequestException extends TikTokLiveException
public TikTokLiveRequestException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
}

View File

@@ -27,42 +27,38 @@ import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData;
import io.github.jwdeveloper.tiktok.data.requests.LiveData;
import io.github.jwdeveloper.tiktok.data.requests.LiveUserData;
public interface LiveHttpClient
{
public interface LiveHttpClient {
/**
* @return list of gifts that are available in your country
*/
GiftsData.Response fetchGiftsData();
/**
* Returns information about user that is having a livestream
* @param userName name of user
* @return {@link LiveUserData.Response}
*
* @param userName
* @return
*/
default LiveUserData.Response fetchLiveUserData(String userName) {
return fetchLiveUserData(new LiveUserData.Request(userName));
}
LiveUserData.Response fetchLiveUserData(String userName);
LiveUserData.Response fetchLiveUserData(LiveUserData.Request request);
/**
* @param roomId can be obtained from browsers cookies or by invoked fetchLiveUserData
* @return {@link LiveData.Response}
* @return
*/
default LiveData.Response fetchLiveData(String roomId) {
return fetchLiveData(new LiveData.Request(roomId));
}
LiveData.Response fetchLiveData(String roomId);
LiveData.Response fetchLiveData(LiveData.Request request);
/**
* @param roomId can be obtained from browsers cookies or by invoked fetchLiveUserData
* @return {@link LiveConnectionData.Response}
* @return
*/
default LiveConnectionData.Response fetchLiveConnectionData(String roomId) {
return fetchLiveConnectionData(new LiveConnectionData.Request(roomId));
}
LiveConnectionData.Response fetchLiveConnectionData(String roomId);
LiveConnectionData.Response fetchLiveConnectionData(LiveConnectionData.Request request);
}
}

View File

@@ -0,0 +1,63 @@
/*
* 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.live;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import java.util.List;
public interface GiftManager {
/**
* In case you can't find your gift in Gift enum. You can register gift
* manually here to make it detected while TikTokGiftEvent
*
* @param id gift's id
* @param name gift's name
* @param diamondCost diamond cost
* @return
*/
Gift registerGift(int id, String name, int diamondCost, Picture picture);
/**
*
* @param giftId
* @return
*/
Gift findById(int giftId);
/**
*
* @param giftName
* @return
*/
Gift findByName(String giftName);
/**
*
* @return all gifts
*/
List<Gift> getGifts();
}

View File

@@ -1,86 +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.live;
import com.google.gson.JsonObject;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.gifts.*;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
public interface GiftsManager {
/**
* You can create and attach your own custom gift to manager
*
* @param gift
*/
void attachGift(Gift gift);
/**
* You can create and attach your own custom gift to manager
*
* @param gifts
*/
void attachGiftsList(List<Gift> gifts);
/**
* finds gift by name
* When gift not found return Gift.UNDEFINED;
*
* @param name gift name
*/
Gift getByName(String name);
/**
* finds gift by id
* When gift not found return Gift.UNDEFINED;
*
* @param giftId giftId
*/
Gift getById(int giftId);
/**
* finds gift by filter
* When gift not found return Gift.UNDEFINED;
*/
Gift getByFilter(Predicate<Gift> filter);
List<Gift> getManyByFilter(Predicate<Gift> filter);
/**
* @return list of all gifts
*/
List<Gift> toList();
/**
* @return list of all map of all gifts where Integer is gift Id
*/
Map<Integer, Gift> toMap();
}

View File

@@ -59,21 +59,12 @@ public interface LiveClient {
/**
* Use to manually invoke event
*/
void publishEvent(TikTokEvent event);
/**
* @param webcastMessageName name of TikTok protocol-buffer message
* @param payloadBase64 protocol-buffer message bytes payload
*/
void publishMessage(String webcastMessageName, String payloadBase64);
void publishMessage(String webcastMessageName, byte[] payload);
void publishEvent(TikTokEvent event);
/**
* Get information about gifts
*/
GiftsManager getGiftManager();
GiftManager getGiftManager();
/**
* Gets the current room info from TikTok API including streamer info, room status and statistics.

View File

@@ -47,7 +47,7 @@ public interface LiveRoomInfo
String getRoomId();
String getHostName();
String getTitle();
User getHost();
User getHostUser();
List<RankingUser> getUsersRanking();
ConnectionState getConnectionState();
}

View File

@@ -24,7 +24,6 @@ package io.github.jwdeveloper.tiktok.live.builder;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.*;
import io.github.jwdeveloper.tiktok.data.events.control.TikTokPreConnectionEvent;
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;
@@ -150,13 +149,6 @@ public interface EventsBuilder<T> {
*/
T onConnected(EventConsumer<TikTokConnectedEvent> action);
/**
* Invoked before client has been successfully connected to live
* @param action
* @return
*/
T onPreConnection(EventConsumer<TikTokPreConnectionEvent> action);
/**
* Invoked when client tries to reconnect
* @param action
@@ -223,4 +215,6 @@ public interface EventsBuilder<T> {
//T onLinkMicBattle(TikTokEventConsumer<TikTokLinkMicBattleEvent> event);
//T onUnhandledControl(TikTokEventConsumer<TikTokUnhandledControlEvent> event);
}
}

View File

@@ -102,8 +102,7 @@ message Text {
// @Image
message Image {
repeated string url = 1;
string extras = 2;
repeated string urlList = 1;
bool isAnimated = 9;
}

View File

@@ -35,14 +35,6 @@ enum LinkmicApplierSortSetting {
LINKMIC_APPLIER_SORT_SETTING_BY_GIFT_SCORE = 1;
}
enum LinkMicBattleStatus {
BATTLE_ARMY_UNKNOWN = 0;
ARMY_ONGOING = 1;
ARMY_FINISHED = 2;
BATTLE_ONGOING = 4;
BATTLE_FINISHED = 5;
}
enum HashtagNamespace {
GLOBAL = 0;
GAMING = 1;

View File

@@ -521,7 +521,7 @@ message WebcastLinkMicArmies {
uint64 id2 = 4;
uint64 timeStamp1 = 5;
uint64 timeStamp2 = 6;
LinkMicBattleStatus battleStatus = 7;
int32 battleStatus = 7; // SHOULD BE AN ENUM
uint64 data1 = 8;
uint64 data2 = 9;
uint32 data3 = 10;
@@ -574,26 +574,11 @@ message WebcastLinkMicBattle {
Common common = 1;
uint64 id = 2;
LinkMicBattleConfig battleConfig = 3;
LinkMicBattleStatus battleStatus = 4;
uint32 data2 = 4;
repeated LinkMicBattleDetails details = 5;
repeated LinkMicBattleTopViewers viewerTeam = 9;
repeated LinkMicBattleHost hostTeam = 10;
repeated LinkMicBattleTeam teams1 = 9;
repeated LinkMicBattleTeam teams2 = 10;
repeated LinkMicBattleTeamData teamData = 13;
uint64 unknownData16 = 16;
repeated Host2v2Data hostData2v2 = 17;
message Host2v2Data {
uint32 teamNumber = 1;
repeated HostData hostdata = 2;
uint32 unknownData3 = 3;
uint32 totalPoints = 4;
message HostData {
uint64 hostId = 1;
uint32 points = 2;
string hostIdStr = 3;
}
}
message LinkMicBattleConfig {
uint64 id1 = 1;
@@ -601,69 +586,29 @@ message WebcastLinkMicBattle {
uint32 data1 = 3;
uint64 id2 = 4;
uint32 data2 = 5;
uint32 data3 = 6;
uint32 data4 = 8;
}
message LinkMicBattleTeamData {
uint64 teamId = 1;
LinkMicBattleData data = 2;
}
message LinkMicBattleData {
uint64 id = 1;
uint32 data1 = 2;
uint32 winStreak = 3;
uint32 data2 = 3;
uint32 data3 = 5;
string url = 6;
}
message LinkMicBattleDetails {
uint64 id = 1;
LinkMicBattleDetailsSummary summary = 2;
message LinkMicBattleDetailsSummary {
uint64 id = 1;
uint32 unknownData2 = 2;
uint32 points = 3;
}
LinkMicBattleData details = 2;
}
message LinkMicBattleTopViewers {
message LinkMicBattleTeam {
uint64 id = 1;
repeated TopViewerGroup viewerGroup = 2;
message TopViewerGroup {
repeated TopViewer viewer = 1;
uint32 points = 2;
string hostIdOrTeamNum = 3; // 1v1 Battle = HostId | 2v2 Battle = Team # - 1 & 2
message TopViewer {
uint64 id = 1;
uint32 points = 2;
string profileId = 3;
repeated Image images = 4;
string stringId = 6;
}
}
repeated User users = 2;
}
message LinkMicBattleHost {
uint64 id = 1;
repeated HostGroup hostGroup = 2;
message HostGroup {
repeated Host host = 1;
uint32 points = 2;
string hostId = 3;
message Host {
uint64 id = 1;
string profileId = 2;
repeated Image images = 3;
string name = 4;
}
}
message LinkMicBattleTeamData {
uint64 teamId = 1;
LinkMicBattleData data = 2;
}
}
@@ -847,4 +792,6 @@ message RoomVerifyMessage {
string content = 3;
int64 noticeType = 4;
bool closeRoom = 5;
}
}

View File

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

View File

@@ -23,64 +23,20 @@
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftsManager;
import io.github.jwdeveloper.tiktok.http.LiveHttpClient;
import io.github.jwdeveloper.tiktok.live.GiftsManager;
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
import java.util.concurrent.CompletableFuture;
public class TikTokLive {
/**
* Example: https://www.tiktok.com/@dostawcavideo - hostName would be 'dostawcavideo'
*
* @param hostName profile name of Tiktok user could be found in profile link
* example: https://www.tiktok.com/@dostawcavideo hostName would be dostawcavideo
* @return LiveClientBuilder
*/
public static LiveClientBuilder newClient(String hostName) {
return new TikTokLiveClientBuilder(hostName);
}
/**
* Example: https://www.tiktok.com/@dostawcavideo - hostName would be 'dostawcavideo'
*
* @param hostName profile name of Tiktok user could be found in profile link
* @return true if live is Online, false if is offline
*/
public static boolean isLiveOnline(String hostName) {
return requests().fetchLiveUserData(hostName).isLiveOnline();
}
/**
* Example: https://www.tiktok.com/@dostawcavideo - hostName would be 'dostawcavideo'
*
* @param hostName profile name of Tiktok user could be found in profile link
* @return true if live is Online, false if is offline
*/
public static CompletableFuture<Boolean> isLiveOnlineAsync(String hostName) {
return CompletableFuture.supplyAsync(() -> isLiveOnline(hostName));
}
/**
* Example: https://www.tiktok.com/@dostawcavideo - hostName would be 'dostawcavideo'
*
* @param hostName profile name of Tiktok user could be found in profile link
* @return true is hostName name is valid and exists, false if not
*/
public static boolean isHostNameValid(String hostName) {
return requests().fetchLiveUserData(hostName).isHostNameValid();
}
/**
* Example: https://www.tiktok.com/@dostawcavideo - hostName would be 'dostawcavideo'
*
* @param hostName profile name of Tiktok user could be found in profile link
* @return true is hostName name is valid and exists, false if not
*/
public static CompletableFuture<Boolean> isHostNameValidAsync(String hostName) {
return CompletableFuture.supplyAsync(() -> isHostNameValid(hostName));
}
/**
* Use to get some data from TikTok about users are lives
@@ -88,23 +44,9 @@ public class TikTokLive {
* @return LiveHttpClient
*/
public static LiveHttpClient requests() {
return new TikTokLiveHttpClient();
}
//I don't like it, but it is reasonable for now
private static GiftsManager giftsManager;
/**
* Fetch gifts from endpoint and returns GiftManager
*
* @return GiftsManager
*/
public static GiftsManager gifts() {
if (giftsManager == null) {
synchronized (GiftsManager.class) {
giftsManager = new TikTokGiftsManager(requests().fetchGiftsData().getGifts());
}
}
return giftsManager;
}
}
}

View File

@@ -22,51 +22,50 @@
*/
package io.github.jwdeveloper.tiktok;
import com.google.protobuf.ByteString;
import io.github.jwdeveloper.tiktok.data.events.*;
import io.github.jwdeveloper.tiktok.data.events.TikTokDisconnectedEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokReconnectingEvent;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.control.*;
import io.github.jwdeveloper.tiktok.data.events.http.TikTokRoomDataResponseEvent;
import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomInfoEvent;
import io.github.jwdeveloper.tiktok.data.requests.*;
import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
import io.github.jwdeveloper.tiktok.exceptions.*;
import io.github.jwdeveloper.tiktok.http.LiveHttpClient;
import io.github.jwdeveloper.tiktok.listener.*;
import io.github.jwdeveloper.tiktok.live.*;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData;
import io.github.jwdeveloper.tiktok.data.requests.LiveData;
import io.github.jwdeveloper.tiktok.data.requests.LiveUserData;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveOfflineHostException;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager;
import io.github.jwdeveloper.tiktok.listener.ListenersManager;
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.LiveRoomInfo;
import io.github.jwdeveloper.tiktok.models.ConnectionState;
import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
import io.github.jwdeveloper.tiktok.websocket.SocketClient;
import java.util.Base64;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.logging.Logger;
public class TikTokLiveClient implements LiveClient {
private final TikTokRoomInfo liveRoomInfo;
private final LiveHttpClient httpClient;
private final TikTokGiftManager tikTokGiftManager;
private final TikTokLiveHttpClient httpClient;
private final SocketClient webSocketClient;
private final TikTokLiveEventHandler tikTokEventHandler;
private final LiveClientSettings clientSettings;
private final TikTokListenersManager listenersManager;
private final Logger logger;
private final GiftsManager giftsManager;
private final TikTokLiveMessageHandler messageHandler;
public TikTokLiveClient(
TikTokLiveMessageHandler messageHandler,
GiftsManager giftsManager,
TikTokRoomInfo tikTokLiveMeta,
LiveHttpClient tiktokHttpClient,
SocketClient webSocketClient,
TikTokLiveEventHandler tikTokEventHandler,
LiveClientSettings clientSettings,
TikTokListenersManager listenersManager,
Logger logger) {
this.messageHandler = messageHandler;
this.giftsManager = giftsManager;
public TikTokLiveClient(TikTokRoomInfo tikTokLiveMeta,
TikTokLiveHttpClient tiktokHttpClient,
SocketClient webSocketClient,
TikTokGiftManager tikTokGiftManager,
TikTokLiveEventHandler tikTokEventHandler,
LiveClientSettings clientSettings,
TikTokListenersManager listenersManager,
Logger logger) {
this.liveRoomInfo = tikTokLiveMeta;
this.tikTokGiftManager = tikTokGiftManager;
this.httpClient = tiktokHttpClient;
this.webSocketClient = webSocketClient;
this.tikTokEventHandler = tikTokEventHandler;
@@ -77,15 +76,19 @@ public class TikTokLiveClient implements LiveClient {
public void connectAsync(Consumer<LiveClient> onConnection) {
CompletableFuture.runAsync(() -> {
CompletableFuture.supplyAsync(() ->
{
connect();
onConnection.accept(this);
return this;
});
}
public CompletableFuture<LiveClient> connectAsync() {
return CompletableFuture.supplyAsync(() -> {
return CompletableFuture.supplyAsync(() ->
{
connect();
return this;
});
@@ -117,36 +120,34 @@ public class TikTokLiveClient implements LiveClient {
}
public void tryConnect() {
if (!liveRoomInfo.hasConnectionState(ConnectionState.DISCONNECTED)) {
if (!liveRoomInfo.hasConnectionState(ConnectionState.DISCONNECTED))
{
throw new TikTokLiveException("Already connected");
}
setState(ConnectionState.CONNECTING);
tikTokEventHandler.publish(this, new TikTokConnectingEvent());
var userDataRequest = new LiveUserData.Request(liveRoomInfo.getHostName());
var userData = httpClient.fetchLiveUserData(userDataRequest);
liveRoomInfo.setStartTime(userData.getStartedAtTimeStamp());
liveRoomInfo.setRoomId(userData.getRoomId());
if (userData.getUserStatus() == LiveUserData.UserStatus.Offline) {
throw new TikTokLiveOfflineHostException("User is offline: "+liveRoomInfo.getHostUser());
}
if (userData.getUserStatus() == LiveUserData.UserStatus.NotFound) {
throw new TikTokLiveOfflineHostException("User not found: "+liveRoomInfo.getHostUser());
}
if (userData.getUserStatus() == LiveUserData.UserStatus.Offline)
throw new TikTokLiveOfflineHostException("User is offline: " + liveRoomInfo.getHostName());
if (userData.getUserStatus() == LiveUserData.UserStatus.NotFound)
throw new TikTokLiveOfflineHostException("User not found: " + liveRoomInfo.getHostName());
var liveDataRequest = new LiveData.Request(userData.getRoomId());
var liveData = httpClient.fetchLiveData(liveDataRequest);
if (liveData.isAgeRestricted() && clientSettings.isThrowOnAgeRestriction())
throw new TikTokLiveException("Livestream for " + liveRoomInfo.getHostName() + " is 18+ or age restricted!");
if (liveData.getLiveStatus() == LiveData.LiveStatus.HostNotFound)
throw new TikTokLiveOfflineHostException("LiveStream for " + liveRoomInfo.getHostName() + " could not be found.");
if (liveData.getLiveStatus() == LiveData.LiveStatus.HostOffline)
throw new TikTokLiveOfflineHostException("LiveStream for " + liveRoomInfo.getHostName() + " not found, is the Host offline?");
tikTokEventHandler.publish(this, new TikTokRoomDataResponseEvent(liveData));
if (liveData.getLiveStatus() == LiveData.LiveStatus.HostNotFound) {
throw new TikTokLiveOfflineHostException("LiveStream for Host name could not be found.");
}
if (liveData.getLiveStatus() == LiveData.LiveStatus.HostOffline) {
throw new TikTokLiveOfflineHostException("LiveStream for not be found, is the Host offline?");
}
liveRoomInfo.setTitle(liveData.getTitle());
liveRoomInfo.setViewersCount(liveData.getViewers());
@@ -154,12 +155,8 @@ public class TikTokLiveClient implements LiveClient {
liveRoomInfo.setAgeRestricted(liveData.isAgeRestricted());
liveRoomInfo.setHost(liveData.getHost());
var preconnectEvent = new TikTokPreConnectionEvent(userData, liveData);
tikTokEventHandler.publish(this, preconnectEvent);
if (preconnectEvent.isCancelConnection())
throw new TikTokLiveException("TikTokPreConnectionEvent cancelled connection!");
var liveConnectionRequest = new LiveConnectionData.Request(userData.getRoomId());
var liveConnectionRequest =new LiveConnectionData.Request(userData.getRoomId());
var liveConnectionData = httpClient.fetchLiveConnectionData(liveConnectionRequest);
webSocketClient.start(liveConnectionData, this);
@@ -171,8 +168,8 @@ public class TikTokLiveClient implements LiveClient {
if (liveRoomInfo.hasConnectionState(ConnectionState.DISCONNECTED)) {
return;
}
setState(ConnectionState.DISCONNECTED);
webSocketClient.stop();
setState(ConnectionState.DISCONNECTED);
}
private void setState(ConnectionState connectionState) {
@@ -184,24 +181,6 @@ public class TikTokLiveClient implements LiveClient {
tikTokEventHandler.publish(this, event);
}
@Override
public void publishMessage(String webcastMessageName, String payloadBase64) {
this.publishMessage(webcastMessageName, Base64.getDecoder().decode(payloadBase64));
}
@Override
public void publishMessage(String webcastMessageName, byte[] payload) {
var builder = WebcastResponse.Message.newBuilder();
builder.setMethod(webcastMessageName);
builder.setPayload(ByteString.copyFrom(payload));
var message = builder.build();
messageHandler.handleSingleMessage(this, message);
}
@Override
public GiftsManager getGiftManager() {
return giftsManager;
}
public LiveRoomInfo getRoomInfo() {
return liveRoomInfo;
@@ -216,4 +195,11 @@ public class TikTokLiveClient implements LiveClient {
public Logger getLogger() {
return logger;
}
@Override
public GiftManager getGiftManager() {
return tikTokGiftManager;
}
}

View File

@@ -22,49 +22,65 @@
*/
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.common.LoggerFactory;
import io.github.jwdeveloper.tiktok.data.events.*;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.control.TikTokPreConnectionEvent;
import io.github.jwdeveloper.tiktok.data.events.envelop.TikTokChestEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.*;
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.*;
import io.github.jwdeveloper.tiktok.data.events.social.*;
import io.github.jwdeveloper.tiktok.data.events.websocket.*;
import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomInfoEvent;
import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomPinEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokFollowEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokJoinEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokLikeEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokShareEvent;
import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketMessageEvent;
import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketResponseEvent;
import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketUnhandledMessageEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftsManager;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager;
import io.github.jwdeveloper.tiktok.http.HttpClientFactory;
import io.github.jwdeveloper.tiktok.listener.*;
import io.github.jwdeveloper.tiktok.live.*;
import io.github.jwdeveloper.tiktok.live.builder.*;
import io.github.jwdeveloper.tiktok.mappers.*;
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.*;
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.data.settings.LiveClientSettings;
import io.github.jwdeveloper.tiktok.utils.ConsoleColors;
import io.github.jwdeveloper.tiktok.websocket.TikTokWebSocketClient;
import io.github.jwdeveloper.tiktok.websocket.TikTokWebSocketOfflineClient;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.logging.Logger;
import java.util.logging.*;
public class TikTokLiveClientBuilder implements LiveClientBuilder {
protected final LiveClientSettings clientSettings;
protected final TikTokLiveEventHandler eventHandler;
protected final Logger logger;
protected final TikTokLiveEventHandler tikTokEventHandler;
protected final List<TikTokEventListener> listeners;
protected Consumer<TikTokMapper> onCustomMappings;
protected Logger logger;
protected GiftsManager giftsManager;
public TikTokLiveClientBuilder(String userName) {
public TikTokLiveClientBuilder(String userName)
{
this.clientSettings = LiveClientSettings.createDefault();
this.clientSettings.setHostName(userName);
this.eventHandler = new TikTokLiveEventHandler();
this.tikTokEventHandler = new TikTokLiveEventHandler();
this.logger = Logger.getLogger(TikTokLive.class.getSimpleName() + " " + userName);
this.listeners = new ArrayList<>();
this.onCustomMappings = (e) -> {
};
@@ -75,36 +91,55 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
return this;
}
public TikTokLiveClientBuilder configure(Consumer<LiveClientSettings> onConfigure) {
onConfigure.accept(clientSettings);
return this;
}
public TikTokLiveClientBuilder addListener(TikTokEventListener listener) {
if (listener != null)
listeners.add(listener);
listeners.add(listener);
return this;
}
protected void validate() {
if (clientSettings.getClientLanguage() == null || clientSettings.getClientLanguage().isEmpty())
if (clientSettings.getClientLanguage() == null || clientSettings.getClientLanguage().isEmpty()) {
clientSettings.setClientLanguage("en");
}
if (clientSettings.getHostName() == null || clientSettings.getHostName().isEmpty())
if (clientSettings.getHostName() == null || clientSettings.getHostName().isEmpty()) {
throw new TikTokLiveException("HostName can not be null");
}
if (clientSettings.getHostName().startsWith("@"))
if (clientSettings.getHostName().startsWith("@")) {
clientSettings.setHostName(clientSettings.getHostName().substring(1));
}
if (clientSettings.getPingInterval() < 250)
throw new TikTokLiveException("Minimum allowed ping interval is 250 millseconds");
var httpSettings = clientSettings.getHttpSettings();
httpSettings.getParams().put("app_language", clientSettings.getClientLanguage());
httpSettings.getParams().put("webcast_language", clientSettings.getClientLanguage());
this.logger = LoggerFactory.create(clientSettings.getHostName(), clientSettings);
this.giftsManager = clientSettings.isFetchGifts() ? TikTokLive.gifts() : new TikTokGiftsManager(List.of());
var handler = new ConsoleHandler();
handler.setFormatter(new Formatter() {
@Override
public String format(LogRecord record) {
var sb = new StringBuilder();
sb.append(ConsoleColors.GREEN).append("[").append(record.getLoggerName()).append("] ");
sb.append(ConsoleColors.GREEN).append("[").append(record.getLevel()).append("]: ");
sb.append(ConsoleColors.WHITE_BRIGHT).append(record.getMessage());
sb.append(ConsoleColors.RESET).append("\n");
return sb.toString();
}
});
logger.setUseParentHandlers(false);
logger.addHandler(handler);
logger.setLevel(clientSettings.getLogLevel());
if (!clientSettings.isPrintToConsole()) {
logger.setLevel(Level.OFF);
}
}
public LiveClient build() {
@@ -113,37 +148,35 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
var tiktokRoomInfo = new TikTokRoomInfo();
tiktokRoomInfo.setHostName(clientSettings.getHostName());
var listenerManager = new TikTokListenersManager(listeners, eventHandler);
var listenerManager = new TikTokListenersManager(listeners, tikTokEventHandler);
var giftManager = new TikTokGiftManager(logger);
var eventsMapper = createMapper(giftManager, tiktokRoomInfo);
var messageHandler = new TikTokLiveMessageHandler(tikTokEventHandler, eventsMapper);
var httpClientFactory = new HttpClientFactory(clientSettings);
var tikTokLiveHttpClient = new TikTokLiveHttpClient(httpClientFactory);
var liveHttpClient = clientSettings.isOffline() ?
new TikTokLiveHttpOfflineClient() :
new TikTokLiveHttpClient(httpClientFactory, clientSettings);
var eventsMapper = createMapper(giftsManager, tiktokRoomInfo);
var messageHandler = new TikTokLiveMessageHandler(eventHandler, eventsMapper);
var webSocketClient = clientSettings.isOffline() ?
new TikTokWebSocketOfflineClient(eventHandler) :
new TikTokWebSocketClient(
clientSettings,
messageHandler,
eventHandler);
return new TikTokLiveClient(
var webSocketClient = new TikTokWebSocketClient(
clientSettings,
messageHandler,
giftsManager,
tiktokRoomInfo,
liveHttpClient,
tikTokEventHandler);
return new TikTokLiveClient(tiktokRoomInfo,
tikTokLiveHttpClient,
webSocketClient,
eventHandler,
giftManager,
tikTokEventHandler,
clientSettings,
listenerManager,
logger);
}
public TikTokLiveMapper createMapper(GiftsManager giftsManager, TikTokRoomInfo roomInfo) {
public TikTokLiveMapper createMapper(GiftManager giftManager, TikTokRoomInfo roomInfo) {
/*
//
*/
var eventMapper = new TikTokGenericEventMapper();
@@ -151,7 +184,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
//ConnectionEvents events
var commonHandler = new TikTokCommonEventHandler();
var giftHandler = new TikTokGiftEventHandler(giftsManager, roomInfo);
var giftHandler = new TikTokGiftEventHandler(giftManager, roomInfo);
var roomInfoHandler = new TikTokRoomInfoEventHandler(roomInfo);
var socialHandler = new TikTokSocialMediaEventHandler(roomInfo);
@@ -207,15 +240,9 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
//LinkMic events
mapper.forMessage(WebcastLinkMicBattle.class, (inputBytes, messageName, mapperHelper) -> {
var message = mapperHelper.bytesToWebcastObject(inputBytes, WebcastLinkMicBattle.class);
return MappingResult.of(message, new TikTokLinkMicBattleEvent(message));
});
mapper.forMessage(WebcastLinkMicArmies.class, (inputBytes, messageName, mapperHelper) -> {
var message = mapperHelper.bytesToWebcastObject(inputBytes, WebcastLinkMicArmies.class);
return MappingResult.of(message, new TikTokLinkMicArmiesEvent(message));
});
// mapper.webcastObjectToConstructor(WebcastLinkMicMethod.class, TikTokLinkMicMethodEvent.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
@@ -248,256 +275,273 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
return build().connectAsync();
}
public TikTokLiveClientBuilder onUnhandledSocial(EventConsumer<TikTokUnhandledSocialEvent> event) {
eventHandler.subscribe(TikTokUnhandledSocialEvent.class, event);
public TikTokLiveClientBuilder onUnhandledSocial(
EventConsumer<TikTokUnhandledSocialEvent> event) {
tikTokEventHandler.subscribe(TikTokUnhandledSocialEvent.class, event);
return this;
}
// @Override
public LiveClientBuilder onChest(EventConsumer<TikTokChestEvent> event) {
eventHandler.subscribe(TikTokChestEvent.class, event);
tikTokEventHandler.subscribe(TikTokChestEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLinkMicFanTicket(EventConsumer<TikTokLinkMicFanTicketEvent> event) {
eventHandler.subscribe(TikTokLinkMicFanTicketEvent.class, event);
public TikTokLiveClientBuilder onLinkMicFanTicket(
EventConsumer<TikTokLinkMicFanTicketEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkMicFanTicketEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onEnvelope(EventConsumer<TikTokEnvelopeEvent> event) {
eventHandler.subscribe(TikTokEnvelopeEvent.class, event);
tikTokEventHandler.subscribe(TikTokEnvelopeEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onShop(EventConsumer<TikTokShopEvent> event) {
eventHandler.subscribe(TikTokShopEvent.class, event);
tikTokEventHandler.subscribe(TikTokShopEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onDetect(EventConsumer<TikTokDetectEvent> event) {
eventHandler.subscribe(TikTokDetectEvent.class, event);
public TikTokLiveClientBuilder onDetect(
EventConsumer<TikTokDetectEvent> event) {
tikTokEventHandler.subscribe(TikTokDetectEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLinkLayer(EventConsumer<TikTokLinkLayerEvent> event) {
eventHandler.subscribe(TikTokLinkLayerEvent.class, event);
public TikTokLiveClientBuilder onLinkLayer(
EventConsumer<TikTokLinkLayerEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkLayerEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onConnected(EventConsumer<TikTokConnectedEvent> event) {
eventHandler.subscribe(TikTokConnectedEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onPreConnection(EventConsumer<TikTokPreConnectionEvent> event) {
eventHandler.subscribe(TikTokPreConnectionEvent.class, event);
tikTokEventHandler.subscribe(TikTokConnectedEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onCaption(EventConsumer<TikTokCaptionEvent> event) {
eventHandler.subscribe(TikTokCaptionEvent.class, event);
tikTokEventHandler.subscribe(TikTokCaptionEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onQuestion(EventConsumer<TikTokQuestionEvent> event) {
eventHandler.subscribe(TikTokQuestionEvent.class, event);
tikTokEventHandler.subscribe(TikTokQuestionEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onRoomPin(EventConsumer<TikTokRoomPinEvent> event) {
eventHandler.subscribe(TikTokRoomPinEvent.class, event);
public TikTokLiveClientBuilder onRoomPin(
EventConsumer<TikTokRoomPinEvent> event) {
tikTokEventHandler.subscribe(TikTokRoomPinEvent.class, event);
return this;
}
@Override
public <E extends TikTokEvent> LiveClientBuilder onEvent(Class<E> eventClass, EventConsumer<E> event) {
eventHandler.subscribe(eventClass, event);
tikTokEventHandler.subscribe(eventClass, event);
return this;
}
@Override
public TikTokLiveClientBuilder onRoomInfo(EventConsumer<TikTokRoomInfoEvent> event) {
eventHandler.subscribe(TikTokRoomInfoEvent.class, event);
public LiveClientBuilder onRoomInfo(EventConsumer<TikTokRoomInfoEvent> event) {
tikTokEventHandler.subscribe(TikTokRoomInfoEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLivePaused(EventConsumer<TikTokLivePausedEvent> event) {
eventHandler.subscribe(TikTokLivePausedEvent.class, event);
tikTokEventHandler.subscribe(TikTokLivePausedEvent.class, event);
return this;
}
@Override
public TikTokLiveClientBuilder onLiveUnpaused(EventConsumer<TikTokLiveUnpausedEvent> event) {
eventHandler.subscribe(TikTokLiveUnpausedEvent.class, event);
public LiveClientBuilder onLiveUnpaused(EventConsumer<TikTokLiveUnpausedEvent> event) {
tikTokEventHandler.subscribe(TikTokLiveUnpausedEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLike(EventConsumer<TikTokLikeEvent> event) {
eventHandler.subscribe(TikTokLikeEvent.class, event);
tikTokEventHandler.subscribe(TikTokLikeEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLink(EventConsumer<TikTokLinkEvent> event) {
eventHandler.subscribe(TikTokLinkEvent.class, event);
tikTokEventHandler.subscribe(TikTokLinkEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onBarrage(EventConsumer<TikTokBarrageEvent> event) {
eventHandler.subscribe(TikTokBarrageEvent.class, event);
public TikTokLiveClientBuilder onBarrage(
EventConsumer<TikTokBarrageEvent> event) {
tikTokEventHandler.subscribe(TikTokBarrageEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onGift(EventConsumer<TikTokGiftEvent> event) {
eventHandler.subscribe(TikTokGiftEvent.class, event);
tikTokEventHandler.subscribe(TikTokGiftEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onGiftCombo(EventConsumer<TikTokGiftComboEvent> event) {
eventHandler.subscribe(TikTokGiftComboEvent.class, event);
tikTokEventHandler.subscribe(TikTokGiftComboEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLinkMicArmies(EventConsumer<TikTokLinkMicArmiesEvent> event) {
eventHandler.subscribe(TikTokLinkMicArmiesEvent.class, event);
public TikTokLiveClientBuilder onLinkMicArmies(
EventConsumer<TikTokLinkMicArmiesEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkMicArmiesEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onEmote(EventConsumer<TikTokEmoteEvent> event) {
eventHandler.subscribe(TikTokEmoteEvent.class, event);
tikTokEventHandler.subscribe(TikTokEmoteEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onUnauthorizedMember(EventConsumer<TikTokUnauthorizedMemberEvent> event) {
eventHandler.subscribe(TikTokUnauthorizedMemberEvent.class, event);
public TikTokLiveClientBuilder onUnauthorizedMember(
EventConsumer<TikTokUnauthorizedMemberEvent> event) {
tikTokEventHandler.subscribe(TikTokUnauthorizedMemberEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onInRoomBanner(EventConsumer<TikTokInRoomBannerEvent> event) {
eventHandler.subscribe(TikTokInRoomBannerEvent.class, event);
public TikTokLiveClientBuilder onInRoomBanner(
EventConsumer<TikTokInRoomBannerEvent> event) {
tikTokEventHandler.subscribe(TikTokInRoomBannerEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLinkMicMethod(EventConsumer<TikTokLinkMicMethodEvent> event) {
eventHandler.subscribe(TikTokLinkMicMethodEvent.class, event);
public TikTokLiveClientBuilder onLinkMicMethod(
EventConsumer<TikTokLinkMicMethodEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkMicMethodEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onSubscribe(EventConsumer<TikTokSubscribeEvent> event) {
eventHandler.subscribe(TikTokSubscribeEvent.class, event);
tikTokEventHandler.subscribe(TikTokSubscribeEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onPoll(EventConsumer<TikTokPollEvent> event) {
eventHandler.subscribe(TikTokPollEvent.class, event);
tikTokEventHandler.subscribe(TikTokPollEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onFollow(EventConsumer<TikTokFollowEvent> event) {
eventHandler.subscribe(TikTokFollowEvent.class, event);
tikTokEventHandler.subscribe(TikTokFollowEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onComment(EventConsumer<TikTokCommentEvent> event) {
eventHandler.subscribe(TikTokCommentEvent.class, event);
tikTokEventHandler.subscribe(TikTokCommentEvent.class, event);
return this;
}
@Override
public LiveClientBuilder onHttpResponse(EventConsumer<TikTokHttpResponseEvent> action) {
eventHandler.subscribe(TikTokHttpResponseEvent.class, action);
tikTokEventHandler.subscribe(TikTokHttpResponseEvent.class, action);
return this;
}
public TikTokLiveClientBuilder onGoalUpdate(EventConsumer<TikTokGoalUpdateEvent> event) {
eventHandler.subscribe(TikTokGoalUpdateEvent.class, event);
tikTokEventHandler.subscribe(TikTokGoalUpdateEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onRankUpdate(EventConsumer<TikTokRankUpdateEvent> event) {
eventHandler.subscribe(TikTokRankUpdateEvent.class, event);
tikTokEventHandler.subscribe(TikTokRankUpdateEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onIMDelete(EventConsumer<TikTokIMDeleteEvent> event) {
eventHandler.subscribe(TikTokIMDeleteEvent.class, event);
tikTokEventHandler.subscribe(TikTokIMDeleteEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLiveEnded(EventConsumer<TikTokLiveEndedEvent> event) {
eventHandler.subscribe(TikTokLiveEndedEvent.class, event);
tikTokEventHandler.subscribe(TikTokLiveEndedEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onError(EventConsumer<TikTokErrorEvent> event) {
eventHandler.subscribe(TikTokErrorEvent.class, event);
tikTokEventHandler.subscribe(TikTokErrorEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onJoin(EventConsumer<TikTokJoinEvent> event) {
eventHandler.subscribe(TikTokJoinEvent.class, event);
tikTokEventHandler.subscribe(TikTokJoinEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onRankText(EventConsumer<TikTokRankTextEvent> event) {
eventHandler.subscribe(TikTokRankTextEvent.class, event);
tikTokEventHandler.subscribe(TikTokRankTextEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onShare(EventConsumer<TikTokShareEvent> event) {
eventHandler.subscribe(TikTokShareEvent.class, event);
tikTokEventHandler.subscribe(TikTokShareEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onUnhandledMember(EventConsumer<TikTokUnhandledMemberEvent> event) {
eventHandler.subscribe(TikTokUnhandledMemberEvent.class, event);
public TikTokLiveClientBuilder onUnhandledMember(
EventConsumer<TikTokUnhandledMemberEvent> event) {
tikTokEventHandler.subscribe(TikTokUnhandledMemberEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onSubNotify(EventConsumer<TikTokSubNotifyEvent> event) {
eventHandler.subscribe(TikTokSubNotifyEvent.class, event);
tikTokEventHandler.subscribe(TikTokSubNotifyEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLinkMicBattle(EventConsumer<TikTokLinkMicBattleEvent> event) {
eventHandler.subscribe(TikTokLinkMicBattleEvent.class, event);
public TikTokLiveClientBuilder onLinkMicBattle(
EventConsumer<TikTokLinkMicBattleEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkMicBattleEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onDisconnected(EventConsumer<TikTokDisconnectedEvent> event) {
eventHandler.subscribe(TikTokDisconnectedEvent.class, event);
public TikTokLiveClientBuilder onDisconnected(
EventConsumer<TikTokDisconnectedEvent> event) {
tikTokEventHandler.subscribe(TikTokDisconnectedEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onUnhandledControl(EventConsumer<TikTokUnhandledControlEvent> event) {
eventHandler.subscribe(TikTokUnhandledControlEvent.class, event);
public TikTokLiveClientBuilder onUnhandledControl(
EventConsumer<TikTokUnhandledControlEvent> event) {
tikTokEventHandler.subscribe(TikTokUnhandledControlEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onEvent(EventConsumer<TikTokEvent> event) {
eventHandler.subscribe(TikTokEvent.class, event);
tikTokEventHandler.subscribe(TikTokEvent.class, event);
return this;
}
@Override
public TikTokLiveClientBuilder onWebsocketResponse(EventConsumer<TikTokWebsocketResponseEvent> event) {
eventHandler.subscribe(TikTokWebsocketResponseEvent.class, event);
tikTokEventHandler.subscribe(TikTokWebsocketResponseEvent.class, event);
return this;
}
@Override
public TikTokLiveClientBuilder onWebsocketMessage(EventConsumer<TikTokWebsocketMessageEvent> event) {
eventHandler.subscribe(TikTokWebsocketMessageEvent.class, event);
tikTokEventHandler.subscribe(TikTokWebsocketMessageEvent.class, event);
return this;
}
@Override
public TikTokLiveClientBuilder onWebsocketUnhandledMessage(EventConsumer<TikTokWebsocketUnhandledMessageEvent> event) {
eventHandler.subscribe(TikTokWebsocketUnhandledMessageEvent.class, event);
tikTokEventHandler.subscribe(TikTokWebsocketUnhandledMessageEvent.class, event);
return this;
}
@Override
public TikTokLiveClientBuilder onReconnecting(EventConsumer<TikTokReconnectingEvent> event) {
eventHandler.subscribe(TikTokReconnectingEvent.class, event);
tikTokEventHandler.subscribe(TikTokReconnectingEvent.class, event);
return this;
}
}

View File

@@ -23,139 +23,125 @@
package io.github.jwdeveloper.tiktok;
import com.google.protobuf.InvalidProtocolBufferException;
import io.github.jwdeveloper.tiktok.common.*;
import io.github.jwdeveloper.tiktok.data.requests.*;
import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
import io.github.jwdeveloper.tiktok.exceptions.*;
import io.github.jwdeveloper.tiktok.http.*;
import io.github.jwdeveloper.tiktok.http.mappers.*;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import io.github.jwdeveloper.tiktok.exceptions.TikTokSignServerException;
import io.github.jwdeveloper.tiktok.http.HttpClientFactory;
import io.github.jwdeveloper.tiktok.http.LiveHttpClient;
import io.github.jwdeveloper.tiktok.http.mappers.GiftsDataMapper;
import io.github.jwdeveloper.tiktok.http.mappers.LiveDataMapper;
import io.github.jwdeveloper.tiktok.http.mappers.LiveUserDataMapper;
import io.github.jwdeveloper.tiktok.http.mappers.SignServerResponseMapper;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import java.net.http.HttpResponse;
import java.util.logging.Logger;
public class TikTokLiveHttpClient implements LiveHttpClient
{
public class TikTokLiveHttpClient implements LiveHttpClient {
/**
* <a href="https://github-wiki-see.page/m/isaackogan/TikTokLive/wiki/All-About-Signatures">Signing API by Isaac Kogan</a>
*/
private static final String TIKTOK_SIGN_API = "https://tiktok.eulerstream.com/webcast/fetch";
* Signing API by Isaac Kogan
* https://github-wiki-see.page/m/isaackogan/TikTokLive/wiki/All-About-Signatures
*/
private static final String TIKTOK_SIGN_API = "https://tiktok.eulerstream.com/webcast/sign_url";
private static final String TIKTOK_URL_WEB = "https://www.tiktok.com/";
private static final String TIKTOK_URL_WEBCAST = "https://webcast.tiktok.com/webcast/";
public static final String TIKTOK_GIFTS_URL = "https://raw.githubusercontent.com/TikTok-LIVE-Private/GiftsGenerator/master/page/public/gifts.json";
public static final int TIKTOK_AGE_RESTRICTED_CODE = 4003110;
private final HttpClientFactory httpFactory;
private final LiveClientSettings clientSettings;
private final LiveUserDataMapper liveUserDataMapper;
private final LiveDataMapper liveDataMapper;
private final SignServerResponseMapper singServerResponseMapper;
private final GiftsDataMapper giftsDataMapper;
private final Logger logger;
public TikTokLiveHttpClient(HttpClientFactory factory, LiveClientSettings settings) {
public TikTokLiveHttpClient(HttpClientFactory factory) {
this.httpFactory = factory;
this.clientSettings = settings;
this.logger = LoggerFactory.create("HttpClient", clientSettings);
liveUserDataMapper = new LiveUserDataMapper();
liveDataMapper = new LiveDataMapper();
singServerResponseMapper = new SignServerResponseMapper();
giftsDataMapper = new GiftsDataMapper();
}
public TikTokLiveHttpClient() {
this(new HttpClientFactory(LiveClientSettings.createDefault()), LiveClientSettings.createDefault());
this(new HttpClientFactory(LiveClientSettings.createDefault()));
}
public GiftsData.Response fetchGiftsData() {
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) {
try {
return getGiftsData();
} catch (TikTokProxyRequestException ignored) {}
}
var url = TIKTOK_URL_WEBCAST + "gift/list/";
var optional = httpFactory.client(url)
.build()
.toJsonResponse();
if (optional.isEmpty()) {
throw new TikTokLiveRequestException("Unable to fetch gifts information's");
}
return getGiftsData();
var json = optional.get();
return giftsDataMapper.map(json);
}
public GiftsData.Response getGiftsData() {
var result = httpFactory.client(TIKTOK_GIFTS_URL)
.build()
.toJsonResponse();
if (result.isFailure())
throw new TikTokLiveRequestException("Unable to fetch gifts information's - "+result);
var json = result.getContent();
return giftsDataMapper.map(json);
@Override
public LiveUserData.Response fetchLiveUserData(String userName) {
return fetchLiveUserData(new LiveUserData.Request(userName));
}
@Override
public LiveUserData.Response fetchLiveUserData(LiveUserData.Request request) {
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) {
try {
return getLiveUserData(request);
} catch (TikTokProxyRequestException ignored) {}
}
var url = TIKTOK_URL_WEB + "api-live/user/room";
var optional = httpFactory.client(url)
.withParam("uniqueId", request.getUserName())
.withParam("sourceType", "54")
.build()
.toJsonResponse();
if (optional.isEmpty()) {
throw new TikTokLiveRequestException("Unable to get information's about user");
}
return getLiveUserData(request);
var json = optional.get();
return liveUserDataMapper.map(json);
}
public LiveUserData.Response getLiveUserData(LiveUserData.Request request) {
var url = TIKTOK_URL_WEB + "api-live/user/room";
var result = httpFactory.client(url)
.withParam("uniqueId", request.getUserName())
.withParam("sourceType", "54")
.build()
.toJsonResponse();
if (result.isFailure())
throw new TikTokLiveRequestException("Unable to get information's about user - "+result);
var json = result.getContent();
return liveUserDataMapper.map(json, logger);
@Override
public LiveData.Response fetchLiveData(String roomId) {
return fetchLiveData(new LiveData.Request(roomId));
}
@Override
public LiveData.Response fetchLiveData(LiveData.Request request) {
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) {
try {
return getLiveData(request);
} catch (TikTokProxyRequestException ignored) {}
}
}
return getLiveData(request);
}
public LiveData.Response getLiveData(LiveData.Request request) {
var url = TIKTOK_URL_WEBCAST + "room/info";
var result = httpFactory.client(url)
.withParam("room_id", request.getRoomId())
.build()
.toJsonResponse();
var optional = httpFactory.client(url)
.withParam("room_id", request.getRoomId())
.build()
.toJsonResponse();
if (result.isFailure())
throw new TikTokLiveRequestException("Unable to get info about live room - "+result);
if (optional.isEmpty()) {
throw new TikTokLiveRequestException("Unable to get info about live room");
}
var json = result.getContent();
var json = optional.get();
return liveDataMapper.map(json);
}
@Override
public LiveConnectionData.Response fetchLiveConnectionData(String roomId) {
return fetchLiveConnectionData(new LiveConnectionData.Request(roomId));
}
@Override
public LiveConnectionData.Response fetchLiveConnectionData(LiveConnectionData.Request request) {
var result = getStartingPayload(request);
HttpResponse<byte[]> credentialsResponse = result.getContent(); // Always guaranteed to have response
var signServerResponse = getSignedUrl(request.getRoomId());
var credentialsResponse = getWebsocketCredentialsResponse(signServerResponse.getSignedUrl());
try {
var resultHeader = ActionResult.of(credentialsResponse.headers().firstValue("x-set-tt-cookie"));
if (resultHeader.isFailure()) {
logger.warning("SignServer Headers: "+request.getRoomId()+" - "+credentialsResponse.headers().map());
throw new TikTokSignServerException("Sign server did not return the x-set-tt-cookie header - "+result);
var optionalHeader = credentialsResponse.headers().firstValue("set-cookie");
if (optionalHeader.isEmpty()) {
throw new TikTokSignServerException("Sign server does not returned set-cookie header");
}
var websocketCookie = resultHeader.getContent();
var websocketCookie = optionalHeader.get();
var webcastResponse = WebcastResponse.parseFrom(credentialsResponse.body());
var webSocketUrl = httpFactory
.client(webcastResponse.getPushServer())
@@ -169,36 +155,43 @@ public class TikTokLiveHttpClient implements LiveHttpClient
return new LiveConnectionData.Response(websocketCookie, webSocketUrl, webcastResponse);
} catch (InvalidProtocolBufferException e) {
throw new TikTokSignServerException("Unable to parse websocket credentials response to WebcastResponse - "+result);
throw new TikTokSignServerException("Unable to parse websocket credentials response to WebcastResponse");
}
}
private ActionResult<HttpResponse<byte[]>> getStartingPayload(LiveConnectionData.Request request) {
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) {
try {
return getByteResponse(request.getRoomId());
} catch (TikTokProxyRequestException | TikTokSignServerException ignored) {}
}
SignServerResponse getSignedUrl(String roomId) {
var urlToSign = httpFactory
.client(TikTokLiveHttpClient.TIKTOK_URL_WEBCAST + "im/fetch")
.withParam("room_id", roomId)
.build()
.toUrl();
var optional = httpFactory
.client(TikTokLiveHttpClient.TIKTOK_SIGN_API)
.withParam("client", "ttlive-java")
.withParam("uuc", "1")
.withParam("url", urlToSign.toString())
.build()
.toJsonResponse();
if (optional.isEmpty()) {
throw new TikTokSignServerException("Unable to sign url: " + urlToSign);
}
return getByteResponse(request.getRoomId());
var json = optional.get();
return singServerResponseMapper.map(json);
}
private ActionResult<HttpResponse<byte[]>> getByteResponse(String room_id) {
HttpClientBuilder builder = httpFactory.client(TIKTOK_SIGN_API)
.withParam("client", "ttlive-java")
.withParam("uuc", "1")
.withParam("room_id", room_id);
if (clientSettings.getApiKey() != null)
builder.withParam("apiKey", clientSettings.getApiKey());
var result = builder.build().toResponse();
if (result.isFailure())
throw new TikTokSignServerException("Unable to get websocket connection credentials - "+result);
return result;
HttpResponse<byte[]> getWebsocketCredentialsResponse(String signedUrl) {
var optionalResponse = httpFactory
.clientEmpty(signedUrl)
.build()
.toResponse(HttpResponse.BodyHandlers.ofByteArray());
if (optionalResponse.isEmpty()) {
throw new TikTokSignServerException("Unable to get websocket connection credentials");
}
return optionalResponse.get();
}
}
}

View File

@@ -1,45 +0,0 @@
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.data.requests.GiftsData;
import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData;
import io.github.jwdeveloper.tiktok.data.requests.LiveData;
import io.github.jwdeveloper.tiktok.data.requests.LiveUserData;
import io.github.jwdeveloper.tiktok.http.LiveHttpClient;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import java.net.URI;
import java.util.List;
public class TikTokLiveHttpOfflineClient implements LiveHttpClient {
@Override
public GiftsData.Response fetchGiftsData() {
return new GiftsData.Response("", List.of());
}
@Override
public LiveUserData.Response fetchLiveUserData(LiveUserData.Request request) {
return new LiveUserData.Response("", LiveUserData.UserStatus.Live, "offline_room_id", 0);
}
@Override
public LiveData.Response fetchLiveData(LiveData.Request request) {
return new LiveData.Response("",
LiveData.LiveStatus.HostOnline,
"offline live",
0,
0,
0,
false,
new User(0L, "offline user", new Picture("")),
LiveData.LiveType.SOLO);
}
@Override
public LiveConnectionData.Response fetchLiveConnectionData(LiveConnectionData.Request request) {
return new LiveConnectionData.Response("",
URI.create("https://example.live"),
WebcastResponse.newBuilder().build());
}
}

View File

@@ -61,6 +61,11 @@ public class TikTokRoomInfo implements LiveRoomInfo {
return connectionState == state;
}
@Override
public User getHostUser() {
return host;
}
public void updateRanking(List<RankingUser> rankingUsers) {
usersRanking.clear();
usersRanking.addAll(rankingUsers);

View File

@@ -1,113 +0,0 @@
package io.github.jwdeveloper.tiktok.common;
import com.google.gson.*;
import io.github.jwdeveloper.tiktok.http.mappers.*;
import lombok.Data;
import lombok.experimental.Accessors;
import java.net.http.*;
import java.util.Optional;
import java.util.function.Function;
@Data
public class ActionResult<T> {
private static final Gson gson = new Gson().newBuilder().disableHtmlEscaping()
.registerTypeHierarchyAdapter(HttpResponse.class, new HttpResponseJsonMapper())
.registerTypeHierarchyAdapter(HttpRequest.class, new HttpRequestJsonMapper())
.setPrettyPrinting().create();
private boolean success = true;
private T content;
private String message;
@Accessors(chain = true, fluent = true)
private ActionResult<?> previous;
protected ActionResult(T object) {
this.content = object;
}
protected ActionResult(T object, boolean success) {
this(object);
this.success = success;
}
protected ActionResult(T object, boolean success, String message) {
this(object, success);
this.message = message;
}
public static <T> ActionResultBuilder<T> of(T content) {
return new ActionResultBuilder<>(content);
}
public static <T> ActionResult<T> of(Optional<T> optional) {
return new ActionResult<>(optional.orElse(null), optional.isPresent());
}
public boolean isFailure() {
return !isSuccess();
}
public boolean hasMessage() {
return message != null;
}
public boolean hasPrevious() {
return previous != null;
}
public boolean hasContent() {
return content != null;
}
public <Output> ActionResult<Output> cast(Output output) {
return new ActionResult<>(output, this.isSuccess(), this.getMessage());
}
public <Output> ActionResult<Output> cast() {
return cast(null);
}
public <U> ActionResult<U> map(Function<? super T, ? extends U> mapper) {
return hasContent() ? cast(mapper.apply(content)) : cast();
}
public static <T> ActionResult<T> success(T payload, String message) {
return new ActionResult<>(payload, true, message);
}
public static <T> ActionResult<T> success(T payload) {
return success(payload, null);
}
public static <T> ActionResult<T> success() {
return success(null);
}
public static <T> ActionResult<T> failure(T target, String message) {
return new ActionResult<>(target, false, message);
}
public static <T> ActionResult<T> failure(String message) {
return failure(null, message);
}
public static <T> ActionResult<T> failure() {
return failure(null);
}
public JsonObject toJson() {
JsonObject map = new JsonObject();
map.addProperty("success", success);
map.add("content", gson.toJsonTree(content));
map.addProperty("message", message);
map.add("previous", hasPrevious() ? previous.toJson() : null);
return map;
}
@Override
public String toString() {
return "ActionResult: "+gson.toJson(toJson());
}
}

View File

@@ -1,32 +0,0 @@
package io.github.jwdeveloper.tiktok.common;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.util.Arrays;
import java.util.stream.Collectors;
public class ActionResultBuilder<T>
{
private final T content;
private String message;
@Setter @Accessors(fluent = true, chain = true)
private ActionResult<?> previous;
public ActionResultBuilder(T content) {
this.content = content;
}
public ActionResultBuilder<T> message(Object... messages) {
this.message = Arrays.stream(messages).map(Object::toString).collect(Collectors.joining(" "));
return this;
}
public ActionResult<T> success() {
return ActionResult.success(content, message).previous(previous);
}
public ActionResult<T> failure() {
return ActionResult.success(content, message).previous(previous);
}
}

View File

@@ -1,34 +0,0 @@
package io.github.jwdeveloper.tiktok.common;
import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
import io.github.jwdeveloper.tiktok.utils.ConsoleColors;
import java.util.logging.*;
public class LoggerFactory
{
public static Logger create(String name, LiveClientSettings settings) {
Logger logger = Logger.getLogger(name);
if (logger.getHandlers().length == 0) {
var handler = new ConsoleHandler();
handler.setFormatter(new Formatter()
{
@Override
public String format(LogRecord record) {
var sb = new StringBuilder();
sb.append(ConsoleColors.GREEN).append("[").append(record.getLoggerName()).append("] ");
sb.append(ConsoleColors.GREEN).append("[").append(record.getLevel()).append("]: ");
sb.append(ConsoleColors.WHITE_BRIGHT).append(record.getMessage());
sb.append(ConsoleColors.RESET).append("\n");
return sb.toString();
}
});
logger.setUseParentHandlers(false);
logger.addHandler(handler);
logger.setLevel(settings.getLogLevel());
if (!settings.isPrintToConsole())
logger.setLevel(Level.OFF);
}
return logger;
}
}

View File

@@ -0,0 +1,103 @@
/*
* 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.gifts;
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.util.*;
import java.util.logging.Logger;
public class TikTokGiftManager implements GiftManager {
private final Map<Integer, Gift> indexById;
private final Map<String, Gift> indexByName;
private final Logger logger;
public TikTokGiftManager(Logger logger)
{
indexById = new HashMap<>();
indexByName = new HashMap<>();
this.logger = logger;
init();
}
protected void init() {
for (var gift : Gift.values()) {
indexById.put(gift.getId(), gift);
indexByName.put(gift.getName(), gift);
}
}
public Gift registerGift(int id, String name, int diamondCost, Picture picture) {
try {
var constructor = Unsafe.class.getDeclaredConstructors()[0];
constructor.setAccessible(true);
var unsafe = (Unsafe) constructor.newInstance();
Gift enumInstance = (Gift) unsafe.allocateInstance(Gift.class);
var field = Gift.class.getDeclaredField("id");
field.setAccessible(true);
field.set(enumInstance, id);
field = Gift.class.getDeclaredField("name");
field.setAccessible(true);
field.set(enumInstance, name);
// EnumSet
field = Gift.class.getDeclaredField("diamondCost");
field.setAccessible(true);
field.set(enumInstance, diamondCost);
field = Gift.class.getDeclaredField("picture");
field.setAccessible(true);
field.set(enumInstance, picture);
indexById.put(enumInstance.getId(), enumInstance);
indexByName.put(enumInstance.getName(), enumInstance);
return enumInstance;
} catch (Exception e) {
throw new TikTokLiveException("Unable to register gift: " + name + ": " + id);
}
}
public Gift findById(int giftId) {
Gift gift = indexById.get(giftId);
return gift == null ? Gift.UNDEFINED : gift;
}
public Gift findByName(String giftName) {
Gift gift = indexByName.get(giftName);
return gift == null ? Gift.UNDEFINED : gift;
}
@Override
public List<Gift> getGifts() {
return indexById.values().stream().toList();
}
}

View File

@@ -1,57 +0,0 @@
package io.github.jwdeveloper.tiktok.gifts;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.live.GiftsManager;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class TikTokGiftsManager implements GiftsManager {
private final Map<Integer, Gift> giftsByIdIndex;
public TikTokGiftsManager(List<Gift> giftList)
{
giftsByIdIndex = giftList.stream().collect(Collectors.toConcurrentMap(Gift::getId, e -> e));
}
public void attachGift(Gift gift) {
giftsByIdIndex.put(gift.getId(), gift);
}
public void attachGiftsList(List<Gift> gifts) {
gifts.forEach(this::attachGift);
}
public Gift getByName(String name) {
return getByFilter(e -> e.getName().equalsIgnoreCase(name));
}
public Gift getById(int giftId) {
return giftsByIdIndex.getOrDefault(giftId, Gift.UNDEFINED);
}
public Gift getByFilter(Predicate<Gift> filter) {
return giftsByIdIndex.values()
.stream()
.filter(filter)
.findFirst()
.orElse(Gift.UNDEFINED);
}
@Override
public List<Gift> getManyByFilter(Predicate<Gift> filter) {
return giftsByIdIndex.values()
.stream()
.filter(filter)
.toList();
}
public List<Gift> toList() {
return giftsByIdIndex.values().stream().toList();
}
public Map<Integer, Gift> toMap() {
return Collections.unmodifiableMap(giftsByIdIndex);
}
}

View File

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

View File

@@ -77,10 +77,11 @@ public class HttpClientBuilder {
return this;
}
public HttpClient build() {
var proxyClientSettings = httpClientSettings.getProxyClientSettings();
if (proxyClientSettings.isEnabled() && proxyClientSettings.hasNext())
return new HttpProxyClient(httpClientSettings, url);
return new HttpClient(httpClientSettings, url);
}
}
}

View File

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

View File

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

View File

@@ -21,35 +21,51 @@
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.http.mappers;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.data.requests.GiftsData;
import java.util.ArrayList;
public class GiftsDataMapper {
public GiftsData.Response map(String json) {
var parsedJson = JsonParser.parseString(json);
var jsonObject = parsedJson.getAsJsonObject();
var gifts = jsonObject.entrySet()
.parallelStream()
.map(e -> mapSingleGift(e.getValue()))
if (!jsonObject.has("data")) {
return new GiftsData.Response(json, new ArrayList<>());
}
var dataElement = jsonObject.getAsJsonObject("data");
if (!dataElement.has("gifts")) {
return new GiftsData.Response(json, new ArrayList<>());
}
var gifts = dataElement.get("gifts").getAsJsonArray()
.asList()
.stream()
.map(this::mapSingleGift)
.toList();
return new GiftsData.Response(json, gifts);
}
private Gift mapSingleGift(JsonElement jsonElement) {
var jsonObject = jsonElement.getAsJsonObject();
private GiftsData.GiftModel mapSingleGift(JsonElement jsonElement) {
var id = jsonElement.getAsJsonObject().get("id").getAsInt();
var name = jsonElement.getAsJsonObject().get("name").getAsString();
var diamondCost = jsonElement.getAsJsonObject().get("diamond_count").getAsInt();
var image = jsonElement.getAsJsonObject()
.get("image").getAsJsonObject()
.get("url_list").getAsJsonArray().get(0).getAsString();
var id = jsonObject.get("id").getAsInt();
var name = jsonObject.get("name").getAsString();
var diamondCost = jsonObject.get("diamondCost").getAsInt();
var image = jsonObject.get("image").getAsString();
return new Gift(id, name, diamondCost, new Picture(image), jsonObject);
if (image.endsWith(".webp")) {
image = image.replace(".webp", ".jpg");
}
var gift = new GiftsData.GiftModel();
gift.setId(id);
gift.setName(name);
gift.setDiamondCost(diamondCost);
gift.setImage(image);
return gift;
}
}

View File

@@ -1,21 +0,0 @@
package io.github.jwdeveloper.tiktok.http.mappers;
import com.google.gson.*;
import java.lang.reflect.Type;
import java.net.http.HttpRequest;
public class HttpRequestJsonMapper implements JsonSerializer<HttpRequest>
{
@Override
public JsonElement serialize(HttpRequest src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject object = new JsonObject();
object.addProperty("method", src.method());
object.add("timeout", context.serialize(src.timeout().toString()));
object.addProperty("expectContinue", src.expectContinue());
object.add("uri", context.serialize(src.uri()));
object.add("version", context.serialize(src.version().toString()));
object.add("headers", context.serialize(src.headers().map()));
return object;
}
}

View File

@@ -1,21 +0,0 @@
package io.github.jwdeveloper.tiktok.http.mappers;
import com.google.gson.*;
import java.lang.reflect.Type;
import java.net.http.HttpResponse;
public class HttpResponseJsonMapper implements JsonSerializer<HttpResponse>
{
@Override
public JsonElement serialize(HttpResponse src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject object = new JsonObject();
object.addProperty("statusCode", src.statusCode());
object.add("request", context.serialize(src.request()));
object.add("headers", context.serialize(src.headers().map()));
object.add("body", context.serialize(src.body()));
object.add("uri", context.serialize(src.uri().toString()));
object.add("version", context.serialize(src.version().toString()));
return object;
}
}

View File

@@ -24,7 +24,6 @@ package io.github.jwdeveloper.tiktok.http.mappers;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.github.jwdeveloper.tiktok.TikTokLiveHttpClient;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.data.models.users.UserAttribute;
@@ -44,7 +43,6 @@ public class LiveDataMapper {
public LiveData.Response map(String json) {
var response = new LiveData.Response();
response.setJson(json);
var parsedJson = JsonParser.parseString(json);
var jsonObject = parsedJson.getAsJsonObject();
@@ -65,8 +63,6 @@ public class LiveDataMapper {
default -> LiveData.LiveStatus.HostNotFound;
};
response.setLiveStatus(statusValue);
} else if (data.has("prompts") && data.get("prompts").getAsString().isEmpty() && jsonObject.has("status_code")) {
response.setAgeRestricted(jsonObject.get("status_code").getAsInt() == TikTokLiveHttpClient.TIKTOK_AGE_RESTRICTED_CODE);
} else {
response.setLiveStatus(LiveData.LiveStatus.HostNotFound);
}
@@ -108,22 +104,6 @@ public class LiveDataMapper {
response.setHost(user);
}
if (data.has("link_mic")) {
var element = data.getAsJsonObject("link_mic");
var multi_live = element.get("multi_live_enum").getAsInt();
var rival_id = element.get("rival_anchor_id").getAsInt();
var battle_scores = element.get("battle_scores").getAsJsonArray();
if (multi_live == 1) {
if (!battle_scores.isEmpty())
response.setLiveType(LiveData.LiveType.BATTLE);
else if (rival_id != 0)
response.setLiveType(LiveData.LiveType.CO_HOST);
else
response.setLiveType(LiveData.LiveType.BOX);
} else
response.setLiveType(LiveData.LiveType.SOLO);
}
return response;
}
@@ -146,4 +126,4 @@ public class LiveDataMapper {
user.addAttribute(UserAttribute.LiveHost);
return user;
}
}
}

View File

@@ -22,52 +22,48 @@
*/
package io.github.jwdeveloper.tiktok.http.mappers;
import com.google.gson.*;
import com.google.gson.JsonParser;
import io.github.jwdeveloper.tiktok.data.requests.LiveUserData;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import java.util.logging.Logger;
public class LiveUserDataMapper {
public class LiveUserDataMapper
{
public LiveUserData.Response map(String json, Logger logger) {
try {
var jsonObject = JsonParser.parseString(json).getAsJsonObject();
var message = jsonObject.get("message").getAsString();
public LiveUserData.Response map(String json) {
var parsedJson = JsonParser.parseString(json);
var jsonObject = parsedJson.getAsJsonObject();
if (message.equals("params_error")) {
throw new TikTokLiveRequestException("fetchRoomIdFromTiktokApi -> Unable to fetch roomID, contact the developer");
}
if (message.equals("user_not_found")) {
return new LiveUserData.Response(json, LiveUserData.UserStatus.NotFound, "", -1);
}
//live -> status 2
//live paused -> 3
//not live -> status 4
var element = jsonObject.get("data");
if (element.isJsonNull()) {
return new LiveUserData.Response(json, LiveUserData.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 message = jsonObject.get("message").getAsString();
var liveRoom = data.getAsJsonObject("liveRoom");
long startTime = liveRoom.get("startTime").getAsLong();
var statusEnum = switch (status) {
case 2 -> LiveUserData.UserStatus.Live;
case 3 -> LiveUserData.UserStatus.LivePaused;
case 4 -> LiveUserData.UserStatus.Offline;
default -> LiveUserData.UserStatus.NotFound;
};
return new LiveUserData.Response(json, statusEnum, roomId, startTime);
} catch (JsonSyntaxException | IllegalStateException e) {
logger.warning("Malformed Json: '"+json+"' - Error Message: "+e.getMessage());
if (message.equals("params_error")) {
throw new TikTokLiveRequestException("fetchRoomIdFromTiktokApi -> Unable to fetch roomID, contact the developer");
}
if (message.equals("user_not_found")) {
return new LiveUserData.Response(json, LiveUserData.UserStatus.NotFound, "", -1);
}
//live -> status 2
//live paused -> 3
//not live -> status 4
var element = jsonObject.get("data");
if (element.isJsonNull()) {
return new LiveUserData.Response(json, LiveUserData.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 -> LiveUserData.UserStatus.Live;
case 3 -> LiveUserData.UserStatus.LivePaused;
case 4 -> LiveUserData.UserStatus.Offline;
default -> LiveUserData.UserStatus.NotFound;
};
return new LiveUserData.Response(json, statusEnum, roomId, startTime);
}
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.http.mappers;
import com.google.gson.JsonParser;
import io.github.jwdeveloper.tiktok.data.requests.SignServerResponse;
public class SignServerResponseMapper {
public SignServerResponse map(String json) {
var parsedJson = JsonParser.parseString(json);
var jsonObject = parsedJson.getAsJsonObject();
var signUrl = jsonObject.get("signedUrl").getAsString();
var userAgent = jsonObject.get("User-Agent").getAsString();
return new SignServerResponse(signUrl, userAgent);
}
}

View File

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

View File

@@ -24,27 +24,32 @@ 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.*;
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.*;
import io.github.jwdeveloper.tiktok.live.GiftsManager;
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;
import java.util.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class TikTokGiftEventHandler {
private final GiftManager giftManager;
private final Map<Long, WebcastGiftMessage> giftsMessages;
private final TikTokRoomInfo tikTokRoomInfo;
private final GiftsManager giftsManager;
public TikTokGiftEventHandler(GiftsManager giftsManager, TikTokRoomInfo tikTokRoomInfo) {
public TikTokGiftEventHandler(GiftManager giftManager, TikTokRoomInfo tikTokRoomInfo) {
this.giftManager = giftManager;
giftsMessages = new HashMap<>();
this.tikTokRoomInfo = tikTokRoomInfo;
this.giftsManager = giftsManager;
}
@SneakyThrows
@@ -56,40 +61,40 @@ public class TikTokGiftEventHandler {
public List<TikTokEvent> handleGift(WebcastGiftMessage currentMessage) {
var userId = currentMessage.getUser().getId();
var currentType = GiftComboStateType.fromNumber(currentMessage.getSendType());
var currentType = GiftSendType.fromNumber(currentMessage.getSendType());
var containsPreviousMessage = giftsMessages.containsKey(userId);
//If gift is not streakable just return onGift event
if (currentMessage.getGift().getType() != 1) {
var comboEvent = getGiftComboEvent(currentMessage, GiftComboStateType.Finished);
var comboEvent = getGiftComboEvent(currentMessage, GiftSendType.Finished);
var giftEvent = getGiftEvent(currentMessage);
return List.of(comboEvent, giftEvent);
}
if (!containsPreviousMessage) {
if (currentType == GiftComboStateType.Finished) {
if (currentType == GiftSendType.Finished) {
return List.of(getGiftEvent(currentMessage));
} else {
giftsMessages.put(userId, currentMessage);
return List.of(getGiftComboEvent(currentMessage, GiftComboStateType.Begin));
return List.of(getGiftComboEvent(currentMessage, GiftSendType.Begin));
}
}
var previousMessage = giftsMessages.get(userId);
var previousType = GiftComboStateType.fromNumber(previousMessage.getSendType());
if (currentType == GiftComboStateType.Active &&
previousType == GiftComboStateType.Active) {
var previousType = GiftSendType.fromNumber(previousMessage.getSendType());
if (currentType == GiftSendType.Active &&
previousType == GiftSendType.Active) {
giftsMessages.put(userId, currentMessage);
return List.of(getGiftComboEvent(currentMessage, GiftComboStateType.Active));
return List.of(getGiftComboEvent(currentMessage, GiftSendType.Active));
}
if (currentType == GiftComboStateType.Finished &&
previousType == GiftComboStateType.Active) {
if (currentType == GiftSendType.Finished &&
previousType == GiftSendType.Active) {
giftsMessages.clear();
return List.of(
getGiftComboEvent(currentMessage, GiftComboStateType.Finished),
getGiftComboEvent(currentMessage, GiftSendType.Finished),
getGiftEvent(currentMessage));
}
@@ -102,28 +107,42 @@ public class TikTokGiftEventHandler {
return new TikTokGiftEvent(gift, tikTokRoomInfo.getHost(), message);
}
private TikTokGiftEvent getGiftComboEvent(WebcastGiftMessage message, GiftComboStateType state) {
private TikTokGiftEvent getGiftComboEvent(WebcastGiftMessage message, GiftSendType state) {
var gift = getGiftObject(message);
return new TikTokGiftComboEvent(gift, tikTokRoomInfo.getHost(), message, state);
}
private Gift getGiftObject(WebcastGiftMessage giftMessage) {
var giftId = (int) giftMessage.getGiftId();
var gift = giftsManager.getById(giftId);
if (gift == Gift.UNDEFINED)
gift = giftsManager.getByName(giftMessage.getGift().getName());
var gift = giftManager.findById(giftId);
if (gift == Gift.UNDEFINED) {
gift = new Gift(giftId,
gift = giftManager.findByName(giftMessage.getGift().getName());
}
if (gift == Gift.UNDEFINED) {
gift = giftManager.registerGift(
giftId,
giftMessage.getGift().getName(),
giftMessage.getGift().getDiamondCount(),
Picture.map(giftMessage.getGift().getImage()));
giftsManager.attachGift(gift);
}
if (gift.getPicture().getLink().endsWith(".webp"))
gift.setPicture(Picture.map(giftMessage.getGift().getImage()));
if (gift.getPicture().getLink().endsWith(".webp")) {
updatePicture(gift, giftMessage);
}
return gift;
}
}
private void updatePicture(Gift gift, WebcastGiftMessage webcastGiftMessage) {
try {
var picture = Picture.map(webcastGiftMessage.getGift().getImage());
var constructor = Unsafe.class.getDeclaredConstructors()[0];
constructor.setAccessible(true);
var field = Gift.class.getDeclaredField("picture");
field.setAccessible(true);
field.set(gift, picture);
} catch (Exception e) {
throw new TikTokLiveException("Unable to update picture in gift: " + gift.toString());
}
}
}

View File

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

View File

@@ -23,18 +23,23 @@
package io.github.jwdeveloper.tiktok.websocket;
import com.google.protobuf.ByteString;
import io.github.jwdeveloper.tiktok.*;
import io.github.jwdeveloper.tiktok.data.events.*;
import io.github.jwdeveloper.tiktok.data.events.TikTokConnectedEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokDisconnectedEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokProtocolBufferException;
import io.github.jwdeveloper.tiktok.TikTokLiveEventHandler;
import io.github.jwdeveloper.tiktok.TikTokLiveMessageHandler;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.messages.webcast.*;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastPushFrame;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft_6455;
import org.java_websocket.handshake.ServerHandshake;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.Map;
import java.util.Optional;
public class TikTokWebSocketListener extends WebSocketClient {
@@ -61,7 +66,7 @@ public class TikTokWebSocketListener extends WebSocketClient {
} catch (Exception e) {
tikTokEventHandler.publish(tikTokLiveClient, new TikTokErrorEvent(e));
}
if (isOpen()) {
if (isNotClosing()) {
sendPing();
}
}
@@ -79,35 +84,39 @@ public class TikTokWebSocketListener extends WebSocketClient {
pushFrameBuilder.setPayloadType("ack");
pushFrameBuilder.setLogId(websocketPushFrame.getLogId());
pushFrameBuilder.setPayload(webcastResponse.getInternalExtBytes());
if (isOpen()) {
this.send(pushFrameBuilder.build().toByteArray());
if (isNotClosing())
{
this.send(pushFrameBuilder.build().toByteArray());
}
}
messageHandler.handle(tikTokLiveClient, webcastResponse);
}
@Override
public void onOpen(ServerHandshake serverHandshake) {
tikTokEventHandler.publish(tikTokLiveClient, new TikTokConnectedEvent());
if (isOpen()) {
if (isNotClosing()) {
sendPing();
}
}
@Override
public void onClose(int code, String reason, boolean remote) {
tikTokEventHandler.publish(tikTokLiveClient, new TikTokDisconnectedEvent(reason));
tikTokLiveClient.disconnect();
public void onClose(int i, String s, boolean b) {
tikTokEventHandler.publish(tikTokLiveClient, new TikTokDisconnectedEvent());
}
@Override
public void onError(Exception error) {
tikTokEventHandler.publish(tikTokLiveClient, new TikTokErrorEvent(error));
if (isOpen()) {
if (isNotClosing()) {
sendPing();
}
}
private Optional<WebcastPushFrame> getWebcastPushFrame(byte[] buffer) {
try {
var websocketMessage = WebcastPushFrame.parseFrom(buffer);
@@ -128,8 +137,13 @@ public class TikTokWebSocketListener extends WebSocketClient {
}
}
private boolean isNotClosing() {
return !isClosed() && !isClosing();
}
@Override
public void onMessage(String s) {
// System.err.println(s);
}
}
}

View File

@@ -1,31 +0,0 @@
package io.github.jwdeveloper.tiktok.websocket;
import io.github.jwdeveloper.tiktok.TikTokLiveEventHandler;
import io.github.jwdeveloper.tiktok.data.events.TikTokConnectedEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokDisconnectedEvent;
import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData;
import io.github.jwdeveloper.tiktok.live.LiveClient;
public class TikTokWebSocketOfflineClient implements SocketClient {
private final TikTokLiveEventHandler handler;
private LiveClient liveClient;
public TikTokWebSocketOfflineClient(TikTokLiveEventHandler handler) {
this.handler = handler;
}
@Override
public void start(LiveConnectionData.Response webcastResponse, LiveClient tikTokLiveClient) {
liveClient = tikTokLiveClient;
handler.publish(liveClient, new TikTokConnectedEvent());
}
@Override
public void stop() {
if (liveClient == null) {
return;
}
handler.publish(liveClient, new TikTokDisconnectedEvent());
}
}

View File

@@ -1,48 +0,0 @@
package io.github.jwdeveloper.tiktok.websocket;
import org.java_websocket.WebSocket;
import java.util.Random;
public class TikTokWebSocketPingingTask
{
private Thread thread;
private boolean isRunning = false;
private final int MAX_TIMEOUT = 250;
private final int SLEEP_TIME = 500;
public void run(WebSocket webSocket, long pingTaskTime)
{
stop();
thread = new Thread(() -> pingTask(webSocket, pingTaskTime));
isRunning = true;
thread.start();
}
public void stop()
{
if (thread != null)
thread.interrupt();
isRunning = false;
}
private void pingTask(WebSocket webSocket, long pingTaskTime)
{
var random = new Random();
while (isRunning) {
try {
if (!webSocket.isOpen()) {
Thread.sleep(SLEEP_TIME);
continue;
}
webSocket.sendPing();
Thread.sleep(pingTaskTime+random.nextInt(MAX_TIMEOUT));
}
catch (Exception e) {
isRunning = false;
}
}
}
}

View File

@@ -22,7 +22,7 @@
*/
package io.github.jwdeveloper.tiktok.gifts;
import io.github.jwdeveloper.tiktok.data.models.gifts.GiftOld;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -35,6 +35,38 @@ import org.mockito.junit.jupiter.MockitoExtension;
public class TikTokGiftManagerTest {
@InjectMocks
TikTokGiftManager giftManager;
private static final Picture rosePicture = new Picture("https://p19-webcast.tiktokcdn.com/img/maliva/webcast-va/eba3a9bb85c33e017f3648eaf88d7189~tplv-obj.png");
@Test
void registerGift() {
var fakeGift = giftManager.registerGift(123, "Fake gift", 123123, rosePicture);
var gifts = giftManager.getGifts();
var optional = gifts.stream().filter(r -> r == fakeGift).findFirst();
Assertions.assertTrue(optional.isPresent());
// Assertions.assertNotNull(optional.get().name());
}
@Test
void findById() {
var target = giftManager.registerGift(123, "FAKE", 123123, rosePicture);
var result = giftManager.findById(target.getId());
Assertions.assertEquals(target, result);
}
@Test
void findByName() {
var target = giftManager.registerGift(123, "FAKE", 123123, rosePicture);
var result = giftManager.findByName(target.getName());
Assertions.assertEquals(target, result);
}
@Test
void getGifts() {
Assertions.assertEquals(Gift.values().length, giftManager.getGifts().size());
}

View File

@@ -26,9 +26,8 @@ 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.Gift;
import io.github.jwdeveloper.tiktok.data.models.gifts.GiftComboStateType;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftsManager;
import io.github.jwdeveloper.tiktok.data.models.gifts.GiftSendType;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager;
import io.github.jwdeveloper.tiktok.mappers.handlers.TikTokGiftEventHandler;
import io.github.jwdeveloper.tiktok.messages.data.GiftStruct;
import io.github.jwdeveloper.tiktok.messages.data.Image;
@@ -39,7 +38,7 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import java.util.List;
import java.util.logging.Logger;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@@ -47,12 +46,13 @@ class TikTokGiftEventHandlerTest {
public static TikTokGiftEventHandler handler;
@BeforeAll
public void before() {
var manager = new TikTokGiftsManager(List.of());
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.attachGift(new Gift(123, "example", 123, "image.webp"));
manager.registerGift(123, "example", 123, new Picture("image.webp"));
handler = new TikTokGiftEventHandler(manager, info);
}
@@ -98,9 +98,9 @@ class TikTokGiftEventHandlerTest {
Assertions.assertEquals(2, result3.size());
var event3 = (TikTokGiftComboEvent) result3.get(0);
Assertions.assertEquals(GiftComboStateType.Begin, event1.getComboState());
Assertions.assertEquals(GiftComboStateType.Active, event2.getComboState());
Assertions.assertEquals(GiftComboStateType.Finished, event3.getComboState());
Assertions.assertEquals(GiftSendType.Begin, event1.getComboState());
Assertions.assertEquals(GiftSendType.Active, event2.getComboState());
Assertions.assertEquals(GiftSendType.Finished, event3.getComboState());
}
@@ -117,7 +117,7 @@ class TikTokGiftEventHandlerTest {
giftBuilder.setId(giftId);
giftBuilder.setName(giftName);
giftBuilder.setImage(Image.newBuilder().addUrl(giftImage).build());
giftBuilder.setImage(Image.newBuilder().addUrlList(giftImage).build());
giftBuilder.setType(streakable ? 1 : 0);
userBuilder.setId(userId);
@@ -129,4 +129,4 @@ class TikTokGiftEventHandlerTest {
}
}
}

View File

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

View File

@@ -22,34 +22,34 @@
*/
package io.github.jwdeveloper.tiktok;
import java.time.Duration;
import io.github.jwdeveloper.tiktok.extension.recorder.TikTokLiveRecorder;
import io.github.jwdeveloper.tiktok.extension.recorder.impl.event.TikTokLiveRecorderStartedEvent;
public class RecorderExample {
public class ChatMessageExample {
public static void main(String[] args) {
TikTokLive.newClient("bangbetmenygy")
.configure(liveClientSettings ->
{
liveClientSettings.setPrintToConsole(true);
})
.onError((liveClient, event) ->
{
event.getException().printStackTrace();
})
.addListener(TikTokLiveRecorder.use(recorderSettings ->
{
recorderSettings.setFfmpegPath("C:\\Users\\ja\\IdeaProjects\\TikTokLiveJava\\extension-recorder\\libs\\ffmpeg.exe");
recorderSettings.setOutputPath("C:\\Users\\ja\\IdeaProjects\\TikTokLiveJava\\extension-recorder\\out");
recorderSettings.setOutputFileName("test.flv");
}))
.onEvent(TikTokLiveRecorderStartedEvent.class, (liveClient, event) ->
{
System.out.println(event.getDownloadData().getFullUrl());
})
.buildAndConnect();
var roomData = TikTokLive.requests()
.fetchLiveData("X");
var gifts = TikTokLive.requests().fetchGiftsData();
var user = TikTokLive.requests()
.fetchLiveUserData("mark");
TikTokLive.newClient(SimpleExample.TIKTOK_HOSTNAME)
.configure(clientSettings ->
{
clientSettings.setPrintToConsole(true);
clientSettings.getHttpSettings().setTimeout(Duration.ofSeconds(21));
})
.onComment((liveClient, event) ->
{
System.out.println("Chat message: " + event.getUser().getName() + " " + event.getText());
})
.onWebsocketUnhandledMessage((liveClient, event) ->
{
liveClient.getLogger().info(event.getMessage().getMethod());
}).buildAndConnect();
}
}

View File

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

View File

@@ -23,7 +23,10 @@
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.models.gifts.*;
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 {
@@ -37,13 +40,13 @@ public class CustomEventExample {
Gift gift;
}
public static void main(String[] args)
{
TikTokLive.newClient(ConnectionExample.TIKTOK_HOSTNAME)
public static void main(String[] args) {
TikTokLive.newClient(SimpleExample.TIKTOK_HOSTNAME)
.configure(clientSettings ->
{
clientSettings.setPrintToConsole(true);
})
.onGift((liveClient, event) ->
{
if (event.getGift().getDiamondCost() > 100)
@@ -61,4 +64,4 @@ public class CustomEventExample {
})
.buildAndConnect();
}
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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.models.Picture;
import io.github.jwdeveloper.tiktok.live.GiftManager;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage;
public class CustomGiftExample {
/**
* If you can't find your wanted Gift inside Gift enum register it manually
*/
public static void main(String[] args) {
LiveClient client = TikTokLive.newClient(SimpleExample.TIKTOK_HOSTNAME)
.onConnected((liveClient, event) ->
{
liveClient.disconnect();
})
.onWebsocketResponse((liveClient, event) ->
{
var packets =event.getResponse().getMessagesList();
for(var packet : packets)
{
var name = packet.getMethod();
var data = packet.getPayload();
if(name.equals("WebcastGiftMessage"))
{
// var message = WebcastGiftMessage.parseFrom(data);
}
}
})
.onGift((liveClient, event) ->
{
liveClient.getLogger().info(event.getGift().getName());
}).build();
GiftManager giftManager = client.getGiftManager();
//If you can't find your wanted Gift inside Gift enum register it manually
giftManager.registerGift(123, "my custom gift", 69, new Picture("https://as2.ftcdn.net/v2/jpg/03/03/62/45/1000_F_303624505_u0bFT1Rnoj8CMUSs8wMCwoKlnWlh5Jiq.jpg"));
//You can also override existing gift, for example Rose has Id 5655
//We can make our custom gift appear in the event instead of rose
giftManager.registerGift(5655, "custom-rose", 999, new Picture("https://as2.ftcdn.net/v2/jpg/03/03/62/45/1000_F_303624505_u0bFT1Rnoj8CMUSs8wMCwoKlnWlh5Jiq.jpg"));
client.connect();
}
}

View File

@@ -1,74 +0,0 @@
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.data.events.TikTokCommentEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokSubscribeEvent;
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.social.TikTokFollowEvent;
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.gifts.GiftComboStateType;
import io.github.jwdeveloper.tiktok.live.LiveClient;
public class Events_And_Gifts_Testing_Example
{
public static void main(String[] args) {
LiveClient client = TikTokLive.newClient(ConnectionExample.TIKTOK_HOSTNAME)
.configure(liveClientSettings ->
{
liveClientSettings.setOffline(true);
liveClientSettings.setPrintToConsole(true);
})
.onConnected((liveClient, event) ->
{
liveClient.getLogger().info("Connected");
})
.onDisconnected((liveClient, event) ->
{
liveClient.getLogger().info("Disconnected");
})
.onGiftCombo((liveClient, event) ->
{
liveClient.getLogger().info("New fake combo Gift: " + event.getGift());
})
.onGift((liveClient, event) ->
{
liveClient.getLogger().info("New fake Gift: " + event.getGift());
})
.onLike((liveClient, event) ->
{
liveClient.getLogger().info("New fake Like event: " + event.getLikes());
})
.build();
var gifts = TikTokLive.gifts();
var roseGift = gifts.getByName("Rose");
var fakeGift = TikTokGiftEvent.of(roseGift);
var fakeComboGift = TikTokGiftComboEvent.of(roseGift, 12, GiftComboStateType.Begin);
var fakeMessage = TikTokCommentEvent.of("Mark", "Hello world");
var fakeSubscriber = TikTokSubscribeEvent.of("Mark");
var fakeFollow = TikTokFollowEvent.of("Mark");
var fakeLike = TikTokLikeEvent.of("Mark", 12);
var fakeJoin = TikTokJoinEvent.of("Mark");
client.connect();
client.publishEvent(fakeGift);
client.publishEvent(fakeComboGift);
client.publishEvent(fakeMessage);
client.publishEvent(fakeSubscriber);
client.publishEvent(fakeFollow);
client.publishEvent(fakeJoin);
client.publishEvent(fakeLike);
client.publishMessage("WebcastLikeMessage", webcastLikeMessageBase64);
client.disconnect();
}
private static final String webcastLikeMessageBase64 = "SAFSBRABGKwCUgcIAhABGKwCCv8BUAFYAbABA7gBARCflqWWo8Ha72UgzoPZhd8xQrwBGg4gkAMKCSNmZmZmZmZmZiJ/qgF6CngIhYjjgPWJv7RgGhDwnZKm8J2TjvCdk47wk4WTsgIKa3lsbGVlaGFsbPICTE1TNHdMakFCQUFBQXUyX21LNEw4WGJYa3lNaUFvZzJUTnNmVjk5N09WM2tpQ3NCTkNjYWkwcWxIcUt0Q3B0UGU1N2RLYVhxb0xWSXoICwoQcG1fbXRfbXNnX3ZpZXdlchIXezA6dXNlcn0gbGlrZWQgdGhlIExJVkVIAQoSV2ViY2FzdExpa2VNZXNzYWdlGIaWvY+RhdjvZTABwAEBEA8Y+Voq7RCyAQYImwEQjwK6AQCCAgDyAkxNUzR3TGpBQkFBQUF1Ml9tSzRMOFhiWGt5TWlBb2cyVE5zZlY5OTdPVjNraUNzQk5DY2FpMHFsSHFLdENwdFBlNTdkS2FYcW9MVkl6ggTqCLoBnwUqBggBEAEYIFoNCgASCSNCMzQ3N0VGRoABDwgEEtgEEix3ZWJjYXN0LXZhL2dyYWRlX2JhZGdlX2ljb25fbGl0ZV9sdjE1X3YyLnBuZzrpAnNzbG9jYWw6Ly93ZWJjYXN0X2x5bnh2aWV3X3BvcHVwP3VzZV9zcGFyaz0xJnVybD1odHRwcyUzQSUyRiUyRmxmMTYtZ2Vja28tc291cmNlLnRpa3Rva2Nkbi5jb20lMkZvYmolMkZieXRlLWd1cmQtc291cmNlLXNnJTJGdGlrdG9rJTJGZmUlMkZsaXZlJTJGdGlrdG9rX2xpdmVfcmV2ZW51ZV91c2VyX2xldmVsX21haW4lMkZzcmMlMkZwYWdlcyUyRnByaXZpbGVnZSUyRnBhbmVsJTJGdGVtcGxhdGUuanMmaGlkZV9zdGF0dXNfYmFyPTAmaGlkZV9uYXZfYmFyPTEmY29udGFpbmVyX2JnX2NvbG9yPTAwMDAwMDAwJmhlaWdodD05MCUyNSZiZGhtX2JpZD10aWt0b2tfbGl2ZV9yZXZlbnVlX3VzZXJfbGV2ZWxfbWFpbiZ1c2VfZm9yZXN0PTEKXWh0dHBzOi8vcDE2LXdlYmNhc3QudGlrdG9rY2RuLmNvbS93ZWJjYXN0LXZhL2dyYWRlX2JhZGdlX2ljb25fbGl0ZV9sdjE1X3YyLnBuZ350cGx2LW9iai5pbWFnZQpdaHR0cHM6Ly9wMTktd2ViY2FzdC50aWt0b2tjZG4uY29tL3dlYmNhc3QtdmEvZ3JhZGVfYmFkZ2VfaWNvbl9saXRlX2x2MTVfdjIucG5nfnRwbHYtb2JqLmltYWdlIgIxNTIAOgYaAhIAIgBiDQoAEgkjQjM0NzdFRkZ4DqIBBggBEAEYIAgEEBQYCCABUukCc3Nsb2NhbDovL3dlYmNhc3RfbHlueHZpZXdfcG9wdXA/dXNlX3NwYXJrPTEmdXJsPWh0dHBzJTNBJTJGJTJGbGYxNi1nZWNrby1zb3VyY2UudGlrdG9rY2RuLmNvbSUyRm9iaiUyRmJ5dGUtZ3VyZC1zb3VyY2Utc2clMkZ0aWt0b2slMkZmZSUyRmxpdmUlMkZ0aWt0b2tfbGl2ZV9yZXZlbnVlX3VzZXJfbGV2ZWxfbWFpbiUyRnNyYyUyRnBhZ2VzJTJGcHJpdmlsZWdlJTJGcGFuZWwlMkZ0ZW1wbGF0ZS5qcyZoaWRlX3N0YXR1c19iYXI9MCZoaWRlX25hdl9iYXI9MSZjb250YWluZXJfYmdfY29sb3I9MDAwMDAwMDAmaGVpZ2h0PTkwJTI1JmJkaG1fYmlkPXRpa3Rva19saXZlX3JldmVudWVfdXNlcl9sZXZlbF9tYWluJnVzZV9mb3Jlc3Q9MVgBYk8qAjE1CgEyEhM3MTM4MzgxNzQ3MjkyNTQyNzU2GgEwIi5tb2NrX2ZpeF93aWR0aF90cmFuc3BhcmVudF83MTM4MzgxNzQ3MjkyNTQyNzU2CIWI44D1ib+0YBoQ8J2SpvCdk47wnZOO8JOFk0r1BhJBMTAweDEwMC90b3MtdXNlYXN0OC1hdnQtMDA2OC10eDIvNjY0NmM4NjZjMzI1MWEwOTY3NjhiYjY4OTUyODVjMzEK0gFodHRwczovL3AxOS1wdS1zaWduLXVzZWFzdDgudGlrdG9rY2RuLXVzLmNvbS90b3MtdXNlYXN0OC1hdnQtMDA2OC10eDIvNjY0NmM4NjZjMzI1MWEwOTY3NjhiYjY4OTUyODVjMzF+dHBsdi10aWt0b2stc2hyaW5rOjcyOjcyLndlYnA/bGszcz1hNWQ0ODA3OCZ4LWV4cGlyZXM9MTcwOTMxMjQwMCZ4LXNpZ25hdHVyZT1VMlNEbUk3Z3R5RW9rMlBlWFdmeTNsM1F6NlElM0QKyAFodHRwczovL3AxNi1wdS1zaWduLXVzZWFzdDgudGlrdG9rY2RuLXVzLmNvbS90b3MtdXNlYXN0OC1hdnQtMDA2OC10eDIvNjY0NmM4NjZjMzI1MWEwOTY3NjhiYjY4OTUyODVjMzF+YzVfMTAweDEwMC53ZWJwP2xrM3M9YTVkNDgwNzgmeC1leHBpcmVzPTE3MDkzMTI0MDAmeC1zaWduYXR1cmU9aWNWZEVZa0FnWkYlMkZ2WU5OTSUyRlVNMzE2eG9HdyUzRArGAWh0dHBzOi8vcDE5LXB1LXNpZ24tdXNlYXN0OC50aWt0b2tjZG4tdXMuY29tL3Rvcy11c2Vhc3Q4LWF2dC0wMDY4LXR4Mi82NjQ2Yzg2NmMzMjUxYTA5Njc2OGJiNjg5NTI4NWMzMX5jNV8xMDB4MTAwLndlYnA/bGszcz1hNWQ0ODA3OCZ4LWV4cGlyZXM9MTcwOTMxMjQwMCZ4LXNpZ25hdHVyZT1PQzdBQ3htQUklMkJsYlp4RkVuWktJT1RyRExGUSUzRArGAWh0dHBzOi8vcDE2LXB1LXNpZ24tdXNlYXN0OC50aWt0b2tjZG4tdXMuY29tL3Rvcy11c2Vhc3Q4LWF2dC0wMDY4LXR4Mi82NjQ2Yzg2NmMzMjUxYTA5Njc2OGJiNjg5NTI4NWMzMX5jNV8xMDB4MTAwLmpwZWc/bGszcz1hNWQ0ODA3OCZ4LWV4cGlyZXM9MTcwOTMxMjQwMCZ4LXNpZ25hdHVyZT02YUwlMkZNZWtOeHg5NXlvVTVLOTZON0xwRUlNdyUzRLICCmt5bGxlZWhhbGxCyQEIgojG1pKb0clgErwBChBwbV9tdF9tc2dfdmlld2VyEhd7MDp1c2VyfSBsaWtlZCB0aGUgTElWRRoOCgkjZmZmZmZmZmYgkAMifwgLqgF6CngIhYjjgPWJv7RgGhDwnZKm8J2TjvCdk47wk4WTsgIKa3lsbGVlaGFsbPICTE1TNHdMakFCQUFBQXUyX21LNEw4WGJYa3lNaUFvZzJUTnNmVjk5N09WM2tpQ3NCTkNjYWkwcWxIcUt0Q3B0UGU1N2RLYVhxb0xWSXo=";
}

View File

@@ -1,63 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
public class GiftsExample {
public static void main(String[] args) {
var giftsManager = TikTokLive.gifts();
var giftsList = giftsManager.toList();
for (var gift : giftsList) {
System.out.println("Gift: " + gift);
}
var giftsMap = giftsManager.toMap();
for (var entry : giftsMap.entrySet()) {
System.out.println("GiftId: " + entry.getKey() + " Gift: " + entry.getValue());
}
System.out.println("total number of gifts: " + giftsManager.toList().size());
var giftRose = giftsManager.getById(5655);
var giftRoseByName = giftsManager.getByName("Rose");
var giftByFilter = giftsManager.getByFilter(e -> e.getDiamondCost() > 50);
var giftsByFilter = giftsManager.getManyByFilter(e -> e.getDiamondCost() > 100);
System.out.println("total number of gifts with cost higher then 100: " + giftsByFilter.size());
/**
* In case searched gift not exists getByName returns you Gift.UNDEFINED
*/
var undefiedGift = giftsManager.getByName("GIFT WITH WRONG NAME");
var customGift = new Gift(123213213, "Custom gift", 50, "https://images.pexels.com/photos/2071882/pexels-photo-2071882.jpeg?cs=srgb&dl=pexels-wojciech-kumpicki-2071882.jpg&fm=jpg");
giftsManager.attachGift(customGift);
}
}

View File

@@ -48,7 +48,7 @@ public class ListenerExample
showLogo();
CustomListener customListener = new CustomListener();
TikTokLive.newClient(ConnectionExample.TIKTOK_HOSTNAME)
TikTokLive.newClient(SimpleExample.TIKTOK_HOSTNAME)
.addListener(customListener)
.buildAndConnect();
System.in.read();
@@ -57,7 +57,7 @@ public class ListenerExample
/**
*
* Method in TikTokEventListener should meet 4 requirements to be detected
* - must have @TikTokEventObserver annotation
* - must have @TikTokEventHandler annotation
* - must have 2 parameters
* - first parameter must be LiveClient
* - second must be class that extending TikTokEvent
@@ -84,12 +84,12 @@ public class ListenerExample
@TikTokEventObserver
public void onGift(LiveClient liveClient, TikTokGiftEvent event) {
var message = switch (event.getGift().getName()) {
case "ROSE" -> "Thanks :)";
case "APPETIZERS" -> ":OO";
case "APRIL" -> ":D";
case "TIKTOK" -> ":P";
case "CAP" -> ":F";
var message = switch (event.getGift()) {
case ROSE -> "Thanks :)";
case APPETIZERS -> ":OO";
case APRIL -> ":D";
case TIKTOK -> ":P";
case CAP -> ":F";
default -> ":I";
};
liveClient.getLogger().info(message);
@@ -115,4 +115,4 @@ public class ListenerExample
""");
}
}
}

View File

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

View File

@@ -23,27 +23,35 @@
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.data.events.TikTokSubNotifyEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokSubscribeEvent;
import io.github.jwdeveloper.tiktok.data.events.envelop.TikTokChestEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveOfflineHostException;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage;
import io.github.jwdeveloper.tiktok.utils.ConsoleColors;
import io.github.jwdeveloper.tiktok.utils.JsonUtil;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.HashMap;
import java.util.logging.Level;
public class ConnectionExample {
public static String TIKTOK_HOSTNAME = "kvadromama_marina1";
public class SimpleExample {
public static String TIKTOK_HOSTNAME = "dash4214";
public static void main(String[] args) throws IOException {
public static void main(String[] args) throws IOException, InterruptedException {
showLogo();
var gifts = TikTokLive.gifts();
TikTokLive.newClient(ConnectionExample.TIKTOK_HOSTNAME)
TikTokLive.newClient(SimpleExample.TIKTOK_HOSTNAME)
.configure(clientSettings ->
{
clientSettings.setHostName(ConnectionExample.TIKTOK_HOSTNAME); // This method is useful in case you want change hostname later
clientSettings.setHostName(SimpleExample.TIKTOK_HOSTNAME); // This method is useful in case you want change hostname later
clientSettings.setClientLanguage("en"); // Language
clientSettings.setLogLevel(Level.ALL); // Log level
clientSettings.setPrintToConsole(true); // Printing all logs to console even if log level is Level.OFF
@@ -85,10 +93,10 @@ public class ConnectionExample {
})
.onGift((liveClient, event) ->
{
switch (event.getGift().getName()) {
case "ROSE" -> print(ConsoleColors.RED, "Rose!");
case "GG" -> print(ConsoleColors.YELLOW, " GOOD GAME!");
case "TIKTOK" -> print(ConsoleColors.CYAN, "Thanks for TikTok");
switch (event.getGift()) {
case ROSE -> print(ConsoleColors.RED, "Rose!");
case GG -> print(ConsoleColors.YELLOW, " GOOD GAME!");
case TIKTOK -> print(ConsoleColors.CYAN, "Thanks for TikTok");
default ->
print(ConsoleColors.GREEN, "[Thanks for gift] ", ConsoleColors.YELLOW, event.getGift().getName(), "x", event.getCombo());
}
@@ -152,4 +160,4 @@ public class ConnectionExample {
""");
}
}
}

501
README.md
View File

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

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<version>1.0.14-Release</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>Tools-EventsCollector</artifactId>
<properties>
<maven.compiler.source>16</maven.compiler.source>
<maven.compiler.target>16</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.34.0</version> <!-- Use the latest version available -->
</dependency>
<dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi3-core</artifactId>
<version>3.23.0</version>
</dependency>
<dependency>
<groupId>com.googlecode.protobuf-java-format</groupId>
<artifactId>protobuf-java-format</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>org.jdbi</groupId>
<artifactId>jdbi3-sqlobject</artifactId>
<version>3.23.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.32</version> <!-- Use the latest version available -->
</dependency>
<dependency>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>Client</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>Tools</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

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

@@ -20,8 +20,14 @@
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.extension.recorder.impl.enums;
package io.github.jwdeveloper.tiktok.tools.collector.api;
public enum LiveQuality {
origin, hd_60, ao, hd, sd, ld,uhd_60
public interface DataCollector {
void connect();
void disconnect();
void disconnect(boolean keepDatabase);
}

View File

@@ -20,23 +20,25 @@
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.extension.recorder;
package io.github.jwdeveloper.tiktok.tools.collector.api;
import io.github.jwdeveloper.tiktok.extension.recorder.api.LiveRecorder;
import io.github.jwdeveloper.tiktok.extension.recorder.impl.RecorderListener;
import io.github.jwdeveloper.tiktok.extension.recorder.impl.data.RecorderSettings;
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
import io.github.jwdeveloper.tiktok.tools.db.TikTokDatabase;
import java.util.function.Consumer;
public class TikTokLiveRecorder
{
public static LiveRecorder use(Consumer<RecorderSettings> consumer)
{
return new RecorderListener(consumer);
}
public interface DataCollectorBuilder extends DataFilters<DataCollectorBuilder> {
DataCollectorBuilder setOutputPath(String path);
DataCollectorBuilder setSessionTag(String sessionTimestamp);
public static LiveRecorder use()
{
return use(x ->{});
}
}
DataCollectorBuilder setDatabase(TikTokDatabase database);
DataCollectorBuilder configureLiveClient(Consumer<LiveClientBuilder> consumer);
DataCollectorBuilder addUser(String user);
DataCollector buildAndRun();
DataCollector build();
}

View File

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

View File

@@ -20,24 +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.extension.collector.api.settings.mongo;
package io.github.jwdeveloper.tiktok.tools.collector.api;
import lombok.*;
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 MongoDataCollectorSettings {
private String connectionUrl;
private String databaseName = "tiktok";
private String collectionName = "data";
public void connectionBuilder(Consumer<MongoDBConnectionStringBuilder> consumer) {
var builder = new MongoDBConnectionStringBuilder();
consumer.accept(builder);
connectionUrl = builder.build();
}
}
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

@@ -0,0 +1,103 @@
/*
* 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.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import io.github.jwdeveloper.tiktok.utils.FilesUtility;
import io.github.jwdeveloper.tiktok.utils.JsonUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.io.File;
import java.lang.reflect.Type;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.util.*;
import java.util.logging.Logger;
public class MessagesManager {
@Getter
Map<String, Queue<MessageData>> messages;
String outputName;
int limit = 20;
public MessagesManager(String outputName) {
this.messages = new TreeMap<>();
this.outputName = outputName;
load();
}
public void addMessage(Logger logger, String host, WebcastResponse.Message message) {
var name = message.getMethod();
var payload = message.getPayload().toByteArray();
var base64 = Base64.getEncoder().encodeToString(payload);
if (!messages.containsKey(name)) {
logger.info("New Message found! " + name);
messages.put(name, new LinkedList<>());
}
var queue = messages.get(name);
if (queue.size() > limit) {
queue.remove();
}
queue.add(new MessageData(base64, host, LocalDateTime.now().toString()));
save();
}
public String toJson() {
return JsonUtil.toJson(messages);
}
public void load() {
var file = new File(path());
Type type = new TypeToken<Map<String, Queue<MessageData>>>() {}.getType();
if (file.exists()) {
var content = FilesUtility.loadFileContent(path());
var gson = new GsonBuilder().create();
messages = gson.fromJson(content,type);
}
}
public void save() {
FilesUtility.saveFile(path(), toJson());
}
public String path() {
return Paths.get("C:\\Users\\ja\\IdeaProjects\\TikTokLiveJava\\Tools-EventsCollector\\src\\main\\resources", outputName + ".json").toString();
}
@AllArgsConstructor
@Getter
public class MessageData {
String eventData;
String uniqueId;
String ts;
}
}

View File

@@ -0,0 +1,37 @@
/*
* 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.tools.db.TikTokDatabase;
public class TikTokClientFactory {
private final MessagesManager messageCollector;
private final TikTokDatabase tikTokDatabase;
public TikTokClientFactory(MessagesManager messageCollector, TikTokDatabase tikTokDatabase) {
this.messageCollector = messageCollector;
this.tikTokDatabase = tikTokDatabase;
}
}

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

@@ -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.tools.db;
public class SqlConsts
{
public static String CREATE_DATA_TABLE = """
CREATE TABLE IF NOT EXISTS TikTokData (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sessionTag TEXT,
tiktokUser TEXT,
dataType TEXT,
dataTypeName TEXT,
content TEXT,
createdAt TEXT
);
""";
public static String CREATE_ERROR_TABLE = """
CREATE TABLE IF NOT EXISTS TikTokErrorModel (
id INT AUTO_INCREMENT PRIMARY KEY,
hostName VARCHAR(255),
errorName VARCHAR(255),
errorType VARCHAR(255),
exceptionContent TEXT,
message TEXT,
response TEXT,
createdAt DATETIME
);
""";
public static String CREATE_RESPONSE_MODEL = """
CREATE TABLE IF NOT EXISTS TikTokResponseModel (
id INTEGER PRIMARY KEY AUTOINCREMENT,
hostName TEXT,
response 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

@@ -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.db;
import io.github.jwdeveloper.tiktok.tools.db.tables.TikTokErrorModel;
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(TikTokErrorModel.class)
public interface TikTokErrorModelDAO
{
@SqlUpdate("INSERT INTO TikTokErrorModel (hostName, errorName, errorType, exceptionContent, message, response, createdAt) " +
"VALUES (:hostName, :errorName, :errorType, :exceptionContent, :message, :response, :createdAt)")
void insertTikTokMessage(@BindBean TikTokErrorModel message);
@SqlQuery("SELECT * FROM TikTokErrorModel")
List<TikTokErrorModel> selectErrors();
}

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