Compare commits

..

2 Commits

Author SHA1 Message Date
kohlerpop1
d777559366 Fix accidental pom.xml comment! 2025-07-19 20:11:29 -04:00
kohlerpop1
567a952c00 Add Paused state for live data due to false positive of returning HostNotFound when they were only paused! 2025-07-19 20:10:29 -04:00
48 changed files with 265 additions and 900 deletions

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.11.8-Release</version> <version>1.10.6-Release</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>API</artifactId> <artifactId>API</artifactId>

View File

@@ -30,22 +30,14 @@ 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(int code, String reason) { public TikTokDisconnectedEvent(String reason) {
this.code = code;
this.reason = reason.isBlank() ? "None" : reason; this.reason = reason.isBlank() ? "None" : reason;
} }
public TikTokDisconnectedEvent(String reason) { public static TikTokDisconnectedEvent of(String reason)
this(UNKNOWN_CLOSE_CODE, reason); {
} return new TikTokDisconnectedEvent(reason);
public boolean isUnknownCloseCode() {
return this.code == UNKNOWN_CLOSE_CODE;
} }
} }

View File

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

@@ -39,16 +39,11 @@ public class TikTokGiftComboEvent extends TikTokGiftEvent {
this.comboState = comboState; this.comboState = comboState;
} }
public TikTokGiftComboEvent(Gift gift, User host, User user, int combo, GiftComboStateType comboState) {
super(gift, user, host, combo);
this.comboState = comboState;
}
public static TikTokGiftComboEvent of(Gift gift, int combo, GiftComboStateType comboState) { public static TikTokGiftComboEvent of(Gift gift, int combo, GiftComboStateType comboState) {
return new TikTokGiftComboEvent(gift, new User(0L, "Test", new Picture("")), WebcastGiftMessage.newBuilder().setComboCount(combo).build(), comboState); return new TikTokGiftComboEvent(
} gift,
new User(0L, "Test", new Picture("")),
public static TikTokGiftComboEvent of(Gift gift, User host, User user, int combo, GiftComboStateType comboState) { WebcastGiftMessage.newBuilder().setComboCount(combo).build(),
return new TikTokGiftComboEvent(gift, host, user, combo, comboState); comboState);
} }
} }

View File

@@ -49,25 +49,17 @@ public class TikTokGiftEvent extends TikTokHeaderEvent {
} }
combo = msg.getComboCount(); combo = msg.getComboCount();
} }
public TikTokGiftEvent(Gift gift, User user, User toUser, int combo) { public TikTokGiftEvent(Gift gift) {
this.gift = gift; this.gift = gift;
this.user = user; user = new User(0L, "sender", new Picture(""));
this.toUser = toUser; toUser = new User(0L, "receiver", new Picture(""));
this.combo = combo; combo = 1;
} }
public static TikTokGiftEvent of(Gift gift) { public static TikTokGiftEvent of(Gift gift) {
return new TikTokGiftEvent( return new TikTokGiftEvent(gift);
gift,
new User(0L, "sender", new Picture("")),
new User(0L, "reviever", new Picture("")),
1
);
}
public static TikTokGiftEvent of(Gift gift, User user, User toUser) {
return new TikTokGiftEvent(gift, user, toUser, 1) ;
} }
public static TikTokGiftEvent of(String name, int id, int diamonds) { public static TikTokGiftEvent of(String name, int id, int diamonds) {

View File

@@ -44,20 +44,10 @@ 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()
.setUsername(userName) .setUsername(userName)
.setNickname(userName) .setNickname(userName)
.build()) .build())
.build()); .build());
}
public static TikTokFollowEvent of(User user) {
return new TikTokFollowEvent(WebcastSocialMessage.newBuilder()
.setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder()
.setUsername(user.getName())
.setNickname(user.getProfileName() != null ? user.getProfileName() : user.getName())
.setId(user.getId())
.build())
.build());
} }
} }

View File

@@ -48,22 +48,13 @@ public class TikTokJoinEvent extends TikTokHeaderEvent {
totalUsers = msg.getMemberCount(); totalUsers = msg.getMemberCount();
} }
public static TikTokJoinEvent of(String userName) { public static TikTokJoinEvent of(String userName)
{
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()
.setUsername(userName) .setUsername(userName)
.setNickname(userName) .setNickname(userName)
.build()) .build())
.build()); .build());
}
public static TikTokJoinEvent of(User user) {
return new TikTokJoinEvent(WebcastMemberMessage.newBuilder()
.setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder()
.setUsername(user.getName())
.setNickname(user.getProfileName())
.setId(user.getId())
.build())
.build());
} }
} }

View File

@@ -56,25 +56,15 @@ public class TikTokLikeEvent extends TikTokHeaderEvent
totalLikes = msg.getTotal(); totalLikes = msg.getTotal();
} }
public static TikTokLikeEvent of(String userName, int likes) { public static TikTokLikeEvent of(String userName, int likes)
{
return new TikTokLikeEvent(WebcastLikeMessage.newBuilder() return new TikTokLikeEvent(WebcastLikeMessage.newBuilder()
.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()
.setUsername(userName) .setUsername(userName)
.setNickname(userName) .setNickname(userName)
.build()) .build())
.build()); .build());
}
public static TikTokLikeEvent of(User user, int likes) {
return new TikTokLikeEvent(WebcastLikeMessage.newBuilder()
.setCount(likes)
.setTotal(likes)
.setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder()
.setUsername(user.getName())
.setNickname(user.getProfileName())
.build())
.build());
} }
} }

View File

