diff --git a/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/control/TikTokPreConnectionEvent.java b/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/control/TikTokPreConnectionEvent.java new file mode 100644 index 00000000..5fd6b004 --- /dev/null +++ b/API/src/main/java/io/github/jwdeveloper/tiktok/data/events/control/TikTokPreConnectionEvent.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023-2023 jwdeveloper jacekwoln@gmail.com + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package io.github.jwdeveloper.tiktok.data.events.control; + +import io.github.jwdeveloper.tiktok.annotations.*; +import io.github.jwdeveloper.tiktok.data.events.common.TikTokLiveClientEvent; +import io.github.jwdeveloper.tiktok.data.requests.*; +import lombok.*; + +/** + * Triggered before the connection is established. + */ +@EventMeta(eventType = EventType.Control) +public class TikTokPreConnectionEvent extends TikTokLiveClientEvent +{ + @Getter private final LiveUserData.Response userData; + @Getter private final LiveData.Response roomData; + @Getter @Setter boolean cancelConnection = false; + + public TikTokPreConnectionEvent(LiveUserData.Response userData, LiveData.Response liveData) { + this.userData = userData; + this.roomData = liveData; + } +} \ No newline at end of file diff --git a/API/src/main/java/io/github/jwdeveloper/tiktok/data/requests/LiveData.java b/API/src/main/java/io/github/jwdeveloper/tiktok/data/requests/LiveData.java index 43260108..9e72b94f 100644 --- a/API/src/main/java/io/github/jwdeveloper/tiktok/data/requests/LiveData.java +++ b/API/src/main/java/io/github/jwdeveloper/tiktok/data/requests/LiveData.java @@ -44,6 +44,7 @@ public static class Response { private int totalViewers; private boolean ageRestricted; private User host; + private LiveType liveType; } public enum LiveStatus { @@ -51,4 +52,11 @@ public enum LiveStatus { HostOnline, HostOffline, } -} + + public enum LiveType { + SOLO, + BOX, + BATTLE, + CO_HOST + } +} \ No newline at end of file diff --git a/API/src/main/java/io/github/jwdeveloper/tiktok/data/settings/ProxyClientSettings.java b/API/src/main/java/io/github/jwdeveloper/tiktok/data/settings/ProxyClientSettings.java index 48061efa..e6ca0f82 100644 --- a/API/src/main/java/io/github/jwdeveloper/tiktok/data/settings/ProxyClientSettings.java +++ b/API/src/main/java/io/github/jwdeveloper/tiktok/data/settings/ProxyClientSettings.java @@ -33,11 +33,10 @@ @Setter public class ProxyClientSettings implements Iterator { - private boolean enabled, lastSuccess; + private boolean enabled, lastSuccess, autoDiscard = true, fallback = true; private Rotation rotation = Rotation.CONSECUTIVE; private final List proxyList = new ArrayList<>(); - private int index = 0; - private boolean autoDiscard = true; + private int index = -1; private Proxy.Type type = Proxy.Type.DIRECT; private Consumer onProxyUpdated = x -> {}; @@ -78,7 +77,10 @@ public ProxyData rotate() { index = new Random().nextInt(proxyList.size()); yield proxyList.get(index).clone(); } - case NONE -> proxyList.get(index).clone(); + case NONE -> { + index = Math.max(index, 0); + yield proxyList.get(index).clone(); + } }; onProxyUpdated.accept(nextProxy); return nextProxy; @@ -99,6 +101,7 @@ public void setIndex(int index) { this.index = index; } } + public ProxyClientSettings clone() { ProxyClientSettings settings = new ProxyClientSettings(); diff --git a/API/src/main/java/io/github/jwdeveloper/tiktok/exceptions/TikTokLiveRequestException.java b/API/src/main/java/io/github/jwdeveloper/tiktok/exceptions/TikTokLiveRequestException.java index 55848cfc..b004a862 100644 --- a/API/src/main/java/io/github/jwdeveloper/tiktok/exceptions/TikTokLiveRequestException.java +++ b/API/src/main/java/io/github/jwdeveloper/tiktok/exceptions/TikTokLiveRequestException.java @@ -23,7 +23,7 @@ package io.github.jwdeveloper.tiktok.exceptions; -/* +/** * Happens while bad response from Http request to TikTok */ public class TikTokLiveRequestException extends TikTokLiveException @@ -46,4 +46,4 @@ public TikTokLiveRequestException(Throwable cause) { public TikTokLiveRequestException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); } -} +} \ No newline at end of file diff --git a/API/src/main/java/io/github/jwdeveloper/tiktok/live/builder/EventsBuilder.java b/API/src/main/java/io/github/jwdeveloper/tiktok/live/builder/EventsBuilder.java index 494030f0..5083c57c 100644 --- a/API/src/main/java/io/github/jwdeveloper/tiktok/live/builder/EventsBuilder.java +++ b/API/src/main/java/io/github/jwdeveloper/tiktok/live/builder/EventsBuilder.java @@ -24,6 +24,7 @@ import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; import io.github.jwdeveloper.tiktok.data.events.*; +import io.github.jwdeveloper.tiktok.data.events.control.TikTokPreConnectionEvent; import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftComboEvent; import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent; import io.github.jwdeveloper.tiktok.data.events.http.TikTokHttpResponseEvent; @@ -149,6 +150,13 @@ public interface EventsBuilder { */ T onConnected(EventConsumer action); + /** + * Invoked before client has been successfully connected to live + * @param action + * @return + */ + T onPreConnection(EventConsumer action); + /** * Invoked when client tries to reconnect * @param action @@ -215,6 +223,4 @@ public interface EventsBuilder { //T onLinkMicBattle(TikTokEventConsumer event); //T onUnhandledControl(TikTokEventConsumer event); -} - - +} \ No newline at end of file diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClient.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClient.java index f0741449..a7ec44c6 100644 --- a/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClient.java +++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClient.java @@ -26,7 +26,7 @@ import io.github.jwdeveloper.tiktok.data.events.TikTokErrorEvent; import io.github.jwdeveloper.tiktok.data.events.TikTokReconnectingEvent; import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; -import io.github.jwdeveloper.tiktok.data.events.control.TikTokConnectingEvent; +import io.github.jwdeveloper.tiktok.data.events.control.*; import io.github.jwdeveloper.tiktok.data.events.http.TikTokRoomDataResponseEvent; import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomInfoEvent; import io.github.jwdeveloper.tiktok.data.requests.LiveConnectionData; @@ -150,6 +150,11 @@ public void tryConnect() { liveRoomInfo.setAgeRestricted(liveData.isAgeRestricted()); liveRoomInfo.setHost(liveData.getHost()); + var preconnectEvent = new TikTokPreConnectionEvent(userData, liveData); + tikTokEventHandler.publish(this, preconnectEvent); + if (preconnectEvent.isCancelConnection()) + throw new TikTokLiveException("TikTokPreConnectionEvent cancelled connection!"); + var liveConnectionRequest =new LiveConnectionData.Request(userData.getRoomId()); var liveConnectionData = httpClient.fetchLiveConnectionData(liveConnectionRequest); webSocketClient.start(liveConnectionData, this); diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClientBuilder.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClientBuilder.java index b431ab58..c8e207be 100644 --- a/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClientBuilder.java +++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClientBuilder.java @@ -24,6 +24,7 @@ import io.github.jwdeveloper.tiktok.data.events.*; import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent; +import io.github.jwdeveloper.tiktok.data.events.control.TikTokPreConnectionEvent; import io.github.jwdeveloper.tiktok.data.events.envelop.TikTokChestEvent; import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftComboEvent; import io.github.jwdeveloper.tiktok.data.events.gift.TikTokGiftEvent; @@ -275,9 +276,7 @@ public CompletableFuture buildAndConnectAsync() { return build().connectAsync(); } - - public TikTokLiveClientBuilder onUnhandledSocial( - EventConsumer event) { + public TikTokLiveClientBuilder onUnhandledSocial(EventConsumer event) { tikTokEventHandler.subscribe(TikTokUnhandledSocialEvent.class, event); return this; } @@ -289,8 +288,7 @@ public LiveClientBuilder onChest(EventConsumer event) { } - public TikTokLiveClientBuilder onLinkMicFanTicket( - EventConsumer event) { + public TikTokLiveClientBuilder onLinkMicFanTicket(EventConsumer event) { tikTokEventHandler.subscribe(TikTokLinkMicFanTicketEvent.class, event); return this; } @@ -305,14 +303,12 @@ public TikTokLiveClientBuilder onShop(EventConsumer event) { return this; } - public TikTokLiveClientBuilder onDetect( - EventConsumer event) { + public TikTokLiveClientBuilder onDetect(EventConsumer event) { tikTokEventHandler.subscribe(TikTokDetectEvent.class, event); return this; } - public TikTokLiveClientBuilder onLinkLayer( - EventConsumer event) { + public TikTokLiveClientBuilder onLinkLayer(EventConsumer event) { tikTokEventHandler.subscribe(TikTokLinkLayerEvent.class, event); return this; } @@ -322,6 +318,11 @@ public TikTokLiveClientBuilder onConnected(EventConsumer e return this; } + public TikTokLiveClientBuilder onPreConnection(EventConsumer event) { + tikTokEventHandler.subscribe(TikTokPreConnectionEvent.class, event); + return this; + } + public TikTokLiveClientBuilder onCaption(EventConsumer event) { tikTokEventHandler.subscribe(TikTokCaptionEvent.class, event); return this; @@ -332,8 +333,7 @@ public TikTokLiveClientBuilder onQuestion(EventConsumer eve return this; } - public TikTokLiveClientBuilder onRoomPin( - EventConsumer event) { + public TikTokLiveClientBuilder onRoomPin(EventConsumer event) { tikTokEventHandler.subscribe(TikTokRoomPinEvent.class, event); return this; } @@ -373,8 +373,7 @@ public TikTokLiveClientBuilder onLink(EventConsumer event) { return this; } - public TikTokLiveClientBuilder onBarrage( - EventConsumer event) { + public TikTokLiveClientBuilder onBarrage(EventConsumer event) { tikTokEventHandler.subscribe(TikTokBarrageEvent.class, event); return this; } @@ -391,8 +390,7 @@ public TikTokLiveClientBuilder onGiftCombo(EventConsumer e } - public TikTokLiveClientBuilder onLinkMicArmies( - EventConsumer event) { + public TikTokLiveClientBuilder onLinkMicArmies(EventConsumer event) { tikTokEventHandler.subscribe(TikTokLinkMicArmiesEvent.class, event); return this; } @@ -402,20 +400,17 @@ public TikTokLiveClientBuilder onEmote(EventConsumer event) { return this; } - public TikTokLiveClientBuilder onUnauthorizedMember( - EventConsumer event) { + public TikTokLiveClientBuilder onUnauthorizedMember(EventConsumer event) { tikTokEventHandler.subscribe(TikTokUnauthorizedMemberEvent.class, event); return this; } - public TikTokLiveClientBuilder onInRoomBanner( - EventConsumer event) { + public TikTokLiveClientBuilder onInRoomBanner(EventConsumer event) { tikTokEventHandler.subscribe(TikTokInRoomBannerEvent.class, event); return this; } - public TikTokLiveClientBuilder onLinkMicMethod( - EventConsumer event) { + public TikTokLiveClientBuilder onLinkMicMethod(EventConsumer event) { tikTokEventHandler.subscribe(TikTokLinkMicMethodEvent.class, event); return this; } @@ -487,8 +482,7 @@ public TikTokLiveClientBuilder onShare(EventConsumer event) { return this; } - public TikTokLiveClientBuilder onUnhandledMember( - EventConsumer event) { + public TikTokLiveClientBuilder onUnhandledMember(EventConsumer event) { tikTokEventHandler.subscribe(TikTokUnhandledMemberEvent.class, event); return this; } @@ -498,20 +492,17 @@ public TikTokLiveClientBuilder onSubNotify(EventConsumer e return this; } - public TikTokLiveClientBuilder onLinkMicBattle( - EventConsumer event) { + public TikTokLiveClientBuilder onLinkMicBattle(EventConsumer event) { tikTokEventHandler.subscribe(TikTokLinkMicBattleEvent.class, event); return this; } - public TikTokLiveClientBuilder onDisconnected( - EventConsumer event) { + public TikTokLiveClientBuilder onDisconnected(EventConsumer event) { tikTokEventHandler.subscribe(TikTokDisconnectedEvent.class, event); return this; } - public TikTokLiveClientBuilder onUnhandledControl( - EventConsumer event) { + public TikTokLiveClientBuilder onUnhandledControl(EventConsumer event) { tikTokEventHandler.subscribe(TikTokUnhandledControlEvent.class, event); return this; } diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveHttpClient.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveHttpClient.java index b694b054..f3453832 100644 --- a/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveHttpClient.java +++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveHttpClient.java @@ -66,6 +66,22 @@ public TikTokLiveHttpClient() { public GiftsData.Response fetchGiftsData() { var url = TIKTOK_URL_WEBCAST + "gift/list/"; + var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings(); + if (proxyClientSettings.isEnabled()) { + while (proxyClientSettings.hasNext()) { + try { + var optional = httpFactory.client(url) + .build() + .toJsonResponse(); + + if (optional.isEmpty()) { + throw new TikTokLiveRequestException("Unable to fetch gifts information's"); + } + var json = optional.get(); + return giftsDataMapper.map(json); + } catch (TikTokProxyRequestException ignored) {} + } + } var optional = httpFactory.client(url) .build() .toJsonResponse(); @@ -73,6 +89,7 @@ public GiftsData.Response fetchGiftsData() { if (optional.isEmpty()) { throw new TikTokLiveRequestException("Unable to fetch gifts information's"); } + var json = optional.get(); return giftsDataMapper.map(json); } @@ -85,11 +102,11 @@ public LiveUserData.Response fetchLiveUserData(String userName) { @Override public LiveUserData.Response fetchLiveUserData(LiveUserData.Request request) { + var url = TIKTOK_URL_WEB + "api-live/user/room"; var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings(); if (proxyClientSettings.isEnabled()) { while (proxyClientSettings.hasNext()) { try { - var url = TIKTOK_URL_WEB + "api-live/user/room"; var optional = httpFactory.client(url) .withParam("uniqueId", request.getUserName()) .withParam("sourceType", "54") @@ -105,7 +122,6 @@ public LiveUserData.Response fetchLiveUserData(LiveUserData.Request request) { } catch (TikTokProxyRequestException ignored) {} } } - var url = TIKTOK_URL_WEB + "api-live/user/room"; var optional = httpFactory.client(url) .withParam("uniqueId", request.getUserName()) .withParam("sourceType", "54") @@ -127,11 +143,11 @@ public LiveData.Response fetchLiveData(String roomId) { @Override public LiveData.Response fetchLiveData(LiveData.Request request) { + var url = TIKTOK_URL_WEBCAST + "room/info"; var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings(); if (proxyClientSettings.isEnabled()) { while (proxyClientSettings.hasNext()) { try { - var url = TIKTOK_URL_WEBCAST + "room/info"; var optional = httpFactory.client(url) .withParam("room_id", request.getRoomId()) .build() @@ -146,7 +162,6 @@ public LiveData.Response fetchLiveData(LiveData.Request request) { } catch (TikTokProxyRequestException ignored) {} } } - var url = TIKTOK_URL_WEBCAST + "room/info"; var optional = httpFactory.client(url) .withParam("room_id", request.getRoomId()) .build() diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/http/HttpClient.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/http/HttpClient.java index a6a675f4..f7a8d35c 100644 --- a/Client/src/main/java/io/github/jwdeveloper/tiktok/http/HttpClient.java +++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/http/HttpClient.java @@ -39,7 +39,7 @@ public class HttpClient { protected final String url; private final Pattern pattern = Pattern.compile("charset=(.*?)(?=&|$)"); - public Optional> toResponse() { + public Optional> toResponse() { var client = prepareClient(); var request = prepareGetRequest(); try { diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/http/HttpProxyClient.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/http/HttpProxyClient.java index d17c4129..580cfd6f 100644 --- a/Client/src/main/java/io/github/jwdeveloper/tiktok/http/HttpProxyClient.java +++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/http/HttpProxyClient.java @@ -77,6 +77,11 @@ public Optional> handleHttpProxyRequest() { if (proxySettings.isAutoDiscard()) proxySettings.remove(); proxySettings.setLastSuccess(false); + throw new TikTokProxyRequestException(e); + } catch (IOException e) { + if (e.getMessage().contains("503") && proxySettings.isFallback()) // Indicates proxy protocol is not supported + return super.toResponse(); + throw new TikTokProxyRequestException(e); } catch (Exception e) { throw new TikTokLiveRequestException(e); } @@ -119,6 +124,8 @@ public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {} proxySettings.setLastSuccess(true); return Optional.of(response); } catch (IOException e) { + if (e.getMessage().contains("503") && proxySettings.isFallback()) // Indicates proxy protocol is not supported + return super.toResponse(); if (proxySettings.isAutoDiscard()) proxySettings.remove(); proxySettings.setLastSuccess(false); diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/http/mappers/LiveDataMapper.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/http/mappers/LiveDataMapper.java index 34041b44..966ff2f0 100644 --- a/Client/src/main/java/io/github/jwdeveloper/tiktok/http/mappers/LiveDataMapper.java +++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/http/mappers/LiveDataMapper.java @@ -105,6 +105,22 @@ public LiveData.Response map(String json) { response.setHost(user); } + if (data.has("link_mic")) { + var element = data.getAsJsonObject("link_mic"); + var multi_live = element.get("multi_live_enum").getAsInt(); + var rival_id = element.get("rival_anchor_id").getAsInt(); + var battle_scores = element.get("battle_scores").getAsJsonArray(); + if (multi_live == 1) { + if (!battle_scores.isEmpty()) + response.setLiveType(LiveData.LiveType.BATTLE); + else if (rival_id != 0) + response.setLiveType(LiveData.LiveType.CO_HOST); + else + response.setLiveType(LiveData.LiveType.BOX); + } else + response.setLiveType(LiveData.LiveType.SOLO); + } + return response; } @@ -127,4 +143,4 @@ public User getUser(JsonObject jsonElement) { user.addAttribute(UserAttribute.LiveHost); return user; } -} +} \ No newline at end of file