Compare commits

...

59 Commits

Author SHA1 Message Date
David Kohler
57f33b2efa Change static schedulers to AsyncHandler to hold for heartbeat and reconnect logic. (#148) 2025-09-24 19:26:44 -04:00
GitHub Action
85cba9fff2 Update version in pom.xml 2025-09-19 12:31:49 +00:00
kohlerpop1
b7977469a0 Simply prevent copying connectionState. It is a control elsewhere so we should not copy default state! 2025-09-19 08:28:56 -04:00
GitHub Action
8910c6a491 Update version in pom.xml 2025-09-17 22:57:05 +00:00
David Kohler
57ff1f1385 Only copy roomInfo if its valid and null check safety! (#146) 2025-09-17 18:55:10 -04:00
GitHub Action
aa1ef1f170 Update version in pom.xml 2025-09-17 01:49:13 +00:00
David Kohler
834dfa0939 MINOR 2025-09-16 21:47:23 -04:00
David Kohler
ab97affc73 Add integration for Eulerstream websocket connections and other QOL changes! (#144)
* Quick string alteration for proper state!

* Remove -1 close code and convert to standard public static value with reference!

* Convert to and use LiveClientStopType enum for disconnecting websocket from magic numbers!

* Add capability to use Eulerstream Enterprise server and websocket!
2025-09-16 21:46:56 -04:00
GitHub Action
c8120d89b2 Update version in pom.xml 2025-09-06 18:11:34 +00:00
Mathieu Bayou
d325dffdac Fix: API Key added in the body instead of param (#142)
* Fix: Add the missing API Key on send chat calls.

* Fix: Wrongly added the apiKey in the body instead of the param

* Update TikTokLiveHttpClient.java

We do not need to null check as Eulerstream verifies anyway so we can just pass whatever the value is.

* Update TikTokLiveHttpClient.java

Revert to your method, but using Header instead of Param, since HttpClient does not allow null values!

Co-authored-by: mbayou <mathieu@novasquare.io>
Co-authored-by: kohlerpop1 <70915561+kohlerpop1@users.noreply.github.com>
2025-09-06 14:09:15 -04:00
GitHub Action
dac688e9d6 Update version in pom.xml 2025-09-06 03:40:42 +00:00
kohlerpop1
92c9724108 Quick string alteration for proper state! 2025-09-05 23:37:50 -04:00
Mathieu Bayou
f7bef6bb31 Fix: Add the missing API Key on send chat calls. (#141) 2025-09-05 23:33:54 -04:00
GitHub Action
d8661fa2e3 Update version in pom.xml 2025-09-05 03:44:01 +00:00
David Kohler
fc02239d48 Develop 1.10.8 (#140)
* Add support for TikTokLinkMicBattleItemCard for battle/match power-ups
* Switch to an efficient pool of daemon threads instead of thread per websocket and sleeping!
* Implement Eulerstream send chat API endpoint!
* Add static to fields for single instance to manage all heartbeat threads. Far more efficient than 1 thread each sleeping!
* Add global comment to known its a true global singleton!
2025-09-04 23:41:18 -04:00
GitHub Action
77eeedc15c Update version in pom.xml 2025-07-20 01:00:06 +00:00
kohlerpop1
54b0216bf3 Downgrade junit jupiter to use Java 16 only! 2025-07-19 20:58:21 -04:00
David Kohler
4443fbe554 Add Paused state for live data due to false positive of returning HostNotFound when they were only paused! (#135)
* Add Paused state for live data due to false positive of returning HostNotFound when they were only paused!

* Fix accidental pom.xml comment!
2025-07-19 20:12:19 -04:00
GitHub Action
a6188d8bb0 Update version in pom.xml 2025-05-21 22:48:07 +00:00
David Kohler
81fd7dc85c Merge pull request #131 from jwdeveloper/develop-1.10.6
Add session id to websocket connection to get authenticated WS as well as optional customizable type for disconnecting websocket client in various ways.
2025-05-21 18:45:17 -04:00
kohlerpop1
7e59099793 Add session id to websocket connection to get authenticated WS as well as optional customizable type for disconnecting websocket client in various ways. 2025-05-21 18:43:14 -04:00
GitHub Action
dd2f311539 Update version in pom.xml 2025-05-19 18:47:52 +00:00
David Kohler
ba69f5f5eb Merge pull request #129 from jwdeveloper/develop-1.10.5
Add TikTok Target Identity Data Center cookie to make sessionid effective and verifiable.
2025-05-19 14:46:16 -04:00
kohlerpop1
e9a91f5741 Add TikTok Target Identity Data Center cookie to make sessionid effective and verifiable. 2025-05-19 14:40:41 -04:00
GitHub Action
053bb5e3dc Update version in pom.xml 2025-05-12 02:21:39 +00:00
David Kohler
906796dc23 Merge pull request #128 from jwdeveloper/develop-1.10.4
Fix throwing error bug when connecting using proxy!
2025-05-11 22:19:37 -04:00
kohlerpop1
162092c638 Fix throwing error bug when connecting using proxy! 2025-05-11 22:19:04 -04:00
GitHub Action
a72d134796 Update version in pom.xml 2025-05-11 02:09:04 +00:00
David Kohler
75f6368f2c Merge pull request #125 from jwdeveloper/develop-1.10.3
Change websocket connection logic!
2025-05-10 22:06:35 -04:00
kohlerpop1
b9eb0eba93 Removal of debug print statements! 2025-05-04 21:59:22 -04:00
GitHub Action
50d6d6e515 Update version in pom.xml 2025-04-25 21:00:20 +00:00
kohlerpop1
42f9fe360b Removal of debug print statements! 2025-04-25 16:58:24 -04:00
GitHub Action
dff226740c Update version in pom.xml 2025-04-25 19:42:02 +00:00
David Kohler
951d30e6a7 Merge pull request #124 from jwdeveloper/develop-1.10.1
Add additional helper methods back to TikTokLinkMicBattleEvent!
2025-04-25 15:40:08 -04:00
kohlerpop1
1df912b722 Add additional helper methods back to TikTokLinkMicBattleEvent! 2025-04-25 15:37:22 -04:00
GitHub Action
4aec20cc35 Update version in pom.xml 2025-04-17 21:47:42 +00:00
kohlerpop1
d877d38db6 MINOR 2025-04-17 17:45:25 -04:00
David Kohler
db199e9a64 MINOR 2025-04-17 17:39:03 -04:00
David Kohler
1c56cf35f0 MINOR 2025-04-17 17:33:10 -04:00
David Kohler
225cb2df11 Merge pull request #123 from jwdeveloper/develop-1.10.0
Adapt everything to newly updated proto and fix all mappings for those events
2025-04-17 17:32:17 -04:00
kohlerpop1
1cc8a5af1b Adapt everything to newly updated proto and fix all mappings for those events. 2025-04-16 22:25:22 -04:00
GitHub Action
edefd0c1fd Update version in pom.xml 2025-02-19 19:16:59 +00:00
David Kohler
07a28e2632 Update README.md 2025-02-19 14:15:01 -05:00
David Kohler
cc811f6c0a Merge pull request #121 from jwdeveloper/develop-1.9.2
Updates to Priority Order and Preconnection logic and Recording Cancellation!
2025-02-19 14:13:30 -05:00
kohlerpop1
735bdfbb10 Clarify Priority calling order
Altered pre-connection logic to make reason settable
Optionally cancel connection if recorder is used and fails to find record url!
2025-02-19 13:18:25 -05:00
GitHub Action
28ef3562b6 Update version in pom.xml 2025-02-15 00:52:55 +00:00
David Kohler
3b6b381e31 Update README.md 2025-02-14 19:51:18 -05:00
David Kohler
86a79d687e Merge pull request #120 from jwdeveloper/develop-1.9.1
Fix clientParams and send cookies when present and not null to specific requests for 18+ livestreams.
2025-02-14 19:50:34 -05:00
kohlerpop1
65a651d4b8 Fix clientParams and send cookies when present and not null to specific requests for 18+ livestreams. 2025-02-14 19:44:32 -05:00
GitHub Action
8bda5d789c Update version in pom.xml 2025-02-11 22:09:28 +00:00
David Kohler
81fb2b4576 MINOR 2025-02-11 17:07:56 -05:00
David Kohler
64b2e74d2b Update README.md 2025-02-11 17:06:36 -05:00
David Kohler
8a1f3255d0 Merge pull request #119 from jwdeveloper/develop-1.9.0
Develop 1.9.0
2025-02-11 17:06:10 -05:00
kohlerpop1
03892390fa Optimize for loop! 2025-02-11 16:39:44 -05:00
kohlerpop1
71602d5513 Updates for example gifts classes! 2025-02-11 16:36:20 -05:00
kohlerpop1
20db7cb773 Removal of deprecated methods and addition of information in TikTokLiveUnknownHostException and TikTokLiveOfflineHostException! 2025-02-11 16:34:27 -05:00
GitHub Action
3fda7fe446 Update version in pom.xml 2025-02-11 04:35:54 +00:00
David Kohler
8fcbb4b20f Update README.md 2025-02-10 23:33:40 -05:00
David Kohler
d90ab60e52 Merge pull request #118 from jwdeveloper/develop-1.8.14
Add signature to user class and remove no longer UUC param!
2025-02-10 23:32:51 -05:00
90 changed files with 3405 additions and 1321 deletions

View File

@@ -46,7 +46,7 @@ jobs:
run: mkdir staging && cp Client/target/Client-${{steps.version.outputs.version_tag}}-all.jar staging run: mkdir staging && cp Client/target/Client-${{steps.version.outputs.version_tag}}-all.jar staging
- name: 5 set up a cache for maven - name: 5 set up a cache for maven
uses: actions/cache@v2 uses: actions/cache@v3
with: with:
path: ~/.m2 path: ~/.m2
key: ${{runner.os}}-m2-${{hashFiles('**/pom.xml')}} key: ${{runner.os}}-m2-${{hashFiles('**/pom.xml')}}

View File

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

View File

@@ -23,11 +23,12 @@
package io.github.jwdeveloper.tiktok.annotations; package io.github.jwdeveloper.tiktok.annotations;
/** /**
* HIGHEST 1 ORDER -
* HIGH 2 * HIGHEST 1st,
* NORMAL 3 * HIGH 2nd,
* LOW 4 * NORMAL 3rd,
* LOWEST 5 * LOW 4th,
* LOWEST 5th
*/ */
public enum Priority { public enum Priority {
LOWEST(2), LOW(1), NORMAL(0), HIGH(-1), HIGHEST(-2); LOWEST(2), LOW(1), NORMAL(0), HIGH(-1), HIGHEST(-2);

View File

@@ -22,25 +22,36 @@
*/ */
package io.github.jwdeveloper.tiktok.data.events; package io.github.jwdeveloper.tiktok.data.events;
import io.github.jwdeveloper.tiktok.annotations.EventMeta; import io.github.jwdeveloper.tiktok.annotations.*;
import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokHeaderEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokHeaderEvent;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastCaptionMessage; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastCaptionMessage;
import lombok.Value; import lombok.Value;
import java.util.*;
@Value @Value
@EventMeta(eventType = EventType.Message) @EventMeta(eventType = EventType.Message)
public class TikTokCaptionEvent extends TikTokHeaderEvent { public class TikTokCaptionEvent extends TikTokHeaderEvent {
Long captionTimeStamp; Long captionTimeStamp;
List<CaptionContent> contents;
String iSOLanguage;
String text;
public TikTokCaptionEvent(WebcastCaptionMessage msg) { public TikTokCaptionEvent(WebcastCaptionMessage msg) {
super(msg.getCommon()); super(msg.getCommon());
captionTimeStamp = msg.getTimeStamp(); captionTimeStamp = msg.getTimestampMs();
iSOLanguage = msg.getCaptionData().getLanguage(); contents = new ArrayList<>();
text = msg.getCaptionData().getText(); for (WebcastCaptionMessage.CaptionContent captionContent : msg.getContentList())
contents.add(new CaptionContent(captionContent));
}
@Value
public static class CaptionContent {
String iSOLanguage;
String text;
public CaptionContent(WebcastCaptionMessage.CaptionContent captionContent) {
this.iSOLanguage = captionContent.getLang();
this.text = captionContent.getContent();
}
} }
} }

View File

@@ -58,7 +58,7 @@ public class TikTokCommentEvent extends TikTokHeaderEvent {
var builder = WebcastChatMessage.newBuilder(); var builder = WebcastChatMessage.newBuilder();
builder.setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder() builder.setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder()
.setNickname(userName) .setNickname(userName)
.setDisplayId(userName) .setUsername(userName)
.build()); .build());
builder.setContentLanguage("en"); builder.setContentLanguage("en");
builder.setVisibleToSender(true); builder.setVisibleToSender(true);

View File

@@ -30,14 +30,22 @@ import lombok.Getter;
@Getter @Getter
@EventMeta(eventType = EventType.Control) @EventMeta(eventType = EventType.Control)
public class TikTokDisconnectedEvent extends TikTokLiveClientEvent { public class TikTokDisconnectedEvent extends TikTokLiveClientEvent {
public static int UNKNOWN_CLOSE_CODE = -1;
/** Valid CloseFrame code or -1 for unknown */
private final int code;
private final String reason; private final String reason;
public TikTokDisconnectedEvent(String reason) { public TikTokDisconnectedEvent(int code, String reason) {
this.code = code;
this.reason = reason.isBlank() ? "None" : reason; this.reason = reason.isBlank() ? "None" : reason;
} }
public static TikTokDisconnectedEvent of(String reason) public TikTokDisconnectedEvent(String reason) {
{ this(UNKNOWN_CLOSE_CODE, reason);
return new TikTokDisconnectedEvent(reason); }
public boolean isUnknownCloseCode() {
return this.code == UNKNOWN_CLOSE_CODE;
} }
} }

View File

@@ -47,9 +47,9 @@ public class TikTokGoalUpdateEvent extends TikTokHeaderEvent {
goalId = msg.getGoal().getId(); goalId = msg.getGoal().getId();
description = msg.getGoal().getDescription(); description = msg.getGoal().getDescription();
users = msg.getGoal() users = msg.getGoal()
.getContributorsListList() .getContributorsList()
.stream() .stream()
.map(u -> new User(u.getUserId(), u.getDisplayId(), Picture.map(u.getAvatar()))) .map(u -> new User(u.getUserId(), u.getDisplayId(), Picture.map(u.getAvatar())))
.toList(); .toList();
} }
} }

View File

@@ -22,8 +22,7 @@
*/ */
package io.github.jwdeveloper.tiktok.data.events; package io.github.jwdeveloper.tiktok.data.events;
import io.github.jwdeveloper.tiktok.annotations.EventMeta; import io.github.jwdeveloper.tiktok.annotations.*;
import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokHeaderEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokHeaderEvent;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastInRoomBannerMessage; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastInRoomBannerMessage;
import lombok.Getter; import lombok.Getter;
@@ -35,6 +34,6 @@ public class TikTokInRoomBannerEvent extends TikTokHeaderEvent {
public TikTokInRoomBannerEvent(WebcastInRoomBannerMessage msg) { public TikTokInRoomBannerEvent(WebcastInRoomBannerMessage msg) {
super(msg.getHeader()); super(msg.getHeader());
json = msg.getJson(); json = msg.getExtraMap().toString();
} }
} }

View File

@@ -22,16 +22,14 @@
*/ */
package io.github.jwdeveloper.tiktok.data.events; package io.github.jwdeveloper.tiktok.data.events;
import io.github.jwdeveloper.tiktok.annotations.EventMeta; import io.github.jwdeveloper.tiktok.annotations.*;
import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokHeaderEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokHeaderEvent;
import io.github.jwdeveloper.tiktok.data.models.LinkMicArmy; import io.github.jwdeveloper.tiktok.data.models.*;
import io.github.jwdeveloper.tiktok.data.models.Picture; import io.github.jwdeveloper.tiktok.messages.enums.*;
import io.github.jwdeveloper.tiktok.messages.enums.LinkMicBattleStatus;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkMicArmies; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkMicArmies;
import lombok.Getter; import lombok.Getter;
import java.util.List; import java.util.*;
/** /**
* Triggered every time a battle participant receives points. Contains the current status of the battle and the army that suported the group. * Triggered every time a battle participant receives points. Contains the current status of the battle and the army that suported the group.
@@ -47,13 +45,28 @@ public class TikTokLinkMicArmiesEvent extends TikTokHeaderEvent {
private final Picture picture; private final Picture picture;
private final List<LinkMicArmy> armies; private final Map<Long, LinkMicArmy> armies;
private final BattleType battleType;
public TikTokLinkMicArmiesEvent(WebcastLinkMicArmies msg) { public TikTokLinkMicArmiesEvent(WebcastLinkMicArmies msg) {
super(msg.getCommon()); super(msg.getCommon());
battleId = msg.getId(); battleId = msg.getBattleId();
armies = msg.getBattleItemsList().stream().map(LinkMicArmy::new).toList(); armies = new HashMap<>();
picture = Picture.map(msg.getImage()); picture = Picture.map(msg.getGifIconImage());
finished = msg.getBattleStatus() == LinkMicBattleStatus.ARMY_FINISHED; finished = msg.getTriggerReason() == TriggerReason.TRIGGER_REASON_BATTLE_END;
battleType = msg.getBattleSettings().getBattleType();
switch (battleType) {
case BATTLE_TYPE_NORMAL_BATTLE -> // 1v1 | Fields present - armies
msg.getArmiesMap().forEach((aLong, userArmies) -> armies.put(aLong, new LinkMicArmy(userArmies)));
case BATTLE_TYPE_TEAM_BATTLE -> // 2v2 | Fields present - team_armies
msg.getTeamArmiesList().forEach(teamArmy -> armies.put(teamArmy.getTeamId(), new LinkMicArmy(teamArmy.getUserArmies())));
case BATTLE_TYPE_INDIVIDUAL_BATTLE -> // 1v1v1 or 1v1v1v1 | Fields present - team_armies
msg.getTeamArmiesList().forEach(teamArmy -> armies.put(teamArmy.getTeamId(), new LinkMicArmy(teamArmy.getUserArmies())));
case BATTLE_TYPE_1_V_N -> { // 1 vs Many | Have no data for this yet
// Most complicated and uncommon battle type - When more data is collected, this will be updated.
}
}
} }
} }

View File

@@ -24,13 +24,15 @@ package io.github.jwdeveloper.tiktok.data.events;
import io.github.jwdeveloper.tiktok.annotations.*; import io.github.jwdeveloper.tiktok.annotations.*;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokHeaderEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokHeaderEvent;
import io.github.jwdeveloper.tiktok.data.models.battles.*; import io.github.jwdeveloper.tiktok.data.models.battles.Team;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException; import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.messages.enums.LinkMicBattleStatus; import io.github.jwdeveloper.tiktok.messages.data.*;
import io.github.jwdeveloper.tiktok.messages.enums.*;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkMicBattle; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkMicBattle;
import lombok.Getter; import lombok.Getter;
import java.util.List; import java.util.*;
import java.util.stream.Collectors;
/** /**
* Triggered every time a battle starts & ends * Triggered every time a battle starts & ends
@@ -44,94 +46,118 @@ public class TikTokLinkMicBattleEvent extends TikTokHeaderEvent
true if battle is finished otherwise false true if battle is finished otherwise false
*/ */
private final boolean finished; private final boolean finished;
private final Team team1, team2; private final List<Team> teams;
private final BattleType battleType;
public TikTokLinkMicBattleEvent(WebcastLinkMicBattle msg) { public TikTokLinkMicBattleEvent(WebcastLinkMicBattle msg) {
super(msg.getCommon()); super(msg.getCommon());
battleId = msg.getId(); battleId = msg.getBattleId();
finished = msg.getBattleStatus() == LinkMicBattleStatus.BATTLE_FINISHED; finished = msg.getAction() == BattleAction.BATTLE_ACTION_FINISH;
if (msg.getHostTeamCount() == 2) { // 1v1 battle battleType = msg.getBattleSetting().getBattleType();
team1 = new Team1v1(msg.getHostTeam(0), msg); teams = new ArrayList<>();
team2 = new Team1v1(msg.getHostTeam(1), msg); switch (battleType) {
} else { // 2v2 battle case BATTLE_TYPE_NORMAL_BATTLE -> { // 1v1 | Fields present - anchor_info, battle_combos
if (isFinished()) { for (Long userId : msg.getAnchorInfoMap().keySet())
team1 = new Team2v2(msg.getHostData2V2List().stream().filter(data -> data.getTeamNumber() == 1).findFirst().orElse(null), msg); teams.add(new Team(msg.getAnchorInfoOrThrow(userId), msg.getBattleCombosOrThrow(userId)));
team2 = new Team2v2(msg.getHostData2V2List().stream().filter(data -> data.getTeamNumber() == 2).findFirst().orElse(null), msg); if (finished) { // Additional fields present - battle_result, armies
} else { for (Team team : teams) {
team1 = new Team2v2(msg.getHostTeam(0), msg.getHostTeam(1), msg); Long userId = team.getHosts().get(0).getId();
team2 = new Team2v2(msg.getHostTeam(2), msg.getHostTeam(3), msg); team.setTotalPoints((int) msg.getBattleResultOrThrow(userId).getScore());
team.setViewers(msg.getArmiesOrThrow(userId).getUserArmyList().stream().collect(Collectors.toMap(User::new, bua -> (int) bua.getScore())));
}
}
}
case BATTLE_TYPE_TEAM_BATTLE -> { // 2v2 | Fields present - anchor_info
if (finished) { // Additional fields present - team_battle_result, team_armies
for (BattleTeamUserArmies army : msg.getTeamArmiesList()) {
Team team = new Team(army.getTeamId(), army.getTeamUsersList().stream()
.map(BattleTeamUser::getUserId).map(userId -> new User(msg.getAnchorInfoOrThrow(userId).getUser())).toList());
team.setTotalPoints((int) army.getTeamTotalScore());
team.setViewers(army.getUserArmies().getUserArmyList().stream().collect(Collectors.toMap(User::new, bua -> (int) bua.getScore())));
teams.add(team);
}
} else { // Additional fields present - team_users
for (WebcastLinkMicBattle.TeamUsersInfo teamUsersInfo : msg.getTeamUsersList())
teams.add(new Team(teamUsersInfo.getTeamId(), teamUsersInfo.getUserIdsList().stream()
.map(userId -> new User(msg.getAnchorInfoOrThrow(userId).getUser())).toList()));
}
}
case BATTLE_TYPE_INDIVIDUAL_BATTLE -> { // 1v1v1 or 1v1v1v1 | Fields present - anchor_info
teams.addAll(msg.getAnchorInfoMap().values().stream().map(Team::new).toList());
if (finished) { // Additional fields present - team_battle_result, team_armies
for (Team team : teams) {
Long userId = team.getHosts().get(0).getId();
BattleTeamUserArmies army = msg.getTeamArmiesList().stream().filter(btua -> btua.getTeamId() == userId).findFirst().orElseThrow();
team.setTotalPoints((int) army.getTeamTotalScore());
team.setViewers(army.getUserArmies().getUserArmyList().stream().collect(Collectors.toMap(User::new, bua -> (int) bua.getScore())));
}
}
}
case BATTLE_TYPE_1_V_N -> { // 1 vs Many | Have no data for this yet
// Most complicated and uncommon battle type - When more data is collected, this will be updated.
} }
} }
// 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
}
/**
* @param battleHostName name of host to search
* @return Team1v1 instance containing name of host or null if no team found */
public Team1v1 get1v1Team(String battleHostName) {
if (!is1v1())
throw new TikTokLiveException("Teams are not instance of 1v1 battle!");
List<Team> list = getTeams(battleHostName);
return list.isEmpty() ? null : list.get(0).getAs1v1Team();
}
public Team2v2 get2v2Team(String battleHostName) {
if (!is2v2())
throw new TikTokLiveException("Teams are not instance of 2v2 battle!");
List<Team> list = getTeams(battleHostName);
return list.isEmpty() ? null : list.get(0).getAs2v2Team();
}
/**
* @param battleHostName name of host to search
* @return Team1v1 instance not containing name of host */
public Team1v1 get1v1OpponentTeam(String battleHostName) {
if (!is1v1())
throw new TikTokLiveException("Teams are not instance of 1v1 battle!");
List<Team> list = getTeams(battleHostName);
return list.isEmpty() ? null : list.get(1).getAs1v1Team();
}
public Team2v2 get2x2OpponentTeam(String battleHostName) {
if (!is2v2())
throw new TikTokLiveException("Teams are not instance of 2v2 battle!");
List<Team> list = getTeams(battleHostName);
return list.isEmpty() ? null : list.get(1).getAs2v2Team();
}
/**
* @param battleHostName name of host to search
* @return {@link List<Team>} with host team first, then opponent team
* <p> Empty if host is in neither otherwise always 2 in length;
*/
public List<Team> getTeams(String battleHostName) {
if (is1v1()) {
if (team1.getAs1v1Team().getHost().getName().equals(battleHostName))
return List.of(team1, team2);
if (team2.getAs1v1Team().getHost().getName().equals(battleHostName))
return List.of(team2, team1);
} else {
if (team1.getAs2v2Team().getHosts().stream().anyMatch(user -> user.getName().equals(battleHostName)))
return List.of(team1, team2);
if (team2.getAs2v2Team().getHosts().stream().anyMatch(user -> user.getName().equals(battleHostName)))
return List.of(team2, team1);
}
return List.of();
} }
/** 1 host vs 1 host */
public boolean is1v1() { public boolean is1v1() {
return team1.is1v1Team() || team2.is1v1Team(); return battleType == BattleType.BATTLE_TYPE_NORMAL_BATTLE;
} }
/** 2 hosts vs 2 hosts*/
public boolean is2v2() { public boolean is2v2() {
return team1.is2v2Team() || team2.is2v2Team(); return battleType == BattleType.BATTLE_TYPE_TEAM_BATTLE;
}
/** Up to four users battling each other all on separate teams */
public boolean isIndividual() {
return battleType == BattleType.BATTLE_TYPE_INDIVIDUAL_BATTLE;
}
/** 1 host vs N hosts | N max value unknown */
public boolean isMultiTeam() {
return battleType == BattleType.BATTLE_TYPE_1_V_N;
} }
public boolean isTie() { public boolean isTie() {
return isFinished() && team1.getTotalPoints() == team2.getTotalPoints(); return isFinished() && isTeamsTie();
}
private boolean isTeamsTie() {
int referencePoints = teams.get(0).getTotalPoints();
return teams.stream().allMatch(team -> team.getTotalPoints() == referencePoints);
}
/**
* @param battleHostName name of host to search
* @return Team instance containing name of host or null if no team found */
public Team getTeam(String battleHostName) {
List<Team> list = getTeams(battleHostName);
return list.isEmpty() ? null : list.get(0);
}
/**
* @param battleHostName name of host to search
* @return Team instances not containing name of host */
public List<Team> getOpponentTeams(String battleHostName) {
List<Team> list = getTeams(battleHostName);
return list.isEmpty() ? null : list;
}
/**
* @param battleHostName name of host to search
* @return {@link List<Team>} with host team first, then opponent teams
* <p> If host is in neither or teams is empty, returns empty
* <p> Otherwise always teams.length in length;
*/
public List<Team> getTeams(String battleHostName) {
if (teams.isEmpty() || teams.stream().noneMatch(team -> team.contains(battleHostName)))
return Collections.EMPTY_LIST;
Team hostTeam = teams.stream().filter(team -> team.contains(battleHostName)).findFirst().orElseThrow();
List<Team> opponentTeams = teams.stream().filter(team -> !team.contains(battleHostName)).toList();
List<Team> teams = new ArrayList<>();
teams.add(hostTeam);
teams.addAll(opponentTeams);
return teams;
} }
} }

View File

@@ -0,0 +1,15 @@
package io.github.jwdeveloper.tiktok.data.events;
import io.github.jwdeveloper.tiktok.annotations.*;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokHeaderEvent;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkMicBattleItemCard;
import lombok.Getter;
@Getter
@EventMeta(eventType = EventType.Message)
public class TikTokLinkMicBattleItemCard extends TikTokHeaderEvent {
public TikTokLinkMicBattleItemCard(WebcastLinkMicBattleItemCard msg) {
super(msg.getCommon());
}
}

View File

@@ -40,9 +40,9 @@ public class TikTokQuestionEvent extends TikTokHeaderEvent {
public TikTokQuestionEvent(WebcastQuestionNewMessage msg) { public TikTokQuestionEvent(WebcastQuestionNewMessage msg) {
super(msg.getCommon()); super(msg.getCommon());
var data = msg.getDetails(); var data = msg.getDetails();
questionId = data.getId(); questionId = data.getQuestionId();
text = data.getText(); text = data.getContent();
time = data.getTimeStamp(); time = data.getCreateTime();
user = User.map(data.getUser()); user = User.map(data.getUser());
} }
} }

