diff --git a/API/src/main/java/io/github/jwdeveloper/tiktok/http/LiveHttpClient.java b/API/src/main/java/io/github/jwdeveloper/tiktok/http/LiveHttpClient.java index 218bcbd..2eb671d 100644 --- a/API/src/main/java/io/github/jwdeveloper/tiktok/http/LiveHttpClient.java +++ b/API/src/main/java/io/github/jwdeveloper/tiktok/http/LiveHttpClient.java @@ -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); } \ No newline at end of file diff --git a/API/src/main/java/io/github/jwdeveloper/tiktok/live/LiveClient.java b/API/src/main/java/io/github/jwdeveloper/tiktok/live/LiveClient.java index 962c05b..c0ce8a4 100644 --- a/API/src/main/java/io/github/jwdeveloper/tiktok/live/LiveClient.java +++ b/API/src/main/java/io/github/jwdeveloper/tiktok/live/LiveClient.java @@ -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 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! + *

We cannot fix this as it is a TikTok issue, not a library issue. + */ + boolean sendChat(String content); } \ 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 074723e..08d65a4 100644 --- a/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClient.java +++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveClient.java @@ -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 onConnection) { connectAsync().thenAccept(onConnection); } 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 71fd9a6..7fd9082 100644 --- a/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveHttpClient.java +++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveHttpClient.java @@ -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 * Signing API by Isaac Kogan */ 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> getStartingPayload(LiveConnectionData.Request request) { var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings(); if (proxyClientSettings.isEnabled()) { diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveHttpOfflineClient.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveHttpOfflineClient.java index f2d5537..a9dd5be 100644 --- a/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveHttpOfflineClient.java +++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/TikTokLiveHttpOfflineClient.java @@ -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; } } \ No newline at end of file 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 f0fee06..678b262 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 @@ -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 ActionResult> toHttpResponse(HttpResponse.BodyHandler 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 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; diff --git a/Client/src/main/java/io/github/jwdeveloper/tiktok/http/HttpClientBuilder.java b/Client/src/main/java/io/github/jwdeveloper/tiktok/http/HttpClientBuilder.java index dd1ab12..d00f168 100644 --- a/Client/src/main/java/io/github/jwdeveloper/tiktok/http/HttpClientBuilder.java +++ b/Client/src/main/java/io/github/jwdeveloper/tiktok/http/HttpClientBuilder.java @@ -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); } } \ No newline at end of file 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 31331ad..a40451b 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 @@ -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)