Update gifts manager

This commit is contained in:
JW
2024-02-22 20:28:13 +01:00
parent a68eaba5a1
commit b809bb6cda
21 changed files with 308 additions and 309 deletions

View File

@@ -23,15 +23,19 @@
package io.github.jwdeveloper.tiktok;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftsManager;
import io.github.jwdeveloper.tiktok.http.LiveHttpClient;
import io.github.jwdeveloper.tiktok.live.GiftsManager;
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class TikTokLive {
/**
* Example: https://www.tiktok.com/@dostawcavideo - hostName would be 'dostawcavideo'
*
* @param hostName profile name of Tiktok user could be found in profile link
* @return LiveClientBuilder
*/
@@ -41,42 +45,42 @@ public class TikTokLive {
/**
* Example: https://www.tiktok.com/@dostawcavideo - hostName would be 'dostawcavideo'
*
* @param hostName profile name of Tiktok user could be found in profile link
* @return true if live is Online, false if is offline
*/
public static boolean isLiveOnline(String hostName)
{
public static boolean isLiveOnline(String hostName) {
return requests().fetchLiveUserData(hostName).isLiveOnline();
}
/**
* Example: https://www.tiktok.com/@dostawcavideo - hostName would be 'dostawcavideo'
*
* @param hostName profile name of Tiktok user could be found in profile link
* @return true if live is Online, false if is offline
*/
public static CompletableFuture<Boolean> isLiveOnlineAsync(String hostName)
{
return CompletableFuture.supplyAsync(()-> isLiveOnline(hostName));
public static CompletableFuture<Boolean> isLiveOnlineAsync(String hostName) {
return CompletableFuture.supplyAsync(() -> isLiveOnline(hostName));
}
/**
* Example: https://www.tiktok.com/@dostawcavideo - hostName would be 'dostawcavideo'
*
* @param hostName profile name of Tiktok user could be found in profile link
* @return true is hostName name is valid and exists, false if not
*/
public static boolean isHostNameValid(String hostName)
{
public static boolean isHostNameValid(String hostName) {
return requests().fetchLiveUserData(hostName).isHostNameValid();
}
/**
* Example: https://www.tiktok.com/@dostawcavideo - hostName would be 'dostawcavideo'
*
* @param hostName profile name of Tiktok user could be found in profile link
* @return true is hostName name is valid and exists, false if not
*/
public static CompletableFuture<Boolean> isHostNameValidAsync(String hostName)
{
return CompletableFuture.supplyAsync(()-> isHostNameValid(hostName));
public static CompletableFuture<Boolean> isHostNameValidAsync(String hostName) {
return CompletableFuture.supplyAsync(() -> isHostNameValid(hostName));
}
/**
@@ -87,4 +91,26 @@ public class TikTokLive {
public static LiveHttpClient requests() {
return new TikTokLiveHttpClient();
}
/**
* Fetch gifts from endpoint and returns GiftManager
*
* @return GiftsManager
*/
public static GiftsManager gifts()
{
return new TikTokGiftsManager(requests().fetchGiftsData().getGifts());
}
/**
* @param fetchGifts fetch gifts from internet or return empty giftManager
* @return
*/
public static GiftsManager gifts(boolean fetchGifts) {
if (fetchGifts) {
return gifts();
}
return new TikTokGiftsManager(List.of());
}
}

View File

@@ -33,10 +33,9 @@ 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.*;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager;
import io.github.jwdeveloper.tiktok.listener.ListenersManager;
import io.github.jwdeveloper.tiktok.listener.TikTokListenersManager;
import io.github.jwdeveloper.tiktok.live.GiftManager;
import io.github.jwdeveloper.tiktok.live.GiftsManager;
import io.github.jwdeveloper.tiktok.live.LiveClient;
import io.github.jwdeveloper.tiktok.live.LiveRoomInfo;
import io.github.jwdeveloper.tiktok.models.ConnectionState;
@@ -49,24 +48,24 @@ import java.util.logging.Logger;
public class TikTokLiveClient implements LiveClient {
private final TikTokRoomInfo liveRoomInfo;
private final TikTokGiftManager tikTokGiftManager;
private final TikTokLiveHttpClient httpClient;
private final SocketClient webSocketClient;
private final TikTokLiveEventHandler tikTokEventHandler;
private final LiveClientSettings clientSettings;
private final TikTokListenersManager listenersManager;
private final Logger logger;
private final GiftsManager giftsManager;
public TikTokLiveClient(TikTokRoomInfo tikTokLiveMeta,
public TikTokLiveClient(GiftsManager giftsManager,
TikTokRoomInfo tikTokLiveMeta,
TikTokLiveHttpClient tiktokHttpClient,
SocketClient webSocketClient,
TikTokGiftManager tikTokGiftManager,
TikTokLiveEventHandler tikTokEventHandler,
LiveClientSettings clientSettings,
TikTokListenersManager listenersManager,
Logger logger) {
this.giftsManager = giftsManager;
this.liveRoomInfo = tikTokLiveMeta;
this.tikTokGiftManager = tikTokGiftManager;
this.httpClient = tiktokHttpClient;
this.webSocketClient = webSocketClient;
this.tikTokEventHandler = tikTokEventHandler;
@@ -102,7 +101,8 @@ public class TikTokLiveClient implements LiveClient {
if (e instanceof TikTokLiveOfflineHostException && clientSettings.isRetryOnConnectionFailure()) {
try {
Thread.sleep(clientSettings.getRetryConnectionTimeout().toMillis());
} catch (Exception ignored) {}
} catch (Exception ignored) {
}
logger.info("Reconnecting");
tikTokEventHandler.publish(this, new TikTokReconnectingEvent());
this.connect();
@@ -121,29 +121,29 @@ public class TikTokLiveClient implements LiveClient {
}
setState(ConnectionState.CONNECTING);
tikTokEventHandler.publish(this,new TikTokConnectingEvent());
tikTokEventHandler.publish(this, new TikTokConnectingEvent());
var userDataRequest = new LiveUserData.Request(liveRoomInfo.getHostName());
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.getHostName());
throw new TikTokLiveOfflineHostException("User is offline: " + liveRoomInfo.getHostName());
if (userData.getUserStatus() == LiveUserData.UserStatus.NotFound)
throw new TikTokLiveOfflineHostException("User not found: "+liveRoomInfo.getHostName());
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!");
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.");
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?");
throw new TikTokLiveOfflineHostException("LiveStream for " + liveRoomInfo.getHostName() + " not found, is the Host offline?");
tikTokEventHandler.publish(this, new TikTokRoomDataResponseEvent(liveData));
@@ -158,7 +158,7 @@ public class TikTokLiveClient implements LiveClient {
if (preconnectEvent.isCancelConnection())
throw new TikTokLiveException("TikTokPreConnectionEvent cancelled connection!");
var liveConnectionRequest =new LiveConnectionData.Request(userData.getRoomId());
var liveConnectionRequest = new LiveConnectionData.Request(userData.getRoomId());
var liveConnectionData = httpClient.fetchLiveConnectionData(liveConnectionRequest);
webSocketClient.start(liveConnectionData, this);
@@ -183,6 +183,11 @@ public class TikTokLiveClient implements LiveClient {
tikTokEventHandler.publish(this, event);
}
@Override
public GiftsManager getGiftManager() {
return giftsManager;
}
public LiveRoomInfo getRoomInfo() {
return liveRoomInfo;
}
@@ -196,9 +201,4 @@ public class TikTokLiveClient implements LiveClient {
public Logger getLogger() {
return logger;
}
@Override
public GiftManager getGiftManager() {
return tikTokGiftManager;
}
}

View File

@@ -35,7 +35,7 @@ import io.github.jwdeveloper.tiktok.data.events.social.*;
import io.github.jwdeveloper.tiktok.data.events.websocket.*;
import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftManager;
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftsManager;
import io.github.jwdeveloper.tiktok.http.HttpClientFactory;
import io.github.jwdeveloper.tiktok.listener.*;
import io.github.jwdeveloper.tiktok.live.*;
@@ -64,7 +64,8 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
this.clientSettings.setHostName(userName);
this.tikTokEventHandler = new TikTokLiveEventHandler();
this.listeners = new ArrayList<>();
this.onCustomMappings = (e) -> {};
this.onCustomMappings = (e) -> {
};
}
public LiveClientBuilder onMapping(Consumer<TikTokMapper> onCustomMappings) {
@@ -108,33 +109,33 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
var listenerManager = new TikTokListenersManager(listeners, tikTokEventHandler);
var giftManager = new TikTokGiftManager(logger);
var eventsMapper = createMapper(giftManager, tiktokRoomInfo);
var messageHandler = new TikTokLiveMessageHandler(tikTokEventHandler, eventsMapper);
var httpClientFactory = new HttpClientFactory(clientSettings);
var tikTokLiveHttpClient = new TikTokLiveHttpClient(httpClientFactory, clientSettings);
var gifts = tikTokLiveHttpClient.getGiftsData().getGifts();
var giftsManager = new TikTokGiftsManager(gifts);
var eventsMapper = createMapper(giftsManager, tiktokRoomInfo);
var messageHandler = new TikTokLiveMessageHandler(tikTokEventHandler, eventsMapper);
var webSocketClient = new TikTokWebSocketClient(
clientSettings,
messageHandler,
tikTokEventHandler);
return new TikTokLiveClient(tiktokRoomInfo,
return new TikTokLiveClient(
giftsManager,
tiktokRoomInfo,
tikTokLiveHttpClient,
webSocketClient,
giftManager,
tikTokEventHandler,
clientSettings,
listenerManager,
logger);
}
public TikTokLiveMapper createMapper(GiftManager giftManager, TikTokRoomInfo roomInfo) {
/*
//
*/
public TikTokLiveMapper createMapper(GiftsManager giftsManager, TikTokRoomInfo roomInfo) {
var eventMapper = new TikTokGenericEventMapper();
@@ -142,7 +143,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
//ConnectionEvents events
var commonHandler = new TikTokCommonEventHandler();
var giftHandler = new TikTokGiftEventHandler(giftManager, roomInfo);
var giftHandler = new TikTokGiftEventHandler(giftsManager, roomInfo);
var roomInfoHandler = new TikTokRoomInfoEventHandler(roomInfo);
var socialHandler = new TikTokSocialMediaEventHandler(roomInfo);

View File

@@ -42,6 +42,7 @@ public class TikTokLiveHttpClient implements LiveHttpClient
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/";
public static final String TIKTOK_GIFTS_URL = "https://raw.githubusercontent.com/TikTok-LIVE-Private/GiftsGenerator/master/page/public/gifts.json";
public static final int TIKTOK_AGE_RESTRICTED_CODE = 4003110;
private final HttpClientFactory httpFactory;
@@ -77,8 +78,7 @@ public class TikTokLiveHttpClient implements LiveHttpClient
}
public GiftsData.Response getGiftsData() {
var url = TIKTOK_URL_WEBCAST + "gift/list/";
var result = httpFactory.client(url)
var result = httpFactory.client(TIKTOK_GIFTS_URL)
.build()
.toJsonResponse();

View File

@@ -1,73 +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.gifts;
import com.google.gson.JsonObject;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.live.GiftManager;
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(Logger logger)
{
indexById = new HashMap<>();
indexByName = new HashMap<>();
this.logger = logger;
init();
}
protected void init() {
for (var gift : Gift.getGifts()) {
indexById.put(gift.getId(), gift);
indexByName.put(gift.getName(), gift);
}
}
public Gift registerGift(int id, String name, int diamondCost, Picture picture, JsonObject properties) {
Gift gift = new Gift(id, name, diamondCost, picture, properties);
indexById.put(gift.getId(), gift);
indexByName.put(gift.getName(), gift);
return gift;
}
public Gift findById(int giftId) {
return indexById.getOrDefault(giftId, Gift.UNDEFINED);
}
public Gift findByName(String giftName) {
return indexByName.getOrDefault(giftName, Gift.UNDEFINED);
}
@Override
public List<Gift> getGifts() {
return indexById.values().stream().toList();
}
}

View File

@@ -0,0 +1,64 @@
package io.github.jwdeveloper.tiktok.gifts;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.live.GiftsManager;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class TikTokGiftsManager implements GiftsManager {
private final Map<Integer, Gift> giftsByIdIndex;
public TikTokGiftsManager(List<Gift> giftList) {
giftsByIdIndex = giftList.stream().collect(Collectors.toConcurrentMap(Gift::getId, e -> e));
}
public void attachGift(Gift gift) {
giftsByIdIndex.put(gift.getId(), gift);
}
public void attachGiftsList(List<Gift> gifts) {
gifts.forEach(this::attachGift);
}
public Gift getByName(String name) {
return getByFilter(e -> e.getName().equalsIgnoreCase(name));
}
public Gift getById(int giftId) {
if (!giftsByIdIndex.containsKey(giftId)) {
return Gift.UNDEFINED;
}
return giftsByIdIndex.get(giftId);
}
public Gift getByFilter(Predicate<Gift> filter) {
return giftsByIdIndex.values()
.stream()
.filter(filter)
.findFirst()
.orElseGet(() -> Gift.UNDEFINED);
}
@Override
public List<Gift> getManyByFilter(Predicate<Gift> filter) {
return giftsByIdIndex.values()
.stream()
.filter(filter)
.toList();
}
public List<Gift> toList() {
return giftsByIdIndex.values().stream().toList();
}
public Map<Integer, Gift> toMap() {
return Collections.unmodifiableMap(giftsByIdIndex);
}
}

View File

@@ -21,51 +21,34 @@
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.github.jwdeveloper.tiktok.http.mappers;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
import io.github.jwdeveloper.tiktok.data.requests.GiftsData;
import java.util.ArrayList;
public class GiftsDataMapper {
public GiftsData.Response map(String json) {
var parsedJson = JsonParser.parseString(json);
var jsonObject = parsedJson.getAsJsonObject();
if (!jsonObject.has("data")) {
return new GiftsData.Response(json, new ArrayList<>());
}
var dataElement = jsonObject.getAsJsonObject("data");
if (!dataElement.has("gifts")) {
return new GiftsData.Response(json, new ArrayList<>());
}
var gifts = dataElement.get("gifts").getAsJsonArray()
.asList()
.stream()
.map(this::mapSingleGift)
var gifts = jsonObject.entrySet()
.parallelStream()
.map(e -> mapSingleGift(e.getValue()))
.toList();
return new GiftsData.Response(json, gifts);
}
private GiftsData.GiftModel mapSingleGift(JsonElement jsonElement) {
var id = jsonElement.getAsJsonObject().get("id").getAsInt();
var name = jsonElement.getAsJsonObject().get("name").getAsString();
var diamondCost = jsonElement.getAsJsonObject().get("diamond_count").getAsInt();
var image = jsonElement.getAsJsonObject()
.get("image").getAsJsonObject()
.get("url_list").getAsJsonArray().get(0).getAsString();
private Gift mapSingleGift(JsonElement jsonElement) {
var jsonObject = jsonElement.getAsJsonObject();
if (image.endsWith(".webp")) {
image = image.replace(".webp", ".jpg");
}
var gift = new GiftsData.GiftModel();
gift.setId(id);
gift.setName(name);
gift.setDiamondCost(diamondCost);
gift.setImage(image);
return gift;
var id = jsonObject.get("id").getAsInt();
var name = jsonObject.get("name").getAsString();
var diamondCost = jsonObject.get("diamondCost").getAsInt();
var image =jsonObject.get("image").getAsString();
return new Gift(id, name, diamondCost, image, jsonObject);
}
}

View File

@@ -28,7 +28,7 @@ import io.github.jwdeveloper.tiktok.data.events.gift.*;
import io.github.jwdeveloper.tiktok.data.models.Picture;
import io.github.jwdeveloper.tiktok.data.models.gifts.*;
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
import io.github.jwdeveloper.tiktok.live.GiftManager;
import io.github.jwdeveloper.tiktok.live.GiftsManager;
import io.github.jwdeveloper.tiktok.mappers.TikTokMapperHelper;
import io.github.jwdeveloper.tiktok.mappers.data.MappingResult;
import io.github.jwdeveloper.tiktok.messages.webcast.WebcastGiftMessage;
@@ -38,14 +38,15 @@ import sun.misc.Unsafe;
import java.util.*;
public class TikTokGiftEventHandler {
private final GiftManager giftManager;
private final Map<Long, WebcastGiftMessage> giftsMessages;
private final TikTokRoomInfo tikTokRoomInfo;
public TikTokGiftEventHandler(GiftManager giftManager, TikTokRoomInfo tikTokRoomInfo) {
this.giftManager = giftManager;
private final GiftsManager giftsManager;
public TikTokGiftEventHandler(GiftsManager giftsManager, TikTokRoomInfo tikTokRoomInfo) {
giftsMessages = new HashMap<>();
this.tikTokRoomInfo = tikTokRoomInfo;
this.giftsManager = giftsManager;
}
@SneakyThrows
@@ -110,23 +111,39 @@ public class TikTokGiftEventHandler {
private Gift getGiftObject(WebcastGiftMessage giftMessage) {
var giftId = (int) giftMessage.getGiftId();
var gift = giftManager.findById(giftId);
var gift = giftsManager.getById(giftId);
if (gift == Gift.UNDEFINED)
gift = giftManager.findByName(giftMessage.getGift().getName());
gift = giftsManager.getByName(giftMessage.getGift().getName());
if (gift == Gift.UNDEFINED) {
gift = giftManager.registerGift(
giftId,
giftMessage.getGift().getName(),
giftMessage.getGift().getDiamondCount(),
Picture.map(giftMessage.getGift().getImage()));
gift = new Gift(giftId,
giftMessage.getGift().getName(),
giftMessage.getGift().getDiamondCount(),
Picture.map(giftMessage.getGift().getImage()));
giftsManager.attachGift(gift);
}
if (gift.getPicture().getLink().endsWith(".webp"))
{
updatePicture(gift, giftMessage);
}
return gift;
}
// TODO-kohlerpop1: I do not think this method is needed for any reason?
// TODO response:
/**
* Some generated gifts in JSON file contains .webp image format,
* that's bad since java by the defult is not supporing .webp and when URL is
* converted to Java.io.Image then image is null
*
* However, TikTok in GiftWebcast event always has image in .jpg format,
* so I take advantage of it and swap .webp url with .jpg url
*
*/
private void updatePicture(Gift gift, WebcastGiftMessage webcastGiftMessage) {
try {
var picture = Picture.map(webcastGiftMessage.getGift().getImage());