Compare commits

...

82 Commits

Author SHA1 Message Date
kohlerpop1
5abfd95c89 Update of Lombok
Fixed headers being passed to Websocket. Huge thanks to @isaackogan - https://github.com/isaackogan
2024-05-23 18:32:55 -04:00
kohlerpop1
5c715bfd52 Changed Picture#Empty to Picture#empty to follow Java standards
Updated descriptions in LiveClientSettings
Added new LiveHttpClient#fetchRoomGiftsData(String room_id) to fetch gifts for this specific room/livestream and altered GiftsDataMapper to reflect the new fetchRoomGiftsData and other TikTokLive client and http client classes
2024-05-13 21:14:03 -04:00
GitHub Action
b153afb332 Update version in pom.xml 2024-05-13 00:55:24 +00:00
David Kohler
d2ea00bcae Merge pull request #72 from jwdeveloper/develop-1.6.1
Develop 1.6.1
2024-05-12 20:52:28 -04:00
kohlerpop1
4297af1349 Simplify LiveDataMapper#map 2024-05-12 20:47:13 -04:00
kohlerpop1
d09c90ef54 Added append live username option to FileStorage and now support connection to 18+ age restricted! 2024-05-10 16:44:42 -04:00
kohlerpop1
9c96c8899a Added option to use File Locking as through testing, some events occur simultaneously causing the file to become overlapped or corrupted. 2024-05-04 15:21:34 -04:00
kohlerpop1
301df6392d More updated to TikTokLinkMicBattleEvent, updated Gift since its no longer enum we do not need to use Unsafe, and added default mappings for WebcastLinkMicBattle and WebcastLinkMicArmies 2024-04-19 13:41:07 -04:00
kohlerpop1
fb9fc04ee5 Revert "More updated to TikTokLinkMicBattleEvent, updated Gift since its no longer enum we do not need to use Unsafe, and added default mappings for WebcastLinkMicBattle and WebcastLinkMicArmies"
This reverts commit 43a8ba4225.
2024-04-19 13:33:49 -04:00
kohlerpop1
43a8ba4225 More updated to TikTokLinkMicBattleEvent, updated Gift since its no longer enum we do not need to use Unsafe, and added default mappings for WebcastLinkMicBattle and WebcastLinkMicArmies 2024-04-19 13:28:10 -04:00
David Kohler
dffccf1f0b Update README.md 2024-04-10 12:08:46 -04:00
GitHub Action
6dcccccb78 Update version in pom.xml 2024-04-10 16:07:17 +00:00
David Kohler
0d467d79c3 Update TikTokGiftEventHandlerTest.java
Fixed minor issue with Client compilation
2024-04-10 12:05:20 -04:00
David Kohler
33c98508c0 MINOR
Merge pull request #70 from jwdeveloper/develop-1.6.0
2024-04-10 12:01:00 -04:00
kohlerpop1
67948b14cc Changed isNotClosing to isOpen because if isOpen is false inside of any of the using methods, it throws an exception. 2024-04-10 11:54:35 -04:00
kohlerpop1
22e11a7822 Removed TikTokRoomInfo.getHostUser() in favor of TikTokRoomInfo.getHost().
Major rework of TikTokLinkMicBattleEvent and proto to support it.
Addition changes to other files to support!
2024-04-09 21:38:04 -04:00
GitHub Action
4545503441 Update version in pom.xml 2024-04-02 02:28:56 +00:00
David Kohler
498d34a90b Merge pull request #69 from jwdeveloper/develop-1.5.4
Changed isNotClosing to isOpen
2024-04-01 22:27:09 -04:00
kohlerpop1
103ed7e3ed Changed isNotClosing to isOpen because if isOpen is false inside of any of the using methods, it throws an exception. 2024-03-31 20:19:24 -04:00
GitHub Action
67e70c34bc Update version in pom.xml 2024-03-03 21:42:11 +00:00
JW
786c24d267 Merge remote-tracking branch 'origin/master' 2024-03-03 22:40:11 +01:00
JW
966d2f65d8 - improve recorder 2024-03-03 22:39:44 +01:00
GitHub Action
7ba7143f5a Update version in pom.xml 2024-03-02 09:57:33 +00:00
JW
92fde03f2b - improve collector 2024-03-02 10:55:44 +01:00
GitHub Action
e058290118 Update version in pom.xml 2024-03-01 23:17:33 +00:00
David Kohler
d25741b229 Merge pull request #67 from jwdeveloper/develop-1.5.1
Fix for mapping of HttpResponse & HttpRequest and more!
2024-03-01 18:15:41 -05:00
kohlerpop1
560a8d7c3b Added IllegalStateException to LiveUserDataMapper to catch getAsJsonObject exception.
Created HttpRequestJsonMapper and HttpResponseJsonMapper for ActionResult gson parser.
2024-03-01 16:08:05 -05:00
GitHub Action
6178bc25cf Update version in pom.xml 2024-03-01 01:54:49 +00:00
Jacek W
48d1138754 MINOR 2024-03-01 02:53:04 +01:00
Jacek W
a5320db820 Merge pull request #63 from jwdeveloper/develop-1.5.0
Develop 1.5.0
2024-03-01 02:51:35 +01:00
JW
4e1ab35a60 Merge branch 'master' into develop-1.5.0
# Conflicts:
#	Tools-EventsCollector/pom.xml
#	Tools-EventsWebViewer/pom.xml
2024-03-01 02:50:35 +01:00
David Kohler
cef4972f37 Merge pull request #64 from jwdeveloper/develop-1.5.0-live-user-data-fix
Develop 1.5.0 live user data fix
2024-02-29 20:42:36 -05:00
JW
713c90a271 . 2024-03-01 02:42:23 +01:00
kohlerpop1
71853db5cc Merge remote-tracking branch 'origin/develop-1.5.0' into develop-1.5.0-live-user-data-fix
# Conflicts:
#	Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClient.java
2024-02-29 20:38:53 -05:00
kohlerpop1
ef90d4cd58 Moved validation to TikTokLiveClientBuilder#validate! 2024-02-29 20:38:05 -05:00
Jacek W
dad4048bc0 Merge pull request #65 from jwdeveloper/develop-1.5.0-publishing-messages
- implementing publishing messages
2024-03-01 02:35:52 +01:00
kohlerpop1
9ba049d37a Fixed CollectorExample and removed useless @Setter in MongoDataCollectorSettings! 2024-02-29 20:27:41 -05:00
kohlerpop1
f7d657371b Merge remote-tracking branch 'origin/develop-1.5.0' into develop-1.5.0-live-user-data-fix
# Conflicts:
#	extension-collector/src/main/java/io/github/jwdeveloper/tiktok/extension/collector/api/settings/mongo/MongoDataCollectorSettings.java
2024-02-29 20:23:32 -05:00
JW
eea691a5aa - implementing publishing messages 2024-03-01 02:20:11 +01:00
Jacek W
a249ac0cdd Merge pull request #62 from jwdeveloper/develop-1.5.0-messages-to-file
Develop 1.5.0 messages to file
2024-03-01 01:53:21 +01:00
JW
b82c7184b3 Removed unused projects. 2024-03-01 01:52:54 +01:00
kohlerpop1
29631ac468 Fixed Live User Data Mapper throwing MalformedJsonException! 2024-02-29 19:19:23 -05:00
kohlerpop1
15c642297c Fixed Live User Data Mapper throwing MalformedJsonException! 2024-02-28 21:03:00 -05:00
kohlerpop1
d3004d76c1 Merge remote-tracking branch 'origin/develop-1.5.0' into develop-1.5.0/messages-to-file 2024-02-28 12:27:05 -05:00
kohlerpop1
3ae73072ff Working on collecting to files! 2024-02-28 12:24:34 -05:00
Jacek W
9c5f97157a Merge pull request #61 from jwdeveloper/develop-1.5.0-remove-old-stuff
Develop 1.5.0 remove old stuff
2024-02-28 16:45:49 +01:00
JW
ea847bb883 Merge branch 'develop-1.5.0' into develop-1.5.0-remove-old-stuff 2024-02-28 16:44:57 +01:00
JW
45bac053b9 Removed unused projects 2024-02-28 16:44:07 +01:00
Jacek W
8cb647f27a Merge pull request #60 from jwdeveloper/develop-1.5.0-client-testing
Testing gifts, follows and more!
2024-02-28 16:27:47 +01:00
JW
ffbd67eef4 made: settings.fetchGifts default to true,
attach to options `setOffline`
create static method `of` for events

-TikTokGiftEvent
-TikTokCommentEvent
-TikTokSubscribeEvent
-TikTokFollowEvent
-TikTokLikeEvent
-TikTokJoinEvent

Rename:

GiftSendType -> GiftComboStateType
2024-02-28 16:05:25 +01:00
JW
e923f3fad7 made: settings.fetchGifts default to true,
attach to options `setOffline`
create static method `of` for events

-TikTokGiftEvent
-TikTokCommentEvent
-TikTokSubscribeEvent
-TikTokFollowEvent
-TikTokLikeEvent
-TikTokJoinEvent
2024-02-28 15:58:16 +01:00
JW
faa1185b97 made: settings.fetchGifts default to true 2024-02-28 15:11:03 +01:00
GitHub Action
ead954dd27 Update version in pom.xml 2024-02-26 15:26:34 +00:00
Jacek W
e37b30ff12 MINOR 2024-02-26 16:20:47 +01:00
GitHub Action
7a5c00d99a Update version in pom.xml 2024-02-26 15:17:09 +00:00
Jacek W
407f51fa73 Merge pull request #59 from jwdeveloper/develop-1.4.0
MINOR update
2024-02-26 16:15:37 +01:00
JW
8581df7f49 Merge branch 'develop-1.4.0' into develop-1.4.0-tester 2024-02-25 21:35:02 +01:00
JW
3e52523644 Prepare tester extension 2024-02-25 21:34:38 +01:00
Jacek W
3387986ced Merge pull request #58 from jwdeveloper/develop-1.4.0-Gifts-Update
Update gifts manager
2024-02-25 21:30:54 +01:00
JW
0fcac60cbe Update gifts manager 2024-02-25 21:29:21 +01:00
JW
63dd8c20ac Update gifts manager 2024-02-25 21:02:39 +01:00
JW
b809bb6cda Update gifts manager 2024-02-22 20:28:13 +01:00
kohlerpop1
a68eaba5a1 Began rework to dynamic gifts. Did not fetch from url yet. 2024-02-21 17:27:02 -05:00
JW
0252b9a42f Gifts 2024-02-21 22:47:29 +01:00
kohlerpop1
1b2a8bad93 Converted from Optional to ActionResult
Moved Logger creation to LoggerFactory
Fixed creating more than 1 recording thread for each livestream
And more optimizations!
2024-02-19 14:55:59 -05:00
kohlerpop1
6b22154c82 Pushing broken changes for JW! 2024-02-15 16:40:39 -05:00
GitHub Action
965816e846 Update version in pom.xml 2024-02-15 19:01:19 +00:00
David Kohler
6b6e82cd93 MINOR 2024-02-15 13:59:36 -05:00
GitHub Action
c93c3144ff Update version in pom.xml 2024-02-15 18:56:11 +00:00
David Kohler
12c64e1c67 MINOR: Merge pull request #57 from jwdeveloper/develop-1.3.0
MINOR: Develop 1.3.0
2024-02-15 13:54:31 -05:00
David Kohler
5794ff2a57 MINOR: Merge pull request #56 from kohlerpop1/fixes-updates
MINOR: Switched to new Signing Server endpoint and more
2024-02-15 13:53:36 -05:00
kohlerpop1
d471e87dd7 Converted magic number to constant AGE_RESTRICTED_CODE 2024-02-15 12:55:45 -05:00
kohlerpop1
c89bcad894 Removed System.out.println of response headers! 2024-02-15 12:13:52 -05:00
kohlerpop1
c9a84c39df Merge remote-tracking branch 'origin/fixes-updates' into fixes-updates 2024-02-15 12:10:52 -05:00
kohlerpop1
c1105f1324 Switched to new Signing Server endpoint and more 2024-02-15 12:10:17 -05:00
kohlerpop1
243ce9bc94 Added PreConnectionEvent with LiveType, made optimizations, and added fallback to default request in proxy class in case proxy protocol is not supported by TikTok or Signing server. 2024-02-15 11:46:13 -05:00
GitHub Action
4f141edb1a Update version in pom.xml 2024-02-15 00:26:07 +00:00
Jacek W
359a1508c7 MINOR 2024-02-15 01:24:26 +01:00
Jacek W
bbfa7b410b Merge pull request #55 from jwdeveloper/develop-1.2.0
Develop 1.2.0
2024-02-15 01:23:56 +01:00
GitHub Action
6da40927d0 Update version in pom.xml 2024-02-15 00:18:41 +00:00
Jacek W
4d97fd9157 Merge pull request #54 from kohlerpop1/fixes-updates
MINOR: Added PreConnectionEvent with LiveType, made optimizations, and more
2024-02-15 01:16:59 +01:00
kohlerpop1
1ba51476d1 Added PreConnectionEvent with LiveType, made optimizations, and added fallback to default request in proxy class in case proxy protocol is not supported by TikTok or Signing server. 2024-02-12 15:24:54 -05:00
206 changed files with 3433 additions and 217304 deletions