View File

@@ -53,7 +53,7 @@ public class TikTokSubscribeEvent extends TikTokHeaderEvent {
public static TikTokSubscribeEvent of(String userName) { public static TikTokSubscribeEvent of(String userName) {
return new TikTokSubscribeEvent(WebcastMemberMessage.newBuilder() return new TikTokSubscribeEvent(WebcastMemberMessage.newBuilder()
.setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder() .setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder()
.setDisplayId(userName) .setUsername(userName)
.setNickname(userName) .setNickname(userName)
.build()) .build())
.build()); .build());

View File

@@ -22,7 +22,7 @@
*/ */
package io.github.jwdeveloper.tiktok.data.events.common; package io.github.jwdeveloper.tiktok.data.events.common;
import io.github.jwdeveloper.tiktok.messages.data.Common; import io.github.jwdeveloper.tiktok.messages.data.*;
import lombok.Getter; import lombok.Getter;
@Getter @Getter
@@ -31,7 +31,7 @@ public class TikTokHeaderEvent extends TikTokEvent {
private final long roomId; private final long roomId;
private final long timeStamp; private final long timeStamp;
public TikTokHeaderEvent(Common header) { public TikTokHeaderEvent(CommonMessageData header) {
this(header.getMsgId(), header.getRoomId(), header.getCreateTime()); this(header.getMsgId(), header.getRoomId(), header.getCreateTime());
} }

View File

@@ -35,9 +35,15 @@ public class TikTokPreConnectionEvent extends TikTokLiveClientEvent
private final LiveUserData.Response userData; private final LiveUserData.Response userData;
private final LiveData.Response roomData; private final LiveData.Response roomData;
@Setter boolean cancelConnection = false; @Setter boolean cancelConnection = false;
@Setter String reason = "TikTokPreConnectionEvent cancelled connection!";
public TikTokPreConnectionEvent(LiveUserData.Response userData, LiveData.Response liveData) { public TikTokPreConnectionEvent(LiveUserData.Response userData, LiveData.Response liveData) {
this.userData = userData; this.userData = userData;
this.roomData = liveData; this.roomData = liveData;
} }
public void setCancelConnection(boolean cancelConnection, String reason) {
this.cancelConnection = cancelConnection;
this.reason = reason;
}
} }

View File