@@ -49,19 +49,10 @@ 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()
.setUsername(userName) .setUsername(userName)
.setNickname(userName) .setNickname(userName)
.build()) .build())
.build(), shaders); .build(), shaders);
}
public static TikTokShareEvent of(User user, int shaders) {
return new TikTokShareEvent(WebcastSocialMessage.newBuilder()
.setUser(io.github.jwdeveloper.tiktok.messages.data.User.newBuilder()
.setUsername(user.getName())
.setNickname(user.getProfileName())
.build())
.build(), shaders);
} }
} }

View File

@@ -23,7 +23,6 @@
package io.github.jwdeveloper.tiktok.data.models.battles; package io.github.jwdeveloper.tiktok.data.models.battles;
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.BattleUserInfo;
import io.github.jwdeveloper.tiktok.messages.enums.BattleType; import io.github.jwdeveloper.tiktok.messages.enums.BattleType;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkMicBattle; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastLinkMicBattle;
import lombok.Data; import lombok.Data;
@@ -73,12 +72,12 @@ public class Team {
this.hosts = List.copyOf(hosts); this.hosts = List.copyOf(hosts);
} }
public Team(BattleUserInfo anchorInfo) { public Team(WebcastLinkMicBattle.BattleUserInfo anchorInfo) {
this.hosts = List.of(new User(anchorInfo.getUser())); this.hosts = List.of(new User(anchorInfo.getUser()));
this.teamId = hosts.get(0).getId(); this.teamId = hosts.get(0).getId();
} }
public Team(BattleUserInfo anchorInfo, WebcastLinkMicBattle.BattleComboInfo battleCombo) { public Team(WebcastLinkMicBattle.BattleUserInfo anchorInfo, WebcastLinkMicBattle.BattleComboInfo battleCombo) {
this(anchorInfo); this(anchorInfo);
this.winStreak = (int) battleCombo.getComboCount(); this.winStreak = (int) battleCombo.getComboCount();
} }

View File