3
.gitignore vendored
View File

@@ -1,10 +1,11 @@
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.1.1-Release</version>
<version>1.6.1-Release</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>API</artifactId>

View File

@@ -54,4 +54,16 @@ public class TikTokCommentEvent extends TikTokHeaderEvent {
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

@@ -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,8 +40,10 @@ import java.util.List;
@EventMeta(eventType = EventType.Message)
public class TikTokLinkMicArmiesEvent extends TikTokHeaderEvent {
private final Long battleId;
private final Integer battleStatus;
/**
true if battle is finished otherwise false
*/
private final boolean finished;
private final Picture picture;
@@ -52,6 +54,6 @@ public class TikTokLinkMicArmiesEvent extends TikTokHeaderEvent {
battleId = msg.getId();
armies = msg.getBattleItemsList().stream().map(LinkMicArmy::new).toList();
picture = Picture.map(msg.getImage());
battleStatus = msg.getBattleStatus();
finished = msg.getBattleStatus() == LinkMicBattleStatus.ARMY_FINISHED;
}
}

View File

@@ -22,29 +22,62 @@
*/
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.annotations.*;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokHeaderEvent;
import io.github.jwdeveloper.tiktok.data.models.LinkMicBattleTeam;
import io.github.jwdeveloper.tiktok.data.models.battles.*;
import io.github.jwdeveloper.tiktok.messages.enums.LinkMicBattleStatus;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkMicBattle;
import lombok.Getter;
import lombok.*;
import java.util.List;
import java.util.*;
/**
* Triggered every time a battle starts.
* Triggered every time a battle starts & ends
*/
@Getter
@EventMeta(eventType = EventType.Message)
public class TikTokLinkMicBattleEvent extends TikTokHeaderEvent {
public class TikTokLinkMicBattleEvent extends TikTokHeaderEvent
{
private final Long battleId;
private final List<LinkMicBattleTeam> team1;
private final List<LinkMicBattleTeam> team2;
/**
true if battle is finished otherwise false
*/
private final boolean finished;
@Getter(AccessLevel.NONE)
private final boolean oneVsOne;
private final List<Team> teams;
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();
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;
}
}

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,4 +52,11 @@ 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

@@ -20,24 +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.tools.db.tables;
package io.github.jwdeveloper.tiktok.data.events.control;
import lombok.Data;
import io.github.jwdeveloper.tiktok.annotations.*;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokLiveClientEvent;
import io.github.jwdeveloper.tiktok.data.requests.*;
import lombok.*;
@Data
public class TikTokDataTable
/**
* Triggered before the connection is established.
*/
@EventMeta(eventType = EventType.Control)
public class TikTokPreConnectionEvent extends TikTokLiveClientEvent
{
private Integer id;
@Getter private final LiveUserData.Response userData;
@Getter private final LiveData.Response roomData;
@Getter @Setter boolean cancelConnection = false;
private String sessionTag;
private String tiktokUser;
private String dataType;
private String dataTypeName;
private String content;
private String createdAt;
public TikTokPreConnectionEvent(LiveUserData.Response userData, LiveData.Response liveData) {
this.userData = userData;
this.roomData = liveData;
}
}

View File

@@ -22,10 +22,9 @@
*/
package io.github.jwdeveloper.tiktok.data.events.gift;
import io.github.jwdeveloper.tiktok.annotations.EventMeta;
import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.data.models.gifts.GiftSendType;
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.data.models.users.User;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage;
import lombok.Getter;
@@ -34,7 +33,7 @@ import lombok.Getter;
/**
* Triggered every time gift is sent
*
* @see GiftSendType it has 3 states
* @see GiftComboStateType it has 3 states
*
* <p>Example when user sends gift with combo</p>
* <p>>Combo: 1 -> comboState = GiftSendType.Begin</p>
@@ -47,10 +46,21 @@ import lombok.Getter;
@EventMeta(eventType = EventType.Message)
@Getter
public class TikTokGiftComboEvent extends TikTokGiftEvent {
private final GiftSendType comboState;
private final GiftComboStateType comboState;
public TikTokGiftComboEvent(Gift gift, User host, WebcastGiftMessage msg, GiftSendType comboState) {
public TikTokGiftComboEvent(Gift gift, User host, WebcastGiftMessage msg, GiftComboStateType 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,18 +23,14 @@
package io.github.jwdeveloper.tiktok.data.events.gift;
import io.github.jwdeveloper.tiktok.annotations.EventMeta;
import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.annotations.*;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokHeaderEvent;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.data.models.gifts.GiftSendType;
import io.github.jwdeveloper.tiktok.data.models.gifts.*;
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
@@ -60,4 +56,20 @@ 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

@@ -24,8 +24,10 @@ 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;
@@ -45,4 +47,12 @@ 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,6 +26,7 @@ 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;
@@ -47,4 +48,13 @@ 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,4 +57,15 @@ 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

@@ -1,41 +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.models;
import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkMicBattle;
import lombok.Value;
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.getUrlListCount() - 1;
var index = profilePicture.getUrlCount() - 1;
if (index < 0) {
return new Picture("");
}
var url = profilePicture.getUrlList(index);
var url = profilePicture.getUrl(index);
return new Picture(url);
}
@@ -93,7 +93,7 @@ public class Picture {
}
}
public static Picture Empty() {
public static Picture empty() {
return new Picture("");
}

View File

@@ -0,0 +1,47 @@
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

@@ -0,0 +1,26 @@
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

@@ -0,0 +1,31 @@
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

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

File diff suppressed because it is too large Load Diff

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.WebcastEnvelopeMessage;
import io.github.jwdeveloper.tiktok.messages.webcast.*;
import lombok.AccessLevel;
import lombok.Getter;
@@ -136,6 +136,14 @@ 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();
@@ -159,10 +167,9 @@ public class User {
}
}
public static User EMPTY = new User(0L,
"",
Picture.Empty(),
Picture.empty(),
0,
0,
List.of(Badge.empty()));
@@ -209,4 +216,18 @@ 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,16 +42,7 @@ public class GiftsData
public static final class Response
{
private String json;
private List<GiftModel> gifts;
}
@Data
public static class GiftModel
{
private int id;
private String name;
private int diamondCost;
private String image;
private List<Gift> gifts;
}
}

View File

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

View File

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

View File

@@ -33,6 +33,18 @@ import java.util.logging.Level;
@Data
public class LiveClientSettings {
/**
* Sets client to offline mode, prohibits connection to TikTok servers
* @apiNote Useful when testing client with custom events
*/
private boolean offline;
/**
* Fetch and download gifts data before TikTokLive starts
* @apiNote If `false`, client.giftManager() does not contain initial gifts
*/
private boolean fetchGifts = true;
/**
* ISO-Language for Client
*/
@@ -68,20 +80,32 @@ public class LiveClientSettings {
*/
private HttpClientSettings httpSettings;
/**
* Interval of time in milliseconds between pings to TikTok
* @apiNote Min: 250 (0.25 seconds), Default: 5000 (5 seconds)
*/
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
* documentation how to obtain sessionId https://github.com/isaackogan/TikTok-Live-Connector#send-chat-messages
* @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;
public static LiveClientSettings createDefault()
{
/**
* Optional: API Key for increased limit to signing server
*/
private String apiKey;
public static LiveClientSettings createDefault() {
var httpSettings = new HttpClientSettings();
httpSettings.getParams().putAll(DefaultClientParams());
httpSettings.getHeaders().putAll(DefaultRequestHeaders());
@@ -154,4 +178,6 @@ public class LiveClientSettings {
headers.put("Accept-Language", "en-US,en; q=0.9");
return headers;
}
}

View File

@@ -33,11 +33,10 @@ import java.util.function.Consumer;
@Setter
public class ProxyClientSettings implements Iterator<ProxyData>
{
private boolean enabled, lastSuccess;
private boolean enabled, autoDiscard = true, fallback = true;
private Rotation rotation = Rotation.CONSECUTIVE;
private final List<ProxyData> proxyList = new ArrayList<>();
private int index = 0;
private boolean autoDiscard = true;
private int index = -1;
private Proxy.Type type = Proxy.Type.DIRECT;
private Consumer<ProxyData> onProxyUpdated = x -> {};
@@ -64,10 +63,6 @@ public class ProxyClientSettings implements Iterator<ProxyData>
@Override
public ProxyData next() {
return lastSuccess ? proxyList.get(index) : rotate();
}
public ProxyData rotate() {
var nextProxy = switch (rotation)
{
case CONSECUTIVE -> {
@@ -78,7 +73,10 @@ public class ProxyClientSettings implements Iterator<ProxyData>
index = new Random().nextInt(proxyList.size());
yield proxyList.get(index).clone();
}
case NONE -> proxyList.get(index).clone();
case NONE -> {
index = Math.max(index, 0);
yield proxyList.get(index).clone();
}
};
onProxyUpdated.accept(nextProxy);
return nextProxy;
@@ -87,7 +85,6 @@ public class ProxyClientSettings implements Iterator<ProxyData>
@Override
public void remove() {
proxyList.remove(index);
lastSuccess = false; // index is no longer valid and lastSuccess needs falsified
}
public void setIndex(int index) {
@@ -99,6 +96,7 @@ public class ProxyClientSettings implements Iterator<ProxyData>
this.index = index;
}
}
public ProxyClientSettings clone()
{
ProxyClientSettings settings = new ProxyClientSettings();

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

View File

@@ -27,38 +27,46 @@ 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
* @return {@link GiftsData.Response} list of gifts that are compiled and available on github
*/
GiftsData.Response fetchGiftsData();
/**
* Returns information about user that is having a livestream
*
* @param userName
* @return
* @return {@link GiftsData.Response} list of gifts that are available in your region / livestream
*/
LiveUserData.Response fetchLiveUserData(String userName);
GiftsData.Response fetchRoomGiftsData(String room_id);
/**
* Returns information about user that is having a livestream
* @param userName name of user
* @return {@link LiveUserData.Response}
*/
default LiveUserData.Response fetchLiveUserData(String userName) {
return fetchLiveUserData(new LiveUserData.Request(userName));
}
LiveUserData.Response fetchLiveUserData(LiveUserData.Request request);
/**
* @param roomId can be obtained from browsers cookies or by invoked fetchLiveUserData
* @return
* @return {@link LiveData.Response}
*/
LiveData.Response fetchLiveData(String roomId);
default LiveData.Response fetchLiveData(String roomId) {
return fetchLiveData(new LiveData.Request(roomId));
}
LiveData.Response fetchLiveData(LiveData.Request request);
/**
* @param roomId can be obtained from browsers cookies or by invoked fetchLiveUserData
* @return
* @return {@link LiveConnectionData.Response}
*/
LiveConnectionData.Response fetchLiveConnectionData(String roomId);
default LiveConnectionData.Response fetchLiveConnectionData(String roomId) {
return fetchLiveConnectionData(new LiveConnectionData.Request(roomId));
}
LiveConnectionData.Response fetchLiveConnectionData(LiveConnectionData.Request request);
}

View File

@@ -22,42 +22,65 @@
*/
package io.github.jwdeveloper.tiktok.live;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
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 GiftManager {
public interface GiftsManager {
/**
* In case you can't find your gift in Gift enum. You can register gift
* manually here to make it detected while TikTokGiftEvent
* You can create and attach your own custom gift to manager
*
* @param id gift's id
* @param name gift's name
* @param diamondCost diamond cost
* @return
* @param gift
*/
Gift registerGift(int id, String name, int diamondCost, Picture picture);
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);
/**
*
* @param giftId
* @return
* finds gift by filter
* When gift not found return Gift.UNDEFINED;
*/
Gift findById(int giftId);
Gift getByFilter(Predicate<Gift> filter);
List<Gift> getManyByFilter(Predicate<Gift> filter);
/**
*
* @param giftName
* @return
* @return list of all gifts
*/
Gift findByName(String giftName);
List<Gift> toList();
/**
*
* @return all gifts
* @return list of all map of all gifts where Integer is gift Id
*/
List<Gift> getGifts();
Map<Integer, Gift> toMap();
}

View File

@@ -61,10 +61,19 @@ public interface LiveClient {
*/
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);
/**
* Get information about gifts
*/
GiftManager getGiftManager();
GiftsManager 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 getHostUser();
User getHost();
List<RankingUser> getUsersRanking();
ConnectionState getConnectionState();
}

View File

@@ -24,6 +24,7 @@ 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;
@@ -149,6 +150,13 @@ 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
@@ -216,5 +224,3 @@ public interface EventsBuilder<T> {
//T onUnhandledControl(TikTokEventConsumer<TikTokUnhandledControlEvent> event);
}

View File

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

View File

@@ -35,6 +35,14 @@ 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;
int32 battleStatus = 7; // SHOULD BE AN ENUM
LinkMicBattleStatus battleStatus = 7;
uint64 data1 = 8;
uint64 data2 = 9;
uint32 data3 = 10;
@@ -574,11 +574,26 @@ message WebcastLinkMicBattle {
Common common = 1;
uint64 id = 2;
LinkMicBattleConfig battleConfig = 3;
uint32 data2 = 4;
LinkMicBattleStatus battleStatus = 4;
repeated LinkMicBattleDetails details = 5;
repeated LinkMicBattleTeam teams1 = 9;
repeated LinkMicBattleTeam teams2 = 10;
repeated LinkMicBattleTopViewers viewerTeam = 9;
repeated LinkMicBattleHost hostTeam = 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;
@@ -586,29 +601,69 @@ 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 data2 = 3;
uint32 winStreak = 3;
uint32 data3 = 5;
string url = 6;
}
message LinkMicBattleDetails {
uint64 id = 1;
LinkMicBattleData details = 2;
}
LinkMicBattleDetailsSummary summary = 2;
message LinkMicBattleTeam {
message LinkMicBattleDetailsSummary {
uint64 id = 1;
repeated User users = 2;
uint32 unknownData2 = 2;
uint32 points = 3;
}
}
message LinkMicBattleTeamData {
uint64 teamId = 1;
LinkMicBattleData data = 2;
message LinkMicBattleTopViewers {
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;
}
}
}
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;
}
}
}
}
@@ -793,5 +848,3 @@ message RoomVerifyMessage {
int64 noticeType = 4;
bool closeRoom = 5;
}

