Converted from Optional to ActionResult

Moved Logger creation to LoggerFactory
Fixed creating more than 1 recording thread for each livestream
And more optimizations!
This commit is contained in:
kohlerpop1
2024-02-19 14:55:59 -05:00
parent 6b22154c82
commit 1b2a8bad93
9 changed files with 150 additions and 206 deletions

View File

@@ -44,14 +44,12 @@ import io.github.jwdeveloper.tiktok.mappers.*;
import io.github.jwdeveloper.tiktok.mappers.data.MappingResult; import io.github.jwdeveloper.tiktok.mappers.data.MappingResult;
import io.github.jwdeveloper.tiktok.mappers.handlers.*; import io.github.jwdeveloper.tiktok.mappers.handlers.*;
import io.github.jwdeveloper.tiktok.messages.webcast.*; import io.github.jwdeveloper.tiktok.messages.webcast.*;
import io.github.jwdeveloper.tiktok.utils.ConsoleColors;
import io.github.jwdeveloper.tiktok.websocket.TikTokWebSocketClient; import io.github.jwdeveloper.tiktok.websocket.TikTokWebSocketClient;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.logging.Formatter; import java.util.logging.Logger;
import java.util.logging.*;
public class TikTokLiveClientBuilder implements LiveClientBuilder { public class TikTokLiveClientBuilder implements LiveClientBuilder {
@@ -61,8 +59,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
protected Consumer<TikTokMapper> onCustomMappings; protected Consumer<TikTokMapper> onCustomMappings;
protected Logger logger; protected Logger logger;
public TikTokLiveClientBuilder(String userName) public TikTokLiveClientBuilder(String userName) {
{
this.clientSettings = LiveClientSettings.createDefault(); this.clientSettings = LiveClientSettings.createDefault();
this.clientSettings.setHostName(userName); this.clientSettings.setHostName(userName);
this.tikTokEventHandler = new TikTokLiveEventHandler(); this.tikTokEventHandler = new TikTokLiveEventHandler();
@@ -75,7 +72,6 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
return this; return this;
} }
public TikTokLiveClientBuilder configure(Consumer<LiveClientSettings> onConfigure) { public TikTokLiveClientBuilder configure(Consumer<LiveClientSettings> onConfigure) {
onConfigure.accept(clientSettings); onConfigure.accept(clientSettings);
return this; return this;
@@ -88,44 +84,20 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
} }
protected void validate() { protected void validate() {
if (clientSettings.getClientLanguage() == null || clientSettings.getClientLanguage().isEmpty()) { if (clientSettings.getClientLanguage() == null || clientSettings.getClientLanguage().isEmpty())
clientSettings.setClientLanguage("en"); clientSettings.setClientLanguage("en");
}
if (clientSettings.getHostName() == null || clientSettings.getHostName().isEmpty())
if (clientSettings.getHostName() == null || clientSettings.getHostName().isEmpty()) {
throw new TikTokLiveException("HostName can not be null"); throw new TikTokLiveException("HostName can not be null");
}
if (clientSettings.getHostName().startsWith("@")) { if (clientSettings.getHostName().startsWith("@"))
clientSettings.setHostName(clientSettings.getHostName().substring(1)); clientSettings.setHostName(clientSettings.getHostName().substring(1));
}
var httpSettings = clientSettings.getHttpSettings(); var httpSettings = clientSettings.getHttpSettings();
httpSettings.getParams().put("app_language", clientSettings.getClientLanguage()); httpSettings.getParams().put("app_language", clientSettings.getClientLanguage());
httpSettings.getParams().put("webcast_language", clientSettings.getClientLanguage()); httpSettings.getParams().put("webcast_language", clientSettings.getClientLanguage());
this.logger = LoggerFactory.create(clientSettings.getHostName(), clientSettings); this.logger = LoggerFactory.create(clientSettings.getHostName(), clientSettings);
var handler = new ConsoleHandler();
handler.setFormatter(new Formatter() {
@Override
public String format(LogRecord record) {
var sb = new StringBuilder();
sb.append(ConsoleColors.GREEN).append("[").append(record.getLoggerName()).append("] ");
sb.append(ConsoleColors.GREEN).append("[").append(record.getLevel()).append("]: ");
sb.append(ConsoleColors.WHITE_BRIGHT).append(record.getMessage());
sb.append(ConsoleColors.RESET).append("\n");
return sb.toString();
}
});
logger.setUseParentHandlers(false);
logger.addHandler(handler);
logger.setLevel(clientSettings.getLogLevel());
if (!clientSettings.isPrintToConsole()) {
logger.setLevel(Level.OFF);
}
} }
public LiveClient build() { public LiveClient build() {
@@ -266,13 +238,11 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
return this; return this;
} }
// @Override
public LiveClientBuilder onChest(EventConsumer<TikTokChestEvent> event) { public LiveClientBuilder onChest(EventConsumer<TikTokChestEvent> event) {
tikTokEventHandler.subscribe(TikTokChestEvent.class, event); tikTokEventHandler.subscribe(TikTokChestEvent.class, event);
return this; return this;
} }
public TikTokLiveClientBuilder onLinkMicFanTicket(EventConsumer<TikTokLinkMicFanTicketEvent> event) { public TikTokLiveClientBuilder onLinkMicFanTicket(EventConsumer<TikTokLinkMicFanTicketEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkMicFanTicketEvent.class, event); tikTokEventHandler.subscribe(TikTokLinkMicFanTicketEvent.class, event);
return this; return this;
@@ -329,14 +299,12 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
return this; return this;
} }
@Override @Override
public TikTokLiveClientBuilder onRoomInfo(EventConsumer<TikTokRoomInfoEvent> event) { public TikTokLiveClientBuilder onRoomInfo(EventConsumer<TikTokRoomInfoEvent> event) {
tikTokEventHandler.subscribe(TikTokRoomInfoEvent.class, event); tikTokEventHandler.subscribe(TikTokRoomInfoEvent.class, event);
return this; return this;
} }
public TikTokLiveClientBuilder onLivePaused(EventConsumer<TikTokLivePausedEvent> event) { public TikTokLiveClientBuilder onLivePaused(EventConsumer<TikTokLivePausedEvent> event) {
tikTokEventHandler.subscribe(TikTokLivePausedEvent.class, event); tikTokEventHandler.subscribe(TikTokLivePausedEvent.class, event);
return this; return this;
@@ -363,7 +331,6 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
return this; return this;
} }
public TikTokLiveClientBuilder onGift(EventConsumer<TikTokGiftEvent> event) { public TikTokLiveClientBuilder onGift(EventConsumer<TikTokGiftEvent> event) {
tikTokEventHandler.subscribe(TikTokGiftEvent.class, event); tikTokEventHandler.subscribe(TikTokGiftEvent.class, event);
return this; return this;
@@ -374,7 +341,6 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
return this; return this;
} }
public TikTokLiveClientBuilder onLinkMicArmies(EventConsumer<TikTokLinkMicArmiesEvent> event) { public TikTokLiveClientBuilder onLinkMicArmies(EventConsumer<TikTokLinkMicArmiesEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkMicArmiesEvent.class, event); tikTokEventHandler.subscribe(TikTokLinkMicArmiesEvent.class, event);
return this; return this;
@@ -451,7 +417,6 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
return this; return this;
} }
public TikTokLiveClientBuilder onJoin(EventConsumer<TikTokJoinEvent> event) { public TikTokLiveClientBuilder onJoin(EventConsumer<TikTokJoinEvent> event) {
tikTokEventHandler.subscribe(TikTokJoinEvent.class, event); tikTokEventHandler.subscribe(TikTokJoinEvent.class, event);
return this; return this;

View File

@@ -23,7 +23,7 @@
package io.github.jwdeveloper.tiktok; package io.github.jwdeveloper.tiktok;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import io.github.jwdeveloper.tiktok.common.LoggerFactory; import io.github.jwdeveloper.tiktok.common.*;
import io.github.jwdeveloper.tiktok.data.requests.*; import io.github.jwdeveloper.tiktok.data.requests.*;
import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings; import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
import io.github.jwdeveloper.tiktok.exceptions.*; import io.github.jwdeveloper.tiktok.exceptions.*;
@@ -32,7 +32,6 @@ import io.github.jwdeveloper.tiktok.http.mappers.*;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse; import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.util.Optional;
import java.util.logging.Logger; import java.util.logging.Logger;
public class TikTokLiveHttpClient implements LiveHttpClient public class TikTokLiveHttpClient implements LiveHttpClient
@@ -65,118 +64,98 @@ public class TikTokLiveHttpClient implements LiveHttpClient
this(new HttpClientFactory(LiveClientSettings.createDefault()), LiveClientSettings.createDefault()); this(new HttpClientFactory(LiveClientSettings.createDefault()), LiveClientSettings.createDefault());
} }
public GiftsData.Response fetchGiftsData() { public GiftsData.Response fetchGiftsData() {
var url = TIKTOK_URL_WEBCAST + "gift/list/";
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings(); var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) { if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) { while (proxyClientSettings.hasNext()) {
try { try {
var optional = httpFactory.client(url) return getGiftsData();
.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) {} } catch (TikTokProxyRequestException ignored) {}
} }
} }
var optional = httpFactory.client(url) return getGiftsData();
.build() }
.toJsonResponse();
if (optional.isEmpty()) { public GiftsData.Response getGiftsData() {
throw new TikTokLiveRequestException("Unable to fetch gifts information's"); var url = TIKTOK_URL_WEBCAST + "gift/list/";
} var result = httpFactory.client(url)
.build()
.toJsonResponse();
var json = optional.get(); if (result.isFailure())
throw new TikTokLiveRequestException("Unable to fetch gifts information's"+result.toStack());
var json = result.getContent();
return giftsDataMapper.map(json); return giftsDataMapper.map(json);
} }
@Override @Override
public LiveUserData.Response fetchLiveUserData(LiveUserData.Request request) { public LiveUserData.Response fetchLiveUserData(LiveUserData.Request request) {
var url = TIKTOK_URL_WEB + "api-live/user/room";
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings(); var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) { if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) { while (proxyClientSettings.hasNext()) {
try { try {
var optional = httpFactory.client(url) return getLiveUserData(request);
.withParam("uniqueId", request.getUserName())
.withParam("sourceType", "54")
.build()
.toJsonResponse();
if (optional.isEmpty()) {
throw new TikTokLiveRequestException("Unable to get information's about user");
}
var json = optional.get();
return liveUserDataMapper.map(json);
} catch (TikTokProxyRequestException ignored) {} } catch (TikTokProxyRequestException ignored) {}
} }
} }
var optional = httpFactory.client(url) return getLiveUserData(request);
.withParam("uniqueId", request.getUserName()) }
.withParam("sourceType", "54")
.build()
.toJsonResponse();
if (optional.isEmpty()) { public LiveUserData.Response getLiveUserData(LiveUserData.Request request) {
throw new TikTokLiveRequestException("Unable to get information's about user"); var url = TIKTOK_URL_WEB + "api-live/user/room";
} var result = httpFactory.client(url)
.withParam("uniqueId", request.getUserName())
.withParam("sourceType", "54")
.build()
.toJsonResponse();
var json = optional.get(); if (result.isFailure())
throw new TikTokLiveRequestException("Unable to get information's about user"+result.toStack());
var json = result.getContent();
return liveUserDataMapper.map(json); return liveUserDataMapper.map(json);
} }
@Override @Override
public LiveData.Response fetchLiveData(LiveData.Request request) { public LiveData.Response fetchLiveData(LiveData.Request request) {
var url = TIKTOK_URL_WEBCAST + "room/info";
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings(); var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) { if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) { while (proxyClientSettings.hasNext()) {
try { try {
var optional = httpFactory.client(url) return getLiveData(request);
.withParam("room_id", request.getRoomId())
.build()
.toJsonResponse();
if (optional.isEmpty()) {
throw new TikTokLiveRequestException("Unable to get info about live room");
}
var json = optional.get();
return liveDataMapper.map(json);
} catch (TikTokProxyRequestException ignored) {} } catch (TikTokProxyRequestException ignored) {}
} }
} }
var optional = httpFactory.client(url) return getLiveData(request);
.withParam("room_id", request.getRoomId()) }
.build()
.toJsonResponse();
if (optional.isEmpty()) { public LiveData.Response getLiveData(LiveData.Request request) {
throw new TikTokLiveRequestException("Unable to get info about live room"); var url = TIKTOK_URL_WEBCAST + "room/info";
} var result = httpFactory.client(url)
.withParam("room_id", request.getRoomId())
.build()
.toJsonResponse();
var json = optional.get(); if (result.isFailure())
throw new TikTokLiveRequestException("Unable to get info about live room"+result.toStack());
var json = result.getContent();
return liveDataMapper.map(json); return liveDataMapper.map(json);
} }
@Override @Override
public LiveConnectionData.Response fetchLiveConnectionData(LiveConnectionData.Request request) { public LiveConnectionData.Response fetchLiveConnectionData(LiveConnectionData.Request request) {
HttpResponse<byte[]> credentialsResponse = getOptionalProxyResponse(request).orElseGet(()-> getStarterPayload(request.getRoomId())); var result = getStartingPayload(request);
HttpResponse<byte[]> credentialsResponse = result.getContent(); // Always guaranteed to have response
try { try {
var optionalHeader = credentialsResponse.headers().firstValue("x-set-tt-cookie"); var resultHeader = ActionResult.of(credentialsResponse.headers().firstValue("x-set-tt-cookie"));
if (optionalHeader.isEmpty()) { if (resultHeader.isFailure()) {
logger.warning("SignServer Headers: "+credentialsResponse.headers().map()); logger.warning("SignServer Headers: "+request.getRoomId()+" - "+credentialsResponse.headers().map());
throw new TikTokSignServerException("Sign server did not return the x-set-tt-cookie header"); throw new TikTokSignServerException("Sign server did not return the x-set-tt-cookie header"+result.toStack());
} }
var websocketCookie = optionalHeader.get(); var websocketCookie = resultHeader.getContent();
var webcastResponse = WebcastResponse.parseFrom(credentialsResponse.body()); var webcastResponse = WebcastResponse.parseFrom(credentialsResponse.body());
var webSocketUrl = httpFactory var webSocketUrl = httpFactory
.client(webcastResponse.getPushServer()) .client(webcastResponse.getPushServer())
@@ -190,11 +169,23 @@ public class TikTokLiveHttpClient implements LiveHttpClient
return new LiveConnectionData.Response(websocketCookie, webSocketUrl, webcastResponse); return new LiveConnectionData.Response(websocketCookie, webSocketUrl, webcastResponse);
} catch (InvalidProtocolBufferException e) { } catch (InvalidProtocolBufferException e) {
throw new TikTokSignServerException("Unable to parse websocket credentials response to WebcastResponse"); throw new TikTokSignServerException("Unable to parse websocket credentials response to WebcastResponse"+result.toStack());
} }
} }
HttpResponse<byte[]> getStarterPayload(String room_id) { private ActionResult<HttpResponse<byte[]>> getStartingPayload(LiveConnectionData.Request request) {
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) {
try {
return getByteResponse(request.getRoomId());
} catch (TikTokProxyRequestException | TikTokSignServerException ignored) {}
}
}
return getByteResponse(request.getRoomId());
}
private ActionResult<HttpResponse<byte[]>> getByteResponse(String room_id) {
HttpClientBuilder builder = httpFactory.client(TIKTOK_SIGN_API) HttpClientBuilder builder = httpFactory.client(TIKTOK_SIGN_API)
.withParam("client", "ttlive-java") .withParam("client", "ttlive-java")
.withParam("uuc", "1") .withParam("uuc", "1")
@@ -203,24 +194,11 @@ public class TikTokLiveHttpClient implements LiveHttpClient
if (clientSettings.getApiKey() != null) if (clientSettings.getApiKey() != null)
builder.withParam("apiKey", clientSettings.getApiKey()); builder.withParam("apiKey", clientSettings.getApiKey());
var optional = builder.build().toResponse(); var result = builder.build().toResponse();
if (optional.isEmpty()) { if (result.isFailure())
throw new TikTokSignServerException("Unable to get websocket connection credentials"); throw new TikTokSignServerException("Unable to get websocket connection credentials"+result.toStack());
}
return optional.get();
}
Optional<HttpResponse<byte[]>> getOptionalProxyResponse(LiveConnectionData.Request request) { return result;
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
if (proxyClientSettings.isEnabled()) {
while (proxyClientSettings.hasNext()) {
try {
HttpResponse<byte[]> credentialsResponse = getStarterPayload(request.getRoomId());
return Optional.of(credentialsResponse);
} catch (TikTokProxyRequestException | TikTokSignServerException ignored) {}
}
}
return Optional.empty();
} }
} }