@@ -24,6 +24,7 @@ package io.github.jwdeveloper.tiktok.data.events.link;
import io.github.jwdeveloper.tiktok.annotations.*; import io.github.jwdeveloper.tiktok.annotations.*;
import io.github.jwdeveloper.tiktok.data.models.users.ListUser; import io.github.jwdeveloper.tiktok.data.models.users.ListUser;
import io.github.jwdeveloper.tiktok.messages.enums.LinkmicMultiLiveEnum;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkMessage; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkMessage;
import lombok.Getter; import lombok.Getter;
@@ -34,7 +35,7 @@ import java.util.List;
public class TikTokLinkEnterEvent extends TikTokLinkEvent { public class TikTokLinkEnterEvent extends TikTokLinkEvent {
private final List<ListUser> listUsers; private final List<ListUser> listUsers;
private final int anchorMultiLiveEnum; private final LinkmicMultiLiveEnum anchorMultiLiveEnum;
public TikTokLinkEnterEvent(WebcastLinkMessage msg) { public TikTokLinkEnterEvent(WebcastLinkMessage msg) {
super(msg); super(msg);

View File

@@ -23,6 +23,7 @@
package io.github.jwdeveloper.tiktok.data.events.link; package io.github.jwdeveloper.tiktok.data.events.link;
import io.github.jwdeveloper.tiktok.annotations.*; import io.github.jwdeveloper.tiktok.annotations.*;
import io.github.jwdeveloper.tiktok.messages.enums.*;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkMessage; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkMessage;
import lombok.Getter; import lombok.Getter;
@@ -30,7 +31,11 @@ import lombok.Getter;
@EventMeta(eventType = EventType.Message) @EventMeta(eventType = EventType.Message)
public class TikTokLinkMediaChangeEvent extends TikTokLinkEvent { public class TikTokLinkMediaChangeEvent extends TikTokLinkEvent {
private final long op, toUserId, anchorId, roomId, changeScene; private final GuestMicCameraManageOp op;
private final long toUserId;
private final long anchorId;
private final long roomId;
private final GuestMicCameraChangeScene changeScene;
public TikTokLinkMediaChangeEvent(WebcastLinkMessage msg) { public TikTokLinkMediaChangeEvent(WebcastLinkMessage msg) {
super(msg); super(msg);

View File

@@ -23,6 +23,7 @@
package io.github.jwdeveloper.tiktok.data.events.link; package io.github.jwdeveloper.tiktok.data.events.link;
import io.github.jwdeveloper.tiktok.annotations.*; import io.github.jwdeveloper.tiktok.annotations.*;
import io.github.jwdeveloper.tiktok.messages.enums.MuteStatus;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkMessage; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkMessage;
import lombok.Getter; import lombok.Getter;
@@ -30,7 +31,8 @@ import lombok.Getter;
@EventMeta(eventType = EventType.Message) @EventMeta(eventType = EventType.Message)
public class TikTokLinkMuteEvent extends TikTokLinkEvent { public class TikTokLinkMuteEvent extends TikTokLinkEvent {
private final long userId, status; private final long userId;
private final MuteStatus status;
public TikTokLinkMuteEvent(WebcastLinkMessage msg) { public TikTokLinkMuteEvent(WebcastLinkMessage msg) {
super(msg); super(msg);
@@ -41,4 +43,8 @@ public class TikTokLinkMuteEvent extends TikTokLinkEvent {
this.userId = content.getUserId(); this.userId = content.getUserId();
this.status = content.getStatus(); this.status = content.getStatus();
} }
public boolean isMuted() {
return status == MuteStatus.MUTE_STATUS_MUTE;
}
} }

View File

@@ -39,7 +39,7 @@ public class TikTokRoomPinEvent extends TikTokHeaderEvent
public TikTokRoomPinEvent(WebcastRoomPinMessage msg, TikTokCommentEvent commentEvent) public TikTokRoomPinEvent(WebcastRoomPinMessage msg, TikTokCommentEvent commentEvent)
{ {
super(msg.getCommon()); super(msg.getCommon());
this.timestamp = msg.getTimestamp(); this.timestamp = msg.getPinTime();
this.pinnedMessage = commentEvent; this.pinnedMessage = commentEvent;
} }
} }

View File

@@ -45,7 +45,7 @@ public class TikTokFollowEvent extends TikTokHeaderEvent {
public static TikTokFollowEvent of(String userName) { public static TikTokFollowEvent of(String userName) {
return new TikTokFollowEvent(WebcastSocialMessage.newBuilder() return new TikTokFollowEvent(WebcastSocialMessage.newBuilder()
.setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder() .setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder()
.setDisplayId(userName) .setUsername(userName)
.setNickname(userName) .setNickname(userName)
.build()) .build())
.build()); .build());

View File

@@ -52,7 +52,7 @@ public class TikTokJoinEvent extends TikTokHeaderEvent {
{ {
return new TikTokJoinEvent(WebcastMemberMessage.newBuilder() return new TikTokJoinEvent(WebcastMemberMessage.newBuilder()
.setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder() .setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder()
.setDisplayId(userName) .setUsername(userName)
.setNickname(userName) .setNickname(userName)
.build()) .build())
.build()); .build());

View File

@@ -62,7 +62,7 @@ public class TikTokLikeEvent extends TikTokHeaderEvent
.setCount(likes) .setCount(likes)
.setTotal(likes) .setTotal(likes)
.setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder() .setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder()
.setDisplayId(userName) .setUsername(userName)
.setNickname(userName) .setNickname(userName)
.build()) .build())
.build()); .build());

View File

@@ -22,11 +22,9 @@
*/ */
package io.github.jwdeveloper.tiktok.data.events.social; package io.github.jwdeveloper.tiktok.data.events.social;
import io.github.jwdeveloper.tiktok.annotations.EventMeta; import io.github.jwdeveloper.tiktok.annotations.*;
import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokHeaderEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokHeaderEvent;
import io.github.jwdeveloper.tiktok.data.models.users.User; import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLikeMessage;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastSocialMessage; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastSocialMessage;
import lombok.Getter; import lombok.Getter;
@@ -52,7 +50,7 @@ public class TikTokShareEvent extends TikTokHeaderEvent {
public static TikTokShareEvent of(String userName, int shaders) { public static TikTokShareEvent of(String userName, int shaders) {
return new TikTokShareEvent(WebcastSocialMessage.newBuilder() return new TikTokShareEvent(WebcastSocialMessage.newBuilder()
.setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder() .setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder()
.setDisplayId(userName) .setUsername(userName)
.setNickname(userName) .setNickname(userName)
.build()) .build())
.build(), shaders); .build(), shaders);

View File

@@ -22,17 +22,11 @@
*/ */
package io.github.jwdeveloper.tiktok.data.events.websocket; package io.github.jwdeveloper.tiktok.data.events.websocket;
import io.github.jwdeveloper.tiktok.annotations.EventMeta; import io.github.jwdeveloper.tiktok.annotations.*;
import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.data.dto.MessageMetaData; import io.github.jwdeveloper.tiktok.data.dto.MessageMetaData;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage; import io.github.jwdeveloper.tiktok.messages.webcast.ProtoMessageFetchResult;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse; import lombok.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Value;
import java.time.Duration;
@@ -47,7 +41,7 @@ public class TikTokWebsocketMessageEvent extends TikTokEvent {
* message.payload - Bytes array that contains actual data of message. * message.payload - Bytes array that contains actual data of message.
* Example of parsing, WebcastGiftMessage giftMessage = WebcastGiftMessage.parseFrom(message.getPayload()); * Example of parsing, WebcastGiftMessage giftMessage = WebcastGiftMessage.parseFrom(message.getPayload());
*/ */
private WebcastResponse.Message message; private ProtoMessageFetchResult.BaseProtoMessage message;
/* /*
* TikTokLiveJava event that was created from TikTok message data * TikTokLiveJava event that was created from TikTok message data

View File

@@ -22,17 +22,15 @@
*/ */
package io.github.jwdeveloper.tiktok.data.events.websocket; package io.github.jwdeveloper.tiktok.data.events.websocket;
import io.github.jwdeveloper.tiktok.annotations.EventMeta; import io.github.jwdeveloper.tiktok.annotations.*;
import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse; import io.github.jwdeveloper.tiktok.messages.webcast.ProtoMessageFetchResult;
import lombok.AllArgsConstructor; import lombok.*;
import lombok.Getter;
@Getter @Getter
@AllArgsConstructor @AllArgsConstructor
@EventMeta(eventType = EventType.Debug) @EventMeta(eventType = EventType.Debug)
public class TikTokWebsocketResponseEvent extends TikTokEvent public class TikTokWebsocketResponseEvent extends TikTokEvent
{ {
private WebcastResponse response; private ProtoMessageFetchResult response;
} }

View File

@@ -25,18 +25,18 @@ package io.github.jwdeveloper.tiktok.data.events.websocket;
import io.github.jwdeveloper.tiktok.annotations.EventMeta; import io.github.jwdeveloper.tiktok.annotations.EventMeta;
import io.github.jwdeveloper.tiktok.annotations.EventType; import io.github.jwdeveloper.tiktok.annotations.EventType;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokUnhandledEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokUnhandledEvent;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse; import io.github.jwdeveloper.tiktok.messages.webcast.ProtoMessageFetchResult;
import lombok.Getter; import lombok.Getter;
@Getter @Getter
@EventMeta(eventType = EventType.Debug) @EventMeta(eventType = EventType.Debug)
public class TikTokWebsocketUnhandledMessageEvent extends TikTokUnhandledEvent<WebcastResponse.Message> public class TikTokWebsocketUnhandledMessageEvent extends TikTokUnhandledEvent<ProtoMessageFetchResult.BaseProtoMessage>
{ {
public TikTokWebsocketUnhandledMessageEvent(WebcastResponse.Message data) { public TikTokWebsocketUnhandledMessageEvent(ProtoMessageFetchResult.BaseProtoMessage data) {
super(data); super(data);
} }
public WebcastResponse.Message getMessage() public ProtoMessageFetchResult.BaseProtoMessage getMessage()
{ {
return this.getData(); return this.getData();
} }

View File

@@ -23,27 +23,21 @@
package io.github.jwdeveloper.tiktok.data.models; package io.github.jwdeveloper.tiktok.data.models;
import io.github.jwdeveloper.tiktok.data.models.users.User; import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.messages.data.LinkMicArmiesItems; import io.github.jwdeveloper.tiktok.messages.data.BattleUserArmies;
import lombok.Value; import lombok.Value;
import java.util.List; import java.util.*;
import java.util.stream.Collectors;
@Value @Value
public class LinkMicArmy { public class LinkMicArmy {
Long armyId; Long armyId;
List<Army> armies; int totalPoints;
Map<User, Integer> armies;
public LinkMicArmy(LinkMicArmiesItems army) { public LinkMicArmy(BattleUserArmies userArmies) {
armyId = army.getHostUserId(); armyId = Long.parseLong(userArmies.getAnchorIdStr());
armies = army.getBattleGroupsList() totalPoints = (int) userArmies.getHostScore();
.stream() armies = userArmies.getUserArmyList().stream().collect(Collectors.toMap(User::new, bua -> (int) bua.getScore()));
.map(x -> new Army(x.getUsersList().stream().map(User::map).toList(), x.getPoints()))
.toList();
}
@Value
public static class Army {
List<User> Users;
Integer Points;
} }
} }

View File

@@ -25,7 +25,7 @@ package io.github.jwdeveloper.tiktok.data.models.badges;
public class Badge { public class Badge {
public static Badge map(io.github.jwdeveloper.tiktok.messages.data.BadgeStruct badge) { public static Badge map(io.github.jwdeveloper.tiktok.messages.data.BadgeStruct badge) {
return switch (badge.getDisplayType()) { return switch (badge.getBadgeDisplayType()) {
case BADGEDISPLAYTYPE_TEXT -> new TextBadge(badge.getText()); case BADGEDISPLAYTYPE_TEXT -> new TextBadge(badge.getText());
case BADGEDISPLAYTYPE_IMAGE -> new PictureBadge(badge.getImage()); case BADGEDISPLAYTYPE_IMAGE -> new PictureBadge(badge.getImage());
case BADGEDISPLAYTYPE_STRING -> new StringBadge(badge.getStr()); case BADGEDISPLAYTYPE_STRING -> new StringBadge(badge.getStr());

View File

@@ -22,53 +22,68 @@
*/ */
package io.github.jwdeveloper.tiktok.data.models.battles; package io.github.jwdeveloper.tiktok.data.models.battles;
import lombok.Getter; import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.messages.data.BattleUserInfo;
import io.github.jwdeveloper.tiktok.messages.enums.BattleType;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkMicBattle;
import lombok.Data;
public abstract class Team { import java.util.*;
@Data
public class Team {
/** TeamId used for all battle types */
private final long teamId;
/** Value >= 0 when finished otherwise -1 */ /** Value >= 0 when finished otherwise -1 */
@Getter protected int totalPoints; private int totalPoints = -1;
/** Value >= 0 when battle type is {@link BattleType}.{@code BATTLE_TYPE_NORMAL_BATTLE} otherwise -1 */
private int winStreak = -1;
/** Up to N hosts */
private final List<User> hosts;
/** Populated when finished */
private Map<User, Integer> viewers = new HashMap<>();
/** // public Team(WebcastLinkMicBattle.BattleUserInfoWrapper anchorInfo, WebcastLinkMicBattle msg) {
* Provides a check for verifying if this team represents a 1v1 Team. // long hostId = anchorInfo.getUserId();
* @return true if this team is of type {@link Team1v1}, false otherwise. // this.winStreak = msg.getBattleCombosOrDefault(hostId, WebcastLinkMicBattle.BattleComboInfo.newBuilder().setComboCount(-1).build()).getComboCount();
*/ // this.totalPoints = (int) msg.getBattleResultOrDefault(hostId, WebcastLinkMicBattle.BattleResult.newBuilder().setScore(-1).build()).getScore();
public boolean is1v1Team() { // this.host = new User(anchorInfo.getUserInfo().getUser());
return this instanceof Team1v1; // this.viewers = new HashMap<>();
// Optional.ofNullable(msg.getArmiesMap().get(host.getId())).ifPresent(armies ->
// armies.getUserArmiesList().forEach(userArmy ->
// viewers.put(new User(userArmy), (int) userArmy.getScore())));
// }
//
// public Team(WebcastLinkMicBattle.BattleTeamResult battleTeamResult, WebcastLinkMicBattle msg) {
// this.totalPoints = (int) battleTeamResult.getTotalScore();
// var host = new User(msg.getAnchorInfoList().stream().filter(data -> data.getUserId() == battleTeamResult.getTeamUsers(0).getUserId()).findFirst().orElseThrow().getUserInfo().getUser());
// var cohost = new User(msg.getAnchorInfoList().stream().filter(data -> data.getUserId() == battleTeamResult.getTeamUsers(1).getUserId()).findFirst().orElseThrow().getUserInfo().getUser());
// this.hosts = List.of(host, cohost);
// this.viewers = new HashMap<>();
// Optional.ofNullable(msg.getArmiesMap().get(host.getId())).ifPresent(armies ->
// armies.getUserArmiesList().forEach(userArmy ->
// viewers.put(new User(userArmy), (int) userArmy.getScore())));
// Optional.ofNullable(msg.getArmiesMap().get(cohost.getId())).ifPresent(armies ->
// armies.getUserArmiesList().forEach(userArmy ->
// viewers.put(new User(userArmy), (int) userArmy.getScore())));
// }
//
public Team(long teamId, List<User> hosts) {
this.teamId = teamId;
this.hosts = List.copyOf(hosts);
} }
/** public Team(BattleUserInfo anchorInfo) {
* Provides a check for verifying if this team represents a 1v1 Team. this.hosts = List.of(new User(anchorInfo.getUser()));
* @return true if this team is of type {@link Team1v1}, false otherwise. this.teamId = hosts.get(0).getId();
*/
public boolean is2v2Team() {
return this instanceof Team2v2;
} }
/** public Team(BattleUserInfo anchorInfo, WebcastLinkMicBattle.BattleComboInfo battleCombo) {
* Convenience method to get this team as a {@link Team1v1}. If this team is of some this(anchorInfo);
* other type, an {@link IllegalStateException} will result. Hence it is best to use this method this.winStreak = (int) battleCombo.getComboCount();
* 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);
} }
/** public boolean contains(String name) {
* Convenience method to get this team as a {@link Team2v2}. If this team is of some return hosts.stream().anyMatch(user -> user.getName().equals(name));
* other type, an {@link IllegalStateException} will result. Hence it is best to use this method
* after ensuring that this element is of the desired type by calling {@link #is2v2Team()} first.
*
* @return this team as a {@link Team2v2}.
* @throws IllegalStateException if this team is of another type.
*/
public Team2v2 getAs2v2Team() {
if (is2v2Team())
return (Team2v2) this;
throw new IllegalStateException("Not a 2v2Team: " + this);
} }
} }

View File

@@ -1,45 +0,0 @@
/*
* Copyright (c) 2023-2024 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.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 {
private final int winStreak;
private final User host;
private final List<Viewer> viewers;
public Team1v1(WebcastLinkMicBattle.LinkMicBattleHost hostTeam, WebcastLinkMicBattle msg) {
long hostId = hostTeam.getId();
this.winStreak = msg.getTeamDataList().stream().filter(data -> data.getTeamId() == hostId).map(data -> data.getData().getWinStreak()).findFirst().orElse(-1);
this.totalPoints = msg.getDetailsList().stream().filter(dets -> dets.getId() == hostId).map(dets -> dets.getSummary().getPoints()).findFirst().orElse(-1);
this.host = new User(hostTeam.getHostGroup(0).getHost(0));
this.viewers = msg.getViewerTeamList().stream().filter(team -> team.getId() == hostId).findFirst().map(topViewers ->
topViewers.getViewerGroup(0).getViewerList().stream().map(Viewer::new).toList()).orElseGet(ArrayList::new);
}
}

View File

@@ -1,50 +0,0 @@
/*
* Copyright (c) 2023-2024 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.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 {
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

@@ -48,7 +48,6 @@ public class Gift {
this.properties = properties; this.properties = properties;
} }
public Gift(int id, String name, int diamondCost, String pictureLink) { public Gift(int id, String name, int diamondCost, String pictureLink) {
this(id, name, diamondCost, new Picture(pictureLink), new JsonObject()); this(id, name, diamondCost, new Picture(pictureLink), new JsonObject());
} }

View File

@@ -22,6 +22,7 @@
*/ */
package io.github.jwdeveloper.tiktok.data.models.users; package io.github.jwdeveloper.tiktok.data.models.users;
import io.github.jwdeveloper.tiktok.messages.enums.*;
import lombok.Getter; import lombok.Getter;
@Getter @Getter
@@ -29,14 +30,19 @@ public class ListUser
{ {
private final User user; private final User user;
private final LinkType linkType; private final LinkType linkType;
private final long linkMicId, linkStatus, modifyTime, linkerId; private final long linkMicId;
private final int userPosition, silenceStatus, roleType; private final LinkmicRoleType linkStatus;
private final long modifyTime;
private final long linkerId;
private final int userPosition;
private final LinkSilenceStatus silenceStatus;
private final LinkRoleType roleType;
public ListUser(io.github.jwdeveloper.tiktok.messages.data.ListUser listUser) { public ListUser(io.github.jwdeveloper.tiktok.messages.data.ListUser listUser) {
this.user = User.map(listUser.getUser()); this.user = User.map(listUser.getUser());
this.linkMicId = listUser.getLinkmicId(); this.linkMicId = listUser.getLinkmicId();
this.linkStatus = listUser.getLinkStatus(); this.linkStatus = listUser.getLinkStatus();
this.linkType = LinkType.values()[listUser.getLinkTypeValue()]; this.linkType = listUser.getLinkType();
this.userPosition = listUser.getUserPosition(); this.userPosition = listUser.getUserPosition();
this.silenceStatus = listUser.getSilenceStatus(); this.silenceStatus = listUser.getSilenceStatus();
this.modifyTime = listUser.getModifyTime(); this.modifyTime = listUser.getModifyTime();
@@ -48,12 +54,6 @@ public class ListUser
return new ListUser(listUser); return new ListUser(listUser);
} }
public enum LinkType {
UNKNOWN,
AUDIO,
VIDEO
}
@Override @Override
public String toString() { public String toString() {
return "ListUser{" + return "ListUser{" +

View File

@@ -24,6 +24,7 @@ package io.github.jwdeveloper.tiktok.data.models.users;
import io.github.jwdeveloper.tiktok.data.models.Picture; import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.badges.Badge; import io.github.jwdeveloper.tiktok.data.models.badges.Badge;
import io.github.jwdeveloper.tiktok.messages.data.*;
import io.github.jwdeveloper.tiktok.messages.webcast.*; import io.github.jwdeveloper.tiktok.messages.webcast.*;
import lombok.*; import lombok.*;
@@ -139,12 +140,16 @@ public class User {
this(id, name, profileId, null, picture, 0, 0, List.of(Badge.empty())); this(id, name, profileId, null, picture, 0, 0, List.of(Badge.empty()));
} }
public User(WebcastLinkMicBattle.LinkMicBattleHost.HostGroup.Host host) { public User(BattleUserInfo.BattleBaseUserInfo host) {
this(host.getId(), host.getName(), host.getProfileId(), Picture.map(host.getImages(0))); this(host.getUserId(), host.getDisplayId(), host.getNickName(), Picture.map(host.getAvatarThumb()));
}
public User(BattleUserArmy topViewer) {
this(topViewer.getUserId(), topViewer.getNickname(), Picture.map(topViewer.getAvatarThumb()));
} }
public User(io.github.jwdeveloper.tiktok.messages.data.User user) { public User(io.github.jwdeveloper.tiktok.messages.data.User user) {
this(user.getId(), user.getDisplayId(), Picture.map(user.getAvatarThumb())); this(user.getId(), user.getUsername(), Picture.map(user.getAvatarThumb()));
signature = user.getBioDescription(); signature = user.getBioDescription();
profileName = user.getNickname(); profileName = user.getNickname();
following = user.getFollowInfo().getFollowingCount(); following = user.getFollowInfo().getFollowingCount();

View File

@@ -22,10 +22,8 @@
*/ */
package io.github.jwdeveloper.tiktok.data.requests; package io.github.jwdeveloper.tiktok.data.requests;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse; import io.github.jwdeveloper.tiktok.messages.webcast.ProtoMessageFetchResult;
import lombok.AllArgsConstructor; import lombok.*;
import lombok.Data;
import lombok.Getter;
import java.net.URI; import java.net.URI;
@@ -41,6 +39,6 @@ public class LiveConnectionData {
public static class Response { public static class Response {
private final String websocketCookies; private final String websocketCookies;
private final URI websocketUrl; private final URI websocketUrl;
private final WebcastResponse webcastResponse; private final ProtoMessageFetchResult webcastResponse;
} }
} }

View File

@@ -52,6 +52,7 @@ public class LiveData {
public enum LiveStatus { public enum LiveStatus {
HostNotFound, HostNotFound,
HostOnline, HostOnline,
HostPaused,
HostOffline, HostOffline,
} }

View File

@@ -22,7 +22,7 @@
*/ */
package io.github.jwdeveloper.tiktok.data.requests; package io.github.jwdeveloper.tiktok.data.requests;
import io.github.jwdeveloper.tiktok.data.models.users.User; import io.github.jwdeveloper.tiktok.live.LiveRoomInfo;
import lombok.*; import lombok.*;
public class LiveUserData { public class LiveUserData {
@@ -43,9 +43,7 @@ public class LiveUserData {
public static class Response { public static class Response {
private final String json; private final String json;
private final UserStatus userStatus; private final UserStatus userStatus;
private final String roomId; private final LiveRoomInfo roomInfo;
private final long startTime;
private final User user;
public boolean isLiveOnline() { public boolean isLiveOnline() {
return userStatus == LiveUserData.UserStatus.Live || userStatus == LiveUserData.UserStatus.LivePaused; return userStatus == LiveUserData.UserStatus.Live || userStatus == LiveUserData.UserStatus.LivePaused;

View File

@@ -89,12 +89,29 @@ public class LiveClientSettings {
/** Throw an exception on 18+ Age Restriction */ /** Throw an exception on 18+ Age Restriction */
private boolean throwOnAgeRestriction; private boolean throwOnAgeRestriction;
/** Use Eulerstream.com websocket for events
* @apiNote Requires API Key
*/
private boolean useEulerstreamWebsocket;
/** Use Eulerstream.com enterprise endpoints
* @apiNote Requires API Key with
*/
private boolean useEulerstreamEnterprise;
/** /**
* Optional: Sometimes not every messages from chat are send to TikTokLiveJava to fix this issue you can set sessionId * Optional: Sometimes not every messages from chat are send to TikTokLiveJava to fix this issue you can set sessionId.
* @see <a href="https://github.com/isaackogan/TikTok-Live-Connector#send-chat-messages">Documentation: How to obtain sessionId</a> * <p>This requires {@link #ttTargetIdc} also being set correctly for sessionid to be effective.
* @apiNote This cookie is supplied by <a href="https://www.tiktok.com">TikTok</a> and can be found in your browser cookies.
*/ */
private String sessionId; private String sessionId;
/**
* Used with {@link #sessionId} to verify it is valid and return extra chat messages and 18+ content.
* @apiNote This cookie is supplied by <a href="https://www.tiktok.com">TikTok</a> and can be found in your browser cookies.
*/
private String ttTargetIdc;
/** /**
* Optional: By default roomID is fetched before connect to live, but you can set it manually * Optional: By default roomID is fetched before connect to live, but you can set it manually
*/ */
@@ -153,8 +170,8 @@ public class LiveClientSettings {
clientParams.put("screen_height", 1152); clientParams.put("screen_height", 1152);
clientParams.put("screen_width", 2048); clientParams.put("screen_width", 2048);
clientParams.put("tz_name", "Europe/Berlin"); clientParams.put("tz_name", "Europe/Berlin");
clientParams.put("referer", "https, //www.tiktok.com/"); clientParams.put("referer", "https://www.tiktok.com/");
clientParams.put("root_referer", "https, //www.tiktok.com/"); clientParams.put("root_referer", "https://www.tiktok.com/");
clientParams.put("msToken", ""); clientParams.put("msToken", "");
clientParams.put("version_code", 180800); clientParams.put("version_code", 180800);
clientParams.put("webcast_sdk_version", "1.3.0"); clientParams.put("webcast_sdk_version", "1.3.0");

View File

@@ -23,7 +23,7 @@
package io.github.jwdeveloper.tiktok.exceptions; package io.github.jwdeveloper.tiktok.exceptions;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse; import io.github.jwdeveloper.tiktok.messages.webcast.ProtoMessageFetchResult;
import lombok.Getter; import lombok.Getter;
import java.util.Base64; import java.util.Base64;
@@ -31,13 +31,13 @@ import java.util.Base64;
public class TikTokLiveMessageException extends TikTokLiveException { public class TikTokLiveMessageException extends TikTokLiveException {
@Getter @Getter
private final WebcastResponse.Message webcastMessage; private final ProtoMessageFetchResult.BaseProtoMessage webcastMessage;
@Getter @Getter
private final WebcastResponse webcastResponse; private final ProtoMessageFetchResult webcastResponse;
public TikTokLiveMessageException(WebcastResponse.Message message, public TikTokLiveMessageException(ProtoMessageFetchResult.BaseProtoMessage message,
WebcastResponse webcastResponse, ProtoMessageFetchResult webcastResponse,
Throwable cause) { Throwable cause) {
super("Error while handling Message: " + message.getMethod() + ": \n", cause); super("Error while handling Message: " + message.getMethod() + ": \n", cause);
this.webcastMessage = message; this.webcastMessage = message;

View File

@@ -22,9 +22,18 @@
*/ */
package io.github.jwdeveloper.tiktok.exceptions; package io.github.jwdeveloper.tiktok.exceptions;
import io.github.jwdeveloper.tiktok.data.requests.*;
import lombok.Getter;
@Getter
public class TikTokLiveOfflineHostException extends TikTokLiveException public class TikTokLiveOfflineHostException extends TikTokLiveException
{ {
public TikTokLiveOfflineHostException(String message) { private final LiveUserData.Response userData;
private final LiveData.Response liveData;
public TikTokLiveOfflineHostException(String message, LiveUserData.Response userData, LiveData.Response liveData) {
super(message); super(message);
this.userData = userData;
this.liveData = liveData;
} }
} }

View File

@@ -20,20 +20,18 @@
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/ */
package io.github.jwdeveloper.tiktok.data.models.battles; package io.github.jwdeveloper.tiktok.exceptions;
import io.github.jwdeveloper.tiktok.data.models.Picture; import io.github.jwdeveloper.tiktok.data.events.control.TikTokPreConnectionEvent;
import io.github.jwdeveloper.tiktok.data.models.users.User;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkMicBattle;
import lombok.Getter; import lombok.Getter;
@Getter @Getter
public class Viewer { public class TikTokLivePreConnectionException extends TikTokLiveException
private final User user; {
private final int points; private final TikTokPreConnectionEvent preconnectEvent;
public Viewer(WebcastLinkMicBattle.LinkMicBattleTopViewers.TopViewerGroup.TopViewer topViewer) { public TikTokLivePreConnectionException(TikTokPreConnectionEvent preconnectEvent) {
this.user = new User(topViewer.getId(), null, topViewer.getProfileId(), Picture.map(topViewer.getImages(0))); super(preconnectEvent.getReason());
this.points = topViewer.getPoints(); this.preconnectEvent = preconnectEvent;
} }
} }

View File

@@ -22,9 +22,18 @@
*/ */
package io.github.jwdeveloper.tiktok.exceptions; package io.github.jwdeveloper.tiktok.exceptions;
import io.github.jwdeveloper.tiktok.data.requests.*;
import lombok.Getter;
@Getter
public class TikTokLiveUnknownHostException extends TikTokLiveException public class TikTokLiveUnknownHostException extends TikTokLiveException
{ {
public TikTokLiveUnknownHostException(String message) { private final LiveUserData.Response userData;
private final LiveData.Response liveData;
public TikTokLiveUnknownHostException(String message, LiveUserData.Response userData, LiveData.Response liveData) {
super(message); super(message);
this.userData = userData;
this.liveData = liveData;
} }
} }

View File

@@ -26,14 +26,10 @@ import io.github.jwdeveloper.tiktok.data.requests.GiftsData;
import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData; import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData;
import io.github.jwdeveloper.tiktok.data.requests.LiveData; import io.github.jwdeveloper.tiktok.data.requests.LiveData;
import io.github.jwdeveloper.tiktok.data.requests.LiveUserData; import io.github.jwdeveloper.tiktok.data.requests.LiveUserData;
import io.github.jwdeveloper.tiktok.live.LiveRoomInfo;
public interface LiveHttpClient public interface LiveHttpClient
{ {
/**
* @return {@link GiftsData.Response} list of gifts that are compiled and available on github
*/
GiftsData.Response fetchGiftsData();
/** /**
* @return {@link GiftsData.Response} list of gifts that are available in your region / livestream * @return {@link GiftsData.Response} list of gifts that are available in your region / livestream
*/ */
@@ -69,4 +65,6 @@ public interface LiveHttpClient
} }
LiveConnectionData.Response fetchLiveConnectionData(LiveConnectionData.Request request); LiveConnectionData.Response fetchLiveConnectionData(LiveConnectionData.Request request);
boolean sendChat(LiveRoomInfo roomInfo, String content);
} }

View File

@@ -1,73 +0,0 @@
/*
* Copyright (c) 2023-2024 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.listener;
import io.github.jwdeveloper.tiktok.live.LiveClient;
/**
* ListenersManager
* <p>
* TikTokEventListener is an alternative way of handing TikTok events.
* <p>
* {@code TikTokLive.newClient("someuser").addListener(listener);}
* <p>
* After registertion, all listeners are kept in Listener manager - {@link LiveClient#getListenersManager()}
* <p>
* Method in TikTokEventListener should meet requirements below to be detected
* <p>- @TikTokEventObserver annotation
* <p>- 2 parameters of (LiveClient, Class extending TikTokEvent)
* <pre>
* {@code
* public static class CustomListener
* {
* @TikTokEventObserver
* public void onError(LiveClient liveClient, TikTokErrorEvent event)
* {
* System.out.println(event.getException().getMessage());
* }
*
* @TikTokEventObserver
* public void onCommentMessage(LiveClient liveClient, TikTokCommentEvent event)
* {
* System.out.println(event.getText());
* }
*
* @TikTokEventObserver
* public void onGiftMessage(LiveClient liveClient, TikTokGiftMessageEvent event)
* {
* System.out.println(event.getGift().getDescription());
* }
*
* @TikTokEventObserver
* public void onAnyEvent(LiveClient liveClient, TikTokEvent event)
* {
* System.out.println(event.getClass().getSimpleName());
* }
* }
* }
* </pre>
*/
@Deprecated(forRemoval = true, since = "1.8.1 (This interface is not longer needed, please remove it from your class) | Removing in 1.9.0")
public interface TikTokEventListener {
}

View File

@@ -22,12 +22,8 @@
*/ */
package io.github.jwdeveloper.tiktok.live; package io.github.jwdeveloper.tiktok.live;
import com.google.gson.JsonObject;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.gifts.*; import io.github.jwdeveloper.tiktok.data.models.gifts.*;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Predicate; import java.util.function.Predicate;

View File

@@ -24,6 +24,7 @@ package io.github.jwdeveloper.tiktok.live;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.listener.ListenersManager; import io.github.jwdeveloper.tiktok.listener.ListenersManager;
import io.github.jwdeveloper.tiktok.websocket.LiveClientStopType;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -36,7 +37,6 @@ public interface LiveClient {
*/ */
void connect(); void connect();
/** /**
* Connects in asynchronous way * Connects in asynchronous way
* When connected Consumer returns instance of LiveClient * When connected Consumer returns instance of LiveClient
@@ -48,19 +48,25 @@ public interface LiveClient {
*/ */
CompletableFuture<LiveClient> connectAsync(); CompletableFuture<LiveClient> connectAsync();
/** /**
* Disconnects the connection. * Disconnects the connection.
* @param type {@code LiveClientStopType}
* @see LiveClientStopType
*/ */
void disconnect(); void disconnect(LiveClientStopType type);
/**
* Disconnects with {@link LiveClientStopType#NORMAL}
*/
default void disconnect() {
disconnect(LiveClientStopType.NORMAL);
}
/** /**
* Use to manually invoke event * Use to manually invoke event
*/ */
void publishEvent(TikTokEvent event); void publishEvent(TikTokEvent event);
/** /**
* @param webcastMessageName name of TikTok protocol-buffer message * @param webcastMessageName name of TikTok protocol-buffer message
* @param payloadBase64 protocol-buffer message bytes payload * @param payloadBase64 protocol-buffer message bytes payload
@@ -88,4 +94,12 @@ public interface LiveClient {
* Logger * Logger
*/ */
Logger getLogger(); Logger getLogger();
/**
* Send a chat message to the connected room
* @return true if successful, otherwise false
* @apiNote This is known to return true on some sessionIds despite failing!
* <p>We cannot fix this as it is a TikTok issue, not a library issue.
*/
boolean sendChat(String content);
} }

View File

@@ -22,10 +22,10 @@
*/ */
package io.github.jwdeveloper.tiktok.live; package io.github.jwdeveloper.tiktok.live;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse; import io.github.jwdeveloper.tiktok.messages.webcast.ProtoMessageFetchResult;
public interface LiveMessagesHandler { public interface LiveMessagesHandler {
void handle(LiveClient client, WebcastResponse webcastResponse); void handle(LiveClient client, ProtoMessageFetchResult webcastResponse);
void handleSingleMessage(LiveClient client, WebcastResponse.Message message); void handleSingleMessage(LiveClient client, ProtoMessageFetchResult.BaseProtoMessage message);
} }

View File

@@ -49,5 +49,7 @@ public interface LiveRoomInfo
String getTitle(); String getTitle();
User getHost(); User getHost();
List<RankingUser> getUsersRanking(); List<RankingUser> getUsersRanking();
String getLanguage();
ConnectionState getConnectionState(); ConnectionState getConnectionState();
void copy(LiveRoomInfo roomInfo);
} }

View File

@@ -30,9 +30,8 @@ import io.github.jwdeveloper.tiktok.mappers.LiveMapper;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer; import java.util.function.Consumer;
public interface LiveClientBuilder extends EventsBuilder<LiveClientBuilder> { public interface LiveClientBuilder extends EventsBuilder<LiveClientBuilder>
{
/** /**
* This method is triggered after default mappings are registered * This method is triggered after default mappings are registered
* It could be used to OVERRIDE behaviour of mappings and implement custom behaviour * It could be used to OVERRIDE behaviour of mappings and implement custom behaviour
@@ -40,18 +39,15 @@ public interface LiveClientBuilder extends EventsBuilder<LiveClientBuilder> {
* Be aware if for example you override WebcastGiftEvent, onGiftEvent() will not be working * Be aware if for example you override WebcastGiftEvent, onGiftEvent() will not be working
* *
* @param onCustomMappings lambda method * @param onCustomMappings lambda method
* @return * @return {@link LiveClientBuilder}
*/ */
LiveClientBuilder mappings(Consumer<LiveMapper> onCustomMappings); LiveClientBuilder mappings(Consumer<LiveMapper> onCustomMappings);
@Deprecated(forRemoval = true, since = "1.8.2 use `mappings` method instead")
LiveClientBuilder onMappings(Consumer<LiveMapper> onCustomMappings);
/** /**
* Configuration of client settings * Configuration of client settings
* *
* @param onConfigure * @param onConfigure Consumer for {@link LiveClientSettings}
* @return * @return {@link LiveClientBuilder}
* @see LiveClientSettings * @see LiveClientSettings
*/ */
LiveClientBuilder configure(Consumer<LiveClientSettings> onConfigure); LiveClientBuilder configure(Consumer<LiveClientSettings> onConfigure);
@@ -59,7 +55,7 @@ public interface LiveClientBuilder extends EventsBuilder<LiveClientBuilder> {
/** /**
* Adds events listener class, its fancy way to register events without using lamda method * Adds events listener class, its fancy way to register events without using lamda method
* *
* @return * @return {@link LiveClientBuilder}
*/ */
LiveClientBuilder addListener(Object listener); LiveClientBuilder addListener(Object listener);
@@ -69,7 +65,7 @@ public interface LiveClientBuilder extends EventsBuilder<LiveClientBuilder> {
* when the default implementation does not meet your needs * when the default implementation does not meet your needs
* *
* @param onCustomizeDependencies access to dependency container * @param onCustomizeDependencies access to dependency container
* @return * @return {@link LiveClientBuilder}
*/ */
LiveClientBuilder customize(Consumer<DependanceContainerBuilder> onCustomizeDependencies); LiveClientBuilder customize(Consumer<DependanceContainerBuilder> onCustomizeDependencies);

View File

@@ -22,10 +22,8 @@
*/ */
package io.github.jwdeveloper.tiktok.utils; package io.github.jwdeveloper.tiktok.utils;
import com.google.protobuf.ByteString; import com.google.protobuf.*;
import com.google.protobuf.InvalidProtocolBufferException; import io.github.jwdeveloper.tiktok.messages.webcast.ProtoMessageFetchResult;
import com.google.protobuf.UnknownFieldSet;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import java.util.Base64; import java.util.Base64;
@@ -35,7 +33,7 @@ public class ProtocolUtils {
return Base64.getEncoder().encodeToString(bytes); return Base64.getEncoder().encodeToString(bytes);
} }
public static String toBase64(WebcastResponse.Message bytes) { public static String toBase64(ProtoMessageFetchResult.BaseProtoMessage bytes) {
return Base64.getEncoder().encodeToString(bytes.toByteArray()); return Base64.getEncoder().encodeToString(bytes.toByteArray());
} }
@@ -107,9 +105,9 @@ public class ProtocolUtils {
} }
} }
public static WebcastResponse.Message fromBase64ToMessage(String base64) throws InvalidProtocolBufferException { public static ProtoMessageFetchResult.BaseProtoMessage fromBase64ToMessage(String base64) throws InvalidProtocolBufferException {
var bytes = fromBase64(base64); var bytes = fromBase64(base64);
return WebcastResponse.Message.parseFrom(bytes); return ProtoMessageFetchResult.BaseProtoMessage.parseFrom(bytes);
} }

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2023-2024 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.websocket;
public enum LiveClientStopType
{
/**
* Initiates the websocket close handshake. This method does not block<br> In oder to make sure
* the connection is closed use {@link LiveClientStopType#CLOSE_BLOCKING}
*/
NORMAL,
/**
* Same as {@link LiveClientStopType#NORMAL} but blocks until the websocket closed or failed to do so.<br>
*
* @apiNote Can throw {@link InterruptedException} when/if the threads get interrupted
*/
CLOSE_BLOCKING,
/**
* This will close the connection immediately without a proper close handshake.
* The code and the message therefore won't be transferred over the wire also they will be forwarded to onClose/onWebsocketClose. */
DISCONNECT
}

View File

@@ -27,5 +27,6 @@ import io.github.jwdeveloper.tiktok.live.LiveClient;
public interface LiveSocketClient { public interface LiveSocketClient {
void start(LiveConnectionData.Response webcastResponse, LiveClient tikTokLiveClient); void start(LiveConnectionData.Response webcastResponse, LiveClient tikTokLiveClient);
void stop(); void stop(LiveClientStopType type);
boolean isConnected();
} }

File diff suppressed because it is too large Load Diff

View File

@@ -35,14 +35,6 @@ enum LinkmicApplierSortSetting {
LINKMIC_APPLIER_SORT_SETTING_BY_GIFT_SCORE = 1; 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 { enum HashtagNamespace {
GLOBAL = 0; GLOBAL = 0;
GAMING = 1; GAMING = 1;
@@ -64,7 +56,6 @@ enum KickoutReason {
KICKOUT_REASON_HOST_REMOVE_ALL_GUESTS = 6; KICKOUT_REASON_HOST_REMOVE_ALL_GUESTS = 6;
} }
enum GroupStatus { enum GroupStatus {
GROUP_STATUS_UNKNOWN = 0; GROUP_STATUS_UNKNOWN = 0;
GROUP_STATUS_WAITING = 1; GROUP_STATUS_WAITING = 1;
@@ -183,7 +174,6 @@ enum LinkLayerMessageType
Linker_Group_Change = 18; Linker_Group_Change = 18;
} }
enum BarrageType enum BarrageType
{ {
BarrageType_Unknown = 0; BarrageType_Unknown = 0;
@@ -212,21 +202,18 @@ enum EnvelopeBusinessType
BusinessTypeEoYDiamond = 6; BusinessTypeEoYDiamond = 6;
BusinessTypeFanClubGtM = 7; BusinessTypeFanClubGtM = 7;
} }
enum EnvelopeFollowShowStatus enum EnvelopeFollowShowStatus {
{ ENVELOPE_FOLLOW_SHOW_STATUS_ENVELOPE_FOLLOW_SHOW_UNKNOWN = 0;
EnvelopeFollowShowUnknown = 0; ENVELOPE_FOLLOW_SHOW_STATUS_ENVELOPE_FOLLOW_SHOW = 1;
EnvelopeFollowShow = 1; ENVELOPE_FOLLOW_SHOW_STATUS_ENVELOPE_FOLLOW_NOT_SHOW = 2;
EnvelopeFollowNotShow = 2;
} }
enum EnvelopeDisplay enum EnvelopeDisplay {
{ ENVELOPE_DISPLAY_UNKNOWN = 0;
EnvelopeDisplayUnknown = 0; ENVELOPE_DISPLAY_NEW = 1;
EnvelopeDisplayNew = 1; ENVELOPE_DISPLAY_HIDE = 2;
EnvelopeDisplayHide = 2;
} }
enum CommonContentCase { enum CommonContentCase {
COMMON_CONTENT_NOT_SET = 0; COMMON_CONTENT_NOT_SET = 0;
CREATE_CHANNEL_CONTENT = 100; CREATE_CHANNEL_CONTENT = 100;
@@ -249,7 +236,6 @@ enum CommonContentCase {
GROUP_CHANGE_CONTENT = 118; GROUP_CHANGE_CONTENT = 118;
} }
enum LinkMessageType { enum LinkMessageType {
TYPE_LINKER_UNKNOWN = 0; TYPE_LINKER_UNKNOWN = 0;
TYPE_LINKER_CREATE = 1; TYPE_LINKER_CREATE = 1;
@@ -294,6 +280,8 @@ enum MessageType {
MESSAGETYPE_SUBGIFTSENDSUCCEEDANCHORNOTICE = 10; MESSAGETYPE_SUBGIFTSENDSUCCEEDANCHORNOTICE = 10;
MESSAGETYPE_SUBGIFTLOWVERSIONUPGRADENOTICE = 11; MESSAGETYPE_SUBGIFTLOWVERSIONUPGRADENOTICE = 11;
MESSAGETYPE_SUBGIFTUSERBUYAUTHNOTICE = 12; MESSAGETYPE_SUBGIFTUSERBUYAUTHNOTICE = 12;
MESSAGETYPE_SUBCOMMONTEXTNOTICE = 13;
MESSAGETYPE_SUBMODERATORPINPERK = 14;
} }
enum Scene { enum Scene {
@@ -301,3 +289,550 @@ enum Scene {
SCENE_CO_HOST = 2; SCENE_CO_HOST = 2;
SCENE_MULTI_LIVE = 4; SCENE_MULTI_LIVE = 4;
} }
enum RewardCondition {
REWARD_CONDITION_SUBSCRIPTION = 0;
REWARD_CONDITION_SUB_WAVE_CUSTOM = 1;
}
enum UserEmoteUploadSource {
USER_EMOTE_UPLOAD_SOURCE_EMOTE_UPLOAD_SOURCE_ANCHOR = 0;
USER_EMOTE_UPLOAD_SOURCE_EMOTE_UPLOAD_SOURCE_SUBSCRIBER = 1;
USER_EMOTE_UPLOAD_SOURCE_EMOTE_UPLOAD_SOURCE_MODERATOR = 2;
}
enum EmoteScene {
EMOTE_SCENE_SUBSCRIPTION = 0;
EMOTE_SCENE_GAME = 1;
}
enum PunishTypeId {
PUNISH_TYPE_IDUN_KNOWN = 0;
PUNISH_TYPE_ID_BAN_LINK_MIC = 9;
PUNISH_TYPE_ID_BAN_GAME_PARTNERSHIP = 25;
PUNISH_TYPE_ID_REMOVE_GAME_PARTNERSHIP = 26;
PUNISH_TYPE_ID_BANCO_HOST_LINK_MIC = 55;
PUNISH_TYPE_ID_AUTHORITY_LIMIT_MATCH = 57;
PUNISH_TYPE_ID_BAN_VOICE_CHAT = 59;
PUNISH_TYPE_ID_BAN_LIVE_GOAL = 64;
PUNISH_TYPE_ID_VIEWER_LIMIT = 70;
}
enum MultiplierType {
MULTIPLIER_TYPE_UNSPECIFIED = 0;
MULTIPLIER_TYPE_CRITICAL_STRIKE = 1;
MULTIPLIER_TYPE_TOP_2 = 2;
MULTIPLIER_TYPE_TOP_3 = 3;
}
enum LinkmicGiftExpressionStrategy {
LINKMIC_GIFT_EXPRESSION_STRATEGY_CONTROL_V_1 = 0;
LINKMIC_GIFT_EXPRESSION_STRATEGY_EXPERIMENT_V_1 = 1;
LINKMIC_GIFT_EXPRESSION_STRATEGY_EXPERIMENT_V_2 = 2;
}
enum GiftMessageVersion {
GIFT_MESSAGE_VERSION_0 = 0;
GIFT_MESSAGE_VERSION_1 = 1;
}
enum TagType {
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_UNKNOWN = 0;
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_USER_GRADE = 1;
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_FANS_LEVEL = 2;
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_WATCH_ME_DAYS_AGO = 3;
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_CUSTOM = 4;
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_TITLE_GIFT = 5;
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_FIRST_JOINED_TEAM = 6;
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_PAY_ACCOMPANY_DAYS = 7;
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_SPONSOR_GIFT_LAST_ROOM = 8;
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_MATCH_MVP_LAST_ROOM = 9;
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_LARGE_AMOUNT_GIFT_LAST_ROOM = 10;
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_COMMENT_LAST_ROOM = 11;
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_RECENT_TITLED_GIFT = 12;
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_MEET_ANNIVERSARY = 13;
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_FANS_SLEEP = 14;
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_NOT_SEND_HEART_ME = 15;
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_NOT_JOIN_TEAM = 16;
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_FIRST_WATCH_LIVE = 17;
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_RECENT_COMMENT = 18;
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_RECENT_GIFT_TIMES = 19;
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_RECENT_WATCH_LIVE_DURATION = 20;
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_RECENT_GIFT = 21;
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_RECENT_LIVE_CONTRIBUTION_TOP = 22;
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_JUST_UPGRADE = 28;
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_FAN_TOTAL_WATCH_DURATION = 29;
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_FAN_TOTAL_COMMENT_NUM = 30;
TAG_TYPE_CREATOR_CR_M_TAG_TYPE_FAN_TOTAL_GIFT_SENT_NUM = 31;
}
enum TopicActionType {
TOPIC_ACTION_TYPE_UNKNOWN = 0;
TOPIC_ACTION_TYPE_FOLLOW = 1;
}
enum UserMetricsType {
USER_METRICS_TYPE_UNKNOWN = 0;
USER_METRICS_TYPE_GRADE = 1;
USER_METRICS_TYPE_SUBSCRIBE = 2;
USER_METRICS_TYPE_FOLLOW = 3;
USER_METRICS_TYPE_FANS_CLUB = 4;
USER_METRICS_TYPE_TOP_VIEWER = 5;
USER_METRICS_TYPE_GIFT = 6;
}
enum GiftMessageIgnoreConfig {
GIFT_MESSAGE_IGNORE_CONFIG_NOT_IGNORE = 0;
GIFT_MESSAGE_IGNORE_CONFIG_IGNORE_TRAY = 1;
GIFT_MESSAGE_IGNORE_CONFIG_IGNORE_PS_M = 2;
GIFT_MESSAGE_IGNORE_CONFIG_IGNORE_TRAY_AND_PS_M = 3;
}
enum HorizontalOnclickTriggerType {
HORIZONTAL_ONCLICK_TRIGGER_TYPE_ONCLICK_TRIGGER_TYPE_UNKNOWN = 0;
HORIZONTAL_ONCLICK_TRIGGER_TYPE_ONCLICK_TRIGGER_TYPE_LEFT = 1;
HORIZONTAL_ONCLICK_TRIGGER_TYPE_ONCLICK_TRIGGER_TYPE_MIDDLE = 2;
HORIZONTAL_ONCLICK_TRIGGER_TYPE_ONCLICK_TRIGGER_TYPE_RIGHT = 3;
HORIZONTAL_ONCLICK_TRIGGER_TYPE_ONCLICK_TRIGGER_TYPE_ALL_AREA = 4;
}
enum ShowType {
SHOW_TYPE_NORMAL = 0;
SHOW_TYPE_FADE_IN_OUT = 1;
}
enum RenderType {
RENDER_TYPE_NATIVE = 0;
RENDER_TYPE_HYBRID = 1;
RENDER_TYPE_ALPHA = 2;
}
enum IconDisplayType {
ICON_DISPLAY_TYPE_IMAGE = 0;
ICON_DISPLAY_TYPE_BADGE = 1;
}
enum CommentTag {
COMMENT_TAG_NORMAL = 0;
COMMENT_TAG_CANDIDATE = 1;
COMMENT_TAG_OVERAGE = 2;
}
enum PerceptionDialogIconType {
PERCEPTION_DIALOG_ICON_TYPE_ICON_TYPE_NONE = 0;
PERCEPTION_DIALOG_ICON_TYPE_ICON_TYPE_WARNING = 1;
PERCEPTION_DIALOG_ICON_TYPE_ICON_TYPE_LINK_MIC = 2;
PERCEPTION_DIALOG_ICON_TYPE_ICON_TYPE_GUEST_LINK_MIC = 3;
PERCEPTION_DIALOG_ICON_TYPE_ICON_TYPE_LIVE = 4;
PERCEPTION_DIALOG_ICON_TYPE_ICON_TYPE_TREASURE_BOX = 5;
PERCEPTION_DIALOG_ICON_TYPE_ICON_TYPE_MUTE = 6;
PERCEPTION_DIALOG_ICON_TYPE_ICON_GAMEPAD_ACCESS_REVOKED = 7;
PERCEPTION_DIALOG_ICON_TYPE_ICON_TYPE_BAN_REPORT_LIVE_SINGLE_ROOM = 8;
PERCEPTION_DIALOG_ICON_TYPE_ICON_TYPE_BAN_REPORT_LIVE_ALL_ROOM = 9;
PERCEPTION_DIALOG_ICON_TYPE_ICON_TYPE_BAN_REPORT_LIVE_GREEN_SCREEN = 10;
PERCEPTION_DIALOG_ICON_TYPE_ICON_TYPE_GIFT = 11;
PERCEPTION_DIALOG_ICON_TYPE_ICON_TYPE_APPEAL_SUCCESS = 12;
PERCEPTION_DIALOG_ICON_TYPE_ICON_TYPE_MATCH = 13;
PERCEPTION_DIALOG_ICON_TYPE_ICON_TYPE_LIVE_GOAL = 14;
PERCEPTION_DIALOG_ICON_TYPE_ICON_TYPE_SUBSCRIPTION = 15;
PERCEPTION_DIALOG_ICON_TYPE_ICON_TYPE_STAR_COMMENT = 16;
PERCEPTION_DIALOG_ICON_TYPE_ICON_TYPE_RANKING = 17;
PERCEPTION_DIALOG_ICON_TYPE_ICON_TYPE_COMMON = 18;
}
enum GoalMessageSource {
GOAL_MESSAGE_SOURCE_UNKNOWN = 0;
GOAL_MESSAGE_SOURCE_COMMIT = 1;
GOAL_MESSAGE_SOURCE_PROGRESS_UPDATE = 2;
GOAL_MESSAGE_SOURCE_PIN = 3;
GOAL_MESSAGE_SOURCE_UNPIN = 4;
GOAL_MESSAGE_SOURCE_REVIEW_CALLBACK = 5;
GOAL_MESSAGE_SOURCE_SUSPEND = 6;
GOAL_MESSAGE_SOURCE_CHALLENGE_PROMPT = 7;
}
enum ExhibitionType {
EXHIBITION_TYPE_DEFAULT = 0;
EXHIBITION_TYPE_FOLD = 1;
EXHIBITION_TYPE_PUBLIC_SCREEN = 2;
}
enum GiftSource {
GIFT_SOURCE_UNKNOWN = 0;
GIFT_SOURCE_PLATFORM = 1;
GIFT_SOURCE_USER_BUY_RANDOM = 2;
GIFT_SOURCE_USER_BUY_SPECIFIC = 3;
}
enum MessageDisplayStyle {
MESSAGE_DISPLAY_STYLE_DEFAULT = 0;
MESSAGE_DISPLAY_STYLE_POPUP = 1;
}
enum ProfitRankType {
PROFIT_RANK_TYPE_TYPE_HOURLY_RANK = 0;
PROFIT_RANK_TYPE_TYPE_WEEKLY_RANK = 1;
PROFIT_RANK_TYPE_TYPE_HOURLY_STAR_RANK = 2;
PROFIT_RANK_TYPE_TYPE_WEEKLY_RISING_RANK_ACTIVITY = 3;
PROFIT_RANK_TYPE_TYPE_WEEKLY_RISING_RANK = 4;
PROFIT_RANK_TYPE_TYPE_WEEKLY_ROOKIE = 5;
PROFIT_RANK_TYPE_TYPE_E_COMMERCE_WEEKLY = 6;
PROFIT_RANK_TYPE_TYPE_E_COMMERCE_DAILY = 7;
PROFIT_RANK_TYPE_TYPE_DAILY_RANK = 8;
PROFIT_RANK_TYPE_TYPE_FIRST_GIFT_RANK = 9;
PROFIT_RANK_TYPE_TYPE_GAME_RANK = 10;
PROFIT_RANK_TYPE_TYPE_DAILY_GAME = 11;
PROFIT_RANK_TYPE_TYPE_HALL_OF_FAME_RANK = 12;
PROFIT_RANK_TYPE_TYPE_RANK_LEAGUE = 13;
PROFIT_RANK_TYPE_DAILY_ROOKIE = 14;
PROFIT_RANK_TYPE_TYPE_TEAM_RANK = 15;
PROFIT_RANK_TYPE_TYPE_CLASS_RANK = 16;
PROFIT_RANK_TYPE_TYPE_DAILY_GAME_PUB_G = 20;
PROFIT_RANK_TYPE_TYPE_DAILY_GAME_MLB_B = 21;
PROFIT_RANK_TYPE_TYPE_DAILY_GAME_FREE_FIRE = 22;
PROFIT_RANK_TYPE_TYPE_WEEKLY_GAME_SUBCATEGORY_ONE = 23;
PROFIT_RANK_TYPE_TYPE_WEEKLY_GAME_SUBCATEGORY_TWO = 24;
PROFIT_RANK_TYPE_TYPE_WEEKLY_GAME_SUBCATEGORY_THREE = 25;
}
enum UnionAnimationInfoType {
UNION_ANIMATION_INFO_TYPE_NO_UNION_ANIMATION = 0;
UNION_ANIMATION_INFO_TYPE_LOOP = 1;
UNION_ANIMATION_INFO_TYPE_LOCK = 2;
}
enum DisplayStyle {
DISPLAY_STYLE_NORMAL = 0;
DISPLAY_STYLE_STAY = 1;
DISPLAY_STYLE_CHAT = 2;
}
enum HitABStatus {
HIT_A_B_STATUS_HIT_AB_STATUS_NO_HIT = 0;
HIT_A_B_STATUS_HIT_AB_STATUS_ENTER_FROM_EXTERNAL_LINK_NEW_TEXT = 1;
HIT_A_B_STATUS_HIT_AB_STATUS_ENTER_FROM_RE_POST_NEW_TEXT = 2;
}
enum PollKind {
POLL_KIND_NORMAL = 0;
POLL_KIND_GIFT = 1;
POLL_KIND_CUSTOMIZABLE = 2;
POLL_KIND_CUSTOMIZABLE_GIFT = 3;
POLL_KIND_QUICK_GIFT = 4;
POLL_KIND_EMOTE = 5;
}
enum PollTemplateStatus {
POLL_TEMPLATE_STATUS_TO_BE_REVIEWED = 0;
POLL_TEMPLATE_STATUS_UNDER_REVIEW = 1;
POLL_TEMPLATE_STATUS_REVIEWED = 2;
POLL_TEMPLATE_STATUS_REFUSED = 3;
}
enum PollAppealStatus {
POLL_APPEAL_STATUS_UNKNOWN = 0;
POLL_APPEAL_STATUS_PASS = 1;
POLL_APPEAL_STATUS_FAIL = 2;
}
enum RankTestMessageScene {
RANK_TEST_MESSAGE_SCENE_UNKNOWN = 0;
RANK_TEST_MESSAGE_SCENE_ONLINE_AUDIENCE_TOP_N_UPDATE_PUBLIC_SCREEN = 1;
}
enum TriggerReason {
TRIGGER_REASON_UNKNOWN = 0;
TRIGGER_REASON_SCORE_UPDATE = 1;
TRIGGER_REASON_BATTLE_END = 2;
TRIGGER_REASON_OPT_OUT_UPDATE = 3;
TRIGGER_REASON_KEEP_ALIVE = 4;
}
enum Reason {
REASON_TIME_UP = 0;
REASON_CUT_SHORT = 1;
}
enum BattleTaskMessageType {
BATTLE_TASK_MESSAGE_TYPE_START = 0;
BATTLE_TASK_MESSAGE_TYPE_TASK_UPDATE = 1;
BATTLE_TASK_MESSAGE_TYPE_TASK_SETTLE = 2;
BATTLE_TASK_MESSAGE_TYPE_REWARD_SETTLE = 3;
}
enum RewardStatus {
REWARD_STATUS_SUCCEED = 0;
REWARD_STATUS_FAILED = 1;
}
enum BattleAction {
BATTLE_ACTION_UNKNOWN = 0;
BATTLE_ACTION_INVITE = 1;
BATTLE_ACTION_REJECT = 2;
BATTLE_ACTION_CANCEL = 3;
BATTLE_ACTION_OPEN = 4;
BATTLE_ACTION_FINISH = 5;
BATTLE_ACTION_CUT_SHORT = 6;
BATTLE_ACTION_ACCEPT = 7;
BATTLE_ACTION_QUIT_APPLY = 8;
BATTLE_ACTION_DECLINE_QUIT = 9;
BATTLE_ACTION_DECLINE_OFF_QUIT = 10;
BATTLE_ACTION_LEAVE_LINK_MIC = 11;
}
enum Result {
RESULT_WIN = 0;
RESULT_LOSE = 1;
RESULT_DRAW = 2;
}
enum GiftPermissionType {
GIFT_PERMISSION_TYPE_UNKNOWN_TYPE = 0;
GIFT_PERMISSION_TYPE_NO_GIFT_PERMISSION = 1;
GIFT_PERMISSION_TYPE_ANCHOR_CLOSE = 2;
GIFT_PERMISSION_TYPE_HAS_GIFT_PERMISSION = 3;
GIFT_PERMISSION_TYPE_ANCHOR_BANNED = 4;
}
enum BattleABTestType {
BATTLE_A_B_TEST_TYPE_UNKNOWN_AB_TEST_TYPE = 0;
BATTLE_A_B_TEST_TYPE_MEANWHILE_INVITE = 1;
BATTLE_A_B_TEST_TYPE_SPECIFIED_GIFT = 2;
BATTLE_A_B_TEST_TYPE_RT_C_MESSAGE_CHANNEL = 3;
BATTLE_A_B_TEST_TYPE_CONNECTION_TIME_OUT = 4;
BATTLE_A_B_TEST_TYPE_REMATCH_SKIP_TEAMMATE = 5;
BATTLE_A_B_TEST_TYPE_OPT_INVITEE_4048 = 6;
BATTLE_A_B_TEST_TYPE_BATTLE_AB_TEST_TYPE_TIME_CALIBRATE = 7;
}
enum PlayScene {
PLAY_SCENE_UNKNOWN = 0;
PLAY_SCENE_COUNTDOWN_FOR_ALL = 1;
PLAY_SCENE_COUNTDOWN_FOR_SINGLE = 2;
PLAY_SCENE_LIVE_SHOW = 3;
PLAY_SCENE_AIG_C = 4;
PLAY_SCENE_KARAOKE = 5;
PLAY_SCENE_DRAW_GUESS = 6;
PLAY_SCENE_ENLARGE_GRID = 7;
PLAY_SCENE_GIFT_PRIORITY_LINK = 8;
PLAY_SCENE_GIFT_THRESHOLD_LINK = 9;
PLAY_SCENE_NOTICE_BOARD = 10;
PLAY_SCENE_PLAY_BOOK = 11;
PLAY_SCENE_GUEST_SHOWDOWN = 12;
}
enum LinkType {
LINK_TYPE_TYPE_UNKNOWN = 0;
LINK_TYPE_TYPE_VIDEO = 1;
LINK_TYPE_TYPE_AUDIO = 2;
LINK_TYPE_TYPE_VIRTUAL = 3;
}
enum LinkSilenceStatus {
LINK_SILENCE_STATUS_STATUS_UN_SILENCE = 0;
LINK_SILENCE_STATUS_STATUS_SILENCE_BY_SELF = 1;
LINK_SILENCE_STATUS_STATUS_SILENCE_BY_OWNER = 2;
}
enum LinkmicRoleType {
LINKMIC_ROLE_TYPE_ROLE_TYPE_UNKOWN = 0;
LINKMIC_ROLE_TYPE_LEADER = 1;
LINKMIC_ROLE_TYPE_PLAYER = 2;
LINKMIC_ROLE_TYPE_INVITEE = 3;
}
enum LinkRoleType {
LINK_ROLE_TYPE_TYPE_ROLE_TYPE_UNKOWN = 0;
LINK_ROLE_TYPE_TYPE_LEADER = 1;
LINK_ROLE_TYPE_TYPE_PLAYER = 2;
LINK_ROLE_TYPE_TYPE_INVITEE = 3;
LINK_ROLE_TYPE_TYPE_APPLIER = 4;
}
enum MuteStatus {
MUTE_STATUS_MUTE = 0;
MUTE_STATUS_UN_MUTE = 1;
}
enum GuestMicCameraManageOp {
GUEST_MIC_CAMERA_MANAGE_OP_OPEN_MIC = 0;
GUEST_MIC_CAMERA_MANAGE_OP_OPEN_CAMERA = 1;
GUEST_MIC_CAMERA_MANAGE_OP_CLOSE_MIC = 2;
GUEST_MIC_CAMERA_MANAGE_OP_CLOSE_CAMERA = 3;
GUEST_MIC_CAMERA_MANAGE_OP_CLOSE_MIC_PUNISH = 4;
}
enum GuestMicCameraChangeScene {
GUEST_MIC_CAMERA_CHANGE_SCENE_CHANGE_SCENE_UNKNOWN = 0;
GUEST_MIC_CAMERA_CHANGE_SCENE_LIVE_SHOW_BY_ANCHOR_AUTO = 1;
GUEST_MIC_CAMERA_CHANGE_SCENE_LIVE_SHOW_BY_SERVER_NORMAL = 2;
GUEST_MIC_CAMERA_CHANGE_SCENE_LIVE_SHOW_BY_SHOW_END = 3;
}
enum LinkMicUserAdminType {
LINK_MIC_USER_ADMIN_TYPE_UNDEFINED_TYPE = 0;
LINK_MIC_USER_ADMIN_TYPE_MANAGER_TYPE = 1;
LINK_MIC_USER_ADMIN_TYPE_HOST_TYPE = 2;
}
enum LinkmicMultiLiveEnum {
LINKMIC_MULTI_LIVE_ENUM_DEFAULT = 0;
LINKMIC_MULTI_LIVE_ENUM_ANCHOR_USE_NEW_LAYOUT = 1;
}
enum PollEndType {
POLL_END_TYPE_POLL_END_BY_TIME = 0;
POLL_END_TYPE_POLL_END_BY_OWNER = 1;
POLL_END_TYPE_POLL_END_BY_OTHER = 2;
POLL_END_TYPE_POLL_END_BY_ADMIN = 3;
}
enum CohostABTestType {
COHOST_A_B_TEST_TYPE_COHOST_AB_TEST_TYPE_UNKNOWN = 0;
COHOST_A_B_TEST_TYPE_COHOST_AB_TEST_TYPE_LINK_TIME_OUT_STRATEGY = 1;
COHOST_A_B_TEST_TYPE_COHOST_AB_TEST_TYPE_COHOST_RESERVATION = 2;
COHOST_A_B_TEST_TYPE_COHOST_AB_TEST_TYPE_QUICK_PAIR_NEW_ARCH_SWITCH = 3;
COHOST_A_B_TEST_TYPE_COHOST_AB_TEST_TYPE_COHOST_INVITATION_TEXT = 4;
}
enum OptPairStatus {
OPT_PAIR_STATUS_UNKNOWN = 0;
OPT_PAIR_STATUS_OFFLINE = 1;
OPT_PAIR_STATUS_FINISHED = 2;
}
enum ContentPositionType {
CONTENT_POSITION_TYPE_UNKNOWN = 0;
CONTENT_POSITION_TYPE_STREAM = 1;
CONTENT_POSITION_TYPE_LIVE_STUDIO_STREAM_PORTRAIT = 2;
CONTENT_POSITION_TYPE_LIVE_STUDIO_STREAM_LANDSCAPE = 3;
}
enum MultiGuestOutsideRoomInviteSource {
MULTI_GUEST_OUTSIDE_ROOM_INVITE_SOURCE_OUTSIDE_ROOM_INVITE_SOURCE_UNKNOWN = 0;
MULTI_GUEST_OUTSIDE_ROOM_INVITE_SOURCE_OUTSIDE_ROOM_INVITE_SOURCE_PANEL = 1;
MULTI_GUEST_OUTSIDE_ROOM_INVITE_SOURCE_OUTSIDE_ROOM_INVITE_SOURCE_CAPSULE = 2;
MULTI_GUEST_OUTSIDE_ROOM_INVITE_SOURCE_OUTSIDE_ROOM_INVITE_SOURCE_EMPTY_POSITION = 3;
}
enum LinkUserType {
LINK_USER_TYPE_DEFAULT = 0;
LINK_USER_TYPE_KARAOKE = 1;
}
enum ContentInviteSource {
CONTENT_INVITE_SOURCE_INVITE_SOURCE_UNKNOWN = 0;
CONTENT_INVITE_SOURCE_INVITE_SOURCE_PANEL_GO_LIVE = 1;
CONTENT_INVITE_SOURCE_INVITE_SOURCE_MUTUAL_NOTICE = 2;
CONTENT_INVITE_SOURCE_INVITE_SOURCE_USER_PROFILE = 3;
CONTENT_INVITE_SOURCE_INVITE_SOURCE_RESERVE = 4;
}
enum LinkmicShareRevenueSetting {
LINKMIC_SHARE_REVENUE_SETTING_LINK_MIC_SHARE_REVENUE_NOT_SET = 0;
LINKMIC_SHARE_REVENUE_SETTING_LINK_MIC_SHARE_REVENUE_OPEN = 1;
LINKMIC_SHARE_REVENUE_SETTING_LINK_MIC_SHARE_REVENUE_CLOSE = 2;
}
enum PosIdentityType {
POS_IDENTITY_TYPE_IDENTITY_EMPTY_SLOT = 0;
POS_IDENTITY_TYPE_IDENTITY_RT_C_USER_ID = 1;
POS_IDENTITY_TYPE_IDENTITY_RT_C_STREAM_ID = 2;
POS_IDENTITY_TYPE_IDENTITY_LIVE_USER_ID = 3;
}
enum JoinType {
JOIN_TYPE_UNKNOWN = 0;
JOIN_TYPE_CHANNEL_APPLY = 1;
JOIN_TYPE_CHANNEL_INVITE = 2;
JOIN_TYPE_GROUP_APPLY = 100;
JOIN_TYPE_GROUP_APPLY_FOLLOW = 101;
JOIN_TYPE_GROUP_INVITE = 102;
JOIN_TYPE_GROUP_INVITE_FOLLOW = 103;
JOIN_TYPE_GROUP_OWNER_JOIN = 104;
}
enum CohostLayoutMode {
COHOST_LAYOUT_MODE_NORMAL = 0;
COHOST_LAYOUT_MODE_SCREEN_SHARE = 1;
}
enum TagClassification {
TAG_CLASSIFICATION_UNKNOWN = 0;
TAG_CLASSIFICATION_COHOST_HISTORY = 1;
TAG_CLASSIFICATION_FIRST_DEGREE_RELATION = 2;
TAG_CLASSIFICATION_SECOND_DEGREE_RELATION = 3;
TAG_CLASSIFICATION_RANK = 4;
TAG_CLASSIFICATION_SIMILAR_INTERESTS = 5;
}
enum SourceType {
SOURCE_TYPE_UNKNOWN = 0;
SOURCE_TYPE_FRIEND_LIST = 1;
SOURCE_TYPE_RECOMMEND_LIST = 2;
SOURCE_TYPE_RECENT = 3;
SOURCE_TYPE_OTHER_FOLLOW = 4;
SOURCE_TYPE_QUICK_PAIR = 5;
SOURCE_TYPE_ACTIVITY = 6;
SOURCE_TYPE_QUICK_RECOMMEND = 7;
SOURCE_TYPE_OFFICIAL_CHANNEL = 8;
SOURCE_TYPE_BEST_TEAMMATE = 9;
SOURCE_TYPE_RESERVATION = 10;
SOURCE_TYPE_PAIRING = 11;
SOURCE_TYPE_PAIRING_ON_RESERVATION = 12;
SOURCE_TYPE_TOPIC_QUICK_PAIR = 13;
SOURCE_TYPE_TOPIC_QUICK_RECOMMEND = 14;
SOURCE_TYPE_ONLINE_FRIEND_CAPSULE = 15;
SOURCE_TYPE_WEEKLY_RANK = 20;
SOURCE_TYPE_HOURLY_RANK = 21;
SOURCE_TYPE_WEEKLY_RISING = 23;
SOURCE_TYPE_WEEKLY_ROOKIE = 24;
SOURCE_TYPE_CONNECTION_LIST = 25;
SOURCE_TYPE_DAILY_RANK = 26;
SOURCE_TYPE_DAILY_RANK_HALL_OF_FAME = 27;
SOURCE_TYPE_RESERVATION_BUBBLE = 28;
SOURCE_TYPE_PAIRING_BUBBLE = 29;
SOURCE_TYPE_LEAGUE_PHASE_ONE = 30;
SOURCE_TYPE_LEAGUE_PHASE_TWO = 31;
SOURCE_TYPE_LEAGUE_PHASE_THREE = 32;
SOURCE_TYPE_DAILY_ROOKIE = 33;
SOURCE_TYPE_MAY_KNOW_LIST = 34;
SOURCE_TYPE_BANNER = 35;
SOURCE_TYPE_FANS_TEAM_RANK = 36;
SOURCE_TYPE_SEARCH = 37;
SOURCE_TYPE_E_OY_RANK_LIST = 38;
SOURCE_TYPE_LEAGUE_CAMPAIGN_RANK = 39;
SOURCE_TYPE_CREATOR_CLASS_RANK = 40;
SOURCE_TYPE_HISTORY = 41;
SOURCE_TYPE_QUICK_RECOMMEND_DURING_COHOST = 43;
}
enum BattleType {
BATTLE_TYPE_UNKNOWN_BATTLE_TYPE = 0;
BATTLE_TYPE_NORMAL_BATTLE = 1;
BATTLE_TYPE_TEAM_BATTLE = 2;
BATTLE_TYPE_INDIVIDUAL_BATTLE = 3;
BATTLE_TYPE_1_V_N = 4;
BATTLE_TYPE_TAKE_THE_STAGE = 51;
BATTLE_TYPE_GROUP_SHOW = 52;
}
enum BattleInviteType {
BATTLE_INVITE_TYPE_NORMAL = 0;
BATTLE_INVITE_TYPE_AGAIN = 1;
}
enum BattleCardMsgType {
BATTLE_CARD_MSG_TYPE_UNKNOWN_CARD_ACTION = 0;
BATTLE_CARD_MSG_TYPE_CARD_OBTAIN_GUIDE = 1;
BATTLE_CARD_MSG_TYPE_USE_CRITICAL_STRIKE_CARD = 2;
BATTLE_CARD_MSG_TYPE_USE_SMOKE_CARD = 3;
BATTLE_CARD_MSG_TYPE_AWARD_CARD_NOTICE = 4;
BATTLE_CARD_MSG_TYPE_USE_EXTRA_TIME_CARD = 5;
BATTLE_CARD_MSG_TYPE_USE_SPECIAL_EFFECT_CARD = 6;
BATTLE_CARD_MSG_TYPE_USE_POTION_CARD = 7;
BATTLE_CARD_MSG_TYPE_USE_WAVE_CARD = 8;
BATTLE_CARD_MSG_TYPE_SPECIAL_EFFECT_NOTICE = 9;
BATTLE_CARD_MSG_TYPE_USE_TOP_2_CARD = 10;
BATTLE_CARD_MSG_TYPE_USE_TOP_3_CARD = 11;
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -22,20 +22,18 @@
*/ */
package io.github.jwdeveloper.tiktok; package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings; import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftsManager;
import io.github.jwdeveloper.tiktok.http.LiveHttpClient; import io.github.jwdeveloper.tiktok.http.LiveHttpClient;
import io.github.jwdeveloper.tiktok.live.GiftsManager;
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder; import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer; import java.util.function.Consumer;
public class TikTokLive { public class TikTokLive
{
/** /**
* Example: https://www.tiktok.com/@dostawcavideo - hostName would be 'dostawcavideo' * Example: {@code https://www.tiktok.com/@dostawcavideo} - hostName would be 'dostawcavideo'
* *
* @param hostName profile name of TikTok user could be found in profile link * @param hostName profile name of TikTok user could be found in profile link
* @return LiveClientBuilder * @return LiveClientBuilder
@@ -45,7 +43,7 @@ public class TikTokLive {
} }
/** /**
* Example: https://www.tiktok.com/@dostawcavideo - hostName would be 'dostawcavideo' * Example: {@code https://www.tiktok.com/@dostawcavideo} - hostName would be 'dostawcavideo'
* *
* @param hostName profile name of TikTok user could be found in profile link * @param hostName profile name of TikTok user could be found in profile link
* @return true if live is Online, false if is offline * @return true if live is Online, false if is offline
@@ -55,17 +53,17 @@ public class TikTokLive {
} }
/** /**
* Example: https://www.tiktok.com/@dostawcavideo - hostName would be 'dostawcavideo' * Example: {@code https://www.tiktok.com/@dostawcavideo} - hostName would be 'dostawcavideo'
* *
* @param hostName profile name of TikTok user could be found in profile link * @param hostName profile name of TikTok user could be found in profile link
* @return true if live is Online, false if is offline * @return {@link CompletableFuture} of 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)); return CompletableFuture.supplyAsync(() -> isLiveOnline(hostName));
} }
/** /**
* Example: https://www.tiktok.com/@dostawcavideo - hostName would be 'dostawcavideo' * Example: {@code https://www.tiktok.com/@dostawcavideo} - hostName would be 'dostawcavideo'
* *
* @param hostName profile name of TikTok user could be found in profile link * @param hostName profile name of TikTok user could be found in profile link
* @return true is hostName name is valid and exists, false if not * @return true is hostName name is valid and exists, false if not
@@ -75,7 +73,7 @@ public class TikTokLive {
} }
/** /**
* Example: https://www.tiktok.com/@dostawcavideo - hostName would be 'dostawcavideo' * Example: {@code https://www.tiktok.com/@dostawcavideo} - hostName would be 'dostawcavideo'
* *
* @param hostName profile name of TikTok user could be found in profile link * @param hostName profile name of TikTok user could be found in profile link
* @return true is hostName name is valid and exists, false if not * @return true is hostName name is valid and exists, false if not
@@ -101,20 +99,4 @@ public class TikTokLive {
public static LiveHttpClient requests() { public static LiveHttpClient requests() {
return requests(liveClientSettings -> {}); return requests(liveClientSettings -> {});
} }
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

@@ -23,6 +23,7 @@
package io.github.jwdeveloper.tiktok; package io.github.jwdeveloper.tiktok;
import com.google.protobuf.ByteString; import com.google.protobuf.ByteString;
import io.github.jwdeveloper.tiktok.common.AsyncHandler;
import io.github.jwdeveloper.tiktok.data.events.*; import io.github.jwdeveloper.tiktok.data.events.*;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.control.*; import io.github.jwdeveloper.tiktok.data.events.control.*;
@@ -31,15 +32,15 @@ import io.github.jwdeveloper.tiktok.data.requests.*;
import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings; import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
import io.github.jwdeveloper.tiktok.exceptions.*; import io.github.jwdeveloper.tiktok.exceptions.*;
import io.github.jwdeveloper.tiktok.http.LiveHttpClient; import io.github.jwdeveloper.tiktok.http.LiveHttpClient;
import io.github.jwdeveloper.tiktok.listener.*; import io.github.jwdeveloper.tiktok.listener.ListenersManager;
import io.github.jwdeveloper.tiktok.live.*; import io.github.jwdeveloper.tiktok.live.*;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse; import io.github.jwdeveloper.tiktok.messages.webcast.ProtoMessageFetchResult;
import io.github.jwdeveloper.tiktok.models.ConnectionState; import io.github.jwdeveloper.tiktok.models.ConnectionState;
import io.github.jwdeveloper.tiktok.websocket.LiveSocketClient; import io.github.jwdeveloper.tiktok.websocket.*;
import lombok.Getter; import lombok.Getter;
import java.util.Base64; import java.util.Base64;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.*;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -79,19 +80,21 @@ public class TikTokLiveClient implements LiveClient
public void connect() { public void connect() {
try { try {
tryConnect(); if (clientSettings.isUseEulerstreamWebsocket())
tryEulerConnect();
else
tryConnect();
} catch (TikTokLiveException e) { } catch (TikTokLiveException e) {
setState(ConnectionState.DISCONNECTED); setState(ConnectionState.DISCONNECTED);
tikTokEventHandler.publish(this, new TikTokErrorEvent(e)); tikTokEventHandler.publish(this, new TikTokErrorEvent(e));
tikTokEventHandler.publish(this, new TikTokDisconnectedEvent("Exception: "+e.getMessage())); tikTokEventHandler.publish(this, new TikTokDisconnectedEvent("Exception: " + e.getMessage()));
if (e instanceof TikTokLiveOfflineHostException && clientSettings.isRetryOnConnectionFailure()) { if (e instanceof TikTokLiveOfflineHostException && clientSettings.isRetryOnConnectionFailure()) {
try { AsyncHandler.getReconnectScheduler().schedule(() -> {
Thread.sleep(clientSettings.getRetryConnectionTimeout().toMillis()); logger.info("Reconnecting");
} catch (Exception ignored) {} tikTokEventHandler.publish(this, new TikTokReconnectingEvent());
logger.info("Reconnecting"); this.connect();
tikTokEventHandler.publish(this, new TikTokReconnectingEvent()); }, clientSettings.getRetryConnectionTimeout().toMillis(), TimeUnit.MILLISECONDS);
this.connect();
} }
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
@@ -101,6 +104,17 @@ public class TikTokLiveClient implements LiveClient
} }
} }
private void tryEulerConnect() {
if (!roomInfo.hasConnectionState(ConnectionState.DISCONNECTED)) {
throw new TikTokLiveException("Already connected");
}
setState(ConnectionState.CONNECTING);
tikTokEventHandler.publish(this, new TikTokConnectingEvent());
webSocketClient.start(null, this);
setState(ConnectionState.CONNECTED);
}
public void tryConnect() { public void tryConnect() {
if (!roomInfo.hasConnectionState(ConnectionState.DISCONNECTED)) { if (!roomInfo.hasConnectionState(ConnectionState.DISCONNECTED)) {
throw new TikTokLiveException("Already connected"); throw new TikTokLiveException("Already connected");
@@ -110,26 +124,26 @@ public class TikTokLiveClient implements LiveClient
tikTokEventHandler.publish(this, new TikTokConnectingEvent()); tikTokEventHandler.publish(this, new TikTokConnectingEvent());
var userDataRequest = new LiveUserData.Request(roomInfo.getHostName()); var userDataRequest = new LiveUserData.Request(roomInfo.getHostName());
var userData = httpClient.fetchLiveUserData(userDataRequest); var userData = httpClient.fetchLiveUserData(userDataRequest);
roomInfo.setStartTime(userData.getStartTime());
roomInfo.setRoomId(userData.getRoomId());
if (userData.getUserStatus() == LiveUserData.UserStatus.Offline) if (userData.getUserStatus() == LiveUserData.UserStatus.Offline)
throw new TikTokLiveOfflineHostException("User is offline: " + roomInfo.getHostName()); throw new TikTokLiveOfflineHostException("User is offline: " + roomInfo.getHostName(), userData, null);
if (userData.getUserStatus() == LiveUserData.UserStatus.NotFound) if (userData.getUserStatus() == LiveUserData.UserStatus.NotFound)
throw new TikTokLiveUnknownHostException("User not found: " + roomInfo.getHostName()); throw new TikTokLiveUnknownHostException("User not found: " + roomInfo.getHostName(), userData, null);
var liveDataRequest = new LiveData.Request(userData.getRoomId()); roomInfo.copy(userData.getRoomInfo());
var liveDataRequest = new LiveData.Request(userData.getRoomInfo().getRoomId());
var liveData = httpClient.fetchLiveData(liveDataRequest); var liveData = httpClient.fetchLiveData(liveDataRequest);
if (liveData.isAgeRestricted() && clientSettings.isThrowOnAgeRestriction()) if (liveData.isAgeRestricted() && clientSettings.isThrowOnAgeRestriction())
throw new TikTokLiveException("Livestream for " + roomInfo.getHostName() + " is 18+ or age restricted!"); throw new TikTokLiveException("Livestream for " + roomInfo.getHostName() + " is 18+ or age restricted!");
if (liveData.getLiveStatus() == LiveData.LiveStatus.HostNotFound) if (liveData.getLiveStatus() == LiveData.LiveStatus.HostNotFound)
throw new TikTokLiveUnknownHostException("LiveStream for " + roomInfo.getHostName() + " could not be found."); throw new TikTokLiveUnknownHostException("LiveStream for " + roomInfo.getHostName() + " could not be found.", userData, liveData);
if (liveData.getLiveStatus() == LiveData.LiveStatus.HostOffline) if (liveData.getLiveStatus() == LiveData.LiveStatus.HostOffline)
throw new TikTokLiveOfflineHostException("LiveStream for " + roomInfo.getHostName() + " not found, is the Host offline?"); throw new TikTokLiveOfflineHostException("LiveStream for " + roomInfo.getHostName() + " not found, is the Host offline?", userData, liveData);
roomInfo.setTitle(liveData.getTitle()); roomInfo.setTitle(liveData.getTitle());
roomInfo.setViewersCount(liveData.getViewers()); roomInfo.setViewersCount(liveData.getViewers());
@@ -140,12 +154,12 @@ public class TikTokLiveClient implements LiveClient
var preconnectEvent = new TikTokPreConnectionEvent(userData, liveData); var preconnectEvent = new TikTokPreConnectionEvent(userData, liveData);
tikTokEventHandler.publish(this, preconnectEvent); tikTokEventHandler.publish(this, preconnectEvent);
if (preconnectEvent.isCancelConnection()) if (preconnectEvent.isCancelConnection())
throw new TikTokLiveException("TikTokPreConnectionEvent cancelled connection!"); throw new TikTokLivePreConnectionException(preconnectEvent);
if (clientSettings.isFetchGifts()) if (clientSettings.isFetchGifts())
giftManager.attachGiftsList(httpClient.fetchRoomGiftsData(userData.getRoomId()).getGifts()); giftManager.attachGiftsList(httpClient.fetchRoomGiftsData(userData.getRoomInfo().getRoomId()).getGifts());
var liveConnectionRequest = new LiveConnectionData.Request(userData.getRoomId()); var liveConnectionRequest = new LiveConnectionData.Request(userData.getRoomInfo().getRoomId());
var liveConnectionData = httpClient.fetchLiveConnectionData(liveConnectionRequest); var liveConnectionData = httpClient.fetchLiveConnectionData(liveConnectionRequest);
webSocketClient.start(liveConnectionData, this); webSocketClient.start(liveConnectionData, this);
@@ -153,13 +167,12 @@ public class TikTokLiveClient implements LiveClient
tikTokEventHandler.publish(this, new TikTokRoomInfoEvent(roomInfo)); tikTokEventHandler.publish(this, new TikTokRoomInfoEvent(roomInfo));
} }
public void disconnect() { public void disconnect(LiveClientStopType type) {
if (roomInfo.hasConnectionState(ConnectionState.DISCONNECTED)) { if (webSocketClient.isConnected())
return; webSocketClient.stop(type);
} if (!roomInfo.hasConnectionState(ConnectionState.DISCONNECTED))
setState(ConnectionState.DISCONNECTED); setState(ConnectionState.DISCONNECTED);
webSocketClient.stop(); }
}
private void setState(ConnectionState connectionState) { private void setState(ConnectionState connectionState) {
logger.info("TikTokLive client state: " + connectionState.name()); logger.info("TikTokLive client state: " + connectionState.name());
@@ -174,16 +187,20 @@ public class TikTokLiveClient implements LiveClient
public void publishMessage(String webcastMessageName, String payloadBase64) { public void publishMessage(String webcastMessageName, String payloadBase64) {
this.publishMessage(webcastMessageName, Base64.getDecoder().decode(payloadBase64)); this.publishMessage(webcastMessageName, Base64.getDecoder().decode(payloadBase64));
} }
@Override @Override
public void publishMessage(String webcastMessageName, byte[] payload) { public void publishMessage(String webcastMessageName, byte[] payload) {
var builder = ProtoMessageFetchResult.BaseProtoMessage.newBuilder();
var builder = WebcastResponse.Message.newBuilder();
builder.setMethod(webcastMessageName); builder.setMethod(webcastMessageName);
builder.setPayload(ByteString.copyFrom(payload)); builder.setPayload(ByteString.copyFrom(payload));
var message = builder.build(); var message = builder.build();
messageHandler.handleSingleMessage(this, message); messageHandler.handleSingleMessage(this, message);
} }
@Override
public boolean sendChat(String content) {
return httpClient.sendChat(roomInfo, content);
}
public void connectAsync(Consumer<LiveClient> onConnection) { public void connectAsync(Consumer<LiveClient> onConnection) {
connectAsync().thenAccept(onConnection); connectAsync().thenAccept(onConnection);

View File

@@ -42,6 +42,7 @@ import io.github.jwdeveloper.tiktok.mappers.handlers.TikTokGiftEventHandler;
import io.github.jwdeveloper.tiktok.mappers.handlers.TikTokRoomInfoEventHandler; import io.github.jwdeveloper.tiktok.mappers.handlers.TikTokRoomInfoEventHandler;
import io.github.jwdeveloper.tiktok.mappers.handlers.TikTokSocialMediaEventHandler; import io.github.jwdeveloper.tiktok.mappers.handlers.TikTokSocialMediaEventHandler;
import io.github.jwdeveloper.tiktok.websocket.*; import io.github.jwdeveloper.tiktok.websocket.*;
import io.github.jwdeveloper.tiktok.websocket.euler.TikTokWebSocketEulerClient;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
@@ -70,12 +71,6 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
return this; return this;
} }
@Override
public LiveClientBuilder onMappings(Consumer<LiveMapper> onCustomMappings) {
mappings(onCustomMappings);
return this;
}
public TikTokLiveClientBuilder configure(Consumer<LiveClientSettings> onConfigure) { public TikTokLiveClientBuilder configure(Consumer<LiveClientSettings> onConfigure) {
onConfigure.accept(clientSettings); onConfigure.accept(clientSettings);
return this; return this;
@@ -143,7 +138,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
dependance.registerSingleton(LiveSocketClient.class, TikTokWebSocketOfflineClient.class); dependance.registerSingleton(LiveSocketClient.class, TikTokWebSocketOfflineClient.class);
dependance.registerSingleton(LiveHttpClient.class, TikTokLiveHttpOfflineClient.class); dependance.registerSingleton(LiveHttpClient.class, TikTokLiveHttpOfflineClient.class);
} else { } else {
dependance.registerSingleton(LiveSocketClient.class, TikTokWebSocketClient.class); dependance.registerSingleton(LiveSocketClient.class, clientSettings.isUseEulerstreamWebsocket() ? TikTokWebSocketEulerClient.class : TikTokWebSocketClient.class);
dependance.registerSingleton(LiveHttpClient.class, TikTokLiveHttpClient.class); dependance.registerSingleton(LiveHttpClient.class, TikTokLiveHttpClient.class);
} }
@@ -155,11 +150,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
*/ */
//gifts //gifts
if (clientSettings.isFetchGifts()) { dependance.registerSingleton(GiftsManager.class, new TikTokGiftsManager(List.of()));
dependance.registerSingleton(GiftsManager.class, TikTokLive.gifts());
} else {
dependance.registerSingleton(GiftsManager.class, new TikTokGiftsManager(List.of()));
}
//mapper //mapper
dependance.registerSingleton(TikTokGenericEventMapper.class); dependance.registerSingleton(TikTokGenericEventMapper.class);

View File

@@ -22,6 +22,7 @@
*/ */
package io.github.jwdeveloper.tiktok; package io.github.jwdeveloper.tiktok;
import com.google.gson.JsonObject;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import io.github.jwdeveloper.dependance.injector.api.annotations.Inject; import io.github.jwdeveloper.dependance.injector.api.annotations.Inject;
import io.github.jwdeveloper.tiktok.common.*; import io.github.jwdeveloper.tiktok.common.*;
@@ -30,9 +31,10 @@ import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
import io.github.jwdeveloper.tiktok.exceptions.*; import io.github.jwdeveloper.tiktok.exceptions.*;
import io.github.jwdeveloper.tiktok.http.*; import io.github.jwdeveloper.tiktok.http.*;
import io.github.jwdeveloper.tiktok.http.mappers.*; import io.github.jwdeveloper.tiktok.http.mappers.*;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse; import io.github.jwdeveloper.tiktok.live.LiveRoomInfo;
import io.github.jwdeveloper.tiktok.messages.webcast.ProtoMessageFetchResult;
import java.net.http.HttpResponse; import java.net.http.*;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -42,17 +44,17 @@ public class TikTokLiveHttpClient implements LiveHttpClient
* <a href="https://github-wiki-see.page/m/isaackogan/TikTokLive/wiki/All-About-Signatures">Signing API by Isaac Kogan</a> * <a href="https://github-wiki-see.page/m/isaackogan/TikTokLive/wiki/All-About-Signatures">Signing API by Isaac Kogan</a>
*/ */
private static final String TIKTOK_SIGN_API = "https://tiktok.eulerstream.com/webcast/fetch"; private static final String TIKTOK_SIGN_API = "https://tiktok.eulerstream.com/webcast/fetch";
private static final String TIKTOK_CHAT_URL = "https://tiktok.eulerstream.com/webcast/chat";
private static final String TIKTOK_SIGN_ENTERPRISE_API = "https://tiktok.enterprise.eulerstream.com/webcast/fetch";
private static final String TIKTOK_CHAT_ENTERPRISE_URL = "https://tiktok.enterprise.eulerstream.com/webcast/chat";
private static final String TIKTOK_URL_WEB = "https://www.tiktok.com/"; private static final String TIKTOK_URL_WEB = "https://www.tiktok.com/";
private static final String TIKTOK_URL_WEBCAST = "https://webcast.tiktok.com/webcast/"; 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"; private static final String TIKTOK_ROOM_GIFTS_URL = TIKTOK_URL_WEBCAST+"gift/list/";
public static final String TIKTOK_ROOM_GIFTS_URL = TIKTOK_URL_WEBCAST+"gift/list/"; private static final String TIKTOK_ROOM_INFO_URL = TIKTOK_URL_WEBCAST + "room/info";
public static final int TIKTOK_AGE_RESTRICTED_CODE = 4003110; public static final int TIKTOK_AGE_RESTRICTED_CODE = 4003110;
private final HttpClientFactory httpFactory; private final HttpClientFactory httpFactory;
private final LiveClientSettings clientSettings; private final LiveClientSettings clientSettings;
private final LiveUserDataMapper liveUserDataMapper;
private final LiveDataMapper liveDataMapper;
private final GiftsDataMapper giftsDataMapper;
private final Logger logger; private final Logger logger;
@Inject @Inject
@@ -60,9 +62,6 @@ public class TikTokLiveHttpClient implements LiveHttpClient
this.httpFactory = factory; this.httpFactory = factory;
this.clientSettings = factory.getLiveClientSettings(); this.clientSettings = factory.getLiveClientSettings();
this.logger = LoggerFactory.create("HttpClient-"+hashCode(), clientSettings); this.logger = LoggerFactory.create("HttpClient-"+hashCode(), clientSettings);
liveUserDataMapper = new LiveUserDataMapper();
liveDataMapper = new LiveDataMapper();
giftsDataMapper = new GiftsDataMapper();
} }
public TikTokLiveHttpClient(Consumer<LiveClientSettings> consumer) { public TikTokLiveHttpClient(Consumer<LiveClientSettings> consumer) {
@@ -92,32 +91,7 @@ public class TikTokLiveHttpClient implements LiveHttpClient
throw new TikTokLiveRequestException("Unable to fetch gifts information's - "+result); throw new TikTokLiveRequestException("Unable to fetch gifts information's - "+result);
var json = result.getContent(); var json = result.getContent();
return giftsDataMapper.mapRoom(json); return GiftsDataMapper.mapRoom(json);
}
public GiftsData.Response fetchGiftsData() {
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) {
try {
return getGiftsData();
} catch (TikTokProxyRequestException ignored) {}
}
}
return getGiftsData();
}
@Deprecated(since = "1.8.6", forRemoval = true)
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 @Override
@@ -138,6 +112,8 @@ public class TikTokLiveHttpClient implements LiveHttpClient
var result = httpFactory.client(url) var result = httpFactory.client(url)
.withParam("uniqueId", request.getUserName()) .withParam("uniqueId", request.getUserName())
.withParam("sourceType", "54") //MAGIC NUMBER, WHAT 54 means? .withParam("sourceType", "54") //MAGIC NUMBER, WHAT 54 means?
.withCookie("sessionid", clientSettings.getSessionId())
.withCookie("tt-target-idc", clientSettings.getTtTargetIdc())
.build() .build()
.toJsonResponse(); .toJsonResponse();
@@ -145,7 +121,7 @@ public class TikTokLiveHttpClient implements LiveHttpClient
throw new TikTokLiveRequestException("Unable to get information's about user - "+result); throw new TikTokLiveRequestException("Unable to get information's about user - "+result);
var json = result.getContent(); var json = result.getContent();
return liveUserDataMapper.map(json, logger); return LiveUserDataMapper.map(json, logger);
} }
@Override @Override
@@ -162,9 +138,10 @@ public class TikTokLiveHttpClient implements LiveHttpClient
} }
public LiveData.Response getLiveData(LiveData.Request request) { public LiveData.Response getLiveData(LiveData.Request request) {
var url = TIKTOK_URL_WEBCAST + "room/info"; var result = httpFactory.client(TIKTOK_ROOM_INFO_URL)
var result = httpFactory.client(url)
.withParam("room_id", request.getRoomId()) .withParam("room_id", request.getRoomId())
.withCookie("sessionid", clientSettings.getSessionId())
.withCookie("tt-target-idc", clientSettings.getTtTargetIdc())
.build() .build()
.toJsonResponse(); .toJsonResponse();
@@ -172,7 +149,7 @@ public class TikTokLiveHttpClient implements LiveHttpClient
throw new TikTokLiveRequestException("Unable to get info about live room - "+result); throw new TikTokLiveRequestException("Unable to get info about live room - "+result);
var json = result.getContent(); var json = result.getContent();
return liveDataMapper.map(json); return LiveDataMapper.map(json);
} }
@Override @Override
@@ -187,7 +164,7 @@ public class TikTokLiveHttpClient implements LiveHttpClient
throw new TikTokSignServerException("Sign server did not return the x-set-tt-cookie header - "+result); throw new TikTokSignServerException("Sign server did not return the x-set-tt-cookie header - "+result);
} }
var websocketCookie = resultHeader.getContent(); var websocketCookie = resultHeader.getContent();
var webcastResponse = WebcastResponse.parseFrom(credentialsResponse.body()); var webcastResponse = ProtoMessageFetchResult.parseFrom(credentialsResponse.body());
var webSocketUrl = httpFactory var webSocketUrl = httpFactory
.client(webcastResponse.getPushServer()) .client(webcastResponse.getPushServer())
.withParam("room_id", request.getRoomId()) .withParam("room_id", request.getRoomId())
@@ -204,6 +181,33 @@ public class TikTokLiveHttpClient implements LiveHttpClient
} }
} }
@Override
public boolean sendChat(LiveRoomInfo roomInfo, String content) {
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) {
try {
return requestSendChat(roomInfo, content);
} catch (TikTokProxyRequestException ignored) {}
}
}
return requestSendChat(roomInfo, content);
}
public boolean requestSendChat(LiveRoomInfo roomInfo, String content) {
JsonObject body = new JsonObject();
body.addProperty("content", content);
body.addProperty("sessionId", clientSettings.getSessionId());
body.addProperty("ttTargetIdc", clientSettings.getTtTargetIdc());
body.addProperty("roomId", roomInfo.getRoomId());
HttpClientBuilder builder = httpFactory.client(clientSettings.isUseEulerstreamEnterprise() ? TIKTOK_CHAT_ENTERPRISE_URL : TIKTOK_CHAT_URL)
.withHeader("Content-Type", "application/json");
if (clientSettings.getApiKey() != null)
builder.withHeader("x-api-key", clientSettings.getApiKey());
var result = builder.withBody(HttpRequest.BodyPublishers.ofString(body.toString())).build().toJsonResponse();
return result.isSuccess();
}
protected ActionResult<HttpResponse<byte[]>> getStartingPayload(LiveConnectionData.Request request) { protected ActionResult<HttpResponse<byte[]>> getStartingPayload(LiveConnectionData.Request request) {
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings(); var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) { if (proxyClientSettings.isEnabled()) {
@@ -217,12 +221,14 @@ public class TikTokLiveHttpClient implements LiveHttpClient
} }
protected ActionResult<HttpResponse<byte[]>> getByteResponse(String room_id) { protected ActionResult<HttpResponse<byte[]>> getByteResponse(String room_id) {
HttpClientBuilder builder = httpFactory.client(TIKTOK_SIGN_API) HttpClientBuilder builder = httpFactory.client(clientSettings.isUseEulerstreamEnterprise() ? TIKTOK_SIGN_ENTERPRISE_API : TIKTOK_SIGN_API)
.withParam("client", "ttlive-java") .withParam("client", "ttlive-java")
.withParam("room_id", room_id); .withParam("room_id", room_id);
if (clientSettings.getSessionId() != null) // Allows receiving of all comments and Subscribe Events
builder.withParam("session_id", clientSettings.getSessionId());
if (clientSettings.getApiKey() != null) if (clientSettings.getApiKey() != null)
builder.withParam("apiKey", clientSettings.getApiKey()); builder.withHeader("x-api-key", clientSettings.getApiKey());
var result = builder.build().toHttpResponse(HttpResponse.BodyHandlers.ofByteArray()); var result = builder.build().toHttpResponse(HttpResponse.BodyHandlers.ofByteArray());

View File

@@ -24,22 +24,15 @@ package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.data.models.Picture; 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.User;
import io.github.jwdeveloper.tiktok.data.requests.GiftsData; import io.github.jwdeveloper.tiktok.data.requests.*;
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.http.LiveHttpClient;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse; import io.github.jwdeveloper.tiktok.live.LiveRoomInfo;
import io.github.jwdeveloper.tiktok.messages.webcast.ProtoMessageFetchResult;
import java.net.URI; import java.net.URI;
import java.util.List; import java.util.List;
public class TikTokLiveHttpOfflineClient implements LiveHttpClient { public class TikTokLiveHttpOfflineClient implements LiveHttpClient {
@Override
public GiftsData.Response fetchGiftsData() {
return new GiftsData.Response("", List.of());
}
@Override @Override
public GiftsData.Response fetchRoomGiftsData(String room_id) { public GiftsData.Response fetchRoomGiftsData(String room_id) {
return new GiftsData.Response("", List.of()); return new GiftsData.Response("", List.of());
@@ -47,26 +40,32 @@ public class TikTokLiveHttpOfflineClient implements LiveHttpClient {
@Override @Override
public LiveUserData.Response fetchLiveUserData(LiveUserData.Request request) { public LiveUserData.Response fetchLiveUserData(LiveUserData.Request request) {
return new LiveUserData.Response("", LiveUserData.UserStatus.Live, "offline_room_id", 0, null); return new LiveUserData.Response("", LiveUserData.UserStatus.Live, null);
} }
@Override @Override
public LiveData.Response fetchLiveData(LiveData.Request request) { public LiveData.Response fetchLiveData(LiveData.Request request) {
return new LiveData.Response("", return new LiveData.Response("",
LiveData.LiveStatus.HostOnline, LiveData.LiveStatus.HostOnline,
"offline live", "offline live",
0, 0,
0, 0,
0, 0,
false, false,
new User(0L, "offline user", new Picture("")), new User(0L, "offline user", new Picture("")),
LiveData.LiveType.SOLO); LiveData.LiveType.SOLO);
} }
@Override @Override
public LiveConnectionData.Response fetchLiveConnectionData(LiveConnectionData.Request request) { public LiveConnectionData.Response fetchLiveConnectionData(LiveConnectionData.Request request) {
return new LiveConnectionData.Response("", return new LiveConnectionData.Response("",
URI.create("https://example.live"), URI.create("https://example.live"),
WebcastResponse.newBuilder().build()); ProtoMessageFetchResult.newBuilder().build());
}
@Override
public boolean sendChat(LiveRoomInfo roomInfo, String content) {
// DO NOTHING
return false;
} }
} }

View File

@@ -25,15 +25,11 @@ package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.data.dto.MessageMetaData; import io.github.jwdeveloper.tiktok.data.dto.MessageMetaData;
import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent; import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent;
import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketMessageEvent; import io.github.jwdeveloper.tiktok.data.events.websocket.*;
import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketResponseEvent;
import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketUnhandledMessageEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveMessageException; import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveMessageException;
import io.github.jwdeveloper.tiktok.live.LiveClient; import io.github.jwdeveloper.tiktok.live.*;
import io.github.jwdeveloper.tiktok.live.LiveEventsHandler;
import io.github.jwdeveloper.tiktok.live.LiveMessagesHandler;
import io.github.jwdeveloper.tiktok.mappers.LiveMapper; import io.github.jwdeveloper.tiktok.mappers.LiveMapper;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse; import io.github.jwdeveloper.tiktok.messages.webcast.ProtoMessageFetchResult;
import io.github.jwdeveloper.tiktok.utils.Stopwatch; import io.github.jwdeveloper.tiktok.utils.Stopwatch;
import java.time.Duration; import java.time.Duration;
@@ -48,7 +44,7 @@ public class TikTokLiveMessageHandler implements LiveMessagesHandler {
this.mapper = mapper; this.mapper = mapper;
} }
public void handle(LiveClient client, WebcastResponse webcastResponse) { public void handle(LiveClient client, ProtoMessageFetchResult webcastResponse) {
tikTokEventHandler.publish(client, new TikTokWebsocketResponseEvent(webcastResponse)); tikTokEventHandler.publish(client, new TikTokWebsocketResponseEvent(webcastResponse));
for (var message : webcastResponse.getMessagesList()) { for (var message : webcastResponse.getMessagesList()) {
try { try {
@@ -60,7 +56,7 @@ public class TikTokLiveMessageHandler implements LiveMessagesHandler {
} }
} }
public void handleSingleMessage(LiveClient client, WebcastResponse.Message message) { public void handleSingleMessage(LiveClient client, ProtoMessageFetchResult.BaseProtoMessage message) {
var messageClassName = message.getMethod(); var messageClassName = message.getMethod();
if (!mapper.isRegistered(messageClassName)) { if (!mapper.isRegistered(messageClassName)) {
tikTokEventHandler.publish(client, new TikTokWebsocketUnhandledMessageEvent(message)); tikTokEventHandler.publish(client, new TikTokWebsocketUnhandledMessageEvent(message));

View File

@@ -32,37 +32,44 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
@Data @Data
public class TikTokRoomInfo implements LiveRoomInfo { public class TikTokRoomInfo implements LiveRoomInfo
private String roomId; {
private String roomId;
private int likesCount;
private int viewersCount;
private int totalViewersCount;
private long startTime;
private boolean ageRestricted;
private User host;
private List<RankingUser> usersRanking = new LinkedList<>();
private String hostName;
private String title;
private String language = "en";
private ConnectionState connectionState = ConnectionState.DISCONNECTED;
private int likesCount; public boolean hasConnectionState(ConnectionState state) {
return connectionState == state;
}
private int viewersCount; public void updateRanking(List<RankingUser> rankingUsers) {
usersRanking.clear();
usersRanking.addAll(rankingUsers);
}
private int totalViewersCount; @Override
public void copy(LiveRoomInfo roomInfo) {
private long startTime; if (roomInfo == null) return;
this.roomId = roomInfo.getRoomId();
private boolean ageRestricted; this.likesCount = roomInfo.getLikesCount();
this.viewersCount = roomInfo.getViewersCount();
private User host; this.totalViewersCount = roomInfo.getTotalViewersCount();
this.startTime = roomInfo.getStartTime();
private List<RankingUser> usersRanking = new LinkedList<>(); this.ageRestricted = roomInfo.isAgeRestricted();
this.host = roomInfo.getHost();
private String hostName; this.usersRanking = roomInfo.getUsersRanking();
this.hostName = roomInfo.getHostName();
private String title; this.title = roomInfo.getTitle();
this.language = roomInfo.getLanguage();
private String language = "en"; // this.connectionState = roomInfo.getConnectionState(); // This should not be copied - Controlled elsewhere!
}
private ConnectionState connectionState = ConnectionState.DISCONNECTED;
public boolean hasConnectionState(ConnectionState state) {
return connectionState == state;
}
public void updateRanking(List<RankingUser> rankingUsers) {
usersRanking.clear();
usersRanking.addAll(rankingUsers);
}
} }

View File

@@ -0,0 +1,22 @@
package io.github.jwdeveloper.tiktok.common;
import lombok.Getter;
import java.util.concurrent.*;
public class AsyncHandler
{
@Getter
private static final ScheduledExecutorService heartBeatScheduler = Executors.newScheduledThreadPool(1, r -> {
Thread t = new Thread(r, "heartbeat-pool");
t.setDaemon(true);
return t;
});
@Getter
private static final ScheduledExecutorService reconnectScheduler = Executors.newScheduledThreadPool(0, r -> {
Thread t = new Thread(r, "reconnect-pool");
t.setDaemon(true);
return t;
});
}

View File

@@ -40,11 +40,12 @@ public class HttpClient {
protected final HttpClientSettings httpClientSettings; protected final HttpClientSettings httpClientSettings;
protected final String url; protected final String url;
protected final HttpRequest.BodyPublisher bodyPublisher;
private final Pattern pattern = Pattern.compile("charset=(.*?)(?=&|$)"); private final Pattern pattern = Pattern.compile("charset=(.*?)(?=&|$)");
public <T> ActionResult<HttpResponse<T>> toHttpResponse(HttpResponse.BodyHandler<T> handler) { public <T> ActionResult<HttpResponse<T>> toHttpResponse(HttpResponse.BodyHandler<T> handler) {
var client = prepareClient(); var client = prepareClient();
var request = prepareGetRequest(); var request = prepareRequest();
try { try {
var response = client.send(request, handler); var response = client.send(request, handler);
var result = ActionResult.of(response); var result = ActionResult.of(response);
@@ -99,10 +100,19 @@ public class HttpClient {
return URI.create(stringUrl); return URI.create(stringUrl);
} }
protected HttpRequest prepareGetRequest() { /**
var requestBuilder = HttpRequest.newBuilder().GET(); * @return {@link HttpRequest} with default GET, otherwise POST if {@link #bodyPublisher} is not null
*/
protected HttpRequest prepareRequest() {
var requestBuilder = HttpRequest.newBuilder();
if (bodyPublisher != null)
requestBuilder.POST(bodyPublisher);
requestBuilder.uri(toUri()); requestBuilder.uri(toUri());
requestBuilder.timeout(httpClientSettings.getTimeout()); requestBuilder.timeout(httpClientSettings.getTimeout());
if (!httpClientSettings.getCookies().isEmpty()) {
String cookieString = httpClientSettings.getCookies().entrySet().stream().map(e -> e.getKey()+"="+e.getValue()).collect(Collectors.joining("; "));
httpClientSettings.getHeaders().put("Cookie", cookieString);
}
httpClientSettings.getHeaders().forEach(requestBuilder::setHeader); httpClientSettings.getHeaders().forEach(requestBuilder::setHeader);
httpClientSettings.getOnRequestCreating().accept(requestBuilder); httpClientSettings.getOnRequestCreating().accept(requestBuilder);
@@ -120,12 +130,10 @@ public class HttpClient {
} }
protected String prepareUrlWithParameters(String url, Map<String, Object> parameters) { protected String prepareUrlWithParameters(String url, Map<String, Object> parameters) {
if (parameters.isEmpty()) { if (parameters.isEmpty())
return url; return url;
}
return url + "?" + parameters.entrySet().stream().map(entry -> return url + "?" + parameters.entrySet().stream().map(entry -> {
{
var encodedKey = URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8); var encodedKey = URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8);
var encodedValue = URLEncoder.encode(entry.getValue().toString(), StandardCharsets.UTF_8); var encodedValue = URLEncoder.encode(entry.getValue().toString(), StandardCharsets.UTF_8);
return encodedKey + "=" + encodedValue; return encodedKey + "=" + encodedValue;

View File

@@ -24,6 +24,7 @@ package io.github.jwdeveloper.tiktok.http;
import io.github.jwdeveloper.tiktok.data.settings.HttpClientSettings; import io.github.jwdeveloper.tiktok.data.settings.HttpClientSettings;
import java.net.http.HttpRequest;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -31,6 +32,7 @@ public class HttpClientBuilder {
private final HttpClientSettings httpClientSettings; private final HttpClientSettings httpClientSettings;
private String url; private String url;
private HttpRequest.BodyPublisher bodyPublisher;
public HttpClientBuilder(String url, HttpClientSettings httpClientSettings) { public HttpClientBuilder(String url, HttpClientSettings httpClientSettings) {
this.httpClientSettings = httpClientSettings; this.httpClientSettings = httpClientSettings;
@@ -53,7 +55,8 @@ public class HttpClientBuilder {
} }
public HttpClientBuilder withCookie(String name, String value) { public HttpClientBuilder withCookie(String name, String value) {
httpClientSettings.getCookies().put(name, value); if (name != null && value != null)
httpClientSettings.getCookies().put(name, value);
return this; return this;
} }
@@ -77,10 +80,15 @@ public class HttpClientBuilder {
return this; return this;
} }
public HttpClientBuilder withBody(HttpRequest.BodyPublisher bodyPublisher) {
this.bodyPublisher = bodyPublisher;
return this;
}
public HttpClient build() { public HttpClient build() {
var proxyClientSettings = httpClientSettings.getProxyClientSettings(); var proxyClientSettings = httpClientSettings.getProxyClientSettings();
if (proxyClientSettings.isEnabled() && proxyClientSettings.hasNext()) if (proxyClientSettings.isEnabled() && proxyClientSettings.hasNext())
return new HttpProxyClient(httpClientSettings, url); return new HttpProxyClient(httpClientSettings, url, bodyPublisher);
return new HttpClient(httpClientSettings, url); return new HttpClient(httpClientSettings, url, bodyPublisher);
} }
} }

View File

@@ -40,8 +40,8 @@ public class HttpProxyClient extends HttpClient {
private final ProxyClientSettings proxySettings; private final ProxyClientSettings proxySettings;
public HttpProxyClient(HttpClientSettings httpClientSettings, String url) { public HttpProxyClient(HttpClientSettings httpClientSettings, String url, HttpRequest.BodyPublisher bodyPublisher) {
super(httpClientSettings, url); super(httpClientSettings, url, bodyPublisher);
this.proxySettings = httpClientSettings.getProxyClientSettings(); this.proxySettings = httpClientSettings.getProxyClientSettings();
} }
@@ -65,7 +65,7 @@ public class HttpProxyClient extends HttpClient {
httpClientSettings.getOnClientCreating().accept(builder); httpClientSettings.getOnClientCreating().accept(builder);
var client = builder.build(); var client = builder.build();
var request = prepareGetRequest(); var request = prepareRequest();
var response = client.send(request, HttpResponse.BodyHandlers.ofByteArray()); var response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
if (response.statusCode() != 200) if (response.statusCode() != 200)

View File

@@ -32,7 +32,7 @@ import java.util.List;
public class GiftsDataMapper { public class GiftsDataMapper {
public GiftsData.Response map(String json) { public static GiftsData.Response map(String json) {
var parsedJson = JsonParser.parseString(json); var parsedJson = JsonParser.parseString(json);
var jsonObject = parsedJson.getAsJsonObject(); var jsonObject = parsedJson.getAsJsonObject();
var gifts = jsonObject.entrySet() var gifts = jsonObject.entrySet()
@@ -43,7 +43,7 @@ public class GiftsDataMapper {
return new GiftsData.Response(json, gifts); return new GiftsData.Response(json, gifts);
} }
private Gift mapSingleGift(JsonElement jsonElement) { private static Gift mapSingleGift(JsonElement jsonElement) {
var jsonObject = jsonElement.getAsJsonObject(); var jsonObject = jsonElement.getAsJsonObject();
var id = jsonObject.get("id").getAsInt(); var id = jsonObject.get("id").getAsInt();
@@ -53,14 +53,13 @@ public class GiftsDataMapper {
return new Gift(id, name, diamondCost, new Picture(image), jsonObject); return new Gift(id, name, diamondCost, new Picture(image), jsonObject);
} }
public GiftsData.Response mapRoom(String json) { public static GiftsData.Response mapRoom(String json) {
var parsedJson = JsonParser.parseString(json); var parsedJson = JsonParser.parseString(json);
var jsonObject = parsedJson.getAsJsonObject(); var jsonObject = parsedJson.getAsJsonObject();
if (jsonObject.get("data") instanceof JsonObject data && data.get("gifts") instanceof JsonArray giftArray) { if (jsonObject.get("data") instanceof JsonObject data && data.get("gifts") instanceof JsonArray giftArray) {
var gifts = new ArrayList<Gift>(); var gifts = new ArrayList<Gift>();
for(int i = 0; i < giftArray.size(); i++) { for (JsonElement element : giftArray) {
JsonElement element = giftArray.get(i);
Gift gift = mapSingleRoomGift(element); Gift gift = mapSingleRoomGift(element);
gifts.add(gift); gifts.add(gift);
} }
@@ -70,7 +69,7 @@ public class GiftsDataMapper {
return new GiftsData.Response("", List.of()); return new GiftsData.Response("", List.of());
} }
private Gift mapSingleRoomGift(JsonElement jsonElement) { private static Gift mapSingleRoomGift(JsonElement jsonElement) {
var jsonObject = jsonElement.getAsJsonObject(); var jsonObject = jsonElement.getAsJsonObject();
var id = jsonObject.get("id").getAsInt(); var id = jsonObject.get("id").getAsInt();

View File

@@ -41,7 +41,7 @@ public class LiveDataMapper {
* 3 - ? * 3 - ?
* 4 - Offline * 4 - Offline
*/ */
public LiveData.Response map(String json) { public static LiveData.Response map(String json) {
var response = new LiveData.Response(); var response = new LiveData.Response();
response.setJson(json); response.setJson(json);
@@ -61,6 +61,7 @@ public class LiveDataMapper {
var statusId = status.getAsInt(); var statusId = status.getAsInt();
var statusValue = switch (statusId) { var statusValue = switch (statusId) {
case 2 -> LiveData.LiveStatus.HostOnline; case 2 -> LiveData.LiveStatus.HostOnline;
case 3 -> LiveData.LiveStatus.HostPaused;
case 4 -> LiveData.LiveStatus.HostOffline; case 4 -> LiveData.LiveStatus.HostOffline;
default -> LiveData.LiveStatus.HostNotFound; default -> LiveData.LiveStatus.HostNotFound;
}; };
@@ -127,7 +128,7 @@ public class LiveDataMapper {
return response; return response;
} }
public User getUser(JsonObject jsonElement) { public static User getUser(JsonObject jsonElement) {
var id = jsonElement.get("id").getAsLong(); var id = jsonElement.get("id").getAsLong();
var name = jsonElement.get("display_id").getAsString(); var name = jsonElement.get("display_id").getAsString();
var profileName = jsonElement.get("nickname").getAsString(); var profileName = jsonElement.get("nickname").getAsString();

View File

@@ -23,6 +23,7 @@
package io.github.jwdeveloper.tiktok.http.mappers; package io.github.jwdeveloper.tiktok.http.mappers;
import com.google.gson.*; import com.google.gson.*;
import io.github.jwdeveloper.tiktok.*;
import io.github.jwdeveloper.tiktok.data.models.Picture; 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.User;
import io.github.jwdeveloper.tiktok.data.requests.LiveUserData; import io.github.jwdeveloper.tiktok.data.requests.LiveUserData;
@@ -33,7 +34,7 @@ import java.util.logging.Logger;
public class LiveUserDataMapper public class LiveUserDataMapper
{ {
public LiveUserData.Response map(String json, Logger logger) { public static LiveUserData.Response map(String json, Logger logger) {
try { try {
var jsonObject = JsonParser.parseString(json).getAsJsonObject(); var jsonObject = JsonParser.parseString(json).getAsJsonObject();
@@ -43,14 +44,14 @@ public class LiveUserDataMapper
throw new TikTokLiveRequestException("fetchRoomIdFromTiktokApi -> Unable to fetch roomID, contact the developer"); throw new TikTokLiveRequestException("fetchRoomIdFromTiktokApi -> Unable to fetch roomID, contact the developer");
} }
if (message.equals("user_not_found")) { if (message.equals("user_not_found")) {
return new LiveUserData.Response(json, LiveUserData.UserStatus.NotFound, "", -1, null); return new LiveUserData.Response(json, LiveUserData.UserStatus.NotFound, null);
} }
//live -> status 2 //live -> status 2
//live paused -> 3 //live paused -> 3
//not live -> status 4 //not live -> status 4
var element = jsonObject.get("data"); var element = jsonObject.get("data");
if (element.isJsonNull()) { if (element.isJsonNull()) {
return new LiveUserData.Response(json, LiveUserData.UserStatus.NotFound, "", -1, null); return new LiveUserData.Response(json, LiveUserData.UserStatus.NotFound, null);
} }
var data = element.getAsJsonObject(); var data = element.getAsJsonObject();
var user = data.getAsJsonObject("user"); var user = data.getAsJsonObject("user");
@@ -58,8 +59,17 @@ public class LiveUserDataMapper
var roomId = user.get("roomId").getAsString(); var roomId = user.get("roomId").getAsString();
var status = user.get("status").getAsInt(); var status = user.get("status").getAsInt();
TikTokRoomInfo roomInfo = new TikTokRoomInfo();
roomInfo.setRoomId(roomId);
var liveRoom = data.getAsJsonObject("liveRoom"); var liveRoom = data.getAsJsonObject("liveRoom");
long startTime = liveRoom.get("startTime").getAsLong();
roomInfo.setTitle(liveRoom.get("title").getAsString());
roomInfo.setStartTime(liveRoom.get("startTime").getAsLong());
roomInfo.setTitle(liveRoom.get("title").getAsString());
roomInfo.setViewersCount(liveRoom.getAsJsonObject("liveRoomStats").get("userCount").getAsInt());
roomInfo.setTotalViewersCount(liveRoom.getAsJsonObject("liveRoomStats").get("enterCount").getAsInt());
roomInfo.setAgeRestricted(jsonObject.get("statusCode").getAsInt() == TikTokLiveHttpClient.TIKTOK_AGE_RESTRICTED_CODE);
var statusEnum = switch (status) { var statusEnum = switch (status) {
case 2 -> LiveUserData.UserStatus.Live; case 2 -> LiveUserData.UserStatus.Live;
@@ -78,10 +88,13 @@ public class LiveUserDataMapper
stats.get("followerCount").getAsLong(), stats.get("followerCount").getAsLong(),
List.of()); List.of());
return new LiveUserData.Response(json, statusEnum, roomId, startTime, foundUser); roomInfo.setHost(foundUser);
roomInfo.setHostName(foundUser.getName());
return new LiveUserData.Response(json, statusEnum, roomInfo);
} catch (JsonSyntaxException | IllegalStateException e) { } catch (JsonSyntaxException | IllegalStateException e) {
logger.warning("Malformed Json: '"+json+"' - Error Message: "+e.getMessage()); logger.warning("Malformed Json: '"+json+"' - Error Message: "+e.getMessage());
return new LiveUserData.Response(json, LiveUserData.UserStatus.NotFound, "", -1, null); return new LiveUserData.Response(json, LiveUserData.UserStatus.NotFound, null);
} }
} }
} }

View File

@@ -104,7 +104,7 @@ public class MessagesMapperFactory {
var message = mapperHelper.bytesToWebcastObject(inputBytes, WebcastLinkMicArmies.class); var message = mapperHelper.bytesToWebcastObject(inputBytes, WebcastLinkMicArmies.class);
return MappingResult.of(message, new TikTokLinkMicArmiesEvent(message)); return MappingResult.of(message, new TikTokLinkMicArmiesEvent(message));
}); });
mapper.forMessage(WebcastLinkMessage.class, ((inputBytes, messageName, mapperHelper) -> { mapper.forMessage(WebcastLinkMessage.class, (inputBytes, messageName, mapperHelper) -> {
var message = mapperHelper.bytesToWebcastObject(inputBytes, WebcastLinkMessage.class); var message = mapperHelper.bytesToWebcastObject(inputBytes, WebcastLinkMessage.class);
return MappingResult.of(message, switch (message.getMessageType()) { return MappingResult.of(message, switch (message.getMessageType()) {
case TYPE_LINKER_INVITE -> new TikTokLinkInviteEvent(message); case TYPE_LINKER_INVITE -> new TikTokLinkInviteEvent(message);
@@ -116,8 +116,7 @@ public class MessagesMapperFactory {
case TYPE_LINKER_KICK_OUT -> new TikTokLinkKickOutEvent(message); case TYPE_LINKER_KICK_OUT -> new TikTokLinkKickOutEvent(message);
case TYPE_LINKER_LINKED_LIST_CHANGE -> new TikTokLinkLinkedListChangeEvent(message); case TYPE_LINKER_LINKED_LIST_CHANGE -> new TikTokLinkLinkedListChangeEvent(message);
case TYPE_LINKER_UPDATE_USER -> new TikTokLinkUpdateUserEvent(message); case TYPE_LINKER_UPDATE_USER -> new TikTokLinkUpdateUserEvent(message);
case TYPE_LINKER_WAITING_LIST_CHANGE, TYPE_LINKER_WAITING_LIST_CHANGE_V2 -> case TYPE_LINKER_WAITING_LIST_CHANGE, TYPE_LINKER_WAITING_LIST_CHANGE_V2 -> new TikTokLinkWaitListChangeEvent(message);
new TikTokLinkWaitListChangeEvent(message);
case TYPE_LINKER_MUTE -> new TikTokLinkMuteEvent(message); case TYPE_LINKER_MUTE -> new TikTokLinkMuteEvent(message);
case TYPE_LINKER_MATCH -> new TikTokLinkRandomMatchEvent(message); case TYPE_LINKER_MATCH -> new TikTokLinkRandomMatchEvent(message);
case TYPE_LINKER_UPDATE_USER_SETTING -> new TikTokLinkUpdateUserSettingEvent(message); case TYPE_LINKER_UPDATE_USER_SETTING -> new TikTokLinkUpdateUserSettingEvent(message);
@@ -130,7 +129,11 @@ public class MessagesMapperFactory {
case TYPE_LINKMIC_USER_TOAST -> new TikTokLinkUserToastEvent(message); case TYPE_LINKMIC_USER_TOAST -> new TikTokLinkUserToastEvent(message);
default -> new TikTokLinkEvent(message); default -> new TikTokLinkEvent(message);
}); });
})); });
mapper.forMessage(WebcastLinkMicBattleItemCard.class, (inputBytes, messageName, mapperHelper) -> {
var message = mapperHelper.bytesToWebcastObject(inputBytes, WebcastLinkMicBattleItemCard.class);
return MappingResult.of(message, new TikTokLinkMicBattleItemCard(message));
});
// mapper.webcastObjectToConstructor(WebcastLinkMicMethod.class, TikTokLinkMicMethodEvent.class); // mapper.webcastObjectToConstructor(WebcastLinkMicMethod.class, TikTokLinkMicMethodEvent.class);
// mapper.webcastObjectToConstructor(WebcastLinkMicFanTicketMethod.class, TikTokLinkMicFanTicketEvent.class); // mapper.webcastObjectToConstructor(WebcastLinkMicFanTicketMethod.class, TikTokLinkMicFanTicketEvent.class);

View File

@@ -25,18 +25,14 @@ package io.github.jwdeveloper.tiktok.mappers.handlers;
import io.github.jwdeveloper.tiktok.data.events.*; import io.github.jwdeveloper.tiktok.data.events.*;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.envelop.TikTokChestEvent; import io.github.jwdeveloper.tiktok.data.events.envelop.TikTokChestEvent;
import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollEndEvent; import io.github.jwdeveloper.tiktok.data.events.poll.*;
import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollEvent;
import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollStartEvent;
import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollUpdateEvent;
import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomPinEvent; import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomPinEvent;
import io.github.jwdeveloper.tiktok.data.models.chest.Chest; import io.github.jwdeveloper.tiktok.data.models.chest.Chest;
import io.github.jwdeveloper.tiktok.messages.enums.EnvelopeDisplay; import io.github.jwdeveloper.tiktok.messages.enums.EnvelopeDisplay;
import io.github.jwdeveloper.tiktok.messages.webcast.*; import io.github.jwdeveloper.tiktok.messages.webcast.*;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import java.util.Collections; import java.util.*;
import java.util.List;
public class TikTokCommonEventHandler public class TikTokCommonEventHandler
{ {
@@ -55,7 +51,7 @@ public class TikTokCommonEventHandler
@SneakyThrows @SneakyThrows
public TikTokEvent handlePinMessage(byte[] msg) { public TikTokEvent handlePinMessage(byte[] msg) {
var pinMessage = WebcastRoomPinMessage.parseFrom(msg); var pinMessage = WebcastRoomPinMessage.parseFrom(msg);
var chatMessage = WebcastChatMessage.parseFrom(pinMessage.getPinnedMessage()); var chatMessage = pinMessage.getChatMessage();
var chatEvent = new TikTokCommentEvent(chatMessage); var chatEvent = new TikTokCommentEvent(chatMessage);
return new TikTokRoomPinEvent(pinMessage, chatEvent); return new TikTokRoomPinEvent(pinMessage, chatEvent);
} }
@@ -75,7 +71,7 @@ public class TikTokCommonEventHandler
@SneakyThrows @SneakyThrows
public List<TikTokEvent> handleEnvelop(byte[] data) { public List<TikTokEvent> handleEnvelop(byte[] data) {
var msg = WebcastEnvelopeMessage.parseFrom(data); var msg = WebcastEnvelopeMessage.parseFrom(data);
if (msg.getDisplay() != EnvelopeDisplay.EnvelopeDisplayNew) { if (msg.getDisplay() != EnvelopeDisplay.ENVELOPE_DISPLAY_NEW) {
return Collections.emptyList(); return Collections.emptyList();
} }
var totalDiamonds = msg.getEnvelopeInfo().getDiamondCount(); var totalDiamonds = msg.getEnvelopeInfo().getDiamondCount();

View File

@@ -26,10 +26,9 @@ import io.github.jwdeveloper.tiktok.data.dto.ProxyData;
import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData; import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData;
import io.github.jwdeveloper.tiktok.data.settings.*; import io.github.jwdeveloper.tiktok.data.settings.*;
import io.github.jwdeveloper.tiktok.exceptions.*; import io.github.jwdeveloper.tiktok.exceptions.*;
import io.github.jwdeveloper.tiktok.live.LiveClient; import io.github.jwdeveloper.tiktok.live.*;
import io.github.jwdeveloper.tiktok.live.LiveEventsHandler;
import io.github.jwdeveloper.tiktok.live.LiveMessagesHandler;
import org.java_websocket.client.WebSocketClient; import org.java_websocket.client.WebSocketClient;
import org.java_websocket.framing.CloseFrame;
import javax.net.ssl.*; import javax.net.ssl.*;
import java.net.Proxy; import java.net.Proxy;
@@ -42,7 +41,6 @@ public class TikTokWebSocketClient implements LiveSocketClient {
private final LiveEventsHandler tikTokEventHandler; private final LiveEventsHandler tikTokEventHandler;
private final WebSocketHeartbeatTask heartbeatTask; private final WebSocketHeartbeatTask heartbeatTask;
private WebSocketClient webSocketClient; private WebSocketClient webSocketClient;
private boolean isConnected;
public TikTokWebSocketClient( public TikTokWebSocketClient(
LiveClientSettings clientSettings, LiveClientSettings clientSettings,
@@ -54,25 +52,23 @@ public class TikTokWebSocketClient implements LiveSocketClient {
this.messageHandler = messageHandler; this.messageHandler = messageHandler;
this.tikTokEventHandler = tikTokEventHandler; this.tikTokEventHandler = tikTokEventHandler;
this.heartbeatTask = heartbeatTask; this.heartbeatTask = heartbeatTask;
isConnected = false;
} }
@Override @Override
public void start(LiveConnectionData.Response connectionData, LiveClient liveClient) { public void start(LiveConnectionData.Response connectionData, LiveClient liveClient) {
if (isConnected) { if (isConnected())
stop(); stop(LiveClientStopType.NORMAL);
}
messageHandler.handle(liveClient, connectionData.getWebcastResponse()); messageHandler.handle(liveClient, connectionData.getWebcastResponse());
var headers = new HashMap<>(clientSettings.getHttpSettings().getHeaders()); var headers = new HashMap<>(clientSettings.getHttpSettings().getHeaders());
headers.put("Cookie", connectionData.getWebsocketCookies()); headers.put("Cookie", connectionData.getWebsocketCookies());
webSocketClient = new TikTokWebSocketListener(connectionData.getWebsocketUrl(), webSocketClient = new TikTokWebSocketListener(connectionData.getWebsocketUrl(),
headers, headers,
clientSettings.getHttpSettings().getTimeout().toMillisPart(), clientSettings.getHttpSettings().getTimeout().toMillisPart(),
messageHandler, messageHandler,
tikTokEventHandler, tikTokEventHandler,
liveClient); liveClient);
ProxyClientSettings proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings(); ProxyClientSettings proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled() && proxyClientSettings.isAllowWebsocket()) if (proxyClientSettings.isEnabled() && proxyClientSettings.isAllowWebsocket())
@@ -81,13 +77,11 @@ public class TikTokWebSocketClient implements LiveSocketClient {
connectDefault(); connectDefault();
} }
private void connectDefault() { public void connectDefault() {
try { try {
webSocketClient.connect(); webSocketClient.connect();
heartbeatTask.run(webSocketClient, clientSettings.getPingInterval()); heartbeatTask.run(webSocketClient, clientSettings.getPingInterval());
isConnected = true;
} catch (Exception e) { } catch (Exception e) {
isConnected = false;
throw new TikTokLiveException("Failed to connect to the websocket", e); throw new TikTokLiveException("Failed to connect to the websocket", e);
} }
} }
@@ -117,14 +111,12 @@ public class TikTokWebSocketClient implements LiveSocketClient {
ProxyData proxyData = proxySettings.next(); ProxyData proxyData = proxySettings.next();
if (tryProxyConnection(proxySettings, proxyData)) { if (tryProxyConnection(proxySettings, proxyData)) {
heartbeatTask.run(webSocketClient, clientSettings.getPingInterval()); heartbeatTask.run(webSocketClient, clientSettings.getPingInterval());
isConnected = true; return;
break;
} }
if (proxySettings.isAutoDiscard()) if (proxySettings.isAutoDiscard())
proxySettings.remove(); proxySettings.remove();
} }
if (!isConnected) throw new TikTokLiveException("Failed to connect to the websocket");
throw new TikTokLiveException("Failed to connect to the websocket");
} }
public boolean tryProxyConnection(ProxyClientSettings proxySettings, ProxyData proxyData) { public boolean tryProxyConnection(ProxyClientSettings proxySettings, ProxyData proxyData) {
@@ -137,12 +129,25 @@ public class TikTokWebSocketClient implements LiveSocketClient {
} }
} }
public void stop() { public void stop(LiveClientStopType type) {
if (isConnected && webSocketClient != null && webSocketClient.isOpen()) { if (isConnected()) {
webSocketClient.closeConnection(0, ""); switch (type) {
case CLOSE_BLOCKING -> {
try {
webSocketClient.closeBlocking();
} catch (InterruptedException e) {
throw new TikTokLiveException("Failed to stop the websocket");
}
}
case DISCONNECT -> webSocketClient.closeConnection(CloseFrame.NORMAL, "");
default -> webSocketClient.close();
}
heartbeatTask.stop(); heartbeatTask.stop();
} }
webSocketClient = null; webSocketClient = null;
isConnected = false; }
public boolean isConnected() {
return webSocketClient != null && webSocketClient.isOpen();
} }
} }

View File

@@ -39,9 +39,9 @@ import java.util.*;
public class TikTokWebSocketListener extends WebSocketClient { public class TikTokWebSocketListener extends WebSocketClient {
private final LiveMessagesHandler messagesHandler; protected final LiveMessagesHandler messagesHandler;
private final LiveEventsHandler eventHandler; protected final LiveEventsHandler eventHandler;
private final LiveClient liveClient; protected final LiveClient liveClient;
public TikTokWebSocketListener(URI serverUri, public TikTokWebSocketListener(URI serverUri,
Map<String, String> httpHeaders, Map<String, String> httpHeaders,
@@ -67,7 +67,7 @@ public class TikTokWebSocketListener extends WebSocketClient {
} }
} }
private void handleBinary(byte[] buffer) { protected void handleBinary(byte[] buffer) {
var websocketPushFrameOptional = getWebcastPushFrame(buffer); var websocketPushFrameOptional = getWebcastPushFrame(buffer);
if (websocketPushFrameOptional.isEmpty()) { if (websocketPushFrameOptional.isEmpty()) {
return; return;
@@ -97,7 +97,7 @@ public class TikTokWebSocketListener extends WebSocketClient {
@Override @Override
public void onClose(int code, String reason, boolean remote) { public void onClose(int code, String reason, boolean remote) {
eventHandler.publish(liveClient, new TikTokDisconnectedEvent(reason)); eventHandler.publish(liveClient, new TikTokDisconnectedEvent(code, reason));
liveClient.disconnect(); liveClient.disconnect();
} }
@@ -111,21 +111,17 @@ public class TikTokWebSocketListener extends WebSocketClient {
private Optional<WebcastPushFrame> getWebcastPushFrame(byte[] buffer) { private Optional<WebcastPushFrame> getWebcastPushFrame(byte[] buffer) {
try { try {
var websocketMessage = WebcastPushFrame.parseFrom(buffer); return Optional.of(WebcastPushFrame.parseFrom(buffer)).filter(msg -> !msg.getPayload().isEmpty());
if (websocketMessage.getPayload().isEmpty()) { } catch (Exception e) {
return Optional.empty();
}
return Optional.of(websocketMessage);
} catch (Exception e) {
throw new TikTokProtocolBufferException("Unable to parse WebcastPushFrame", buffer, e); throw new TikTokProtocolBufferException("Unable to parse WebcastPushFrame", buffer, e);
} }
} }
private WebcastResponse getWebResponseMessage(ByteString buffer) { private ProtoMessageFetchResult getWebResponseMessage(ByteString buffer) {
try { try {
return WebcastResponse.parseFrom(buffer); return ProtoMessageFetchResult.parseFrom(buffer);
} catch (Exception e) { } catch (Exception e) {
throw new TikTokProtocolBufferException("Unable to parse WebcastResponse", buffer.toByteArray(), e); throw new TikTokProtocolBufferException("Unable to parse ProtoMessageFetchResult", buffer.toByteArray(), e);
} }
} }

View File

@@ -44,10 +44,13 @@ public class TikTokWebSocketOfflineClient implements LiveSocketClient {
} }
@Override @Override
public void stop() { public void stop(LiveClientStopType type) {
if (liveClient == null) { if (liveClient != null)
return; handler.publish(liveClient, new TikTokDisconnectedEvent("Stopping"));
} }
handler.publish(liveClient, new TikTokDisconnectedEvent("Stopping"));
@Override
public boolean isConnected() {
return false;
} }
} }

View File

@@ -22,43 +22,38 @@
*/ */
package io.github.jwdeveloper.tiktok.websocket; package io.github.jwdeveloper.tiktok.websocket;
import io.github.jwdeveloper.tiktok.common.AsyncHandler;
import org.java_websocket.WebSocket; import org.java_websocket.WebSocket;
import java.util.Base64; import java.util.*;
import java.util.concurrent.*;
public class WebSocketHeartbeatTask public class WebSocketHeartbeatTask
{ {
private Thread thread; private ScheduledFuture<?> task;
private boolean isRunning = false; private Long commTime;
private final int MAX_TIMEOUT = 250;
private final int SLEEP_TIME = 500; private final static byte[] heartbeatBytes = Base64.getDecoder().decode("MgJwYjoCaGI="); // Used to be '3A026862' aka ':\x02hb', now is '2\x02pb:\x02hb'.
private final byte[] heartbeatBytes = Base64.getDecoder().decode("MgJwYjoCaGI="); // Used to be '3A026862' aka ':\x02hb', now is '2\x02pb:\x02hb'.
public void run(WebSocket webSocket, long pingTaskTime) { public void run(WebSocket webSocket, long pingTaskTime) {
stop(); stop(); // remove existing task if any
thread = new Thread(() -> heartbeatTask(webSocket, pingTaskTime), "heartbeat-task");
isRunning = true;
thread.start();
}
public void stop() { task = AsyncHandler.getHeartBeatScheduler().scheduleAtFixedRate(() -> {
if (thread != null)
thread.interrupt();
isRunning = false;
}
private void heartbeatTask(WebSocket webSocket, long pingTaskTime) {
while (isRunning) {
try { try {
if (webSocket.isOpen()) { if (webSocket.isOpen()) {
webSocket.send(heartbeatBytes); webSocket.send(heartbeatBytes);
Thread.sleep(pingTaskTime + (int) (Math.random() * MAX_TIMEOUT)); commTime = System.currentTimeMillis();
} else } else if (commTime != null && System.currentTimeMillis() - commTime >= 60_000) // Stop if disconnected longer than 60s
Thread.sleep(SLEEP_TIME); stop();
} catch (Exception e) { } catch (Exception e) {
//TODO we should display some kind of error message e.printStackTrace();
isRunning = false; stop();
} }
} }, 0, pingTaskTime, TimeUnit.MILLISECONDS);
}
public void stop() {
if (task != null)
task.cancel(true);
} }
} }

View File

@@ -0,0 +1,100 @@
/*
* Copyright (c) 2023-2024 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.websocket.euler;
import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData;
import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.live.*;
import io.github.jwdeveloper.tiktok.websocket.*;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.framing.CloseFrame;
import java.net.URI;
import java.util.HashMap;
public class TikTokWebSocketEulerClient implements LiveSocketClient {
private final LiveClientSettings clientSettings;
private final LiveMessagesHandler messageHandler;
private final LiveEventsHandler tikTokEventHandler;
private WebSocketClient webSocketClient;
public TikTokWebSocketEulerClient(
LiveClientSettings clientSettings,
LiveMessagesHandler messageHandler,
LiveEventsHandler tikTokEventHandler)
{
this.clientSettings = clientSettings;
this.messageHandler = messageHandler;
this.tikTokEventHandler = tikTokEventHandler;
}
@Override
public void start(LiveConnectionData.Response connectionData, LiveClient liveClient) {
if (isConnected())
stop(LiveClientStopType.NORMAL);
String url = "wss://ws.eulerstream.com?uniqueId=%s&apiKey=%s&features.rawMessages=true".formatted(liveClient.getRoomInfo().getHostName(), clientSettings.getApiKey())
+ (clientSettings.isUseEulerstreamWebsocket() ? "&features.useEnterpriseApi=true" : "");
webSocketClient = new TikTokWebSocketEulerListener(
URI.create(url),
new HashMap<>(clientSettings.getHttpSettings().getHeaders()),
clientSettings.getHttpSettings().getTimeout().toMillisPart(),
messageHandler,
tikTokEventHandler,
liveClient);
connect();
}
public void connect() {
try {
webSocketClient.connect();
} catch (Exception e) {
throw new TikTokLiveException("Failed to connect to the websocket", e);
}
}
public void stop(LiveClientStopType type) {
if (isConnected()) {
switch (type) {
case CLOSE_BLOCKING -> {
try {
webSocketClient.closeBlocking();
} catch (InterruptedException e) {
throw new TikTokLiveException("Failed to stop the websocket");
}
}
case DISCONNECT -> webSocketClient.closeConnection(CloseFrame.NORMAL, "");
default -> webSocketClient.close();
}
}
webSocketClient = null;
}
public boolean isConnected() {
return webSocketClient != null && webSocketClient.isOpen();
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright (c) 2023-2024 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.websocket.euler;
import com.google.gson.*;
import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent;
import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomInfoEvent;
import io.github.jwdeveloper.tiktok.data.requests.LiveUserData;
import io.github.jwdeveloper.tiktok.http.mappers.LiveUserDataMapper;
import io.github.jwdeveloper.tiktok.live.*;
import io.github.jwdeveloper.tiktok.websocket.TikTokWebSocketListener;
import java.net.URI;
import java.util.Map;
public class TikTokWebSocketEulerListener extends TikTokWebSocketListener
{
public TikTokWebSocketEulerListener(URI serverUri,
Map<String, String> httpHeaders,
int connectTimeout,
LiveMessagesHandler messageHandler,
LiveEventsHandler tikTokEventHandler,
LiveClient tikTokLiveClient) {
super(serverUri, httpHeaders, connectTimeout, messageHandler, tikTokEventHandler, tikTokLiveClient);
}
@Override
public void onMessage(String raw) {
try {
JsonElement element = JsonParser.parseString(raw);
if (element instanceof JsonObject o) {
if (o.get("messages") instanceof JsonArray msgs) {
for (JsonElement msg : msgs) {
if (msg instanceof JsonObject oMsg) {
switch (oMsg.get("type").getAsString()) { // Should only receive these 2 types ever
case "workerInfo" -> liveClient.getLogger().info(oMsg.toString()); // Always 1st message
case "roomInfo" -> { // Always 2nd message
LiveUserData.Response data = LiveUserDataMapper.map(oMsg.getAsJsonObject("data").getAsJsonObject("data").getAsJsonObject("raw").toString(), liveClient.getLogger());
liveClient.getRoomInfo().copy(data.getRoomInfo());
eventHandler.publish(liveClient, new TikTokRoomInfoEvent(liveClient.getRoomInfo()));
}
}
}
}
}
} else
throw new IllegalArgumentException("Invalid JsonObject: "+element);
} catch (Exception e) {
eventHandler.publish(liveClient, new TikTokErrorEvent(e));
}
if (isOpen()) {
sendPing();
}
}
}

View File

@@ -30,7 +30,6 @@ import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.data.models.gifts.GiftComboStateType; import io.github.jwdeveloper.tiktok.data.models.gifts.GiftComboStateType;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftsManager; import io.github.jwdeveloper.tiktok.gifts.TikTokGiftsManager;
import io.github.jwdeveloper.tiktok.mappers.handlers.TikTokGiftEventHandler; import io.github.jwdeveloper.tiktok.mappers.handlers.TikTokGiftEventHandler;
import io.github.jwdeveloper.tiktok.messages.data.GiftStruct;
import io.github.jwdeveloper.tiktok.messages.data.Image; import io.github.jwdeveloper.tiktok.messages.data.Image;
import io.github.jwdeveloper.tiktok.messages.data.User; import io.github.jwdeveloper.tiktok.messages.data.User;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage;
@@ -111,7 +110,7 @@ class TikTokGiftEventHandlerTest {
int userId, int userId,
boolean streakable) { boolean streakable) {
var builder = WebcastGiftMessage.newBuilder(); var builder = WebcastGiftMessage.newBuilder();
var giftBuilder = GiftStruct.newBuilder(); var giftBuilder = io.github.jwdeveloper.tiktok.messages.data.Gift.newBuilder();
var userBuilder = User.newBuilder(); var userBuilder = User.newBuilder();

View File

@@ -70,7 +70,7 @@ Maven
<dependency> <dependency>
<groupId>com.github.jwdeveloper.TikTok-Live-Java</groupId> <groupId>com.github.jwdeveloper.TikTok-Live-Java</groupId>
<artifactId>Client</artifactId> <artifactId>Client</artifactId>
<version>1.8.13-Release</version> <version>1.11.0-Release</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
</dependencies> </dependencies>
@@ -87,7 +87,7 @@ dependencyResolutionManagement {
} }
dependencies { dependencies {
implementation 'com.github.jwdeveloper.TikTok-Live-Java:Client:1.8.13-Release' implementation 'com.github.jwdeveloper.TikTok-Live-Java:Client:1.10.0-Release'
} }
``` ```
@@ -746,7 +746,6 @@ public static class CustomListener {
} }
//
``` ```

View File

@@ -41,7 +41,7 @@
<parent> <parent>
<artifactId>TikTokLiveJava</artifactId> <artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId> <groupId>io.github.jwdeveloper.tiktok</groupId>
<version>1.8.13-Release</version> <version>1.11.2-Release</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@@ -72,12 +72,6 @@
<version>${project.version}</version> <version>${project.version}</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>extension-collector</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>
<properties> <properties>

View File

@@ -64,8 +64,7 @@ public class Events_And_Gifts_Testing_Example
}) })
.build(); .build();
var gifts = TikTokLive.gifts(); var roseGift = client.getGiftManager().getByName("Rose");
var roseGift = gifts.getByName("Rose");
var fakeGift = TikTokGiftEvent.of(roseGift); var fakeGift = TikTokGiftEvent.of(roseGift);
var fakeComboGift = TikTokGiftComboEvent.of(roseGift, 12, GiftComboStateType.Begin); var fakeComboGift = TikTokGiftComboEvent.of(roseGift, 12, GiftComboStateType.Begin);

View File

@@ -1,61 +0,0 @@
/*
* Copyright (c) 2023-2024 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

@@ -6,7 +6,7 @@
<parent> <parent>
<groupId>io.github.jwdeveloper.tiktok</groupId> <groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>TikTokLiveJava</artifactId> <artifactId>TikTokLiveJava</artifactId>
<version>1.8.13-Release</version> <version>1.11.2-Release</version>
</parent> </parent>
@@ -30,12 +30,6 @@
<artifactId>mongodb-driver-sync</artifactId> <artifactId>mongodb-driver-sync</artifactId>
<version>4.4.0</version> <version>4.4.0</version>
</dependency> </dependency>
<dependency>
<groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>API</artifactId>
<version>1.8.13-Release</version>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -29,16 +29,14 @@ import io.github.jwdeveloper.tiktok.data.events.control.TikTokConnectingEvent;
import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomInfoEvent; import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomInfoEvent;
import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketResponseEvent; import io.github.jwdeveloper.tiktok.data.events.websocket.TikTokWebsocketResponseEvent;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveMessageException; import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveMessageException;
import io.github.jwdeveloper.tiktok.extension.collector.api.LiveDataCollector; import io.github.jwdeveloper.tiktok.extension.collector.api.*;
import io.github.jwdeveloper.tiktok.extension.collector.api.Storage;
import io.github.jwdeveloper.tiktok.extension.collector.api.settings.CollectorListenerSettings; import io.github.jwdeveloper.tiktok.extension.collector.api.settings.CollectorListenerSettings;
import io.github.jwdeveloper.tiktok.live.LiveClient; import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse; import io.github.jwdeveloper.tiktok.messages.webcast.ProtoMessageFetchResult;
import io.github.jwdeveloper.tiktok.utils.JsonUtil; import io.github.jwdeveloper.tiktok.utils.JsonUtil;
import org.bson.Document; import org.bson.Document;
import java.io.PrintWriter; import java.io.*;
import java.io.StringWriter;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Base64; import java.util.Base64;
@@ -77,12 +75,12 @@ public class DataCollectorListener implements LiveDataCollector {
includeError(liveClient, event); includeError(liveClient, event);
} }
private void includeResponse(LiveClient liveClient, WebcastResponse message) { private void includeResponse(LiveClient liveClient, ProtoMessageFetchResult message) {
var messageContent = Base64.getEncoder().encodeToString(message.toByteArray()); var messageContent = Base64.getEncoder().encodeToString(message.toByteArray());
insertDocument(liveClient, createDocument("response", "webcast", messageContent)); insertDocument(liveClient, createDocument("response", "webcast", messageContent));
} }
private void includeMessage(LiveClient liveClient, WebcastResponse.Message message) { private void includeMessage(LiveClient liveClient, ProtoMessageFetchResult.BaseProtoMessage message) {
var method = message.getMethod(); var method = message.getMethod();
var messageContent = Base64.getEncoder().encodeToString(message.getPayload().toByteArray()); var messageContent = Base64.getEncoder().encodeToString(message.getPayload().toByteArray());
insertDocument(liveClient, createDocument("message", method, messageContent)); insertDocument(liveClient, createDocument("message", method, messageContent));

View File

@@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>TikTokLiveJava</artifactId> <artifactId>TikTokLiveJava</artifactId>
<groupId>io.github.jwdeveloper.tiktok</groupId> <groupId>io.github.jwdeveloper.tiktok</groupId>
<version>1.8.13-Release</version> <version>1.11.2-Release</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>extension-recorder</artifactId> <artifactId>extension-recorder</artifactId>

View File

@@ -63,12 +63,14 @@ public class RecorderListener implements LiveRecorder {
liveClient.getLogger().info("Searching for live download url"); liveClient.getLogger().info("Searching for live download url");
downloadData = settings.getPrepareDownloadData() != null ? downloadData = settings.getPrepareDownloadData() != null ?
settings.getPrepareDownloadData().apply(json) : settings.getPrepareDownloadData().apply(json) :
mapToDownloadData(json); mapToDownloadData(json);
if (downloadData.getDownloadLiveUrl().isEmpty()) if (downloadData.getDownloadLiveUrl().isEmpty()) {
liveClient.getLogger().warning("Unable to find download live url!"); liveClient.getLogger().warning("Unable to find download live url!");
else if (settings.isCancelConnectionIfNotFound())
event.setCancelConnection(true, "Unable to find download live url!");
} else
liveClient.getLogger().info("Live download url found!"); liveClient.getLogger().info("Live download url found!");
} }

View File

@@ -40,6 +40,10 @@ public class RecorderSettings {
private File outputFile; private File outputFile;
private Function<String,DownloadData> prepareDownloadData; private Function<String,DownloadData> prepareDownloadData;
private boolean stopOnDisconnect = true; private boolean stopOnDisconnect = true;
/**
True to Cancel connection to live if the download url is not found
*/
private boolean cancelConnectionIfNotFound = false;
public static RecorderSettings DEFAULT() { public static RecorderSettings DEFAULT() {
return new RecorderSettings(); return new RecorderSettings();

View File

@@ -7,7 +7,7 @@
<groupId>io.github.jwdeveloper.tiktok</groupId> <groupId>io.github.jwdeveloper.tiktok</groupId>
<artifactId>TikTokLiveJava</artifactId> <artifactId>TikTokLiveJava</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<version>1.8.13-Release</version> <version>1.11.2-Release</version>
<modules> <modules>
<module>API</module> <module>API</module>
<module>Client</module> <module>Client</module>
@@ -78,7 +78,6 @@
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
@@ -110,7 +109,7 @@
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId> <artifactId>junit-jupiter</artifactId>
<version>RELEASE</version> <version>5.9.3</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>

View File

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