View File

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

View File

@@ -23,7 +23,9 @@
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;
@@ -31,8 +33,9 @@ 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) {
@@ -40,46 +43,42 @@ 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 true if live is Online, false if is offline
*/
public static boolean isLiveOnline(String hostName)
{
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
* example: https://www.tiktok.com/@dostawcavideo hostName would be dostawcavideo
* @return true if live is Online, false if is offline
*/
public static CompletableFuture<Boolean> isLiveOnlineAsync(String hostName)
{
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
* example: https://www.tiktok.com/@dostawcavideo hostName would be dostawcavideo
* @return true is hostName name is valid and exists, false if not
*/
public static boolean isHostNameValid(String hostName)
{
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
* example: https://www.tiktok.com/@dostawcavideo hostName would be dostawcavideo
* @return true is hostName name is valid and exists, false if not
*/
public static CompletableFuture<Boolean> isHostNameValidAsync(String hostName)
{
public static CompletableFuture<Boolean> isHostNameValidAsync(String hostName) {
return CompletableFuture.supplyAsync(() -> isHostNameValid(hostName));
}
@@ -91,4 +90,21 @@ public class TikTokLive {
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,52 +22,51 @@
*/
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.data.events.TikTokDisconnectedEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent;
import io.github.jwdeveloper.tiktok.data.events.TikTokReconnectingEvent;
import com.google.protobuf.ByteString;
import io.github.jwdeveloper.tiktok.data.events.*;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.control.TikTokConnectingEvent;
import io.github.jwdeveloper.tiktok.data.events.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.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.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.models.ConnectionState;
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 TikTokGiftManager tikTokGiftManager;
private final TikTokLiveHttpClient httpClient;
private final LiveHttpClient 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(TikTokRoomInfo tikTokLiveMeta,
TikTokLiveHttpClient tiktokHttpClient,
public TikTokLiveClient(
TikTokLiveMessageHandler messageHandler,
GiftsManager giftsManager,
TikTokRoomInfo tikTokLiveMeta,
LiveHttpClient tiktokHttpClient,
SocketClient webSocketClient,
TikTokGiftManager tikTokGiftManager,
TikTokLiveEventHandler tikTokEventHandler,
LiveClientSettings clientSettings,
TikTokListenersManager listenersManager,
Logger logger) {
this.messageHandler = messageHandler;
this.giftsManager = giftsManager;
this.liveRoomInfo = tikTokLiveMeta;
this.tikTokGiftManager = tikTokGiftManager;
this.httpClient = tiktokHttpClient;
this.webSocketClient = webSocketClient;
this.tikTokEventHandler = tikTokEventHandler;
@@ -103,7 +102,8 @@ public class TikTokLiveClient implements LiveClient {
if (e instanceof TikTokLiveOfflineHostException && clientSettings.isRetryOnConnectionFailure()) {
try {
Thread.sleep(clientSettings.getRetryConnectionTimeout().toMillis());
} catch (Exception ignored) {}
} catch (Exception ignored) {
}
logger.info("Reconnecting");
tikTokEventHandler.publish(this, new TikTokReconnectingEvent());
this.connect();
@@ -127,22 +127,29 @@ public class TikTokLiveClient implements LiveClient {
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 (clientSettings.isFetchGifts())
giftsManager.attachGiftsList(httpClient.fetchRoomGiftsData(userData.getRoomId()).getGifts());
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());
@@ -150,6 +157,11 @@ 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 liveConnectionData = httpClient.fetchLiveConnectionData(liveConnectionRequest);
webSocketClient.start(liveConnectionData, this);
@@ -175,6 +187,25 @@ 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;
}
@@ -188,9 +219,4 @@ public class TikTokLiveClient implements LiveClient {
public Logger getLogger() {
return logger;
}
@Override
public GiftManager getGiftManager() {
return tikTokGiftManager;
}
}

View File

@@ -22,65 +22,49 @@
*/
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.TikTokGiftComboEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.*;
import io.github.jwdeveloper.tiktok.data.events.http.TikTokHttpResponseEvent;
import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollEvent;
import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomInfoEvent;
import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomPinEvent;
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.TikTokGiftManager;
import io.github.jwdeveloper.tiktok.http.HttpClientFactory;
import io.github.jwdeveloper.tiktok.listener.TikTokEventListener;
import io.github.jwdeveloper.tiktok.listener.TikTokListenersManager;
import io.github.jwdeveloper.tiktok.live.GiftManager;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.live.builder.EventConsumer;
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
import io.github.jwdeveloper.tiktok.mappers.TikTokGenericEventMapper;
import io.github.jwdeveloper.tiktok.mappers.TikTokLiveMapper;
import io.github.jwdeveloper.tiktok.mappers.TikTokLiveMapperHelper;
import io.github.jwdeveloper.tiktok.mappers.TikTokMapper;
import io.github.jwdeveloper.tiktok.mappers.data.MappingResult;
import io.github.jwdeveloper.tiktok.mappers.handlers.TikTokCommonEventHandler;
import io.github.jwdeveloper.tiktok.mappers.handlers.TikTokGiftEventHandler;
import io.github.jwdeveloper.tiktok.mappers.handlers.TikTokRoomInfoEventHandler;
import io.github.jwdeveloper.tiktok.mappers.handlers.TikTokSocialMediaEventHandler;
import io.github.jwdeveloper.tiktok.messages.webcast.*;
import io.github.jwdeveloper.tiktok.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.utils.ConsoleColors;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftsManager;
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.mappers.data.MappingResult;
import io.github.jwdeveloper.tiktok.mappers.handlers.*;
import io.github.jwdeveloper.tiktok.messages.webcast.*;
import io.github.jwdeveloper.tiktok.websocket.TikTokWebSocketClient;
import io.github.jwdeveloper.tiktok.websocket.TikTokWebSocketOfflineClient;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.logging.*;
import java.util.logging.Logger;
public class TikTokLiveClientBuilder implements LiveClientBuilder {
protected final LiveClientSettings clientSettings;
protected final Logger logger;
protected final TikTokLiveEventHandler tikTokEventHandler;
protected final TikTokLiveEventHandler eventHandler;
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.tikTokEventHandler = new TikTokLiveEventHandler();
this.logger = Logger.getLogger(TikTokLive.class.getSimpleName() + " " + userName);
this.eventHandler = new TikTokLiveEventHandler();
this.listeners = new ArrayList<>();
this.onCustomMappings = (e) -> {
};
@@ -91,55 +75,36 @@ 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);
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());
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);
}
this.logger = LoggerFactory.create(clientSettings.getHostName(), clientSettings);
this.giftsManager = clientSettings.isFetchGifts() ? TikTokLive.gifts() : new TikTokGiftsManager(List.of());
}
public LiveClient build() {
@@ -148,35 +113,37 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
var tiktokRoomInfo = new TikTokRoomInfo();
tiktokRoomInfo.setHostName(clientSettings.getHostName());
var listenerManager = new TikTokListenersManager(listeners, tikTokEventHandler);
var giftManager = new TikTokGiftManager(logger);
var eventsMapper = createMapper(giftManager, tiktokRoomInfo);
var messageHandler = new TikTokLiveMessageHandler(tikTokEventHandler, eventsMapper);
var listenerManager = new TikTokListenersManager(listeners, eventHandler);
var httpClientFactory = new HttpClientFactory(clientSettings);
var tikTokLiveHttpClient = new TikTokLiveHttpClient(httpClientFactory, clientSettings);
var webSocketClient = new TikTokWebSocketClient(
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,
tikTokEventHandler);
eventHandler);
return new TikTokLiveClient(tiktokRoomInfo,
tikTokLiveHttpClient,
return new TikTokLiveClient(
messageHandler,
giftsManager,
tiktokRoomInfo,
liveHttpClient,
webSocketClient,
giftManager,
tikTokEventHandler,
eventHandler,
clientSettings,
listenerManager,
logger);
}
public TikTokLiveMapper createMapper(GiftManager giftManager, TikTokRoomInfo roomInfo) {
/*
//
*/
public TikTokLiveMapper createMapper(GiftsManager giftsManager, TikTokRoomInfo roomInfo) {
var eventMapper = new TikTokGenericEventMapper();
@@ -184,7 +151,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
//ConnectionEvents events
var commonHandler = new TikTokCommonEventHandler();
var giftHandler = new TikTokGiftEventHandler(giftManager, roomInfo);
var giftHandler = new TikTokGiftEventHandler(giftsManager, roomInfo);
var roomInfoHandler = new TikTokRoomInfoEventHandler(roomInfo);
var socialHandler = new TikTokSocialMediaEventHandler(roomInfo);
@@ -240,8 +207,14 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
//LinkMic events
// mapper.webcastObjectToConstructor(WebcastLinkMicBattle.class, TikTokLinkMicBattleEvent.class);
// mapper.webcastObjectToConstructor(WebcastLinkMicArmies.class, TikTokLinkMicArmiesEvent.class);
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(WebcastLinkMicFanTicketMethod.class, TikTokLinkMicFanTicketEvent.class);
@@ -275,273 +248,256 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
return build().connectAsync();
}
public TikTokLiveClientBuilder onUnhandledSocial(
EventConsumer<TikTokUnhandledSocialEvent> event) {
tikTokEventHandler.subscribe(TikTokUnhandledSocialEvent.class, event);
public TikTokLiveClientBuilder onUnhandledSocial(EventConsumer<TikTokUnhandledSocialEvent> event) {
eventHandler.subscribe(TikTokUnhandledSocialEvent.class, event);
return this;
}
// @Override
public LiveClientBuilder onChest(EventConsumer<TikTokChestEvent> event) {
tikTokEventHandler.subscribe(TikTokChestEvent.class, event);
eventHandler.subscribe(TikTokChestEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLinkMicFanTicket(
EventConsumer<TikTokLinkMicFanTicketEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkMicFanTicketEvent.class, event);
public TikTokLiveClientBuilder onLinkMicFanTicket(EventConsumer<TikTokLinkMicFanTicketEvent> event) {
eventHandler.subscribe(TikTokLinkMicFanTicketEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onEnvelope(EventConsumer<TikTokEnvelopeEvent> event) {
tikTokEventHandler.subscribe(TikTokEnvelopeEvent.class, event);
eventHandler.subscribe(TikTokEnvelopeEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onShop(EventConsumer<TikTokShopEvent> event) {
tikTokEventHandler.subscribe(TikTokShopEvent.class, event);
eventHandler.subscribe(TikTokShopEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onDetect(
EventConsumer<TikTokDetectEvent> event) {
tikTokEventHandler.subscribe(TikTokDetectEvent.class, event);
public TikTokLiveClientBuilder onDetect(EventConsumer<TikTokDetectEvent> event) {
eventHandler.subscribe(TikTokDetectEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLinkLayer(
EventConsumer<TikTokLinkLayerEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkLayerEvent.class, event);
public TikTokLiveClientBuilder onLinkLayer(EventConsumer<TikTokLinkLayerEvent> event) {
eventHandler.subscribe(TikTokLinkLayerEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onConnected(EventConsumer<TikTokConnectedEvent> event) {
tikTokEventHandler.subscribe(TikTokConnectedEvent.class, event);
eventHandler.subscribe(TikTokConnectedEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onPreConnection(EventConsumer<TikTokPreConnectionEvent> event) {
eventHandler.subscribe(TikTokPreConnectionEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onCaption(EventConsumer<TikTokCaptionEvent> event) {
tikTokEventHandler.subscribe(TikTokCaptionEvent.class, event);
eventHandler.subscribe(TikTokCaptionEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onQuestion(EventConsumer<TikTokQuestionEvent> event) {
tikTokEventHandler.subscribe(TikTokQuestionEvent.class, event);
eventHandler.subscribe(TikTokQuestionEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onRoomPin(
EventConsumer<TikTokRoomPinEvent> event) {
tikTokEventHandler.subscribe(TikTokRoomPinEvent.class, event);
public TikTokLiveClientBuilder onRoomPin(EventConsumer<TikTokRoomPinEvent> event) {
eventHandler.subscribe(TikTokRoomPinEvent.class, event);
return this;
}
@Override
public <E extends TikTokEvent> LiveClientBuilder onEvent(Class<E> eventClass, EventConsumer<E> event) {
tikTokEventHandler.subscribe(eventClass, event);
eventHandler.subscribe(eventClass, event);
return this;
}
@Override
public TikTokLiveClientBuilder onRoomInfo(EventConsumer<TikTokRoomInfoEvent> event) {
tikTokEventHandler.subscribe(TikTokRoomInfoEvent.class, event);
eventHandler.subscribe(TikTokRoomInfoEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLivePaused(EventConsumer<TikTokLivePausedEvent> event) {
tikTokEventHandler.subscribe(TikTokLivePausedEvent.class, event);
eventHandler.subscribe(TikTokLivePausedEvent.class, event);
return this;
}
@Override
public TikTokLiveClientBuilder onLiveUnpaused(EventConsumer<TikTokLiveUnpausedEvent> event) {
tikTokEventHandler.subscribe(TikTokLiveUnpausedEvent.class, event);
eventHandler.subscribe(TikTokLiveUnpausedEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLike(EventConsumer<TikTokLikeEvent> event) {
tikTokEventHandler.subscribe(TikTokLikeEvent.class, event);
eventHandler.subscribe(TikTokLikeEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLink(EventConsumer<TikTokLinkEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkEvent.class, event);
eventHandler.subscribe(TikTokLinkEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onBarrage(
EventConsumer<TikTokBarrageEvent> event) {
tikTokEventHandler.subscribe(TikTokBarrageEvent.class, event);
public TikTokLiveClientBuilder onBarrage(EventConsumer<TikTokBarrageEvent> event) {
eventHandler.subscribe(TikTokBarrageEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onGift(EventConsumer<TikTokGiftEvent> event) {
tikTokEventHandler.subscribe(TikTokGiftEvent.class, event);
eventHandler.subscribe(TikTokGiftEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onGiftCombo(EventConsumer<TikTokGiftComboEvent> event) {
tikTokEventHandler.subscribe(TikTokGiftComboEvent.class, event);
eventHandler.subscribe(TikTokGiftComboEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLinkMicArmies(
EventConsumer<TikTokLinkMicArmiesEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkMicArmiesEvent.class, event);
public TikTokLiveClientBuilder onLinkMicArmies(EventConsumer<TikTokLinkMicArmiesEvent> event) {
eventHandler.subscribe(TikTokLinkMicArmiesEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onEmote(EventConsumer<TikTokEmoteEvent> event) {
tikTokEventHandler.subscribe(TikTokEmoteEvent.class, event);
eventHandler.subscribe(TikTokEmoteEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onUnauthorizedMember(
EventConsumer<TikTokUnauthorizedMemberEvent> event) {
tikTokEventHandler.subscribe(TikTokUnauthorizedMemberEvent.class, event);
public TikTokLiveClientBuilder onUnauthorizedMember(EventConsumer<TikTokUnauthorizedMemberEvent> event) {
eventHandler.subscribe(TikTokUnauthorizedMemberEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onInRoomBanner(
EventConsumer<TikTokInRoomBannerEvent> event) {
tikTokEventHandler.subscribe(TikTokInRoomBannerEvent.class, event);
public TikTokLiveClientBuilder onInRoomBanner(EventConsumer<TikTokInRoomBannerEvent> event) {
eventHandler.subscribe(TikTokInRoomBannerEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLinkMicMethod(
EventConsumer<TikTokLinkMicMethodEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkMicMethodEvent.class, event);
public TikTokLiveClientBuilder onLinkMicMethod(EventConsumer<TikTokLinkMicMethodEvent> event) {
eventHandler.subscribe(TikTokLinkMicMethodEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onSubscribe(EventConsumer<TikTokSubscribeEvent> event) {
tikTokEventHandler.subscribe(TikTokSubscribeEvent.class, event);
eventHandler.subscribe(TikTokSubscribeEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onPoll(EventConsumer<TikTokPollEvent> event) {
tikTokEventHandler.subscribe(TikTokPollEvent.class, event);
eventHandler.subscribe(TikTokPollEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onFollow(EventConsumer<TikTokFollowEvent> event) {
tikTokEventHandler.subscribe(TikTokFollowEvent.class, event);
eventHandler.subscribe(TikTokFollowEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onComment(EventConsumer<TikTokCommentEvent> event) {
tikTokEventHandler.subscribe(TikTokCommentEvent.class, event);
eventHandler.subscribe(TikTokCommentEvent.class, event);
return this;
}
@Override
public LiveClientBuilder onHttpResponse(EventConsumer<TikTokHttpResponseEvent> action) {
tikTokEventHandler.subscribe(TikTokHttpResponseEvent.class, action);
eventHandler.subscribe(TikTokHttpResponseEvent.class, action);
return this;
}
public TikTokLiveClientBuilder onGoalUpdate(EventConsumer<TikTokGoalUpdateEvent> event) {
tikTokEventHandler.subscribe(TikTokGoalUpdateEvent.class, event);
eventHandler.subscribe(TikTokGoalUpdateEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onRankUpdate(EventConsumer<TikTokRankUpdateEvent> event) {
tikTokEventHandler.subscribe(TikTokRankUpdateEvent.class, event);
eventHandler.subscribe(TikTokRankUpdateEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onIMDelete(EventConsumer<TikTokIMDeleteEvent> event) {
tikTokEventHandler.subscribe(TikTokIMDeleteEvent.class, event);
eventHandler.subscribe(TikTokIMDeleteEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLiveEnded(EventConsumer<TikTokLiveEndedEvent> event) {
tikTokEventHandler.subscribe(TikTokLiveEndedEvent.class, event);
eventHandler.subscribe(TikTokLiveEndedEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onError(EventConsumer<TikTokErrorEvent> event) {
tikTokEventHandler.subscribe(TikTokErrorEvent.class, event);
eventHandler.subscribe(TikTokErrorEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onJoin(EventConsumer<TikTokJoinEvent> event) {
tikTokEventHandler.subscribe(TikTokJoinEvent.class, event);
eventHandler.subscribe(TikTokJoinEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onRankText(EventConsumer<TikTokRankTextEvent> event) {
tikTokEventHandler.subscribe(TikTokRankTextEvent.class, event);
eventHandler.subscribe(TikTokRankTextEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onShare(EventConsumer<TikTokShareEvent> event) {
tikTokEventHandler.subscribe(TikTokShareEvent.class, event);
eventHandler.subscribe(TikTokShareEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onUnhandledMember(
EventConsumer<TikTokUnhandledMemberEvent> event) {
tikTokEventHandler.subscribe(TikTokUnhandledMemberEvent.class, event);
public TikTokLiveClientBuilder onUnhandledMember(EventConsumer<TikTokUnhandledMemberEvent> event) {
eventHandler.subscribe(TikTokUnhandledMemberEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onSubNotify(EventConsumer<TikTokSubNotifyEvent> event) {
tikTokEventHandler.subscribe(TikTokSubNotifyEvent.class, event);
eventHandler.subscribe(TikTokSubNotifyEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLinkMicBattle(
EventConsumer<TikTokLinkMicBattleEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkMicBattleEvent.class, event);
public TikTokLiveClientBuilder onLinkMicBattle(EventConsumer<TikTokLinkMicBattleEvent> event) {
eventHandler.subscribe(TikTokLinkMicBattleEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onDisconnected(
EventConsumer<TikTokDisconnectedEvent> event) {
tikTokEventHandler.subscribe(TikTokDisconnectedEvent.class, event);
public TikTokLiveClientBuilder onDisconnected(EventConsumer<TikTokDisconnectedEvent> event) {
eventHandler.subscribe(TikTokDisconnectedEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onUnhandledControl(
EventConsumer<TikTokUnhandledControlEvent> event) {
tikTokEventHandler.subscribe(TikTokUnhandledControlEvent.class, event);
public TikTokLiveClientBuilder onUnhandledControl(EventConsumer<TikTokUnhandledControlEvent> event) {
eventHandler.subscribe(TikTokUnhandledControlEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onEvent(EventConsumer<TikTokEvent> event) {
tikTokEventHandler.subscribe(TikTokEvent.class, event);
eventHandler.subscribe(TikTokEvent.class, event);
return this;
}
@Override
public TikTokLiveClientBuilder onWebsocketResponse(EventConsumer<TikTokWebsocketResponseEvent> event) {
tikTokEventHandler.subscribe(TikTokWebsocketResponseEvent.class, event);
eventHandler.subscribe(TikTokWebsocketResponseEvent.class, event);
return this;
}
@Override
public TikTokLiveClientBuilder onWebsocketMessage(EventConsumer<TikTokWebsocketMessageEvent> event) {
tikTokEventHandler.subscribe(TikTokWebsocketMessageEvent.class, event);
eventHandler.subscribe(TikTokWebsocketMessageEvent.class, event);
return this;
}
@Override
public TikTokLiveClientBuilder onWebsocketUnhandledMessage(EventConsumer<TikTokWebsocketUnhandledMessageEvent> event) {
tikTokEventHandler.subscribe(TikTokWebsocketUnhandledMessageEvent.class, event);
eventHandler.subscribe(TikTokWebsocketUnhandledMessageEvent.class, event);
return this;
}
@Override
public TikTokLiveClientBuilder onReconnecting(EventConsumer<TikTokReconnectingEvent> event) {
tikTokEventHandler.subscribe(TikTokReconnectingEvent.class, event);
eventHandler.subscribe(TikTokReconnectingEvent.class, event);
return this;
}
}

View File

@@ -23,39 +23,42 @@
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.*;
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.messages.webcast.WebcastResponse;
import java.net.http.HttpResponse;
import java.util.Optional;
public class TikTokLiveHttpClient implements LiveHttpClient {
import java.util.logging.Logger;
public class TikTokLiveHttpClient implements LiveHttpClient
{
/**
* Signing API by Isaac Kogan
* https://github-wiki-see.page/m/isaackogan/TikTokLive/wiki/All-About-Signatures
* <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/sign_url";
private static final String TIKTOK_SIGN_API = "https://tiktok.eulerstream.com/webcast/fetch";
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 String TIKTOK_ROOM_GIFTS_URL = TIKTOK_URL_WEBCAST+"gift/list/";
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 signServerResponseMapper;
private final GiftsDataMapper giftsDataMapper;
private final Logger logger;
public TikTokLiveHttpClient(HttpClientFactory factory, LiveClientSettings settings) {
this.httpFactory = factory;
clientSettings = settings;
this.clientSettings = settings;
this.logger = LoggerFactory.create("HttpClient", clientSettings);
liveUserDataMapper = new LiveUserDataMapper();
liveDataMapper = new LiveDataMapper();
signServerResponseMapper = new SignServerResponseMapper();
giftsDataMapper = new GiftsDataMapper();
}
@@ -63,66 +66,81 @@ public class TikTokLiveHttpClient implements LiveHttpClient {
this(new HttpClientFactory(LiveClientSettings.createDefault()), LiveClientSettings.createDefault());
}
public GiftsData.Response fetchRoomGiftsData(String room_id) {
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) {
try {
return getRoomGiftsData(room_id);
} catch (TikTokProxyRequestException ignored) {}
}
}
return getRoomGiftsData(room_id);
}
public GiftsData.Response fetchGiftsData() {
var url = TIKTOK_URL_WEBCAST + "gift/list/";
var optional = httpFactory.client(url)
public GiftsData.Response getRoomGiftsData(String room_id) {
var result = httpFactory.client(TIKTOK_ROOM_GIFTS_URL)
.withParam("room_id", room_id)
.build()
.toJsonResponse();
if (optional.isEmpty()) {
throw new TikTokLiveRequestException("Unable to fetch gifts information's");
if (result.isFailure())
throw new TikTokLiveRequestException("Unable to fetch gifts information's - "+result);
var json = result.getContent();
return giftsDataMapper.mapRoom(json);
}
var json = optional.get();
public GiftsData.Response fetchGiftsData() {
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) {
try {
return getGiftsData();
} catch (TikTokProxyRequestException ignored) {}
}
}
return getGiftsData();
}
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 {
var url = TIKTOK_URL_WEB + "api-live/user/room";
var optional = httpFactory.client(url)
.withParam("uniqueId", request.getUserName())
.withParam("sourceType", "54")
.build()
.toJsonResponse();
if (optional.isEmpty()) {
throw new TikTokLiveRequestException("Unable to get information's about user");
}
var json = optional.get();
return liveUserDataMapper.map(json);
return getLiveUserData(request);
} catch (TikTokProxyRequestException ignored) {}
}
}
return getLiveUserData(request);
}
public LiveUserData.Response getLiveUserData(LiveUserData.Request request) {
var url = TIKTOK_URL_WEB + "api-live/user/room";
var optional = httpFactory.client(url)
var result = 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");
}
if (result.isFailure())
throw new TikTokLiveRequestException("Unable to get information's about user - "+result);
var json = optional.get();
return liveUserDataMapper.map(json);
}
@Override
public LiveData.Response fetchLiveData(String roomId) {
return fetchLiveData(new LiveData.Request(roomId));
var json = result.getContent();
return liveUserDataMapper.map(json, logger);
}
@Override
@@ -131,53 +149,39 @@ public class TikTokLiveHttpClient implements LiveHttpClient {
if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) {
try {
var url = TIKTOK_URL_WEBCAST + "room/info";
var optional = httpFactory.client(url)
.withParam("room_id", request.getRoomId())
.build()
.toJsonResponse();
if (optional.isEmpty()) {
throw new TikTokLiveRequestException("Unable to get info about live room");
}
var json = optional.get();
return liveDataMapper.map(json);
return getLiveData(request);
} catch (TikTokProxyRequestException ignored) {}
}
}
return getLiveData(request);
}
public LiveData.Response getLiveData(LiveData.Request request) {
var url = TIKTOK_URL_WEBCAST + "room/info";
var optional = httpFactory.client(url)
var result = httpFactory.client(url)
.withParam("room_id", request.getRoomId())
.build()
.toJsonResponse();
if (optional.isEmpty()) {
throw new TikTokLiveRequestException("Unable to get info about live room");
}
if (result.isFailure())
throw new TikTokLiveRequestException("Unable to get info about live room - "+result);
var json = optional.get();
var json = result.getContent();
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) {
HttpResponse<byte[]> credentialsResponse = getOptionalProxyResponse(request).orElseGet(()-> {
SignServerResponse signServerResponse = getSignedUrl(request.getRoomId());
return getWebsocketCredentialsResponse(signServerResponse.getSignedUrl());
});
var result = getStartingPayload(request);
HttpResponse<byte[]> credentialsResponse = result.getContent(); // Always guaranteed to have response
try {
var optionalHeader = credentialsResponse.headers().firstValue("set-cookie");
if (optionalHeader.isEmpty()) {
throw new TikTokSignServerException("Sign server did not return the set-cookie header");
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 websocketCookie = optionalHeader.get();
var websocketCookie = resultHeader.getContent();
var webcastResponse = WebcastResponse.parseFrom(credentialsResponse.body());
var webSocketUrl = httpFactory
.client(webcastResponse.getPushServer())
@@ -191,57 +195,36 @@ 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");
throw new TikTokSignServerException("Unable to parse websocket credentials response to WebcastResponse - "+result);
}
}
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);
}
var json = optional.get();
return signServerResponseMapper.map(json);
}
HttpResponse<byte[]> getWebsocketCredentialsResponse(String signedUrl) {
var optionalResponse = httpFactory
.clientEmpty(signedUrl)
.build()
.toResponse();
if (optionalResponse.isEmpty()) {
throw new TikTokSignServerException("Unable to get websocket connection credentials");
}
return optionalResponse.get();
}
Optional<HttpResponse<byte[]>> getOptionalProxyResponse(LiveConnectionData.Request request) {
private ActionResult<HttpResponse<byte[]>> getStartingPayload(LiveConnectionData.Request request) {
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) {
try {
SignServerResponse signServerResponse = getSignedUrl(request.getRoomId());
HttpResponse<byte[]> credentialsResponse = getWebsocketCredentialsResponse(signServerResponse.getSignedUrl());
clientSettings.getHttpSettings().getProxyClientSettings().rotate();
return Optional.of(credentialsResponse);
return getByteResponse(request.getRoomId());
} catch (TikTokProxyRequestException | TikTokSignServerException ignored) {}
}
}
return Optional.empty();
return getByteResponse(request.getRoomId());
}
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;
}
}

View File

@@ -0,0 +1,50 @@
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 GiftsData.Response fetchRoomGiftsData(String room_id) {
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,11 +61,6 @@ 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

@@ -0,0 +1,113 @@
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

@@ -0,0 +1,32 @@
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

@@ -0,0 +1,34 @@
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

@@ -1,103 +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.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

@@ -0,0 +1,57 @@
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.*;
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, Function.identity()));
}
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,6 +22,7 @@
*/
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;
@@ -35,35 +36,25 @@ import java.util.stream.Collectors;
@AllArgsConstructor
public class HttpClient {
protected final HttpClientSettings httpClientSettings;
protected final String url;
private final Pattern pattern = Pattern.compile("charset=(.*?)(?=&|$)");
public Optional<HttpResponse<byte[]>> toResponse() {
public ActionResult<HttpResponse<byte[]>> toResponse() {
var client = prepareClient();
var request = prepareGetRequest();
try {
var response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
if (response.statusCode() != 200) {
return Optional.empty();
}
return Optional.of(response);
var result = ActionResult.of(response);
return response.statusCode() != 200 ? result.message("HttpResponse Code: ", response.statusCode()).failure() : result.success();
} catch (Exception e) {
throw new TikTokLiveRequestException(e);
}
}
public Optional<String> toJsonResponse() {
var optional = toResponse();
if (optional.isEmpty()) {
return Optional.empty();
}
var response = optional.get();
var body = response.body();
var charset = charsetFrom(response.headers());
return Optional.of(new String(body,charset));
public ActionResult<String> toJsonResponse() {
return toResponse().map(content -> new String(content.body(), charsetFrom(content.headers())));
}
private Charset charsetFrom(HttpHeaders headers) {
@@ -80,13 +71,8 @@ public class HttpClient {
}
}
public Optional<byte[]> toBinaryResponse() {
var optional = toResponse();
if (optional.isEmpty()) {
return Optional.empty();
}
var body = optional.get().body();
return Optional.of(body);
public ActionResult<byte[]> toBinaryResponse() {
return toResponse().map(HttpResponse::body);
}
public URI toUrl() {

View File

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

View File

@@ -22,6 +22,7 @@
*/
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.*;
@@ -35,8 +36,8 @@ import java.security.cert.X509Certificate;
import java.util.*;
import java.util.stream.Collectors;
public class HttpProxyClient extends HttpClient
{
public class HttpProxyClient extends HttpClient {
private final ProxyClientSettings proxySettings;
public HttpProxyClient(HttpClientSettings httpClientSettings, String url) {
@@ -44,14 +45,14 @@ public class HttpProxyClient extends HttpClient
this.proxySettings = httpClientSettings.getProxyClientSettings();
}
public Optional<HttpResponse<byte[]>> toResponse() {
public ActionResult<HttpResponse<byte[]>> toResponse() {
return switch (proxySettings.getType()) {
case HTTP, DIRECT -> handleHttpProxyRequest();
default -> handleSocksProxyRequest();
};
}
public Optional<HttpResponse<byte[]>> handleHttpProxyRequest() {
public ActionResult<HttpResponse<byte[]>> handleHttpProxyRequest() {
var builder = java.net.http.HttpClient.newBuilder()
.followRedirects(java.net.http.HttpClient.Redirect.NORMAL)
.cookieHandler(new CookieManager())
@@ -67,16 +68,17 @@ public class HttpProxyClient extends HttpClient
var request = prepareGetRequest();
var response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
if (response.statusCode() != 200) {
proxySettings.setLastSuccess(false);
if (response.statusCode() != 200)
continue;
}
proxySettings.setLastSuccess(true);
return Optional.of(response);
return ActionResult.success(response);
} catch (HttpConnectTimeoutException | ConnectException e) {
if (proxySettings.isAutoDiscard())
proxySettings.remove();
proxySettings.setLastSuccess(false);
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);
}
@@ -84,7 +86,7 @@ public class HttpProxyClient extends HttpClient
throw new TikTokLiveRequestException("No more proxies available!");
}
private Optional<HttpResponse<byte[]>> handleSocksProxyRequest() {
private ActionResult<HttpResponse<byte[]>> handleSocksProxyRequest() {
try {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[]{ new X509TrustManager() {
@@ -116,12 +118,12 @@ public class HttpProxyClient extends HttpClient
var response = createHttpResponse(body, toUrl(), responseInfo);
proxySettings.setLastSuccess(true);
return Optional.of(response);
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();
proxySettings.setLastSuccess(false);
throw new TikTokProxyRequestException(e);
} catch (Exception e) {
throw new TikTokLiveRequestException(e);
@@ -130,13 +132,12 @@ public class HttpProxyClient extends HttpClient
throw new TikTokLiveRequestException("No more proxies available!");
} catch (NoSuchAlgorithmException | MalformedURLException | KeyManagementException e) {
// Should never be reached!
System.out.println("handleSocksProxyRequest()! If you see this message, reach us on discord!");
System.out.println("handleSocksProxyRequest: If you see this, message us on discord!");
e.printStackTrace();
return Optional.empty();
} catch (TikTokLiveRequestException e) {
e.printStackTrace();
return Optional.empty();
}
return ActionResult.failure();
}
private ResponseInfo createResponseInfo(int code, Map<String, List<String>> headers) {

View File

@@ -21,51 +21,64 @@
* 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 com.google.gson.*;
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;
import java.util.List;
public class GiftsDataMapper {
public GiftsData.Response map(String json) {
var parsedJson = JsonParser.parseString(json);
var jsonObject = parsedJson.getAsJsonObject();
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)
var gifts = jsonObject.entrySet()
.parallelStream()
.map(e -> mapSingleGift(e.getValue()))
.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();
if (image.endsWith(".webp")) {
image = image.replace(".webp", ".jpg");
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);
}
var gift = new GiftsData.GiftModel();
gift.setId(id);
gift.setName(name);
gift.setDiamondCost(diamondCost);
gift.setImage(image);
return gift;
public GiftsData.Response mapRoom(String json) {
var parsedJson = JsonParser.parseString(json);
var jsonObject = parsedJson.getAsJsonObject();
if (jsonObject.get("data") instanceof JsonObject data && data.get("gifts") instanceof JsonArray giftArray) {
var gifts = giftArray.asList().parallelStream()
.map(this::mapSingleRoomGift)
.toList();
return new GiftsData.Response(json, gifts);
}
return new GiftsData.Response("", List.of());
}
private Gift mapSingleRoomGift(JsonElement jsonElement) {
var jsonObject = jsonElement.getAsJsonObject();
var id = jsonObject.get("id").getAsInt();
var name = jsonObject.get("name").getAsString();
var diamondCost = jsonObject.get("diamond_count").getAsInt();
Picture picture;
if (jsonObject.get("image") instanceof JsonObject image && image.get("url_list") instanceof JsonArray urls && !urls.isEmpty()) {
String url = urls.get(0).getAsString();
if (url.endsWith(".webp"))
url = url.substring(0, url.length()-4)+"png";
picture = new Picture(url);
} else
picture = Picture.empty();
return new Gift(id, name, diamondCost, picture, jsonObject);
}
}

View File

@@ -0,0 +1,21 @@
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

@@ -0,0 +1,21 @@
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,6 +24,7 @@ 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;
@@ -64,6 +65,8 @@ 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);
}
@@ -105,6 +108,22 @@ 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;
}

View File

@@ -22,13 +22,16 @@
*/
package io.github.jwdeveloper.tiktok.http.mappers;
import com.google.gson.JsonParser;
import com.google.gson.*;
import io.github.jwdeveloper.tiktok.data.requests.LiveUserData;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import java.util.logging.Logger;
public class LiveUserDataMapper
{
public LiveUserData.Response map(String json) {
public LiveUserData.Response map(String json, Logger logger) {
try {
var jsonObject = JsonParser.parseString(json).getAsJsonObject();
var message = jsonObject.get("message").getAsString();
@@ -62,5 +65,9 @@ public class LiveUserDataMapper
};
return new LiveUserData.Response(json, statusEnum, roomId, startTime);
} catch (JsonSyntaxException | IllegalStateException e) {
logger.warning("Malformed Json: '"+json+"' - Error Message: "+e.getMessage());
return new LiveUserData.Response(json, LiveUserData.UserStatus.NotFound, "", -1);
}
}
}

View File

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

@@ -24,32 +24,27 @@ package io.github.jwdeveloper.tiktok.mappers.handlers;
import io.github.jwdeveloper.tiktok.TikTokRoomInfo;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftComboEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.*;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.data.models.gifts.GiftSendType;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.live.GiftManager;
import io.github.jwdeveloper.tiktok.data.models.gifts.*;
import io.github.jwdeveloper.tiktok.live.GiftsManager;
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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
public class TikTokGiftEventHandler {
private final GiftManager giftManager;
private final Map<Long, WebcastGiftMessage> giftsMessages;
private final TikTokRoomInfo tikTokRoomInfo;
public TikTokGiftEventHandler(GiftManager giftManager, TikTokRoomInfo tikTokRoomInfo) {
this.giftManager = giftManager;
private final GiftsManager giftsManager;
public TikTokGiftEventHandler(GiftsManager giftsManager, TikTokRoomInfo tikTokRoomInfo) {
giftsMessages = new HashMap<>();
this.tikTokRoomInfo = tikTokRoomInfo;
this.giftsManager = giftsManager;
}
@SneakyThrows
@@ -61,40 +56,40 @@ public class TikTokGiftEventHandler {
public List<TikTokEvent> handleGift(WebcastGiftMessage currentMessage) {
var userId = currentMessage.getUser().getId();
var currentType = GiftSendType.fromNumber(currentMessage.getSendType());
var currentType = GiftComboStateType.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, GiftSendType.Finished);
var comboEvent = getGiftComboEvent(currentMessage, GiftComboStateType.Finished);
var giftEvent = getGiftEvent(currentMessage);
return List.of(comboEvent, giftEvent);
}
if (!containsPreviousMessage) {
if (currentType == GiftSendType.Finished) {
if (currentType == GiftComboStateType.Finished) {
return List.of(getGiftEvent(currentMessage));
} else {
giftsMessages.put(userId, currentMessage);
return List.of(getGiftComboEvent(currentMessage, GiftSendType.Begin));
return List.of(getGiftComboEvent(currentMessage, GiftComboStateType.Begin));
}
}
var previousMessage = giftsMessages.get(userId);
var previousType = GiftSendType.fromNumber(previousMessage.getSendType());
if (currentType == GiftSendType.Active &&
previousType == GiftSendType.Active) {
var previousType = GiftComboStateType.fromNumber(previousMessage.getSendType());
if (currentType == GiftComboStateType.Active &&
previousType == GiftComboStateType.Active) {
giftsMessages.put(userId, currentMessage);
return List.of(getGiftComboEvent(currentMessage, GiftSendType.Active));
return List.of(getGiftComboEvent(currentMessage, GiftComboStateType.Active));
}
if (currentType == GiftSendType.Finished &&
previousType == GiftSendType.Active) {
if (currentType == GiftComboStateType.Finished &&
previousType == GiftComboStateType.Active) {
giftsMessages.clear();
return List.of(
getGiftComboEvent(currentMessage, GiftSendType.Finished),
getGiftComboEvent(currentMessage, GiftComboStateType.Finished),
getGiftEvent(currentMessage));
}
@@ -107,42 +102,28 @@ public class TikTokGiftEventHandler {
return new TikTokGiftEvent(gift, tikTokRoomInfo.getHost(), message);
}
private TikTokGiftEvent getGiftComboEvent(WebcastGiftMessage message, GiftSendType state) {
private TikTokGiftEvent getGiftComboEvent(WebcastGiftMessage message, GiftComboStateType 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 = giftManager.findById(giftId);
var gift = giftsManager.getById(giftId);
if (gift == Gift.UNDEFINED)
gift = giftsManager.getByName(giftMessage.getGift().getName());
if (gift == Gift.UNDEFINED) {
gift = giftManager.findByName(giftMessage.getGift().getName());
}
if (gift == Gift.UNDEFINED) {
gift = giftManager.registerGift(
giftId,
gift = new Gift(giftId,
giftMessage.getGift().getName(),
giftMessage.getGift().getDiamondCount(),
Picture.map(giftMessage.getGift().getImage()));
giftsManager.attachGift(gift);
}
if (gift.getPicture().getLink().endsWith(".webp")) {
updatePicture(gift, giftMessage);
}
if (gift.getPicture().getLink().endsWith(".webp"))
gift.setPicture(Picture.map(giftMessage.getGift().getImage()));
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

@@ -33,7 +33,6 @@ 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 {
private final LiveClientSettings clientSettings;
@@ -63,7 +62,7 @@ public class TikTokWebSocketClient implements SocketClient {
messageHandler.handle(liveClient, connectionData.getWebcastResponse());
var headers = new HashMap<String, String>();
var headers = clientSettings.getHttpSettings().getHeaders();
headers.put("Cookie", connectionData.getWebsocketCookies());
webSocketClient = new TikTokWebSocketListener(connectionData.getWebsocketUrl(),
headers,
@@ -82,7 +81,7 @@ public class TikTokWebSocketClient implements SocketClient {
private void connectDefault() {
try {
webSocketClient.connect();
pingingTask.run(webSocketClient);
pingingTask.run(webSocketClient, clientSettings.getPingInterval());
isConnected = true;
} catch (Exception e) {
isConnected = false;
@@ -112,7 +111,7 @@ public class TikTokWebSocketClient implements SocketClient {
proxySettings.remove();
continue;
}
pingingTask.run(webSocketClient);
pingingTask.run(webSocketClient, clientSettings.getPingInterval());
isConnected = true;
break;
}

View File

@@ -61,7 +61,7 @@ public class TikTokWebSocketListener extends WebSocketClient {
} catch (Exception e) {
tikTokEventHandler.publish(tikTokLiveClient, new TikTokErrorEvent(e));
}
if (isNotClosing()) {
if (isOpen()) {
sendPing();
}
}
@@ -79,8 +79,7 @@ public class TikTokWebSocketListener extends WebSocketClient {
pushFrameBuilder.setPayloadType("ack");
pushFrameBuilder.setLogId(websocketPushFrame.getLogId());
pushFrameBuilder.setPayload(webcastResponse.getInternalExtBytes());
if (isNotClosing())
{
if (isOpen()) {
this.send(pushFrameBuilder.build().toByteArray());
}
}
@@ -90,7 +89,7 @@ public class TikTokWebSocketListener extends WebSocketClient {
@Override
public void onOpen(ServerHandshake serverHandshake) {
tikTokEventHandler.publish(tikTokLiveClient, new TikTokConnectedEvent());
if (isNotClosing()) {
if (isOpen()) {
sendPing();
}
}
@@ -104,7 +103,7 @@ public class TikTokWebSocketListener extends WebSocketClient {
@Override
public void onError(Exception error) {
tikTokEventHandler.publish(tikTokLiveClient, new TikTokErrorEvent(error));
if (isNotClosing()) {
if (isOpen()) {
sendPing();
}
}
@@ -129,10 +128,6 @@ public class TikTokWebSocketListener extends WebSocketClient {
}
}
private boolean isNotClosing() {
return !isClosed() && !isClosing();
}
@Override
public void onMessage(String s) {
// System.err.println(s);

View File

@@ -0,0 +1,31 @@
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

@@ -8,13 +8,13 @@ public class TikTokWebSocketPingingTask
{
private Thread thread;
private boolean isRunning = false;
private final int MIN_TIMEOUT = 250;
private final int MAX_TIMEOUT = 500;
private final int MAX_TIMEOUT = 250;
private final int SLEEP_TIME = 500;
public void run(WebSocket webSocket)
public void run(WebSocket webSocket, long pingTaskTime)
{
stop();
thread = new Thread(() -> pingTask(webSocket));
thread = new Thread(() -> pingTask(webSocket, pingTaskTime));
isRunning = true;
thread.start();
}
@@ -26,20 +26,18 @@ public class TikTokWebSocketPingingTask
isRunning = false;
}
private void pingTask(WebSocket webSocket)
private void pingTask(WebSocket webSocket, long pingTaskTime)
{
var random = new Random();
while (isRunning) {
try {
if (!webSocket.isOpen()) {
Thread.sleep(100);
Thread.sleep(SLEEP_TIME);
continue;
}
webSocket.sendPing();
var timeout = random.nextInt(MAX_TIMEOUT)+MIN_TIMEOUT;
Thread.sleep(timeout);
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.Gift;
import io.github.jwdeveloper.tiktok.data.models.gifts.GiftOld;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -35,38 +35,6 @@ 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,8 +26,9 @@ import io.github.jwdeveloper.tiktok.TikTokRoomInfo;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftComboEvent;
import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.gifts.GiftSendType;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager;
import io.github.jwdeveloper.tiktok.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.mappers.handlers.TikTokGiftEventHandler;
import io.github.jwdeveloper.tiktok.messages.data.GiftStruct;
import io.github.jwdeveloper.tiktok.messages.data.Image;
@@ -38,7 +39,7 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import java.util.logging.Logger;
import java.util.List;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@@ -46,13 +47,12 @@ class TikTokGiftEventHandlerTest {
public static TikTokGiftEventHandler handler;
@BeforeAll
public void before() {
var manager = new TikTokGiftManager(Logger.getLogger("x"));
var manager = new TikTokGiftsManager(List.of());
var info = new TikTokRoomInfo();
info.setHost(new io.github.jwdeveloper.tiktok.data.models.users.User(123L, "test", new Picture("")));
manager.registerGift(123, "example", 123, new Picture("image.webp"));
manager.attachGift(new Gift(123, "example", 123, "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(GiftSendType.Begin, event1.getComboState());
Assertions.assertEquals(GiftSendType.Active, event2.getComboState());
Assertions.assertEquals(GiftSendType.Finished, event3.getComboState());
Assertions.assertEquals(GiftComboStateType.Begin, event1.getComboState());
Assertions.assertEquals(GiftComboStateType.Active, event2.getComboState());
Assertions.assertEquals(GiftComboStateType.Finished, event3.getComboState());
}
@@ -117,7 +117,7 @@ class TikTokGiftEventHandlerTest {
giftBuilder.setId(giftId);
giftBuilder.setName(giftName);
giftBuilder.setImage(Image.newBuilder().addUrlList(giftImage).build());
giftBuilder.setImage(Image.newBuilder().addUrl(giftImage).build());
giftBuilder.setType(streakable ? 1 : 0);
userBuilder.setId(userId);

View File

@@ -41,7 +41,7 @@
<parent>
<artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<version>1.1.1-Release</version>
<version>1.6.1-Release</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -75,7 +75,7 @@
<dependency>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>extension-collector</artifactId>
<version>1.1.1-Release</version>
<version>1.6.1-Release</version>
<scope>compile</scope>
</dependency>
</dependencies>

View File

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

@@ -23,33 +23,25 @@
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.extension.collector.TikTokLiveCollector;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.*;
public class CollectorExample {
private static String mongoUser;
private static String mongoPassword;
private static String mongoDatabase;
public static void main(String[] args) throws IOException {
var collector = TikTokLiveCollector.use(settings ->
var path = "C:\\Users\\ja\\IdeaProjects\\TikTokLiveJava\\Examples\\src\\main\\resources";
var collector = TikTokLiveCollector.useFile(settings ->
{
settings.setConnectionUrl("mongodb+srv://" + mongoUser + ":" + mongoPassword + "@" + mongoDatabase + "/?retryWrites=true&w=majority");
settings.setDatabaseName("tiktok");
settings.setParentFile(new File(path));
});
collector.connectDatabase();
collector.connect();
var users = List.of("tehila_723", "dino123597", "domaxyzx", "dash4214", "obserwacje_live");
var sessionTag = "Tag1";
Map<String, Object> additionalDataFields = Map.of("sessionTag", "ExampleTag");
for (var user : users) {
TikTokLive.newClient(user)
.configure(liveClientSettings ->
@@ -60,17 +52,11 @@ public class CollectorExample {
{
event.getException().printStackTrace();
})
.addListener(collector.newListener(Map.of("sessionTag", sessionTag), document ->
{
if (document.get("dataType") == "message") {
return false;
}
return true;
}))
.addListener(collector.newListener(additionalDataFields))
.buildAndConnectAsync();
}
System.in.read();
collector.disconnectDatabase();
collector.disconnect();
}
}

View File

@@ -31,18 +31,19 @@ import java.io.IOException;
import java.time.Duration;
import java.util.logging.Level;
public class SimpleExample {
public static String TIKTOK_HOSTNAME = "dash4114";
public class ConnectionExample {
public static String TIKTOK_HOSTNAME = "kvadromama_marina1";
public static void main(String[] args) throws IOException {
showLogo();
var gifts = TikTokLive.gifts();
TikTokLive.newClient(SimpleExample.TIKTOK_HOSTNAME)
TikTokLive.newClient(ConnectionExample.TIKTOK_HOSTNAME)
.configure(clientSettings ->
{
clientSettings.setHostName(SimpleExample.TIKTOK_HOSTNAME); // This method is useful in case you want change hostname later
clientSettings.setHostName(ConnectionExample.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
@@ -84,10 +85,10 @@ public class SimpleExample {
})
.onGift((liveClient, event) ->
{
switch (event.getGift()) {
case ROSE -> print(ConsoleColors.RED, "Rose!");
case GG -> print(ConsoleColors.YELLOW, " GOOD GAME!");
case TIKTOK -> print(ConsoleColors.CYAN, "Thanks for TikTok");
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");
default ->
print(ConsoleColors.GREEN, "[Thanks for gift] ", ConsoleColors.YELLOW, event.getGift().getName(), "x", event.getCombo());
}

View File

@@ -23,10 +23,7 @@
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.live.GiftManager;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.data.models.gifts.*;
import lombok.AllArgsConstructor;
public class CustomEventExample {
@@ -40,13 +37,13 @@ public class CustomEventExample {
Gift gift;
}
public static void main(String[] args) {
TikTokLive.newClient(SimpleExample.TIKTOK_HOSTNAME)
public static void main(String[] args)
{
TikTokLive.newClient(ConnectionExample.TIKTOK_HOSTNAME)
.configure(clientSettings ->
{
clientSettings.setPrintToConsole(true);
})
.onGift((liveClient, event) ->
{
if (event.getGift().getDiamondCost() > 100)

View File

@@ -1,73 +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.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

@@ -0,0 +1,74 @@
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

@@ -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;
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(SimpleExample.TIKTOK_HOSTNAME)
TikTokLive.newClient(ConnectionExample.TIKTOK_HOSTNAME)
.addListener(customListener)
.buildAndConnect();
System.in.read();
@@ -84,12 +84,12 @@ public class ListenerExample
@TikTokEventObserver
public void onGift(LiveClient liveClient, TikTokGiftEvent event) {
var message = switch (event.getGift()) {
case ROSE -> "Thanks :)";
case APPETIZERS -> ":OO";
case APRIL -> ":D";
case TIKTOK -> ":P";
case CAP -> ":F";
var message = switch (event.getGift().getName()) {
case "ROSE" -> "Thanks :)";
case "APPETIZERS" -> ":OO";
case "APRIL" -> ":D";
case "TIKTOK" -> ":P";
case "CAP" -> ":F";
default -> ":I";
};
liveClient.getLogger().info(message);

View File

@@ -26,7 +26,7 @@ import java.net.Proxy;
public class ProxyExample {
public static void main(String[] args) throws Exception {
TikTokLive.newClient(SimpleExample.TIKTOK_HOSTNAME)
TikTokLive.newClient(ConnectionExample.TIKTOK_HOSTNAME)
.configure(clientSettings -> {
clientSettings.setPrintToConsole(true);
clientSettings.getHttpSettings().configureProxy(proxySettings -> {

View File

@@ -70,7 +70,7 @@ Maven
<dependency>
<groupId>com.github.jwdeveloper.TikTok-Live-Java</groupId>
<artifactId>Client</artifactId>
<version>1.1.0-Release</version>
<version>1.6.0-Release</version>
<scope>compile</scope>
</dependency>
</dependencies>
@@ -87,7 +87,7 @@ dependencyResolutionManagement {
}
dependencies {
implementation 'com.github.jwdeveloper.TikTok-Live-Java:Client:1.1.0-Release'
implementation 'com.github.jwdeveloper.TikTok-Live-Java:Client:1.5.0-Release'
}
```

View File

@@ -1,62 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<version>1.1.1-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

@@ -1,65 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.tools;
import io.github.jwdeveloper.tiktok.tools.collector.client.TikTokDataCollectorBuilder;
import io.github.jwdeveloper.tiktok.tools.collector.api.DataCollectorBuilder;
import io.github.jwdeveloper.tiktok.tools.tester.TikTokDataTesterBuilder;
import io.github.jwdeveloper.tiktok.tools.tester.api.DataTesterBuilder;
public class TikTokLiveTools
{
/**
*
* @param databaseName dataCollector use sql-lite database to store message
* if database not exits it creates new one
* @return
*/
public static DataCollectorBuilder createCollector(String databaseName)
{
return new TikTokDataCollectorBuilder(databaseName);
}
/**
*
* @param databaseName dataTester will read messages for database
* before using dataTester, use dataCollector to create database
* if database not exits exception will be thrown
* @return
*/
public static DataTesterBuilder createTester(String databaseName)
{
return new TikTokDataTesterBuilder(databaseName);
}
/**
*
* Returns browser application that collects and display Events, Messages, WebcastResponses
* in online web editor so it's easier to read and analyze data structures
* @return
*/
public static void createWebViewer()
{
}
}

View File

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

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

View File

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

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

View File

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

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

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

@@ -1,138 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.tools.collector.client;
import com.google.protobuf.GeneratedMessageV3;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
import io.github.jwdeveloper.tiktok.tools.collector.api.DataCollectorBuilder;
import io.github.jwdeveloper.tiktok.tools.collector.api.DataCollector;
import io.github.jwdeveloper.tiktok.tools.collector.api.TikTokDataCollectorModel;
import io.github.jwdeveloper.tiktok.tools.db.TikTokDatabase;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.UUID;
import java.util.function.Consumer;
public class TikTokDataCollectorBuilder implements DataCollectorBuilder {
TikTokDataCollectorModel dataModel;
TikTokDatabase database;
public TikTokDataCollectorBuilder(String outputName) {
dataModel = new TikTokDataCollectorModel();
dataModel.setOutputName(outputName);
dataModel.setUsers(new ArrayList<>());
dataModel.setEventsFilter(new HashSet<>());
dataModel.setMessagesFilter(new HashSet<>());
dataModel.setOutputPath("...");
dataModel.setOnConfigureLiveClient((e) -> {
});
}
@Override
public DataCollectorBuilder addUser(String user) {
dataModel.getUsers().add(user);
return this;
}
@Override
public TikTokDataCollectorBuilder addMessageFilter(Class<? extends GeneratedMessageV3> message) {
dataModel.getMessagesFilter().add(message.getSimpleName());
return this;
}
@Override
public TikTokDataCollectorBuilder addMessageFilter(String message) {
dataModel.getMessagesFilter().add(message);
return this;
}
@Override
public TikTokDataCollectorBuilder addEventFilter(Class<? extends TikTokEvent> event) {
dataModel.getEventsFilter().add(event.getSimpleName());
return this;
}
@Override
public TikTokDataCollectorBuilder addEventFilter(String event) {
dataModel.getEventsFilter().add(event);
return this;
}
@Override
public DataCollectorBuilder setOutputPath(String path) {
dataModel.setOutputPath(path);
return this;
}
@Override
public DataCollectorBuilder setSessionTag(String sessionTimestamp) {
dataModel.setSessionTag(sessionTimestamp);
return this;
}
@Override
public DataCollectorBuilder setDatabase(TikTokDatabase database)
{
this.database =database;
return this;
}
@Override
public DataCollectorBuilder configureLiveClient(Consumer<LiveClientBuilder> consumer) {
dataModel.setOnConfigureLiveClient(consumer);
return this;
}
@Override
public DataCollector buildAndRun() {
var collector = build();
collector.connect();
return collector;
}
@Override
public DataCollector build() {
if (dataModel.getSessionTag().isEmpty()) {
dataModel.setSessionTag(UUID.randomUUID().toString());
}
if(database == null)
{
database = new TikTokDatabase(dataModel.getOutputName());
}
var dataCollector = new TikTokDataCollector(dataModel, database);
return dataCollector;
}
}

View File

@@ -1,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.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

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

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

@@ -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.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();
}

View File

@@ -1,53 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.tools.db.tables;
import java.io.PrintWriter;
import java.io.StringWriter;
public class ExceptionInfoModel
{
private String message;
private String stackTrace;
public ExceptionInfoModel(Throwable throwable) {
this.message = throwable.getMessage();
this.stackTrace = getStackTraceAsString(throwable);
}
public static String getStackTraceAsString(Throwable throwable) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
throwable.printStackTrace(pw);
return sw.toString();
}
// Getters for message and stackTrace
public String getMessage() {
return message;
}
public String getStackTrace() {
return stackTrace;
}
}

View File

@@ -1,94 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.tools.tester;
import com.google.gson.JsonParser;
import io.github.jwdeveloper.tiktok.tools.tester.mockClient.TikTokLiveMock;
import io.github.jwdeveloper.tiktok.tools.tester.mockClient.mocks.LiveClientMock;
import io.github.jwdeveloper.tiktok.tools.util.MessageUtil;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.*;
public class RunJsonTester {
public static void main(String[] args) throws IOException {
var messages = getMessages();
var client =(LiveClientMock) TikTokLiveMock.create()
.onWebsocketUnhandledMessage((liveClient, event) ->
{
var sb = new StringBuilder();
sb.append("Unhandled Message! " );
sb.append(event.getData().getMethod());
sb.append("\n");
sb.append(MessageUtil.getContent(event.getData()));
// liveClient.getLogger().info(sb.toString());
}).
onGift((liveClient, event) ->
{
liveClient.getLogger().info("Gift event: "+event.toJson());
})
.onGiftCombo((liveClient, event) ->
{
liveClient.getLogger().info("GiftCombo event"+event.toJson());
})
.onError((liveClient, event) ->
{
event.getException().printStackTrace();
})
.build();
for(var msg : messages.entrySet())
{
for(var content : msg.getValue())
{
client.publishMessage(msg.getKey(),content);
}
}
client.connect();
}
private static Map<String, List<String>> getMessages() throws IOException {
var path = "C:\\Users\\ja\\IdeaProjects\\TikTokLiveJava\\Tools-EventsCollector\\src\\main\\resources\\log.json";
var jsonElement = JsonParser.parseReader(new FileReader(path, Charset.defaultCharset()));
var res = new HashMap<String, List<String>>();
if (jsonElement.isJsonObject()) {
var jsonObject = jsonElement.getAsJsonObject();
var keys = jsonObject.keySet();
for (String key : keys) {
var messages = jsonObject.get(key).getAsJsonArray();
for (var msg : messages) {
var data = msg.getAsJsonObject().get("eventData").getAsString();
res.computeIfAbsent(key, s -> new ArrayList<>()).add(data);
}
}
}
return res;
}
}

View File

@@ -1,90 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.tools.tester;
import io.github.jwdeveloper.tiktok.tools.db.TikTokDatabase;
import io.github.jwdeveloper.tiktok.tools.db.tables.TikTokDataTable;
import io.github.jwdeveloper.tiktok.tools.tester.api.DataTester;
import io.github.jwdeveloper.tiktok.tools.tester.api.DataTesterModel;
import io.github.jwdeveloper.tiktok.tools.tester.mockClient.TikTokLiveMock;
import io.github.jwdeveloper.tiktok.tools.tester.mockClient.mocks.LiveClientMock;
import java.util.LinkedList;
import java.util.Queue;
public class TikTokDataTester implements DataTester {
private DataTesterModel model;
private LiveClientMock client;
private Queue<TikTokDataTable> data;
private TikTokDatabase database;
public TikTokDataTester(DataTesterModel model) {
this.model = model;
}
@Override
public void connect() {
try {
database = new TikTokDatabase(model.getDatabaseName());
database.connect();
var mockBuilder = TikTokLiveMock.create();
model.getBuilderConsumer().accept(mockBuilder);
client = mockBuilder.build();
var respocnes = database.getSessionResponces(model.getSessionTag(), model.getUser());
data = new LinkedList<>(respocnes);
client.connect();
while (!data.isEmpty()) {
nextResponse();
}
} catch (Exception e) {
throw new RuntimeException("Error while running tester", e);
}
}
@Override
public void nextResponse() {
try {
var responce = data.poll();
client.publishResponse(responce.getContent());
Thread.sleep(1);
} catch (Exception e) {
throw new RuntimeException("Unable to run response!");
}
}
@Override
public void disconnect() {
try {
client.disconnect();
database.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,111 +0,0 @@
/*
* Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.tools.tester.mockClient;
import io.github.jwdeveloper.tiktok.TikTokLiveClientBuilder;
import io.github.jwdeveloper.tiktok.TikTokRoomInfo;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager;
import io.github.jwdeveloper.tiktok.TikTokLiveMessageHandler;
import io.github.jwdeveloper.tiktok.TikTokLiveHttpClient;
import io.github.jwdeveloper.tiktok.listener.TikTokListenersManager;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import io.github.jwdeveloper.tiktok.tools.tester.mockClient.mocks.LiveClientMock;
import io.github.jwdeveloper.tiktok.tools.tester.mockClient.mocks.WebsocketClientMock;
import java.util.Base64;
import java.util.List;
import java.util.Stack;
public class TikTokMockBuilder extends TikTokLiveClientBuilder {
Stack<WebcastResponse> responses;
public TikTokMockBuilder(String userName) {
super(userName);
responses = new Stack<>();
}
public TikTokMockBuilder addResponse(String value) {
var bytes = Base64.getDecoder().decode(value);
return addResponse(bytes);
}
public TikTokMockBuilder addResponses(List<String> values) {
for (var value : values) {
try {
addResponse(value);
} catch (Exception e) {
throw new TikTokLiveException(value, e);
}
}
return this;
}
public TikTokMockBuilder addResponse(byte[] bytes) {
try {
var response = WebcastResponse.parseFrom(bytes);
return addResponse(response);
} catch (Exception e) {
throw new RuntimeException("Unable to parse response from bytes", e);
}
}
public TikTokMockBuilder addResponse(WebcastResponse message) {
responses.push(message);
return this;
}
@Override
public LiveClientMock build() {
validate();
var tiktokRoomInfo = new TikTokRoomInfo();
tiktokRoomInfo.setHostName(clientSettings.getHostName());
var listenerManager = new TikTokListenersManager(listeners, tikTokEventHandler);
var giftManager = new TikTokGiftManager(logger);
var mapper = createMapper(giftManager, tiktokRoomInfo);
var handler = new TikTokLiveMessageHandler(tikTokEventHandler, mapper);
var webSocketClient = new WebsocketClientMock(logger, responses, handler);
return new LiveClientMock(tiktokRoomInfo,
new TikTokLiveHttpClient(),
webSocketClient,
giftManager,
tikTokEventHandler,
clientSettings,
listenerManager,
logger);
}
@Override
public LiveClientMock buildAndConnect() {
var client = build();
client.connect();
return client;
}
}

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