View File

@@ -2,11 +2,12 @@ package io.github.jwdeveloper.tiktok.common;
import lombok.Data; import lombok.Data;
import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
@Data @Data
public class ActionResult<T> public class ActionResult<T> {
{
private boolean success = true; private boolean success = true;
private T content; private T content;
private String message; private String message;
@@ -29,6 +30,10 @@ public class ActionResult<T>
return new ActionResultBuilder<>(content); return new ActionResultBuilder<>(content);
} }
public static <T> ActionResult<T> of(Optional<T> optional) {
return new ActionResult<>(optional.orElse(null), optional.isPresent());
}
public boolean isFailure() { public boolean isFailure() {
return !isSuccess(); return !isSuccess();
} }
@@ -36,48 +41,47 @@ public class ActionResult<T>
public boolean hasMessage() { public boolean hasMessage() {
return message != null; return message != null;
} }
public String toStack() {
return hasMessage() ? " - "+message : "";
}
public boolean hasContent() { public boolean hasContent() {
return content != null; return content != null;
} }
public static <T> ActionResult<T> success() {
return new ActionResult<>(null, true);
}
public <Output> ActionResult<Output> cast(Output output) { public <Output> ActionResult<Output> cast(Output output) {
return new ActionResult<>(output, this.isSuccess(), this.getMessage()); return new ActionResult<>(output, this.isSuccess(), this.getMessage());
} }
public <Output> ActionResult<Output> cast() { public <Output> ActionResult<Output> cast() {
return new ActionResult<>(null, this.isSuccess(), this.getMessage()); return cast(null);
} }
public <U> ActionResult<U> map(Function<? super T, ? extends U> mapper) { public <U> ActionResult<U> map(Function<? super T, ? extends U> mapper) {
return hasContent() ? cast(mapper.apply(content)) : cast(); return hasContent() ? cast(mapper.apply(content)) : cast();
} }
public static <Input, Output> ActionResult<Output> cast(ActionResult<Input> action, Output output) {
return new ActionResult<>(output, action.isSuccess(), action.getMessage());
}
public static <T> ActionResult<T> success(T payload) {
return new ActionResult<>(payload, true);
}
public static <T> ActionResult<T> success(T payload, String message) { public static <T> ActionResult<T> success(T payload, String message) {
return new ActionResult<>(payload, true, message); return new ActionResult<>(payload, true, message);
} }
public static <T> ActionResult<T> failure() { public static <T> ActionResult<T> success(T payload) {
return new ActionResult<>(null, false); return success(payload, null);
} }
public static <T> ActionResult<T> failure(String message) { public static <T> ActionResult<T> success() {
return new ActionResult<>(null, false, message); return success(null);
} }
public static <T> ActionResult<T> failure(T target, String message) { public static <T> ActionResult<T> failure(T target, String message) {
return new ActionResult<>(target, false, message); return new ActionResult<>(target, false, message);
} }
public static <T> ActionResult<T> failure(String message) {
return failure(null, message);
}
public static <T> ActionResult<T> failure() {
return failure(null);
}
} }

