Merge pull request #15 from jwdeveloper/develop-1-0-4

Changes:
   Generated new Gifts Json
   
   TikTokLive.isLiveOnline() check if live if online
   TikTokLive.isLiveOnlineAsync()
   
   TikTokLive.isHostNameValid() check if hostName is correct
   TikTokLive.isHostNameValidAsync()
This commit is contained in:
Jacek W
2023-11-10 22:24:53 +01:00
committed by GitHub
53 changed files with 29068 additions and 343 deletions

View File

@@ -23,12 +23,25 @@
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.http.TikTokLiveOnlineChecker;
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
import java.util.concurrent.CompletableFuture;
public class TikTokLive
{
public static LiveClientBuilder newClient(String hostName)
{
return new TikTokLiveClientBuilder(hostName);
}
public static boolean isLiveOnline(String hostName)
{
return new TikTokLiveOnlineChecker().isOnline(hostName);
}
public static CompletableFuture<Boolean> isLiveOnlineAsync(String hostName)
{
return new TikTokLiveOnlineChecker().isOnlineAsync(hostName);
}
}

View File

@@ -24,6 +24,7 @@ package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.data.events.*;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
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;
import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollEvent;
@@ -135,6 +136,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
}
}
public LiveClient build() {
@@ -148,7 +150,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
var requestFactory = new TikTokHttpRequestFactory(cookieJar);
var apiClient = new TikTokHttpClient(cookieJar, requestFactory);
var apiService = new TikTokApiService(apiClient, logger, clientSettings);
var giftManager = new TikTokGiftManager();
var giftManager = new TikTokGiftManager(logger);
var eventMapper = new TikTokGenericEventMapper();
var giftHandler = new TikTokGiftEventHandler(giftManager);
@@ -190,6 +192,12 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
return this;
}
@Override
public LiveClientBuilder onChestOpen(EventConsumer<TikTokChestEvent> event) {
tikTokEventHandler.subscribe(TikTokChestEvent.class, event);
return this;
}
public TikTokLiveClientBuilder onLinkMicFanTicket(
EventConsumer<TikTokLinkMicFanTicketEvent> event) {
tikTokEventHandler.subscribe(TikTokLinkMicFanTicketEvent.class, event);

View File

@@ -22,6 +22,8 @@
*/
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.messages.data.User;
import io.github.jwdeveloper.tiktok.models.ConnectionState;
import io.github.jwdeveloper.tiktok.live.LiveRoomInfo;
import lombok.Data;
@@ -29,15 +31,20 @@ import lombok.Data;
@Data
public class TikTokRoomInfo implements LiveRoomInfo
{
private String roomId;
private int likesCount;
private int viewersCount;
private String roomId;
private boolean ageRestricted;
private User host;
private Picture picture;
private String description;
private String hostName;
private ConnectionState connectionState = ConnectionState.DISCONNECTED;

View File

@@ -28,18 +28,22 @@ import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.live.GiftManager;
import sun.misc.Unsafe;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.logging.Logger;
public class TikTokGiftManager implements GiftManager {
private final Map<Integer, Gift> indexById;
private final Map<String, Gift> indexByName;
private final Logger logger;
public TikTokGiftManager() {
public TikTokGiftManager(Logger logger)
{
indexById = new HashMap<>();
indexByName = new HashMap<>();
this.logger = logger;
init();
}
@@ -66,6 +70,17 @@ public class TikTokGiftManager implements GiftManager {
field.set(enumInstance, name);
Arrays.stream(Gift.class.getSuperclass().getDeclaredFields()).toList().forEach(field1 ->
{
System.out.println(field1.getName()+" ");
});
field = Gift.class.getSuperclass().getDeclaredField("name");
field.setAccessible(true);
field.set(enumInstance,"dupa");
// EnumSet
field = Gift.class.getDeclaredField("diamondCost");
field.setAccessible(true);
field.set(enumInstance, diamondCost);

View File

@@ -25,6 +25,7 @@ package io.github.jwdeveloper.tiktok.handlers;
import io.github.jwdeveloper.tiktok.TikTokRoomInfo;
import io.github.jwdeveloper.tiktok.data.events.*;
import io.github.jwdeveloper.tiktok.data.events.common.TikTokEvent;
import io.github.jwdeveloper.tiktok.data.events.envelop.TikTokChestEvent;
import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollEndEvent;
import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollEvent;
import io.github.jwdeveloper.tiktok.data.events.poll.TikTokPollStartEvent;
@@ -37,12 +38,16 @@ import io.github.jwdeveloper.tiktok.data.events.social.TikTokJoinEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokLikeEvent;
import io.github.jwdeveloper.tiktok.data.events.social.TikTokShareEvent;
import io.github.jwdeveloper.tiktok.data.models.Text;
import io.github.jwdeveloper.tiktok.data.models.chest.Chest;
import io.github.jwdeveloper.tiktok.handlers.events.TikTokGiftEventHandler;
import io.github.jwdeveloper.tiktok.mappers.TikTokGenericEventMapper;
import io.github.jwdeveloper.tiktok.messages.enums.EnvelopeDisplay;
import io.github.jwdeveloper.tiktok.messages.webcast.*;
import io.github.jwdeveloper.tiktok.models.SocialTypes;
import lombok.SneakyThrows;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
public class TikTokMessageHandlerRegistration extends TikTokMessageHandler {
@@ -106,7 +111,7 @@ public class TikTokMessageHandlerRegistration extends TikTokMessageHandler {
registerMapping(WebcastOecLiveShoppingMessage.class, TikTokShopEvent.class);
registerMapping(WebcastImDeleteMessage.class, TikTokIMDeleteEvent.class);
registerMapping(WebcastQuestionNewMessage.class, TikTokQuestionEvent.class);
registerMapping(WebcastEnvelopeMessage.class, TikTokEnvelopeEvent.class);
registerMappings(WebcastEnvelopeMessage.class, this::handleEnvelop);
registerMapping(WebcastSubNotifyMessage.class, TikTokSubNotifyEvent.class);
registerMapping(WebcastEmoteChatMessage.class, TikTokEmoteEvent.class);
}
@@ -148,6 +153,7 @@ public class TikTokMessageHandlerRegistration extends TikTokMessageHandler {
@SneakyThrows
private TikTokEvent handleMemberMessage(byte[] msg) {
var message = WebcastMemberMessage.parseFrom(msg);
roomInfo.setViewersCount(message.getMemberCount());
return switch (message.getAction()) {
case JOINED -> new TikTokJoinEvent(message);
case SUBSCRIBED -> new TikTokSubscribeEvent(message);
@@ -187,5 +193,18 @@ public class TikTokMessageHandlerRegistration extends TikTokMessageHandler {
};
}
@SneakyThrows
private List<TikTokEvent> handleEnvelop(byte[] data) {
var msg = WebcastEnvelopeMessage.parseFrom(data);
if (msg.getDisplay() != EnvelopeDisplay.EnvelopeDisplayNew) {
return Collections.emptyList();
}
var totalDiamonds = msg.getEnvelopeInfo().getDiamondCount();
var totalUsers = msg.getEnvelopeInfo().getPeopleCount();
var chest = new Chest(totalDiamonds, totalUsers);
return List.of(new TikTokChestEvent(chest, msg));
}
}

View File

@@ -0,0 +1,4 @@
package io.github.jwdeveloper.tiktok.handlers.events;
public class TikTokChestEventHandler {
}

View File

@@ -47,6 +47,12 @@ public class TikTokGiftEventHandler {
giftsMessages = new HashMap<>();
}
@SneakyThrows
public List<TikTokEvent> handleGift(byte[] msg) {
var currentMessage = WebcastGiftMessage.parseFrom(msg);
return handleGift(currentMessage);
}
public List<TikTokEvent> handleGift(WebcastGiftMessage currentMessage)
{
var userId = currentMessage.getUser().getId();
@@ -81,11 +87,6 @@ public class TikTokGiftEventHandler {
return List.of();
}
@SneakyThrows
public List<TikTokEvent> handleGift(byte[] msg) {
var currentMessage = WebcastGiftMessage.parseFrom(msg);
return handleGift(currentMessage);
}
@@ -99,14 +100,17 @@ public class TikTokGiftEventHandler {
return new TikTokGiftComboEvent(gift, message, state);
}
private Gift getGiftObject(WebcastGiftMessage giftMessage) {
var gift = giftManager.findById((int) giftMessage.getGiftId());
private Gift getGiftObject(WebcastGiftMessage giftMessage)
{
var giftId = (int) giftMessage.getGiftId();
var gift = giftManager.findById(giftId);
if (gift == Gift.UNDEFINED) {
gift = giftManager.findByName(giftMessage.getGift().getName());
}
if (gift == Gift.UNDEFINED) {
if (gift == Gift.UNDEFINED)
{
gift = giftManager.registerGift(
(int) giftMessage.getGift().getId(),
giftId,
giftMessage.getGift().getName(),
giftMessage.getGift().getDiamondCount(),
Picture.map(giftMessage.getGift().getImage()));

View File

@@ -30,7 +30,6 @@ import io.github.jwdeveloper.tiktok.live.LiveRoomMeta;
import io.github.jwdeveloper.tiktok.mappers.LiveRoomMetaMapper;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastResponse;
import javax.script.ScriptEngineManager;
import java.util.HashMap;
import java.util.logging.Logger;
import java.util.regex.Pattern;
@@ -47,49 +46,34 @@ public class TikTokApiService {
}
public void updateSessionId()
{
if(clientSettings.getSessionId() == null)
{
public void updateSessionId() {
if (clientSettings.getSessionId() == null) {
return;
}
if(clientSettings.getSessionId().isEmpty())
{
return;
if (clientSettings.getSessionId().isEmpty()) {
return;
}
tiktokHttpClient.setSessionId(clientSettings.getSessionId());
}
public boolean sendMessage(String message, String sessionId) {
if (sessionId.isEmpty()) {
throw new TikTokLiveException("Session ID must not be Empty");
}
var roomId = clientSettings.getClientParameters().get("room_id");
if (roomId == null) {
throw new TikTokLiveException("Room ID must not be Empty");
}
logger.info("Sending message to chat");
try {
var params = new HashMap<String, Object>(clientSettings.getClientParameters());
params.put("content", message);
params.put("channel", "tiktok_web");
params.remove("cursor");
tiktokHttpClient.setSessionId(sessionId);
tiktokHttpClient.postMessageToChat(params);
return true;
} catch (Exception e) {
throw new TikTokLiveRequestException("Failed to fetch room id from WebCast, see stacktrace for more info.", e);
}
public String fetchRoomId(String userName) {
var roomId = fetchRoomIdFromTiktokApi(userName);
clientSettings.getClientParameters().put("room_id", roomId);
logger.info("RoomID -> " + roomId);
return roomId;
}
public String fetchRoomId(String userName) {
private String fetchRoomIdFromTikTokPage(String userName)
{
/* var roomId = RequestChain.<String>create()
.then(() -> fetchRoomIdFromTikTokPage(userName))
.then(() -> fetchRoomIdFromTiktokApi(userName))
.run();*/
logger.info("Fetching room ID");
String html;
try {
html = tiktokHttpClient.getLivestreamPage(userName);
}
catch (Exception e)
{
} catch (Exception e) {
throw new TikTokLiveRequestException("Failed to fetch room id from WebCast, see stacktrace for more info.", e);
}
@@ -112,22 +96,35 @@ public class TikTokApiService {
throw new TikTokLiveOfflineHostException("Unable to fetch room ID, live host could be offline or name is misspelled");
}
clientSettings.getClientParameters().put("room_id", id);
logger.info("RoomID -> " + id);
return id;
}
private String fetchRoomIdFromTiktokApi(String userName) {
var params = new HashMap<>(clientSettings.getClientParameters());
params.put("uniqueId", userName);
params.put("sourceType", 54);
var roomData = tiktokHttpClient.getJsonFromTikTokApi("api-live/user/room/", params);
var data = roomData.getAsJsonObject("data");
var user =data.getAsJsonObject("user");
var roomId = user.get("roomId").getAsString();
return roomId;
}
public LiveRoomMeta fetchRoomInfo() {
logger.info("Fetching RoomInfo");
try {
var response = tiktokHttpClient.getJObjectFromWebcastAPI("room/info/", clientSettings.getClientParameters());
var response = tiktokHttpClient.getJsonFromWebcastApi("room/info/", clientSettings.getClientParameters());
var mapper = new LiveRoomMetaMapper();
var liveRoomMeta = mapper.map(response);
logger.info("RoomInfo status -> " + liveRoomMeta.getStatus());
return liveRoomMeta;
} catch (Exception e)
{
} catch (Exception e) {
throw new TikTokLiveRequestException("Failed to fetch room info from WebCast, see stacktrace for more info.", e);
}
}
@@ -136,7 +133,7 @@ public class TikTokApiService {
logger.info("Fetching ClientData");
try {
var response = tiktokHttpClient.getSigningServerMessage("im/fetch/", clientSettings.getClientParameters());
var response = tiktokHttpClient.getSigningServerResponse("im/fetch/", clientSettings.getClientParameters());
clientSettings.getClientParameters().put("cursor", response.getCursor());
clientSettings.getClientParameters().put("internal_ext", response.getInternalExt());
return response;

View File

@@ -60,20 +60,22 @@ public class TikTokHttpClient {
return get;
}
public String postMessageToChat(Map<String,Object> parameters)
public JsonObject getJsonFromTikTokApi(String path, Map<String,Object> params)
{
var get = postRequest(Constants.TIKTOK_URL_WEBCAST + "room/chat/", parameters);
return get;
var get = getRequest(Constants.TIKTOK_URL_WEB + path, params);
var json = JsonParser.parseString(get);
var jsonObject = json.getAsJsonObject();
return jsonObject;
}
public JsonObject getJObjectFromWebcastAPI(String path, Map<String, Object> parameters) {
public JsonObject getJsonFromWebcastApi(String path, Map<String, Object> parameters) {
var get = getRequest(Constants.TIKTOK_URL_WEBCAST + path, parameters);
var json = JsonParser.parseString(get);
var jsonObject = json.getAsJsonObject();
return jsonObject;
}
public WebcastResponse getSigningServerMessage(String path, Map<String, Object> parameters) {
public WebcastResponse getSigningServerResponse(String path, Map<String, Object> parameters) {
var bytes = getSignRequest(Constants.TIKTOK_URL_WEBCAST + path, parameters);
try {
return WebcastResponse.parseFrom(bytes);

View File

@@ -59,22 +59,29 @@ public class TikTokHttpRequestFactory implements TikTokHttpRequest {
@SneakyThrows
public String get(String url) {
var uri = URI.create(url);
var request = HttpRequest.newBuilder().GET();
var requestBuilder = HttpRequest.newBuilder().GET();
for (var header : defaultHeaders.entrySet())
{
if(header.getKey().equals("Connection") || header.getKey().equals("Accept-Encoding"))
{
continue;
}
request.setHeader(header.getKey(), header.getValue());
requestBuilder.setHeader(header.getKey(), header.getValue());
}
if (query != null) {
var baseUri = uri.toString();
var requestUri = URI.create(baseUri + "?" + query);
request.uri(requestUri);
requestBuilder.uri(requestUri);
}
else
{
requestBuilder.uri(uri);
}
return getContent(request.build());
var result = requestBuilder.build();
return getContent(result);
}
@SneakyThrows

View File

@@ -0,0 +1,51 @@
package io.github.jwdeveloper.tiktok.http;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveRequestException;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Pattern;
public class TikTokLiveOnlineChecker
{
public CompletableFuture<Boolean> isOnlineAsync(String hostName) {
return CompletableFuture.supplyAsync(() -> isOnline(hostName));
}
public boolean isOnline(String hostName) {
var factory = new TikTokHttpRequestFactory(new TikTokCookieJar());
var url = getLiveUrl(hostName);
try {
var response = factory.get(url);
var titleContent = extractTitleContent(response);
return isTitleLiveOnline(titleContent);
} catch (Exception e)
{
throw new TikTokLiveRequestException("Unable to make check live online request",e);
}
}
private boolean isTitleLiveOnline(String title) {
return title.contains("is LIVE");
}
private String extractTitleContent(String html) {
var regex = "<title\\b[^>]*>(.*?)<\\/title>";
var pattern = Pattern.compile(regex);
var matcher = pattern.matcher(html);
if (matcher.find()) {
return matcher.group(1);
} else {
return "";
}
}
private String getLiveUrl(String hostName) {
var sb = new StringBuilder();
sb.append("https://www.tiktok.com/@");
sb.append(hostName);
sb.append("/live");
return sb.toString();
}
}

View File

@@ -46,6 +46,7 @@ public class TikTokGiftManagerTest {
var gifts = giftManager.getGifts();
var optional = gifts.stream().filter(r -> r == fakeGift).findFirst();
Assertions.assertTrue(optional.isPresent());
Assertions.assertNotNull(optional.get().name());
}
@Test

View File

@@ -34,6 +34,8 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import java.util.logging.Logger;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TikTokGiftEventHandlerTest {
@@ -43,7 +45,7 @@ class TikTokGiftEventHandlerTest {
@BeforeAll
public void before() {
var manager = new TikTokGiftManager();
var manager = new TikTokGiftManager(Logger.getLogger("x"));
manager.registerGift(123, "example", 123, new Picture("image.webp"));
handler = new TikTokGiftEventHandler(manager);
}

View File

@@ -84,21 +84,6 @@ public class TikTokApiServiceTest
verify(tiktokHttpClient, times(1)).setSessionId("validSessionId");
}
@Test
void sendMessage_EmptySessionId_ThrowsException() {
assertThrows(TikTokLiveException.class, () -> {
tikTokApiService.sendMessage("some message", "");
});
}
@Test
void sendMessage_NullRoomId_ThrowsException() {
when(clientSettings.getClientParameters()).thenReturn(new HashMap<>());
assertThrows(TikTokLiveException.class, () -> {
tikTokApiService.sendMessage("some message", "someSessionId");
});
}
// @Test
void fetchRoomId_ValidResponse_ReturnsRoomId() throws Exception {
@@ -128,7 +113,7 @@ public class TikTokApiServiceTest
var expectedLiveRoomMeta = new LiveRoomMeta(); // Assume LiveRoomMeta is a simple POJO
when(clientSettings.getClientParameters()).thenReturn(clientParameters);
when(tiktokHttpClient.getJObjectFromWebcastAPI(anyString(), any())).thenReturn(mockResponse);
when(tiktokHttpClient.getJsonFromWebcastApi(anyString(), any())).thenReturn(mockResponse);
when(new LiveRoomMetaMapper().map(mockResponse)).thenReturn(expectedLiveRoomMeta); // Assuming LiveRoomMetaMapper is a simple mapper class
LiveRoomMeta liveRoomMeta = tikTokApiService.fetchRoomInfo();
@@ -138,7 +123,7 @@ public class TikTokApiServiceTest
// @Test
void fetchRoomInfo_ExceptionThrown_ThrowsTikTokLiveRequestException() throws Exception {
when(tiktokHttpClient.getJObjectFromWebcastAPI(anyString(), any())).thenThrow(new Exception("some exception"));
when(tiktokHttpClient.getJsonFromWebcastApi(anyString(), any())).thenThrow(new Exception("some exception"));
assertThrows(TikTokLiveRequestException.class, () -> {
tikTokApiService.fetchRoomInfo();

View File

@@ -0,0 +1,20 @@
package io.github.jwdeveloper.tiktok.http;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class TikTokLiveOnlineCheckerTest {
private final String TARGET_USER = "bangbetmenygy";
@Test
public void shouldTestOnline() {
var sut = new TikTokLiveOnlineChecker();
var result = sut.isOnline(TARGET_USER);
Assertions.assertTrue(result);
}
}