@@ -24,7 +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.data.BattleUserArmy;
import io.github.jwdeveloper.tiktok.messages.webcast.*; import io.github.jwdeveloper.tiktok.messages.webcast.*;
import lombok.*; import lombok.*;
@@ -140,7 +140,7 @@ 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(BattleUserInfo.BattleBaseUserInfo host) { public User(WebcastLinkMicBattle.BattleUserInfo.BattleBaseUserInfo host) {
this(host.getUserId(), host.getDisplayId(), host.getNickName(), Picture.map(host.getAvatarThumb())); this(host.getUserId(), host.getDisplayId(), host.getNickName(), Picture.map(host.getAvatarThumb()));
} }

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.live.LiveRoomInfo; import io.github.jwdeveloper.tiktok.data.models.users.User;
import lombok.*; import lombok.*;
public class LiveUserData { public class LiveUserData {
@@ -43,7 +43,9 @@ 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 LiveRoomInfo roomInfo; private final String roomId;
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,16 +89,6 @@ 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.
* <p>This requires {@link #ttTargetIdc} also being set correctly for sessionid to be effective. * <p>This requires {@link #ttTargetIdc} also being set correctly for sessionid to be effective.

View File

@@ -95,9 +95,6 @@ public class ProxyClientSettings implements Iterator<ProxyData>, Iterable<ProxyD
public ProxyClientSettings clone() { public ProxyClientSettings clone() {
ProxyClientSettings settings = new ProxyClientSettings(); ProxyClientSettings settings = new ProxyClientSettings();
settings.setEnabled(enabled); settings.setEnabled(enabled);
settings.setAutoDiscard(autoDiscard);
settings.setFallback(fallback);
settings.setAllowWebsocket(allowWebsocket);
settings.setRotation(rotation); settings.setRotation(rotation);
settings.setIndex(index); settings.setIndex(index);
settings.setType(type); settings.setType(type);

View File

@@ -26,7 +26,6 @@ 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
{ {
@@ -65,6 +64,4 @@ public interface LiveHttpClient
} }
LiveConnectionData.Response fetchLiveConnectionData(LiveConnectionData.Request request); LiveConnectionData.Response fetchLiveConnectionData(LiveConnectionData.Request request);
boolean sendChat(LiveRoomInfo roomInfo, String content, String sessionId, String ttTargetIdc);
} }

View File

@@ -24,7 +24,6 @@ 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;
@@ -37,6 +36,7 @@ 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,18 +48,19 @@ public interface LiveClient {
*/ */
CompletableFuture<LiveClient> connectAsync(); CompletableFuture<LiveClient> connectAsync();
/**
* Disconnects the connection.
* @param type {@code LiveClientStopType}
* @see LiveClientStopType
*/
void disconnect(LiveClientStopType type);
/** /**
* Disconnects with {@link LiveClientStopType#NORMAL} * Disconnects the connection.
* @param type
* <p>0 - Normal - Initiates disconnection and returns
* <p>1 - Disconnects blocking and returns after closure
* <p>2 - Disconnects and kills connection to websocket
* <p>Default {@link #disconnect()} is 0
*/ */
void disconnect(int type);
default void disconnect() { default void disconnect() {
disconnect(LiveClientStopType.NORMAL); disconnect(0);
} }
/** /**
@@ -67,6 +68,7 @@ public interface LiveClient {
*/ */
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
@@ -94,20 +96,4 @@ 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);
/**
* 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, String sessionId, String ttTargetIdc);
} }

View File

@@ -49,7 +49,5 @@ 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

@@ -1,42 +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.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,6 +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(LiveClientStopType type); void stop(int type);
boolean isConnected(); boolean isConnected();
} }

View File

@@ -62,11 +62,12 @@ message Text {
int32 type = 1; int32 type = 1;
TextFormat format = 2; TextFormat format = 2;
string stringValue = 11; string stringValue = 11;
TextPieceUser userValue = 21; oneof textPieceType
TextPieceGift giftValue = 22; {
TextPieceHeart heartValue = 23; TextPieceUser userValue = 21;
TextPieceGift giftValue = 22;
}
TextPiecePatternRef patternRefValue = 24; TextPiecePatternRef patternRefValue = 24;
TextPieceImage imageValue = 25;
} }
message TextFormat { message TextFormat {
@@ -82,7 +83,7 @@ message Text {
message TextPieceGift { message TextPieceGift {
int32 giftId = 1; int32 giftId = 1;
TextPiecePatternRef nameRef = 2; PatternRef nameRef = 2;
ShowType showType = 3; // Enum ShowType showType = 3; // Enum
int64 colorId = 4; int64 colorId = 4;
} }
@@ -97,19 +98,16 @@ message Text {
bool withColon = 2; bool withColon = 2;
} }
message PatternRef {
string key = 1;
string default_pattern = 2;
}
enum ShowType { enum ShowType {
SHOW_TYPE_NORMAL = 0; SHOW_TYPE_NORMAL = 0;
SHOW_TYPE_FADE_IN_OUT = 1; SHOW_TYPE_FADE_IN_OUT = 1;
} }
message TextPieceHeart {
string color = 1;
}
message TextPieceImage {
Image image_model = 1;
}
} }
// @Image // @Image
@@ -153,7 +151,7 @@ message BadgeStruct {
bool is_customized = 24; bool is_customized = 24;
message CombineBadge { message CombineBadge {
BadgeDisplayType badge_display_type = 1; int32 badge_display_type = 1;
Image icon = 2; Image icon = 2;
TextBadge text = 3; TextBadge text = 3;
string str = 4; string str = 4;
@@ -2123,24 +2121,6 @@ message PublicAreaMessageCommon {
} }
} }
message BattleUserInfo {
BattleBaseUserInfo user = 1;
repeated BattleRivalTag tags = 2;
message BattleBaseUserInfo {
int64 user_id = 1;
string nick_name = 2;
Image avatar_thumb = 3;
string display_id = 4;
}
message BattleRivalTag {
Image bg_image = 1;
Image icon_image = 2;
string content = 3;
}
}
message GiftModeMeta { message GiftModeMeta {
int64 gift_id = 1; int64 gift_id = 1;
string gift_name_key = 2; string gift_name_key = 2;

View File

@@ -820,19 +820,4 @@ enum BattleType {
enum BattleInviteType { enum BattleInviteType {
BATTLE_INVITE_TYPE_NORMAL = 0; BATTLE_INVITE_TYPE_NORMAL = 0;
BATTLE_INVITE_TYPE_AGAIN = 1; 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;
} }

View File

@@ -1219,6 +1219,24 @@ message WebcastLinkMicBattle {
// BattleUserInfo user_info = 2; // BattleUserInfo user_info = 2;
// } // }
message BattleUserInfo {
BattleBaseUserInfo user = 1;
repeated BattleRivalTag tags = 2;
message BattleBaseUserInfo {
int64 user_id = 1;
string nick_name = 2;
Image avatar_thumb = 3;
string display_id = 4;
}
message BattleRivalTag {
Image bg_image = 1;
Image icon_image = 2;
string content = 3;
}
}
message BattleABTestSetting { message BattleABTestSetting {
int64 uid = 1; int64 uid = 1;
BattleABTestList ab_test_list = 2; BattleABTestList ab_test_list = 2;
@@ -1453,139 +1471,4 @@ message RoomVerifyMessage {
string content = 3; string content = 3;
int64 noticeType = 4; int64 noticeType = 4;
bool closeRoom = 5; bool closeRoom = 5;
}
message WebcastLinkMicBattleItemCard {
CommonMessageData common = 1;
int64 battle_id = 2;
BattleCardMsgType msg_type = 3;
CardObtainGuide card_obtain_guide = 4;
UseCriticalStrikeCard use_critical_strike_card = 5;
UseSmokeCard use_smoke_card = 6;
AwardCardNotice award_card_notice = 7;
UseExtraTimeCard use_extra_time_card = 8;
UseSpecialEffectCard use_special_effect_card = 9;
UsePotionCard use_potion_card = 10;
UseWaveCard use_wave_card = 11;
SpecialEffectNotice special_effect_notice = 12;
UseTop2Card use_top2_card = 13;
UseTop3Card use_top3_card = 14;
message CardObtainGuide {
int32 not_in_use = 1;
}
message UseCriticalStrikeCard {
CriticalStrikeCardInfo card_info = 1;
int64 anchor_id = 2;
Text display_content = 3;
message CriticalStrikeCardInfo {
string card_name_key = 1;
Image card_image = 2;
int64 send_time_sec = 3;
BattleUserInfo send_user = 4;
int64 effect_last_duration = 5;
int64 critical_strike_rate_low = 6;
int64 critical_strike_rate_high = 7;
int64 multiple = 8;
string gift_name_key = 9;
string rule_url = 10;
int64 effect_time_sec = 11;
int64 to_anchor_id = 12;
string to_anchor_id_str = 13;
}
}
message UseSmokeCard {
CommonCardInfo card_info = 1;
int64 anchor_id = 2;
Text display_content = 3;
}
message AwardCardNotice {
Text display_content = 1;
repeated BattleUserInfo awarded_users = 2;
}
message UseExtraTimeCard {
ExtraTimeCardInfo card_info = 1;
int64 anchor_id = 2;
Text display_content = 3;
message ExtraTimeCardInfo {
string card_name_key = 1;
Image card_image = 2;
int64 send_time_sec = 3;
BattleUserInfo send_user = 4;
int64 effect_last_duration = 5;
string rule_url = 6;
int64 effect_time_sec = 7;
int64 to_anchor_id = 8;
int64 extra_duration_sec = 9;
string to_anchor_id_str = 10;
}
}
message UseSpecialEffectCard {
CommonCardInfo card_info = 1;
int64 anchor_id = 2;
Text display_content = 3;
repeated AnchorPair affected_anchor_pairs = 4;
}
message AnchorPair {
int64 source_anchor_id = 1;
int64 target_anchor_id = 2;
}
message UsePotionCard {
CommonCardInfo card_info = 1;
int64 anchor_id = 2;
Text display_content = 3;
}
message UseWaveCard {
CommonCardInfo card_info = 1;
int64 anchor_id = 2;
Text display_content = 3;
}
message CommonCardInfo {
string card_name_key = 1;
Image card_image = 2;
int64 send_time_sec = 3;
BattleUserInfo send_user = 4;
int64 effect_last_duration = 5;
string rule_url = 6;
int64 effect_time_sec = 7;
int64 to_anchor_id = 8;
string to_anchor_id_str = 9;
}
message SpecialEffectNotice {
int64 score = 1;
int64 from_user_id = 2;
int64 to_anchor_id = 3;
repeated AnchorPair affected_anchor_pairs = 4;
}
message UseTop2Card {
Top2CardInfo card_info = 1;
int64 anchor_id = 2;
Text display_content = 3;
message Top2CardInfo {
CommonCardInfo common = 1;
}
}
message UseTop3Card {
Top3CardInfo card_info = 1;
int64 anchor_id = 2;
Text display_content = 3;
message Top3CardInfo {
CommonCardInfo common = 1;
}
}
} }

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.11.8-Release</version> <version>1.10.6-Release</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

View File

@@ -23,7 +23,6 @@
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.*;
@@ -36,11 +35,11 @@ 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.ProtoMessageFetchResult; 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.*; import io.github.jwdeveloper.tiktok.websocket.LiveSocketClient;
import lombok.Getter; import lombok.Getter;
import java.util.Base64; import java.util.Base64;
import java.util.concurrent.*; import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -80,21 +79,19 @@ public class TikTokLiveClient implements LiveClient
public void connect() { public void connect() {
try { try {
if (clientSettings.isUseEulerstreamWebsocket()) tryConnect();
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()) {
AsyncHandler.getReconnectScheduler().schedule(() -> { try {
logger.info("Reconnecting"); Thread.sleep(clientSettings.getRetryConnectionTimeout().toMillis());
tikTokEventHandler.publish(this, new TikTokReconnectingEvent()); } catch (Exception ignored) {}
this.connect(); logger.info("Reconnecting");
}, clientSettings.getRetryConnectionTimeout().toMillis(), TimeUnit.MILLISECONDS); tikTokEventHandler.publish(this, new TikTokReconnectingEvent());
this.connect();
} }
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
@@ -104,17 +101,6 @@ 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");
@@ -124,6 +110,8 @@ 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(), userData, null); throw new TikTokLiveOfflineHostException("User is offline: " + roomInfo.getHostName(), userData, null);
@@ -131,9 +119,7 @@ public class TikTokLiveClient implements LiveClient
if (userData.getUserStatus() == LiveUserData.UserStatus.NotFound) if (userData.getUserStatus() == LiveUserData.UserStatus.NotFound)
throw new TikTokLiveUnknownHostException("User not found: " + roomInfo.getHostName(), userData, null); throw new TikTokLiveUnknownHostException("User not found: " + roomInfo.getHostName(), userData, null);
roomInfo.copy(userData.getRoomInfo()); var liveDataRequest = new LiveData.Request(userData.getRoomId());
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())
@@ -157,9 +143,9 @@ public class TikTokLiveClient implements LiveClient
throw new TikTokLivePreConnectionException(preconnectEvent); throw new TikTokLivePreConnectionException(preconnectEvent);
if (clientSettings.isFetchGifts()) if (clientSettings.isFetchGifts())
giftManager.attachGiftsList(httpClient.fetchRoomGiftsData(userData.getRoomInfo().getRoomId()).getGifts()); giftManager.attachGiftsList(httpClient.fetchRoomGiftsData(userData.getRoomId()).getGifts());
var liveConnectionRequest = new LiveConnectionData.Request(userData.getRoomInfo().getRoomId()); var liveConnectionRequest = new LiveConnectionData.Request(userData.getRoomId());
var liveConnectionData = httpClient.fetchLiveConnectionData(liveConnectionRequest); var liveConnectionData = httpClient.fetchLiveConnectionData(liveConnectionRequest);
webSocketClient.start(liveConnectionData, this); webSocketClient.start(liveConnectionData, this);
@@ -167,7 +153,7 @@ public class TikTokLiveClient implements LiveClient
tikTokEventHandler.publish(this, new TikTokRoomInfoEvent(roomInfo)); tikTokEventHandler.publish(this, new TikTokRoomInfoEvent(roomInfo));
} }
public void disconnect(LiveClientStopType type) { public void disconnect(int type) {
if (webSocketClient.isConnected()) if (webSocketClient.isConnected())
webSocketClient.stop(type); webSocketClient.stop(type);
if (!roomInfo.hasConnectionState(ConnectionState.DISCONNECTED)) if (!roomInfo.hasConnectionState(ConnectionState.DISCONNECTED))
@@ -197,16 +183,6 @@ public class TikTokLiveClient implements LiveClient
messageHandler.handleSingleMessage(this, message); messageHandler.handleSingleMessage(this, message);
} }
@Override
public boolean sendChat(String content) {
return sendChat(content, clientSettings.getSessionId(), clientSettings.getTtTargetIdc());
}
@Override
public boolean sendChat(String content, String sessionId, String ttTargetIdc) {
return httpClient.sendChat(roomInfo, content, sessionId, ttTargetIdc);
}
public void connectAsync(Consumer<LiveClient> onConnection) { public void connectAsync(Consumer<LiveClient> onConnection) {
connectAsync().thenAccept(onConnection); connectAsync().thenAccept(onConnection);
} }

View File

@@ -42,7 +42,6 @@ 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;
@@ -138,7 +137,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, clientSettings.isUseEulerstreamWebsocket() ? TikTokWebSocketEulerClient.class : TikTokWebSocketClient.class); dependance.registerSingleton(LiveSocketClient.class, TikTokWebSocketClient.class);
dependance.registerSingleton(LiveHttpClient.class, TikTokLiveHttpClient.class); dependance.registerSingleton(LiveHttpClient.class, TikTokLiveHttpClient.class);
} }

View File

@@ -22,7 +22,6 @@
*/ */
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.*;
@@ -31,10 +30,9 @@ 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.live.LiveRoomInfo;
import io.github.jwdeveloper.tiktok.messages.webcast.ProtoMessageFetchResult; import io.github.jwdeveloper.tiktok.messages.webcast.ProtoMessageFetchResult;
import java.net.http.*; import java.net.http.HttpResponse;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -44,17 +42,16 @@ 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/";
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
@@ -62,6 +59,9 @@ 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) {
@@ -91,7 +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);
} }
@Override @Override
@@ -121,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
@@ -138,7 +138,8 @@ public class TikTokLiveHttpClient implements LiveHttpClient
} }
public LiveData.Response getLiveData(LiveData.Request request) { public LiveData.Response getLiveData(LiveData.Request request) {
var result = httpFactory.client(TIKTOK_ROOM_INFO_URL) var url = TIKTOK_URL_WEBCAST + "room/info";
var result = httpFactory.client(url)
.withParam("room_id", request.getRoomId()) .withParam("room_id", request.getRoomId())
.withCookie("sessionid", clientSettings.getSessionId()) .withCookie("sessionid", clientSettings.getSessionId())
.withCookie("tt-target-idc", clientSettings.getTtTargetIdc()) .withCookie("tt-target-idc", clientSettings.getTtTargetIdc())
@@ -149,7 +150,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
@@ -181,33 +182,6 @@ public class TikTokLiveHttpClient implements LiveHttpClient
} }
} }
@Override
public boolean sendChat(LiveRoomInfo roomInfo, String content, String sessionId, String ttTargetIdc) {
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) {
try {
return requestSendChat(roomInfo, content, sessionId, ttTargetIdc);
} catch (TikTokProxyRequestException ignored) {}
}
}
return requestSendChat(roomInfo, content, sessionId, ttTargetIdc);
}
public boolean requestSendChat(LiveRoomInfo roomInfo, String content, String sessionId, String ttTargetIdc) {
JsonObject body = new JsonObject();
body.addProperty("content", content);
body.addProperty("sessionId", sessionId);
body.addProperty("ttTargetIdc", ttTargetIdc);
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()) {
@@ -221,14 +195,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(clientSettings.isUseEulerstreamEnterprise() ? TIKTOK_SIGN_ENTERPRISE_API : TIKTOK_SIGN_API) HttpClientBuilder builder = httpFactory.client(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 if (clientSettings.getSessionId() != null) // Allows receiving of all comments and Subscribe Events
builder.withParam("session_id", clientSettings.getSessionId()); builder.withParam("session_id", clientSettings.getSessionId());
if (clientSettings.getApiKey() != null) if (clientSettings.getApiKey() != null)
builder.withHeader("x-api-key", clientSettings.getApiKey()); builder.withParam("apiKey", clientSettings.getApiKey());
var result = builder.build().toHttpResponse(HttpResponse.BodyHandlers.ofByteArray()); var result = builder.build().toHttpResponse(HttpResponse.BodyHandlers.ofByteArray());

View File

@@ -26,7 +26,6 @@ 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.*; import io.github.jwdeveloper.tiktok.data.requests.*;
import io.github.jwdeveloper.tiktok.http.LiveHttpClient; import io.github.jwdeveloper.tiktok.http.LiveHttpClient;
import io.github.jwdeveloper.tiktok.live.LiveRoomInfo;
import io.github.jwdeveloper.tiktok.messages.webcast.ProtoMessageFetchResult; import io.github.jwdeveloper.tiktok.messages.webcast.ProtoMessageFetchResult;
import java.net.URI; import java.net.URI;
@@ -40,32 +39,26 @@ 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, null); return new LiveUserData.Response("", LiveUserData.UserStatus.Live, "offline_room_id", 0, 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"),
ProtoMessageFetchResult.newBuilder().build()); ProtoMessageFetchResult.newBuilder().build());
}
@Override
public boolean sendChat(LiveRoomInfo roomInfo, String content, String sessionId, String ttTargetIdc) {
// DO NOTHING
return false;
} }
} }

