mirror of
https://github.com/jwdeveloper/TikTokLiveJava.git
synced 2026-02-28 01:09:40 -05:00
Added PreConnectionEvent with LiveType, made optimizations, and added fallback to default request in proxy class in case proxy protocol is not supported by TikTok or Signing server.
This commit is contained in:
@@ -32,8 +32,7 @@ import io.github.jwdeveloper.tiktok.data.events.room.TikTokRoomInfoEvent;
|
||||
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.exceptions.TikTokLiveException;
|
||||
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveOfflineHostException;
|
||||
import io.github.jwdeveloper.tiktok.exceptions.*;
|
||||
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager;
|
||||
import io.github.jwdeveloper.tiktok.listener.ListenersManager;
|
||||
import io.github.jwdeveloper.tiktok.listener.TikTokListenersManager;
|
||||
@@ -127,22 +126,26 @@ public class TikTokLiveClient implements LiveClient {
|
||||
var userData = httpClient.fetchLiveUserData(userDataRequest);
|
||||
liveRoomInfo.setStartTime(userData.getStartedAtTimeStamp());
|
||||
liveRoomInfo.setRoomId(userData.getRoomId());
|
||||
if (userData.getUserStatus() == LiveUserData.UserStatus.Offline) {
|
||||
throw new TikTokLiveOfflineHostException("User is offline: "+liveRoomInfo.getHostUser());
|
||||
}
|
||||
if (userData.getUserStatus() == LiveUserData.UserStatus.NotFound) {
|
||||
throw new TikTokLiveOfflineHostException("User not found: "+liveRoomInfo.getHostUser());
|
||||
}
|
||||
|
||||
if (userData.getUserStatus() == LiveUserData.UserStatus.Offline)
|
||||
throw new TikTokLiveOfflineHostException("User is offline: "+liveRoomInfo.getHostName());
|
||||
|
||||
if (userData.getUserStatus() == LiveUserData.UserStatus.NotFound)
|
||||
throw new TikTokLiveOfflineHostException("User not found: "+liveRoomInfo.getHostName());
|
||||
|
||||
var liveDataRequest = new LiveData.Request(userData.getRoomId());
|
||||
var liveData = httpClient.fetchLiveData(liveDataRequest);
|
||||
|
||||
if (liveData.isAgeRestricted())
|
||||
throw new TikTokLiveException("Livestream for "+liveRoomInfo.getHostName()+" is 18+ or age restricted!");
|
||||
|
||||
if (liveData.getLiveStatus() == LiveData.LiveStatus.HostNotFound)
|
||||
throw new TikTokLiveOfflineHostException("LiveStream for "+liveRoomInfo.getHostName()+" could not be found.");
|
||||
|
||||
if (liveData.getLiveStatus() == LiveData.LiveStatus.HostOffline)
|
||||
throw new TikTokLiveOfflineHostException("LiveStream for "+liveRoomInfo.getHostName()+" not found, is the Host offline?");
|
||||
|
||||
tikTokEventHandler.publish(this, new TikTokRoomDataResponseEvent(liveData));
|
||||
if (liveData.getLiveStatus() == LiveData.LiveStatus.HostNotFound) {
|
||||
throw new TikTokLiveOfflineHostException("LiveStream for Host name could not be found.");
|
||||
}
|
||||
if (liveData.getLiveStatus() == LiveData.LiveStatus.HostOffline) {
|
||||
throw new TikTokLiveOfflineHostException("LiveStream for not be found, is the Host offline?");
|
||||
}
|
||||
|
||||
liveRoomInfo.setTitle(liveData.getTitle());
|
||||
liveRoomInfo.setViewersCount(liveData.getViewers());
|
||||
|
||||
@@ -99,7 +99,8 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
|
||||
}
|
||||
|
||||
public TikTokLiveClientBuilder addListener(TikTokEventListener listener) {
|
||||
listeners.add(listener);
|
||||
if (listener != null)
|
||||
listeners.add(listener);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,10 +36,9 @@ import java.util.Optional;
|
||||
public class TikTokLiveHttpClient implements LiveHttpClient {
|
||||
|
||||
/**
|
||||
* Signing API by Isaac Kogan
|
||||
* https://github-wiki-see.page/m/isaackogan/TikTokLive/wiki/All-About-Signatures
|
||||
*/
|
||||
private static final String TIKTOK_SIGN_API = "https://tiktok.eulerstream.com/webcast/sign_url";
|
||||
* <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_URL_WEB = "https://www.tiktok.com/";
|
||||
private static final String TIKTOK_URL_WEBCAST = "https://webcast.tiktok.com/webcast/";
|
||||
|
||||
@@ -47,7 +46,6 @@ public class TikTokLiveHttpClient implements LiveHttpClient {
|
||||
private final LiveClientSettings clientSettings;
|
||||
private final LiveUserDataMapper liveUserDataMapper;
|
||||
private final LiveDataMapper liveDataMapper;
|
||||
private final SignServerResponseMapper signServerResponseMapper;
|
||||
private final GiftsDataMapper giftsDataMapper;
|
||||
|
||||
public TikTokLiveHttpClient(HttpClientFactory factory, LiveClientSettings settings) {
|
||||
@@ -55,7 +53,6 @@ public class TikTokLiveHttpClient implements LiveHttpClient {
|
||||
clientSettings = settings;
|
||||
liveUserDataMapper = new LiveUserDataMapper();
|
||||
liveDataMapper = new LiveDataMapper();
|
||||
signServerResponseMapper = new SignServerResponseMapper();
|
||||
giftsDataMapper = new GiftsDataMapper();
|
||||
}
|
||||
|
||||
@@ -94,12 +91,6 @@ public class TikTokLiveHttpClient implements LiveHttpClient {
|
||||
return giftsDataMapper.map(json);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public LiveUserData.Response fetchLiveUserData(String userName) {
|
||||
return fetchLiveUserData(new LiveUserData.Request(userName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiveUserData.Response fetchLiveUserData(LiveUserData.Request request) {
|
||||
var url = TIKTOK_URL_WEB + "api-live/user/room";
|
||||
@@ -136,11 +127,6 @@ public class TikTokLiveHttpClient implements LiveHttpClient {
|
||||
return liveUserDataMapper.map(json);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiveData.Response fetchLiveData(String roomId) {
|
||||
return fetchLiveData(new LiveData.Request(roomId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiveData.Response fetchLiveData(LiveData.Request request) {
|
||||
var url = TIKTOK_URL_WEBCAST + "room/info";
|
||||
@@ -175,20 +161,13 @@ public class TikTokLiveHttpClient implements LiveHttpClient {
|
||||
return liveDataMapper.map(json);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiveConnectionData.Response fetchLiveConnectionData(String roomId) {
|
||||
return fetchLiveConnectionData(new LiveConnectionData.Request(roomId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LiveConnectionData.Response fetchLiveConnectionData(LiveConnectionData.Request request) {
|
||||
HttpResponse<byte[]> credentialsResponse = getOptionalProxyResponse(request).orElseGet(()-> {
|
||||
SignServerResponse signServerResponse = getSignedUrl(request.getRoomId());
|
||||
return getWebsocketCredentialsResponse(signServerResponse.getSignedUrl());
|
||||
});
|
||||
HttpResponse<byte[]> credentialsResponse = getOptionalProxyResponse(request).orElseGet(()-> getStarterPayload(request.getRoomId()));
|
||||
|
||||
try {
|
||||
var optionalHeader = credentialsResponse.headers().firstValue("set-cookie");
|
||||
System.out.println(credentialsResponse.headers().map());
|
||||
var optionalHeader = credentialsResponse.headers().firstValue("x-set-tt-cookie");
|
||||
if (optionalHeader.isEmpty()) {
|
||||
throw new TikTokSignServerException("Sign server did not return the set-cookie header");
|
||||
}
|
||||
@@ -210,39 +189,21 @@ public class TikTokLiveHttpClient implements LiveHttpClient {
|
||||
}
|
||||
}
|
||||
|
||||
SignServerResponse getSignedUrl(String roomId) {
|
||||
var urlToSign = httpFactory
|
||||
.client(TikTokLiveHttpClient.TIKTOK_URL_WEBCAST + "im/fetch")
|
||||
.withParam("room_id", roomId)
|
||||
.build()
|
||||
.toUrl();
|
||||
HttpResponse<byte[]> getStarterPayload(String room_id) {
|
||||
HttpClientBuilder builder = httpFactory.client(TIKTOK_SIGN_API)
|
||||
.withParam("client", "ttlive-java")
|
||||
.withParam("uuc", "1")
|
||||
.withParam("room_id", room_id);
|
||||
|
||||
if (clientSettings.getApiKey() != null)
|
||||
builder.withParam("apiKey", clientSettings.getApiKey());
|
||||
|
||||
var optional = httpFactory
|
||||
.client(TikTokLiveHttpClient.TIKTOK_SIGN_API)
|
||||
.withParam("client", "ttlive-java")
|
||||
.withParam("uuc", "1")
|
||||
.withParam("url", urlToSign.toString())
|
||||
.build()
|
||||
.toJsonResponse();
|
||||
var optional = builder.build().toResponse();
|
||||
|
||||
if (optional.isEmpty()) {
|
||||
throw new TikTokSignServerException("Unable to sign url: " + urlToSign);
|
||||
}
|
||||
|
||||
var json = optional.get();
|
||||
return signServerResponseMapper.map(json);
|
||||
}
|
||||
|
||||
HttpResponse<byte[]> getWebsocketCredentialsResponse(String signedUrl) {
|
||||
var optionalResponse = httpFactory
|
||||
.clientEmpty(signedUrl)
|
||||
.build()
|
||||
.toResponse();
|
||||
if (optionalResponse.isEmpty()) {
|
||||
throw new TikTokSignServerException("Unable to get websocket connection credentials");
|
||||
}
|
||||
return optionalResponse.get();
|
||||
return optional.get();
|
||||
}
|
||||
|
||||
Optional<HttpResponse<byte[]>> getOptionalProxyResponse(LiveConnectionData.Request request) {
|
||||
@@ -250,9 +211,7 @@ public class TikTokLiveHttpClient implements LiveHttpClient {
|
||||
if (proxyClientSettings.isEnabled()) {
|
||||
while (proxyClientSettings.hasNext()) {
|
||||
try {
|
||||
SignServerResponse signServerResponse = getSignedUrl(request.getRoomId());
|
||||
HttpResponse<byte[]> credentialsResponse = getWebsocketCredentialsResponse(signServerResponse.getSignedUrl());
|
||||
clientSettings.getHttpSettings().getProxyClientSettings().rotate();
|
||||
HttpResponse<byte[]> credentialsResponse = getStarterPayload(request.getRoomId());
|
||||
return Optional.of(credentialsResponse);
|
||||
} catch (TikTokProxyRequestException | TikTokSignServerException ignored) {}
|
||||
}
|
||||
|
||||
@@ -67,16 +67,12 @@ public class HttpProxyClient extends HttpClient
|
||||
var request = prepareGetRequest();
|
||||
|
||||
var response = client.send(request, HttpResponse.BodyHandlers.ofByteArray());
|
||||
if (response.statusCode() != 200) {
|
||||
proxySettings.setLastSuccess(false);
|
||||
if (response.statusCode() != 200)
|
||||
continue;
|
||||
}
|
||||
proxySettings.setLastSuccess(true);
|
||||
return Optional.of(response);
|
||||
} catch (HttpConnectTimeoutException | ConnectException e) {
|
||||
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
|
||||
@@ -121,14 +117,12 @@ public class HttpProxyClient extends HttpClient
|
||||
|
||||
var response = createHttpResponse(body, toUrl(), responseInfo);
|
||||
|
||||
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);
|
||||
throw new TikTokProxyRequestException(e);
|
||||
} catch (Exception e) {
|
||||
throw new TikTokLiveRequestException(e);
|
||||
@@ -137,7 +131,7 @@ public class HttpProxyClient extends HttpClient
|
||||
throw new TikTokLiveRequestException("No more proxies available!");
|
||||
} catch (NoSuchAlgorithmException | MalformedURLException | KeyManagementException e) {
|
||||
// Should never be reached!
|
||||
System.out.println("handleSocksProxyRequest()! If you see this message, reach us on discord!");
|
||||
System.out.println("handleSocksProxyRequest: If you see this, message us on discord!");
|
||||
e.printStackTrace();
|
||||
return Optional.empty();
|
||||
} catch (TikTokLiveRequestException e) {
|
||||
|
||||
@@ -64,6 +64,10 @@ public class LiveDataMapper {
|
||||
default -> LiveData.LiveStatus.HostNotFound;
|
||||
};
|
||||
response.setLiveStatus(statusValue);
|
||||
} else if (data.has("prompts") && jsonObject.has("status_code") &&
|
||||
data.get("prompts").getAsString().isEmpty() && jsonObject.get("status_code").isJsonPrimitive()) {
|
||||
// 4003110 is age restriction code
|
||||
response.setAgeRestricted(jsonObject.get("status_code").getAsInt() == 4003110);
|
||||
} else {
|
||||
response.setLiveStatus(LiveData.LiveStatus.HostNotFound);
|
||||
}
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* 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.http.mappers;
|
||||
|
||||
import com.google.gson.JsonParser;
|
||||
import io.github.jwdeveloper.tiktok.data.requests.SignServerResponse;
|
||||
|
||||
public class SignServerResponseMapper {
|
||||
public SignServerResponse map(String json) {
|
||||
var parsedJson = JsonParser.parseString(json);
|
||||
var jsonObject = parsedJson.getAsJsonObject();
|
||||
|
||||
var signUrl = jsonObject.get("signedUrl").getAsString();
|
||||
var userAgent = jsonObject.get("User-Agent").getAsString();
|
||||
return new SignServerResponse(signUrl, userAgent);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user