mirror of
https://github.com/jwdeveloper/TikTokLiveJava.git
synced 2026-02-27 08:49:40 -05:00
@@ -48,7 +48,6 @@ public class Gift {
|
|||||||
this.properties = properties;
|
this.properties = properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Gift(int id, String name, int diamondCost, String pictureLink) {
|
public Gift(int id, String name, int diamondCost, String pictureLink) {
|
||||||
this(id, name, diamondCost, new Picture(pictureLink), new JsonObject());
|
this(id, name, diamondCost, new Picture(pictureLink), new JsonObject());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,9 +22,18 @@
|
|||||||
*/
|
*/
|
||||||
package io.github.jwdeveloper.tiktok.exceptions;
|
package io.github.jwdeveloper.tiktok.exceptions;
|
||||||
|
|
||||||
|
import io.github.jwdeveloper.tiktok.data.requests.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
public class TikTokLiveOfflineHostException extends TikTokLiveException
|
public class TikTokLiveOfflineHostException extends TikTokLiveException
|
||||||
{
|
{
|
||||||
public TikTokLiveOfflineHostException(String message) {
|
private final LiveUserData.Response userData;
|
||||||
|
private final LiveData.Response liveData;
|
||||||
|
|
||||||
|
public TikTokLiveOfflineHostException(String message, LiveUserData.Response userData, LiveData.Response liveData) {
|
||||||
super(message);
|
super(message);
|
||||||
|
this.userData = userData;
|
||||||
|
this.liveData = liveData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,9 +22,18 @@
|
|||||||
*/
|
*/
|
||||||
package io.github.jwdeveloper.tiktok.exceptions;
|
package io.github.jwdeveloper.tiktok.exceptions;
|
||||||
|
|
||||||
|
import io.github.jwdeveloper.tiktok.data.requests.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
public class TikTokLiveUnknownHostException extends TikTokLiveException
|
public class TikTokLiveUnknownHostException extends TikTokLiveException
|
||||||
{
|
{
|
||||||
public TikTokLiveUnknownHostException(String message) {
|
private final LiveUserData.Response userData;
|
||||||
|
private final LiveData.Response liveData;
|
||||||
|
|
||||||
|
public TikTokLiveUnknownHostException(String message, LiveUserData.Response userData, LiveData.Response liveData) {
|
||||||
super(message);
|
super(message);
|
||||||
|
this.userData = userData;
|
||||||
|
this.liveData = liveData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,11 +29,6 @@ import io.github.jwdeveloper.tiktok.data.requests.LiveUserData;
|
|||||||
|
|
||||||
public interface LiveHttpClient
|
public interface LiveHttpClient
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* @return {@link GiftsData.Response} list of gifts that are compiled and available on github
|
|
||||||
*/
|
|
||||||
GiftsData.Response fetchGiftsData();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {@link GiftsData.Response} list of gifts that are available in your region / livestream
|
* @return {@link GiftsData.Response} list of gifts that are available in your region / livestream
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,73 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2023-2024 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.listener;
|
|
||||||
|
|
||||||
import io.github.jwdeveloper.tiktok.live.LiveClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ListenersManager
|
|
||||||
* <p>
|
|
||||||
* TikTokEventListener is an alternative way of handing TikTok events.
|
|
||||||
* <p>
|
|
||||||
* {@code TikTokLive.newClient("someuser").addListener(listener);}
|
|
||||||
* <p>
|
|
||||||
* After registertion, all listeners are kept in Listener manager - {@link LiveClient#getListenersManager()}
|
|
||||||
* <p>
|
|
||||||
* Method in TikTokEventListener should meet requirements below to be detected
|
|
||||||
* <p>- @TikTokEventObserver annotation
|
|
||||||
* <p>- 2 parameters of (LiveClient, Class extending TikTokEvent)
|
|
||||||
* <pre>
|
|
||||||
* {@code
|
|
||||||
* public static class CustomListener
|
|
||||||
* {
|
|
||||||
* @TikTokEventObserver
|
|
||||||
* public void onError(LiveClient liveClient, TikTokErrorEvent event)
|
|
||||||
* {
|
|
||||||
* System.out.println(event.getException().getMessage());
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* @TikTokEventObserver
|
|
||||||
* public void onCommentMessage(LiveClient liveClient, TikTokCommentEvent event)
|
|
||||||
* {
|
|
||||||
* System.out.println(event.getText());
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* @TikTokEventObserver
|
|
||||||
* public void onGiftMessage(LiveClient liveClient, TikTokGiftMessageEvent event)
|
|
||||||
* {
|
|
||||||
* System.out.println(event.getGift().getDescription());
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* @TikTokEventObserver
|
|
||||||
* public void onAnyEvent(LiveClient liveClient, TikTokEvent event)
|
|
||||||
* {
|
|
||||||
* System.out.println(event.getClass().getSimpleName());
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = true, since = "1.8.1 (This interface is not longer needed, please remove it from your class) | Removing in 1.9.0")
|
|
||||||
public interface TikTokEventListener {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -22,12 +22,8 @@
|
|||||||
*/
|
*/
|
||||||
package io.github.jwdeveloper.tiktok.live;
|
package io.github.jwdeveloper.tiktok.live;
|
||||||
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import io.github.jwdeveloper.tiktok.data.models.Picture;
|
|
||||||
import io.github.jwdeveloper.tiktok.data.models.gifts.*;
|
import io.github.jwdeveloper.tiktok.data.models.gifts.*;
|
||||||
import io.github.jwdeveloper.tiktok.exceptions.TikTokLiveException;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|||||||
@@ -30,9 +30,8 @@ import io.github.jwdeveloper.tiktok.mappers.LiveMapper;
|
|||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public interface LiveClientBuilder extends EventsBuilder<LiveClientBuilder> {
|
public interface LiveClientBuilder extends EventsBuilder<LiveClientBuilder>
|
||||||
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is triggered after default mappings are registered
|
* This method is triggered after default mappings are registered
|
||||||
* It could be used to OVERRIDE behaviour of mappings and implement custom behaviour
|
* It could be used to OVERRIDE behaviour of mappings and implement custom behaviour
|
||||||
@@ -40,18 +39,15 @@ public interface LiveClientBuilder extends EventsBuilder<LiveClientBuilder> {
|
|||||||
* Be aware if for example you override WebcastGiftEvent, onGiftEvent() will not be working
|
* Be aware if for example you override WebcastGiftEvent, onGiftEvent() will not be working
|
||||||
*
|
*
|
||||||
* @param onCustomMappings lambda method
|
* @param onCustomMappings lambda method
|
||||||
* @return
|
* @return {@link LiveClientBuilder}
|
||||||
*/
|
*/
|
||||||
LiveClientBuilder mappings(Consumer<LiveMapper> onCustomMappings);
|
LiveClientBuilder mappings(Consumer<LiveMapper> onCustomMappings);
|
||||||
|
|
||||||
@Deprecated(forRemoval = true, since = "1.8.2 use `mappings` method instead")
|
|
||||||
LiveClientBuilder onMappings(Consumer<LiveMapper> onCustomMappings);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration of client settings
|
* Configuration of client settings
|
||||||
*
|
*
|
||||||
* @param onConfigure
|
* @param onConfigure Consumer for {@link LiveClientSettings}
|
||||||
* @return
|
* @return {@link LiveClientBuilder}
|
||||||
* @see LiveClientSettings
|
* @see LiveClientSettings
|
||||||
*/
|
*/
|
||||||
LiveClientBuilder configure(Consumer<LiveClientSettings> onConfigure);
|
LiveClientBuilder configure(Consumer<LiveClientSettings> onConfigure);
|
||||||
@@ -59,7 +55,7 @@ public interface LiveClientBuilder extends EventsBuilder<LiveClientBuilder> {
|
|||||||
/**
|
/**
|
||||||
* Adds events listener class, its fancy way to register events without using lamda method
|
* Adds events listener class, its fancy way to register events without using lamda method
|
||||||
*
|
*
|
||||||
* @return
|
* @return {@link LiveClientBuilder}
|
||||||
*/
|
*/
|
||||||
LiveClientBuilder addListener(Object listener);
|
LiveClientBuilder addListener(Object listener);
|
||||||
|
|
||||||
@@ -69,7 +65,7 @@ public interface LiveClientBuilder extends EventsBuilder<LiveClientBuilder> {
|
|||||||
* when the default implementation does not meet your needs
|
* when the default implementation does not meet your needs
|
||||||
*
|
*
|
||||||
* @param onCustomizeDependencies access to dependency container
|
* @param onCustomizeDependencies access to dependency container
|
||||||
* @return
|
* @return {@link LiveClientBuilder}
|
||||||
*/
|
*/
|
||||||
LiveClientBuilder customize(Consumer<DependanceContainerBuilder> onCustomizeDependencies);
|
LiveClientBuilder customize(Consumer<DependanceContainerBuilder> onCustomizeDependencies);
|
||||||
|
|
||||||
|
|||||||
@@ -22,20 +22,18 @@
|
|||||||
*/
|
*/
|
||||||
package io.github.jwdeveloper.tiktok;
|
package io.github.jwdeveloper.tiktok;
|
||||||
|
|
||||||
|
|
||||||
import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
|
import io.github.jwdeveloper.tiktok.data.settings.LiveClientSettings;
|
||||||
import io.github.jwdeveloper.tiktok.gifts.TikTokGiftsManager;
|
|
||||||
import io.github.jwdeveloper.tiktok.http.LiveHttpClient;
|
import io.github.jwdeveloper.tiktok.http.LiveHttpClient;
|
||||||
import io.github.jwdeveloper.tiktok.live.GiftsManager;
|
|
||||||
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
|
import io.github.jwdeveloper.tiktok.live.builder.LiveClientBuilder;
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public class TikTokLive {
|
public class TikTokLive
|
||||||
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Example: https://www.tiktok.com/@dostawcavideo - hostName would be 'dostawcavideo'
|
* Example: {@code https://www.tiktok.com/@dostawcavideo} - hostName would be 'dostawcavideo'
|
||||||
*
|
*
|
||||||
* @param hostName profile name of TikTok user could be found in profile link
|
* @param hostName profile name of TikTok user could be found in profile link
|
||||||
* @return LiveClientBuilder
|
* @return LiveClientBuilder
|
||||||
@@ -45,7 +43,7 @@ public class TikTokLive {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Example: https://www.tiktok.com/@dostawcavideo - hostName would be 'dostawcavideo'
|
* Example: {@code https://www.tiktok.com/@dostawcavideo} - hostName would be 'dostawcavideo'
|
||||||
*
|
*
|
||||||
* @param hostName profile name of TikTok user could be found in profile link
|
* @param hostName profile name of TikTok user could be found in profile link
|
||||||
* @return true if live is Online, false if is offline
|
* @return true if live is Online, false if is offline
|
||||||
@@ -55,17 +53,17 @@ public class TikTokLive {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Example: https://www.tiktok.com/@dostawcavideo - hostName would be 'dostawcavideo'
|
* Example: {@code https://www.tiktok.com/@dostawcavideo} - hostName would be 'dostawcavideo'
|
||||||
*
|
*
|
||||||
* @param hostName profile name of TikTok user could be found in profile link
|
* @param hostName profile name of TikTok user could be found in profile link
|
||||||
* @return true if live is Online, false if is offline
|
* @return {@link CompletableFuture} of true if live is Online, false if is offline
|
||||||
*/
|
*/
|
||||||
public static CompletableFuture<Boolean> isLiveOnlineAsync(String hostName) {
|
public static CompletableFuture<Boolean> isLiveOnlineAsync(String hostName) {
|
||||||
return CompletableFuture.supplyAsync(() -> isLiveOnline(hostName));
|
return CompletableFuture.supplyAsync(() -> isLiveOnline(hostName));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Example: https://www.tiktok.com/@dostawcavideo - hostName would be 'dostawcavideo'
|
* Example: {@code https://www.tiktok.com/@dostawcavideo} - hostName would be 'dostawcavideo'
|
||||||
*
|
*
|
||||||
* @param hostName profile name of TikTok user could be found in profile link
|
* @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
|
* @return true is hostName name is valid and exists, false if not
|
||||||
@@ -75,7 +73,7 @@ public class TikTokLive {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Example: https://www.tiktok.com/@dostawcavideo - hostName would be 'dostawcavideo'
|
* Example: {@code https://www.tiktok.com/@dostawcavideo} - hostName would be 'dostawcavideo'
|
||||||
*
|
*
|
||||||
* @param hostName profile name of TikTok user could be found in profile link
|
* @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
|
* @return true is hostName name is valid and exists, false if not
|
||||||
@@ -101,20 +99,4 @@ public class TikTokLive {
|
|||||||
public static LiveHttpClient requests() {
|
public static LiveHttpClient requests() {
|
||||||
return requests(liveClientSettings -> {});
|
return requests(liveClientSettings -> {});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GiftsManager giftsManager;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch gifts from endpoint and returns GiftManager
|
|
||||||
*
|
|
||||||
* @return GiftsManager
|
|
||||||
*/
|
|
||||||
public static GiftsManager gifts() {
|
|
||||||
if (giftsManager == null) {
|
|
||||||
synchronized (GiftsManager.class) {
|
|
||||||
giftsManager = new TikTokGiftsManager(requests().fetchGiftsData().getGifts());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return giftsManager;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -114,10 +114,10 @@ public class TikTokLiveClient implements LiveClient
|
|||||||
roomInfo.setRoomId(userData.getRoomId());
|
roomInfo.setRoomId(userData.getRoomId());
|
||||||
|
|
||||||
if (userData.getUserStatus() == LiveUserData.UserStatus.Offline)
|
if (userData.getUserStatus() == LiveUserData.UserStatus.Offline)
|
||||||
throw new TikTokLiveOfflineHostException("User is offline: " + roomInfo.getHostName());
|
throw new TikTokLiveOfflineHostException("User is offline: " + roomInfo.getHostName(), userData, null);
|
||||||
|
|
||||||
if (userData.getUserStatus() == LiveUserData.UserStatus.NotFound)
|
if (userData.getUserStatus() == LiveUserData.UserStatus.NotFound)
|
||||||
throw new TikTokLiveUnknownHostException("User not found: " + roomInfo.getHostName());
|
throw new TikTokLiveUnknownHostException("User not found: " + roomInfo.getHostName(), userData, null);
|
||||||
|
|
||||||
var liveDataRequest = new LiveData.Request(userData.getRoomId());
|
var liveDataRequest = new LiveData.Request(userData.getRoomId());
|
||||||
var liveData = httpClient.fetchLiveData(liveDataRequest);
|
var liveData = httpClient.fetchLiveData(liveDataRequest);
|
||||||
@@ -126,10 +126,10 @@ public class TikTokLiveClient implements LiveClient
|
|||||||
throw new TikTokLiveException("Livestream for " + roomInfo.getHostName() + " is 18+ or age restricted!");
|
throw new TikTokLiveException("Livestream for " + roomInfo.getHostName() + " is 18+ or age restricted!");
|
||||||
|
|
||||||
if (liveData.getLiveStatus() == LiveData.LiveStatus.HostNotFound)
|
if (liveData.getLiveStatus() == LiveData.LiveStatus.HostNotFound)
|
||||||
throw new TikTokLiveUnknownHostException("LiveStream for " + roomInfo.getHostName() + " could not be found.");
|
throw new TikTokLiveUnknownHostException("LiveStream for " + roomInfo.getHostName() + " could not be found.", userData, liveData);
|
||||||
|
|
||||||
if (liveData.getLiveStatus() == LiveData.LiveStatus.HostOffline)
|
if (liveData.getLiveStatus() == LiveData.LiveStatus.HostOffline)
|
||||||
throw new TikTokLiveOfflineHostException("LiveStream for " + roomInfo.getHostName() + " not found, is the Host offline?");
|
throw new TikTokLiveOfflineHostException("LiveStream for " + roomInfo.getHostName() + " not found, is the Host offline?", userData, liveData);
|
||||||
|
|
||||||
roomInfo.setTitle(liveData.getTitle());
|
roomInfo.setTitle(liveData.getTitle());
|
||||||
roomInfo.setViewersCount(liveData.getViewers());
|
roomInfo.setViewersCount(liveData.getViewers());
|
||||||
|
|||||||
@@ -70,12 +70,6 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public LiveClientBuilder onMappings(Consumer<LiveMapper> onCustomMappings) {
|
|
||||||
mappings(onCustomMappings);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TikTokLiveClientBuilder configure(Consumer<LiveClientSettings> onConfigure) {
|
public TikTokLiveClientBuilder configure(Consumer<LiveClientSettings> onConfigure) {
|
||||||
onConfigure.accept(clientSettings);
|
onConfigure.accept(clientSettings);
|
||||||
return this;
|
return this;
|
||||||
@@ -155,11 +149,7 @@ public class TikTokLiveClientBuilder implements LiveClientBuilder {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
//gifts
|
//gifts
|
||||||
if (clientSettings.isFetchGifts()) {
|
dependance.registerSingleton(GiftsManager.class, new TikTokGiftsManager(List.of()));
|
||||||
dependance.registerSingleton(GiftsManager.class, TikTokLive.gifts());
|
|
||||||
} else {
|
|
||||||
dependance.registerSingleton(GiftsManager.class, new TikTokGiftsManager(List.of()));
|
|
||||||
}
|
|
||||||
|
|
||||||
//mapper
|
//mapper
|
||||||
dependance.registerSingleton(TikTokGenericEventMapper.class);
|
dependance.registerSingleton(TikTokGenericEventMapper.class);
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ public class TikTokLiveHttpClient implements LiveHttpClient
|
|||||||
private static final String TIKTOK_SIGN_API = "https://tiktok.eulerstream.com/webcast/fetch";
|
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_WEB = "https://www.tiktok.com/";
|
||||||
private static final String TIKTOK_URL_WEBCAST = "https://webcast.tiktok.com/webcast/";
|
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 String TIKTOK_ROOM_GIFTS_URL = TIKTOK_URL_WEBCAST+"gift/list/";
|
public static final String TIKTOK_ROOM_GIFTS_URL = TIKTOK_URL_WEBCAST+"gift/list/";
|
||||||
public static final int TIKTOK_AGE_RESTRICTED_CODE = 4003110;
|
public static final int TIKTOK_AGE_RESTRICTED_CODE = 4003110;
|
||||||
|
|
||||||
@@ -95,31 +94,6 @@ public class TikTokLiveHttpClient implements LiveHttpClient
|
|||||||
return giftsDataMapper.mapRoom(json);
|
return giftsDataMapper.mapRoom(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
public GiftsData.Response fetchGiftsData() {
|
|
||||||
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
|
|
||||||
if (proxyClientSettings.isEnabled()) {
|
|
||||||
while (proxyClientSettings.hasNext()) {
|
|
||||||
try {
|
|
||||||
return getGiftsData();
|
|
||||||
} catch (TikTokProxyRequestException ignored) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return getGiftsData();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated(since = "1.8.6", forRemoval = true)
|
|
||||||
public GiftsData.Response getGiftsData() {
|
|
||||||
var result = httpFactory.client(TIKTOK_GIFTS_URL)
|
|
||||||
.build()
|
|
||||||
.toJsonResponse();
|
|
||||||
|
|
||||||
if (result.isFailure())
|
|
||||||
throw new TikTokLiveRequestException("Unable to fetch gifts information's - "+result);
|
|
||||||
|
|
||||||
var json = result.getContent();
|
|
||||||
return giftsDataMapper.map(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LiveUserData.Response fetchLiveUserData(LiveUserData.Request request) {
|
public LiveUserData.Response fetchLiveUserData(LiveUserData.Request request) {
|
||||||
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
|
var proxyClientSettings = clientSettings.getHttpSettings().getProxyClientSettings();
|
||||||
|
|||||||
@@ -35,11 +35,6 @@ import java.net.URI;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class TikTokLiveHttpOfflineClient implements LiveHttpClient {
|
public class TikTokLiveHttpOfflineClient implements LiveHttpClient {
|
||||||
@Override
|
|
||||||
public GiftsData.Response fetchGiftsData() {
|
|
||||||
return new GiftsData.Response("", List.of());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GiftsData.Response fetchRoomGiftsData(String room_id) {
|
public GiftsData.Response fetchRoomGiftsData(String room_id) {
|
||||||
return new GiftsData.Response("", List.of());
|
return new GiftsData.Response("", List.of());
|
||||||
|
|||||||
@@ -59,8 +59,7 @@ public class GiftsDataMapper {
|
|||||||
if (jsonObject.get("data") instanceof JsonObject data && data.get("gifts") instanceof JsonArray giftArray) {
|
if (jsonObject.get("data") instanceof JsonObject data && data.get("gifts") instanceof JsonArray giftArray) {
|
||||||
var gifts = new ArrayList<Gift>();
|
var gifts = new ArrayList<Gift>();
|
||||||
|
|
||||||
for(int i = 0; i < giftArray.size(); i++) {
|
for (JsonElement element : giftArray) {
|
||||||
JsonElement element = giftArray.get(i);
|
|
||||||
Gift gift = mapSingleRoomGift(element);
|
Gift gift = mapSingleRoomGift(element);
|
||||||
gifts.add(gift);
|
gifts.add(gift);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,8 +64,7 @@ public class Events_And_Gifts_Testing_Example
|
|||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
var gifts = TikTokLive.gifts();
|
var roseGift = client.getGiftManager().getByName("Rose");
|
||||||
var roseGift = gifts.getByName("Rose");
|
|
||||||
|
|
||||||
var fakeGift = TikTokGiftEvent.of(roseGift);
|
var fakeGift = TikTokGiftEvent.of(roseGift);
|
||||||
var fakeComboGift = TikTokGiftComboEvent.of(roseGift, 12, GiftComboStateType.Begin);
|
var fakeComboGift = TikTokGiftComboEvent.of(roseGift, 12, GiftComboStateType.Begin);
|
||||||
@@ -91,4 +90,4 @@ public class Events_And_Gifts_Testing_Example
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final String webcastLikeMessageBase64 = "SAFSBRABGKwCUgcIAhABGKwCCv8BUAFYAbABA7gBARCflqWWo8Ha72UgzoPZhd8xQrwBGg4gkAMKCSNmZmZmZmZmZiJ/qgF6CngIhYjjgPWJv7RgGhDwnZKm8J2TjvCdk47wk4WTsgIKa3lsbGVlaGFsbPICTE1TNHdMakFCQUFBQXUyX21LNEw4WGJYa3lNaUFvZzJUTnNmVjk5N09WM2tpQ3NCTkNjYWkwcWxIcUt0Q3B0UGU1N2RLYVhxb0xWSXoICwoQcG1fbXRfbXNnX3ZpZXdlchIXezA6dXNlcn0gbGlrZWQgdGhlIExJVkVIAQoSV2ViY2FzdExpa2VNZXNzYWdlGIaWvY+RhdjvZTABwAEBEA8Y+Voq7RCyAQYImwEQjwK6AQCCAgDyAkxNUzR3TGpBQkFBQUF1Ml9tSzRMOFhiWGt5TWlBb2cyVE5zZlY5OTdPVjNraUNzQk5DY2FpMHFsSHFLdENwdFBlNTdkS2FYcW9MVkl6ggTqCLoBnwUqBggBEAEYIFoNCgASCSNCMzQ3N0VGRoABDwgEEtgEEix3ZWJjYXN0LXZhL2dyYWRlX2JhZGdlX2ljb25fbGl0ZV9sdjE1X3YyLnBuZzrpAnNzbG9jYWw6Ly93ZWJjYXN0X2x5bnh2aWV3X3BvcHVwP3VzZV9zcGFyaz0xJnVybD1odHRwcyUzQSUyRiUyRmxmMTYtZ2Vja28tc291cmNlLnRpa3Rva2Nkbi5jb20lMkZvYmolMkZieXRlLWd1cmQtc291cmNlLXNnJTJGdGlrdG9rJTJGZmUlMkZsaXZlJTJGdGlrdG9rX2xpdmVfcmV2ZW51ZV91c2VyX2xldmVsX21haW4lMkZzcmMlMkZwYWdlcyUyRnByaXZpbGVnZSUyRnBhbmVsJTJGdGVtcGxhdGUuanMmaGlkZV9zdGF0dXNfYmFyPTAmaGlkZV9uYXZfYmFyPTEmY29udGFpbmVyX2JnX2NvbG9yPTAwMDAwMDAwJmhlaWdodD05MCUyNSZiZGhtX2JpZD10aWt0b2tfbGl2ZV9yZXZlbnVlX3VzZXJfbGV2ZWxfbWFpbiZ1c2VfZm9yZXN0PTEKXWh0dHBzOi8vcDE2LXdlYmNhc3QudGlrdG9rY2RuLmNvbS93ZWJjYXN0LXZhL2dyYWRlX2JhZGdlX2ljb25fbGl0ZV9sdjE1X3YyLnBuZ350cGx2LW9iai5pbWFnZQpdaHR0cHM6Ly9wMTktd2ViY2FzdC50aWt0b2tjZG4uY29tL3dlYmNhc3QtdmEvZ3JhZGVfYmFkZ2VfaWNvbl9saXRlX2x2MTVfdjIucG5nfnRwbHYtb2JqLmltYWdlIgIxNTIAOgYaAhIAIgBiDQoAEgkjQjM0NzdFRkZ4DqIBBggBEAEYIAgEEBQYCCABUukCc3Nsb2NhbDovL3dlYmNhc3RfbHlueHZpZXdfcG9wdXA/dXNlX3NwYXJrPTEmdXJsPWh0dHBzJTNBJTJGJTJGbGYxNi1nZWNrby1zb3VyY2UudGlrdG9rY2RuLmNvbSUyRm9iaiUyRmJ5dGUtZ3VyZC1zb3VyY2Utc2clMkZ0aWt0b2slMkZmZSUyRmxpdmUlMkZ0aWt0b2tfbGl2ZV9yZXZlbnVlX3VzZXJfbGV2ZWxfbWFpbiUyRnNyYyUyRnBhZ2VzJTJGcHJpdmlsZWdlJTJGcGFuZWwlMkZ0ZW1wbGF0ZS5qcyZoaWRlX3N0YXR1c19iYXI9MCZoaWRlX25hdl9iYXI9MSZjb250YWluZXJfYmdfY29sb3I9MDAwMDAwMDAmaGVpZ2h0PTkwJTI1JmJkaG1fYmlkPXRpa3Rva19saXZlX3JldmVudWVfdXNlcl9sZXZlbF9tYWluJnVzZV9mb3Jlc3Q9MVgBYk8qAjE1CgEyEhM3MTM4MzgxNzQ3MjkyNTQyNzU2GgEwIi5tb2NrX2ZpeF93aWR0aF90cmFuc3BhcmVudF83MTM4MzgxNzQ3MjkyNTQyNzU2CIWI44D1ib+0YBoQ8J2SpvCdk47wnZOO8JOFk0r1BhJBMTAweDEwMC90b3MtdXNlYXN0OC1hdnQtMDA2OC10eDIvNjY0NmM4NjZjMzI1MWEwOTY3NjhiYjY4OTUyODVjMzEK0gFodHRwczovL3AxOS1wdS1zaWduLXVzZWFzdDgudGlrdG9rY2RuLXVzLmNvbS90b3MtdXNlYXN0OC1hdnQtMDA2OC10eDIvNjY0NmM4NjZjMzI1MWEwOTY3NjhiYjY4OTUyODVjMzF+dHBsdi10aWt0b2stc2hyaW5rOjcyOjcyLndlYnA/bGszcz1hNWQ0ODA3OCZ4LWV4cGlyZXM9MTcwOTMxMjQwMCZ4LXNpZ25hdHVyZT1VMlNEbUk3Z3R5RW9rMlBlWFdmeTNsM1F6NlElM0QKyAFodHRwczovL3AxNi1wdS1zaWduLXVzZWFzdDgudGlrdG9rY2RuLXVzLmNvbS90b3MtdXNlYXN0OC1hdnQtMDA2OC10eDIvNjY0NmM4NjZjMzI1MWEwOTY3NjhiYjY4OTUyODVjMzF+YzVfMTAweDEwMC53ZWJwP2xrM3M9YTVkNDgwNzgmeC1leHBpcmVzPTE3MDkzMTI0MDAmeC1zaWduYXR1cmU9aWNWZEVZa0FnWkYlMkZ2WU5OTSUyRlVNMzE2eG9HdyUzRArGAWh0dHBzOi8vcDE5LXB1LXNpZ24tdXNlYXN0OC50aWt0b2tjZG4tdXMuY29tL3Rvcy11c2Vhc3Q4LWF2dC0wMDY4LXR4Mi82NjQ2Yzg2NmMzMjUxYTA5Njc2OGJiNjg5NTI4NWMzMX5jNV8xMDB4MTAwLndlYnA/bGszcz1hNWQ0ODA3OCZ4LWV4cGlyZXM9MTcwOTMxMjQwMCZ4LXNpZ25hdHVyZT1PQzdBQ3htQUklMkJsYlp4RkVuWktJT1RyRExGUSUzRArGAWh0dHBzOi8vcDE2LXB1LXNpZ24tdXNlYXN0OC50aWt0b2tjZG4tdXMuY29tL3Rvcy11c2Vhc3Q4LWF2dC0wMDY4LXR4Mi82NjQ2Yzg2NmMzMjUxYTA5Njc2OGJiNjg5NTI4NWMzMX5jNV8xMDB4MTAwLmpwZWc/bGszcz1hNWQ0ODA3OCZ4LWV4cGlyZXM9MTcwOTMxMjQwMCZ4LXNpZ25hdHVyZT02YUwlMkZNZWtOeHg5NXlvVTVLOTZON0xwRUlNdyUzRLICCmt5bGxlZWhhbGxCyQEIgojG1pKb0clgErwBChBwbV9tdF9tc2dfdmlld2VyEhd7MDp1c2VyfSBsaWtlZCB0aGUgTElWRRoOCgkjZmZmZmZmZmYgkAMifwgLqgF6CngIhYjjgPWJv7RgGhDwnZKm8J2TjvCdk47wk4WTsgIKa3lsbGVlaGFsbPICTE1TNHdMakFCQUFBQXUyX21LNEw4WGJYa3lNaUFvZzJUTnNmVjk5N09WM2tpQ3NCTkNjYWkwcWxIcUt0Q3B0UGU1N2RLYVhxb0xWSXo=";
|
private static final String webcastLikeMessageBase64 = "SAFSBRABGKwCUgcIAhABGKwCCv8BUAFYAbABA7gBARCflqWWo8Ha72UgzoPZhd8xQrwBGg4gkAMKCSNmZmZmZmZmZiJ/qgF6CngIhYjjgPWJv7RgGhDwnZKm8J2TjvCdk47wk4WTsgIKa3lsbGVlaGFsbPICTE1TNHdMakFCQUFBQXUyX21LNEw4WGJYa3lNaUFvZzJUTnNmVjk5N09WM2tpQ3NCTkNjYWkwcWxIcUt0Q3B0UGU1N2RLYVhxb0xWSXoICwoQcG1fbXRfbXNnX3ZpZXdlchIXezA6dXNlcn0gbGlrZWQgdGhlIExJVkVIAQoSV2ViY2FzdExpa2VNZXNzYWdlGIaWvY+RhdjvZTABwAEBEA8Y+Voq7RCyAQYImwEQjwK6AQCCAgDyAkxNUzR3TGpBQkFBQUF1Ml9tSzRMOFhiWGt5TWlBb2cyVE5zZlY5OTdPVjNraUNzQk5DY2FpMHFsSHFLdENwdFBlNTdkS2FYcW9MVkl6ggTqCLoBnwUqBggBEAEYIFoNCgASCSNCMzQ3N0VGRoABDwgEEtgEEix3ZWJjYXN0LXZhL2dyYWRlX2JhZGdlX2ljb25fbGl0ZV9sdjE1X3YyLnBuZzrpAnNzbG9jYWw6Ly93ZWJjYXN0X2x5bnh2aWV3X3BvcHVwP3VzZV9zcGFyaz0xJnVybD1odHRwcyUzQSUyRiUyRmxmMTYtZ2Vja28tc291cmNlLnRpa3Rva2Nkbi5jb20lMkZvYmolMkZieXRlLWd1cmQtc291cmNlLXNnJTJGdGlrdG9rJTJGZmUlMkZsaXZlJTJGdGlrdG9rX2xpdmVfcmV2ZW51ZV91c2VyX2xldmVsX21haW4lMkZzcmMlMkZwYWdlcyUyRnByaXZpbGVnZSUyRnBhbmVsJTJGdGVtcGxhdGUuanMmaGlkZV9zdGF0dXNfYmFyPTAmaGlkZV9uYXZfYmFyPTEmY29udGFpbmVyX2JnX2NvbG9yPTAwMDAwMDAwJmhlaWdodD05MCUyNSZiZGhtX2JpZD10aWt0b2tfbGl2ZV9yZXZlbnVlX3VzZXJfbGV2ZWxfbWFpbiZ1c2VfZm9yZXN0PTEKXWh0dHBzOi8vcDE2LXdlYmNhc3QudGlrdG9rY2RuLmNvbS93ZWJjYXN0LXZhL2dyYWRlX2JhZGdlX2ljb25fbGl0ZV9sdjE1X3YyLnBuZ350cGx2LW9iai5pbWFnZQpdaHR0cHM6Ly9wMTktd2ViY2FzdC50aWt0b2tjZG4uY29tL3dlYmNhc3QtdmEvZ3JhZGVfYmFkZ2VfaWNvbl9saXRlX2x2MTVfdjIucG5nfnRwbHYtb2JqLmltYWdlIgIxNTIAOgYaAhIAIgBiDQoAEgkjQjM0NzdFRkZ4DqIBBggBEAEYIAgEEBQYCCABUukCc3Nsb2NhbDovL3dlYmNhc3RfbHlueHZpZXdfcG9wdXA/dXNlX3NwYXJrPTEmdXJsPWh0dHBzJTNBJTJGJTJGbGYxNi1nZWNrby1zb3VyY2UudGlrdG9rY2RuLmNvbSUyRm9iaiUyRmJ5dGUtZ3VyZC1zb3VyY2Utc2clMkZ0aWt0b2slMkZmZSUyRmxpdmUlMkZ0aWt0b2tfbGl2ZV9yZXZlbnVlX3VzZXJfbGV2ZWxfbWFpbiUyRnNyYyUyRnBhZ2VzJTJGcHJpdmlsZWdlJTJGcGFuZWwlMkZ0ZW1wbGF0ZS5qcyZoaWRlX3N0YXR1c19iYXI9MCZoaWRlX25hdl9iYXI9MSZjb250YWluZXJfYmdfY29sb3I9MDAwMDAwMDAmaGVpZ2h0PTkwJTI1JmJkaG1fYmlkPXRpa3Rva19saXZlX3JldmVudWVfdXNlcl9sZXZlbF9tYWluJnVzZV9mb3Jlc3Q9MVgBYk8qAjE1CgEyEhM3MTM4MzgxNzQ3MjkyNTQyNzU2GgEwIi5tb2NrX2ZpeF93aWR0aF90cmFuc3BhcmVudF83MTM4MzgxNzQ3MjkyNTQyNzU2CIWI44D1ib+0YBoQ8J2SpvCdk47wnZOO8JOFk0r1BhJBMTAweDEwMC90b3MtdXNlYXN0OC1hdnQtMDA2OC10eDIvNjY0NmM4NjZjMzI1MWEwOTY3NjhiYjY4OTUyODVjMzEK0gFodHRwczovL3AxOS1wdS1zaWduLXVzZWFzdDgudGlrdG9rY2RuLXVzLmNvbS90b3MtdXNlYXN0OC1hdnQtMDA2OC10eDIvNjY0NmM4NjZjMzI1MWEwOTY3NjhiYjY4OTUyODVjMzF+dHBsdi10aWt0b2stc2hyaW5rOjcyOjcyLndlYnA/bGszcz1hNWQ0ODA3OCZ4LWV4cGlyZXM9MTcwOTMxMjQwMCZ4LXNpZ25hdHVyZT1VMlNEbUk3Z3R5RW9rMlBlWFdmeTNsM1F6NlElM0QKyAFodHRwczovL3AxNi1wdS1zaWduLXVzZWFzdDgudGlrdG9rY2RuLXVzLmNvbS90b3MtdXNlYXN0OC1hdnQtMDA2OC10eDIvNjY0NmM4NjZjMzI1MWEwOTY3NjhiYjY4OTUyODVjMzF+YzVfMTAweDEwMC53ZWJwP2xrM3M9YTVkNDgwNzgmeC1leHBpcmVzPTE3MDkzMTI0MDAmeC1zaWduYXR1cmU9aWNWZEVZa0FnWkYlMkZ2WU5OTSUyRlVNMzE2eG9HdyUzRArGAWh0dHBzOi8vcDE5LXB1LXNpZ24tdXNlYXN0OC50aWt0b2tjZG4tdXMuY29tL3Rvcy11c2Vhc3Q4LWF2dC0wMDY4LXR4Mi82NjQ2Yzg2NmMzMjUxYTA5Njc2OGJiNjg5NTI4NWMzMX5jNV8xMDB4MTAwLndlYnA/bGszcz1hNWQ0ODA3OCZ4LWV4cGlyZXM9MTcwOTMxMjQwMCZ4LXNpZ25hdHVyZT1PQzdBQ3htQUklMkJsYlp4RkVuWktJT1RyRExGUSUzRArGAWh0dHBzOi8vcDE2LXB1LXNpZ24tdXNlYXN0OC50aWt0b2tjZG4tdXMuY29tL3Rvcy11c2Vhc3Q4LWF2dC0wMDY4LXR4Mi82NjQ2Yzg2NmMzMjUxYTA5Njc2OGJiNjg5NTI4NWMzMX5jNV8xMDB4MTAwLmpwZWc/bGszcz1hNWQ0ODA3OCZ4LWV4cGlyZXM9MTcwOTMxMjQwMCZ4LXNpZ25hdHVyZT02YUwlMkZNZWtOeHg5NXlvVTVLOTZON0xwRUlNdyUzRLICCmt5bGxlZWhhbGxCyQEIgojG1pKb0clgErwBChBwbV9tdF9tc2dfdmlld2VyEhd7MDp1c2VyfSBsaWtlZCB0aGUgTElWRRoOCgkjZmZmZmZmZmYgkAMifwgLqgF6CngIhYjjgPWJv7RgGhDwnZKm8J2TjvCdk47wk4WTsgIKa3lsbGVlaGFsbPICTE1TNHdMakFCQUFBQXUyX21LNEw4WGJYa3lNaUFvZzJUTnNmVjk5N09WM2tpQ3NCTkNjYWkwcWxIcUt0Q3B0UGU1N2RLYVhxb0xWSXo=";
|
||||||
}
|
}
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2023-2024 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;
|
|
||||||
|
|
||||||
import io.github.jwdeveloper.tiktok.data.models.gifts.Gift;
|
|
||||||
|
|
||||||
public class GiftsExample {
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
var giftsManager = TikTokLive.gifts();
|
|
||||||
|
|
||||||
var giftsList = giftsManager.toList();
|
|
||||||
for (var gift : giftsList) {
|
|
||||||
System.out.println("Gift: " + gift);
|
|
||||||
}
|
|
||||||
|
|
||||||
var giftsMap = giftsManager.toMap();
|
|
||||||
for (var entry : giftsMap.entrySet()) {
|
|
||||||
System.out.println("GiftId: " + entry.getKey() + " Gift: " + entry.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println("total number of gifts: " + giftsManager.toList().size());
|
|
||||||
|
|
||||||
var giftRose = giftsManager.getById(5655);
|
|
||||||
var giftRoseByName = giftsManager.getByName("Rose");
|
|
||||||
var giftByFilter = giftsManager.getByFilter(e -> e.getDiamondCost() > 50);
|
|
||||||
|
|
||||||
var giftsByFilter = giftsManager.getManyByFilter(e -> e.getDiamondCost() > 100);
|
|
||||||
System.out.println("total number of gifts with cost higher then 100: " + giftsByFilter.size());
|
|
||||||
/**
|
|
||||||
* In case searched gift not exists getByName returns you Gift.UNDEFINED
|
|
||||||
*/
|
|
||||||
var undefiedGift = giftsManager.getByName("GIFT WITH WRONG NAME");
|
|
||||||
|
|
||||||
|
|
||||||
var customGift = new Gift(123213213, "Custom gift", 50, "https://images.pexels.com/photos/2071882/pexels-photo-2071882.jpeg?cs=srgb&dl=pexels-wojciech-kumpicki-2071882.jpg&fm=jpg");
|
|
||||||
giftsManager.attachGift(customGift);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user