View File

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

@@ -1,22 +0,0 @@
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,12 +40,11 @@ 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 = prepareRequest(); var request = prepareGetRequest();
try { try {
var response = client.send(request, handler); var response = client.send(request, handler);
var result = ActionResult.of(response); var result = ActionResult.of(response);
@@ -69,7 +68,7 @@ public class HttpClient {
} }
} }
protected <T> ActionResult<T> toResponse(HttpResponse.BodyHandler<T> handler) { public <T> ActionResult<T> toResponse(HttpResponse.BodyHandler<T> handler) {
return toHttpResponse(handler).map(HttpResponse::body); return toHttpResponse(handler).map(HttpResponse::body);
} }
@@ -100,13 +99,8 @@ public class HttpClient {
return URI.create(stringUrl); return URI.create(stringUrl);
} }
/** protected HttpRequest prepareGetRequest() {
* @return {@link HttpRequest} with default GET, otherwise POST if {@link #bodyPublisher} is not null var requestBuilder = HttpRequest.newBuilder().GET();
*/
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()) { if (!httpClientSettings.getCookies().isEmpty()) {
@@ -130,10 +124,12 @@ 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,7 +24,6 @@ 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;
@@ -32,7 +31,6 @@ 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;
@@ -80,15 +78,10 @@ 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, bodyPublisher); return new HttpProxyClient(httpClientSettings, url);
return new HttpClient(httpClientSettings, url, bodyPublisher); return new HttpClient(httpClientSettings, url);
} }
} }

