mirror of
https://github.com/jwdeveloper/TikTokLiveJava.git
synced 2026-02-27 08:49:40 -05:00
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!
This commit is contained in:
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
package io.github.jwdeveloper.tiktok.data.models.battles;
|
||||
|
||||
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;
|
||||
@@ -72,12 +73,12 @@ public class Team {
|
||||
this.hosts = List.copyOf(hosts);
|
||||
}
|
||||
|
||||
public Team(WebcastLinkMicBattle.BattleUserInfo anchorInfo) {
|
||||
public Team(BattleUserInfo anchorInfo) {
|
||||
this.hosts = List.of(new User(anchorInfo.getUser()));
|
||||
this.teamId = hosts.get(0).getId();
|
||||
}
|
||||
|
||||
public Team(WebcastLinkMicBattle.BattleUserInfo anchorInfo, WebcastLinkMicBattle.BattleComboInfo battleCombo) {
|
||||
public Team(BattleUserInfo anchorInfo, WebcastLinkMicBattle.BattleComboInfo battleCombo) {
|
||||
this(anchorInfo);
|
||||
this.winStreak = (int) battleCombo.getComboCount();
|
||||
}
|
||||
|
||||
@@ -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.badges.Badge;
|
||||
import io.github.jwdeveloper.tiktok.messages.data.BattleUserArmy;
|
||||
import io.github.jwdeveloper.tiktok.messages.data.*;
|
||||
import io.github.jwdeveloper.tiktok.messages.webcast.*;
|
||||
import lombok.*;
|
||||
|
||||
@@ -140,7 +140,7 @@ public class User {
|
||||
this(id, name, profileId, null, picture, 0, 0, List.of(Badge.empty()));
|
||||
}
|
||||
|
||||
public User(WebcastLinkMicBattle.BattleUserInfo.BattleBaseUserInfo host) {
|
||||
public User(BattleUserInfo.BattleBaseUserInfo host) {
|
||||
this(host.getUserId(), host.getDisplayId(), host.getNickName(), Picture.map(host.getAvatarThumb()));
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import io.github.jwdeveloper.tiktok.data.requests.GiftsData;
|
||||
import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData;
|
||||
import io.github.jwdeveloper.tiktok.data.requests.LiveData;
|
||||
import io.github.jwdeveloper.tiktok.data.requests.LiveUserData;
|
||||
import io.github.jwdeveloper.tiktok.live.LiveRoomInfo;
|
||||
|
||||
public interface LiveHttpClient
|
||||
{
|
||||
@@ -64,4 +65,6 @@ public interface LiveHttpClient
|
||||
}
|
||||
|
||||
LiveConnectionData.Response fetchLiveConnectionData(LiveConnectionData.Request request);
|
||||
|
||||
boolean sendChat(LiveRoomInfo roomInfo, String content);
|
||||
}
|
||||
@@ -36,7 +36,6 @@ public interface LiveClient {
|
||||
*/
|
||||
void connect();
|
||||
|
||||
|
||||
/**
|
||||
* Connects in asynchronous way
|
||||
* When connected Consumer returns instance of LiveClient
|
||||
@@ -48,7 +47,6 @@ public interface LiveClient {
|
||||
*/
|
||||
CompletableFuture<LiveClient> connectAsync();
|
||||
|
||||
|
||||
/**
|
||||
* Disconnects the connection.
|
||||
* @param type
|
||||
@@ -68,7 +66,6 @@ public interface LiveClient {
|
||||
*/
|
||||
void publishEvent(TikTokEvent event);
|
||||
|
||||
|
||||
/**
|
||||
* @param webcastMessageName name of TikTok protocol-buffer message
|
||||
* @param payloadBase64 protocol-buffer message bytes payload
|
||||
@@ -96,4 +93,12 @@ public interface LiveClient {
|
||||
* Logger
|
||||
*/
|
||||
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);
|
||||
}
|
||||
@@ -2121,6 +2121,24 @@ 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 {
|
||||
int64 gift_id = 1;
|
||||
string gift_name_key = 2;
|
||||
|
||||
@@ -821,3 +821,18 @@ 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;
|
||||
}
|
||||
@@ -1219,24 +1219,6 @@ message WebcastLinkMicBattle {
|
||||
// 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 {
|
||||
int64 uid = 1;
|
||||
BattleABTestList ab_test_list = 2;
|
||||
@@ -1472,3 +1454,138 @@ message RoomVerifyMessage {
|
||||
int64 noticeType = 4;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,6 +183,11 @@ public class TikTokLiveClient implements LiveClient
|
||||
messageHandler.handleSingleMessage(this, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendChat(String content) {
|
||||
return httpClient.sendChat(roomInfo, content);
|
||||
}
|
||||
|
||||
public void connectAsync(Consumer<LiveClient> onConnection) {
|
||||
connectAsync().thenAccept(onConnection);
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
|
||||
|
||||
//networking
|
||||
dependance.registerSingleton(HttpClientFactory.class);
|
||||
dependance.registerSingleton(WebSocketHeartbeatTask.class);
|
||||
dependance.registerSingleton(WebSocketHeartbeatTask.class); // True global singleton - Static objects are located to serve as global
|
||||
if (clientSettings.isOffline()) {
|
||||
dependance.registerSingleton(LiveSocketClient.class, TikTokWebSocketOfflineClient.class);
|
||||
dependance.registerSingleton(LiveHttpClient.class, TikTokLiveHttpOfflineClient.class);
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
*/
|
||||
package io.github.jwdeveloper.tiktok;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import io.github.jwdeveloper.dependance.injector.api.annotations.Inject;
|
||||
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.http.*;
|
||||
import io.github.jwdeveloper.tiktok.http.mappers.*;
|
||||
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.logging.Logger;
|
||||
|
||||
@@ -42,6 +44,7 @@ public class TikTokLiveHttpClient implements LiveHttpClient
|
||||
* <a href="https://github-wiki-see.page/m/isaackogan/TikTokLive/wiki/All-About-Signatures">Signing API by Isaac Kogan</a>
|
||||
*/
|
||||
private static final String TIKTOK_SIGN_API = "https://tiktok.eulerstream.com/webcast/fetch";
|
||||
private static final String TIKTOK_CHAT_URL = "https://tiktok.eulerstream.com/webcast/chat";
|
||||
private static final String TIKTOK_URL_WEB = "https://www.tiktok.com/";
|
||||
private static final String TIKTOK_URL_WEBCAST = "https://webcast.tiktok.com/webcast/";
|
||||
public static final String TIKTOK_ROOM_GIFTS_URL = TIKTOK_URL_WEBCAST+"gift/list/";
|
||||
@@ -182,6 +185,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());
|
||||
var result = httpFactory.client(TIKTOK_CHAT_URL)
|
||||
.withHeader("Content-Type", "application/json")
|
||||
.withBody(HttpRequest.BodyPublishers.ofString(body.toString()))
|
||||
.build()
|
||||
.toJsonResponse();
|
||||
return result.isSuccess();
|
||||
}
|
||||
|
||||
protected ActionResult<HttpResponse<byte[]>> getStartingPayload(LiveConnectionData.Request request) {
|
||||
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
|
||||
if (proxyClientSettings.isEnabled()) {
|
||||
|
||||
@@ -26,6 +26,7 @@ import io.github.jwdeveloper.tiktok.data.models.Picture;
|
||||
import io.github.jwdeveloper.tiktok.data.models.users.User;
|
||||
import io.github.jwdeveloper.tiktok.data.requests.*;
|
||||
import io.github.jwdeveloper.tiktok.http.LiveHttpClient;
|
||||
import io.github.jwdeveloper.tiktok.live.LiveRoomInfo;
|
||||
import io.github.jwdeveloper.tiktok.messages.webcast.ProtoMessageFetchResult;
|
||||
|
||||
import java.net.URI;
|
||||
@@ -45,20 +46,26 @@ public class TikTokLiveHttpOfflineClient implements LiveHttpClient {
|
||||
@Override
|
||||
public LiveData.Response fetchLiveData(LiveData.Request request) {
|
||||
return new LiveData.Response("",
|
||||
LiveData.LiveStatus.HostOnline,
|
||||
"offline live",
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
new User(0L, "offline user", new Picture("")),
|
||||
LiveData.LiveType.SOLO);
|
||||
LiveData.LiveStatus.HostOnline,
|
||||
"offline live",
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
false,
|
||||
new User(0L, "offline user", new Picture("")),
|
||||
LiveData.LiveType.SOLO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiveConnectionData.Response fetchLiveConnectionData(LiveConnectionData.Request request) {
|
||||
return new LiveConnectionData.Response("",
|
||||
URI.create("https://example.live"),
|
||||
ProtoMessageFetchResult.newBuilder().build());
|
||||
URI.create("https://example.live"),
|
||||
ProtoMessageFetchResult.newBuilder().build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendChat(LiveRoomInfo roomInfo, String content) {
|
||||
// DO NOTHING
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -40,11 +40,12 @@ public class HttpClient {
|
||||
|
||||
protected final HttpClientSettings httpClientSettings;
|
||||
protected final String url;
|
||||
protected final HttpRequest.BodyPublisher bodyPublisher;
|
||||
private final Pattern pattern = Pattern.compile("charset=(.*?)(?=&|$)");
|
||||
|
||||
public <T> ActionResult<HttpResponse<T>> toHttpResponse(HttpResponse.BodyHandler<T> handler) {
|
||||
var client = prepareClient();
|
||||
var request = prepareGetRequest();
|
||||
var request = prepareRequest();
|
||||
try {
|
||||
var response = client.send(request, handler);
|
||||
var result = ActionResult.of(response);
|
||||
@@ -99,8 +100,13 @@ public class HttpClient {
|
||||
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.timeout(httpClientSettings.getTimeout());
|
||||
if (!httpClientSettings.getCookies().isEmpty()) {
|
||||
@@ -124,12 +130,10 @@ public class HttpClient {
|
||||
}
|
||||
|
||||
protected String prepareUrlWithParameters(String url, Map<String, Object> parameters) {
|
||||
if (parameters.isEmpty()) {
|
||||
return url;
|
||||
}
|
||||
if (parameters.isEmpty())
|
||||
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 encodedValue = URLEncoder.encode(entry.getValue().toString(), StandardCharsets.UTF_8);
|
||||
return encodedKey + "=" + encodedValue;
|
||||
|
||||
@@ -24,6 +24,7 @@ package io.github.jwdeveloper.tiktok.http;
|
||||
|
||||
import io.github.jwdeveloper.tiktok.data.settings.HttpClientSettings;
|
||||
|
||||
import java.net.http.HttpRequest;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -31,6 +32,7 @@ public class HttpClientBuilder {
|
||||
|
||||
private final HttpClientSettings httpClientSettings;
|
||||
private String url;
|
||||
private HttpRequest.BodyPublisher bodyPublisher;
|
||||
|
||||
public HttpClientBuilder(String url, HttpClientSettings httpClientSettings) {
|
||||
this.httpClientSettings = httpClientSettings;
|
||||
@@ -78,10 +80,15 @@ public class HttpClientBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpClientBuilder withBody(HttpRequest.BodyPublisher bodyPublisher) {
|
||||
this.bodyPublisher = bodyPublisher;
|
||||
return this;
|
||||
}
|
||||
|
||||
public HttpClient build() {
|
||||
var proxyClientSettings = httpClientSettings.getProxyClientSettings();
|
||||
if (proxyClientSettings.isEnabled() && proxyClientSettings.hasNext())
|
||||
return new HttpProxyClient(httpClientSettings, url);
|
||||
return new HttpClient(httpClientSettings, url);
|
||||
return new HttpProxyClient(httpClientSettings, url, bodyPublisher);
|
||||
return new HttpClient(httpClientSettings, url, bodyPublisher);
|
||||
}
|
||||
}
|
||||
@@ -40,8 +40,8 @@ public class HttpProxyClient extends HttpClient {
|
||||
|
||||
private final ProxyClientSettings proxySettings;
|
||||
|
||||
public HttpProxyClient(HttpClientSettings httpClientSettings, String url) {
|
||||
super(httpClientSettings, url);
|
||||
public HttpProxyClient(HttpClientSettings httpClientSettings, String url, HttpRequest.BodyPublisher bodyPublisher) {
|
||||
super(httpClientSettings, url, bodyPublisher);
|
||||
this.proxySettings = httpClientSettings.getProxyClientSettings();
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public class HttpProxyClient extends HttpClient {
|
||||
|
||||
httpClientSettings.getOnClientCreating().accept(builder);
|
||||
var client = builder.build();
|
||||
var request = prepareGetRequest();
|
||||
var request = prepareRequest();
|
||||
|
||||
var response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
|
||||
if (response.statusCode() != 200)
|
||||
|
||||
@@ -104,7 +104,7 @@ public class MessagesMapperFactory {
|
||||
var message = mapperHelper.bytesToWebcastObject(inputBytes, WebcastLinkMicArmies.class);
|
||||
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);
|
||||
return MappingResult.of(message, switch (message.getMessageType()) {
|
||||
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_LINKED_LIST_CHANGE -> new TikTokLinkLinkedListChangeEvent(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_MATCH -> new TikTokLinkRandomMatchEvent(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);
|
||||
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(WebcastLinkMicFanTicketMethod.class, TikTokLinkMicFanTicketEvent.class);
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@ public class TikTokWebSocketClient implements LiveSocketClient {
|
||||
case 2 -> webSocketClient.closeConnection(CloseFrame.NORMAL, "");
|
||||
default -> webSocketClient.close();
|
||||
}
|
||||
heartbeatTask.stop();
|
||||
heartbeatTask.stop(webSocketClient);
|
||||
}
|
||||
webSocketClient = null;
|
||||
}
|
||||
|
||||
@@ -24,41 +24,52 @@ package io.github.jwdeveloper.tiktok.websocket;
|
||||
|
||||
import org.java_websocket.WebSocket;
|
||||
|
||||
import java.util.Base64;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class WebSocketHeartbeatTask
|
||||
{
|
||||
private Thread thread;
|
||||
private boolean isRunning = false;
|
||||
private final int MAX_TIMEOUT = 250;
|
||||
private final int SLEEP_TIME = 500;
|
||||
// Single shared pool for all heartbeat tasks
|
||||
private static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, r -> {
|
||||
Thread t = new Thread(r, "heartbeat-pool");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
private static final Map<WebSocket, ScheduledFuture<?>> tasks = new ConcurrentHashMap<>();
|
||||
private static final Map<WebSocket, Long> commTime = new ConcurrentHashMap<>();
|
||||
|
||||
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) {
|
||||
stop();
|
||||
thread = new Thread(() -> heartbeatTask(webSocket, pingTaskTime), "heartbeat-task");
|
||||
isRunning = true;
|
||||
thread.start();
|
||||
}
|
||||
stop(webSocket); // remove existing task if any
|
||||
|
||||
public void stop() {
|
||||
if (thread != null)
|
||||
thread.interrupt();
|
||||
isRunning = false;
|
||||
}
|
||||
|
||||
private void heartbeatTask(WebSocket webSocket, long pingTaskTime) {
|
||||
while (isRunning) {
|
||||
tasks.put(webSocket, scheduler.scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
if (webSocket.isOpen()) {
|
||||
webSocket.send(heartbeatBytes);
|
||||
Thread.sleep(pingTaskTime + (int) (Math.random() * MAX_TIMEOUT));
|
||||
} else
|
||||
Thread.sleep(SLEEP_TIME);
|
||||
commTime.put(webSocket, System.currentTimeMillis());
|
||||
} else {
|
||||
Long time = commTime.get(webSocket);
|
||||
if (time != null && System.currentTimeMillis() - time >= 60_000) // Stop if disconnected longer than 60s
|
||||
stop(webSocket);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
//TODO we should display some kind of error message
|
||||
isRunning = false;
|
||||
e.printStackTrace();
|
||||
stop(webSocket);
|
||||
}
|
||||
}
|
||||
}, 0, pingTaskTime, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
public void stop(WebSocket webSocket) {
|
||||
ScheduledFuture<?> future = tasks.remove(webSocket);
|
||||
if (future != null)
|
||||
future.cancel(true);
|
||||
commTime.remove(webSocket);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
tasks.values().forEach(f -> f.cancel(true));
|
||||
commTime.clear();
|
||||
scheduler.shutdownNow();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user