View File

@@ -9,23 +9,25 @@ public class LoggerFactory
{ {
public static Logger create(String name, LiveClientSettings settings) { public static Logger create(String name, LiveClientSettings settings) {
Logger logger = Logger.getLogger(name); Logger logger = Logger.getLogger(name);
var handler = new ConsoleHandler(); if (logger.getHandlers().length == 0) {
handler.setFormatter(new Formatter() { var handler = new ConsoleHandler();
@Override handler.setFormatter(new Formatter()
public String format(LogRecord record) { {
var sb = new StringBuilder(); @Override
sb.append(ConsoleColors.GREEN).append("[").append(record.getLoggerName()).append("] "); public String format(LogRecord record) {
sb.append(ConsoleColors.GREEN).append("[").append(record.getLevel()).append("]: "); var sb = new StringBuilder();
sb.append(ConsoleColors.WHITE_BRIGHT).append(record.getMessage()); sb.append(ConsoleColors.GREEN).append("[").append(record.getLoggerName()).append("] ");
sb.append(ConsoleColors.RESET).append("\n"); sb.append(ConsoleColors.GREEN).append("[").append(record.getLevel()).append("]: ");
return sb.toString(); sb.append(ConsoleColors.WHITE_BRIGHT).append(record.getMessage());
} sb.append(ConsoleColors.RESET).append("\n");
}); return sb.toString();
logger.setUseParentHandlers(false); }
logger.addHandler(handler); });
logger.setLevel(settings.getLogLevel()); logger.setUseParentHandlers(false);
if (!settings.isPrintToConsole()) { logger.addHandler(handler);
logger.setLevel(Level.OFF); logger.setLevel(settings.getLogLevel());
if (!settings.isPrintToConsole())
logger.setLevel(Level.OFF);
} }
return logger; return logger;
} }

View File

@@ -35,8 +35,8 @@ import java.util.regex.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@AllArgsConstructor @AllArgsConstructor
public class HttpClient public class HttpClient {
{
protected final HttpClientSettings httpClientSettings; protected final HttpClientSettings httpClientSettings;
protected final String url; protected final String url;
private final Pattern pattern = Pattern.compile("charset=(.*?)(?=&|$)"); private final Pattern pattern = Pattern.compile("charset=(.*?)(?=&|$)");
@@ -71,13 +71,8 @@ public class HttpClient
} }
} }
public Optional<byte[]> toBinaryResponse() { public ActionResult<byte[]> toBinaryResponse() {
var optional = toResponse(); return toResponse().map(HttpResponse::body);
if (optional.isEmpty()) {
return Optional.empty();
}
var body = optional.get().body();
return Optional.of(body);
} }
public URI toUrl() { public URI toUrl() {

View File

@@ -78,7 +78,8 @@ public class HttpClientBuilder {
} }
public HttpClient build() { public HttpClient build() {
if (httpClientSettings.getProxyClientSettings().isEnabled()) var proxyClientSettings = httpClientSettings.getProxyClientSettings();
if (proxyClientSettings.isEnabled() && proxyClientSettings.hasNext())
return new HttpProxyClient(httpClientSettings, url); return new HttpProxyClient(httpClientSettings, url);
return new HttpClient(httpClientSettings, url); return new HttpClient(httpClientSettings, url);
} }

View File

@@ -22,6 +22,7 @@
*/ */
package io.github.jwdeveloper.tiktok.http; package io.github.jwdeveloper.tiktok.http;
import io.github.jwdeveloper.tiktok.common.ActionResult;
import io.github.jwdeveloper.tiktok.data.settings.*; import io.github.jwdeveloper.tiktok.data.settings.*;
import io.github.jwdeveloper.tiktok.exceptions.*; import io.github.jwdeveloper.tiktok.exceptions.*;
@@ -35,8 +36,8 @@ import java.security.cert.X509Certificate;
import java.util.*; import java.util.*;
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) { public HttpProxyClient(HttpClientSettings httpClientSettings, String url) {
@@ -44,14 +45,14 @@ public class HttpProxyClient extends HttpClient
this.proxySettings = httpClientSettings.getProxyClientSettings(); this.proxySettings = httpClientSettings.getProxyClientSettings();
} }
public Optional<HttpResponse<byte[]>> toResponse() { public ActionResult<HttpResponse<byte[]>> toResponse() {
return switch (proxySettings.getType()) { return switch (proxySettings.getType()) {
case HTTP, DIRECT -> handleHttpProxyRequest(); case HTTP, DIRECT -> handleHttpProxyRequest();
default -> handleSocksProxyRequest(); default -> handleSocksProxyRequest();
}; };
} }
public Optional<HttpResponse<byte[]>> handleHttpProxyRequest() { 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())
@@ -69,7 +70,7 @@ public class HttpProxyClient extends HttpClient
var response = client.send(request, HttpResponse.BodyHandlers.ofByteArray()); var response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
if (response.statusCode() != 200) if (response.statusCode() != 200)
continue; continue;
return Optional.of(response); return ActionResult.success(response);
} catch (HttpConnectTimeoutException | ConnectException e) { } catch (HttpConnectTimeoutException | ConnectException e) {
if (proxySettings.isAutoDiscard()) if (proxySettings.isAutoDiscard())
proxySettings.remove(); proxySettings.remove();
@@ -85,7 +86,7 @@ public class HttpProxyClient extends HttpClient
throw new TikTokLiveRequestException("No more proxies available!"); throw new TikTokLiveRequestException("No more proxies available!");
} }
private Optional<HttpResponse<byte[]>> handleSocksProxyRequest() { 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() {
@@ -117,7 +118,7 @@ public class HttpProxyClient extends HttpClient
var response = createHttpResponse(body, toUrl(), responseInfo); var response = createHttpResponse(body, toUrl(), responseInfo);
return Optional.of(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.toResponse(); return super.toResponse();
@@ -133,11 +134,10 @@ public class HttpProxyClient extends HttpClient
// Should never be reached! // Should never be reached!
System.out.println("handleSocksProxyRequest: If you see this, message us on discord!"); System.out.println("handleSocksProxyRequest: If you see this, message us on discord!");
e.printStackTrace(); e.printStackTrace();
return Optional.empty();
} catch (TikTokLiveRequestException e) { } catch (TikTokLiveRequestException e) {
e.printStackTrace(); e.printStackTrace();
return Optional.empty();
} }
return ActionResult.failure();
} }
private ResponseInfo createResponseInfo(int code, Map<String, List<String>> headers) { private ResponseInfo createResponseInfo(int code, Map<String, List<String>> headers) {

View File

@@ -31,6 +31,7 @@ import io.github.jwdeveloper.tiktok.extension.recorder.api.LiveRecorder;
import io.github.jwdeveloper.tiktok.extension.recorder.impl.data.*; import io.github.jwdeveloper.tiktok.extension.recorder.impl.data.*;
import io.github.jwdeveloper.tiktok.extension.recorder.impl.enums.LiveQuality; import io.github.jwdeveloper.tiktok.extension.recorder.impl.enums.LiveQuality;
import io.github.jwdeveloper.tiktok.live.LiveClient; import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.models.ConnectionState;
import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.HttpsURLConnection;
import java.io.*; import java.io.*;
@@ -79,9 +80,10 @@ public class RecorderListener implements LiveRecorder {
@TikTokEventObserver @TikTokEventObserver
private void onConnected(LiveClient liveClient, TikTokConnectedEvent event) { private void onConnected(LiveClient liveClient, TikTokConnectedEvent event) {
if (isConnected())
return;
liveDownloadThread = new Thread(() -> { liveDownloadThread = new Thread(() -> {
try { try {
var bufferSize = 1024;
var url = new URL(downloadData.getFullUrl()); var url = new URL(downloadData.getFullUrl());
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection(); HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
var headers = LiveClientSettings.DefaultRequestHeaders(); var headers = LiveClientSettings.DefaultRequestHeaders();
@@ -89,18 +91,26 @@ public class RecorderListener implements LiveRecorder {
connection.setRequestProperty(entry.getKey(), entry.getValue()); connection.setRequestProperty(entry.getKey(), entry.getValue());
} }
var in = new BufferedInputStream(connection.getInputStream());
var path = settings.getOutputPath() + File.separator + settings.getOutputFileName(); var path = settings.getOutputPath() + File.separator + settings.getOutputFileName();
var file = new File(path); var file = new File(path);
file.getParentFile().mkdirs(); file.getParentFile().mkdirs();
var fileOutputStream = new FileOutputStream(file); file.createNewFile();
byte[] dataBuffer = new byte[bufferSize];
int bytesRead; try (
while ((bytesRead = in.read(dataBuffer, 0, bufferSize)) != -1) { var in = connection.getInputStream();
fileOutputStream.write(dataBuffer, 0, bytesRead); var fos = new FileOutputStream(file)
) {
byte[] dataBuffer = new byte[1024];
int bytesRead;
while (liveClient.getRoomInfo().getConnectionState() == ConnectionState.CONNECTED && (bytesRead = in.read(dataBuffer)) != -1) {
fos.write(dataBuffer, 0, bytesRead);
fos.flush();
}
} catch (IOException ignored) {
} finally {
liveClient.getLogger().severe("Stopped recording "+liveClient.getRoomInfo().getHostName());
} }
in.close(); } catch (Exception e) {
} catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
}); });
@@ -108,27 +118,14 @@ public class RecorderListener implements LiveRecorder {
liveDownloadThread.start(); liveDownloadThread.start();
} }
private static void downloadUsingStream(String urlStr, String file) throws IOException {
URL url = new URL(urlStr);
BufferedInputStream bis = new BufferedInputStream(url.openStream());
FileOutputStream fis = new FileOutputStream(file);
byte[] buffer = new byte[1024];
int count;
while ((count = bis.read(buffer, 0, 1024)) != -1)
fis.write(buffer, 0, count);
fis.close();
bis.close();
}
@TikTokEventObserver @TikTokEventObserver
private void onDisconnected(LiveClient liveClient, TikTokDisconnectedEvent event) { private void onDisconnected(LiveClient liveClient, TikTokDisconnectedEvent event) {
if (isConnected()) if (isConnected() && settings.isStopOnDisconnect())
liveDownloadThread.interrupt(); liveDownloadThread.interrupt();
} }
@TikTokEventObserver @TikTokEventObserver
private void onDisconnected(LiveClient liveClient, TikTokLiveEndedEvent event) { private void onLiveEnded(LiveClient liveClient, TikTokLiveEndedEvent event) {
if (isConnected()) if (isConnected())
liveDownloadThread.interrupt(); liveDownloadThread.interrupt();
} }

View File

@@ -32,6 +32,7 @@ import java.util.function.Function;
@Getter @Getter
@Setter @Setter
public class RecorderSettings { public class RecorderSettings {
private String ffmpegPath; private String ffmpegPath;
private String quality; private String quality;
private String format; private String format;
@@ -39,6 +40,7 @@ public class RecorderSettings {
private String outputFileName; private String outputFileName;
private Function<String,DownloadData> prepareDownloadData; private Function<String,DownloadData> prepareDownloadData;
private boolean startOnConnected; private boolean startOnConnected;
private boolean stopOnDisconnect = true;
public static RecorderSettings DEFAULT() { public static RecorderSettings DEFAULT() {
return new RecorderSettings(); return new RecorderSettings();