View File

@@ -31,30 +31,28 @@ import java.io.IOException;
import java.net.*; import java.net.*;
import java.net.http.*; import java.net.http.*;
import java.net.http.HttpResponse.ResponseInfo; import java.net.http.HttpResponse.ResponseInfo;
import java.nio.ByteBuffer;
import java.security.*; import java.security.*;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.*; import java.util.*;
import java.util.concurrent.Flow;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class HttpProxyClient extends HttpClient { public class HttpProxyClient extends HttpClient {
private final ProxyClientSettings proxySettings; private final ProxyClientSettings proxySettings;
public HttpProxyClient(HttpClientSettings httpClientSettings, String url, HttpRequest.BodyPublisher bodyPublisher) { public HttpProxyClient(HttpClientSettings httpClientSettings, String url) {
super(httpClientSettings, url, bodyPublisher); super(httpClientSettings, url);
this.proxySettings = httpClientSettings.getProxyClientSettings(); this.proxySettings = httpClientSettings.getProxyClientSettings();
} }
public <T> ActionResult<HttpResponse<T>> toHttpResponse(HttpResponse.BodyHandler<T> handler) { public ActionResult<HttpResponse<byte[]>> toResponse() {
return switch (proxySettings.getType()) { return switch (proxySettings.getType()) {
case HTTP, DIRECT -> handleHttpProxyRequest(handler); case HTTP, DIRECT -> handleHttpProxyRequest();
default -> handleSocksProxyRequest(handler); default -> handleSocksProxyRequest();
}; };
} }
public <T> ActionResult<HttpResponse<T>> handleHttpProxyRequest(HttpResponse.BodyHandler<T> handler) { public ActionResult<HttpResponse<byte[]>> handleHttpProxyRequest() {
var builder = java.net.http.HttpClient.newBuilder() var builder = java.net.http.HttpClient.newBuilder()
.followRedirects(java.net.http.HttpClient.Redirect.NORMAL) .followRedirects(java.net.http.HttpClient.Redirect.NORMAL)
.cookieHandler(new CookieManager()) .cookieHandler(new CookieManager())
@@ -67,9 +65,9 @@ public class HttpProxyClient extends HttpClient {
httpClientSettings.getOnClientCreating().accept(builder); httpClientSettings.getOnClientCreating().accept(builder);
var client = builder.build(); var client = builder.build();
var request = prepareRequest(); var request = prepareGetRequest();
var response = client.send(request, handler); var response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
if (response.statusCode() != 200) if (response.statusCode() != 200)
continue; continue;
return ActionResult.success(response); return ActionResult.success(response);
@@ -79,7 +77,7 @@ public class HttpProxyClient extends HttpClient {
throw new TikTokProxyRequestException(e); throw new TikTokProxyRequestException(e);
} catch (IOException e) { } catch (IOException e) {
if (e.getMessage().contains("503") && proxySettings.isFallback()) // Indicates proxy protocol is not supported if (e.getMessage().contains("503") && proxySettings.isFallback()) // Indicates proxy protocol is not supported
return super.toHttpResponse(handler); return super.toHttpResponse(HttpResponse.BodyHandlers.ofByteArray());
throw new TikTokProxyRequestException(e); throw new TikTokProxyRequestException(e);
} catch (Exception e) { } catch (Exception e) {
throw new TikTokLiveRequestException(e); throw new TikTokLiveRequestException(e);
@@ -88,7 +86,7 @@ public class HttpProxyClient extends HttpClient {
throw new TikTokLiveRequestException("No more proxies available!"); throw new TikTokLiveRequestException("No more proxies available!");
} }
private <T> ActionResult<HttpResponse<T>> handleSocksProxyRequest(HttpResponse.BodyHandler<T> handler) { private ActionResult<HttpResponse<byte[]>> handleSocksProxyRequest() {
try { try {
SSLContext sc = SSLContext.getInstance("SSL"); SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[]{ new X509TrustManager() { sc.init(null, new TrustManager[]{ new X509TrustManager() {
@@ -97,8 +95,7 @@ public class HttpProxyClient extends HttpClient {
public X509Certificate[] getAcceptedIssuers() { return null; } public X509Certificate[] getAcceptedIssuers() { return null; }
}}, null); }}, null);
URI uri = toUri(); URL url = toUri().toURL();
URL url = uri.toURL();
if (proxySettings.hasNext()) { if (proxySettings.hasNext()) {
try { try {
@@ -120,22 +117,12 @@ public class HttpProxyClient extends HttpClient {
var responseInfo = createResponseInfo(socksConnection.getResponseCode(), headers); var responseInfo = createResponseInfo(socksConnection.getResponseCode(), headers);
HttpResponse.BodySubscriber<T> subscriber = handler.apply(responseInfo); var response = createHttpResponse(body, toUri(), responseInfo);
subscriber.onSubscribe(new Flow.Subscription() {
@Override public void request(long n) {}
@Override public void cancel() {}
});
subscriber.onNext(List.of(ByteBuffer.wrap(body)));
subscriber.onComplete();
var response = createHttpResponse(subscriber.getBody().toCompletableFuture().join(), uri, responseInfo);
return ActionResult.success(response); return ActionResult.success(response);
} catch (IOException e) { } catch (IOException e) {
if (e.getMessage().contains("503") && proxySettings.isFallback()) // Indicates proxy protocol is not supported if (e.getMessage().contains("503") && proxySettings.isFallback()) // Indicates proxy protocol is not supported
return super.toHttpResponse(handler); return super.toHttpResponse(HttpResponse.BodyHandlers.ofByteArray());
if (proxySettings.isAutoDiscard()) if (proxySettings.isAutoDiscard())
proxySettings.remove(); proxySettings.remove();
throw new TikTokProxyRequestException(e); throw new TikTokProxyRequestException(e);
@@ -173,7 +160,7 @@ public class HttpProxyClient extends HttpClient {
}; };
} }
private <T> HttpResponse<T> createHttpResponse(T body, private HttpResponse<byte[]> createHttpResponse(byte[] body,
URI uri, URI uri,
ResponseInfo info) { ResponseInfo info) {
return new HttpResponse<>() return new HttpResponse<>()
@@ -189,7 +176,7 @@ public class HttpProxyClient extends HttpClient {
} }
@Override @Override
public Optional<HttpResponse<T>> previousResponse() { public Optional<HttpResponse<byte[]>> previousResponse() {
return Optional.empty(); return Optional.empty();
} }
@@ -199,7 +186,7 @@ public class HttpProxyClient extends HttpClient {
} }
@Override @Override
public T body() { public byte[] body() {
return body; return body;
} }

View File

@@ -32,7 +32,7 @@ import java.util.List;
public class GiftsDataMapper { public class GiftsDataMapper {
public static GiftsData.Response map(String json) { public 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 static Gift mapSingleGift(JsonElement jsonElement) { private 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,7 +53,7 @@ public class GiftsDataMapper {
return new Gift(id, name, diamondCost, new Picture(image), jsonObject); return new Gift(id, name, diamondCost, new Picture(image), jsonObject);
} }
public static GiftsData.Response mapRoom(String json) { public 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) {
@@ -69,7 +69,7 @@ public class GiftsDataMapper {
return new GiftsData.Response("", List.of()); return new GiftsData.Response("", List.of());
} }
private static Gift mapSingleRoomGift(JsonElement jsonElement) { private 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 static LiveData.Response map(String json) { public LiveData.Response map(String json) {
var response = new LiveData.Response(); var response = new LiveData.Response();
response.setJson(json); response.setJson(json);
@@ -128,7 +128,7 @@ public class LiveDataMapper {
return response; return response;
} }
public static User getUser(JsonObject jsonElement) { public 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,18 +23,17 @@
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;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException; import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import java.util.*; import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
public class LiveUserDataMapper public class LiveUserDataMapper
{ {
public static LiveUserData.Response map(String json, Logger logger) { public LiveUserData.Response map(String json, Logger logger) {
try { try {
var jsonObject = JsonParser.parseString(json).getAsJsonObject(); var jsonObject = JsonParser.parseString(json).getAsJsonObject();
@@ -44,14 +43,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, null); return new LiveUserData.Response(json, LiveUserData.UserStatus.NotFound, "", -1, 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, null); return new LiveUserData.Response(json, LiveUserData.UserStatus.NotFound, "", -1, null);
} }
var data = element.getAsJsonObject(); var data = element.getAsJsonObject();
var user = data.getAsJsonObject("user"); var user = data.getAsJsonObject("user");
@@ -59,16 +58,8 @@ 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.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;
@@ -87,55 +78,10 @@ public class LiveUserDataMapper
stats.get("followerCount").getAsLong(), stats.get("followerCount").getAsLong(),
List.of()); List.of());
roomInfo.setHost(foundUser); return new LiveUserData.Response(json, statusEnum, roomId, startTime, foundUser);
roomInfo.setHostName(foundUser.getName()); } catch (JsonSyntaxException | IllegalStateException e) {
return new LiveUserData.Response(json, statusEnum, roomInfo);
} catch (JsonSyntaxException | IllegalStateException | NullPointerException 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, null); return new LiveUserData.Response(json, LiveUserData.UserStatus.NotFound, "", -1, null);
}
}
public static LiveUserData.Response mapEulerstream(JsonObject jsonObject, Logger logger) {
try {
JsonObject roomInfoJson = jsonObject.getAsJsonObject("roomInfo");
JsonObject userJson = jsonObject.getAsJsonObject("user");
var roomId = roomInfoJson.get("id").getAsString();
var status = roomInfoJson.get("status").getAsInt();
TikTokRoomInfo roomInfo = new TikTokRoomInfo();
roomInfo.setRoomId(roomId);
roomInfo.setTitle(roomInfoJson.get("title").getAsString());
roomInfo.setStartTime(roomInfoJson.get("startTime").getAsLong());
roomInfo.setViewersCount(Optional.ofNullable(roomInfoJson.get("currentViewers")).filter(JsonElement::isJsonPrimitive).map(JsonElement::getAsInt).orElse(0));
roomInfo.setTotalViewersCount(roomInfoJson.get("totalViewers").getAsInt());
var statusEnum = switch (status) {
case 2 -> LiveUserData.UserStatus.Live;
case 3 -> LiveUserData.UserStatus.LivePaused;
case 4 -> LiveUserData.UserStatus.Offline;
default -> LiveUserData.UserStatus.NotFound;
};
User foundUser = new User(
Long.parseLong(userJson.get("numericUid").getAsString()),
userJson.get("uniqueId").getAsString(),
userJson.get("nickname").getAsString(),
userJson.get("signature").getAsString(),
new Picture(userJson.get("avatarUrl").getAsString()),
userJson.get("following").getAsLong(),
userJson.get("followers").getAsLong(),
List.of());
roomInfo.setHost(foundUser);
roomInfo.setHostName(foundUser.getName());
return new LiveUserData.Response(jsonObject.toString(), statusEnum, roomInfo);
} catch (JsonSyntaxException | IllegalStateException | NullPointerException e) {
logger.warning("Malformed Json: '"+jsonObject.toString()+"' - Error Message: "+e.getMessage());
return new LiveUserData.Response(jsonObject.toString(), 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,7 +116,8 @@ 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 -> new TikTokLinkWaitListChangeEvent(message); case TYPE_LINKER_WAITING_LIST_CHANGE, TYPE_LINKER_WAITING_LIST_CHANGE_V2 ->
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);
@@ -129,11 +130,7 @@ 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);
@@ -152,4 +149,4 @@ public class MessagesMapperFactory {
// mapper.bytesToEvents(WebcastEnvelopeMessage.class, commonHandler::handleEnvelop); // mapper.bytesToEvents(WebcastEnvelopeMessage.class, commonHandler::handleEnvelop);
return mapper; return mapper;
} }
} }

View File

@@ -57,7 +57,7 @@ public class TikTokWebSocketClient implements LiveSocketClient {
@Override @Override
public void start(LiveConnectionData.Response connectionData, LiveClient liveClient) { public void start(LiveConnectionData.Response connectionData, LiveClient liveClient) {
if (isConnected()) if (isConnected())
stop(LiveClientStopType.NORMAL); stop(0);
messageHandler.handle(liveClient, connectionData.getWebcastResponse()); messageHandler.handle(liveClient, connectionData.getWebcastResponse());
@@ -77,7 +77,7 @@ public class TikTokWebSocketClient implements LiveSocketClient {
connectDefault(); connectDefault();
} }
public void connectDefault() { private void connectDefault() {
try { try {
webSocketClient.connect(); webSocketClient.connect();
heartbeatTask.run(webSocketClient, clientSettings.getPingInterval()); heartbeatTask.run(webSocketClient, clientSettings.getPingInterval());
@@ -129,17 +129,17 @@ public class TikTokWebSocketClient implements LiveSocketClient {
} }
} }
public void stop(LiveClientStopType type) { public void stop(int type) {
if (isConnected()) { if (isConnected()) {
switch (type) { switch (type) {
case CLOSE_BLOCKING -> { case 1 -> {
try { try {
webSocketClient.closeBlocking(); webSocketClient.closeBlocking();
} catch (InterruptedException e) { } catch (InterruptedException e) {
throw new TikTokLiveException("Failed to stop the websocket"); throw new TikTokLiveException("Failed to stop the websocket");
} }
} }
case DISCONNECT -> webSocketClient.closeConnection(CloseFrame.NORMAL, ""); case 2 -> webSocketClient.closeConnection(CloseFrame.NORMAL, "");
default -> webSocketClient.close(); default -> webSocketClient.close();
} }
heartbeatTask.stop(); heartbeatTask.stop();

View File

@@ -39,9 +39,9 @@ import java.util.*;
public class TikTokWebSocketListener extends WebSocketClient { public class TikTokWebSocketListener extends WebSocketClient {
protected final LiveMessagesHandler messagesHandler; private final LiveMessagesHandler messagesHandler;
protected final LiveEventsHandler eventHandler; private final LiveEventsHandler eventHandler;
protected final LiveClient liveClient; private 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 {
} }
} }
protected void handleBinary(byte[] buffer) { private 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(code, reason)); eventHandler.publish(liveClient, new TikTokDisconnectedEvent(reason));
liveClient.disconnect(); liveClient.disconnect();
} }
@@ -111,8 +111,12 @@ public class TikTokWebSocketListener extends WebSocketClient {
private Optional<WebcastPushFrame> getWebcastPushFrame(byte[] buffer) { private Optional<WebcastPushFrame> getWebcastPushFrame(byte[] buffer) {
try { try {
return Optional.of(WebcastPushFrame.parseFrom(buffer)).filter(msg -> !msg.getPayload().isEmpty()); var websocketMessage = WebcastPushFrame.parseFrom(buffer);
} catch (Exception e) { if (websocketMessage.getPayload().isEmpty()) {
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);
} }
} }

View File

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

View File

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

View File

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

@@ -1,77 +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.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.mapEulerstream(oMsg.getAsJsonObject("data"), liveClient.getLogger());
liveClient.getRoomInfo().copy(data.getRoomInfo());
eventHandler.publish(liveClient, new TikTokRoomInfoEvent(liveClient.getRoomInfo()));
}
}
}
}
}
} else
throw new IllegalArgumentException("Invalid JsonObject: "+element);
} catch (Exception e) {
e.printStackTrace();
eventHandler.publish(liveClient, new TikTokErrorEvent(e));
}
if (isOpen()) {
sendPing();
}
}
}

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.11.0-Release</version> <version>1.10.0-Release</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
</dependencies> </dependencies>

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.11.8-Release</version> <version>1.10.6-Release</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

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.11.8-Release</version> <version>1.10.6-Release</version>
</parent> </parent>

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.11.8-Release</version> <version>1.10.6-Release</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>extension-recorder</artifactId> <artifactId>extension-recorder</artifactId>

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.11.8-Release</version> <version>1.10.6-Release</version>
<modules> <modules>
<module>API</module> <module>API</module>
<module>Client</module> <module>Client</module>
@@ -109,7 +109,7 @@
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId> <artifactId>junit-jupiter</artifactId>
<version>5.9.3</version> <version>RELEASE</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.11.8-Release</version> <version>1.10.6-Release</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>